From 8d29c28d36da49570ef20126a4ec6aa55b20b030 Mon Sep 17 00:00:00 2001 From: Gurge44 Date: Sun, 1 Dec 2024 14:17:38 +0100 Subject: [PATCH] file-scoped namespaces in solution --- CustomRoles.cs | 937 ++- GameMode/AllInOneGameMode.cs | 269 +- GameMode/CTFManager.cs | 881 ++- GameMode/FFAManager.cs | 849 ++- GameMode/HideAndSeekRoles/Agent.cs | 71 +- GameMode/HideAndSeekRoles/Dasher.cs | 191 +- GameMode/HideAndSeekRoles/Detector.cs | 121 +- GameMode/HideAndSeekRoles/Fox.cs | 71 +- GameMode/HideAndSeekRoles/Hider.cs | 155 +- GameMode/HideAndSeekRoles/IHideAndSeekRole.cs | 33 +- GameMode/HideAndSeekRoles/Jet.cs | 179 +- GameMode/HideAndSeekRoles/Jumper.cs | 131 +- GameMode/HideAndSeekRoles/Locator.cs | 187 +- GameMode/HideAndSeekRoles/Seeker.cs | 117 +- GameMode/HideAndSeekRoles/Taskinator.cs | 105 +- GameMode/HideAndSeekRoles/Troll.cs | 71 +- GameMode/HideAndSeekRoles/Venter.cs | 105 +- GameMode/HnSManager.cs | 883 ++- GameMode/HotPotato.cs | 303 +- GameMode/MoveAndStop.cs | 751 +- GameMode/NaturalDisasters.cs | 1687 +++-- GameMode/RoomRush.cs | 805 ++- GameMode/SoloKombatManager.cs | 695 +- GameMode/SpeedrunManager.cs | 335 +- Modules/AFKDetector.cs | 247 +- Modules/AURoleOptions.cs | 205 +- Modules/Achievements.cs | 341 +- Modules/AddSteamID.cs | 33 +- Modules/AntiBlackout.cs | 341 +- Modules/BanManager.cs | 473 +- Modules/Camouflague.cs | 407 +- Modules/ChatBubbleShower.cs | 155 +- Modules/CheckAUVersion.cs | 29 +- Modules/ClientOptionItem.cs | 199 +- Modules/Cloud.cs | 225 +- Modules/CustomRoleSelector.cs | 1341 ++-- Modules/CustomRolesHelper.cs | 3443 +++++---- Modules/CustomRpcSender.cs | 621 +- Modules/CustomSounds.cs | 123 +- Modules/CustomTeamManager.cs | 423 +- Modules/CustomWinnerHolder.cs | 179 +- Modules/DebugModeManager.cs | 17 +- Modules/Debugger.cs | 533 +- Modules/DevManager.cs | 301 +- Modules/DisableDevice.cs | 341 +- Modules/DoorsReset.cs | 195 +- Modules/DoubleTrigger.cs | 135 +- Modules/EAC.cs | 1357 ++-- Modules/ErrorText.cs | 371 +- Modules/ExtendedPlayerControl.cs | 2915 ++++---- Modules/Extensions/CollectionExtensions.cs | 829 ++- Modules/Extensions/HazelExtensions.cs | 77 +- Modules/Extensions/IGameManagerEx.cs | 123 +- Modules/Extensions/ObjectHelper.cs | 63 +- .../GameOptionsSender/GameOptionsSender.cs | 201 +- .../NormalGameOptionsSender.cs | 51 +- .../PlayerGameOptionsSender.cs | 717 +- Modules/GameState.cs | 1017 ++- Modules/GhostRolesManager.cs | 289 +- Modules/GuessManager.cs | 2007 +++--- Modules/HashAuth.cs | 85 +- Modules/LateTask.cs | 107 +- Modules/LoadingScreen.cs | 181 +- Modules/LobbyNotifierForDiscord.cs | 251 +- Modules/LocateArrow.cs | 303 +- Modules/LogHandler/ILogHandler.cs | 19 +- Modules/LogHandler/LogHandler.cs | 55 +- Modules/MeetingTimeManager.cs | 169 +- Modules/MessageManager/ICustomWriter.cs | 37 +- Modules/MessageManager/WriterProxy.cs | 127 +- Modules/ModUpdater.cs | 495 +- Modules/NameColorManager.cs | 561 +- Modules/NameNotifyManager.cs | 149 +- Modules/OptionBackup/OptionBackupData.cs | 117 +- Modules/OptionBackup/OptionBackupValue.cs | 93 +- Modules/OptionHolder.cs | 5083 +++++++------ Modules/OptionItem/BooleanOptionItem.cs | 29 +- Modules/OptionItem/FloatOptionItem.cs | 51 +- Modules/OptionItem/IntegerOptionItem.cs | 51 +- Modules/OptionItem/OptionItem.cs | 665 +- Modules/OptionItem/PresetOptionItem.cs | 77 +- Modules/OptionItem/StringOptionItem.cs | 93 +- Modules/OptionItem/TextOptionItem.cs | 43 +- Modules/OptionItem/ValueRule.cs | 129 +- Modules/OptionSaver.cs | 189 +- Modules/OptionShower.cs | 189 +- Modules/RPC.cs | 2735 ++++--- Modules/Random/HashRandomWrapper.cs | 35 +- Modules/Random/IRandom.cs | 139 +- Modules/Random/MersenneTwister.cs | 209 +- Modules/Random/NetRandomWrapper.cs | 39 +- Modules/Random/Xorshift.cs | 53 +- Modules/RoleBlockManager.cs | 79 +- Modules/SimpleButton.cs | 169 +- Modules/SpamManager.cs | 779 +- Modules/Statistics.cs | 565 +- Modules/TargetArrow.cs | 327 +- Modules/TemplateManager.cs | 221 +- Modules/Translator.cs | 419 +- Modules/Utils.cs | 6394 ++++++++--------- Modules/Zoom.cs | 163 +- Patches/AirShipElectricalDoors.cs | 113 +- Patches/AirshipStatus.cs | 15 +- Patches/AnnouncementPatch.cs | 187 +- Patches/AprilFoolsPatch.cs | 273 +- Patches/ChatBubblePatch.cs | 45 +- Patches/ChatCommandPatch.cs | 4759 ++++++------ Patches/ChatControlPatch.cs | 467 +- Patches/CheckGameEndPatch.cs | 1863 +++-- Patches/ClientOptionsPatch.cs | 303 +- Patches/ControlPatch.cs | 689 +- Patches/CosmeticsPatch.cs | 17 +- Patches/CredentialsPatch.cs | 795 +- Patches/DeconSystemPatch.cs | 41 +- Patches/DisconnectPatch.cs | 17 +- Patches/DisconnectPenaltyPatch.cs | 29 +- Patches/DiscordPatch.cs | 61 +- Patches/DleksPatch.cs | 235 +- Patches/EHROnlySearch.cs | 67 +- Patches/EndGameManagerPatch.cs | 97 +- Patches/ExilePatch.cs | 397 +- Patches/GameManagerPatch.cs | 71 +- Patches/GameOptionsMenuPatch.cs | 2290 +++--- Patches/GameOptionsPatch.cs | 217 +- Patches/GameStartManagerPatch.cs | 997 ++- Patches/GuardAngelPatch.cs | 21 +- Patches/HashRandomPatch.cs | 53 +- Patches/HideBanButtonPatch.cs | 13 +- Patches/HudPatch.cs | 1711 +++-- Patches/HudSpritePatch.cs | 705 +- Patches/IntroPatch.cs | 1711 +++-- Patches/JoinGameButtonPatch.cs | 25 +- Patches/LadderPatch.cs | 121 +- Patches/LobbyListPatch.cs | 93 +- Patches/LobbyPatch.cs | 77 +- Patches/LogoAndStampPatch.cs | 129 +- Patches/MainMenuManagerPatch.cs | 377 +- Patches/MapPickerMenuPatch.cs | 255 +- Patches/MeetingHudPatch.cs | 2363 +++--- Patches/MovingPlatformBehaviourPatch.cs | 82 +- Patches/NoBlackoutPatch.cs | 13 +- Patches/NotificationPopperPatch.cs | 94 +- Patches/OnGameStartedPatch.cs | 2227 +++--- Patches/OneWayShadowsPatch.cs | 25 +- Patches/OutroPatch.cs | 907 ++- Patches/PetActionsPatch.cs | 318 +- Patches/PetsPatch.cs | 47 +- Patches/PhantomRolePatch.cs | 380 +- Patches/PlayerControlPatch.cs | 4017 ++++++----- Patches/PlayerJoinAndLeftPatch.cs | 957 ++- Patches/RandomSpawnPatch.cs | 423 +- Patches/RecomputeTaskPatch.cs | 37 +- Patches/RegionMenuPatch.cs | 60 +- Patches/SabotageButtonPatch.cs | 26 +- Patches/SabotageSystemPatch.cs | 678 +- Patches/ServerVersionPatch.cs | 41 +- Patches/ShipStatusPatch.cs | 1099 ++- Patches/TaskAdderPatch.cs | 157 +- Patches/TaskAssignPatch.cs | 517 +- Patches/TextBoxPatch.cs | 495 +- Patches/UsablesPatch.cs | 171 +- Patches/VoteBanSystemPatch.cs | 31 +- Roles/AddOns/Common/Allergic.cs | 165 +- Roles/AddOns/Common/AntiTP.cs | 17 +- Roles/AddOns/Common/Antidote.cs | 27 +- Roles/AddOns/Common/Asthmatic.cs | 243 +- Roles/AddOns/Common/Autopsy.cs | 17 +- Roles/AddOns/Common/Avanger.cs | 17 +- Roles/AddOns/Common/Bait.cs | 39 +- Roles/AddOns/Common/Bewilder.cs | 21 +- Roles/AddOns/Common/Brakar.cs | 17 +- Roles/AddOns/Common/Busy.cs | 23 +- Roles/AddOns/Common/Clumsy.cs | 37 +- Roles/AddOns/Common/Deadlined.cs | 113 +- Roles/AddOns/Common/Disco.cs | 79 +- Roles/AddOns/Common/Diseased.cs | 27 +- Roles/AddOns/Common/DoubleShot.cs | 81 +- Roles/AddOns/Common/DualPersonality.cs | 19 +- Roles/AddOns/Common/Dynamo.cs | 115 +- Roles/AddOns/Common/Energetic.cs | 17 +- Roles/AddOns/Common/Flash.cs | 21 +- Roles/AddOns/Common/Fool.cs | 17 +- Roles/AddOns/Common/Fragile.cs | 17 +- Roles/AddOns/Common/Giant.cs | 21 +- Roles/AddOns/Common/Glow.cs | 17 +- Roles/AddOns/Common/Gravestone.cs | 17 +- Roles/AddOns/Common/Guesser.cs | 29 +- Roles/AddOns/Common/Haste.cs | 17 +- Roles/AddOns/Common/Introvert.cs | 147 +- Roles/AddOns/Common/Lovers.cs | 275 +- Roles/AddOns/Common/Loyal.cs | 23 +- Roles/AddOns/Common/Lucky.cs | 21 +- Roles/AddOns/Common/Magnet.cs | 17 +- Roles/AddOns/Common/Messenger.cs | 19 +- Roles/AddOns/Common/Mischievous.cs | 17 +- Roles/AddOns/Common/Necroview.cs | 17 +- Roles/AddOns/Common/Oblivious.cs | 19 +- Roles/AddOns/Common/Onbound.cs | 23 +- Roles/AddOns/Common/Rascal.cs | 19 +- Roles/AddOns/Common/Reach.cs | 17 +- Roles/AddOns/Common/Rookie.cs | 17 +- Roles/AddOns/Common/Seer.cs | 17 +- Roles/AddOns/Common/Sleep.cs | 35 +- Roles/AddOns/Common/Sleuth.cs | 17 +- Roles/AddOns/Common/Sonar.cs | 63 +- Roles/AddOns/Common/Spurt.cs | 199 +- Roles/AddOns/Common/Stained.cs | 41 +- Roles/AddOns/Common/Sunglasses.cs | 21 +- Roles/AddOns/Common/Torch.cs | 25 +- Roles/AddOns/Common/Trainee.cs | 17 +- Roles/AddOns/Common/Trapper.cs | 21 +- Roles/AddOns/Common/Truant.cs | 21 +- Roles/AddOns/Common/Unlucky.cs | 45 +- Roles/AddOns/Common/Unreportable.cs | 17 +- Roles/AddOns/Common/Watcher.cs | 17 +- Roles/AddOns/Common/YouTuber.cs | 17 +- Roles/AddOns/Crewmate/Bloodlust.cs | 37 +- Roles/AddOns/Crewmate/Finder.cs | 39 +- Roles/AddOns/Crewmate/Lazy.cs | 23 +- Roles/AddOns/Crewmate/Nimble.cs | 31 +- Roles/AddOns/Crewmate/Noisy.cs | 29 +- Roles/AddOns/Crewmate/Physicist.cs | 31 +- Roles/AddOns/Crewmate/Stressed.cs | 427 +- Roles/AddOns/Crewmate/Workhorse.cs | 195 +- Roles/AddOns/GhostRoles/Bloodmoon.cs | 179 +- Roles/AddOns/GhostRoles/GA.cs | 55 +- Roles/AddOns/GhostRoles/Haunter.cs | 293 +- Roles/AddOns/GhostRoles/IGhostRole.cs | 17 +- Roles/AddOns/GhostRoles/Minion.cs | 57 +- Roles/AddOns/GhostRoles/Specter.cs | 89 +- Roles/AddOns/GhostRoles/Warden.cs | 107 +- Roles/AddOns/IAddon.cs | 11 +- Roles/AddOns/Impostor/Circumvent.cs | 173 +- Roles/AddOns/Impostor/Damocles.cs | 291 +- Roles/AddOns/Impostor/DeadlyQuota.cs | 19 +- Roles/AddOns/Impostor/Egoist.cs | 19 +- Roles/AddOns/Impostor/LastImpostor.cs | 119 +- Roles/AddOns/Impostor/Madmate.cs | 61 +- Roles/AddOns/Impostor/Mare.cs | 37 +- Roles/AddOns/Impostor/Mimic.cs | 19 +- Roles/AddOns/Impostor/Swift.cs | 17 +- Roles/AddOns/Impostor/Taskcounter.cs | 17 +- Roles/AddOns/Impostor/TicketsStealer.cs | 19 +- Roles/Crewmate/Addict.cs | 267 +- Roles/Crewmate/Adrenaline.cs | 199 +- Roles/Crewmate/Adventurer.cs | 829 ++- Roles/Crewmate/Aid.cs | 289 +- Roles/Crewmate/Alchemist.cs | 895 ++- Roles/Crewmate/Altruist.cs | 279 +- Roles/Crewmate/Analyst.cs | 297 +- Roles/Crewmate/Ankylosaurus.cs | 95 +- Roles/Crewmate/Autocrat.cs | 43 +- Roles/Crewmate/Bane.cs | 47 +- Roles/Crewmate/Beacon.cs | 151 +- Roles/Crewmate/Benefactor.cs | 289 +- Roles/Crewmate/Bloodhound.cs | 243 +- Roles/Crewmate/Bodyguard.cs | 121 +- Roles/Crewmate/CameraMan.cs | 199 +- Roles/Crewmate/CandleLighter.cs | 197 +- Roles/Crewmate/Car.cs | 199 +- Roles/Crewmate/Catcher.cs | 287 +- Roles/Crewmate/Chameleon.cs | 69 +- Roles/Crewmate/Chef.cs | 667 +- Roles/Crewmate/Clairvoyant.cs | 107 +- Roles/Crewmate/Cleanser.cs | 207 +- Roles/Crewmate/Convener.cs | 141 +- Roles/Crewmate/CopyCat.cs | 291 +- Roles/Crewmate/CrewmateVanillaRoles.cs | 99 +- Roles/Crewmate/Crusader.cs | 129 +- Roles/Crewmate/CyberStar.cs | 31 +- Roles/Crewmate/Dad.cs | 897 ++- Roles/Crewmate/Demolitionist.cs | 77 +- Roles/Crewmate/Deputy.cs | 159 +- Roles/Crewmate/Detective.cs | 97 +- Roles/Crewmate/Detour.cs | 31 +- Roles/Crewmate/Dictator.cs | 23 +- Roles/Crewmate/Divinator.cs | 189 +- Roles/Crewmate/Doctor.cs | 53 +- Roles/Crewmate/DonutDelivery.cs | 247 +- Roles/Crewmate/Doormaster.cs | 147 +- Roles/Crewmate/DoubleAgent.cs | 43 +- Roles/Crewmate/DovesOfNeace.cs | 171 +- Roles/Crewmate/Drainer.cs | 197 +- Roles/Crewmate/Druid.cs | 391 +- Roles/Crewmate/Electric.cs | 87 +- Roles/Crewmate/Enigma.cs | 923 ++- Roles/Crewmate/Escort.cs | 111 +- Roles/Crewmate/Express.cs | 97 +- Roles/Crewmate/Farseer.cs | 249 +- Roles/Crewmate/Gaulois.cs | 121 +- Roles/Crewmate/Goose.cs | 65 +- Roles/Crewmate/Grappler.cs | 127 +- Roles/Crewmate/Grenadier.cs | 271 +- Roles/Crewmate/Guardian.cs | 43 +- Roles/Crewmate/GuessManager.cs | 43 +- Roles/Crewmate/Inquirer.cs | 59 +- Roles/Crewmate/Insight.cs | 91 +- Roles/Crewmate/Jailer.cs | 205 +- Roles/Crewmate/Journalist.cs | 151 +- Roles/Crewmate/Judge.cs | 553 +- Roles/Crewmate/Leery.cs | 167 +- Roles/Crewmate/Lighter.cs | 267 +- Roles/Crewmate/Lookout.cs | 83 +- Roles/Crewmate/Luckey.cs | 63 +- Roles/Crewmate/Lyncher.cs | 273 +- Roles/Crewmate/Markseeker.cs | 79 +- Roles/Crewmate/Marshall.cs | 113 +- Roles/Crewmate/Mathematician.cs | 131 +- Roles/Crewmate/Mayor.cs | 153 +- Roles/Crewmate/Medic.cs | 567 +- Roles/Crewmate/Mediumshiper.cs | 191 +- Roles/Crewmate/Merchant.cs | 295 +- Roles/Crewmate/Mole.cs | 85 +- Roles/Crewmate/Monarch.cs | 145 +- Roles/Crewmate/Monitor.cs | 51 +- Roles/Crewmate/Mortician.cs | 145 +- Roles/Crewmate/Needy.cs | 23 +- Roles/Crewmate/Negotiator.cs | 245 +- Roles/Crewmate/NiceEraser.cs | 157 +- Roles/Crewmate/NiceGuesser.cs | 43 +- Roles/Crewmate/NiceHacker.cs | 387 +- Roles/Crewmate/NiceSwapper.cs | 563 +- Roles/Crewmate/Nightmare.cs | 41 +- Roles/Crewmate/Observer.cs | 23 +- Roles/Crewmate/Oracle.cs | 209 +- Roles/Crewmate/Oxyman.cs | 369 +- Roles/Crewmate/Paranoia.cs | 143 +- Roles/Crewmate/ParityCop.cs | 727 +- Roles/Crewmate/Perceiver.cs | 127 +- Roles/Crewmate/Philantropist.cs | 7 +- Roles/Crewmate/President.cs | 439 +- Roles/Crewmate/Psychic.cs | 257 +- Roles/Crewmate/Rabbit.cs | 173 +- Roles/Crewmate/Randomizer.cs | 1469 ++-- Roles/Crewmate/Rhapsode.cs | 231 +- Roles/Crewmate/Ricochet.cs | 191 +- Roles/Crewmate/SabotageMaster.cs | 463 +- Roles/Crewmate/Safeguard.cs | 131 +- Roles/Crewmate/Scout.cs | 313 +- Roles/Crewmate/SecurityGuard.cs | 205 +- Roles/Crewmate/Sentinel.cs | 369 +- Roles/Crewmate/Sentry.cs | 581 +- Roles/Crewmate/Sheriff.cs | 303 +- Roles/Crewmate/Shiftguard.cs | 23 +- Roles/Crewmate/Snitch.cs | 301 +- Roles/Crewmate/Socialite.cs | 217 +- Roles/Crewmate/Soothsayer.cs | 81 +- Roles/Crewmate/SpeedBooster.cs | 57 +- Roles/Crewmate/Speedrunner.cs | 151 +- Roles/Crewmate/Spiritualist.cs | 245 +- Roles/Crewmate/Spy.cs | 217 +- Roles/Crewmate/SuperStar.cs | 45 +- Roles/Crewmate/SwordsMan.cs | 157 +- Roles/Crewmate/TaskManager.cs | 49 +- Roles/Crewmate/Telekinetic.cs | 391 +- Roles/Crewmate/Tether.cs | 255 +- Roles/Crewmate/TimeManager.cs | 109 +- Roles/Crewmate/TimeMaster.cs | 251 +- Roles/Crewmate/ToiletMaster.cs | 563 +- Roles/Crewmate/Tornado.cs | 315 +- Roles/Crewmate/Tracefinder.cs | 169 +- Roles/Crewmate/Transmitter.cs | 41 +- Roles/Crewmate/Transporter.cs | 85 +- Roles/Crewmate/Tunneler.cs | 77 +- Roles/Crewmate/Ventguard.cs | 149 +- Roles/Crewmate/Veteran.cs | 245 +- Roles/Crewmate/Whisperer.cs | 419 +- Roles/Crewmate/Witness.cs | 113 +- Roles/Crewmate/Wizard.cs | 547 +- Roles/IVanillaSettingHolder.cs | 11 +- Roles/Impostor/Abyssbringer.cs | 447 +- Roles/Impostor/AntiAdminer.cs | 589 +- Roles/Impostor/Assassin.cs | 313 +- Roles/Impostor/Assumer.cs | 139 +- Roles/Impostor/Augmenter.cs | 85 +- Roles/Impostor/BallLightning.cs | 375 +- Roles/Impostor/Bard.cs | 59 +- Roles/Impostor/Blackmailer.cs | 199 +- Roles/Impostor/Bomber.cs | 327 +- Roles/Impostor/BoobyTrap.cs | 149 +- Roles/Impostor/BountyHunter.cs | 395 +- Roles/Impostor/Camouflager.cs | 209 +- Roles/Impostor/Cantankerous.cs | 109 +- Roles/Impostor/Capitalism.cs | 127 +- Roles/Impostor/Changeling.cs | 239 +- Roles/Impostor/Chronomancer.cs | 291 +- Roles/Impostor/Cleaner.cs | 189 +- Roles/Impostor/Commander.cs | 469 +- Roles/Impostor/Consort.cs | 97 +- Roles/Impostor/Councillor.cs | 595 +- Roles/Impostor/Crewpostor.cs | 211 +- Roles/Impostor/CursedWolf.cs | 165 +- Roles/Impostor/Dazzler.cs | 175 +- Roles/Impostor/Deathpact.cs | 575 +- Roles/Impostor/Devourer.cs | 261 +- Roles/Impostor/Disperser.cs | 199 +- Roles/Impostor/Duellist.cs | 151 +- Roles/Impostor/Echo.cs | 245 +- Roles/Impostor/Eraser.cs | 247 +- Roles/Impostor/Escapee.cs | 147 +- Roles/Impostor/EvilDiviner.cs | 227 +- Roles/Impostor/EvilGuesser.cs | 43 +- Roles/Impostor/EvilTracker.cs | 423 +- Roles/Impostor/FireWorks.cs | 403 +- Roles/Impostor/Framer.cs | 85 +- Roles/Impostor/Freezer.cs | 99 +- Roles/Impostor/Gambler.cs | 571 +- Roles/Impostor/Gangster.cs | 215 +- Roles/Impostor/Generator.cs | 315 +- Roles/Impostor/Godfather.cs | 51 +- Roles/Impostor/Greedier.cs | 229 +- Roles/Impostor/Hacker.cs | 181 +- Roles/Impostor/Hangman.cs | 157 +- Roles/Impostor/Hitman.cs | 217 +- Roles/Impostor/Hypnotist.cs | 195 +- Roles/Impostor/ImperiusCurse.cs | 103 +- Roles/Impostor/ImpostorVanillaRoles.cs | 51 +- Roles/Impostor/Inhibitor.cs | 101 +- Roles/Impostor/Kamikaze.cs | 159 +- Roles/Impostor/Kidnapper.cs | 67 +- Roles/Impostor/Librarian.cs | 407 +- Roles/Impostor/Lurker.cs | 91 +- Roles/Impostor/Mafia.cs | 387 +- Roles/Impostor/Mafioso.cs | 515 +- Roles/Impostor/Mastermind.cs | 311 +- Roles/Impostor/Miner.cs | 131 +- Roles/Impostor/Minimalism.cs | 115 +- Roles/Impostor/Morphling.cs | 89 +- Roles/Impostor/Nullifier.cs | 159 +- Roles/Impostor/Occultist.cs | 305 +- Roles/Impostor/OverKiller.cs | 159 +- Roles/Impostor/Overheat.cs | 267 +- Roles/Impostor/Parasite.cs | 107 +- Roles/Impostor/Penguin.cs | 723 +- Roles/Impostor/Puppeteer.cs | 393 +- Roles/Impostor/QuickShooter.cs | 261 +- Roles/Impostor/Refugee.cs | 53 +- Roles/Impostor/RiftMaker.cs | 305 +- Roles/Impostor/Saboteur.cs | 101 +- Roles/Impostor/Sans.cs | 193 +- Roles/Impostor/Sapper.cs | 271 +- Roles/Impostor/Scavenger.cs | 109 +- Roles/Impostor/SerialKiller.cs | 171 +- Roles/Impostor/Silencer.cs | 159 +- Roles/Impostor/Sniper.cs | 633 +- Roles/Impostor/Stealth.cs | 243 +- Roles/Impostor/Swapster.cs | 93 +- Roles/Impostor/Swiftclaw.cs | 153 +- Roles/Impostor/Swooper.cs | 549 +- Roles/Impostor/TimeThief.cs | 107 +- Roles/Impostor/Trickster.cs | 23 +- Roles/Impostor/Twister.cs | 199 +- Roles/Impostor/Underdog.cs | 67 +- Roles/Impostor/Undertaker.cs | 49 +- Roles/Impostor/Vampire.cs | 283 +- Roles/Impostor/Ventriloquist.cs | 59 +- Roles/Impostor/Vindicator.cs | 33 +- Roles/Impostor/Visionary.cs | 117 +- Roles/Impostor/Warlock.cs | 561 +- Roles/Impostor/Wasp.cs | 409 +- Roles/Impostor/Wildling.cs | 335 +- Roles/Impostor/Witch.cs | 467 +- Roles/Impostor/YinYanger.cs | 253 +- Roles/Impostor/Zombie.cs | 135 +- Roles/Neutral/Agitater.cs | 437 +- Roles/Neutral/Amnesiac.cs | 473 +- Roles/Neutral/Arsonist.cs | 347 +- Roles/Neutral/Backstabber.cs | 177 +- Roles/Neutral/Bandit.cs | 371 +- Roles/Neutral/Bargainer.cs | 937 ++- Roles/Neutral/Beehive.cs | 265 +- Roles/Neutral/BloodKnight.cs | 49 +- Roles/Neutral/Bubble.cs | 315 +- Roles/Neutral/Chemist.cs | 1329 ++-- Roles/Neutral/Cherokious.cs | 79 +- Roles/Neutral/Collector.cs | 193 +- Roles/Neutral/Curser.cs | 221 +- Roles/Neutral/DarkHide.cs | 219 +- Roles/Neutral/Doomsayer.cs | 191 +- Roles/Neutral/Doppelganger.cs | 515 +- Roles/Neutral/Eclipse.cs | 123 +- Roles/Neutral/Enderman.cs | 187 +- Roles/Neutral/Evolver.cs | 575 +- Roles/Neutral/Executioner.cs | 319 +- Roles/Neutral/FFF.cs | 277 +- Roles/Neutral/Gamer.cs | 331 +- Roles/Neutral/Gaslighter.cs | 369 +- Roles/Neutral/Glitch.cs | 555 +- Roles/Neutral/God.cs | 57 +- Roles/Neutral/HeadHunter.cs | 311 +- Roles/Neutral/HexMaster.cs | 33 +- Roles/Neutral/Hookshot.cs | 273 +- Roles/Neutral/Imitator.cs | 57 +- Roles/Neutral/Impartial.cs | 293 +- Roles/Neutral/Innocent.cs | 93 +- Roles/Neutral/Jackal.cs | 443 +- Roles/Neutral/Jester.cs | 103 +- Roles/Neutral/Jinx.cs | 55 +- Roles/Neutral/Juggernaut.cs | 55 +- Roles/Neutral/Lawyer.cs | 333 +- Roles/Neutral/Magician.cs | 805 ++- Roles/Neutral/Mario.cs | 129 +- Roles/Neutral/Maverick.cs | 137 +- Roles/Neutral/Medusa.cs | 63 +- Roles/Neutral/Mycologist.cs | 319 +- Roles/Neutral/NSerialKiller.cs | 73 +- Roles/Neutral/NecroGuesser.cs | 47 +- Roles/Neutral/Necromancer.cs | 405 +- Roles/Neutral/Nonplus.cs | 149 +- Roles/Neutral/NoteKiller.cs | 349 +- Roles/Neutral/Opportunist.cs | 85 +- Roles/Neutral/Patroller.cs | 267 +- Roles/Neutral/Pelican.cs | 505 +- Roles/Neutral/Phantasm.cs | 37 +- Roles/Neutral/Pickpocket.cs | 89 +- Roles/Neutral/PlagueBearer.cs | 309 +- Roles/Neutral/PlagueDoctor.cs | 621 +- Roles/Neutral/Poisoner.cs | 55 +- Roles/Neutral/Postman.cs | 303 +- Roles/Neutral/Predator.cs | 267 +- Roles/Neutral/Provocateur.cs | 105 +- Roles/Neutral/Pursuer.cs | 199 +- Roles/Neutral/Pyromaniac.cs | 139 +- Roles/Neutral/QuizMaster.cs | 617 +- Roles/Neutral/Reckless.cs | 55 +- Roles/Neutral/Revolutionist.cs | 389 +- Roles/Neutral/Ritualist.cs | 57 +- Roles/Neutral/Rogue.cs | 653 +- Roles/Neutral/Romantic.cs | 755 +- Roles/Neutral/RouleteGrandeur.cs | 313 +- Roles/Neutral/Samurai.cs | 237 +- Roles/Neutral/SchrodingersCat.cs | 73 +- Roles/Neutral/Shifter.cs | 247 +- Roles/Neutral/Simon.cs | 389 +- Roles/Neutral/SoulHunter.cs | 469 +- Roles/Neutral/Spiritcaller.cs | 337 +- Roles/Neutral/Sprayer.cs | 357 +- Roles/Neutral/Succubus.cs | 251 +- Roles/Neutral/Sunnyboy.cs | 39 +- Roles/Neutral/Tank.cs | 125 +- Roles/Neutral/Technician.cs | 389 +- Roles/Neutral/Terrorist.cs | 33 +- Roles/Neutral/Tiger.cs | 285 +- Roles/Neutral/Totocalcio.cs | 317 +- Roles/Neutral/Traitor.cs | 109 +- Roles/Neutral/Tremor.cs | 267 +- Roles/Neutral/Vengeance.cs | 237 +- Roles/Neutral/Virus.cs | 339 +- Roles/Neutral/Vortex.cs | 125 +- Roles/Neutral/Vulture.cs | 321 +- Roles/Neutral/WeaponMaster.cs | 455 +- Roles/Neutral/Weatherman.cs | 151 +- Roles/Neutral/Werewolf.cs | 419 +- Roles/Neutral/Workaholic.cs | 127 +- Roles/Neutral/Wraith.cs | 45 +- Roles/RoleBase.cs | 409 +- Roles/VanillaRole.cs | 31 +- main.cs | 1817 +++-- 558 files changed, 84722 insertions(+), 85295 deletions(-) diff --git a/CustomRoles.cs b/CustomRoles.cs index 22b0ebda5..b6e1e7924 100644 --- a/CustomRoles.cs +++ b/CustomRoles.cs @@ -1,494 +1,493 @@ -namespace EHR +namespace EHR; + +public enum CustomRoles { - public enum CustomRoles - { - // Default - Crewmate = 0, + // Default + Crewmate = 0, - // Impostors (Vanilla) - Impostor, - Phantom, - Shapeshifter, + // Impostors (Vanilla) + Impostor, + Phantom, + Shapeshifter, - // Vanilla Remakes - ImpostorEHR, - PhantomEHR, - ShapeshifterEHR, + // Vanilla Remakes + ImpostorEHR, + PhantomEHR, + ShapeshifterEHR, - // Impostors + // Impostors - Abyssbringer, - Hacker, // Anonymous - AntiAdminer, - Sans, // Arrogance - Assumer, - Augmenter, - Bard, - Blackmailer, - Bomber, - BountyHunter, - OverKiller, // Butcher - Camouflager, - Capitalism, - Cantankerous, - Changeling, - Chronomancer, - Cleaner, - Commander, - EvilDiviner, // Consigliere - Consort, - Councillor, - Crewpostor, - CursedWolf, - Deathpact, - Devourer, - Disperser, - Duellist, - Dazzler, - Echo, - Escapee, - Eraser, - EvilGuesser, - EvilTracker, - FireWorks, - Framer, - Freezer, - Gambler, - Gangster, - Generator, - Godfather, - Greedier, - Hangman, - Hitman, - Hypnotist, - Inhibitor, - Kamikaze, - Kidnapper, - Minimalism, // Killing Machine - BallLightning, // Lightning - Librarian, - Lurker, - Mafioso, - Mastermind, - Mafia, // Nemesis - SerialKiller, // Mercenary - Miner, - Morphling, - Assassin, // Ninja - Nuker, - Nullifier, - Occultist, - Overheat, - Parasite, - Penguin, - Puppeteer, - QuickShooter, - Refugee, - RiftMaker, - Saboteur, - Sapper, - Scavenger, - Silencer, - Sniper, - ImperiusCurse, // Soul Catcher - Swapster, - Swiftclaw, - Swooper, - Stealth, - TimeThief, - BoobyTrap, // Trapster - Trickster, - Twister, - Underdog, - Undertaker, - Vampire, - Ventriloquist, - Vindicator, - Visionary, - Warlock, - Wasp, - Wildling, - Witch, - YinYanger, - Zombie, + Abyssbringer, + Hacker, // Anonymous + AntiAdminer, + Sans, // Arrogance + Assumer, + Augmenter, + Bard, + Blackmailer, + Bomber, + BountyHunter, + OverKiller, // Butcher + Camouflager, + Capitalism, + Cantankerous, + Changeling, + Chronomancer, + Cleaner, + Commander, + EvilDiviner, // Consigliere + Consort, + Councillor, + Crewpostor, + CursedWolf, + Deathpact, + Devourer, + Disperser, + Duellist, + Dazzler, + Echo, + Escapee, + Eraser, + EvilGuesser, + EvilTracker, + FireWorks, + Framer, + Freezer, + Gambler, + Gangster, + Generator, + Godfather, + Greedier, + Hangman, + Hitman, + Hypnotist, + Inhibitor, + Kamikaze, + Kidnapper, + Minimalism, // Killing Machine + BallLightning, // Lightning + Librarian, + Lurker, + Mafioso, + Mastermind, + Mafia, // Nemesis + SerialKiller, // Mercenary + Miner, + Morphling, + Assassin, // Ninja + Nuker, + Nullifier, + Occultist, + Overheat, + Parasite, + Penguin, + Puppeteer, + QuickShooter, + Refugee, + RiftMaker, + Saboteur, + Sapper, + Scavenger, + Silencer, + Sniper, + ImperiusCurse, // Soul Catcher + Swapster, + Swiftclaw, + Swooper, + Stealth, + TimeThief, + BoobyTrap, // Trapster + Trickster, + Twister, + Underdog, + Undertaker, + Vampire, + Ventriloquist, + Vindicator, + Visionary, + Warlock, + Wasp, + Wildling, + Witch, + YinYanger, + Zombie, - // Crewmates (Vanilla) - Engineer, - GuardianAngel, - Noisemaker, - Scientist, - Tracker, + // Crewmates (Vanilla) + Engineer, + GuardianAngel, + Noisemaker, + Scientist, + Tracker, - // Vanilla Remakes - CrewmateEHR, - EngineerEHR, - GuardianAngelEHR, - NoisemakerEHR, - ScientistEHR, - TrackerEHR, + // Vanilla Remakes + CrewmateEHR, + EngineerEHR, + GuardianAngelEHR, + NoisemakerEHR, + ScientistEHR, + TrackerEHR, - // Crewmates + // Crewmates - Addict, - Adrenaline, - Adventurer, - Aid, - Alchemist, - Altruist, - Analyst, - Ankylosaurus, - Autocrat, - Bane, - Beacon, - Benefactor, - Bodyguard, - CameraMan, - Car, - Catcher, - CyberStar, // Celebrity - Chameleon, - Chef, - Clairvoyant, - Cleanser, - Convener, - CopyCat, - Bloodhound, // Coroner - LovingCrewmate, - LovingImpostor, - Crusader, - Dad, - Demolitionist, - Deputy, - Detective, - Detour, - Dictator, - Doctor, - DonutDelivery, - Doormaster, - DoubleAgent, - DovesOfNeace, - Drainer, - Druid, - Electric, - Enigma, - Escort, - Express, - Farseer, - Divinator, // Fortune Teller - Gaulois, - Goose, - Grappler, - Grenadier, - GuessManagerRole, - Guardian, - Ignitor, - Insight, - ParityCop, // Inspector - Inquirer, - Jailor, - Journalist, - Judge, - Needy, // Lazy Guy - Leery, - Lighter, - Lookout, - Luckey, - Lyncher, - Markseeker, - Marshall, - Mathematician, - Mayor, - SabotageMaster, // Mechanic - Medic, - Mediumshiper, - Merchant, - Monitor, - Mole, - Monarch, - Mortician, - NiceEraser, - NiceGuesser, - NiceHacker, - NiceSwapper, - Nightmare, - Negotiator, - Observer, - Oracle, - Oxyman, - Paranoia, - Perceiver, - Philantropist, - President, - Psychic, - Rabbit, - Randomizer, - Rhapsode, - Ricochet, - Safeguard, - Scout, - SecurityGuard, - Sentinel, - Sentry, - Sheriff, - Shiftguard, - Snitch, - Socialite, - Soothsayer, - Spiritualist, - Speedrunner, - SpeedBooster, - Spy, - SuperStar, - TaskManager, - Telekinetic, - Tether, - TimeManager, - TimeMaster, - ToiletMaster, - Tornado, - Transmitter, - Transporter, - Tracefinder, - Tunneler, - Ventguard, - Veteran, - SwordsMan, // Vigilante - Whisperer, - Witness, - Wizard, + Addict, + Adrenaline, + Adventurer, + Aid, + Alchemist, + Altruist, + Analyst, + Ankylosaurus, + Autocrat, + Bane, + Beacon, + Benefactor, + Bodyguard, + CameraMan, + Car, + Catcher, + CyberStar, // Celebrity + Chameleon, + Chef, + Clairvoyant, + Cleanser, + Convener, + CopyCat, + Bloodhound, // Coroner + LovingCrewmate, + LovingImpostor, + Crusader, + Dad, + Demolitionist, + Deputy, + Detective, + Detour, + Dictator, + Doctor, + DonutDelivery, + Doormaster, + DoubleAgent, + DovesOfNeace, + Drainer, + Druid, + Electric, + Enigma, + Escort, + Express, + Farseer, + Divinator, // Fortune Teller + Gaulois, + Goose, + Grappler, + Grenadier, + GuessManagerRole, + Guardian, + Ignitor, + Insight, + ParityCop, // Inspector + Inquirer, + Jailor, + Journalist, + Judge, + Needy, // Lazy Guy + Leery, + Lighter, + Lookout, + Luckey, + Lyncher, + Markseeker, + Marshall, + Mathematician, + Mayor, + SabotageMaster, // Mechanic + Medic, + Mediumshiper, + Merchant, + Monitor, + Mole, + Monarch, + Mortician, + NiceEraser, + NiceGuesser, + NiceHacker, + NiceSwapper, + Nightmare, + Negotiator, + Observer, + Oracle, + Oxyman, + Paranoia, + Perceiver, + Philantropist, + President, + Psychic, + Rabbit, + Randomizer, + Rhapsode, + Ricochet, + Safeguard, + Scout, + SecurityGuard, + Sentinel, + Sentry, + Sheriff, + Shiftguard, + Snitch, + Socialite, + Soothsayer, + Spiritualist, + Speedrunner, + SpeedBooster, + Spy, + SuperStar, + TaskManager, + Telekinetic, + Tether, + TimeManager, + TimeMaster, + ToiletMaster, + Tornado, + Transmitter, + Transporter, + Tracefinder, + Tunneler, + Ventguard, + Veteran, + SwordsMan, // Vigilante + Whisperer, + Witness, + Wizard, - // Neutrals + // Neutrals - Agitater, - Amnesiac, - Arsonist, - Backstabber, - Bandit, - Bargainer, - Beehive, - BloodKnight, - Bubble, - Chemist, - Cherokious, - Collector, - Curser, - Deathknight, - Gamer, // Demon - Doppelganger, - Doomsayer, - Eclipse, - Enderman, - Evolver, - Executioner, - Totocalcio, // Follower - Gaslighter, - Glitch, - God, - FFF, // Hater - HeadHunter, - HexMaster, - Hookshot, - Imitator, - Impartial, - Innocent, - Jackal, - Jester, - Jinx, - Juggernaut, - Konan, - Lawyer, - Magician, - Mario, - Maverick, - Medusa, - Mycologist, - NecroGuesser, - Necromancer, - Nonplus, - NoteKiller, - Opportunist, - Patroller, - Pelican, - Pestilence, - Phantasm, - Pickpocket, - PlagueBearer, - PlagueDoctor, - Poisoner, - Postman, - Predator, - Provocateur, - Pursuer, - Pyromaniac, - QuizMaster, - Reckless, - Revolutionist, - Ritualist, - Rogue, - Romantic, - RouleteGrandeur, - RuthlessRomantic, - Samurai, - SchrodingersCat, - NSerialKiller, // Serial Killer - Shifter, - Sidekick, - Simon, - SoulHunter, - Spiritcaller, - Sprayer, - DarkHide, // Stalker - Succubus, - Sunnyboy, - Technician, - Tank, - Terrorist, - Tiger, - Traitor, - Tremor, - Vengeance, - VengefulRomantic, - Virus, - Vortex, - Vulture, - Wraith, - Werewolf, - WeaponMaster, - Weatherman, - Workaholic, + Agitater, + Amnesiac, + Arsonist, + Backstabber, + Bandit, + Bargainer, + Beehive, + BloodKnight, + Bubble, + Chemist, + Cherokious, + Collector, + Curser, + Deathknight, + Gamer, // Demon + Doppelganger, + Doomsayer, + Eclipse, + Enderman, + Evolver, + Executioner, + Totocalcio, // Follower + Gaslighter, + Glitch, + God, + FFF, // Hater + HeadHunter, + HexMaster, + Hookshot, + Imitator, + Impartial, + Innocent, + Jackal, + Jester, + Jinx, + Juggernaut, + Konan, + Lawyer, + Magician, + Mario, + Maverick, + Medusa, + Mycologist, + NecroGuesser, + Necromancer, + Nonplus, + NoteKiller, + Opportunist, + Patroller, + Pelican, + Pestilence, + Phantasm, + Pickpocket, + PlagueBearer, + PlagueDoctor, + Poisoner, + Postman, + Predator, + Provocateur, + Pursuer, + Pyromaniac, + QuizMaster, + Reckless, + Revolutionist, + Ritualist, + Rogue, + Romantic, + RouleteGrandeur, + RuthlessRomantic, + Samurai, + SchrodingersCat, + NSerialKiller, // Serial Killer + Shifter, + Sidekick, + Simon, + SoulHunter, + Spiritcaller, + Sprayer, + DarkHide, // Stalker + Succubus, + Sunnyboy, + Technician, + Tank, + Terrorist, + Tiger, + Traitor, + Tremor, + Vengeance, + VengefulRomantic, + Virus, + Vortex, + Vulture, + Wraith, + Werewolf, + WeaponMaster, + Weatherman, + Workaholic, - // Solo Kombat - KB_Normal, + // Solo Kombat + KB_Normal, - // FFA - Killer, + // FFA + Killer, - // Move And Stop - Tasker, + // Move And Stop + Tasker, - // Hot Potato - Potato, + // Hot Potato + Potato, - // H&S - Hider, - Seeker, - Fox, - Troll, - Jumper, - Detector, - Jet, - Dasher, - Locator, - Venter, - Agent, - Taskinator, + // H&S + Hider, + Seeker, + Fox, + Troll, + Jumper, + Detector, + Jet, + Dasher, + Locator, + Venter, + Agent, + Taskinator, - // Speedrun - Runner, + // Speedrun + Runner, - // Capture The Flag - CTFPlayer, + // Capture The Flag + CTFPlayer, - // Natural Disasters - NDPlayer, + // Natural Disasters + NDPlayer, - // Room Rush - RRPlayer, + // Room Rush + RRPlayer, - // GM - GM, + // GM + GM, - // ???? - Convict, + // ???? + Convict, - // Sub-role after 500 - NotAssigned = 500, - Allergic, - Antidote, - AntiTP, - Asthmatic, - Autopsy, - Avanger, - Bait, - Busy, - Trapper, // Beartrap - Bewilder, - Bloodlust, - Bloodmoon, // Ghost role - Charmed, - Circumvent, - Cleansed, - Clumsy, - Contagious, - Damocles, - Deadlined, - DeadlyQuota, - Disco, - Diseased, - Dynamo, - Unreportable, // Disregarded - DoubleShot, - Egoist, - Energetic, - EvilSpirit, - Finder, - Flashman, - Fragile, - Fool, - Giant, - Glow, - Gravestone, - GA, // Guardian Angel - Ghost role - Guesser, - Haste, - Haunter, // Ghost role - Introvert, - Knighted, - LastImpostor, - Lazy, - Lovers, - Loyal, - Lucky, - Madmate, - Magnet, - Mare, - Messenger, - Mimic, - Minion, // Ghost role - Mischievous, - Necroview, - Nimble, - Noisy, - Oblivious, - Onbound, - Spurt, - Sleep, - Specter, // Ghost role - Physicist, - Rascal, - Reach, - Recruit, - Rookie, - Trainee, - DualPersonality, // Schizophrenic - Seer, - Sleuth, - Sonar, - Stained, - Taskcounter, - TicketsStealer, // Stealer - Stressed, - Swift, - Sunglasses, - Brakar, // Tiebreaker - Torch, - Truant, - Undead, - Unlucky, - Warden, // Ghost role - Watcher, - Workhorse, - Youtuber - } + // Sub-role after 500 + NotAssigned = 500, + Allergic, + Antidote, + AntiTP, + Asthmatic, + Autopsy, + Avanger, + Bait, + Busy, + Trapper, // Beartrap + Bewilder, + Bloodlust, + Bloodmoon, // Ghost role + Charmed, + Circumvent, + Cleansed, + Clumsy, + Contagious, + Damocles, + Deadlined, + DeadlyQuota, + Disco, + Diseased, + Dynamo, + Unreportable, // Disregarded + DoubleShot, + Egoist, + Energetic, + EvilSpirit, + Finder, + Flashman, + Fragile, + Fool, + Giant, + Glow, + Gravestone, + GA, // Guardian Angel - Ghost role + Guesser, + Haste, + Haunter, // Ghost role + Introvert, + Knighted, + LastImpostor, + Lazy, + Lovers, + Loyal, + Lucky, + Madmate, + Magnet, + Mare, + Messenger, + Mimic, + Minion, // Ghost role + Mischievous, + Necroview, + Nimble, + Noisy, + Oblivious, + Onbound, + Spurt, + Sleep, + Specter, // Ghost role + Physicist, + Rascal, + Reach, + Recruit, + Rookie, + Trainee, + DualPersonality, // Schizophrenic + Seer, + Sleuth, + Sonar, + Stained, + Taskcounter, + TicketsStealer, // Stealer + Stressed, + Swift, + Sunglasses, + Brakar, // Tiebreaker + Torch, + Truant, + Undead, + Unlucky, + Warden, // Ghost role + Watcher, + Workhorse, + Youtuber } \ No newline at end of file diff --git a/GameMode/AllInOneGameMode.cs b/GameMode/AllInOneGameMode.cs index b3f0a1871..43d9025df 100644 --- a/GameMode/AllInOneGameMode.cs +++ b/GameMode/AllInOneGameMode.cs @@ -5,152 +5,151 @@ using System.Linq; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +public static class AllInOneGameMode { - public static class AllInOneGameMode + public static HashSet Taskers = []; + + public static readonly Dictionary GameModeIntegrationSettings = []; + + public static OptionItem HotPotatoTimerMultiplier; + public static OptionItem MoveAndStopMinGreenTimeBonus; + public static OptionItem MoveAndStopMaxGreenTimeBonus; + public static OptionItem NaturalDisastersDisasterSpawnCooldownMultiplier; + public static OptionItem NaturalDisastersWarningDurationMultiplier; + public static OptionItem RoomRushTimeLimitMultiplier; + public static OptionItem RoomRushDontKillLastPlayer; + public static OptionItem RoomRushDontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom; + public static OptionItem RoomRushDontKillPlayersOutsideRoomWhenTimeRunsOut; + public static OptionItem SpeedrunTimeLimitMultiplier; + + public static void SetupCustomOption() { - public static HashSet Taskers = []; + var id = 69_217_001; + var color = ColorUtility.TryParseHtmlString("#f542ad", out Color c) ? c : Color.magenta; + const CustomGameMode gameMode = CustomGameMode.AllInOne; - public static readonly Dictionary GameModeIntegrationSettings = []; - - public static OptionItem HotPotatoTimerMultiplier; - public static OptionItem MoveAndStopMinGreenTimeBonus; - public static OptionItem MoveAndStopMaxGreenTimeBonus; - public static OptionItem NaturalDisastersDisasterSpawnCooldownMultiplier; - public static OptionItem NaturalDisastersWarningDurationMultiplier; - public static OptionItem RoomRushTimeLimitMultiplier; - public static OptionItem RoomRushDontKillLastPlayer; - public static OptionItem RoomRushDontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom; - public static OptionItem RoomRushDontKillPlayersOutsideRoomWhenTimeRunsOut; - public static OptionItem SpeedrunTimeLimitMultiplier; - - public static void SetupCustomOption() + foreach (CustomGameMode mode in Enum.GetValues()) { - var id = 69_217_001; - var color = ColorUtility.TryParseHtmlString("#f542ad", out Color c) ? c : Color.magenta; - const CustomGameMode gameMode = CustomGameMode.AllInOne; + if (mode is CustomGameMode.Standard or gameMode) continue; - foreach (CustomGameMode mode in Enum.GetValues()) + bool defaultValue = mode is CustomGameMode.HotPotato or CustomGameMode.MoveAndStop or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.SoloKombat or CustomGameMode.Speedrun; + + GameModeIntegrationSettings[mode] = new BooleanOptionItem(id++, $"AllInOne.{mode}.Integration", defaultValue, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color); + } + + HotPotatoTimerMultiplier = new IntegerOptionItem(id++, "AllInOne.HotPotato.TimerMultiplier", new(1, 10, 1), 3, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Multiplier) + .SetGameMode(gameMode) + .SetColor(color); + + MoveAndStopMinGreenTimeBonus = new IntegerOptionItem(id++, "AllInOne.MoveAndStop.MinGreenTimeBonus", new(0, 60, 1), 5, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(gameMode) + .SetColor(color); + + MoveAndStopMaxGreenTimeBonus = new IntegerOptionItem(id++, "AllInOne.MoveAndStop.MaxGreenTimeBonus", new(0, 60, 1), 20, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(gameMode) + .SetColor(color); + + NaturalDisastersDisasterSpawnCooldownMultiplier = new IntegerOptionItem(id++, "AllInOne.NaturalDisasters.DisasterSpawnCooldownMultiplier", new(1, 10, 1), 4, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Multiplier) + .SetGameMode(gameMode) + .SetColor(color); + + NaturalDisastersWarningDurationMultiplier = new IntegerOptionItem(id++, "AllInOne.NaturalDisasters.WarningDurationMultiplier", new(1, 10, 1), 6, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Multiplier) + .SetGameMode(gameMode) + .SetColor(color); + + RoomRushTimeLimitMultiplier = new IntegerOptionItem(id++, "AllInOne.RoomRush.TimeLimitMultiplier", new(1, 10, 1), 3, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Multiplier) + .SetGameMode(gameMode) + .SetColor(color); + + RoomRushDontKillLastPlayer = new BooleanOptionItem(id++, "AllInOne.RoomRush.DontKillLastPlayer", true, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color); + + RoomRushDontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom = new BooleanOptionItem(id++, "AllInOne.RoomRush.DontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom", true, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color); + + RoomRushDontKillPlayersOutsideRoomWhenTimeRunsOut = new BooleanOptionItem(id++, "AllInOne.RoomRush.DontKillPlayersOutsideRoomWhenTimeRunsOut", true, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color); + + SpeedrunTimeLimitMultiplier = new IntegerOptionItem(id, "AllInOne.Speedrun.TimeLimitMultiplier", new(1, 10, 1), 3, TabGroup.GameSettings) + .SetValueFormat(OptionFormat.Multiplier) + .SetGameMode(gameMode) + .SetColor(color); + } + + public static void Init() + { + Taskers = []; + } + + public static CustomGameMode GetPrioritizedGameModeForRoles() + { + Dictionary order = new() + { + { CustomGameMode.HideAndSeek, 0 }, + { CustomGameMode.CaptureTheFlag, 1 }, + { CustomGameMode.FFA, 2 }, + { CustomGameMode.SoloKombat, 3 }, + { CustomGameMode.RoomRush, 4 }, + { CustomGameMode.Speedrun, 5 }, + { CustomGameMode.NaturalDisasters, 6 }, + { CustomGameMode.MoveAndStop, 7 }, + { CustomGameMode.HotPotato, 8 } + }; + + return order.Where(x => x.Key.IsActiveOrIntegrated()).MinBy(x => x.Value).Key; + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || GameStates.IsEnded || !CustomGameMode.AllInOne.IsActiveOrIntegrated() || Main.HasJustStarted || __instance.Is(CustomRoles.Killer) || CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) return; + + var gameModes = Enum.GetValues().Where(x => x.IsActiveOrIntegrated()).Split(x => x is CustomGameMode.CaptureTheFlag or CustomGameMode.FFA or CustomGameMode.SoloKombat); + if (gameModes.TrueList.Count == 0 || gameModes.FalseList.Count == 0) return; + + bool doneWithTasks = Taskers.Contains(__instance.PlayerId) && __instance.GetTaskState().IsTaskFinished; + + if (doneWithTasks) { - if (mode is CustomGameMode.Standard or gameMode) continue; - - bool defaultValue = mode is CustomGameMode.HotPotato or CustomGameMode.MoveAndStop or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.SoloKombat or CustomGameMode.Speedrun; - - GameModeIntegrationSettings[mode] = new BooleanOptionItem(id++, $"AllInOne.{mode}.Integration", defaultValue, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color); + Taskers.Remove(__instance.PlayerId); + __instance.RpcChangeRoleBasis(CustomRoles.Killer); + __instance.RpcSetCustomRole(CustomRoles.Killer); + Logger.Info($"{__instance.GetRealName()} has completed all tasks, changing role to Killer", "AllInOneGameMode"); + return; } - HotPotatoTimerMultiplier = new IntegerOptionItem(id++, "AllInOne.HotPotato.TimerMultiplier", new(1, 10, 1), 3, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Multiplier) - .SetGameMode(gameMode) - .SetColor(color); + Vector2 pos = __instance.Pos(); - MoveAndStopMinGreenTimeBonus = new IntegerOptionItem(id++, "AllInOne.MoveAndStop.MinGreenTimeBonus", new(0, 60, 1), 5, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(gameMode) - .SetColor(color); + bool nearTask = __instance.myTasks.ToArray().Where(x => !x.IsComplete).SelectMany(x => x.FindValidConsolesPositions().ToArray()).Any(x => Vector2.Distance(x, pos) <= DisableDevice.UsableDistance); - MoveAndStopMaxGreenTimeBonus = new IntegerOptionItem(id++, "AllInOne.MoveAndStop.MaxGreenTimeBonus", new(0, 60, 1), 20, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(gameMode) - .SetColor(color); - - NaturalDisastersDisasterSpawnCooldownMultiplier = new IntegerOptionItem(id++, "AllInOne.NaturalDisasters.DisasterSpawnCooldownMultiplier", new(1, 10, 1), 4, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Multiplier) - .SetGameMode(gameMode) - .SetColor(color); - - NaturalDisastersWarningDurationMultiplier = new IntegerOptionItem(id++, "AllInOne.NaturalDisasters.WarningDurationMultiplier", new(1, 10, 1), 6, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Multiplier) - .SetGameMode(gameMode) - .SetColor(color); - - RoomRushTimeLimitMultiplier = new IntegerOptionItem(id++, "AllInOne.RoomRush.TimeLimitMultiplier", new(1, 10, 1), 3, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Multiplier) - .SetGameMode(gameMode) - .SetColor(color); - - RoomRushDontKillLastPlayer = new BooleanOptionItem(id++, "AllInOne.RoomRush.DontKillLastPlayer", true, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color); - - RoomRushDontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom = new BooleanOptionItem(id++, "AllInOne.RoomRush.DontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom", true, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color); - - RoomRushDontKillPlayersOutsideRoomWhenTimeRunsOut = new BooleanOptionItem(id++, "AllInOne.RoomRush.DontKillPlayersOutsideRoomWhenTimeRunsOut", true, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color); - - SpeedrunTimeLimitMultiplier = new IntegerOptionItem(id, "AllInOne.Speedrun.TimeLimitMultiplier", new(1, 10, 1), 3, TabGroup.GameSettings) - .SetValueFormat(OptionFormat.Multiplier) - .SetGameMode(gameMode) - .SetColor(color); - } - - public static void Init() - { - Taskers = []; - } - - public static CustomGameMode GetPrioritizedGameModeForRoles() - { - Dictionary order = new() + switch (nearTask) { - { CustomGameMode.HideAndSeek, 0 }, - { CustomGameMode.CaptureTheFlag, 1 }, - { CustomGameMode.FFA, 2 }, - { CustomGameMode.SoloKombat, 3 }, - { CustomGameMode.RoomRush, 4 }, - { CustomGameMode.Speedrun, 5 }, - { CustomGameMode.NaturalDisasters, 6 }, - { CustomGameMode.MoveAndStop, 7 }, - { CustomGameMode.HotPotato, 8 } - }; - - return order.Where(x => x.Key.IsActiveOrIntegrated()).MinBy(x => x.Value).Key; - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch - { - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || GameStates.IsEnded || !CustomGameMode.AllInOne.IsActiveOrIntegrated() || Main.HasJustStarted || __instance.Is(CustomRoles.Killer) || CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) return; - - var gameModes = Enum.GetValues().Where(x => x.IsActiveOrIntegrated()).Split(x => x is CustomGameMode.CaptureTheFlag or CustomGameMode.FFA or CustomGameMode.SoloKombat); - if (gameModes.TrueList.Count == 0 || gameModes.FalseList.Count == 0) return; - - bool doneWithTasks = Taskers.Contains(__instance.PlayerId) && __instance.GetTaskState().IsTaskFinished; - - if (doneWithTasks) - { - Taskers.Remove(__instance.PlayerId); - __instance.RpcChangeRoleBasis(CustomRoles.Killer); - __instance.RpcSetCustomRole(CustomRoles.Killer); - Logger.Info($"{__instance.GetRealName()} has completed all tasks, changing role to Killer", "AllInOneGameMode"); - return; - } - - Vector2 pos = __instance.Pos(); - - bool nearTask = __instance.myTasks.ToArray().Where(x => !x.IsComplete).SelectMany(x => x.FindValidConsolesPositions().ToArray()).Any(x => Vector2.Distance(x, pos) <= DisableDevice.UsableDistance); - - switch (nearTask) - { - case true when Taskers.Add(__instance.PlayerId): - __instance.RpcChangeRoleBasis(CustomRoles.Tasker); - __instance.RpcSetCustomRole(CustomRoles.Tasker); - Logger.Info($"{__instance.GetRealName()} is near an incomplete task, changing role to Tasker", "AllInOneGameMode"); - break; - case false when Taskers.Remove(__instance.PlayerId): - __instance.RpcChangeRoleBasis(CustomRoles.KB_Normal); - __instance.RpcSetCustomRole(CustomRoles.KB_Normal); - Logger.Info($"{__instance.GetRealName()} is no longer near an incomplete task, changing role to KB_Normal", "AllInOneGameMode"); - break; - } + case true when Taskers.Add(__instance.PlayerId): + __instance.RpcChangeRoleBasis(CustomRoles.Tasker); + __instance.RpcSetCustomRole(CustomRoles.Tasker); + Logger.Info($"{__instance.GetRealName()} is near an incomplete task, changing role to Tasker", "AllInOneGameMode"); + break; + case false when Taskers.Remove(__instance.PlayerId): + __instance.RpcChangeRoleBasis(CustomRoles.KB_Normal); + __instance.RpcSetCustomRole(CustomRoles.KB_Normal); + Logger.Info($"{__instance.GetRealName()} is no longer near an incomplete task, changing role to KB_Normal", "AllInOneGameMode"); + break; } } } diff --git a/GameMode/CTFManager.cs b/GameMode/CTFManager.cs index 3bac72ec2..b7b253658 100644 --- a/GameMode/CTFManager.cs +++ b/GameMode/CTFManager.cs @@ -7,481 +7,480 @@ using EHR.Modules; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +public static class CTFManager { - public static class CTFManager + private static OptionItem AlertTeamMembersOfFlagTaken; + private static OptionItem ArrowToEnemyFlagCarrier; + private static OptionItem AlertTeamMembersOfEnemyFlagTaken; + private static OptionItem ArrowToOwnFlagCarrier; + private static OptionItem TaggedPlayersGet; + private static OptionItem WhenFlagCarrierGetsTagged; + private static OptionItem SpeedReductionForFlagCarrier; + private static OptionItem TagCooldown; + + private static readonly string[] TaggedPlayersGetOptions = + [ + "CTF_TaggedPlayersGet.SentBackToBase", + "CTF_TaggedPlayersGet.Killed" + ]; + + private static readonly string[] WhenFlagCarrierGetsTaggedOptions = + [ + "CTF_WhenFlagCarrierGetsTagged.FlagIsDropped", + "CTF_WhenFlagCarrierGetsTagged.FlagIsReturned" + ]; + + public static (Color Color, string Team) WinnerData = (Color.white, "No one wins"); + + private static Dictionary PlayerTeams = []; + private static Dictionary TeamData = []; + private static Dictionary PlayerData = []; + private static Dictionary DefaultOutfits = []; + private static bool ValidTag; + public static bool IsDeathPossible => TaggedPlayersGet.GetValue() == 1; + + private static NetworkedPlayerInfo.PlayerOutfit YellowOutfit => new NetworkedPlayerInfo.PlayerOutfit().Set("", 5, "", "", "", "pet_coaltonpet", ""); + private static NetworkedPlayerInfo.PlayerOutfit BlueOutfit => new NetworkedPlayerInfo.PlayerOutfit().Set("", 1, "", "", "", "pet_coaltonpet", ""); + + private static (Vector2 Position, string RoomName) BlueFlagBase => Main.CurrentMap switch { - private static OptionItem AlertTeamMembersOfFlagTaken; - private static OptionItem ArrowToEnemyFlagCarrier; - private static OptionItem AlertTeamMembersOfEnemyFlagTaken; - private static OptionItem ArrowToOwnFlagCarrier; - private static OptionItem TaggedPlayersGet; - private static OptionItem WhenFlagCarrierGetsTagged; - private static OptionItem SpeedReductionForFlagCarrier; - private static OptionItem TagCooldown; + MapNames.Skeld => (new(16.5f, -4.8f), Translator.GetString(SystemTypes.Nav.ToString())), + MapNames.Mira => (new(-4.5f, 2.0f), Translator.GetString(SystemTypes.Launchpad.ToString())), + MapNames.Dleks => (new(-16.5f, -4.8f), Translator.GetString(SystemTypes.Nav.ToString())), + MapNames.Polus => (new(9.5f, -12.5f), Translator.GetString(SystemTypes.Electrical.ToString())), + MapNames.Airship => (new(-23.5f, -1.6f), Translator.GetString(SystemTypes.Cockpit.ToString())), + MapNames.Fungle => (new(-15.5f, -7.5f), Translator.GetString(SystemTypes.Kitchen.ToString())), + _ => (Vector2.zero, string.Empty) + }; - private static readonly string[] TaggedPlayersGetOptions = - [ - "CTF_TaggedPlayersGet.SentBackToBase", - "CTF_TaggedPlayersGet.Killed" - ]; + private static (Vector2 Position, string RoomName) YellowFlagBase => Main.CurrentMap switch + { + MapNames.Skeld => (new(-20.5f, -5.5f), Translator.GetString(SystemTypes.Reactor.ToString())), + MapNames.Mira => (new(17.8f, 23.0f), Translator.GetString(SystemTypes.Greenhouse.ToString())), + MapNames.Dleks => (new(20.5f, -5.5f), Translator.GetString(SystemTypes.Reactor.ToString())), + MapNames.Polus => (new(36.5f, -7.5f), Translator.GetString(SystemTypes.Laboratory.ToString())), + MapNames.Airship => (new(33.5f, -1.5f), Translator.GetString(SystemTypes.CargoBay.ToString())), + MapNames.Fungle => (new(22.2f, 13.7f), Translator.GetString(SystemTypes.Comms.ToString())), + _ => (Vector2.zero, string.Empty) + }; - private static readonly string[] WhenFlagCarrierGetsTaggedOptions = - [ - "CTF_WhenFlagCarrierGetsTagged.FlagIsDropped", - "CTF_WhenFlagCarrierGetsTagged.FlagIsReturned" - ]; + public static void SetupCustomOption() + { + const int id = 69_215_001; + Color color = Utils.GetRoleColor(CustomRoles.CTFPlayer); - public static (Color Color, string Team) WinnerData = (Color.white, "No one wins"); + AlertTeamMembersOfFlagTaken = new BooleanOptionItem(id, "CTF_AlertTeamMembersOfFlagTaken", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetColor(color); - private static Dictionary PlayerTeams = []; - private static Dictionary TeamData = []; - private static Dictionary PlayerData = []; - private static Dictionary DefaultOutfits = []; - private static bool ValidTag; - public static bool IsDeathPossible => TaggedPlayersGet.GetValue() == 1; + ArrowToEnemyFlagCarrier = new BooleanOptionItem(id + 1, "CTF_ArrowToEnemyFlagCarrier", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetParent(AlertTeamMembersOfFlagTaken) + .SetColor(color); - private static NetworkedPlayerInfo.PlayerOutfit YellowOutfit => new NetworkedPlayerInfo.PlayerOutfit().Set("", 5, "", "", "", "pet_coaltonpet", ""); - private static NetworkedPlayerInfo.PlayerOutfit BlueOutfit => new NetworkedPlayerInfo.PlayerOutfit().Set("", 1, "", "", "", "pet_coaltonpet", ""); + AlertTeamMembersOfEnemyFlagTaken = new BooleanOptionItem(id + 2, "CTF_AlertTeamMembersOfEnemyFlagTaken", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetColor(color); - private static (Vector2 Position, string RoomName) BlueFlagBase => Main.CurrentMap switch + ArrowToOwnFlagCarrier = new BooleanOptionItem(id + 3, "CTF_ArrowToOwnFlagCarrier", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetParent(AlertTeamMembersOfEnemyFlagTaken) + .SetColor(color); + + TaggedPlayersGet = new StringOptionItem(id + 4, "CTF_TaggedPlayersGet", TaggedPlayersGetOptions, 0, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetColor(color); + + WhenFlagCarrierGetsTagged = new StringOptionItem(id + 5, "CTF_WhenFlagCarrierGetsTagged", WhenFlagCarrierGetsTaggedOptions, 0, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetColor(color); + + SpeedReductionForFlagCarrier = new FloatOptionItem(id + 6, "CTF_SpeedReductionForFlagCarrier", new(0f, 1f, 0.05f), 0.25f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(color); + + TagCooldown = new FloatOptionItem(id + 7, "CTF_TagCooldown", new(0f, 10f, 0.5f), 2f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.CaptureTheFlag) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(color); + } + + public static bool KnowTargetRoleColor(PlayerControl target, ref string color) + { + if (!ValidTag || !PlayerTeams.TryGetValue(target.PlayerId, out var team)) return false; + + color = team switch { - MapNames.Skeld => (new(16.5f, -4.8f), Translator.GetString(SystemTypes.Nav.ToString())), - MapNames.Mira => (new(-4.5f, 2.0f), Translator.GetString(SystemTypes.Launchpad.ToString())), - MapNames.Dleks => (new(-16.5f, -4.8f), Translator.GetString(SystemTypes.Nav.ToString())), - MapNames.Polus => (new(9.5f, -12.5f), Translator.GetString(SystemTypes.Electrical.ToString())), - MapNames.Airship => (new(-23.5f, -1.6f), Translator.GetString(SystemTypes.Cockpit.ToString())), - MapNames.Fungle => (new(-15.5f, -7.5f), Translator.GetString(SystemTypes.Kitchen.ToString())), - _ => (Vector2.zero, string.Empty) + CTFTeam.Blue => "#0000FF", + CTFTeam.Yellow => "#FFFF00", + _ => color }; - private static (Vector2 Position, string RoomName) YellowFlagBase => Main.CurrentMap switch + return true; + } + + public static string GetSuffixText(PlayerControl seer, PlayerControl target) + { + if (!ValidTag) return string.Empty; + + if (seer.PlayerId != target.PlayerId) return string.Empty; + + string arrows = TargetArrow.GetAllArrows(seer); + arrows = arrows.Length > 0 ? $"{arrows}\n" : string.Empty; + return $"{arrows}{GetStatistics(target.PlayerId).Replace(" | ", "\n")}"; + } + + public static string GetStatistics(byte id) + { + if (!PlayerData.TryGetValue(id, out CTFPlayerData stats)) return string.Empty; + return string.Format(Translator.GetString("CTF_PlayerStats"), Math.Round(stats.FlagTime, 1), stats.TagCount); + } + + public static int GetFlagTime(byte id) + { + if (!PlayerData.TryGetValue(id, out CTFPlayerData data)) return 0; + return (int)Math.Round(data.FlagTime); + } + + public static int GetTagCount(byte id) + { + if (!PlayerData.TryGetValue(id, out CTFPlayerData data)) return 0; + return data.TagCount; + } + + public static bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + if (!ValidTag) return false; + + switch (aapc.Length) { - MapNames.Skeld => (new(-20.5f, -5.5f), Translator.GetString(SystemTypes.Reactor.ToString())), - MapNames.Mira => (new(17.8f, 23.0f), Translator.GetString(SystemTypes.Greenhouse.ToString())), - MapNames.Dleks => (new(20.5f, -5.5f), Translator.GetString(SystemTypes.Reactor.ToString())), - MapNames.Polus => (new(36.5f, -7.5f), Translator.GetString(SystemTypes.Laboratory.ToString())), - MapNames.Airship => (new(33.5f, -1.5f), Translator.GetString(SystemTypes.CargoBay.ToString())), - MapNames.Fungle => (new(22.2f, 13.7f), Translator.GetString(SystemTypes.Comms.ToString())), - _ => (Vector2.zero, string.Empty) - }; - - public static void SetupCustomOption() - { - const int id = 69_215_001; - Color color = Utils.GetRoleColor(CustomRoles.CTFPlayer); - - AlertTeamMembersOfFlagTaken = new BooleanOptionItem(id, "CTF_AlertTeamMembersOfFlagTaken", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetColor(color); - - ArrowToEnemyFlagCarrier = new BooleanOptionItem(id + 1, "CTF_ArrowToEnemyFlagCarrier", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetParent(AlertTeamMembersOfFlagTaken) - .SetColor(color); - - AlertTeamMembersOfEnemyFlagTaken = new BooleanOptionItem(id + 2, "CTF_AlertTeamMembersOfEnemyFlagTaken", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetColor(color); - - ArrowToOwnFlagCarrier = new BooleanOptionItem(id + 3, "CTF_ArrowToOwnFlagCarrier", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetParent(AlertTeamMembersOfEnemyFlagTaken) - .SetColor(color); - - TaggedPlayersGet = new StringOptionItem(id + 4, "CTF_TaggedPlayersGet", TaggedPlayersGetOptions, 0, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetColor(color); - - WhenFlagCarrierGetsTagged = new StringOptionItem(id + 5, "CTF_WhenFlagCarrierGetsTagged", WhenFlagCarrierGetsTaggedOptions, 0, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetColor(color); - - SpeedReductionForFlagCarrier = new FloatOptionItem(id + 6, "CTF_SpeedReductionForFlagCarrier", new(0f, 1f, 0.05f), 0.25f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(color); - - TagCooldown = new FloatOptionItem(id + 7, "CTF_TagCooldown", new(0f, 10f, 0.5f), 2f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.CaptureTheFlag) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(color); - } - - public static bool KnowTargetRoleColor(PlayerControl target, ref string color) - { - if (!ValidTag || !PlayerTeams.TryGetValue(target.PlayerId, out var team)) return false; - - color = team switch - { - CTFTeam.Blue => "#0000FF", - CTFTeam.Yellow => "#FFFF00", - _ => color - }; - - return true; - } - - public static string GetSuffixText(PlayerControl seer, PlayerControl target) - { - if (!ValidTag) return string.Empty; - - if (seer.PlayerId != target.PlayerId) return string.Empty; - - string arrows = TargetArrow.GetAllArrows(seer); - arrows = arrows.Length > 0 ? $"{arrows}\n" : string.Empty; - return $"{arrows}{GetStatistics(target.PlayerId).Replace(" | ", "\n")}"; - } - - public static string GetStatistics(byte id) - { - if (!PlayerData.TryGetValue(id, out CTFPlayerData stats)) return string.Empty; - return string.Format(Translator.GetString("CTF_PlayerStats"), Math.Round(stats.FlagTime, 1), stats.TagCount); - } - - public static int GetFlagTime(byte id) - { - if (!PlayerData.TryGetValue(id, out CTFPlayerData data)) return 0; - return (int)Math.Round(data.FlagTime); - } - - public static int GetTagCount(byte id) - { - if (!PlayerData.TryGetValue(id, out CTFPlayerData data)) return 0; - return data.TagCount; - } - - public static bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - if (!ValidTag) return false; - - switch (aapc.Length) - { - case 0: + case 0: + ResetSkins(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + CustomWinnerHolder.WinnerIds = Main.PlayerStates.Keys.ToHashSet(); + reason = GameOverReason.HumansDisconnect; + return true; + case 1: + ResetSkins(); + TeamData[PlayerTeams[aapc[0].PlayerId]].SetAsWinner(); + return true; + default: + // If WinnerData is already set, end the game + if (WinnerData.Team != "No one wins") + { ResetSkins(); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - CustomWinnerHolder.WinnerIds = Main.PlayerStates.Keys.ToHashSet(); - reason = GameOverReason.HumansDisconnect; return true; - case 1: + } + + // If all players are on the same team, end the game + if (aapc.All(x => PlayerTeams.TryGetValue(x.PlayerId, out var team) && team == CTFTeam.Blue) || aapc.All(x => PlayerTeams.TryGetValue(x.PlayerId, out var team) && team == CTFTeam.Yellow)) + { ResetSkins(); TeamData[PlayerTeams[aapc[0].PlayerId]].SetAsWinner(); return true; - default: - // If WinnerData is already set, end the game - if (WinnerData.Team != "No one wins") + } + + return false; + } + + void ResetSkins() + { + DefaultOutfits.Select(x => (pc: x.Key.GetPlayer(), outfit: x.Value)).DoIf(x => x.pc != null && x.outfit != null, x => Utils.RpcChangeSkin(x.pc, x.outfit)); + } + } + + public static void OnGameStart() + { + // Reset all data + PlayerTeams = []; + TeamData = []; + WinnerData = (Color.white, "No one wins"); + PlayerData = Main.PlayerStates.Keys.ToDictionary(x => x, _ => new CTFPlayerData()); + DefaultOutfits = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, x => x.Data.DefaultOutfit); + ValidTag = false; + + // Check if the current game mode is Capture The Flag + if (!CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated()) return; + + LateTask.New(() => + { + Main.AllPlayerKillCooldown.SetAllValues(TagCooldown.GetFloat()); + + // Assign players to teams + List players = Main.AllAlivePlayerControls.Shuffle().ToList(); + if (Main.GM.Value) players.RemoveAll(x => x.IsHost()); + + int blueCount = players.Count / 2; + HashSet bluePlayers = []; + HashSet yellowPlayers = []; + NetworkedPlayerInfo.PlayerOutfit blueOutfit = BlueOutfit; + NetworkedPlayerInfo.PlayerOutfit yellowOutfit = YellowOutfit; + + for (var i = 0; i < blueCount; i++) + { + PlayerControl player = players.FirstOrDefault(p => p.Data.DefaultOutfit.ColorId == 1) ?? players.FirstOrDefault(x => x.Data.DefaultOutfit.ColorId != 5) ?? players.RandomElement(); + players.Remove(player); + PlayerTeams[player.PlayerId] = CTFTeam.Blue; + bluePlayers.Add(player.PlayerId); + blueOutfit.PlayerName = player.GetRealName(); + blueOutfit.PetId = player.Data.DefaultOutfit.PetId; + Utils.RpcChangeSkin(player, blueOutfit); + } + + foreach (PlayerControl player in players) + { + PlayerTeams[player.PlayerId] = CTFTeam.Yellow; + yellowPlayers.Add(player.PlayerId); + yellowOutfit.PlayerName = player.GetRealName(); + yellowOutfit.PetId = player.Data.DefaultOutfit.PetId; + Utils.RpcChangeSkin(player, yellowOutfit); + } + + // Create flags + (Vector2 Position, string RoomName) blueFlagBase = BlueFlagBase; + (Vector2 Position, string RoomName) yellowFlagBase = YellowFlagBase; + + CustomNetObject blueFlag = new BlueFlag(blueFlagBase.Position); + CustomNetObject yellowFlag = new YellowFlag(yellowFlagBase.Position); + + // Create team data + TeamData[CTFTeam.Blue] = new(CTFTeam.Blue, blueFlag, bluePlayers, byte.MaxValue); + TeamData[CTFTeam.Yellow] = new(CTFTeam.Yellow, yellowFlag, yellowPlayers, byte.MaxValue); + + // Teleport players to their respective bases + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (PlayerTeams.TryGetValue(pc.PlayerId, out CTFTeam team)) + { + switch (team) { - ResetSkins(); - return true; + case CTFTeam.Blue: + pc.TP(blueFlagBase.Position); + pc.Notify(string.Format(Translator.GetString("CTF_Notify_EnemyTeamRoom"), yellowFlagBase.RoomName)); + break; + case CTFTeam.Yellow: + pc.TP(yellowFlagBase.Position); + pc.Notify(string.Format(Translator.GetString("CTF_Notify_EnemyTeamRoom"), blueFlagBase.RoomName)); + break; } + } - // If all players are on the same team, end the game - if (aapc.All(x => PlayerTeams.TryGetValue(x.PlayerId, out var team) && team == CTFTeam.Blue) || aapc.All(x => PlayerTeams.TryGetValue(x.PlayerId, out var team) && team == CTFTeam.Yellow)) + pc.CheckAndSetUnshiftState(force: true); + pc.RpcResetAbilityCooldown(); + } + + ValidTag = true; + LateTask.New(() => Main.ProcessShapeshifts = true, 3f, log: false); + }, 12f, "CTFManager.OnGameStart"); + } + + public static void OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!ValidTag || !PlayerTeams.TryGetValue(target.PlayerId, out var targetTeam) || !PlayerTeams.TryGetValue(killer.PlayerId, out var killerTeam) || killerTeam == targetTeam || TeamData.Values.Any(x => x.FlagCarrier == killer.PlayerId)) return; + + new[] { killer, target }.Do(x => x.SetKillCooldown(TagCooldown.GetFloat())); + + if (TeamData.FindFirst(x => x.Value.FlagCarrier == target.PlayerId, out KeyValuePair kvp)) + { + kvp.Value.DropFlag(); + if (WhenFlagCarrierGetsTagged.GetValue() == 1) kvp.Value.Flag.TP(kvp.Key.GetFlagBase().Position); + } + + switch (TaggedPlayersGet.GetValue()) + { + case 0: + target.TP(targetTeam.GetFlagBase().Position); + Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + target.MarkDirtySettings(); + break; + case 1: + target.Suicide(); + string notify = string.Format(Translator.GetString("CTF_TeamMemberFallen"), target.PlayerId.ColoredPlayerName()); + TeamData[targetTeam].Players.ToValidPlayers().Do(x => x.Notify(notify)); + break; + } + + if (PlayerData.TryGetValue(killer.PlayerId, out var data)) data.TagCount++; + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); + } + + public static void TryPickUpFlag(PlayerControl pc) + { + if (!ValidTag) return; + + Logger.Info($"Received flag pickup request from {pc.GetRealName()}", "CTF"); + // If the player is near the enemy's flag, pick it up + Vector2 pos = pc.Pos(); + CTFTeamData enemy = TeamData[PlayerTeams[pc.PlayerId].GetOppositeTeam()]; + if (enemy.IsNearFlag(pos)) enemy.PickUpFlag(pc.PlayerId); + } + + public static void ApplyGameOptions(IGameOptions opt) + { + AURoleOptions.ShapeshifterCooldown = 1f; + } + + private static Color GetTeamColor(this CTFTeam team) + { + return team switch + { + CTFTeam.Blue => Color.blue, + CTFTeam.Yellow => Color.yellow, + _ => Color.white + }; + } + + private static string GetTeamName(this CTFTeam team) + { + return team switch + { + CTFTeam.Blue => Translator.GetString("CTF_BlueTeamWins"), + CTFTeam.Yellow => Translator.GetString("CTF_YellowTeamWins"), + _ => string.Empty + }; + } + + private static CTFTeam GetOppositeTeam(this CTFTeam team) + { + return team switch + { + CTFTeam.Blue => CTFTeam.Yellow, + CTFTeam.Yellow => CTFTeam.Blue, + _ => CTFTeam.Blue + }; + } + + private static (Vector2 Position, string RoomName) GetFlagBase(this CTFTeam team) + { + return team switch + { + CTFTeam.Blue => BlueFlagBase, + CTFTeam.Yellow => YellowFlagBase, + _ => (Vector2.zero, string.Empty) + }; + } + + private enum CTFTeam + { + Blue, + Yellow + } + + private class CTFTeamData(CTFTeam team, CustomNetObject flag, HashSet players, byte flagCarrier) + { + public CustomNetObject Flag { get; } = flag; + public HashSet Players { get; } = players; + public byte FlagCarrier { get; private set; } = flagCarrier; + + public void SetAsWinner() + { + WinnerData = (team.GetTeamColor(), team.GetTeamName()); + CustomWinnerHolder.WinnerIds = Players; + Logger.Info($"{team} team wins", "CTF"); + } + + public void Update() + { + try + { + if (FlagCarrier == byte.MaxValue) return; + + PlayerControl flagCarrierPc = FlagCarrier.GetPlayer(); + + if (flagCarrierPc == null || !flagCarrierPc.IsAlive()) + { + DropFlag(); + return; + } + + Flag.TP(flagCarrierPc.Pos()); + if (PlayerData.TryGetValue(FlagCarrier, out var data)) data.FlagTime += Time.fixedDeltaTime; + Utils.NotifyRoles(SpecifySeer: flagCarrierPc, SpecifyTarget: flagCarrierPc); + + CTFTeam enemy = team.GetOppositeTeam(); + PlainShipRoom flagRoom = Flag.playerControl.GetPlainShipRoom(); + + if (flagRoom != null && Translator.GetString(flagRoom.RoomId.ToString()) == enemy.GetFlagBase().RoomName) + TeamData[enemy].SetAsWinner(); + } + catch { } + } + + public void PickUpFlag(byte id) + { + if (FlagCarrier == id) return; + + FlagCarrier = id; + Update(); + + Logger.Info($"{id.ColoredPlayerName().RemoveHtmlTags()} picked up the {team} flag", "CTF"); + + Main.AllPlayerSpeed[id] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod) - SpeedReductionForFlagCarrier.GetFloat(); + PlayerGameOptionsSender.SetDirty(id); + + if (AlertTeamMembersOfFlagTaken.GetBool()) + { + bool arrow = ArrowToEnemyFlagCarrier.GetBool(); + + TeamData[team].Players + .Select(x => x.GetPlayer()) + .DoIf(x => x != null, x => { - ResetSkins(); - TeamData[PlayerTeams[aapc[0].PlayerId]].SetAsWinner(); - return true; - } - - return false; + if (arrow) TargetArrow.Add(x.PlayerId, id); + x.Notify(Utils.ColorString(Color.yellow, Translator.GetString("CTF_FlagTaken"))); + }); } - void ResetSkins() + if (AlertTeamMembersOfEnemyFlagTaken.GetBool()) { - DefaultOutfits.Select(x => (pc: x.Key.GetPlayer(), outfit: x.Value)).DoIf(x => x.pc != null && x.outfit != null, x => Utils.RpcChangeSkin(x.pc, x.outfit)); - } - } + bool arrow = ArrowToOwnFlagCarrier.GetBool(); - public static void OnGameStart() - { - // Reset all data - PlayerTeams = []; - TeamData = []; - WinnerData = (Color.white, "No one wins"); - PlayerData = Main.PlayerStates.Keys.ToDictionary(x => x, _ => new CTFPlayerData()); - DefaultOutfits = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, x => x.Data.DefaultOutfit); - ValidTag = false; - - // Check if the current game mode is Capture The Flag - if (!CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated()) return; - - LateTask.New(() => - { - Main.AllPlayerKillCooldown.SetAllValues(TagCooldown.GetFloat()); - - // Assign players to teams - List players = Main.AllAlivePlayerControls.Shuffle().ToList(); - if (Main.GM.Value) players.RemoveAll(x => x.IsHost()); - - int blueCount = players.Count / 2; - HashSet bluePlayers = []; - HashSet yellowPlayers = []; - NetworkedPlayerInfo.PlayerOutfit blueOutfit = BlueOutfit; - NetworkedPlayerInfo.PlayerOutfit yellowOutfit = YellowOutfit; - - for (var i = 0; i < blueCount; i++) - { - PlayerControl player = players.FirstOrDefault(p => p.Data.DefaultOutfit.ColorId == 1) ?? players.FirstOrDefault(x => x.Data.DefaultOutfit.ColorId != 5) ?? players.RandomElement(); - players.Remove(player); - PlayerTeams[player.PlayerId] = CTFTeam.Blue; - bluePlayers.Add(player.PlayerId); - blueOutfit.PlayerName = player.GetRealName(); - blueOutfit.PetId = player.Data.DefaultOutfit.PetId; - Utils.RpcChangeSkin(player, blueOutfit); - } - - foreach (PlayerControl player in players) - { - PlayerTeams[player.PlayerId] = CTFTeam.Yellow; - yellowPlayers.Add(player.PlayerId); - yellowOutfit.PlayerName = player.GetRealName(); - yellowOutfit.PetId = player.Data.DefaultOutfit.PetId; - Utils.RpcChangeSkin(player, yellowOutfit); - } - - // Create flags - (Vector2 Position, string RoomName) blueFlagBase = BlueFlagBase; - (Vector2 Position, string RoomName) yellowFlagBase = YellowFlagBase; - - CustomNetObject blueFlag = new BlueFlag(blueFlagBase.Position); - CustomNetObject yellowFlag = new YellowFlag(yellowFlagBase.Position); - - // Create team data - TeamData[CTFTeam.Blue] = new(CTFTeam.Blue, blueFlag, bluePlayers, byte.MaxValue); - TeamData[CTFTeam.Yellow] = new(CTFTeam.Yellow, yellowFlag, yellowPlayers, byte.MaxValue); - - // Teleport players to their respective bases - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (PlayerTeams.TryGetValue(pc.PlayerId, out CTFTeam team)) + TeamData[team.GetOppositeTeam()].Players + .ToValidPlayers() + .Do(x => { - switch (team) - { - case CTFTeam.Blue: - pc.TP(blueFlagBase.Position); - pc.Notify(string.Format(Translator.GetString("CTF_Notify_EnemyTeamRoom"), yellowFlagBase.RoomName)); - break; - case CTFTeam.Yellow: - pc.TP(yellowFlagBase.Position); - pc.Notify(string.Format(Translator.GetString("CTF_Notify_EnemyTeamRoom"), blueFlagBase.RoomName)); - break; - } - } - - pc.CheckAndSetUnshiftState(force: true); - pc.RpcResetAbilityCooldown(); - } - - ValidTag = true; - LateTask.New(() => Main.ProcessShapeshifts = true, 3f, log: false); - }, 12f, "CTFManager.OnGameStart"); - } - - public static void OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!ValidTag || !PlayerTeams.TryGetValue(target.PlayerId, out var targetTeam) || !PlayerTeams.TryGetValue(killer.PlayerId, out var killerTeam) || killerTeam == targetTeam || TeamData.Values.Any(x => x.FlagCarrier == killer.PlayerId)) return; - - new[] { killer, target }.Do(x => x.SetKillCooldown(TagCooldown.GetFloat())); - - if (TeamData.FindFirst(x => x.Value.FlagCarrier == target.PlayerId, out KeyValuePair kvp)) - { - kvp.Value.DropFlag(); - if (WhenFlagCarrierGetsTagged.GetValue() == 1) kvp.Value.Flag.TP(kvp.Key.GetFlagBase().Position); - } - - switch (TaggedPlayersGet.GetValue()) - { - case 0: - target.TP(targetTeam.GetFlagBase().Position); - Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - target.MarkDirtySettings(); - break; - case 1: - target.Suicide(); - string notify = string.Format(Translator.GetString("CTF_TeamMemberFallen"), target.PlayerId.ColoredPlayerName()); - TeamData[targetTeam].Players.ToValidPlayers().Do(x => x.Notify(notify)); - break; - } - - if (PlayerData.TryGetValue(killer.PlayerId, out var data)) data.TagCount++; - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); - } - - public static void TryPickUpFlag(PlayerControl pc) - { - if (!ValidTag) return; - - Logger.Info($"Received flag pickup request from {pc.GetRealName()}", "CTF"); - // If the player is near the enemy's flag, pick it up - Vector2 pos = pc.Pos(); - CTFTeamData enemy = TeamData[PlayerTeams[pc.PlayerId].GetOppositeTeam()]; - if (enemy.IsNearFlag(pos)) enemy.PickUpFlag(pc.PlayerId); - } - - public static void ApplyGameOptions(IGameOptions opt) - { - AURoleOptions.ShapeshifterCooldown = 1f; - } - - private static Color GetTeamColor(this CTFTeam team) - { - return team switch - { - CTFTeam.Blue => Color.blue, - CTFTeam.Yellow => Color.yellow, - _ => Color.white - }; - } - - private static string GetTeamName(this CTFTeam team) - { - return team switch - { - CTFTeam.Blue => Translator.GetString("CTF_BlueTeamWins"), - CTFTeam.Yellow => Translator.GetString("CTF_YellowTeamWins"), - _ => string.Empty - }; - } - - private static CTFTeam GetOppositeTeam(this CTFTeam team) - { - return team switch - { - CTFTeam.Blue => CTFTeam.Yellow, - CTFTeam.Yellow => CTFTeam.Blue, - _ => CTFTeam.Blue - }; - } - - private static (Vector2 Position, string RoomName) GetFlagBase(this CTFTeam team) - { - return team switch - { - CTFTeam.Blue => BlueFlagBase, - CTFTeam.Yellow => YellowFlagBase, - _ => (Vector2.zero, string.Empty) - }; - } - - private enum CTFTeam - { - Blue, - Yellow - } - - private class CTFTeamData(CTFTeam team, CustomNetObject flag, HashSet players, byte flagCarrier) - { - public CustomNetObject Flag { get; } = flag; - public HashSet Players { get; } = players; - public byte FlagCarrier { get; private set; } = flagCarrier; - - public void SetAsWinner() - { - WinnerData = (team.GetTeamColor(), team.GetTeamName()); - CustomWinnerHolder.WinnerIds = Players; - Logger.Info($"{team} team wins", "CTF"); - } - - public void Update() - { - try - { - if (FlagCarrier == byte.MaxValue) return; - - PlayerControl flagCarrierPc = FlagCarrier.GetPlayer(); - - if (flagCarrierPc == null || !flagCarrierPc.IsAlive()) - { - DropFlag(); - return; - } - - Flag.TP(flagCarrierPc.Pos()); - if (PlayerData.TryGetValue(FlagCarrier, out var data)) data.FlagTime += Time.fixedDeltaTime; - Utils.NotifyRoles(SpecifySeer: flagCarrierPc, SpecifyTarget: flagCarrierPc); - - CTFTeam enemy = team.GetOppositeTeam(); - PlainShipRoom flagRoom = Flag.playerControl.GetPlainShipRoom(); - - if (flagRoom != null && Translator.GetString(flagRoom.RoomId.ToString()) == enemy.GetFlagBase().RoomName) - TeamData[enemy].SetAsWinner(); - } - catch { } - } - - public void PickUpFlag(byte id) - { - if (FlagCarrier == id) return; - - FlagCarrier = id; - Update(); - - Logger.Info($"{id.ColoredPlayerName().RemoveHtmlTags()} picked up the {team} flag", "CTF"); - - Main.AllPlayerSpeed[id] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod) - SpeedReductionForFlagCarrier.GetFloat(); - PlayerGameOptionsSender.SetDirty(id); - - if (AlertTeamMembersOfFlagTaken.GetBool()) - { - bool arrow = ArrowToEnemyFlagCarrier.GetBool(); - - TeamData[team].Players - .Select(x => x.GetPlayer()) - .DoIf(x => x != null, x => - { - if (arrow) TargetArrow.Add(x.PlayerId, id); - x.Notify(Utils.ColorString(Color.yellow, Translator.GetString("CTF_FlagTaken"))); - }); - } - - if (AlertTeamMembersOfEnemyFlagTaken.GetBool()) - { - bool arrow = ArrowToOwnFlagCarrier.GetBool(); - - TeamData[team.GetOppositeTeam()].Players - .ToValidPlayers() - .Do(x => - { - if (arrow) TargetArrow.Add(x.PlayerId, id); - x.Notify(Translator.GetString("CTF_EnemyFlagTaken")); - }); - } - } - - public void DropFlag() - { - Logger.Info($"{FlagCarrier.ColoredPlayerName().RemoveHtmlTags()} dropped the {team} flag", "CTF"); - if (ArrowToEnemyFlagCarrier.GetBool() || ArrowToOwnFlagCarrier.GetBool()) TeamData.Values.SelectMany(x => x.Players).Do(x => TargetArrow.Remove(x, FlagCarrier)); - - FlagCarrier = byte.MaxValue; - Utils.NotifyRoles(); - } - - public bool IsNearFlag(Vector2 pos) - { - return Vector2.Distance(Flag.Position, pos) < 1f; + if (arrow) TargetArrow.Add(x.PlayerId, id); + x.Notify(Translator.GetString("CTF_EnemyFlagTaken")); + }); } } - private class CTFPlayerData + public void DropFlag() { - public int TagCount { get; set; } - public float FlagTime { get; set; } + Logger.Info($"{FlagCarrier.ColoredPlayerName().RemoveHtmlTags()} dropped the {team} flag", "CTF"); + if (ArrowToEnemyFlagCarrier.GetBool() || ArrowToOwnFlagCarrier.GetBool()) TeamData.Values.SelectMany(x => x.Players).Do(x => TargetArrow.Remove(x, FlagCarrier)); + + FlagCarrier = byte.MaxValue; + Utils.NotifyRoles(); } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch + public bool IsNearFlag(Vector2 pos) { - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated() || Main.HasJustStarted || !__instance.IsHost()) return; + return Vector2.Distance(Flag.Position, pos) < 1f; + } + } - TeamData.Values.Do(x => x.Update()); - } + private class CTFPlayerData + { + public int TagCount { get; set; } + public float FlagTime { get; set; } + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated() || Main.HasJustStarted || !__instance.IsHost()) return; + + TeamData.Values.Do(x => x.Update()); } } } \ No newline at end of file diff --git a/GameMode/FFAManager.cs b/GameMode/FFAManager.cs index 7d741d5da..6dab77f34 100644 --- a/GameMode/FFAManager.cs +++ b/GameMode/FFAManager.cs @@ -7,478 +7,477 @@ using HarmonyLib; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +internal static class FFAManager { - internal static class FFAManager + private static Dictionary FFAShieldedList = []; + private static Dictionary FFAIncreasedSpeedList = []; + private static Dictionary FFADecreasedSpeedList = []; + public static Dictionary FFALowerVisionList = []; + + private static Dictionary OriginalSpeed = []; + public static Dictionary KillCount = []; + public static int RoundTime; + + private static string LatestChatMessage = string.Empty; + + public static Dictionary PlayerTeams = []; + + public static readonly Dictionary TeamColors = new() { - private static Dictionary FFAShieldedList = []; - private static Dictionary FFAIncreasedSpeedList = []; - private static Dictionary FFADecreasedSpeedList = []; - public static Dictionary FFALowerVisionList = []; + { 0, "#00ffff" }, + { 1, "#ffff00" }, + { 2, "#ff00ff" }, + { 3, "#ff0000" }, + { 4, "#00ff00" }, + { 5, "#0000ff" }, + { 6, "#ffffff" }, + { 7, "#000000" } + }; - private static Dictionary OriginalSpeed = []; - public static Dictionary KillCount = []; - public static int RoundTime; + public static OptionItem FFAGameTime; + public static OptionItem FFAKcd; + public static OptionItem FFALowerVision; + public static OptionItem FFAIncreasedSpeed; + public static OptionItem FFADecreasedSpeed; + public static OptionItem FFAShieldDuration; + public static OptionItem FFAModifiedVisionDuration; + public static OptionItem FFAModifiedSpeedDuration; + public static OptionItem FFADisableVentingWhenTwoPlayersAlive; + public static OptionItem FFADisableVentingWhenKcdIsUp; + public static OptionItem FFAEnableRandomAbilities; + public static OptionItem FFAEnableRandomTwists; + public static OptionItem FFAShieldIsOneTimeUse; + public static OptionItem FFAChatDuringGame; + public static OptionItem FFATeamMode; + public static OptionItem FFATeamNumber; - private static string LatestChatMessage = string.Empty; + public static void SetupCustomOption() + { + FFAGameTime = new IntegerOptionItem(67_223_001, "FFA_GameTime", new(30, 600, 10), 300, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetHeader(true); - public static Dictionary PlayerTeams = []; + FFAKcd = new FloatOptionItem(67_223_002, "FFA_KCD", new(1f, 60f, 1f), 10f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); - public static readonly Dictionary TeamColors = new() + FFADisableVentingWhenTwoPlayersAlive = new BooleanOptionItem(67_223_003, "FFA_DisableVentingWhenTwoPlayersAlive", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFADisableVentingWhenKcdIsUp = new BooleanOptionItem(67_223_004, "FFA_DisableVentingWhenKCDIsUp", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFAEnableRandomAbilities = new BooleanOptionItem(67_223_005, "FFA_EnableRandomAbilities", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFAShieldDuration = new FloatOptionItem(67_223_006, "FFA_ShieldDuration", new(1f, 70f, 1f), 7f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + FFAIncreasedSpeed = new FloatOptionItem(67_223_007, "FFA_IncreasedSpeed", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier); + + FFADecreasedSpeed = new FloatOptionItem(67_223_008, "FFA_DecreasedSpeed", new(0.1f, 5f, 0.1f), 1f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier); + + FFAModifiedSpeedDuration = new FloatOptionItem(67_223_009, "FFA_ModifiedSpeedDuration", new(1f, 60f, 1f), 10f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + FFALowerVision = new FloatOptionItem(67_223_010, "FFA_LowerVision", new(0f, 1f, 0.05f), 0.5f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier); + + FFAModifiedVisionDuration = new FloatOptionItem(67_223_011, "FFA_ModifiedVisionDuration", new(1f, 70f, 1f), 5f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + FFAEnableRandomTwists = new BooleanOptionItem(67_223_012, "FFA_EnableRandomTwists", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFAShieldIsOneTimeUse = new BooleanOptionItem(67_223_013, "FFA_ShieldIsOneTimeUse", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFAChatDuringGame = new BooleanOptionItem(67_223_014, "FFA_ChatDuringGame", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFATeamMode = new BooleanOptionItem(67_223_015, "FFA_TeamMode", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.FFA) + .SetHeader(true) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + + FFATeamNumber = new IntegerOptionItem(67_223_016, "FFA_TeamNumber", new(2, 8, 1), 2, TabGroup.GameSettings) + .SetParent(FFATeamMode) + .SetGameMode(CustomGameMode.FFA) + .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + } + + public static void Init() + { + if (!CustomGameMode.FFA.IsActiveOrIntegrated()) return; + + FFADecreasedSpeedList = []; + FFAIncreasedSpeedList = []; + FFALowerVisionList = []; + FFAShieldedList = []; + + OriginalSpeed = []; + KillCount = []; + RoundTime = FFAGameTime.GetInt() + 8; + + PlayerTeams = []; + + PlayerControl[] allPlayers = Main.AllAlivePlayerControls; + if (Main.GM.Value && AmongUsClient.Instance.AmHost) allPlayers = allPlayers.Without(PlayerControl.LocalPlayer).ToArray(); + + foreach (PlayerControl pc in allPlayers) KillCount[pc.PlayerId] = 0; + + if (FFATeamMode.GetBool()) { - { 0, "#00ffff" }, - { 1, "#ffff00" }, - { 2, "#ff00ff" }, - { 3, "#ff0000" }, - { 4, "#00ff00" }, - { 5, "#0000ff" }, - { 6, "#ffffff" }, - { 7, "#000000" } - }; + int teamNum = FFATeamNumber.GetInt(); + int playerNum = allPlayers.Length; + int memberNum = (teamNum > 5 && playerNum >= 15) || playerNum % teamNum == 0 ? playerNum / teamNum : (playerNum / teamNum) + 1; + List teamMembers = allPlayers.Select(x => x.PlayerId).Shuffle().Chunk(memberNum).ToList(); - public static OptionItem FFAGameTime; - public static OptionItem FFAKcd; - public static OptionItem FFALowerVision; - public static OptionItem FFAIncreasedSpeed; - public static OptionItem FFADecreasedSpeed; - public static OptionItem FFAShieldDuration; - public static OptionItem FFAModifiedVisionDuration; - public static OptionItem FFAModifiedSpeedDuration; - public static OptionItem FFADisableVentingWhenTwoPlayersAlive; - public static OptionItem FFADisableVentingWhenKcdIsUp; - public static OptionItem FFAEnableRandomAbilities; - public static OptionItem FFAEnableRandomTwists; - public static OptionItem FFAShieldIsOneTimeUse; - public static OptionItem FFAChatDuringGame; - public static OptionItem FFATeamMode; - public static OptionItem FFATeamNumber; + for (var i = 0; i < teamMembers.Count; i++) + foreach (byte id in teamMembers[i]) + PlayerTeams.Add(id, i); - public static void SetupCustomOption() + LateTask.New(() => Utils.NotifyRoles(NoCache: true, ForceLoop: true), 10f, log: false); + } + } + + public static int GetRankFromScore(byte playerId) + { + try { - FFAGameTime = new IntegerOptionItem(67_223_001, "FFA_GameTime", new(30, 600, 10), 300, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds) - .SetHeader(true); + int ms = KillCount[playerId]; + int rank = 1 + KillCount.Values.Count(x => x > ms); + rank += KillCount.Where(x => x.Value == ms).Select(x => x.Key).ToList().IndexOf(playerId); // In the old version, the struct 'KeyValuePair' was checked for equality using the inefficient runtime-provided implementation + return rank; + } + catch + { + return Main.AllPlayerControls.Length; + } + } - FFAKcd = new FloatOptionItem(67_223_002, "FFA_KCD", new(1f, 60f, 1f), 10f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); + public static string GetHudText() + { + return string.Format(GetString("KBTimeRemain"), RoundTime.ToString()); + } - FFADisableVentingWhenTwoPlayersAlive = new BooleanOptionItem(67_223_003, "FFA_DisableVentingWhenTwoPlayersAlive", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || !CustomGameMode.FFA.IsActiveOrIntegrated()) return; - FFADisableVentingWhenKcdIsUp = new BooleanOptionItem(67_223_004, "FFA_DisableVentingWhenKCDIsUp", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); - - FFAEnableRandomAbilities = new BooleanOptionItem(67_223_005, "FFA_EnableRandomAbilities", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); - - FFAShieldDuration = new FloatOptionItem(67_223_006, "FFA_ShieldDuration", new(1f, 70f, 1f), 7f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - FFAIncreasedSpeed = new FloatOptionItem(67_223_007, "FFA_IncreasedSpeed", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Multiplier); - - FFADecreasedSpeed = new FloatOptionItem(67_223_008, "FFA_DecreasedSpeed", new(0.1f, 5f, 0.1f), 1f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Multiplier); - - FFAModifiedSpeedDuration = new FloatOptionItem(67_223_009, "FFA_ModifiedSpeedDuration", new(1f, 60f, 1f), 10f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - FFALowerVision = new FloatOptionItem(67_223_010, "FFA_LowerVision", new(0f, 1f, 0.05f), 0.5f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Multiplier); - - FFAModifiedVisionDuration = new FloatOptionItem(67_223_011, "FFA_ModifiedVisionDuration", new(1f, 70f, 1f), 5f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA).SetColor(new Color32(0, 255, 165, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - FFAEnableRandomTwists = new BooleanOptionItem(67_223_012, "FFA_EnableRandomTwists", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); - - FFAShieldIsOneTimeUse = new BooleanOptionItem(67_223_013, "FFA_ShieldIsOneTimeUse", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); - - FFAChatDuringGame = new BooleanOptionItem(67_223_014, "FFA_ChatDuringGame", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); - - FFATeamMode = new BooleanOptionItem(67_223_015, "FFA_TeamMode", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.FFA) - .SetHeader(true) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); - - FFATeamNumber = new IntegerOptionItem(67_223_016, "FFA_TeamNumber", new(2, 8, 1), 2, TabGroup.GameSettings) - .SetParent(FFATeamMode) - .SetGameMode(CustomGameMode.FFA) - .SetColor(new Color32(0, 255, 165, byte.MaxValue)); + if (target.inVent) + { + Logger.Info("Target is in a vent, kill blocked", "FFA"); + return; } - public static void Init() + if (FFATeamMode.GetBool() && PlayerTeams[killer.PlayerId] == PlayerTeams[target.PlayerId]) { - if (!CustomGameMode.FFA.IsActiveOrIntegrated()) return; - - FFADecreasedSpeedList = []; - FFAIncreasedSpeedList = []; - FFALowerVisionList = []; - FFAShieldedList = []; - - OriginalSpeed = []; - KillCount = []; - RoundTime = FFAGameTime.GetInt() + 8; - - PlayerTeams = []; - - PlayerControl[] allPlayers = Main.AllAlivePlayerControls; - if (Main.GM.Value && AmongUsClient.Instance.AmHost) allPlayers = allPlayers.Without(PlayerControl.LocalPlayer).ToArray(); - - foreach (PlayerControl pc in allPlayers) KillCount[pc.PlayerId] = 0; - - if (FFATeamMode.GetBool()) - { - int teamNum = FFATeamNumber.GetInt(); - int playerNum = allPlayers.Length; - int memberNum = (teamNum > 5 && playerNum >= 15) || playerNum % teamNum == 0 ? playerNum / teamNum : (playerNum / teamNum) + 1; - List teamMembers = allPlayers.Select(x => x.PlayerId).Shuffle().Chunk(memberNum).ToList(); - - for (var i = 0; i < teamMembers.Count; i++) - foreach (byte id in teamMembers[i]) - PlayerTeams.Add(id, i); - - LateTask.New(() => Utils.NotifyRoles(NoCache: true, ForceLoop: true), 10f, log: false); - } + Logger.Info("Killer and Target are in the same team, attack blocked", "FFA"); + return; } - public static int GetRankFromScore(byte playerId) + int totalalive = Main.AllAlivePlayerControls.Length; + + if (FFAShieldedList.TryGetValue(target.PlayerId, out long dur)) { - try + killer.Notify(GetString("FFA_TargetIsShielded")); + Logger.Info($"{killer.GetRealName().RemoveHtmlTags()} attacked shielded player {target.GetRealName().RemoveHtmlTags()}, their shield expires in {FFAShieldDuration.GetInt() - (Utils.TimeStamp - dur)}s", "FFA"); + + if (FFAShieldIsOneTimeUse.GetBool()) { - int ms = KillCount[playerId]; - int rank = 1 + KillCount.Values.Count(x => x > ms); - rank += KillCount.Where(x => x.Value == ms).Select(x => x.Key).ToList().IndexOf(playerId); // In the old version, the struct 'KeyValuePair' was checked for equality using the inefficient runtime-provided implementation - return rank; - } - catch - { - return Main.AllPlayerControls.Length; + FFAShieldedList.Remove(target.PlayerId); + target.Notify(GetString("FFA_ShieldBroken")); + Logger.Info($"{target.GetRealName().RemoveHtmlTags()}'s shield was removed because {killer.GetRealName().RemoveHtmlTags()} tried to kill them and the shield is one-time-use according to settings", "FFA"); } + + return; } - public static string GetHudText() + OnPlayerKill(killer); + + if (totalalive == 3) { - return string.Format(GetString("KBTimeRemain"), RoundTime.ToString()); + PlayerControl otherPC = null; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(a => a.PlayerId != killer.PlayerId && a.PlayerId != target.PlayerId && a.IsAlive())) + { + TargetArrow.Add(killer.PlayerId, pc.PlayerId); + TargetArrow.Add(pc.PlayerId, killer.PlayerId); + otherPC = pc; + } + + Logger.Info($"The last 2 players ({killer.GetRealName().RemoveHtmlTags()} & {otherPC?.GetRealName().RemoveHtmlTags()}) now have an arrow toward each other", "FFA"); } - public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) + if (FFAEnableRandomAbilities.GetBool()) { - if (killer == null || target == null || !CustomGameMode.FFA.IsActiveOrIntegrated()) return; + var sync = false; + var mark = false; + float nowKCD = Main.AllPlayerKillCooldown[killer.PlayerId]; + byte EffectType; - if (target.inVent) + if (Main.NormalOptions.MapId != 4) + EffectType = (byte)IRandom.Instance.Next(0, 10); + else + EffectType = (byte)IRandom.Instance.Next(4, 10); + + switch (EffectType) { - Logger.Info("Target is in a vent, kill blocked", "FFA"); - return; - } - - if (FFATeamMode.GetBool() && PlayerTeams[killer.PlayerId] == PlayerTeams[target.PlayerId]) - { - Logger.Info("Killer and Target are in the same team, attack blocked", "FFA"); - return; - } - - int totalalive = Main.AllAlivePlayerControls.Length; - - if (FFAShieldedList.TryGetValue(target.PlayerId, out long dur)) - { - killer.Notify(GetString("FFA_TargetIsShielded")); - Logger.Info($"{killer.GetRealName().RemoveHtmlTags()} attacked shielded player {target.GetRealName().RemoveHtmlTags()}, their shield expires in {FFAShieldDuration.GetInt() - (Utils.TimeStamp - dur)}s", "FFA"); - - if (FFAShieldIsOneTimeUse.GetBool()) + // Buff + case <= 7: { - FFAShieldedList.Remove(target.PlayerId); - target.Notify(GetString("FFA_ShieldBroken")); - Logger.Info($"{target.GetRealName().RemoveHtmlTags()}'s shield was removed because {killer.GetRealName().RemoveHtmlTags()} tried to kill them and the shield is one-time-use according to settings", "FFA"); - } + var EffectID = (byte)IRandom.Instance.Next(0, 3); + if (Main.NormalOptions.MapId == 4) EffectID = 2; - return; - } - - OnPlayerKill(killer); - - if (totalalive == 3) - { - PlayerControl otherPC = null; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(a => a.PlayerId != killer.PlayerId && a.PlayerId != target.PlayerId && a.IsAlive())) - { - TargetArrow.Add(killer.PlayerId, pc.PlayerId); - TargetArrow.Add(pc.PlayerId, killer.PlayerId); - otherPC = pc; - } - - Logger.Info($"The last 2 players ({killer.GetRealName().RemoveHtmlTags()} & {otherPC?.GetRealName().RemoveHtmlTags()}) now have an arrow toward each other", "FFA"); - } - - if (FFAEnableRandomAbilities.GetBool()) - { - var sync = false; - var mark = false; - float nowKCD = Main.AllPlayerKillCooldown[killer.PlayerId]; - byte EffectType; - - if (Main.NormalOptions.MapId != 4) - EffectType = (byte)IRandom.Instance.Next(0, 10); - else - EffectType = (byte)IRandom.Instance.Next(4, 10); - - switch (EffectType) - { - // Buff - case <= 7: + switch (EffectID) { - var EffectID = (byte)IRandom.Instance.Next(0, 3); - if (Main.NormalOptions.MapId == 4) EffectID = 2; + case 0: + FFAShieldedList.TryAdd(killer.PlayerId, Utils.TimeStamp); + killer.Notify(GetString("FFA-Event-GetShield"), FFAShieldDuration.GetFloat()); + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + break; + case 1: + if (FFAIncreasedSpeedList.ContainsKey(killer.PlayerId)) + { + FFAIncreasedSpeedList.Remove(killer.PlayerId); + FFAIncreasedSpeedList.Add(killer.PlayerId, Utils.TimeStamp); + } + else + { + FFAIncreasedSpeedList.TryAdd(killer.PlayerId, Utils.TimeStamp); + OriginalSpeed.TryAdd(killer.PlayerId, Main.AllPlayerSpeed[killer.PlayerId]); + Main.AllPlayerSpeed[killer.PlayerId] = FFAIncreasedSpeed.GetFloat(); + } - switch (EffectID) - { - case 0: - FFAShieldedList.TryAdd(killer.PlayerId, Utils.TimeStamp); - killer.Notify(GetString("FFA-Event-GetShield"), FFAShieldDuration.GetFloat()); - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - break; - case 1: - if (FFAIncreasedSpeedList.ContainsKey(killer.PlayerId)) - { - FFAIncreasedSpeedList.Remove(killer.PlayerId); - FFAIncreasedSpeedList.Add(killer.PlayerId, Utils.TimeStamp); - } - else - { - FFAIncreasedSpeedList.TryAdd(killer.PlayerId, Utils.TimeStamp); - OriginalSpeed.TryAdd(killer.PlayerId, Main.AllPlayerSpeed[killer.PlayerId]); - Main.AllPlayerSpeed[killer.PlayerId] = FFAIncreasedSpeed.GetFloat(); - } - - killer.Notify(GetString("FFA-Event-GetIncreasedSpeed"), FFAModifiedSpeedDuration.GetFloat()); - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - mark = true; - break; - case 2: - Main.AllPlayerKillCooldown[killer.PlayerId] = Math.Clamp(FFAKcd.GetFloat() - 3f, 1f, 60f); - killer.Notify(GetString("FFA-Event-GetLowKCD")); - sync = true; - break; - default: - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - break; - } - - break; - } - // De-Buff - case 8: - { - var EffectID = (byte)IRandom.Instance.Next(0, 3); - if (Main.NormalOptions.MapId == 4) EffectID = 1; - - switch (EffectID) - { - case 0: - if (FFADecreasedSpeedList.ContainsKey(killer.PlayerId)) - { - FFADecreasedSpeedList.Remove(killer.PlayerId); - FFADecreasedSpeedList.Add(killer.PlayerId, Utils.TimeStamp); - } - else - { - FFADecreasedSpeedList.TryAdd(killer.PlayerId, Utils.TimeStamp); - OriginalSpeed.TryAdd(killer.PlayerId, Main.AllPlayerSpeed[killer.PlayerId]); - Main.AllPlayerSpeed[killer.PlayerId] = FFADecreasedSpeed.GetFloat(); - } - - killer.Notify(GetString("FFA-Event-GetDecreasedSpeed"), FFAModifiedSpeedDuration.GetFloat()); - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - mark = true; - break; - case 1: - Main.AllPlayerKillCooldown[killer.PlayerId] = Math.Clamp(FFAKcd.GetFloat() + 3f, 1f, 60f); - killer.Notify(GetString("FFA-Event-GetHighKCD")); - sync = true; - break; - case 2: - FFALowerVisionList.TryAdd(killer.PlayerId, Utils.TimeStamp); - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - killer.Notify(GetString("FFA-Event-GetLowVision")); - mark = true; - break; - default: - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - break; - } - - break; - } - // Mixed - default: - LateTask.New(() => { killer.TPToRandomVent(); }, 0.5f, "FFA-Event-TP"); - killer.Notify(GetString("FFA-Event-GetTP")); - Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); - break; - } - - if (sync || Math.Abs(nowKCD - Main.AllPlayerKillCooldown[killer.PlayerId]) > 0.1f) - { - mark = false; - killer.SyncSettings(); - } - - if (mark) killer.MarkDirtySettings(); - } - - killer.Kill(target); - } - - private static void OnPlayerKill(PlayerControl killer) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) PlayerControl.LocalPlayer.KillFlash(); - - KillCount[killer.PlayerId]++; - } - - public static string GetPlayerArrow(PlayerControl seer, PlayerControl target = null) - { - if (GameStates.IsMeeting) return string.Empty; - - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (Main.AllAlivePlayerControls.Length != 2) return string.Empty; - - var arrows = string.Empty; - PlayerControl otherPlayer = Main.AllAlivePlayerControls.FirstOrDefault(pc => pc.IsAlive() && pc.PlayerId != seer.PlayerId); - - if (otherPlayer == null) return string.Empty; - - string arrow = TargetArrow.GetArrows(seer, otherPlayer.PlayerId); - arrows += Utils.ColorString(Utils.GetRoleColor(CustomRoles.Killer), arrow); - - return arrows; - } - - public static void UpdateLastChatMessage(string playerName, string message) - { - LatestChatMessage = string.Format(GetString("FFAChatMessageNotify"), playerName, message); - foreach (PlayerControl pc in Main.AllAlivePlayerControls) pc.Notify(LatestChatMessage); - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch - { - private static long LastFixedUpdate; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix() - { - if (!GameStates.IsInTask || ExileController.Instance || !CustomGameMode.FFA.IsActiveOrIntegrated() || !AmongUsClient.Instance.AmHost) return; - - long now = Utils.TimeStamp; - if (LastFixedUpdate == now) return; - LastFixedUpdate = now; - - RoundTime--; - - var rd = IRandom.Instance; - var FFAdoTPdecider = (byte)rd.Next(0, 100); - bool FFAdoTP = FFAdoTPdecider == 0; - - if (FFAEnableRandomTwists.GetBool() && FFAdoTP) - { - Logger.Info("Swap everyone with someone", "FFA"); - - List changePositionPlayers = []; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (changePositionPlayers.Contains(pc.PlayerId) || !pc.IsAlive() || pc.onLadder || pc.inVent || pc.inMovingPlat) continue; - - PlayerControl[] filtered = Main.AllAlivePlayerControls.Where(a => - pc.IsAlive() && !pc.inVent && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); - - if (filtered.Length == 0) break; - - PlayerControl target = filtered.RandomElement(); - - if (pc.inVent || target.inVent) continue; - - changePositionPlayers.Add(target.PlayerId); - changePositionPlayers.Add(pc.PlayerId); - - pc.RPCPlayCustomSound("Teleport"); - - Vector2 originPs = target.Pos(); - target.TP(pc.Pos()); - pc.TP(originPs); - - target.Notify(Utils.ColorString(new(0, 255, 165, byte.MaxValue), string.Format(GetString("FFA-Event-RandomTP"), pc.GetRealName()))); - pc.Notify(Utils.ColorString(new(0, 255, 165, byte.MaxValue), string.Format(GetString("FFA-Event-RandomTP"), target.GetRealName()))); + killer.Notify(GetString("FFA-Event-GetIncreasedSpeed"), FFAModifiedSpeedDuration.GetFloat()); + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + mark = true; + break; + case 2: + Main.AllPlayerKillCooldown[killer.PlayerId] = Math.Clamp(FFAKcd.GetFloat() - 3f, 1f, 60f); + killer.Notify(GetString("FFA-Event-GetLowKCD")); + sync = true; + break; + default: + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + break; } - changePositionPlayers.Clear(); + break; } + // De-Buff + case 8: + { + var EffectID = (byte)IRandom.Instance.Next(0, 3); + if (Main.NormalOptions.MapId == 4) EffectID = 1; - if (Main.NormalOptions.MapId == 4) return; + switch (EffectID) + { + case 0: + if (FFADecreasedSpeedList.ContainsKey(killer.PlayerId)) + { + FFADecreasedSpeedList.Remove(killer.PlayerId); + FFADecreasedSpeedList.Add(killer.PlayerId, Utils.TimeStamp); + } + else + { + FFADecreasedSpeedList.TryAdd(killer.PlayerId, Utils.TimeStamp); + OriginalSpeed.TryAdd(killer.PlayerId, Main.AllPlayerSpeed[killer.PlayerId]); + Main.AllPlayerSpeed[killer.PlayerId] = FFADecreasedSpeed.GetFloat(); + } + + killer.Notify(GetString("FFA-Event-GetDecreasedSpeed"), FFAModifiedSpeedDuration.GetFloat()); + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + mark = true; + break; + case 1: + Main.AllPlayerKillCooldown[killer.PlayerId] = Math.Clamp(FFAKcd.GetFloat() + 3f, 1f, 60f); + killer.Notify(GetString("FFA-Event-GetHighKCD")); + sync = true; + break; + case 2: + FFALowerVisionList.TryAdd(killer.PlayerId, Utils.TimeStamp); + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + killer.Notify(GetString("FFA-Event-GetLowVision")); + mark = true; + break; + default: + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + break; + } + + break; + } + // Mixed + default: + LateTask.New(() => { killer.TPToRandomVent(); }, 0.5f, "FFA-Event-TP"); + killer.Notify(GetString("FFA-Event-GetTP")); + Main.AllPlayerKillCooldown[killer.PlayerId] = FFAKcd.GetFloat(); + break; + } + + if (sync || Math.Abs(nowKCD - Main.AllPlayerKillCooldown[killer.PlayerId]) > 0.1f) + { + mark = false; + killer.SyncSettings(); + } + + if (mark) killer.MarkDirtySettings(); + } + + killer.Kill(target); + } + + private static void OnPlayerKill(PlayerControl killer) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) PlayerControl.LocalPlayer.KillFlash(); + + KillCount[killer.PlayerId]++; + } + + public static string GetPlayerArrow(PlayerControl seer, PlayerControl target = null) + { + if (GameStates.IsMeeting) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + if (Main.AllAlivePlayerControls.Length != 2) return string.Empty; + + var arrows = string.Empty; + PlayerControl otherPlayer = Main.AllAlivePlayerControls.FirstOrDefault(pc => pc.IsAlive() && pc.PlayerId != seer.PlayerId); + + if (otherPlayer == null) return string.Empty; + + string arrow = TargetArrow.GetArrows(seer, otherPlayer.PlayerId); + arrows += Utils.ColorString(Utils.GetRoleColor(CustomRoles.Killer), arrow); + + return arrows; + } + + public static void UpdateLastChatMessage(string playerName, string message) + { + LatestChatMessage = string.Format(GetString("FFAChatMessageNotify"), playerName, message); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) pc.Notify(LatestChatMessage); + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + private static long LastFixedUpdate; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix() + { + if (!GameStates.IsInTask || ExileController.Instance || !CustomGameMode.FFA.IsActiveOrIntegrated() || !AmongUsClient.Instance.AmHost) return; + + long now = Utils.TimeStamp; + if (LastFixedUpdate == now) return; + LastFixedUpdate = now; + + RoundTime--; + + var rd = IRandom.Instance; + var FFAdoTPdecider = (byte)rd.Next(0, 100); + bool FFAdoTP = FFAdoTPdecider == 0; + + if (FFAEnableRandomTwists.GetBool() && FFAdoTP) + { + Logger.Info("Swap everyone with someone", "FFA"); + + List changePositionPlayers = []; foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (pc == null) return; + if (changePositionPlayers.Contains(pc.PlayerId) || !pc.IsAlive() || pc.onLadder || pc.inVent || pc.inMovingPlat) continue; - var sync = false; + PlayerControl[] filtered = Main.AllAlivePlayerControls.Where(a => + pc.IsAlive() && !pc.inVent && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); - if (FFADecreasedSpeedList.TryGetValue(pc.PlayerId, out long dstime) && dstime + FFAModifiedSpeedDuration.GetInt() < now) - { - Logger.Info(pc.GetRealName() + "'s decreased speed expired", "FFA"); - FFADecreasedSpeedList.Remove(pc.PlayerId); - Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed[pc.PlayerId]; - OriginalSpeed.Remove(pc.PlayerId); - sync = true; - } + if (filtered.Length == 0) break; - if (FFAIncreasedSpeedList.TryGetValue(pc.PlayerId, out long istime) && istime + FFAModifiedSpeedDuration.GetInt() < now) - { - Logger.Info(pc.GetRealName() + "'s increased speed expired", "FFA"); - FFAIncreasedSpeedList.Remove(pc.PlayerId); - Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed[pc.PlayerId]; - OriginalSpeed.Remove(pc.PlayerId); - sync = true; - } + PlayerControl target = filtered.RandomElement(); - if (FFALowerVisionList.TryGetValue(pc.PlayerId, out long lvtime) && lvtime + FFAModifiedVisionDuration.GetInt() < now) - { - Logger.Info(pc.GetRealName() + "'s lower vision effect expired", "FFA"); - FFALowerVisionList.Remove(pc.PlayerId); - sync = true; - } + if (pc.inVent || target.inVent) continue; - if (FFAShieldedList.TryGetValue(pc.PlayerId, out long stime) && stime + FFAShieldDuration.GetInt() < now) - { - Logger.Info(pc.GetRealName() + "'s shield expired", "FFA"); - FFAShieldedList.Remove(pc.PlayerId); - } + changePositionPlayers.Add(target.PlayerId); + changePositionPlayers.Add(pc.PlayerId); - if (sync) pc.MarkDirtySettings(); + pc.RPCPlayCustomSound("Teleport"); + + Vector2 originPs = target.Pos(); + target.TP(pc.Pos()); + pc.TP(originPs); + + target.Notify(Utils.ColorString(new(0, 255, 165, byte.MaxValue), string.Format(GetString("FFA-Event-RandomTP"), pc.GetRealName()))); + pc.Notify(Utils.ColorString(new(0, 255, 165, byte.MaxValue), string.Format(GetString("FFA-Event-RandomTP"), target.GetRealName()))); } + + changePositionPlayers.Clear(); + } + + if (Main.NormalOptions.MapId == 4) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc == null) return; + + var sync = false; + + if (FFADecreasedSpeedList.TryGetValue(pc.PlayerId, out long dstime) && dstime + FFAModifiedSpeedDuration.GetInt() < now) + { + Logger.Info(pc.GetRealName() + "'s decreased speed expired", "FFA"); + FFADecreasedSpeedList.Remove(pc.PlayerId); + Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed[pc.PlayerId]; + OriginalSpeed.Remove(pc.PlayerId); + sync = true; + } + + if (FFAIncreasedSpeedList.TryGetValue(pc.PlayerId, out long istime) && istime + FFAModifiedSpeedDuration.GetInt() < now) + { + Logger.Info(pc.GetRealName() + "'s increased speed expired", "FFA"); + FFAIncreasedSpeedList.Remove(pc.PlayerId); + Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed[pc.PlayerId]; + OriginalSpeed.Remove(pc.PlayerId); + sync = true; + } + + if (FFALowerVisionList.TryGetValue(pc.PlayerId, out long lvtime) && lvtime + FFAModifiedVisionDuration.GetInt() < now) + { + Logger.Info(pc.GetRealName() + "'s lower vision effect expired", "FFA"); + FFALowerVisionList.Remove(pc.PlayerId); + sync = true; + } + + if (FFAShieldedList.TryGetValue(pc.PlayerId, out long stime) && stime + FFAShieldDuration.GetInt() < now) + { + Logger.Info(pc.GetRealName() + "'s shield expired", "FFA"); + FFAShieldedList.Remove(pc.PlayerId); + } + + if (sync) pc.MarkDirtySettings(); } } } diff --git a/GameMode/HideAndSeekRoles/Agent.cs b/GameMode/HideAndSeekRoles/Agent.cs index 0236bec4a..968298894 100644 --- a/GameMode/HideAndSeekRoles/Agent.cs +++ b/GameMode/HideAndSeekRoles/Agent.cs @@ -1,44 +1,43 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Agent : RoleBase, IHideAndSeekRole { - public class Agent : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem Vision; + public static OptionItem Speed; + + public override bool IsEnable => On; + public Team Team => Team.Impostor; + public int Chance => CustomRoles.Agent.GetMode(); + public int Count => CustomRoles.Agent.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_2001, TabGroup.ImpostorRoles, CustomRoles.Agent, CustomGameMode.HideAndSeek); - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_2003, "AgentVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(255, 143, 143, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agent]); - public override bool IsEnable => On; - public Team Team => Team.Impostor; - public int Chance => CustomRoles.Agent.GetMode(); - public int Count => CustomRoles.Agent.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_213_2004, "AgentSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(255, 143, 143, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agent]); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(69_211_2001, TabGroup.ImpostorRoles, CustomRoles.Agent, CustomGameMode.HideAndSeek); + public override void Add(byte playerId) + { + On = true; + } - Vision = new FloatOptionItem(69_211_2003, "AgentVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(255, 143, 143, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agent]); - - Speed = new FloatOptionItem(69_213_2004, "AgentSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(255, 143, 143, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agent]); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } + public override void Init() + { + On = false; } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Dasher.cs b/GameMode/HideAndSeekRoles/Dasher.cs index 1a403f555..0580e3e2e 100644 --- a/GameMode/HideAndSeekRoles/Dasher.cs +++ b/GameMode/HideAndSeekRoles/Dasher.cs @@ -1,116 +1,115 @@ using AmongUs.GameOptions; -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Dasher : RoleBase, IHideAndSeekRole { - public class Dasher : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem DashCooldown; + public static OptionItem DashDuration; + public static OptionItem DashSpeed; + public static OptionItem UseLimit; + public static OptionItem Vision; + public static OptionItem Speed; + + private DashStatus DashStatus; + + public override bool IsEnable => On; + public Team Team => Team.Impostor; + public int Chance => CustomRoles.Dasher.GetMode(); + public int Count => CustomRoles.Dasher.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_801, TabGroup.ImpostorRoles, CustomRoles.Dasher, CustomGameMode.HideAndSeek); - public static OptionItem DashCooldown; - public static OptionItem DashDuration; - public static OptionItem DashSpeed; - public static OptionItem UseLimit; - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_803, "DasherVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(245, 66, 176, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); - private DashStatus DashStatus; + Speed = new FloatOptionItem(69_213_804, "DasherSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(245, 66, 176, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); - public override bool IsEnable => On; - public Team Team => Team.Impostor; - public int Chance => CustomRoles.Dasher.GetMode(); - public int Count => CustomRoles.Dasher.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + DashCooldown = new FloatOptionItem(69_213_805, "DasherCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(245, 66, 176, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); - public override void SetupCustomOption() + DashDuration = new FloatOptionItem(69_213_806, "DasherDuration", new(1f, 30f, 1f), 5f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(245, 66, 176, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + + DashSpeed = new FloatOptionItem(69_213_807, "DasherSpeedIncreased", new(0.05f, 5f, 0.05f), 1.5f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(245, 66, 176, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + + UseLimit = new IntegerOptionItem(69_213_808, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(245, 66, 176, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(UseLimit.GetFloat()); + + DashStatus = new() { - Options.SetupRoleOptions(69_211_801, TabGroup.ImpostorRoles, CustomRoles.Dasher, CustomGameMode.HideAndSeek); + Cooldown = DashCooldown.GetInt(), + Duration = DashDuration.GetInt() + }; + } - Vision = new FloatOptionItem(69_211_803, "DasherVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(245, 66, 176, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + public override void Init() + { + On = false; + } - Speed = new FloatOptionItem(69_213_804, "DasherSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(245, 66, 176, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } - DashCooldown = new FloatOptionItem(69_213_805, "DasherCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(245, 66, 176, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + Main.AllPlayerSpeed[playerId] = DashStatus.IsDashing ? DashSpeed.GetFloat() : HnSManager.IsBlindTime ? Main.MinSpeed : RoleSpeed; + } - DashDuration = new FloatOptionItem(69_213_806, "DasherDuration", new(1f, 30f, 1f), 5f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(245, 66, 176, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + public override void OnFixedUpdate(PlayerControl pc) + { + if (!DashStatus.IsDashing) return; - DashSpeed = new FloatOptionItem(69_213_807, "DasherSpeedIncreased", new(0.05f, 5f, 0.05f), 1.5f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(245, 66, 176, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); + long now = Utils.TimeStamp; - UseLimit = new IntegerOptionItem(69_213_808, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(245, 66, 176, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dasher]); - } - - public override void Add(byte playerId) + if (DashStatus.DashEndTime <= now) { - On = true; - playerId.SetAbilityUseLimit(UseLimit.GetFloat()); - - DashStatus = new() - { - Cooldown = DashCooldown.GetInt(), - Duration = DashDuration.GetInt() - }; - } - - public override void Init() - { - On = false; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - Main.AllPlayerSpeed[playerId] = DashStatus.IsDashing ? DashSpeed.GetFloat() : HnSManager.IsBlindTime ? Main.MinSpeed : RoleSpeed; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!DashStatus.IsDashing) return; - - long now = Utils.TimeStamp; - - if (DashStatus.DashEndTime <= now) - { - DashStatus.IsDashing = false; - pc.MarkDirtySettings(); - } - } - - public override void OnPet(PlayerControl pc) - { - if (pc.HasAbilityCD() || DashStatus.IsDashing || pc.GetAbilityUseLimit() < 1f) return; - - DashStatus.IsDashing = true; - DashStatus.DashEndTime = Utils.TimeStamp + DashStatus.Duration; - pc.AddAbilityCD(DashStatus.Cooldown + DashStatus.Duration); + DashStatus.IsDashing = false; pc.MarkDirtySettings(); - pc.RpcRemoveAbilityUse(); } } + + public override void OnPet(PlayerControl pc) + { + if (pc.HasAbilityCD() || DashStatus.IsDashing || pc.GetAbilityUseLimit() < 1f) return; + + DashStatus.IsDashing = true; + DashStatus.DashEndTime = Utils.TimeStamp + DashStatus.Duration; + pc.AddAbilityCD(DashStatus.Cooldown + DashStatus.Duration); + pc.MarkDirtySettings(); + pc.RpcRemoveAbilityUse(); + } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Detector.cs b/GameMode/HideAndSeekRoles/Detector.cs index 9665bba0a..269e78650 100644 --- a/GameMode/HideAndSeekRoles/Detector.cs +++ b/GameMode/HideAndSeekRoles/Detector.cs @@ -1,75 +1,74 @@ using System.Linq; -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Detector : RoleBase, IHideAndSeekRole { - public class Detector : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem InfoFrequency; + public static OptionItem Vision; + public static OptionItem Speed; + + private long LastInfoTime; + + public override bool IsEnable => On; + public Team Team => Team.Crewmate; + public int Chance => CustomRoles.Detector.GetMode(); + public int Count => CustomRoles.Detector.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_601, TabGroup.CrewmateRoles, CustomRoles.Detector, CustomGameMode.HideAndSeek); - public static OptionItem InfoFrequency; - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_603, "DetectorVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(66, 221, 245, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]); - private long LastInfoTime; + Speed = new FloatOptionItem(69_213_604, "DetectorSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(66, 221, 245, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]); - public override bool IsEnable => On; - public Team Team => Team.Crewmate; - public int Chance => CustomRoles.Detector.GetMode(); - public int Count => CustomRoles.Detector.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + InfoFrequency = new IntegerOptionItem(69_213_605, "DetectorFrequency", new(0, 60, 1), 20, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(66, 221, 245, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + LastInfoTime = Utils.TimeStamp + 8 + InfoFrequency.GetInt(); + } + + public override void Init() + { + On = false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive()) return; + + long now = Utils.TimeStamp; + + if (LastInfoTime + InfoFrequency.GetInt() <= now) { - Options.SetupRoleOptions(69_211_601, TabGroup.CrewmateRoles, CustomRoles.Detector, CustomGameMode.HideAndSeek); + PlayerControl[] imps = HnSManager.PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.GetPlainShipRoom() != null).ToArray(); - Vision = new FloatOptionItem(69_211_603, "DetectorVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(66, 221, 245, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]); - - Speed = new FloatOptionItem(69_213_604, "DetectorSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(66, 221, 245, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]); - - InfoFrequency = new IntegerOptionItem(69_213_605, "DetectorFrequency", new(0, 60, 1), 20, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(66, 221, 245, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detector]); - } - - public override void Add(byte playerId) - { - On = true; - LastInfoTime = Utils.TimeStamp + 8 + InfoFrequency.GetInt(); - } - - public override void Init() - { - On = false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive()) return; - - long now = Utils.TimeStamp; - - if (LastInfoTime + InfoFrequency.GetInt() <= now) + if (imps.Length > 0) { - PlayerControl[] imps = HnSManager.PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.GetPlainShipRoom() != null).ToArray(); - - if (imps.Length > 0) - { - PlayerControl imp = imps.RandomElement(); - string room = Translator.GetString($"{imp.GetPlainShipRoom().RoomId}"); - pc.Notify(string.Format(Translator.GetString("DetectorNotify"), room)); - LastInfoTime = now; - } + PlayerControl imp = imps.RandomElement(); + string room = Translator.GetString($"{imp.GetPlainShipRoom().RoomId}"); + pc.Notify(string.Format(Translator.GetString("DetectorNotify"), room)); + LastInfoTime = now; } } } diff --git a/GameMode/HideAndSeekRoles/Fox.cs b/GameMode/HideAndSeekRoles/Fox.cs index 9579a429f..658a3a096 100644 --- a/GameMode/HideAndSeekRoles/Fox.cs +++ b/GameMode/HideAndSeekRoles/Fox.cs @@ -1,44 +1,43 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +internal class Fox : RoleBase, IHideAndSeekRole { - internal class Fox : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem Vision; + public static OptionItem Speed; + + public override bool IsEnable => On; + public Team Team => Team.Neutral; + public int Chance => CustomRoles.Fox.GetMode(); + public int Count => CustomRoles.Fox.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_301, TabGroup.NeutralRoles, CustomRoles.Fox, CustomGameMode.HideAndSeek); - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_303, "FoxVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(0, 255, 0, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Fox]); - public override bool IsEnable => On; - public Team Team => Team.Neutral; - public int Chance => CustomRoles.Fox.GetMode(); - public int Count => CustomRoles.Fox.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_213_304, "FoxSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(0, 255, 0, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Fox]); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(69_211_301, TabGroup.NeutralRoles, CustomRoles.Fox, CustomGameMode.HideAndSeek); + public override void Add(byte playerId) + { + On = true; + } - Vision = new FloatOptionItem(69_211_303, "FoxVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(0, 255, 0, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Fox]); - - Speed = new FloatOptionItem(69_213_304, "FoxSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(0, 255, 0, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Fox]); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } + public override void Init() + { + On = false; } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Hider.cs b/GameMode/HideAndSeekRoles/Hider.cs index 7ae7cc7d2..99c17bd06 100644 --- a/GameMode/HideAndSeekRoles/Hider.cs +++ b/GameMode/HideAndSeekRoles/Hider.cs @@ -1,93 +1,92 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +internal class Hider : RoleBase, IHideAndSeekRole { - internal class Hider : RoleBase, IHideAndSeekRole + private static bool On; + + private static OptionItem Vision; + private static OptionItem Speed; + private static OptionItem TimeDecreaseOnShortTaskComplete; + private static OptionItem TimeDecreaseOnCommonTaskComplete; + private static OptionItem TimeDecreaseOnLongTaskComplete; + private static OptionItem TimeDecreaseOnSituationalTaskComplete; + private static OptionItem TimeDecreaseOnOtherTaskComplete; + + public override bool IsEnable => On; + public Team Team => Team.Crewmate; + public int Chance => 100; + public int Count => Main.AllPlayerControls.Length; + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - private static bool On; + new TextOptionItem(69_211_199, "Hider", TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetHeader(true) + .SetColor(new(52, 94, 235, byte.MaxValue)); - private static OptionItem Vision; - private static OptionItem Speed; - private static OptionItem TimeDecreaseOnShortTaskComplete; - private static OptionItem TimeDecreaseOnCommonTaskComplete; - private static OptionItem TimeDecreaseOnLongTaskComplete; - private static OptionItem TimeDecreaseOnSituationalTaskComplete; - private static OptionItem TimeDecreaseOnOtherTaskComplete; + Vision = new FloatOptionItem(69_211_101, "HiderVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(52, 94, 235, byte.MaxValue)); - public override bool IsEnable => On; - public Team Team => Team.Crewmate; - public int Chance => 100; - public int Count => Main.AllPlayerControls.Length; - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_211_102, "HiderSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(52, 94, 235, byte.MaxValue)); - public override void SetupCustomOption() - { - new TextOptionItem(69_211_199, "Hider", TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetHeader(true) - .SetColor(new(52, 94, 235, byte.MaxValue)); + TimeDecreaseOnShortTaskComplete = new IntegerOptionItem(69_211_103, "TimeDecreaseOnShortTaskComplete", new(0, 60, 1), 5, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(52, 94, 235, byte.MaxValue)); - Vision = new FloatOptionItem(69_211_101, "HiderVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(52, 94, 235, byte.MaxValue)); + TimeDecreaseOnCommonTaskComplete = new IntegerOptionItem(69_211_104, "TimeDecreaseOnCommonTaskComplete", new(0, 60, 1), 10, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(52, 94, 235, byte.MaxValue)); - Speed = new FloatOptionItem(69_211_102, "HiderSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(52, 94, 235, byte.MaxValue)); + TimeDecreaseOnLongTaskComplete = new IntegerOptionItem(69_211_105, "TimeDecreaseOnLongTaskComplete", new(0, 60, 1), 15, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(52, 94, 235, byte.MaxValue)); - TimeDecreaseOnShortTaskComplete = new IntegerOptionItem(69_211_103, "TimeDecreaseOnShortTaskComplete", new(0, 60, 1), 5, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(52, 94, 235, byte.MaxValue)); + TimeDecreaseOnSituationalTaskComplete = new IntegerOptionItem(69_211_106, "TimeDecreaseOnSituationalTaskComplete", new(0, 60, 1), 20, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(52, 94, 235, byte.MaxValue)); - TimeDecreaseOnCommonTaskComplete = new IntegerOptionItem(69_211_104, "TimeDecreaseOnCommonTaskComplete", new(0, 60, 1), 10, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(52, 94, 235, byte.MaxValue)); + TimeDecreaseOnOtherTaskComplete = new IntegerOptionItem(69_211_107, "TimeDecreaseOnOtherTaskComplete", new(0, 60, 1), 5, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(52, 94, 235, byte.MaxValue)); + } - TimeDecreaseOnLongTaskComplete = new IntegerOptionItem(69_211_105, "TimeDecreaseOnLongTaskComplete", new(0, 60, 1), 15, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(52, 94, 235, byte.MaxValue)); + public override void Add(byte playerId) + { + On = true; + } - TimeDecreaseOnSituationalTaskComplete = new IntegerOptionItem(69_211_106, "TimeDecreaseOnSituationalTaskComplete", new(0, 60, 1), 20, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(52, 94, 235, byte.MaxValue)); + public override void Init() + { + On = false; + } - TimeDecreaseOnOtherTaskComplete = new IntegerOptionItem(69_211_107, "TimeDecreaseOnOtherTaskComplete", new(0, 60, 1), 5, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(52, 94, 235, byte.MaxValue)); - } + public static void OnSpecificTaskComplete(PlayerControl pc, PlayerTask task) + { + int time = !AddTasksFromListPatch.DisableTasksSettings.TryGetValue(task.TaskType, out OptionItem setting) + ? TimeDecreaseOnOtherTaskComplete.GetInt() + : setting.Parent.Name switch + { + "DisableShortTasks" => TimeDecreaseOnShortTaskComplete.GetInt(), + "DisableCommonTasks" => TimeDecreaseOnCommonTaskComplete.GetInt(), + "DisableLongTasks" => TimeDecreaseOnLongTaskComplete.GetInt(), + "DisableOtherTasks" => TimeDecreaseOnSituationalTaskComplete.GetInt(), + _ => TimeDecreaseOnOtherTaskComplete.GetInt() + }; - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public static void OnSpecificTaskComplete(PlayerControl pc, PlayerTask task) - { - int time = !AddTasksFromListPatch.DisableTasksSettings.TryGetValue(task.TaskType, out OptionItem setting) - ? TimeDecreaseOnOtherTaskComplete.GetInt() - : setting.Parent.Name switch - { - "DisableShortTasks" => TimeDecreaseOnShortTaskComplete.GetInt(), - "DisableCommonTasks" => TimeDecreaseOnCommonTaskComplete.GetInt(), - "DisableLongTasks" => TimeDecreaseOnLongTaskComplete.GetInt(), - "DisableOtherTasks" => TimeDecreaseOnSituationalTaskComplete.GetInt(), - _ => TimeDecreaseOnOtherTaskComplete.GetInt() - }; - - HnSManager.TimeLeft -= time; - pc.Notify(string.Format(Translator.GetString("TimeDecreased"), time)); - if (60 - (HnSManager.TimeLeft % 60) <= time) Utils.NotifyRoles(); - } + HnSManager.TimeLeft -= time; + pc.Notify(string.Format(Translator.GetString("TimeDecreased"), time)); + if (60 - (HnSManager.TimeLeft % 60) <= time) Utils.NotifyRoles(); } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/IHideAndSeekRole.cs b/GameMode/HideAndSeekRoles/IHideAndSeekRole.cs index ce181af44..89d343c49 100644 --- a/GameMode/HideAndSeekRoles/IHideAndSeekRole.cs +++ b/GameMode/HideAndSeekRoles/IHideAndSeekRole.cs @@ -1,19 +1,18 @@ -namespace EHR.GameMode.HideAndSeekRoles -{ - internal interface IHideAndSeekRole - { - public Team Team { get; } - public int Count { get; } - public int Chance { get; } - public float RoleSpeed { get; } - public float RoleVision { get; } - } +namespace EHR.GameMode.HideAndSeekRoles; - public class DashStatus - { - public long DashEndTime { get; set; } = Utils.TimeStamp; - public bool IsDashing { get; set; } = false; - public int Cooldown { get; init; } = 20; - public int Duration { get; init; } = 5; - } +internal interface IHideAndSeekRole +{ + public Team Team { get; } + public int Count { get; } + public int Chance { get; } + public float RoleSpeed { get; } + public float RoleVision { get; } +} + +public class DashStatus +{ + public long DashEndTime { get; set; } = Utils.TimeStamp; + public bool IsDashing { get; set; } = false; + public int Cooldown { get; init; } = 20; + public int Duration { get; init; } = 5; } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Jet.cs b/GameMode/HideAndSeekRoles/Jet.cs index 48429e5aa..696a635f3 100644 --- a/GameMode/HideAndSeekRoles/Jet.cs +++ b/GameMode/HideAndSeekRoles/Jet.cs @@ -1,111 +1,110 @@ using AmongUs.GameOptions; -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Jet : RoleBase, IHideAndSeekRole { - public class Jet : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem DashCooldown; + public static OptionItem DashDuration; + public static OptionItem DashSpeed; + public static OptionItem UseLimit; + public static OptionItem Vision; + public static OptionItem Speed; + + private DashStatus DashStatus; + + public override bool IsEnable => On; + public Team Team => Team.Crewmate; + public int Chance => CustomRoles.Jet.GetMode(); + public int Count => CustomRoles.Jet.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_701, TabGroup.CrewmateRoles, CustomRoles.Jet, CustomGameMode.HideAndSeek); - public static OptionItem DashCooldown; - public static OptionItem DashDuration; - public static OptionItem DashSpeed; - public static OptionItem UseLimit; - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_703, "JetVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(66, 245, 75, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); - private DashStatus DashStatus; + Speed = new FloatOptionItem(69_213_704, "JetSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(66, 245, 75, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); - public override bool IsEnable => On; - public Team Team => Team.Crewmate; - public int Chance => CustomRoles.Jet.GetMode(); - public int Count => CustomRoles.Jet.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + DashCooldown = new FloatOptionItem(69_213_705, "JetCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(66, 245, 75, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); - public override void SetupCustomOption() + DashDuration = new FloatOptionItem(69_213_706, "JetDuration", new(1f, 30f, 1f), 5f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(66, 245, 75, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + + DashSpeed = new FloatOptionItem(69_213_707, "JetSpeedIncreased", new(0.05f, 5f, 0.05f), 1.5f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(66, 245, 75, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + + UseLimit = new IntegerOptionItem(69_213_708, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(66, 245, 75, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(UseLimit.GetFloat()); + + DashStatus = new() { - Options.SetupRoleOptions(69_211_701, TabGroup.CrewmateRoles, CustomRoles.Jet, CustomGameMode.HideAndSeek); + Cooldown = DashCooldown.GetInt(), + Duration = DashDuration.GetInt() + }; + } - Vision = new FloatOptionItem(69_211_703, "JetVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(66, 245, 75, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + public override void Init() + { + On = false; + } - Speed = new FloatOptionItem(69_213_704, "JetSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(66, 245, 75, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + Main.AllPlayerSpeed[playerId] = DashStatus.IsDashing ? DashSpeed.GetFloat() : RoleSpeed; + } - DashCooldown = new FloatOptionItem(69_213_705, "JetCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(66, 245, 75, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + public override void OnPet(PlayerControl pc) + { + if (pc.HasAbilityCD() || DashStatus.IsDashing || pc.GetAbilityUseLimit() < 1f) return; - DashDuration = new FloatOptionItem(69_213_706, "JetDuration", new(1f, 30f, 1f), 5f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(66, 245, 75, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + DashStatus.IsDashing = true; + DashStatus.DashEndTime = Utils.TimeStamp + DashStatus.Duration; + pc.AddAbilityCD(DashStatus.Cooldown + DashStatus.Duration); + pc.MarkDirtySettings(); + pc.RpcRemoveAbilityUse(); + } - DashSpeed = new FloatOptionItem(69_213_707, "JetSpeedIncreased", new(0.05f, 5f, 0.05f), 1.5f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(66, 245, 75, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); + public override void OnFixedUpdate(PlayerControl pc) + { + if (!DashStatus.IsDashing) return; - UseLimit = new IntegerOptionItem(69_213_708, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(66, 245, 75, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jet]); - } + long now = Utils.TimeStamp; - public override void Add(byte playerId) + if (DashStatus.DashEndTime <= now) { - On = true; - playerId.SetAbilityUseLimit(UseLimit.GetFloat()); - - DashStatus = new() - { - Cooldown = DashCooldown.GetInt(), - Duration = DashDuration.GetInt() - }; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - Main.AllPlayerSpeed[playerId] = DashStatus.IsDashing ? DashSpeed.GetFloat() : RoleSpeed; - } - - public override void OnPet(PlayerControl pc) - { - if (pc.HasAbilityCD() || DashStatus.IsDashing || pc.GetAbilityUseLimit() < 1f) return; - - DashStatus.IsDashing = true; - DashStatus.DashEndTime = Utils.TimeStamp + DashStatus.Duration; - pc.AddAbilityCD(DashStatus.Cooldown + DashStatus.Duration); + DashStatus.IsDashing = false; pc.MarkDirtySettings(); - pc.RpcRemoveAbilityUse(); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!DashStatus.IsDashing) return; - - long now = Utils.TimeStamp; - - if (DashStatus.DashEndTime <= now) - { - DashStatus.IsDashing = false; - pc.MarkDirtySettings(); - } } } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Jumper.cs b/GameMode/HideAndSeekRoles/Jumper.cs index 6272d113c..b737b2b64 100644 --- a/GameMode/HideAndSeekRoles/Jumper.cs +++ b/GameMode/HideAndSeekRoles/Jumper.cs @@ -1,81 +1,80 @@ using AmongUs.GameOptions; -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Jumper : RoleBase, IHideAndSeekRole { - public class Jumper : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem VentCooldown; + public static OptionItem MaxInVentTime; + public static OptionItem UseLimit; + public static OptionItem Vision; + public static OptionItem Speed; + + public override bool IsEnable => On; + public Team Team => Team.Crewmate; + public int Chance => CustomRoles.Jumper.GetMode(); + public int Count => CustomRoles.Jumper.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_501, TabGroup.CrewmateRoles, CustomRoles.Jumper, CustomGameMode.HideAndSeek); - public static OptionItem VentCooldown; - public static OptionItem MaxInVentTime; - public static OptionItem UseLimit; - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_503, "JumperVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(221, 245, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); - public override bool IsEnable => On; - public Team Team => Team.Crewmate; - public int Chance => CustomRoles.Jumper.GetMode(); - public int Count => CustomRoles.Jumper.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_213_504, "JumperSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(221, 245, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(69_211_501, TabGroup.CrewmateRoles, CustomRoles.Jumper, CustomGameMode.HideAndSeek); + VentCooldown = new FloatOptionItem(69_213_505, "VentCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(221, 245, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); - Vision = new FloatOptionItem(69_211_503, "JumperVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(221, 245, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); + MaxInVentTime = new FloatOptionItem(69_213_506, "MaxInVentTime", new(0f, 60f, 0.5f), 3f, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(221, 245, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); - Speed = new FloatOptionItem(69_213_504, "JumperSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(221, 245, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); + UseLimit = new IntegerOptionItem(69_213_507, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.CrewmateRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(221, 245, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); + } - VentCooldown = new FloatOptionItem(69_213_505, "VentCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(221, 245, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(UseLimit.GetFloat()); + } - MaxInVentTime = new FloatOptionItem(69_213_506, "MaxInVentTime", new(0f, 60f, 0.5f), 3f, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(221, 245, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); + public override void Init() + { + On = false; + } - UseLimit = new IntegerOptionItem(69_213_507, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.CrewmateRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(221, 245, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Jumper]); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); + } - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(UseLimit.GetFloat()); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (physics.myPlayer.GetAbilityUseLimit() < 1f) - LateTask.New(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Jumper no uses boot from vent"); - else - physics.myPlayer.RpcRemoveAbilityUse(); - } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (physics.myPlayer.GetAbilityUseLimit() < 1f) + LateTask.New(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Jumper no uses boot from vent"); + else + physics.myPlayer.RpcRemoveAbilityUse(); } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Locator.cs b/GameMode/HideAndSeekRoles/Locator.cs index a05cd63b5..e88582840 100644 --- a/GameMode/HideAndSeekRoles/Locator.cs +++ b/GameMode/HideAndSeekRoles/Locator.cs @@ -1,112 +1,111 @@ using System.Linq; -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Locator : RoleBase, IHideAndSeekRole { - public class Locator : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem ArrowFrequency; + public static OptionItem ArrowDuration; + public static OptionItem HidersKnowTheyAreLocated; + public static OptionItem Vision; + public static OptionItem Speed; + private byte LocatorId; + + private LocateStatus Status; + + public override bool IsEnable => On; + public Team Team => Team.Impostor; + public int Chance => CustomRoles.Locator.GetMode(); + public int Count => CustomRoles.Locator.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_901, TabGroup.ImpostorRoles, CustomRoles.Locator, CustomGameMode.HideAndSeek); - public static OptionItem ArrowFrequency; - public static OptionItem ArrowDuration; - public static OptionItem HidersKnowTheyAreLocated; - public static OptionItem Vision; - public static OptionItem Speed; - private byte LocatorId; + Vision = new FloatOptionItem(69_211_903, "LocatorVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(245, 158, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - private LocateStatus Status; + Speed = new FloatOptionItem(69_213_904, "LocatorSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(245, 158, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - public override bool IsEnable => On; - public Team Team => Team.Impostor; - public int Chance => CustomRoles.Locator.GetMode(); - public int Count => CustomRoles.Locator.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + ArrowFrequency = new IntegerOptionItem(69_213_905, "LocatorFrequency", new(0, 60, 1), 20, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(245, 158, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - public override void SetupCustomOption() + ArrowDuration = new FloatOptionItem(69_213_906, "LocatorDuration", new(1f, 30f, 1f), 5f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(245, 158, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); + + HidersKnowTheyAreLocated = new BooleanOptionItem(69_213_907, "LocatorTargetKnows", true, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(245, 158, 66, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); + } + + public override void Add(byte playerId) + { + On = true; + Status = new(); + LocatorId = playerId; + } + + public override void Init() + { + On = false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive()) return; + + if (Status.TargetId == byte.MaxValue) { - Options.SetupRoleOptions(69_211_901, TabGroup.ImpostorRoles, CustomRoles.Locator, CustomGameMode.HideAndSeek); - - Vision = new FloatOptionItem(69_211_903, "LocatorVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(245, 158, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - - Speed = new FloatOptionItem(69_213_904, "LocatorSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(245, 158, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - - ArrowFrequency = new IntegerOptionItem(69_213_905, "LocatorFrequency", new(0, 60, 1), 20, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(245, 158, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - - ArrowDuration = new FloatOptionItem(69_213_906, "LocatorDuration", new(1f, 30f, 1f), 5f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(245, 158, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - - HidersKnowTheyAreLocated = new BooleanOptionItem(69_213_907, "LocatorTargetKnows", true, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(245, 158, 66, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Locator]); - } - - public override void Add(byte playerId) - { - On = true; - Status = new(); - LocatorId = playerId; - } - - public override void Init() - { - On = false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive()) return; - - if (Status.TargetId == byte.MaxValue) + if (Status.LastArrowEndTime + ArrowFrequency.GetInt() < Utils.TimeStamp) { - if (Status.LastArrowEndTime + ArrowFrequency.GetInt() < Utils.TimeStamp) - { - PlayerControl target = HnSManager.PlayerRoles.Where(x => x.Value.Interface.Team != Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.IsAlive()).Shuffle().FirstOrDefault(); + PlayerControl target = HnSManager.PlayerRoles.Where(x => x.Value.Interface.Team != Team.Impostor).Select(x => Utils.GetPlayerById(x.Key)).Where(x => x != null && x.IsAlive()).Shuffle().FirstOrDefault(); - if (target != null) - { - Status.TargetId = target.PlayerId; - Status.LastArrowEndTime = Utils.TimeStamp + ArrowDuration.GetInt(); - TargetArrow.Add(pc.PlayerId, target.PlayerId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - if (HidersKnowTheyAreLocated.GetBool()) target.Notify(Translator.GetString("LocatorNotify")); - } + if (target != null) + { + Status.TargetId = target.PlayerId; + Status.LastArrowEndTime = Utils.TimeStamp + ArrowDuration.GetInt(); + TargetArrow.Add(pc.PlayerId, target.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (HidersKnowTheyAreLocated.GetBool()) target.Notify(Translator.GetString("LocatorNotify")); } } - else if (Status.LastArrowEndTime < Utils.TimeStamp) - { - TargetArrow.Remove(pc.PlayerId, Status.TargetId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - Status.TargetId = byte.MaxValue; - } } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + else if (Status.LastArrowEndTime < Utils.TimeStamp) { - if (seer.PlayerId != target.PlayerId || hud || seer.PlayerId != LocatorId) return string.Empty; - - return Status.TargetId == byte.MaxValue ? string.Empty : TargetArrow.GetArrows(seer, Status.TargetId); - } - - private class LocateStatus - { - public byte TargetId { get; set; } = byte.MaxValue; - public long LastArrowEndTime { get; set; } = Utils.TimeStamp; + TargetArrow.Remove(pc.PlayerId, Status.TargetId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + Status.TargetId = byte.MaxValue; } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || hud || seer.PlayerId != LocatorId) return string.Empty; + + return Status.TargetId == byte.MaxValue ? string.Empty : TargetArrow.GetArrows(seer, Status.TargetId); + } + + private class LocateStatus + { + public byte TargetId { get; set; } = byte.MaxValue; + public long LastArrowEndTime { get; set; } = Utils.TimeStamp; + } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Seeker.cs b/GameMode/HideAndSeekRoles/Seeker.cs index 1c44694a7..1356538b9 100644 --- a/GameMode/HideAndSeekRoles/Seeker.cs +++ b/GameMode/HideAndSeekRoles/Seeker.cs @@ -1,72 +1,71 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +internal class Seeker : RoleBase, IHideAndSeekRole { - internal class Seeker : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem Vision; + public static OptionItem Speed; + public static OptionItem CanVent; + public static OptionItem BlindTime; + public static OptionItem KillCooldown; + + public override bool IsEnable => On; + public Team Team => Team.Impostor; + public int Chance => 100; + public int Count => HnSManager.SeekerNum; + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + new TextOptionItem(69_211_299, "Seeker", TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetHeader(true) + .SetColor(new(255, 25, 25, byte.MaxValue)); - public static OptionItem Vision; - public static OptionItem Speed; - public static OptionItem CanVent; - public static OptionItem BlindTime; - public static OptionItem KillCooldown; + Vision = new FloatOptionItem(69_211_201, "SeekerVision", new(0.05f, 5f, 0.05f), 0.5f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(255, 25, 25, byte.MaxValue)); - public override bool IsEnable => On; - public Team Team => Team.Impostor; - public int Chance => 100; - public int Count => HnSManager.SeekerNum; - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_211_202, "SeekerSpeed", new(0.05f, 5f, 0.05f), 1.5f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(255, 25, 25, byte.MaxValue)); - public override void SetupCustomOption() - { - new TextOptionItem(69_211_299, "Seeker", TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetHeader(true) - .SetColor(new(255, 25, 25, byte.MaxValue)); + CanVent = new BooleanOptionItem(69_211_204, "CanVent", false, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(255, 25, 25, byte.MaxValue)); - Vision = new FloatOptionItem(69_211_201, "SeekerVision", new(0.05f, 5f, 0.05f), 0.5f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(255, 25, 25, byte.MaxValue)); + BlindTime = new FloatOptionItem(69_211_206, "BlindTime", new(0f, 60f, 1f), 10f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(255, 25, 25, byte.MaxValue)); - Speed = new FloatOptionItem(69_211_202, "SeekerSpeed", new(0.05f, 5f, 0.05f), 1.5f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(255, 25, 25, byte.MaxValue)); + KillCooldown = new IntegerOptionItem(69_211_207, "KillCooldown", new(0, 60, 1), 10, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new(255, 25, 25, byte.MaxValue)); + } - CanVent = new BooleanOptionItem(69_211_204, "CanVent", false, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(255, 25, 25, byte.MaxValue)); + public override void Add(byte playerId) + { + On = true; + } - BlindTime = new FloatOptionItem(69_211_206, "BlindTime", new(0f, 60f, 1f), 10f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(255, 25, 25, byte.MaxValue)); + public override void Init() + { + On = false; + } - KillCooldown = new IntegerOptionItem(69_211_207, "KillCooldown", new(0, 60, 1), 10, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new(255, 25, 25, byte.MaxValue)); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Taskinator.cs b/GameMode/HideAndSeekRoles/Taskinator.cs index 2bcc3f20b..7d1c1bb41 100644 --- a/GameMode/HideAndSeekRoles/Taskinator.cs +++ b/GameMode/HideAndSeekRoles/Taskinator.cs @@ -1,62 +1,61 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Taskinator : RoleBase, IHideAndSeekRole { - public class Taskinator : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem Vision; + public static OptionItem Speed; + public static OptionItem CanWinWhenDead; + + public override bool IsEnable => On; + public Team Team => Team.Neutral; + public int Chance => CustomRoles.Taskinator.GetMode(); + public int Count => CustomRoles.Taskinator.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_3001, TabGroup.NeutralRoles, CustomRoles.Taskinator, CustomGameMode.HideAndSeek); - public static OptionItem Vision; - public static OptionItem Speed; - public static OptionItem CanWinWhenDead; + Vision = new FloatOptionItem(69_211_3003, "TaskinatorVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(86, 29, 209, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Taskinator]); - public override bool IsEnable => On; - public Team Team => Team.Neutral; - public int Chance => CustomRoles.Taskinator.GetMode(); - public int Count => CustomRoles.Taskinator.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_213_3004, "TaskinatorSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(86, 29, 209, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Taskinator]); - public override void SetupCustomOption() + CanWinWhenDead = new BooleanOptionItem(69_213_3005, "TaskinatorCanWinAfterDeath", true, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(86, 29, 209, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Taskinator]); + + Options.OverrideTasksData.Create(69_213_3006, TabGroup.NeutralRoles, CustomRoles.Taskinator); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (completedTaskCount + 1 >= totalTaskCount && (pc.IsAlive() || CanWinWhenDead.GetBool())) { - Options.SetupRoleOptions(69_211_3001, TabGroup.NeutralRoles, CustomRoles.Taskinator, CustomGameMode.HideAndSeek); - - Vision = new FloatOptionItem(69_211_3003, "TaskinatorVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(86, 29, 209, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Taskinator]); - - Speed = new FloatOptionItem(69_213_3004, "TaskinatorSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(86, 29, 209, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Taskinator]); - - CanWinWhenDead = new BooleanOptionItem(69_213_3005, "TaskinatorCanWinAfterDeath", true, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(86, 29, 209, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Taskinator]); - - Options.OverrideTasksData.Create(69_213_3006, TabGroup.NeutralRoles, CustomRoles.Taskinator); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (completedTaskCount + 1 >= totalTaskCount && (pc.IsAlive() || CanWinWhenDead.GetBool())) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Taskinator); - CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); - HnSManager.AddFoxesToWinners(); - } + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Taskinator); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); + HnSManager.AddFoxesToWinners(); } } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Troll.cs b/GameMode/HideAndSeekRoles/Troll.cs index 7d6db0bbb..b222024c7 100644 --- a/GameMode/HideAndSeekRoles/Troll.cs +++ b/GameMode/HideAndSeekRoles/Troll.cs @@ -1,44 +1,43 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +internal class Troll : RoleBase, IHideAndSeekRole { - internal class Troll : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem Vision; + public static OptionItem Speed; + + public override bool IsEnable => On; + public Team Team => Team.Neutral; + public int Chance => CustomRoles.Troll.GetMode(); + public int Count => CustomRoles.Troll.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_401, TabGroup.NeutralRoles, CustomRoles.Troll, CustomGameMode.HideAndSeek); - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_403, "TrollVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(255, 0, 255, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Troll]); - public override bool IsEnable => On; - public Team Team => Team.Neutral; - public int Chance => CustomRoles.Troll.GetMode(); - public int Count => CustomRoles.Troll.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_211_404, "TrollSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(255, 0, 255, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Troll]); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(69_211_401, TabGroup.NeutralRoles, CustomRoles.Troll, CustomGameMode.HideAndSeek); + public override void Add(byte playerId) + { + On = true; + } - Vision = new FloatOptionItem(69_211_403, "TrollVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(255, 0, 255, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Troll]); - - Speed = new FloatOptionItem(69_211_404, "TrollSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(255, 0, 255, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Troll]); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } + public override void Init() + { + On = false; } } \ No newline at end of file diff --git a/GameMode/HideAndSeekRoles/Venter.cs b/GameMode/HideAndSeekRoles/Venter.cs index bf626341e..25a4d77f3 100644 --- a/GameMode/HideAndSeekRoles/Venter.cs +++ b/GameMode/HideAndSeekRoles/Venter.cs @@ -1,64 +1,63 @@ -namespace EHR.GameMode.HideAndSeekRoles +namespace EHR.GameMode.HideAndSeekRoles; + +public class Venter : RoleBase, IHideAndSeekRole { - public class Venter : RoleBase, IHideAndSeekRole + public static bool On; + + public static OptionItem UseLimit; + public static OptionItem Vision; + public static OptionItem Speed; + + public override bool IsEnable => On; + public Team Team => Team.Impostor; + public int Chance => CustomRoles.Venter.GetMode(); + public int Count => CustomRoles.Venter.GetCount(); + public float RoleSpeed => Speed.GetFloat(); + public float RoleVision => Vision.GetFloat(); + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(69_211_1001, TabGroup.ImpostorRoles, CustomRoles.Venter, CustomGameMode.HideAndSeek); - public static OptionItem UseLimit; - public static OptionItem Vision; - public static OptionItem Speed; + Vision = new FloatOptionItem(69_211_1003, "VenterVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(105, 65, 65, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Venter]); - public override bool IsEnable => On; - public Team Team => Team.Impostor; - public int Chance => CustomRoles.Venter.GetMode(); - public int Count => CustomRoles.Venter.GetCount(); - public float RoleSpeed => Speed.GetFloat(); - public float RoleVision => Vision.GetFloat(); + Speed = new FloatOptionItem(69_213_1004, "VenterSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new(105, 65, 65, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Venter]); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(69_211_1001, TabGroup.ImpostorRoles, CustomRoles.Venter, CustomGameMode.HideAndSeek); + UseLimit = new IntegerOptionItem(69_213_1007, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(new(105, 65, 65, byte.MaxValue)) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Venter]); + } - Vision = new FloatOptionItem(69_211_1003, "VenterVision", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(105, 65, 65, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Venter]); + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(UseLimit.GetFloat()); + } - Speed = new FloatOptionItem(69_213_1004, "VenterSpeed", new(0.05f, 5f, 0.05f), 1.25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new(105, 65, 65, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Venter]); + public override void Init() + { + On = false; + } - UseLimit = new IntegerOptionItem(69_213_1007, "AbilityUseLimit", new(0, 60, 1), 3, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(new(105, 65, 65, byte.MaxValue)) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Venter]); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(UseLimit.GetFloat()); - } - - public override void Init() - { - On = false; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (physics.myPlayer.GetAbilityUseLimit() < 1f) - LateTask.New(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Venter no uses boot from vent"); - else - physics.myPlayer.RpcRemoveAbilityUse(); - } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (physics.myPlayer.GetAbilityUseLimit() < 1f) + LateTask.New(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Venter no uses boot from vent"); + else + physics.myPlayer.RpcRemoveAbilityUse(); } } \ No newline at end of file diff --git a/GameMode/HnSManager.cs b/GameMode/HnSManager.cs index 85ca17cbb..4ca700c98 100644 --- a/GameMode/HnSManager.cs +++ b/GameMode/HnSManager.cs @@ -7,506 +7,505 @@ using EHR.GameMode.HideAndSeekRoles; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +internal static class HnSManager { - internal static class HnSManager + public static int TimeLeft; + private static long LastUpdate; + public static bool IsBlindTime; + + private static OptionItem MaxGameLength; + private static OptionItem MinNeutrals; + private static OptionItem MaxNeutrals; + private static OptionItem DangerMeter; + private static OptionItem PlayersSeeRoles; + + public static Dictionary> HideAndSeekRoles = []; + public static Dictionary PlayerRoles = []; + public static Dictionary ClosestImpostor = []; + public static Dictionary Danger = []; + + public static List AllHnSRoles = []; + + public static int SeekerNum => Math.Max(Main.RealOptionsData.GetInt(Int32OptionNames.NumImpostors), 1); + + public static void SetupCustomOption() { - public static int TimeLeft; - private static long LastUpdate; - public static bool IsBlindTime; + const int id = 69_211_001; + Color color = new(52, 94, 235, byte.MaxValue); - private static OptionItem MaxGameLength; - private static OptionItem MinNeutrals; - private static OptionItem MaxNeutrals; - private static OptionItem DangerMeter; - private static OptionItem PlayersSeeRoles; + MaxGameLength = new IntegerOptionItem(id, "FFA_GameTime", new(0, 1200, 10), 300, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(color); - public static Dictionary> HideAndSeekRoles = []; - public static Dictionary PlayerRoles = []; - public static Dictionary ClosestImpostor = []; - public static Dictionary Danger = []; + MinNeutrals = new IntegerOptionItem(id + 1, "HNS.MinNeutrals", new(0, 13, 1), 0, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(color); - public static List AllHnSRoles = []; + MaxNeutrals = new IntegerOptionItem(id + 2, "HNS.MaxNeutrals", new(0, 13, 1), 2, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(color); - public static int SeekerNum => Math.Max(Main.RealOptionsData.GetInt(Int32OptionNames.NumImpostors), 1); + DangerMeter = new BooleanOptionItem(id + 3, "HNS.DangerMeter", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(color); - public static void SetupCustomOption() + PlayersSeeRoles = new BooleanOptionItem(id + 4, "HNS.PlayersSeeRoles", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HideAndSeek) + .SetColor(color); + } + + public static void Init() + { + TimeLeft = MaxGameLength.GetInt(); + LastUpdate = Utils.TimeStamp; + + Type[] types = GetAllHnsRoleTypes(); + + AllHnSRoles = GetAllHnsRoles(types); + + HideAndSeekRoles = types + .Select(x => (IHideAndSeekRole)Activator.CreateInstance(x)) + .Where(x => x != null) + .Join(AllHnSRoles, x => x.GetType().Name.ToLower(), x => x.ToString().ToLower(), (Interface, Enum) => (Enum, Interface)) + .Where(x => (!x.Enum.OnlySpawnsWithPets() || Options.UsePets.GetBool()) && (x.Enum != CustomRoles.Agent || SeekerNum >= 2) && x.Interface.Count > 0 && (x.Interface.Team == Team.Neutral || x.Interface.Chance > IRandom.Instance.Next(100))) + .OrderBy(x => x.Enum is CustomRoles.Seeker or CustomRoles.Hider ? 100 : IRandom.Instance.Next(100)) + .GroupBy(x => x.Interface.Team) + .ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Enum, y => y.Interface.Count)); + + PlayerRoles = []; + ClosestImpostor = []; + } + + public static void StartSeekerBlindTime() + { + Main.AllPlayerKillCooldown.SetAllValues(Seeker.KillCooldown.GetFloat()); + IsBlindTime = true; + Utils.MarkEveryoneDirtySettingsV4(); + + LateTask.New(() => { - const int id = 69_211_001; - Color color = new(52, 94, 235, byte.MaxValue); - - MaxGameLength = new IntegerOptionItem(id, "FFA_GameTime", new(0, 1200, 10), 300, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(color); - - MinNeutrals = new IntegerOptionItem(id + 1, "HNS.MinNeutrals", new(0, 13, 1), 0, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(color); - - MaxNeutrals = new IntegerOptionItem(id + 2, "HNS.MaxNeutrals", new(0, 13, 1), 2, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(color); - - DangerMeter = new BooleanOptionItem(id + 3, "HNS.DangerMeter", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(color); - - PlayersSeeRoles = new BooleanOptionItem(id + 4, "HNS.PlayersSeeRoles", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HideAndSeek) - .SetColor(color); - } - - public static void Init() - { - TimeLeft = MaxGameLength.GetInt(); - LastUpdate = Utils.TimeStamp; - - Type[] types = GetAllHnsRoleTypes(); - - AllHnSRoles = GetAllHnsRoles(types); - - HideAndSeekRoles = types - .Select(x => (IHideAndSeekRole)Activator.CreateInstance(x)) - .Where(x => x != null) - .Join(AllHnSRoles, x => x.GetType().Name.ToLower(), x => x.ToString().ToLower(), (Interface, Enum) => (Enum, Interface)) - .Where(x => (!x.Enum.OnlySpawnsWithPets() || Options.UsePets.GetBool()) && (x.Enum != CustomRoles.Agent || SeekerNum >= 2) && x.Interface.Count > 0 && (x.Interface.Team == Team.Neutral || x.Interface.Chance > IRandom.Instance.Next(100))) - .OrderBy(x => x.Enum is CustomRoles.Seeker or CustomRoles.Hider ? 100 : IRandom.Instance.Next(100)) - .GroupBy(x => x.Interface.Team) - .ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Enum, y => y.Interface.Count)); - - PlayerRoles = []; - ClosestImpostor = []; - } - - public static void StartSeekerBlindTime() - { - Main.AllPlayerKillCooldown.SetAllValues(Seeker.KillCooldown.GetFloat()); - IsBlindTime = true; + IsBlindTime = false; Utils.MarkEveryoneDirtySettingsV4(); - LateTask.New(() => - { - IsBlindTime = false; - Utils.MarkEveryoneDirtySettingsV4(); + Main.AllAlivePlayerControls + .Join(PlayerRoles, x => x.PlayerId, x => x.Key, (pc, role) => (pc, role.Value.Interface)) + .Where(x => x.Interface.Team == Team.Impostor) + .Do(x => x.pc.SetKillCooldown()); - Main.AllAlivePlayerControls - .Join(PlayerRoles, x => x.PlayerId, x => x.Key, (pc, role) => (pc, role.Value.Interface)) - .Where(x => x.Interface.Team == Team.Impostor) - .Do(x => x.pc.SetKillCooldown()); + LateTask.New(() => Main.Instance.StartCoroutine(Utils.NotifyEveryoneAsync()), 3f, log: false); + }, Seeker.BlindTime.GetFloat() + 8f, "Blind Time Expire"); + } - LateTask.New(() => Main.Instance.StartCoroutine(Utils.NotifyEveryoneAsync()), 3f, log: false); - }, Seeker.BlindTime.GetFloat() + 8f, "Blind Time Expire"); + public static List GetAllHnsRoles(IEnumerable types) + { + return types + .Select(x => Enum.Parse(ignoreCase: true, value: x.Name)) + .Where(role => role is CustomRoles.Seeker or CustomRoles.Hider || role.GetMode() != 0) + .ToList(); + } + + public static Type[] GetAllHnsRoleTypes() + { + return Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(t => typeof(IHideAndSeekRole).IsAssignableFrom(t) && !t.IsInterface) + .ToArray(); + } + + public static void AssignRoles() + { + Dictionary result = []; + List allPlayers = [.. Main.AllPlayerControls]; + + if (Main.GM.Value) + { + allPlayers.RemoveAll(x => x.IsHost()); + PlayerRoles[0] = (new Hider(), CustomRoles.GM); + PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); } - public static List GetAllHnsRoles(IEnumerable types) + allPlayers.Shuffle(); + + Dictionary memberNum = new() { - return types - .Select(x => Enum.Parse(ignoreCase: true, value: x.Name)) - .Where(role => role is CustomRoles.Seeker or CustomRoles.Hider || role.GetMode() != 0) - .ToList(); - } + [Team.Neutral] = IRandom.Instance.Next(MinNeutrals.GetInt(), MaxNeutrals.GetInt() + 1), + [Team.Impostor] = SeekerNum + }; - public static Type[] GetAllHnsRoleTypes() + memberNum[Team.Crewmate] = allPlayers.Count - memberNum.Values.Sum(); + + Logger.Warn($"Number of impostors: {memberNum[Team.Impostor]}", "HnsRoleAssigner"); + + foreach (KeyValuePair item in Main.SetRoles) { - return Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(t => typeof(IHideAndSeekRole).IsAssignableFrom(t) && !t.IsInterface) - .ToArray(); - } - - public static void AssignRoles() - { - Dictionary result = []; - List allPlayers = [.. Main.AllPlayerControls]; - - if (Main.GM.Value) + try { - allPlayers.RemoveAll(x => x.IsHost()); - PlayerRoles[0] = (new Hider(), CustomRoles.GM); - PlayerControl.LocalPlayer.RpcSetCustomRole(CustomRoles.GM); - } + PlayerControl pc = allPlayers.FirstOrDefault(x => x.PlayerId == item.Key); + if (pc == null) continue; - allPlayers.Shuffle(); - - Dictionary memberNum = new() - { - [Team.Neutral] = IRandom.Instance.Next(MinNeutrals.GetInt(), MaxNeutrals.GetInt() + 1), - [Team.Impostor] = SeekerNum - }; - - memberNum[Team.Crewmate] = allPlayers.Count - memberNum.Values.Sum(); - - Logger.Warn($"Number of impostors: {memberNum[Team.Impostor]}", "HnsRoleAssigner"); - - foreach (KeyValuePair item in Main.SetRoles) - { - try - { - PlayerControl pc = allPlayers.FirstOrDefault(x => x.PlayerId == item.Key); - if (pc == null) continue; - - result[pc] = item.Value; - allPlayers.Remove(pc); - - KeyValuePair> role = HideAndSeekRoles.FirstOrDefault(x => x.Value.ContainsKey(item.Value)); - role.Value[item.Value]--; - memberNum[role.Key]--; - - Logger.Warn($"Pre-Set Role Assigned: {pc.GetRealName()} => {item.Value}", "HnsRoleAssigner"); - } - catch (Exception e) - { - Logger.SendInGame($"Error Assigning Pre-Set Role: {item.Key.ColoredPlayerName()} => {item.Value}"); - Utils.ThrowException(e); - } - } - - Dictionary playerTeams = Enum.GetValues()[1..] - .SelectMany(x => Enumerable.Repeat(x, Math.Max(memberNum[x], 0))) - .Shuffle() - .Zip(allPlayers) - .GroupBy(x => x.First, x => x.Second) - .ToDictionary(x => x.Key, x => x.ToArray()); - - if (memberNum[Team.Neutral] > 0 && HideAndSeekRoles.TryGetValue(Team.Neutral, out Dictionary neutrals)) HideAndSeekRoles[Team.Neutral] = neutrals.Shuffle().ToDictionary(x => x.Key, x => x.Value); - - foreach ((Team team, Dictionary roleCounts) in HideAndSeekRoles) - { - try - { - if (playerTeams[team].Length == 0 || memberNum[team] <= 0) continue; - } - catch (KeyNotFoundException) - { - continue; - } - - foreach ((CustomRoles role, int count) in roleCounts) - { - for (var i = 0; i < count; i++) - { - try - { - PlayerControl pc = playerTeams[team][0]; - if (pc == null) continue; - - result[pc] = role; - allPlayers.Remove(pc); - playerTeams[team] = playerTeams[team][1..]; - memberNum[team]--; - - if (memberNum[team] <= 0) break; - } - catch (Exception e) - { - if (e is IndexOutOfRangeException) break; - - Utils.ThrowException(e); - } - } - - if (playerTeams[team].Length == 0 || memberNum[team] <= 0) break; - } - } - - foreach (PlayerControl pc in allPlayers.Except(result.Keys).ToArray()) - { - Logger.Warn($"Unassigned, force Hider: {pc.GetRealName()} => {CustomRoles.Hider}", "HnsRoleAssigner"); - result[pc] = CustomRoles.Hider; - memberNum[Team.Crewmate]--; + result[pc] = item.Value; allPlayers.Remove(pc); + + KeyValuePair> role = HideAndSeekRoles.FirstOrDefault(x => x.Value.ContainsKey(item.Value)); + role.Value[item.Value]--; + memberNum[role.Key]--; + + Logger.Warn($"Pre-Set Role Assigned: {pc.GetRealName()} => {item.Value}", "HnsRoleAssigner"); } - - if (allPlayers.Count > 0) Logger.Error($"Some players were not assigned a role: {allPlayers.Join(x => x.GetRealName())}", "HnsRoleAssigner"); - - Logger.Msg($"Roles: {result.Join(x => $"{x.Key.GetRealName()} => {x.Value}")}", "HnsRoleAssigner"); - - Dictionary roleInterfaces = Assembly.GetExecutingAssembly().GetTypes() - .Where(x => typeof(IHideAndSeekRole).IsAssignableFrom(x) && !x.IsInterface) - .Select(x => (IHideAndSeekRole)Activator.CreateInstance(x)) - .Where(x => x != null) - .ToDictionary(x => x.GetType().Name, x => x); - - PlayerRoles = result.ToDictionary(x => x.Key.PlayerId, x => (roleInterfaces[x.Value.ToString()], x.Value)); - - LateTask.New(() => result.IntersectBy(Main.PlayerStates.Keys, x => x.Key.PlayerId).Do(x => x.Key.RpcSetCustomRole(x.Value)), 5f, log: false); - - // ================================================================================================================== - - if (result.ContainsValue(CustomRoles.Agent)) + catch (Exception e) { - byte agent = result.GetKeyByValue(CustomRoles.Agent).PlayerId; - PlayerRoles.DoIf(x => x.Value.Role != CustomRoles.Agent && x.Value.Interface.Team == Team.Impostor, x => TargetArrow.Add(x.Key, agent)); + Logger.SendInGame($"Error Assigning Pre-Set Role: {item.Key.ColoredPlayerName()} => {item.Value}"); + Utils.ThrowException(e); } } - public static void ApplyGameOptions(IGameOptions opt, PlayerControl pc) + Dictionary playerTeams = Enum.GetValues()[1..] + .SelectMany(x => Enumerable.Repeat(x, Math.Max(memberNum[x], 0))) + .Shuffle() + .Zip(allPlayers) + .GroupBy(x => x.First, x => x.Second) + .ToDictionary(x => x.Key, x => x.ToArray()); + + if (memberNum[Team.Neutral] > 0 && HideAndSeekRoles.TryGetValue(Team.Neutral, out Dictionary neutrals)) HideAndSeekRoles[Team.Neutral] = neutrals.Shuffle().ToDictionary(x => x.Key, x => x.Value); + + foreach ((Team team, Dictionary roleCounts) in HideAndSeekRoles) { - (IHideAndSeekRole Interface, CustomRoles Role) role = PlayerRoles.GetValueOrDefault(pc.PlayerId); - bool blind = role.Interface.Team == Team.Impostor && IsBlindTime; - Main.AllPlayerSpeed[pc.PlayerId] = blind ? Main.MinSpeed : role.Interface.RoleSpeed; - opt.SetFloat(FloatOptionNames.CrewLightMod, blind ? 0f : role.Interface.RoleVision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, blind ? 0f : role.Interface.RoleVision); - opt.SetFloat(FloatOptionNames.PlayerSpeedMod, Main.AllPlayerSpeed[pc.PlayerId]); + try + { + if (playerTeams[team].Length == 0 || memberNum[team] <= 0) continue; + } + catch (KeyNotFoundException) + { + continue; + } + + foreach ((CustomRoles role, int count) in roleCounts) + { + for (var i = 0; i < count; i++) + { + try + { + PlayerControl pc = playerTeams[team][0]; + if (pc == null) continue; + + result[pc] = role; + allPlayers.Remove(pc); + playerTeams[team] = playerTeams[team][1..]; + memberNum[team]--; + + if (memberNum[team] <= 0) break; + } + catch (Exception e) + { + if (e is IndexOutOfRangeException) break; + + Utils.ThrowException(e); + } + } + + if (playerTeams[team].Length == 0 || memberNum[team] <= 0) break; + } } - public static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target, ref string color) + foreach (PlayerControl pc in allPlayers.Except(result.Keys).ToArray()) { - if (seer.PlayerId == target.PlayerId) return true; + Logger.Warn($"Unassigned, force Hider: {pc.GetRealName()} => {CustomRoles.Hider}", "HnsRoleAssigner"); + result[pc] = CustomRoles.Hider; + memberNum[Team.Crewmate]--; + allPlayers.Remove(pc); + } - if (!PlayerRoles.TryGetValue(target.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) targetRole)) return false; + if (allPlayers.Count > 0) Logger.Error($"Some players were not assigned a role: {allPlayers.Join(x => x.GetRealName())}", "HnsRoleAssigner"); - if (!PlayerRoles.TryGetValue(seer.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) seerRole)) return false; + Logger.Msg($"Roles: {result.Join(x => $"{x.Key.GetRealName()} => {x.Value}")}", "HnsRoleAssigner"); + + Dictionary roleInterfaces = Assembly.GetExecutingAssembly().GetTypes() + .Where(x => typeof(IHideAndSeekRole).IsAssignableFrom(x) && !x.IsInterface) + .Select(x => (IHideAndSeekRole)Activator.CreateInstance(x)) + .Where(x => x != null) + .ToDictionary(x => x.GetType().Name, x => x); + + PlayerRoles = result.ToDictionary(x => x.Key.PlayerId, x => (roleInterfaces[x.Value.ToString()], x.Value)); + + LateTask.New(() => result.IntersectBy(Main.PlayerStates.Keys, x => x.Key.PlayerId).Do(x => x.Key.RpcSetCustomRole(x.Value)), 5f, log: false); + + // ================================================================================================================== + + if (result.ContainsValue(CustomRoles.Agent)) + { + byte agent = result.GetKeyByValue(CustomRoles.Agent).PlayerId; + PlayerRoles.DoIf(x => x.Value.Role != CustomRoles.Agent && x.Value.Interface.Team == Team.Impostor, x => TargetArrow.Add(x.Key, agent)); + } + } + + public static void ApplyGameOptions(IGameOptions opt, PlayerControl pc) + { + (IHideAndSeekRole Interface, CustomRoles Role) role = PlayerRoles.GetValueOrDefault(pc.PlayerId); + bool blind = role.Interface.Team == Team.Impostor && IsBlindTime; + Main.AllPlayerSpeed[pc.PlayerId] = blind ? Main.MinSpeed : role.Interface.RoleSpeed; + opt.SetFloat(FloatOptionNames.CrewLightMod, blind ? 0f : role.Interface.RoleVision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, blind ? 0f : role.Interface.RoleVision); + opt.SetFloat(FloatOptionNames.PlayerSpeedMod, Main.AllPlayerSpeed[pc.PlayerId]); + } + + public static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target, ref string color) + { + if (seer.PlayerId == target.PlayerId) return true; + + if (!PlayerRoles.TryGetValue(target.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) targetRole)) return false; + + if (!PlayerRoles.TryGetValue(seer.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) seerRole)) return false; + + if (PlayersSeeRoles.GetBool()) + { + color = Main.RoleColors[targetRole.Role]; + if (targetRole.Role == CustomRoles.Agent) color = Main.RoleColors[CustomRoles.Hider]; + + return true; + } + + if (targetRole.Interface.Team == Team.Impostor && (targetRole.Role != CustomRoles.Agent || seerRole.Interface.Team == Team.Impostor)) + { + color = Main.RoleColors[CustomRoles.Seeker]; + return true; + } + + return false; + } + + public static bool HasTasks(NetworkedPlayerInfo playerInfo) + { + if (!AmongUsClient.Instance.AmHost && playerInfo.IsLocalPlayer()) return PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.Taskinator or CustomRoles.Hider or CustomRoles.Jet or CustomRoles.Detector or CustomRoles.Jumper; + + if (!PlayerRoles.TryGetValue(playerInfo.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) role)) return false; + + return role.Interface.Team == Team.Crewmate || role.Role == CustomRoles.Taskinator; + } + + public static bool IsRoleTextEnabled(PlayerControl seer, PlayerControl target) + { + if (seer.PlayerId == target.PlayerId || PlayersSeeRoles.GetBool()) return true; + + if (!PlayerRoles.TryGetValue(target.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) targetRole)) return false; + + if (!PlayerRoles.TryGetValue(seer.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) seerRole)) return false; + + return targetRole.Interface.Team == Team.Impostor && (targetRole.Role != CustomRoles.Agent || seerRole.Interface.Team == Team.Impostor); + } + + public static string GetSuffixText(PlayerControl seer, PlayerControl target, bool hud = false) + { + if (GameStates.IsLobby || !CustomGameMode.HideAndSeek.IsActiveOrIntegrated() || Main.HasJustStarted) return string.Empty; + + if (seer.PlayerId != target.PlayerId || (seer.IsHost() && !hud) || TimeLeft < 0) return string.Empty; + + string dangerMeter = GetDangerMeter(seer); + + if (PlayerRoles.TryGetValue(seer.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) seerRole) && seerRole.Interface.Team == Team.Impostor && PlayerRoles.FindFirst(x => x.Value.Role == CustomRoles.Agent, out KeyValuePair kvp)) + { + byte agent = kvp.Key; + dangerMeter += TargetArrow.GetArrows(seer, agent); + } + + if (TimeLeft <= 60) return $"{dangerMeter}\n{Translator.GetString("TimeLeft")}: {TimeLeft}s"; + + int minutes = TimeLeft / 60; + int seconds = TimeLeft % 60; + return dangerMeter + "\n" + (hud ? $"{minutes:00}:{seconds:00}" : $"{string.Format(Translator.GetString("MinutesLeft"), $"{minutes}-{minutes + 1}")}"); + } + + private static string GetDangerMeter(PlayerControl seer) + { + return Danger.TryGetValue(seer.PlayerId, out int danger) + ? danger <= 5 + ? $"\n{new('\u25a0', 5 - danger)}{new('\u25a1', danger)}" + : $"\n{new('\u25a1', 5)}" + : string.Empty; + + string GetColorFromDanger() // 0: Highest, 4: Lowest + { + return danger switch + { + 0 => "#ff1313", + 1 => "#ff6a00", + 2 => "#ffaa00", + 3 => "#ffea00", + 4 => "#ffff00", + _ => "#ffffff" + }; + } + } + + public static string GetRoleInfoText(PlayerControl seer) + { + return $"{Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), seer.GetRoleInfo())}"; + } + + public static bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + PlayerControl[] alivePlayers = Main.AllAlivePlayerControls; + + // If there are 0 players alive, the game is over and only foxes win + if (alivePlayers.Length == 0) + { + reason = GameOverReason.HumansDisconnect; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); + AddFoxesToWinners(); + return true; + } + + // If there are no crew roles left, the game is over and only impostors win + if (alivePlayers.All(x => PlayerRoles[x.PlayerId].Interface.Team != Team.Crewmate)) + { + reason = GameOverReason.HideAndSeek_ByKills; + SetWinners(CustomWinner.Seeker, Team.Impostor); + return true; + } + + // If time is up or there are no impostors in the game, the game is over and crewmates win + if (TimeLeft <= 0 || PlayerRoles.Values.All(x => x.Interface.Team != Team.Impostor)) + { + reason = TimeLeft <= 0 ? GameOverReason.HideAndSeek_ByTimer : GameOverReason.ImpostorDisconnect; + SetWinners(CustomWinner.Hider, Team.Crewmate); + return true; + } + + return false; + + static void SetWinners(CustomWinner winner, Team team) + { + CustomWinnerHolder.ResetAndSetWinner(winner); + CustomWinnerHolder.WinnerIds.UnionWith(PlayerRoles.Where(x => x.Value.Interface.Team == team).Select(x => x.Key)); + AddFoxesToWinners(); + } + } + + public static void AddFoxesToWinners() + { + List foxes = Main.PlayerStates.Where(x => x.Value.MainRole == CustomRoles.Fox).Select(x => x.Key).ToList(); + + foxes.RemoveAll(x => + { + PlayerControl pc = Utils.GetPlayerById(x); + return pc == null || !pc.IsAlive(); + }); + + if (foxes.Count == 0) return; + + CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Fox); + CustomWinnerHolder.WinnerIds.UnionWith(foxes); + } + + public static string GetTaskBarText() + { + string text = HasTasks(PlayerControl.LocalPlayer.Data) ? $"{GetTaskText()}\r\n\r\n" : string.Empty; + text += Main.PlayerStates.IntersectBy(PlayerRoles.Keys, x => x.Key).Aggregate("", (current, state) => $"{current}{GetStateText(state)}\n"); + return $"{text}\r\n\r\n<#00ffa5>{Translator.GetString("HNS.TaskCount")} {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}"; + + static string GetStateText(KeyValuePair state) + { + string name = Main.AllPlayerNames.GetValueOrDefault(state.Key, $"ID {state.Key}"); + name = Utils.ColorString(Main.PlayerColors.GetValueOrDefault(state.Key, Color.white), name); + bool isSeeker = PlayerRoles[state.Key].Interface.Team == Team.Impostor; + bool alive = !state.Value.IsDead; + + TaskState ts = state.Value.TaskState; + var stateText = string.Empty; if (PlayersSeeRoles.GetBool()) - { - color = Main.RoleColors[targetRole.Role]; - if (targetRole.Role == CustomRoles.Agent) color = Main.RoleColors[CustomRoles.Hider]; + stateText = $" ({GetRole().ToColoredString()}){GetTaskCount()}"; + else if (isSeeker) stateText = $" ({CustomRoles.Seeker.ToColoredString()})"; - return true; + if (!alive) stateText += $" {Translator.GetString("Dead")}"; + + stateText = $"{name}{stateText}"; + return stateText; + + CustomRoles GetRole() + { + return state.Value.MainRole == CustomRoles.Agent ? CustomRoles.Hider : state.Value.MainRole; } - if (targetRole.Interface.Team == Team.Impostor && (targetRole.Role != CustomRoles.Agent || seerRole.Interface.Team == Team.Impostor)) + string GetTaskCount() { - color = Main.RoleColors[CustomRoles.Seeker]; - return true; + return CustomRoles.Agent.IsEnable() || !ts.HasTasks ? string.Empty : $" ({ts.CompletedTasksCount}/{ts.AllTasksCount})"; } - - return false; } - public static bool HasTasks(NetworkedPlayerInfo playerInfo) + static string GetTaskText() { - if (!AmongUsClient.Instance.AmHost && playerInfo.IsLocalPlayer()) return PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.Taskinator or CustomRoles.Hider or CustomRoles.Jet or CustomRoles.Detector or CustomRoles.Jumper; + Il2CppSystem.Text.StringBuilder sb = new(); + bool flag = PlayerControl.LocalPlayer.Data.Role != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor; - if (!PlayerRoles.TryGetValue(playerInfo.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) role)) return false; - - return role.Interface.Team == Team.Crewmate || role.Role == CustomRoles.Taskinator; - } - - public static bool IsRoleTextEnabled(PlayerControl seer, PlayerControl target) - { - if (seer.PlayerId == target.PlayerId || PlayersSeeRoles.GetBool()) return true; - - if (!PlayerRoles.TryGetValue(target.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) targetRole)) return false; - - if (!PlayerRoles.TryGetValue(seer.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) seerRole)) return false; - - return targetRole.Interface.Team == Team.Impostor && (targetRole.Role != CustomRoles.Agent || seerRole.Interface.Team == Team.Impostor); - } - - public static string GetSuffixText(PlayerControl seer, PlayerControl target, bool hud = false) - { - if (GameStates.IsLobby || !CustomGameMode.HideAndSeek.IsActiveOrIntegrated() || Main.HasJustStarted) return string.Empty; - - if (seer.PlayerId != target.PlayerId || (seer.IsHost() && !hud) || TimeLeft < 0) return string.Empty; - - string dangerMeter = GetDangerMeter(seer); - - if (PlayerRoles.TryGetValue(seer.PlayerId, out (IHideAndSeekRole Interface, CustomRoles Role) seerRole) && seerRole.Interface.Team == Team.Impostor && PlayerRoles.FindFirst(x => x.Value.Role == CustomRoles.Agent, out KeyValuePair kvp)) + foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) { - byte agent = kvp.Key; - dangerMeter += TargetArrow.GetArrows(seer, agent); - } - - if (TimeLeft <= 60) return $"{dangerMeter}\n{Translator.GetString("TimeLeft")}: {TimeLeft}s"; - - int minutes = TimeLeft / 60; - int seconds = TimeLeft % 60; - return dangerMeter + "\n" + (hud ? $"{minutes:00}:{seconds:00}" : $"{string.Format(Translator.GetString("MinutesLeft"), $"{minutes}-{minutes + 1}")}"); - } - - private static string GetDangerMeter(PlayerControl seer) - { - return Danger.TryGetValue(seer.PlayerId, out int danger) - ? danger <= 5 - ? $"\n{new('\u25a0', 5 - danger)}{new('\u25a1', danger)}" - : $"\n{new('\u25a1', 5)}" - : string.Empty; - - string GetColorFromDanger() // 0: Highest, 4: Lowest - { - return danger switch + if (task != null) { - 0 => "#ff1313", - 1 => "#ff6a00", - 2 => "#ffaa00", - 3 => "#ffea00", - 4 => "#ffff00", - _ => "#ffffff" - }; - } - } - - public static string GetRoleInfoText(PlayerControl seer) - { - return $"{Utils.ColorString(Utils.GetRoleColor(seer.GetCustomRole()), seer.GetRoleInfo())}"; - } - - public static bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - PlayerControl[] alivePlayers = Main.AllAlivePlayerControls; - - // If there are 0 players alive, the game is over and only foxes win - if (alivePlayers.Length == 0) - { - reason = GameOverReason.HumansDisconnect; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); - AddFoxesToWinners(); - return true; - } - - // If there are no crew roles left, the game is over and only impostors win - if (alivePlayers.All(x => PlayerRoles[x.PlayerId].Interface.Team != Team.Crewmate)) - { - reason = GameOverReason.HideAndSeek_ByKills; - SetWinners(CustomWinner.Seeker, Team.Impostor); - return true; - } - - // If time is up or there are no impostors in the game, the game is over and crewmates win - if (TimeLeft <= 0 || PlayerRoles.Values.All(x => x.Interface.Team != Team.Impostor)) - { - reason = TimeLeft <= 0 ? GameOverReason.HideAndSeek_ByTimer : GameOverReason.ImpostorDisconnect; - SetWinners(CustomWinner.Hider, Team.Crewmate); - return true; - } - - return false; - - static void SetWinners(CustomWinner winner, Team team) - { - CustomWinnerHolder.ResetAndSetWinner(winner); - CustomWinnerHolder.WinnerIds.UnionWith(PlayerRoles.Where(x => x.Value.Interface.Team == team).Select(x => x.Key)); - AddFoxesToWinners(); - } - } - - public static void AddFoxesToWinners() - { - List foxes = Main.PlayerStates.Where(x => x.Value.MainRole == CustomRoles.Fox).Select(x => x.Key).ToList(); - - foxes.RemoveAll(x => - { - PlayerControl pc = Utils.GetPlayerById(x); - return pc == null || !pc.IsAlive(); - }); - - if (foxes.Count == 0) return; - - CustomWinnerHolder.AdditionalWinnerTeams.Add(AdditionalWinners.Fox); - CustomWinnerHolder.WinnerIds.UnionWith(foxes); - } - - public static string GetTaskBarText() - { - string text = HasTasks(PlayerControl.LocalPlayer.Data) ? $"{GetTaskText()}\r\n\r\n" : string.Empty; - text += Main.PlayerStates.IntersectBy(PlayerRoles.Keys, x => x.Key).Aggregate("", (current, state) => $"{current}{GetStateText(state)}\n"); - return $"{text}\r\n\r\n<#00ffa5>{Translator.GetString("HNS.TaskCount")} {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}"; - - static string GetStateText(KeyValuePair state) - { - string name = Main.AllPlayerNames.GetValueOrDefault(state.Key, $"ID {state.Key}"); - name = Utils.ColorString(Main.PlayerColors.GetValueOrDefault(state.Key, Color.white), name); - bool isSeeker = PlayerRoles[state.Key].Interface.Team == Team.Impostor; - bool alive = !state.Value.IsDead; - - TaskState ts = state.Value.TaskState; - var stateText = string.Empty; - - if (PlayersSeeRoles.GetBool()) - stateText = $" ({GetRole().ToColoredString()}){GetTaskCount()}"; - else if (isSeeker) stateText = $" ({CustomRoles.Seeker.ToColoredString()})"; - - if (!alive) stateText += $" {Translator.GetString("Dead")}"; - - stateText = $"{name}{stateText}"; - return stateText; - - CustomRoles GetRole() - { - return state.Value.MainRole == CustomRoles.Agent ? CustomRoles.Hider : state.Value.MainRole; - } - - string GetTaskCount() - { - return CustomRoles.Agent.IsEnable() || !ts.HasTasks ? string.Empty : $" ({ts.CompletedTasksCount}/{ts.AllTasksCount})"; - } - } - - static string GetTaskText() - { - Il2CppSystem.Text.StringBuilder sb = new(); - bool flag = PlayerControl.LocalPlayer.Data.Role != null && PlayerControl.LocalPlayer.Data.Role.IsImpostor; - - foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) - { - if (task != null) + if (task.TaskType == TaskTypes.FixComms && !flag) { - if (task.TaskType == TaskTypes.FixComms && !flag) - { - sb.Clear(); - task.AppendTaskText(sb); - break; - } - + sb.Clear(); task.AppendTaskText(sb); + break; } + + task.AppendTaskText(sb); } - - return sb.ToString(); } + + return sb.ToString(); } + } - public static void OnCheckMurder(PlayerControl killer, PlayerControl target) + public static void OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || PlayerRoles[killer.PlayerId].Interface.Team != Team.Impostor || PlayerRoles[target.PlayerId].Interface.Team == Team.Impostor) return; + + killer.Kill(target); + + // If the Troll is killed, they win + if (target.Is(CustomRoles.Troll)) { - if (killer == null || target == null || PlayerRoles[killer.PlayerId].Interface.Team != Team.Impostor || PlayerRoles[target.PlayerId].Interface.Team == Team.Impostor) return; - - killer.Kill(target); - - // If the Troll is killed, they win - if (target.Is(CustomRoles.Troll)) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Troll); - CustomWinnerHolder.WinnerIds.Add(target.PlayerId); - AddFoxesToWinners(); - } + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Troll); + CustomWinnerHolder.WinnerIds.Add(target.PlayerId); + AddFoxesToWinners(); } + } - public static void OnCoEnterVent(PlayerPhysics physics, int ventId) { } + public static void OnCoEnterVent(PlayerPhysics physics, int ventId) { } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private class FixedUpdatePatch + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private class FixedUpdatePatch + { + public static void Postfix() { - public static void Postfix() + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.HideAndSeek.IsActiveOrIntegrated() || Main.HasJustStarted) return; + + long now = Utils.TimeStamp; + if (LastUpdate == now) return; + + LastUpdate = now; + + TimeLeft--; + + PlayerRoles = PlayerRoles.IntersectBy(Main.AllPlayerControls.Select(x => x.PlayerId), x => x.Key).ToDictionary(x => x.Key, x => x.Value); + + try { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.HideAndSeek.IsActiveOrIntegrated() || Main.HasJustStarted) return; - - long now = Utils.TimeStamp; - if (LastUpdate == now) return; - - LastUpdate = now; - - TimeLeft--; - - PlayerRoles = PlayerRoles.IntersectBy(Main.AllPlayerControls.Select(x => x.PlayerId), x => x.Key).ToDictionary(x => x.Key, x => x.Value); - - try - { - Dictionary idPosPairs = PlayerRoles.Join(Main.AllPlayerControls, x => x.Key, x => x.PlayerId, (role, pc) => (role.Key, pc)).ToDictionary(x => x.Key, x => x.pc.Pos()); - Dictionary imps = PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).ToDictionary(x => x.Key, x => idPosPairs[x.Key]); - KeyValuePair[] nonImps = PlayerRoles.Where(x => x.Value.Interface.Team is Team.Crewmate or Team.Neutral).ToArray(); - ClosestImpostor = nonImps.ToDictionary(x => x.Key, x => imps.MinBy(y => Vector2.Distance(y.Value, idPosPairs[x.Key])).Key); - Danger = nonImps.ToDictionary(x => x.Key, x => Math.Clamp(((1 + (int)Math.Ceiling(Vector2.Distance(idPosPairs[x.Key], idPosPairs[ClosestImpostor[x.Key]]))) / 3) - 1, 0, 5)); - } - catch { } - - if (DangerMeter.GetBool() || (TimeLeft + 1) % 60 == 0 || TimeLeft <= 60) Utils.NotifyRoles(); + Dictionary idPosPairs = PlayerRoles.Join(Main.AllPlayerControls, x => x.Key, x => x.PlayerId, (role, pc) => (role.Key, pc)).ToDictionary(x => x.Key, x => x.pc.Pos()); + Dictionary imps = PlayerRoles.Where(x => x.Value.Interface.Team == Team.Impostor).ToDictionary(x => x.Key, x => idPosPairs[x.Key]); + KeyValuePair[] nonImps = PlayerRoles.Where(x => x.Value.Interface.Team is Team.Crewmate or Team.Neutral).ToArray(); + ClosestImpostor = nonImps.ToDictionary(x => x.Key, x => imps.MinBy(y => Vector2.Distance(y.Value, idPosPairs[x.Key])).Key); + Danger = nonImps.ToDictionary(x => x.Key, x => Math.Clamp(((1 + (int)Math.Ceiling(Vector2.Distance(idPosPairs[x.Key], idPosPairs[ClosestImpostor[x.Key]]))) / 3) - 1, 0, 5)); } + catch { } + + if (DangerMeter.GetBool() || (TimeLeft + 1) % 60 == 0 || TimeLeft <= 60) Utils.NotifyRoles(); } } } \ No newline at end of file diff --git a/GameMode/HotPotato.cs b/GameMode/HotPotato.cs index 921e30acf..f99f63ea1 100644 --- a/GameMode/HotPotato.cs +++ b/GameMode/HotPotato.cs @@ -6,174 +6,173 @@ using EHR.Modules; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +internal static class HotPotato { - internal static class HotPotato + private static OptionItem Time; + private static OptionItem HolderSpeed; + private static OptionItem Range; + + private static (byte HolderID, byte LastHolderID, int TimeLeft, int RoundNum) HotPotatoState; + private static Dictionary SurvivalTimes; + private static float DefaultSpeed; + + public static (byte HolderID, byte LastHolderID) GetState() { - private static OptionItem Time; - private static OptionItem HolderSpeed; - private static OptionItem Range; + return (HotPotatoState.HolderID, HotPotatoState.LastHolderID); + } - private static (byte HolderID, byte LastHolderID, int TimeLeft, int RoundNum) HotPotatoState; - private static Dictionary SurvivalTimes; - private static float DefaultSpeed; + public static void SetupCustomOption() + { + Time = new IntegerOptionItem(69_213_001, "HotPotato_Time", new(1, 90, 1), 20, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.HotPotato) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(new Color32(232, 205, 70, byte.MaxValue)); - public static (byte HolderID, byte LastHolderID) GetState() + HolderSpeed = new FloatOptionItem(69_213_002, "HotPotato_HolderSpeed", new(0.1f, 5f, 0.1f), 2f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HotPotato) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new Color32(232, 205, 70, byte.MaxValue)); + + Range = new FloatOptionItem(69_213_004, "HotPotato_Range", new(0.25f, 5f, 0.25f), 1f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.HotPotato) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new Color32(232, 205, 70, byte.MaxValue)); + } + + public static void Init() + { + FixedUpdatePatch.Return = true; + + HotPotatoState = (byte.MaxValue, byte.MaxValue, Time.GetInt() + 60, 1); + SurvivalTimes = []; + foreach (PlayerControl pc in Main.AllPlayerControls) SurvivalTimes[pc.PlayerId] = 0; + + DefaultSpeed = Main.AllPlayerSpeed[0]; + } + + public static void OnGameStart() + { + LateTask.New(() => { FixedUpdatePatch.Return = false; }, 7f, log: false); + HotPotatoState = (byte.MaxValue, byte.MaxValue, Time.GetInt() + 40, 1); + } + + public static int GetSurvivalTime(byte id) + { + return SurvivalTimes.GetValueOrDefault(id, 1); + } + + public static string GetIndicator(byte id) + { + return HotPotatoState.HolderID == id ? " \u2668 " : string.Empty; + } + + public static string GetSuffixText(byte id) + { + if (!Main.PlayerStates.TryGetValue(id, out var state) || state.IsDead) return string.Empty; + return $"{(HotPotatoState.HolderID == id ? $"{Translator.GetString("HotPotato_HoldingNotify")}\n" : string.Empty)}{Translator.GetString("HotPotato_TimeLeftSuffix")}{HotPotatoState.TimeLeft}s"; + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + private static float UpdateDelay; + private static long LastFixedUpdate; + public static bool Return; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) { - return (HotPotatoState.HolderID, HotPotatoState.LastHolderID); - } + if (!CustomGameMode.HotPotato.IsActiveOrIntegrated() || Return || !AmongUsClient.Instance.AmHost || !GameStates.IsInTask || __instance.PlayerId == 255) return; - public static void SetupCustomOption() - { - Time = new IntegerOptionItem(69_213_001, "HotPotato_Time", new(1, 90, 1), 20, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.HotPotato) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(new Color32(232, 205, 70, byte.MaxValue)); + PlayerControl Holder = Utils.GetPlayerById(HotPotatoState.HolderID); - HolderSpeed = new FloatOptionItem(69_213_002, "HotPotato_HolderSpeed", new(0.1f, 5f, 0.1f), 2f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HotPotato) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new Color32(232, 205, 70, byte.MaxValue)); - - Range = new FloatOptionItem(69_213_004, "HotPotato_Range", new(0.25f, 5f, 0.25f), 1f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.HotPotato) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new Color32(232, 205, 70, byte.MaxValue)); - } - - public static void Init() - { - FixedUpdatePatch.Return = true; - - HotPotatoState = (byte.MaxValue, byte.MaxValue, Time.GetInt() + 60, 1); - SurvivalTimes = []; - foreach (PlayerControl pc in Main.AllPlayerControls) SurvivalTimes[pc.PlayerId] = 0; - - DefaultSpeed = Main.AllPlayerSpeed[0]; - } - - public static void OnGameStart() - { - LateTask.New(() => { FixedUpdatePatch.Return = false; }, 7f, log: false); - HotPotatoState = (byte.MaxValue, byte.MaxValue, Time.GetInt() + 40, 1); - } - - public static int GetSurvivalTime(byte id) - { - return SurvivalTimes.GetValueOrDefault(id, 1); - } - - public static string GetIndicator(byte id) - { - return HotPotatoState.HolderID == id ? " \u2668 " : string.Empty; - } - - public static string GetSuffixText(byte id) - { - if (!Main.PlayerStates.TryGetValue(id, out var state) || state.IsDead) return string.Empty; - return $"{(HotPotatoState.HolderID == id ? $"{Translator.GetString("HotPotato_HoldingNotify")}\n" : string.Empty)}{Translator.GetString("HotPotato_TimeLeftSuffix")}{HotPotatoState.TimeLeft}s"; - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch - { - private static float UpdateDelay; - private static long LastFixedUpdate; - public static bool Return; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) + if (Holder == null || Holder.Data.Disconnected || !Holder.IsAlive()) { - if (!CustomGameMode.HotPotato.IsActiveOrIntegrated() || Return || !AmongUsClient.Instance.AmHost || !GameStates.IsInTask || __instance.PlayerId == 255) return; - - PlayerControl Holder = Utils.GetPlayerById(HotPotatoState.HolderID); - - if (Holder == null || Holder.Data.Disconnected || !Holder.IsAlive()) - { - PassHotPotato(); - return; - } - - long now = Utils.TimeStamp; - - if (now > LastFixedUpdate) - { - HotPotatoState.TimeLeft--; - LastFixedUpdate = now; - Utils.NotifyRoles(); - } - - if (HotPotatoState.TimeLeft <= 0) - { - Holder.Suicide(); - SurvivalTimes[HotPotatoState.HolderID] = Time.GetInt() * (HotPotatoState.RoundNum - 1); - PassHotPotato(); - - if (Holder.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - - return; - } - - PlayerControl[] aapc = Main.AllAlivePlayerControls; - if (HotPotatoState.HolderID != __instance.PlayerId || !aapc.Any(x => x.PlayerId != HotPotatoState.HolderID && (x.PlayerId != HotPotatoState.LastHolderID || aapc.Length == 2) && Vector2.Distance(x.Pos(), Holder.Pos()) <= Range.GetFloat())) return; - - float wait = aapc.Length <= 2 ? 0.4f : 0.05f; - UpdateDelay += UnityEngine.Time.fixedDeltaTime; - if (UpdateDelay < wait) return; - - UpdateDelay = 0; - - PlayerControl Target = aapc.OrderBy(x => Vector2.Distance(x.Pos(), Holder.Pos())).FirstOrDefault(x => x.PlayerId != HotPotatoState.HolderID && (x.PlayerId != HotPotatoState.LastHolderID || aapc.Length == 2)); - if (Target == null) return; - - PassHotPotato(Target, false); + PassHotPotato(); + return; } - private static void PassHotPotato(PlayerControl target = null, bool resetTime = true) + long now = Utils.TimeStamp; + + if (now > LastFixedUpdate) { - if (Return || Main.AllAlivePlayerControls.Length < 2) return; + HotPotatoState.TimeLeft--; + LastFixedUpdate = now; + Utils.NotifyRoles(); + } - if (resetTime) + if (HotPotatoState.TimeLeft <= 0) + { + Holder.Suicide(); + SurvivalTimes[HotPotatoState.HolderID] = Time.GetInt() * (HotPotatoState.RoundNum - 1); + PassHotPotato(); + + if (Holder.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + + return; + } + + PlayerControl[] aapc = Main.AllAlivePlayerControls; + if (HotPotatoState.HolderID != __instance.PlayerId || !aapc.Any(x => x.PlayerId != HotPotatoState.HolderID && (x.PlayerId != HotPotatoState.LastHolderID || aapc.Length == 2) && Vector2.Distance(x.Pos(), Holder.Pos()) <= Range.GetFloat())) return; + + float wait = aapc.Length <= 2 ? 0.4f : 0.05f; + UpdateDelay += UnityEngine.Time.fixedDeltaTime; + if (UpdateDelay < wait) return; + + UpdateDelay = 0; + + PlayerControl Target = aapc.OrderBy(x => Vector2.Distance(x.Pos(), Holder.Pos())).FirstOrDefault(x => x.PlayerId != HotPotatoState.HolderID && (x.PlayerId != HotPotatoState.LastHolderID || aapc.Length == 2)); + if (Target == null) return; + + PassHotPotato(Target, false); + } + + private static void PassHotPotato(PlayerControl target = null, bool resetTime = true) + { + if (Return || Main.AllAlivePlayerControls.Length < 2) return; + + if (resetTime) + { + int time = Time.GetInt(); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) time *= AllInOneGameMode.HotPotatoTimerMultiplier.GetInt(); + + HotPotatoState.TimeLeft = time; + HotPotatoState.RoundNum++; + } + + try + { + target ??= Main.AllAlivePlayerControls.RandomElement(); + + HotPotatoState.LastHolderID = HotPotatoState.HolderID; + HotPotatoState.HolderID = target.PlayerId; + + Main.AllPlayerSpeed[target.PlayerId] = HolderSpeed.GetFloat(); + target.MarkDirtySettings(); + + PlayerControl LastHolder = Utils.GetPlayerById(HotPotatoState.LastHolderID); + + if (LastHolder != null) { - int time = Time.GetInt(); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) time *= AllInOneGameMode.HotPotatoTimerMultiplier.GetInt(); + Main.AllPlayerSpeed[HotPotatoState.LastHolderID] = DefaultSpeed; + LastHolder.MarkDirtySettings(); + Utils.NotifyRoles(SpecifyTarget: LastHolder); - HotPotatoState.TimeLeft = time; - HotPotatoState.RoundNum++; - } - - try - { - target ??= Main.AllAlivePlayerControls.RandomElement(); - - HotPotatoState.LastHolderID = HotPotatoState.HolderID; - HotPotatoState.HolderID = target.PlayerId; - - Main.AllPlayerSpeed[target.PlayerId] = HolderSpeed.GetFloat(); - target.MarkDirtySettings(); - - PlayerControl LastHolder = Utils.GetPlayerById(HotPotatoState.LastHolderID); - - if (LastHolder != null) - { - Main.AllPlayerSpeed[HotPotatoState.LastHolderID] = DefaultSpeed; - LastHolder.MarkDirtySettings(); - Utils.NotifyRoles(SpecifyTarget: LastHolder); - - Logger.Info($"Hot Potato Passed: {LastHolder.GetRealName()} => {target.GetRealName()}", "HotPotato"); - } - } - catch (Exception ex) - { - Logger.Exception(ex, "HotPotatoManager.FixedUpdatePatch.PassHotPotato"); - } - finally - { - Utils.NotifyRoles(SpecifyTarget: target); + Logger.Info($"Hot Potato Passed: {LastHolder.GetRealName()} => {target.GetRealName()}", "HotPotato"); } } + catch (Exception ex) + { + Logger.Exception(ex, "HotPotatoManager.FixedUpdatePatch.PassHotPotato"); + } + finally + { + Utils.NotifyRoles(SpecifyTarget: target); + } } } } \ No newline at end of file diff --git a/GameMode/MoveAndStop.cs b/GameMode/MoveAndStop.cs index 0e7546d51..42a8034b1 100644 --- a/GameMode/MoveAndStop.cs +++ b/GameMode/MoveAndStop.cs @@ -6,453 +6,452 @@ using HarmonyLib; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +public class Counter(int totalGreenTime, int totalRedTime, long startTimeStamp, char symbol, bool isRed, Func randomRedTimeFunc, Func randomGreenTimeFunc, bool isYellow = false) { - public class Counter(int totalGreenTime, int totalRedTime, long startTimeStamp, char symbol, bool isRed, Func randomRedTimeFunc, Func randomGreenTimeFunc, bool isYellow = false) + private int TotalGreenTime { get; set; } = totalGreenTime; + + private int TotalRedTime { get; set; } = totalRedTime; + + private long StartTimeStamp { get; set; } = startTimeStamp; + + private char Symbol { get; } = symbol; + + public bool IsRed { get; private set; } = isRed; + + private Func RandomRedTime { get; } = randomRedTimeFunc; + + private Func RandomGreenTime { get; } = randomGreenTimeFunc; + + private bool IsYellow { get; set; } = isYellow; + + private static int TotalYellowTime => 3; + + private int Timer => (IsRed ? TotalRedTime : IsYellow ? TotalYellowTime : TotalGreenTime) - (int)Math.Round((double)(Utils.TimeStamp - StartTimeStamp)); + + public string ColoredTimerString { - private int TotalGreenTime { get; set; } = totalGreenTime; - - private int TotalRedTime { get; set; } = totalRedTime; - - private long StartTimeStamp { get; set; } = startTimeStamp; - - private char Symbol { get; } = symbol; - - public bool IsRed { get; private set; } = isRed; - - private Func RandomRedTime { get; } = randomRedTimeFunc; - - private Func RandomGreenTime { get; } = randomGreenTimeFunc; - - private bool IsYellow { get; set; } = isYellow; - - private static int TotalYellowTime => 3; - - private int Timer => (IsRed ? TotalRedTime : IsYellow ? TotalYellowTime : TotalGreenTime) - (int)Math.Round((double)(Utils.TimeStamp - StartTimeStamp)); - - public string ColoredTimerString + get { - get - { - string result = IsYellow || (Timer == TotalGreenTime && !IsRed && !IsYellow) || (Timer == TotalRedTime && IsRed) ? Utils.ColorString(Color.clear, "0") : Utils.ColorString(IsRed ? Color.red : Color.green, Timer < 10 ? $" {Timer}" : Timer.ToString()); + string result = IsYellow || (Timer == TotalGreenTime && !IsRed && !IsYellow) || (Timer == TotalRedTime && IsRed) ? Utils.ColorString(Color.clear, "0") : Utils.ColorString(IsRed ? Color.red : Color.green, Timer < 10 ? $" {Timer}" : Timer.ToString()); - if (Timer is <= 19 and >= 10 && !IsYellow) result = $" {result}"; + if (Timer is <= 19 and >= 10 && !IsYellow) result = $" {result}"; - if (Timer % 10 == 1 && !IsYellow) result = result.Insert(result.Length - 9, " "); + if (Timer % 10 == 1 && !IsYellow) result = result.Insert(result.Length - 9, " "); - return $"{result}"; - } - } - - public string ColoredArrow => Utils.ColorString(IsRed ? Timer <= 2 ? Palette.Orange : Color.red : IsYellow ? Color.yellow : Color.green, Symbol.ToString()); - - public void Update() - { - if (Timer <= 0) - { - switch (IsRed) - { - // Change from green to yellow - case false when !IsYellow: - IsYellow = true; - break; - // Change from red to green - case true when !IsYellow: - TotalGreenTime = RandomGreenTime(Symbol); - IsRed = false; - break; - // Change from yellow to red - case false when IsYellow: - TotalRedTime = RandomRedTime(Symbol); - IsYellow = false; - IsRed = true; - break; - } - - StartTimeStamp = Utils.TimeStamp; - } + return $"{result}"; } } - internal class MoveAndStopPlayerData(Counter leftCounter, Counter middleCounter, Counter rightCounter, float positionX, float positionY, int lives) + public string ColoredArrow => Utils.ColorString(IsRed ? Timer <= 2 ? Palette.Orange : Color.red : IsYellow ? Color.yellow : Color.green, Symbol.ToString()); + + public void Update() { - public Counter LeftCounter { get; } = leftCounter; - - public Counter MiddleCounter { get; } = middleCounter; - - public Counter RightCounter { get; } = rightCounter; - - public float PositionX { get; set; } = positionX; - - public float PositionY { get; set; } = positionY; - - public int Lives { get; private set; } = lives; - - private float LostLifeCooldownTimer { get; set; } - - public override string ToString() + if (Timer <= 0) { - string leftTimer = LeftCounter.ColoredTimerString; - string middleTimer = MiddleCounter.ColoredTimerString; - string rightTimer = RightCounter.ColoredTimerString; - - var arrowRow = $"{LeftCounter.ColoredArrow} {MiddleCounter.ColoredArrow} {RightCounter.ColoredArrow}"; - var counterRow = $"{leftTimer} {middleTimer} {rightTimer}"; - - return $"{counterRow}\n{arrowRow}"; - } - - public void UpdateCounters() - { - LeftCounter.Update(); - MiddleCounter.Update(); - RightCounter.Update(); - - if (LostLifeCooldownTimer > 0f) LostLifeCooldownTimer -= Time.deltaTime; - } - - public bool RemoveLife(PlayerControl pc) - { - if (LostLifeCooldownTimer > 0f) return false; - - Lives--; - LostLifeCooldownTimer = 1f; - - if (Lives <= 0) + switch (IsRed) { - pc.Suicide(); - return true; + // Change from green to yellow + case false when !IsYellow: + IsYellow = true; + break; + // Change from red to green + case true when !IsYellow: + TotalGreenTime = RandomGreenTime(Symbol); + IsRed = false; + break; + // Change from yellow to red + case false when IsYellow: + TotalRedTime = RandomRedTime(Symbol); + IsYellow = false; + IsRed = true; + break; } - pc.KillFlash(); - pc.Notify(string.Format(GetString("MoveAndStop_LivesRemainingNotify"), Lives)); - - return false; + StartTimeStamp = Utils.TimeStamp; } } +} - internal static class MoveAndStop +internal class MoveAndStopPlayerData(Counter leftCounter, Counter middleCounter, Counter rightCounter, float positionX, float positionY, int lives) +{ + public Counter LeftCounter { get; } = leftCounter; + + public Counter MiddleCounter { get; } = middleCounter; + + public Counter RightCounter { get; } = rightCounter; + + public float PositionX { get; set; } = positionX; + + public float PositionY { get; set; } = positionY; + + public int Lives { get; private set; } = lives; + + private float LostLifeCooldownTimer { get; set; } + + public override string ToString() { - private static Dictionary AllPlayerTimers = []; + string leftTimer = LeftCounter.ColoredTimerString; + string middleTimer = MiddleCounter.ColoredTimerString; + string rightTimer = RightCounter.ColoredTimerString; - private static OptionItem GameTime; - private static OptionItem PlayerLives; - private static OptionItem RightCounterGreenMin; - private static OptionItem RightCounterRedMin; - private static OptionItem RightCounterGreenMax; - private static OptionItem RightCounterRedMax; - private static OptionItem LeftCounterGreenMin; - private static OptionItem LeftCounterRedMin; - private static OptionItem LeftCounterGreenMax; - private static OptionItem LeftCounterRedMax; - private static OptionItem MiddleCounterGreenMin; - private static OptionItem MiddleCounterRedMin; - private static OptionItem MiddleCounterGreenMax; - private static OptionItem MiddleCounterRedMax; - private static OptionItem ExtraGreenTimeOnAirhip; - private static OptionItem ExtraGreenTimeOnFungle; - private static OptionItem EnableTutorial; + var arrowRow = $"{LeftCounter.ColoredArrow} {MiddleCounter.ColoredArrow} {RightCounter.ColoredArrow}"; + var counterRow = $"{leftTimer} {middleTimer} {rightTimer}"; - public static readonly HashSet HasPlayed = []; + return $"{counterRow}\n{arrowRow}"; + } - private static IRandom Random => IRandom.Instance; + public void UpdateCounters() + { + LeftCounter.Update(); + MiddleCounter.Update(); + RightCounter.Update(); - public static int RoundTime { get; set; } + if (LostLifeCooldownTimer > 0f) LostLifeCooldownTimer -= Time.deltaTime; + } - private static bool HasJustStarted => GameTime.GetInt() - RoundTime < 20; + public bool RemoveLife(PlayerControl pc) + { + if (LostLifeCooldownTimer > 0f) return false; - private static int ExtraGreenTime => Main.CurrentMap switch + Lives--; + LostLifeCooldownTimer = 1f; + + if (Lives <= 0) { - MapNames.Airship => ExtraGreenTimeOnAirhip.GetInt(), - MapNames.Fungle => ExtraGreenTimeOnFungle.GetInt(), - _ => 0 - } + (Options.CurrentGameMode == CustomGameMode.AllInOne ? IRandom.Instance.Next(AllInOneGameMode.MoveAndStopMinGreenTimeBonus.GetInt(), AllInOneGameMode.MoveAndStopMaxGreenTimeBonus.GetInt() + 1) : 0); - - private static IntegerValueRule CounterValueRule => new(1, 100, 1); - private static IntegerValueRule ExtraTimeValue => new(0, 50, 1); - private static int DefaultMinValue => 5; - private static int DefaultMaxValue => 30; - - public static string HUDText => string.Format(GetString("KBTimeRemain"), RoundTime.ToString()); - - private static int StartingGreenTime(PlayerControl pc) - { - if (Options.CurrentGameMode == CustomGameMode.AllInOne) return 60; - bool tutorial = EnableTutorial.GetBool() && !HasPlayed.Contains(pc.FriendCode); - return Main.CurrentMap == MapNames.Airship ? tutorial ? 57 : 47 : tutorial ? 47 : 37; + pc.Suicide(); + return true; } - private static int RandomRedTime(char direction) + pc.KillFlash(); + pc.Notify(string.Format(GetString("MoveAndStop_LivesRemainingNotify"), Lives)); + + return false; + } +} + +internal static class MoveAndStop +{ + private static Dictionary AllPlayerTimers = []; + + private static OptionItem GameTime; + private static OptionItem PlayerLives; + private static OptionItem RightCounterGreenMin; + private static OptionItem RightCounterRedMin; + private static OptionItem RightCounterGreenMax; + private static OptionItem RightCounterRedMax; + private static OptionItem LeftCounterGreenMin; + private static OptionItem LeftCounterRedMin; + private static OptionItem LeftCounterGreenMax; + private static OptionItem LeftCounterRedMax; + private static OptionItem MiddleCounterGreenMin; + private static OptionItem MiddleCounterRedMin; + private static OptionItem MiddleCounterGreenMax; + private static OptionItem MiddleCounterRedMax; + private static OptionItem ExtraGreenTimeOnAirhip; + private static OptionItem ExtraGreenTimeOnFungle; + private static OptionItem EnableTutorial; + + public static readonly HashSet HasPlayed = []; + + private static IRandom Random => IRandom.Instance; + + public static int RoundTime { get; set; } + + private static bool HasJustStarted => GameTime.GetInt() - RoundTime < 20; + + private static int ExtraGreenTime => Main.CurrentMap switch + { + MapNames.Airship => ExtraGreenTimeOnAirhip.GetInt(), + MapNames.Fungle => ExtraGreenTimeOnFungle.GetInt(), + _ => 0 + } + (Options.CurrentGameMode == CustomGameMode.AllInOne ? IRandom.Instance.Next(AllInOneGameMode.MoveAndStopMinGreenTimeBonus.GetInt(), AllInOneGameMode.MoveAndStopMaxGreenTimeBonus.GetInt() + 1) : 0); + + private static IntegerValueRule CounterValueRule => new(1, 100, 1); + private static IntegerValueRule ExtraTimeValue => new(0, 50, 1); + private static int DefaultMinValue => 5; + private static int DefaultMaxValue => 30; + + public static string HUDText => string.Format(GetString("KBTimeRemain"), RoundTime.ToString()); + + private static int StartingGreenTime(PlayerControl pc) + { + if (Options.CurrentGameMode == CustomGameMode.AllInOne) return 60; + bool tutorial = EnableTutorial.GetBool() && !HasPlayed.Contains(pc.FriendCode); + return Main.CurrentMap == MapNames.Airship ? tutorial ? 57 : 47 : tutorial ? 47 : 37; + } + + private static int RandomRedTime(char direction) + { + return direction switch { - return direction switch - { - '➡' => Random.Next(RightCounterRedMin.GetInt(), RightCounterRedMax.GetInt()), - '⇅' => Random.Next(MiddleCounterRedMin.GetInt(), MiddleCounterRedMax.GetInt()), - '⬅' => Random.Next(LeftCounterRedMin.GetInt(), LeftCounterRedMax.GetInt()), - _ => throw new ArgumentException("Invalid symbol representing the direction (RandomRedTime method in MoveAndStop.cs)", nameof(direction)) - }; - } + '➡' => Random.Next(RightCounterRedMin.GetInt(), RightCounterRedMax.GetInt()), + '⇅' => Random.Next(MiddleCounterRedMin.GetInt(), MiddleCounterRedMax.GetInt()), + '⬅' => Random.Next(LeftCounterRedMin.GetInt(), LeftCounterRedMax.GetInt()), + _ => throw new ArgumentException("Invalid symbol representing the direction (RandomRedTime method in MoveAndStop.cs)", nameof(direction)) + }; + } - private static int RandomGreenTime(char direction) + private static int RandomGreenTime(char direction) + { + return ExtraGreenTime + direction switch { - return ExtraGreenTime + direction switch - { - '➡' => Random.Next(RightCounterGreenMin.GetInt(), RightCounterGreenMax.GetInt()), - '⇅' => Random.Next(MiddleCounterGreenMin.GetInt(), MiddleCounterGreenMax.GetInt()), - '⬅' => Random.Next(LeftCounterGreenMin.GetInt(), LeftCounterGreenMax.GetInt()), - _ => throw new ArgumentException("Invalid symbol representing the direction (RandomGreenTime method in MoveAndStop.cs)", nameof(direction)) - }; - } + '➡' => Random.Next(RightCounterGreenMin.GetInt(), RightCounterGreenMax.GetInt()), + '⇅' => Random.Next(MiddleCounterGreenMin.GetInt(), MiddleCounterGreenMax.GetInt()), + '⬅' => Random.Next(LeftCounterGreenMin.GetInt(), LeftCounterGreenMax.GetInt()), + _ => throw new ArgumentException("Invalid symbol representing the direction (RandomGreenTime method in MoveAndStop.cs)", nameof(direction)) + }; + } - private static string CounterSettingString(string direction, bool red, bool min) + private static string CounterSettingString(string direction, bool red, bool min) + { + return $"MoveAndStop_{direction}Counter{(red ? "Red" : "Green")}{(min ? "Min" : "Max")}"; + } + + private static OptionItem CreateSetting(int Id, string direction, bool red, bool min) + { + return new IntegerOptionItem(Id, CounterSettingString(direction, red, min), CounterValueRule, min ? DefaultMinValue : DefaultMaxValue, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.MoveAndStop) + .SetColor(new Color32(0, 255, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .AddReplacement(new("Red", Utils.ColorString(Color.red, "Red"))) + .AddReplacement(new("Green", Utils.ColorString(Color.green, "Green"))) + .AddReplacement(new("Minimum", Utils.ColorString(Color.blue, "Minimum"))) + .AddReplacement(new("Maximum", Utils.ColorString(Color.gray, "Maximum"))) + .AddReplacement(new("Right", Utils.ColorString(Color.magenta, "Right"))) + .AddReplacement(new("Left", Utils.ColorString(Color.yellow, "Left"))) + .AddReplacement(new("Middle", Utils.ColorString(Color.white, "Middle"))); + } + + private static OptionItem CreateExtraTimeSetting(int Id, string mapName, int defaultValue) + { + return new IntegerOptionItem(Id, $"MoveAndStop_ExtraGreenTimeOn{mapName}", ExtraTimeValue, defaultValue, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.MoveAndStop) + .SetColor(new Color32(0, 255, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .AddReplacement(new("Green", Utils.ColorString(Color.green, "Green"))); + } + + public static void SetupCustomOption() + { + GameTime = new IntegerOptionItem(68_213_001, "FFA_GameTime", new(30, 1200, 10), 900, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.MoveAndStop) + .SetColor(new Color32(0, 255, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetHeader(true); + + PlayerLives = new IntegerOptionItem(68_213_017, "MoveAndStop_PlayerLives", new(1, 10, 1), 3, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.MoveAndStop) + .SetColor(new Color32(0, 255, 255, byte.MaxValue)) + .SetValueFormat(OptionFormat.Health) + .SetHeader(true); + + RightCounterGreenMin = CreateSetting(68_213_002, "Right", false, true).SetHeader(true); + RightCounterRedMin = CreateSetting(68_213_003, "Right", true, true); + RightCounterGreenMax = CreateSetting(68_213_004, "Right", false, false); + RightCounterRedMax = CreateSetting(68_213_005, "Right", true, false); + LeftCounterGreenMin = CreateSetting(68_213_006, "Left", false, true); + LeftCounterRedMin = CreateSetting(68_213_007, "Left", true, true); + LeftCounterGreenMax = CreateSetting(68_213_008, "Left", false, false); + LeftCounterRedMax = CreateSetting(68_213_009, "Left", true, false); + MiddleCounterGreenMin = CreateSetting(68_213_010, "Middle", false, true); + MiddleCounterRedMin = CreateSetting(68_213_011, "Middle", true, true); + MiddleCounterGreenMax = CreateSetting(68_213_012, "Middle", false, false); + MiddleCounterRedMax = CreateSetting(68_213_013, "Middle", true, false); + ExtraGreenTimeOnAirhip = CreateExtraTimeSetting(68_213_014, "Airship", 20); + ExtraGreenTimeOnFungle = CreateExtraTimeSetting(68_213_015, "Fungle", 10); + + EnableTutorial = new BooleanOptionItem(68_213_016, "MoveAndStop_EnableTutorial", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.MoveAndStop) + .SetHeader(true) + .SetColor(new Color32(0, 255, 255, byte.MaxValue)); + } + + public static void Init() + { + if (!CustomGameMode.MoveAndStop.IsActiveOrIntegrated()) return; + + FixedUpdatePatch.LastSuffix = []; + FixedUpdatePatch.Limit = []; + AllPlayerTimers = []; + RoundTime = GameTime.GetInt() + 8; + + FixedUpdatePatch.DoChecks = false; + LateTask.New(() => FixedUpdatePatch.DoChecks = true, 10f, log: false); + + long now = Utils.TimeStamp; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - return $"MoveAndStop_{direction}Counter{(red ? "Red" : "Green")}{(min ? "Min" : "Max")}"; - } + int startingGreenTime = StartingGreenTime(pc); + Vector2 pos = pc.Pos(); - private static OptionItem CreateSetting(int Id, string direction, bool red, bool min) - { - return new IntegerOptionItem(Id, CounterSettingString(direction, red, min), CounterValueRule, min ? DefaultMinValue : DefaultMaxValue, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.MoveAndStop) - .SetColor(new Color32(0, 255, 255, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds) - .AddReplacement(new("Red", Utils.ColorString(Color.red, "Red"))) - .AddReplacement(new("Green", Utils.ColorString(Color.green, "Green"))) - .AddReplacement(new("Minimum", Utils.ColorString(Color.blue, "Minimum"))) - .AddReplacement(new("Maximum", Utils.ColorString(Color.gray, "Maximum"))) - .AddReplacement(new("Right", Utils.ColorString(Color.magenta, "Right"))) - .AddReplacement(new("Left", Utils.ColorString(Color.yellow, "Left"))) - .AddReplacement(new("Middle", Utils.ColorString(Color.white, "Middle"))); - } + AllPlayerTimers.TryAdd(pc.PlayerId, new( + new(startingGreenTime, RandomRedTime('⬅'), now, '⬅', false, RandomRedTime, RandomGreenTime), + new(startingGreenTime, RandomRedTime('⇅'), now, '⇅', false, RandomRedTime, RandomGreenTime), + new(startingGreenTime, RandomRedTime('➡'), now, '➡', false, RandomRedTime, RandomGreenTime), + pos.x, pos.y, PlayerLives.GetInt())); - private static OptionItem CreateExtraTimeSetting(int Id, string mapName, int defaultValue) - { - return new IntegerOptionItem(Id, $"MoveAndStop_ExtraGreenTimeOn{mapName}", ExtraTimeValue, defaultValue, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.MoveAndStop) - .SetColor(new Color32(0, 255, 255, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds) - .AddReplacement(new("Green", Utils.ColorString(Color.green, "Green"))); - } + float limit; - public static void SetupCustomOption() - { - GameTime = new IntegerOptionItem(68_213_001, "FFA_GameTime", new(30, 1200, 10), 900, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.MoveAndStop) - .SetColor(new Color32(0, 255, 255, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds) - .SetHeader(true); - - PlayerLives = new IntegerOptionItem(68_213_017, "MoveAndStop_PlayerLives", new(1, 10, 1), 3, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.MoveAndStop) - .SetColor(new Color32(0, 255, 255, byte.MaxValue)) - .SetValueFormat(OptionFormat.Health) - .SetHeader(true); - - RightCounterGreenMin = CreateSetting(68_213_002, "Right", false, true).SetHeader(true); - RightCounterRedMin = CreateSetting(68_213_003, "Right", true, true); - RightCounterGreenMax = CreateSetting(68_213_004, "Right", false, false); - RightCounterRedMax = CreateSetting(68_213_005, "Right", true, false); - LeftCounterGreenMin = CreateSetting(68_213_006, "Left", false, true); - LeftCounterRedMin = CreateSetting(68_213_007, "Left", true, true); - LeftCounterGreenMax = CreateSetting(68_213_008, "Left", false, false); - LeftCounterRedMax = CreateSetting(68_213_009, "Left", true, false); - MiddleCounterGreenMin = CreateSetting(68_213_010, "Middle", false, true); - MiddleCounterRedMin = CreateSetting(68_213_011, "Middle", true, true); - MiddleCounterGreenMax = CreateSetting(68_213_012, "Middle", false, false); - MiddleCounterRedMax = CreateSetting(68_213_013, "Middle", true, false); - ExtraGreenTimeOnAirhip = CreateExtraTimeSetting(68_213_014, "Airship", 20); - ExtraGreenTimeOnFungle = CreateExtraTimeSetting(68_213_015, "Fungle", 10); - - EnableTutorial = new BooleanOptionItem(68_213_016, "MoveAndStop_EnableTutorial", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.MoveAndStop) - .SetHeader(true) - .SetColor(new Color32(0, 255, 255, byte.MaxValue)); - } - - public static void Init() - { - if (!CustomGameMode.MoveAndStop.IsActiveOrIntegrated()) return; - - FixedUpdatePatch.LastSuffix = []; - FixedUpdatePatch.Limit = []; - AllPlayerTimers = []; - RoundTime = GameTime.GetInt() + 8; - - FixedUpdatePatch.DoChecks = false; - LateTask.New(() => FixedUpdatePatch.DoChecks = true, 10f, log: false); - - long now = Utils.TimeStamp; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - int startingGreenTime = StartingGreenTime(pc); - Vector2 pos = pc.Pos(); - - AllPlayerTimers.TryAdd(pc.PlayerId, new( - new(startingGreenTime, RandomRedTime('⬅'), now, '⬅', false, RandomRedTime, RandomGreenTime), - new(startingGreenTime, RandomRedTime('⇅'), now, '⇅', false, RandomRedTime, RandomGreenTime), - new(startingGreenTime, RandomRedTime('➡'), now, '➡', false, RandomRedTime, RandomGreenTime), - pos.x, pos.y, PlayerLives.GetInt())); - - float limit; - - try - { - limit = pc.GetClient().PlatformData.Platform is Platforms.Unknown or Platforms.IPhone or Platforms.Android or Platforms.Switch or Platforms.Xbox or Platforms.Playstation - ? 2f // If the player has a joystick, the game is a lot harder - : 0.5f; // On PC, you have WASD, you can't mess up - } - catch - { - limit = 2f; - } - - FixedUpdatePatch.Limit[pc.PlayerId] = limit; - } - } - - public static int GetRankFromScore(byte playerId) - { try { - int ms = Main.PlayerStates[playerId].TaskState.CompletedTasksCount; - int rank = 1 + Main.PlayerStates.Values.Count(x => x.TaskState.CompletedTasksCount > ms); - rank += Main.PlayerStates.Values.Where(x => x.TaskState.CompletedTasksCount == ms).ToList().IndexOf(Main.PlayerStates[playerId]); - return rank; + limit = pc.GetClient().PlatformData.Platform is Platforms.Unknown or Platforms.IPhone or Platforms.Android or Platforms.Switch or Platforms.Xbox or Platforms.Playstation + ? 2f // If the player has a joystick, the game is a lot harder + : 0.5f; // On PC, you have WASD, you can't mess up } catch { - return Main.AllPlayerControls.Length; + limit = 2f; } + + FixedUpdatePatch.Limit[pc.PlayerId] = limit; } + } - public static string GetSuffixText(PlayerControl pc) + public static int GetRankFromScore(byte playerId) + { + try { - if (!pc.IsAlive() || !AllPlayerTimers.TryGetValue(pc.PlayerId, out MoveAndStopPlayerData timers)) return string.Empty; - - var text = timers.ToString(); - - if (HasJustStarted && EnableTutorial.GetBool() && !HasPlayed.Contains(pc.FriendCode) && Options.CurrentGameMode != CustomGameMode.AllInOne) - text += $"\n\n{GetString("MoveAndStop_Tutorial")}"; - - return text; + int ms = Main.PlayerStates[playerId].TaskState.CompletedTasksCount; + int rank = 1 + Main.PlayerStates.Values.Count(x => x.TaskState.CompletedTasksCount > ms); + rank += Main.PlayerStates.Values.Where(x => x.TaskState.CompletedTasksCount == ms).ToList().IndexOf(Main.PlayerStates[playerId]); + return rank; } - - public static int GetLivesRemaining(byte id) => AllPlayerTimers.TryGetValue(id, out MoveAndStopPlayerData data) ? data.Lives : 0; - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch + catch { - public static bool DoChecks; - private static long LastFixedUpdate; - public static Dictionary LastSuffix = []; - public static Dictionary Limit = []; + return Main.AllPlayerControls.Length; + } + } - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) + public static string GetSuffixText(PlayerControl pc) + { + if (!pc.IsAlive() || !AllPlayerTimers.TryGetValue(pc.PlayerId, out MoveAndStopPlayerData timers)) return string.Empty; + + var text = timers.ToString(); + + if (HasJustStarted && EnableTutorial.GetBool() && !HasPlayed.Contains(pc.FriendCode) && Options.CurrentGameMode != CustomGameMode.AllInOne) + text += $"\n\n{GetString("MoveAndStop_Tutorial")}"; + + return text; + } + + public static int GetLivesRemaining(byte id) => AllPlayerTimers.TryGetValue(id, out MoveAndStopPlayerData data) ? data.Lives : 0; + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + public static bool DoChecks; + private static long LastFixedUpdate; + public static Dictionary LastSuffix = []; + public static Dictionary Limit = []; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + if (!GameStates.IsInTask || !CustomGameMode.MoveAndStop.IsActiveOrIntegrated() || !__instance.IsAlive() || !AmongUsClient.Instance.AmHost || !DoChecks || __instance.PlayerId == 255) return; + + PlayerControl pc = __instance; + + if (AllPlayerTimers.TryGetValue(pc.PlayerId, out MoveAndStopPlayerData data)) { - if (!GameStates.IsInTask || !CustomGameMode.MoveAndStop.IsActiveOrIntegrated() || !__instance.IsAlive() || !AmongUsClient.Instance.AmHost || !DoChecks || __instance.PlayerId == 255) return; + Vector2 previousPosition = new(data.PositionX, data.PositionY); - PlayerControl pc = __instance; + // Update the player's position + Vector2 currentPosition = pc.transform.position; + currentPosition.x += data.PositionX * Time.deltaTime; + currentPosition.y += data.PositionY * Time.deltaTime; - if (AllPlayerTimers.TryGetValue(pc.PlayerId, out MoveAndStopPlayerData data)) + // Calculate the direction of movement + Vector2 direction = currentPosition - previousPosition; + + // Normalize the direction vector to get a unit vector + direction.Normalize(); + + // Calculate the distance moved along each axis + float distanceX = currentPosition.x - previousPosition.x; + float distanceY = currentPosition.y - previousPosition.y; + + float limit = Limit.GetValueOrDefault(pc.PlayerId, 2f); + + switch (direction.x) { - Vector2 previousPosition = new(data.PositionX, data.PositionY); - - // Update the player's position - Vector2 currentPosition = pc.transform.position; - currentPosition.x += data.PositionX * Time.deltaTime; - currentPosition.y += data.PositionY * Time.deltaTime; - - // Calculate the direction of movement - Vector2 direction = currentPosition - previousPosition; - - // Normalize the direction vector to get a unit vector - direction.Normalize(); - - // Calculate the distance moved along each axis - float distanceX = currentPosition.x - previousPosition.x; - float distanceY = currentPosition.y - previousPosition.y; - - float limit = Limit.GetValueOrDefault(pc.PlayerId, 2f); - - switch (direction.x) + // Now we can check the components of the direction vector to determine the movement direction + // Player is moving right + case > 0: { - // Now we can check the components of the direction vector to determine the movement direction - // Player is moving right - case > 0: + switch (data.RightCounter.IsRed) { - switch (data.RightCounter.IsRed) - { - // If: Right counter is red && player moved more than their limit - case true when distanceX > limit: - if (data.RemoveLife(pc)) // Remove a life from the player - goto End; // If no lives left, kill them and skip the rest of the code + // If: Right counter is red && player moved more than their limit + case true when distanceX > limit: + if (data.RemoveLife(pc)) // Remove a life from the player + goto End; // If no lives left, kill them and skip the rest of the code - // If the player has lives left, update their position - goto case false; - // Else If: the Right counter is yellow or green - case false: - data.PositionX = currentPosition.x; // Update the player's last position regardless of the distance - break; - } - - break; + // If the player has lives left, update their position + goto case false; + // Else If: the Right counter is yellow or green + case false: + data.PositionX = currentPosition.x; // Update the player's last position regardless of the distance + break; } - // Player is moving left - case < 0: - { - switch (data.LeftCounter.IsRed) - { - // The distance is negative here because it's the opposite direction as right - case true when distanceX < -limit: - if (data.RemoveLife(pc)) goto End; - goto case false; - case false: - data.PositionX = currentPosition.x; - break; - } - break; - } + break; } - - if (direction.y is > 0 or < 0) // y > 0 means the player is moving up, y < 0 means the player is moving down + // Player is moving left + case < 0: { - switch (data.MiddleCounter.IsRed) + switch (data.LeftCounter.IsRed) { - // The player dies if either they moved up OR down too far - case true when distanceY > limit || distanceY < -limit: + // The distance is negative here because it's the opposite direction as right + case true when distanceX < -limit: if (data.RemoveLife(pc)) goto End; goto case false; case false: - data.PositionY = currentPosition.y; + data.PositionX = currentPosition.x; break; } + + break; } - - End: - - data.UpdateCounters(); - - if (!pc.IsAlive()) goto NoSuffix; // If the player is dead, there's no need to get them a suffix and notify them - - string suffix = GetSuffixText(pc); - - if (!LastSuffix.TryGetValue(pc.PlayerId, out string beforeSuffix) || beforeSuffix != suffix) - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - LastSuffix[pc.PlayerId] = suffix; } - NoSuffix: + if (direction.y is > 0 or < 0) // y > 0 means the player is moving up, y < 0 means the player is moving down + { + switch (data.MiddleCounter.IsRed) + { + // The player dies if either they moved up OR down too far + case true when distanceY > limit || distanceY < -limit: + if (data.RemoveLife(pc)) goto End; + goto case false; + case false: + data.PositionY = currentPosition.y; + break; + } + } - long now = Utils.TimeStamp; - if (LastFixedUpdate == now) return; + End: - LastFixedUpdate = now; + data.UpdateCounters(); - RoundTime--; + if (!pc.IsAlive()) goto NoSuffix; // If the player is dead, there's no need to get them a suffix and notify them + + string suffix = GetSuffixText(pc); + + if (!LastSuffix.TryGetValue(pc.PlayerId, out string beforeSuffix) || beforeSuffix != suffix) + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + LastSuffix[pc.PlayerId] = suffix; } + + NoSuffix: + + long now = Utils.TimeStamp; + if (LastFixedUpdate == now) return; + + LastFixedUpdate = now; + + RoundTime--; } } } \ No newline at end of file diff --git a/GameMode/NaturalDisasters.cs b/GameMode/NaturalDisasters.cs index aae43073c..028a859ce 100644 --- a/GameMode/NaturalDisasters.cs +++ b/GameMode/NaturalDisasters.cs @@ -9,937 +9,936 @@ using HarmonyLib; using UnityEngine; using Random = UnityEngine.Random; -namespace EHR +namespace EHR; + +[SuppressMessage("ReSharper", "UnusedType.Local")] +[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] +public static class NaturalDisasters { - [SuppressMessage("ReSharper", "UnusedType.Local")] - [SuppressMessage("ReSharper", "ClassNeverInstantiated.Local")] - public static class NaturalDisasters + private const float Range = 1.5f; + private static List AllDisasters = []; + private static readonly List ActiveDisasters = []; + private static readonly List PreparingDisasters = []; + private static readonly Dictionary SurvivalTimes = []; + + private static ((float Left, float Right) X, (float Bottom, float Top) Y) MapBounds; + private static long GameStartTimeStamp; + + private static OptionItem DisasterFrequency; + private static OptionItem DisasterWarningTime; + private static OptionItem LimitMaximumDisastersAtOnce; + private static OptionItem MaximumDisastersAtOnce; + private static OptionItem WhenLimitIsReached; + private static OptionItem PreferRemovingThunderstorm; + private static readonly Dictionary DisasterSpawnChances = []; + + private static readonly string[] LimitReachedOptions = + [ + "ND_LimitReachedOptions.OnlySpawnInstantDisasters", + "ND_LimitReachedOptions.RemoveRandom", + "ND_LimitReachedOptions.RemoveOldest" + ]; + + public static List GetActiveDisasters() => ActiveDisasters; + public static List GetAllDisasters() => AllDisasters; + + public static void SetupCustomOption() { - private const float Range = 1.5f; - private static List AllDisasters = []; - private static readonly List ActiveDisasters = []; - private static readonly List PreparingDisasters = []; - private static readonly Dictionary SurvivalTimes = []; + var id = 69_216_001; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + const CustomGameMode gameMode = CustomGameMode.NaturalDisasters; - private static ((float Left, float Right) X, (float Bottom, float Top) Y) MapBounds; - private static long GameStartTimeStamp; + DisasterFrequency = new IntegerOptionItem(id++, "ND_DisasterFrequency", new(1, 20, 1), 2, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(gameMode) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem DisasterFrequency; - private static OptionItem DisasterWarningTime; - private static OptionItem LimitMaximumDisastersAtOnce; - private static OptionItem MaximumDisastersAtOnce; - private static OptionItem WhenLimitIsReached; - private static OptionItem PreferRemovingThunderstorm; - private static readonly Dictionary DisasterSpawnChances = []; + DisasterWarningTime = new IntegerOptionItem(id++, "ND_DisasterWarningTime", new(1, 30, 1), 5, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); - private static readonly string[] LimitReachedOptions = - [ - "ND_LimitReachedOptions.OnlySpawnInstantDisasters", - "ND_LimitReachedOptions.RemoveRandom", - "ND_LimitReachedOptions.RemoveOldest" - ]; + LimitMaximumDisastersAtOnce = new BooleanOptionItem(id++, "ND_LimitMaximumDisastersAtOnce", false, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color); - public static List GetActiveDisasters() => ActiveDisasters; - public static List GetAllDisasters() => AllDisasters; + MaximumDisastersAtOnce = new IntegerOptionItem(id++, "ND_MaximumDisastersAtOnce", new(1, 120, 1), 20, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetParent(LimitMaximumDisastersAtOnce) + .SetColor(color); - public static void SetupCustomOption() + WhenLimitIsReached = new StringOptionItem(id++, "ND_WhenLimitIsReached", LimitReachedOptions, 2, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetParent(LimitMaximumDisastersAtOnce) + .SetColor(color); + + PreferRemovingThunderstorm = new BooleanOptionItem(id++, "ND_PreferRemovingThunderstorm", true, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetParent(WhenLimitIsReached) + .SetColor(color); + + LoadAllDisasters(); + + AllDisasters.ConvertAll(x => x.Name).ForEach(x => DisasterSpawnChances[x] = new IntegerOptionItem(id++, "ND_Disaster.SpawnChance", new(0, 100, 5), 50, TabGroup.GameSettings) + .SetGameMode(gameMode) + .SetColor(color) + .SetValueFormat(OptionFormat.Percent) + .AddReplacement(("{disaster}", Translator.GetString($"ND_{x}")))); + + AllDisasters.ForEach(x => x.GetMethod("SetupOwnCustomOption")?.Invoke(null, null)); + } + + public static void LoadAllDisasters() + { + AllDisasters = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Disaster))) + .ToList(); + } + + public static void OnGameStart() + { + LoadAllDisasters(); + SurvivalTimes.Clear(); + ActiveDisasters.Clear(); + PreparingDisasters.Clear(); + Sinkhole.Sinkholes.Clear(); + BuildingCollapse.CollapsedRooms.Clear(); + BuildingCollapse.LastPosition.Clear(); + + if (!CustomGameMode.NaturalDisasters.IsActiveOrIntegrated()) return; + + Dictionary.ValueCollection rooms = RoomLocations()?.Values; + if (rooms == null) return; + + float[] x = rooms.Select(r => r.x).ToArray(); + float[] y = rooms.Select(r => r.y).ToArray(); + + const float extend = 3.5f; + MapBounds = ((x.Min() - extend, x.Max() + extend), (y.Min() - extend, y.Max() + extend)); + + GameStartTimeStamp = Utils.TimeStamp + 8; + + LateTask.New(() => { - var id = 69_216_001; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); - const CustomGameMode gameMode = CustomGameMode.NaturalDisasters; + Main.AllPlayerSpeed = Main.PlayerStates.Keys.ToDictionary(k => k, _ => Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod)); + Utils.SyncAllSettings(); + }, 11f, log: false); + } - DisasterFrequency = new IntegerOptionItem(id++, "ND_DisasterFrequency", new(1, 20, 1), 2, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(gameMode) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); + public static void ApplyGameOptions(IGameOptions opt, byte id) + { + ActiveDisasters.ForEach(x => x.ApplyOwnGameOptions(opt, id)); + } - DisasterWarningTime = new IntegerOptionItem(id++, "ND_DisasterWarningTime", new(1, 30, 1), 5, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); + public static string SuffixText() + { + string cb = string.Format(Translator.GetString("CollapsedBuildings"), BuildingCollapse.CollapsedBuildingsString); + string ts = ActiveDisasters.Exists(x => x is Thunderstorm) ? $"\n{Translator.GetString("OngoingThunderstorm")}" : string.Empty; + return $"{cb}{ts}"; + } - LimitMaximumDisastersAtOnce = new BooleanOptionItem(id++, "ND_LimitMaximumDisastersAtOnce", false, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color); + public static int SurvivalTime(byte id) + { + return SurvivalTimes.GetValueOrDefault(id, 0); + } - MaximumDisastersAtOnce = new IntegerOptionItem(id++, "ND_MaximumDisastersAtOnce", new(1, 120, 1), 20, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetParent(LimitMaximumDisastersAtOnce) - .SetColor(color); + public static void RecordDeath(PlayerControl pc, PlayerState.DeathReason deathReason) + { + SurvivalTimes[pc.PlayerId] = (int)(Utils.TimeStamp - GameStartTimeStamp); - WhenLimitIsReached = new StringOptionItem(id++, "ND_WhenLimitIsReached", LimitReachedOptions, 2, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetParent(LimitMaximumDisastersAtOnce) - .SetColor(color); + string message = Translator.GetString($"ND_DRLaughMessage.{deathReason}"); + message = Utils.ColorString(DeathReasonColor(deathReason), message); + LateTask.New(() => pc.Notify(message, 20f), 1f, $"{pc.GetRealName()} died with the reason {deathReason}, survived for {SurvivalTime(pc.PlayerId)} seconds"); + } - PreferRemovingThunderstorm = new BooleanOptionItem(id++, "ND_PreferRemovingThunderstorm", true, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetParent(WhenLimitIsReached) - .SetColor(color); - - LoadAllDisasters(); - - AllDisasters.ConvertAll(x => x.Name).ForEach(x => DisasterSpawnChances[x] = new IntegerOptionItem(id++, "ND_Disaster.SpawnChance", new(0, 100, 5), 50, TabGroup.GameSettings) - .SetGameMode(gameMode) - .SetColor(color) - .SetValueFormat(OptionFormat.Percent) - .AddReplacement(("{disaster}", Translator.GetString($"ND_{x}")))); - - AllDisasters.ForEach(x => x.GetMethod("SetupOwnCustomOption")?.Invoke(null, null)); - } - - public static void LoadAllDisasters() + private static Color DeathReasonColor(PlayerState.DeathReason deathReason) + { + return deathReason switch { - AllDisasters = Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(x => x.IsClass && !x.IsAbstract && x.IsSubclassOf(typeof(Disaster))) - .ToList(); - } + PlayerState.DeathReason.Meteor => Color.red, + PlayerState.DeathReason.Lava => Palette.Orange, + PlayerState.DeathReason.Tornado => Color.gray, + PlayerState.DeathReason.Lightning => Palette.White_75Alpha, + PlayerState.DeathReason.Drowned => Color.cyan, + PlayerState.DeathReason.Sunken => Color.yellow, + PlayerState.DeathReason.Collapsed => Palette.Brown, + _ => Color.white + }; + } - public static void OnGameStart() + private static string Sprite(string name) + { + return name switch { - LoadAllDisasters(); - SurvivalTimes.Clear(); - ActiveDisasters.Clear(); - PreparingDisasters.Clear(); - Sinkhole.Sinkholes.Clear(); - BuildingCollapse.CollapsedRooms.Clear(); - BuildingCollapse.LastPosition.Clear(); + "Earthquake" => "<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588<#adadad>\u2588\u2588\u2588\u2588\u2588
<#5e5e5e>\u2588<#000000>\u2588<#5e5e5e>\u2588<#adadad>\u2588<#adadad>\u2588\u2588\u2588\u2588
<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#adadad>\u2588<#adadad>\u2588\u2588
<#adadad>\u2588<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#adadad>\u2588
\u2588<#adadad>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588
\u2588\u2588<#adadad>\u2588<#adadad>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588
\u2588\u2588\u2588\u2588<#adadad>\u2588<#adadad>\u2588<#5e5e5e>\u2588<#000000>\u2588
\u2588\u2588\u2588\u2588\u2588<#adadad>\u2588<#5e5e5e>\u2588<#000000>\u2588
", + "Meteor" => "\u2588\u2588\u2588<#fff700>\u2588<#fff700>\u2588\u2588\u2588\u2588
\u2588\u2588<#fff700>\u2588<#ffae00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588\u2588
\u2588<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588
<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff1100>\u2588<#ff1100>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588
<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff1100>\u2588<#ff1100>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588
\u2588<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588
\u2588\u2588<#fff700>\u2588<#ffae00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588\u2588
\u2588\u2588\u2588<#fff700>\u2588<#fff700>\u2588\u2588\u2588\u2588
", + "VolcanoEruption" => "\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588\u2588
\u2588\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
", + "Tornado" => "\u2588\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588\u2588\u2588
\u2588<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#828282>\u2588<#828282>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#828282>\u2588<#474747>\u2588<#474747>\u2588<#828282>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#828282>\u2588<#474747>\u2588<#474747>\u2588<#828282>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#828282>\u2588<#828282>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
\u2588<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588\u2588
\u2588\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588\u2588\u2588
", + "SandStorm" => "<#ffdc7a>█<#dbcfba>█<#f5c387>█<#ffe8d6>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█
<#dbcfba>█<#f5c6a2>█<#ffc18f>█<#ffe8d6>█<#e6a875>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█
<#e6a875>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█<#ffe8d6>█<#f5c387>█
<#ffe8d6>█<#ffdc7a>█<#dbcfba>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#f5c6a2>█<#ffc18f>█
<#f5c6a2>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#f5c6a2>█<#e6a875>█<#dbcfba>█<#ffe8d6>█
<#ffc18f>█<#ffe8d6>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█<#ffe8d6>█<#e6a875>█
<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█
<#ffdc7a>█<#ffe8d6>█<#e6a875>█<#ffe8d6>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█
", + "Sinkhole" => "<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588
<#545454>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#7d7d7d>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#545454>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#7d7d7d>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#545454>\u2588
<#545454>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#7d7d7d>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#545454>\u2588<#545454>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588
", + _ => Utils.EmptyMessage + }; + } - if (!CustomGameMode.NaturalDisasters.IsActiveOrIntegrated()) return; + private static Dictionary RoomLocations() + { + return Main.CurrentMap switch + { + MapNames.Skeld => new RandomSpawn.SkeldSpawnMap().positions, + MapNames.Mira => new RandomSpawn.MiraHQSpawnMap().positions, + MapNames.Polus => new RandomSpawn.PolusSpawnMap().positions, + MapNames.Dleks => new RandomSpawn.DleksSpawnMap().positions, + MapNames.Airship => new RandomSpawn.AirshipSpawnMap().positions, + MapNames.Fungle => new RandomSpawn.FungleSpawnMap().positions, + _ => null + }; + } - Dictionary.ValueCollection rooms = RoomLocations()?.Values; - if (rooms == null) return; + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + public static class FixedUpdatePatch + { + private static long LastDisaster = Utils.TimeStamp; + private static long LastSync = Utils.TimeStamp; - float[] x = rooms.Select(r => r.x).ToArray(); - float[] y = rooms.Select(r => r.y).ToArray(); + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.NaturalDisasters.IsActiveOrIntegrated() || Main.HasJustStarted || GameStartTimeStamp + 5 > Utils.TimeStamp || !__instance.IsHost()) return; - const float extend = 3.5f; - MapBounds = ((x.Min() - extend, x.Max() + extend), (y.Min() - extend, y.Max() + extend)); + UpdatePreparingDisasters(); - GameStartTimeStamp = Utils.TimeStamp + 8; + ActiveDisasters.ToArray().Do(x => x.Update()); + Sinkhole.OnFixedUpdate(); + BuildingCollapse.OnFixedUpdate(); - LateTask.New(() => + if (LimitMaximumDisastersAtOnce.GetBool()) { - Main.AllPlayerSpeed = Main.PlayerStates.Keys.ToDictionary(k => k, _ => Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod)); - Utils.SyncAllSettings(); - }, 11f, log: false); - } + int numDisasters = ActiveDisasters.Count + PreparingDisasters.Count + Sinkhole.Sinkholes.Count; - public static void ApplyGameOptions(IGameOptions opt, byte id) - { - ActiveDisasters.ForEach(x => x.ApplyOwnGameOptions(opt, id)); - } - - public static string SuffixText() - { - string cb = string.Format(Translator.GetString("CollapsedBuildings"), BuildingCollapse.CollapsedBuildingsString); - string ts = ActiveDisasters.Exists(x => x is Thunderstorm) ? $"\n{Translator.GetString("OngoingThunderstorm")}" : string.Empty; - return $"{cb}{ts}"; - } - - public static int SurvivalTime(byte id) - { - return SurvivalTimes.GetValueOrDefault(id, 0); - } - - public static void RecordDeath(PlayerControl pc, PlayerState.DeathReason deathReason) - { - SurvivalTimes[pc.PlayerId] = (int)(Utils.TimeStamp - GameStartTimeStamp); - - string message = Translator.GetString($"ND_DRLaughMessage.{deathReason}"); - message = Utils.ColorString(DeathReasonColor(deathReason), message); - LateTask.New(() => pc.Notify(message, 20f), 1f, $"{pc.GetRealName()} died with the reason {deathReason}, survived for {SurvivalTime(pc.PlayerId)} seconds"); - } - - private static Color DeathReasonColor(PlayerState.DeathReason deathReason) - { - return deathReason switch - { - PlayerState.DeathReason.Meteor => Color.red, - PlayerState.DeathReason.Lava => Palette.Orange, - PlayerState.DeathReason.Tornado => Color.gray, - PlayerState.DeathReason.Lightning => Palette.White_75Alpha, - PlayerState.DeathReason.Drowned => Color.cyan, - PlayerState.DeathReason.Sunken => Color.yellow, - PlayerState.DeathReason.Collapsed => Palette.Brown, - _ => Color.white - }; - } - - private static string Sprite(string name) - { - return name switch - { - "Earthquake" => "<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588<#adadad>\u2588\u2588\u2588\u2588\u2588
<#5e5e5e>\u2588<#000000>\u2588<#5e5e5e>\u2588<#adadad>\u2588<#adadad>\u2588\u2588\u2588\u2588
<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#adadad>\u2588<#adadad>\u2588\u2588
<#adadad>\u2588<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#adadad>\u2588
\u2588<#adadad>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#5e5e5e>\u2588
\u2588\u2588<#adadad>\u2588<#adadad>\u2588<#5e5e5e>\u2588<#5e5e5e>\u2588<#000000>\u2588<#000000>\u2588
\u2588\u2588\u2588\u2588<#adadad>\u2588<#adadad>\u2588<#5e5e5e>\u2588<#000000>\u2588
\u2588\u2588\u2588\u2588\u2588<#adadad>\u2588<#5e5e5e>\u2588<#000000>\u2588
", - "Meteor" => "\u2588\u2588\u2588<#fff700>\u2588<#fff700>\u2588\u2588\u2588\u2588
\u2588\u2588<#fff700>\u2588<#ffae00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588\u2588
\u2588<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588
<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff1100>\u2588<#ff1100>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588
<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff1100>\u2588<#ff1100>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588
\u2588<#fff700>\u2588<#ffae00>\u2588<#ff6f00>\u2588<#ff6f00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588
\u2588\u2588<#fff700>\u2588<#ffae00>\u2588<#ffae00>\u2588<#fff700>\u2588\u2588\u2588
\u2588\u2588\u2588<#fff700>\u2588<#fff700>\u2588\u2588\u2588\u2588
", - "VolcanoEruption" => "\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588\u2588
\u2588\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
", - "Tornado" => "\u2588\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588\u2588\u2588
\u2588<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#828282>\u2588<#828282>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#828282>\u2588<#474747>\u2588<#474747>\u2588<#828282>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#828282>\u2588<#474747>\u2588<#474747>\u2588<#828282>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#828282>\u2588<#828282>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588
\u2588<#dbdbdb>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#b0b0b0>\u2588<#dbdbdb>\u2588\u2588
\u2588\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588<#dbdbdb>\u2588\u2588\u2588
", - "SandStorm" => "<#ffdc7a>█<#dbcfba>█<#f5c387>█<#ffe8d6>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█
<#dbcfba>█<#f5c6a2>█<#ffc18f>█<#ffe8d6>█<#e6a875>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█
<#e6a875>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█<#ffe8d6>█<#f5c387>█
<#ffe8d6>█<#ffdc7a>█<#dbcfba>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#f5c6a2>█<#ffc18f>█
<#f5c6a2>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#f5c6a2>█<#e6a875>█<#dbcfba>█<#ffe8d6>█
<#ffc18f>█<#ffe8d6>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█<#ffe8d6>█<#e6a875>█
<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█<#ffe8d6>█<#f5c6a2>█<#ffe8d6>█
<#ffdc7a>█<#ffe8d6>█<#e6a875>█<#ffe8d6>█<#ffc18f>█<#ffe8d6>█<#ffe8d6>█<#ffdc7a>█
", - "Sinkhole" => "<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588
<#545454>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#7d7d7d>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#545454>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#7d7d7d>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#545454>\u2588
<#545454>\u2588<#424242>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#000000>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#7d7d7d>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#424242>\u2588<#7d7d7d>\u2588
<#545454>\u2588<#545454>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588<#7d7d7d>\u2588<#7d7d7d>\u2588<#545454>\u2588
", - _ => Utils.EmptyMessage - }; - } - - private static Dictionary RoomLocations() - { - return Main.CurrentMap switch - { - MapNames.Skeld => new RandomSpawn.SkeldSpawnMap().positions, - MapNames.Mira => new RandomSpawn.MiraHQSpawnMap().positions, - MapNames.Polus => new RandomSpawn.PolusSpawnMap().positions, - MapNames.Dleks => new RandomSpawn.DleksSpawnMap().positions, - MapNames.Airship => new RandomSpawn.AirshipSpawnMap().positions, - MapNames.Fungle => new RandomSpawn.FungleSpawnMap().positions, - _ => null - }; - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - public static class FixedUpdatePatch - { - private static long LastDisaster = Utils.TimeStamp; - private static long LastSync = Utils.TimeStamp; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.NaturalDisasters.IsActiveOrIntegrated() || Main.HasJustStarted || GameStartTimeStamp + 5 > Utils.TimeStamp || !__instance.IsHost()) return; - - UpdatePreparingDisasters(); - - ActiveDisasters.ToArray().Do(x => x.Update()); - Sinkhole.OnFixedUpdate(); - BuildingCollapse.OnFixedUpdate(); - - if (LimitMaximumDisastersAtOnce.GetBool()) + if (numDisasters > MaximumDisastersAtOnce.GetInt()) { - int numDisasters = ActiveDisasters.Count + PreparingDisasters.Count + Sinkhole.Sinkholes.Count; - - if (numDisasters > MaximumDisastersAtOnce.GetInt()) + switch (WhenLimitIsReached.GetValue()) { - switch (WhenLimitIsReached.GetValue()) + case 0: { - case 0: + AllDisasters.RemoveAll(x => x.Name is "Earthquake" or "VolcanoEruption" or "Tornado" or "Thunderstorm" or "SandStorm" or "Tsunami"); + break; + } + case 1: + { + if (IRandom.Instance.Next(AllDisasters.Count) == 0) { - AllDisasters.RemoveAll(x => x.Name is "Earthquake" or "VolcanoEruption" or "Tornado" or "Thunderstorm" or "SandStorm" or "Tsunami"); - break; + Sinkhole.RemoveRandomSinkhole(); + return; } - case 1: - { - if (IRandom.Instance.Next(AllDisasters.Count) == 0) - { - Sinkhole.RemoveRandomSinkhole(); - return; - } - Disaster remove = PreferRemovingThunderstorm.GetBool() ? ActiveDisasters.Find(x => x is Thunderstorm) : ActiveDisasters.RandomElement(); - if (remove != null) remove.Duration = 0; + Disaster remove = PreferRemovingThunderstorm.GetBool() ? ActiveDisasters.Find(x => x is Thunderstorm) : ActiveDisasters.RandomElement(); + if (remove != null) remove.Duration = 0; - remove?.RemoveIfExpired(); - break; - } - case 2: - { - Disaster oldest = ActiveDisasters.MinBy(x => x.StartTimeStamp); - oldest.Duration = 0; - oldest.RemoveIfExpired(); - break; - } + remove?.RemoveIfExpired(); + break; + } + case 2: + { + Disaster oldest = ActiveDisasters.MinBy(x => x.StartTimeStamp); + oldest.Duration = 0; + oldest.RemoveIfExpired(); + break; } } } - - long now = Utils.TimeStamp; - - int frequency = DisasterFrequency.GetInt(); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) frequency *= AllInOneGameMode.NaturalDisastersDisasterSpawnCooldownMultiplier.GetInt(); - - if (now - LastDisaster >= frequency) - { - LastDisaster = now; - List disasters = AllDisasters.ToList(); - if (ActiveDisasters.Exists(x => x is Thunderstorm)) disasters.RemoveAll(x => x.Name == "Thunderstorm"); - - Type disaster = disasters.SelectMany(x => Enumerable.Repeat(x, DisasterSpawnChances[x.Name].GetInt() / 5)).RandomElement(); - KeyValuePair roomKvp = RoomLocations().RandomElement(); - - Vector2 position = disaster.Name switch - { - "BuildingCollapse" => roomKvp.Value, - "Thunderstorm" => Pelican.GetBlackRoomPS(), - _ => IRandom.Instance.Next(2) == 0 - ? Main.AllAlivePlayerControls.RandomElement().Pos() - : new(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Top, MapBounds.Y.Bottom)) - }; - - SystemTypes? room = disaster.Name == "BuildingCollapse" ? roomKvp.Key : null; - float warningTime = DisasterWarningTime.GetFloat(); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) warningTime *= AllInOneGameMode.NaturalDisastersWarningDurationMultiplier.GetInt(); - PreparingDisasters.Add(new(position, warningTime, Sprite(disaster.Name), disaster.Name, room)); - } - - if (now - LastSync >= 10) - { - LastSync = now; - BuildingCollapse.CollapsedRooms.Clear(); - Sinkhole.RemoveRandomSinkhole(); - Utils.NotifyRoles(); - Utils.MarkEveryoneDirtySettings(); - } } - public static void UpdatePreparingDisasters() + long now = Utils.TimeStamp; + + int frequency = DisasterFrequency.GetInt(); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) frequency *= AllInOneGameMode.NaturalDisastersDisasterSpawnCooldownMultiplier.GetInt(); + + if (now - LastDisaster >= frequency) { - foreach (NaturalDisaster naturalDisaster in PreparingDisasters.ToArray()) + LastDisaster = now; + List disasters = AllDisasters.ToList(); + if (ActiveDisasters.Exists(x => x is Thunderstorm)) disasters.RemoveAll(x => x.Name == "Thunderstorm"); + + Type disaster = disasters.SelectMany(x => Enumerable.Repeat(x, DisasterSpawnChances[x.Name].GetInt() / 5)).RandomElement(); + KeyValuePair roomKvp = RoomLocations().RandomElement(); + + Vector2 position = disaster.Name switch { - naturalDisaster.Update(); + "BuildingCollapse" => roomKvp.Value, + "Thunderstorm" => Pelican.GetBlackRoomPS(), + _ => IRandom.Instance.Next(2) == 0 + ? Main.AllAlivePlayerControls.RandomElement().Pos() + : new(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Top, MapBounds.Y.Bottom)) + }; - if (float.IsNaN(naturalDisaster.SpawnTimer)) - { - Type type = AllDisasters.Find(d => d.Name == naturalDisaster.DisasterName); - LateTask.New(() => Activator.CreateInstance(type, naturalDisaster.Position, naturalDisaster), 1f, log: false); - PreparingDisasters.Remove(naturalDisaster); - } - } + SystemTypes? room = disaster.Name == "BuildingCollapse" ? roomKvp.Key : null; + float warningTime = DisasterWarningTime.GetFloat(); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) warningTime *= AllInOneGameMode.NaturalDisastersWarningDurationMultiplier.GetInt(); + PreparingDisasters.Add(new(position, warningTime, Sprite(disaster.Name), disaster.Name, room)); } - public static void AddPreparingDisaster(Vector2 position, string disasterName, SystemTypes? room) => PreparingDisasters.Add(new(position, DisasterWarningTime.GetFloat(), Sprite(disasterName), disasterName, room)); - } - - public abstract class Disaster - { - protected Disaster(Vector2 position) + if (now - LastSync >= 10) { - Position = position; - ActiveDisasters.Add(this); - } - - public long StartTimeStamp { get; } = Utils.TimeStamp; - protected Vector2 Position { get; set; } - protected NaturalDisaster NetObject { get; init; } - public virtual int Duration { get; set; } - - public virtual bool RemoveIfExpired() - { - if (Duration == 0 || Utils.TimeStamp - StartTimeStamp >= Duration) - { - ActiveDisasters.Remove(this); - NetObject.Despawn(); - return true; - } - - return false; - } - - public abstract void Update(); - - public virtual void ApplyOwnGameOptions(IGameOptions opt, byte id) { } - - protected void KillNearbyPlayers(PlayerState.DeathReason deathReason, float range = Range) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (Vector2.Distance(pc.Pos(), Position) <= range) - pc.Suicide(deathReason); + LastSync = now; + BuildingCollapse.CollapsedRooms.Clear(); + Sinkhole.RemoveRandomSinkhole(); + Utils.NotifyRoles(); + Utils.MarkEveryoneDirtySettings(); } } - public sealed class Earthquake : Disaster + public static void UpdatePreparingDisasters() { - private static OptionItem DurationOpt; - private static OptionItem Speed; - - private readonly HashSet AffectedPlayers = []; - - public Earthquake(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + foreach (NaturalDisaster naturalDisaster in PreparingDisasters.ToArray()) { - NetObject = naturalDisaster; - Update(); + naturalDisaster.Update(); + + if (float.IsNaN(naturalDisaster.SpawnTimer)) + { + Type type = AllDisasters.Find(d => d.Name == naturalDisaster.DisasterName); + LateTask.New(() => Activator.CreateInstance(type, naturalDisaster.Position, naturalDisaster), 1f, log: false); + PreparingDisasters.Remove(naturalDisaster); + } + } + } + + public static void AddPreparingDisaster(Vector2 position, string disasterName, SystemTypes? room) => PreparingDisasters.Add(new(position, DisasterWarningTime.GetFloat(), Sprite(disasterName), disasterName, room)); + } + + public abstract class Disaster + { + protected Disaster(Vector2 position) + { + Position = position; + ActiveDisasters.Add(this); + } + + public long StartTimeStamp { get; } = Utils.TimeStamp; + protected Vector2 Position { get; set; } + protected NaturalDisaster NetObject { get; init; } + public virtual int Duration { get; set; } + + public virtual bool RemoveIfExpired() + { + if (Duration == 0 || Utils.TimeStamp - StartTimeStamp >= Duration) + { + ActiveDisasters.Remove(this); + NetObject.Despawn(); + return true; } - public override int Duration { get; set; } = DurationOpt.GetInt(); + return false; + } - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void SetupOwnCustomOption() + public abstract void Update(); + + public virtual void ApplyOwnGameOptions(IGameOptions opt, byte id) { } + + protected void KillNearbyPlayers(PlayerState.DeathReason deathReason, float range = Range) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (Vector2.Distance(pc.Pos(), Position) <= range) + pc.Suicide(deathReason); + } + } + + public sealed class Earthquake : Disaster + { + private static OptionItem DurationOpt; + private static OptionItem Speed; + + private readonly HashSet AffectedPlayers = []; + + public Earthquake(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + } + + public override int Duration { get; set; } = DurationOpt.GetInt(); + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void SetupOwnCustomOption() + { + const int id = 69_216_100; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + + DurationOpt = new IntegerOptionItem(id, "ND_Earthquake.DurationOpt", new(1, 120, 1), 30, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + + Speed = new FloatOptionItem(id + 1, "ND_Earthquake.Speed", new(0.05f, 2f, 0.05f), 0.2f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Update() + { + if (RemoveIfExpired()) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - const int id = 69_216_100; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + float speed = (Vector2.Distance(pc.Pos(), Position) <= Range) switch + { + true when AffectedPlayers.Add(pc.PlayerId) => Speed.GetFloat(), + false when AffectedPlayers.Remove(pc.PlayerId) => Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod), + _ => float.NaN + }; - DurationOpt = new IntegerOptionItem(id, "ND_Earthquake.DurationOpt", new(1, 120, 1), 30, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - - Speed = new FloatOptionItem(id + 1, "ND_Earthquake.Speed", new(0.05f, 2f, 0.05f), 0.2f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Multiplier); + if (!float.IsNaN(speed) && !Mathf.Approximately(Main.AllPlayerSpeed[pc.PlayerId], speed)) + { + Main.AllPlayerSpeed[pc.PlayerId] = speed; + pc.MarkDirtySettings(); + } } + } - public override void Update() + public override bool RemoveIfExpired() + { + if (base.RemoveIfExpired()) { - if (RemoveIfExpired()) return; - foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - float speed = (Vector2.Distance(pc.Pos(), Position) <= Range) switch + if (AffectedPlayers.Remove(pc.PlayerId)) { - true when AffectedPlayers.Add(pc.PlayerId) => Speed.GetFloat(), - false when AffectedPlayers.Remove(pc.PlayerId) => Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod), - _ => float.NaN - }; - - if (!float.IsNaN(speed) && !Mathf.Approximately(Main.AllPlayerSpeed[pc.PlayerId], speed)) - { - Main.AllPlayerSpeed[pc.PlayerId] = speed; + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); pc.MarkDirtySettings(); } } + + return true; } - public override bool RemoveIfExpired() + return false; + } + } + + public sealed class Meteor : Disaster + { + public Meteor(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + + KillNearbyPlayers(PlayerState.DeathReason.Meteor); + } + + public override int Duration { get; set; } = 5; + + public override void Update() + { + RemoveIfExpired(); + } + } + + public sealed class VolcanoEruption : Disaster + { + private const int Phases = 4; + private static OptionItem FlowStepDelay; + private static OptionItem DurationAfterFlowComplete; + + private int Phase = 1; + private float Timer; + + public VolcanoEruption(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + } + + public override int Duration { get; set; } = (int)((Phases - 1) * FlowStepDelay.GetFloat()) + DurationAfterFlowComplete.GetInt(); + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void SetupOwnCustomOption() + { + const int id = 69_216_200; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + + FlowStepDelay = new FloatOptionItem(id, "ND_VolcanoEruption.FlowStepDelay", new(0.5f, 5f, 0.5f), 1f, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + + DurationAfterFlowComplete = new IntegerOptionItem(id + 1, "ND_VolcanoEruption.DurationAfterFlowComplete", new(1, 120, 1), 10, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Update() + { + if (RemoveIfExpired()) return; + + Timer += Time.deltaTime; + + if (Timer >= FlowStepDelay.GetFloat()) { - if (base.RemoveIfExpired()) + Timer = 0f; + Phase++; + if (Phase > Phases) return; + + string newSprite = Phase switch + { + 2 => "\u2588\u2588\u2588\u2588\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
", + 3 => "<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
", + 4 => "<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
", + _ => Utils.EmptyMessage + }; + + NetObject.RpcChangeSprite(newSprite); + } + + float range = Range - ((Phases - Phase) * 0.4f); + KillNearbyPlayers(PlayerState.DeathReason.Lava, range); + } + } + + public sealed class Tornado : Disaster + { + private static OptionItem DurationOpt; + private static OptionItem GoesThroughWalls; + private static OptionItem MovingSpeed; + + private float Angle = RandomAngle(); + private int Count = 2; + + private long LastAngleChange = Utils.TimeStamp; + + public Tornado(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + } + + public override int Duration { get; set; } = DurationOpt.GetInt(); + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void SetupOwnCustomOption() + { + const int id = 69_216_300; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + + DurationOpt = new IntegerOptionItem(id, "ND_Tornado.DurationOpt", new(1, 120, 1), 40, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + + GoesThroughWalls = new BooleanOptionItem(id + 1, "ND_Tornado.GoesThroughWalls", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color); + + MovingSpeed = new FloatOptionItem(id + 2, "ND_Tornado.MovingSpeed", new(0f, 0.5f, 0.05f), 0.1f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Update() + { + if (RemoveIfExpired()) return; + + const float eyeRange = Range / 4f; + const float dragRange = Range * 1.25f; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + Vector2 pos = pc.Pos(); + + switch (Vector2.Distance(pos, Position)) + { + case <= eyeRange: + pc.Suicide(PlayerState.DeathReason.Tornado); + continue; + case <= dragRange: + Vector2 direction = (Position - pos).normalized; + Vector2 newPosition = pos + (direction * 0.1f); + pc.TP(newPosition); + continue; + } + } + + if (Count++ < 3) return; + + Count = 0; + + float angle; + long now = Utils.TimeStamp; + + if (LastAngleChange + 7 <= now) + { + angle = RandomAngle(); + LastAngleChange = now; + Angle = angle; + } + else angle = Angle; + + Vector2 newPos = Position + (new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * MovingSpeed.GetFloat()); + + if ((!GoesThroughWalls.GetBool() && PhysicsHelpers.AnythingBetween(NetObject.playerControl.Collider, Position, newPos, Constants.ShipOnlyMask, false)) || + newPos.x < MapBounds.X.Left || newPos.x > MapBounds.X.Right || newPos.y < MapBounds.Y.Bottom || newPos.y > MapBounds.Y.Top) + { + Angle = RandomAngle(); + LastAngleChange = now; + Count = 2; + return; + } + + Position = newPos; + NetObject.TP(Position); + } + + private static float RandomAngle() + { + return Random.Range(0, 2 * Mathf.PI); + } + } + + public sealed class Thunderstorm : Disaster + { + private static OptionItem HitFrequency; + private static OptionItem DurationOpt; + + private long LastHit; + + public Thunderstorm(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + + naturalDisaster.Despawn(); + + LateTask.New(() => Utils.NotifyRoles(), 0.2f, "Thunderstorm Notify"); + } + + public override int Duration { get; set; } = DurationOpt.GetInt(); + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void SetupOwnCustomOption() + { + const int id = 69_216_400; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + + DurationOpt = new IntegerOptionItem(id, "ND_Thunderstorm.DurationOpt", new(1, 120, 1), 20, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + + HitFrequency = new IntegerOptionItem(id + 1, "ND_Thunderstorm.HitFrequency", new(1, 20, 1), 3, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Update() + { + if (RemoveIfExpired()) return; + + long now = Utils.TimeStamp; + + if (now - LastHit >= HitFrequency.GetInt()) + { + LastHit = now; + var hit = new Vector2(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Bottom, MapBounds.Y.Top)); + var cno = new Lightning(hit); + + if (cno.playerControl.GetPlainShipRoom() != default(PlainShipRoom)) + cno.Despawn(); + else { foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (AffectedPlayers.Remove(pc.PlayerId)) - { - Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - pc.MarkDirtySettings(); - } - } - - return true; - } - - return false; - } - } - - public sealed class Meteor : Disaster - { - public Meteor(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - - KillNearbyPlayers(PlayerState.DeathReason.Meteor); - } - - public override int Duration { get; set; } = 5; - - public override void Update() - { - RemoveIfExpired(); - } - } - - public sealed class VolcanoEruption : Disaster - { - private const int Phases = 4; - private static OptionItem FlowStepDelay; - private static OptionItem DurationAfterFlowComplete; - - private int Phase = 1; - private float Timer; - - public VolcanoEruption(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - } - - public override int Duration { get; set; } = (int)((Phases - 1) * FlowStepDelay.GetFloat()) + DurationAfterFlowComplete.GetInt(); - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void SetupOwnCustomOption() - { - const int id = 69_216_200; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); - - FlowStepDelay = new FloatOptionItem(id, "ND_VolcanoEruption.FlowStepDelay", new(0.5f, 5f, 0.5f), 1f, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - - DurationAfterFlowComplete = new IntegerOptionItem(id + 1, "ND_VolcanoEruption.DurationAfterFlowComplete", new(1, 120, 1), 10, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Update() - { - if (RemoveIfExpired()) return; - - Timer += Time.deltaTime; - - if (Timer >= FlowStepDelay.GetFloat()) - { - Timer = 0f; - Phase++; - if (Phase > Phases) return; - - string newSprite = Phase switch - { - 2 => "\u2588\u2588\u2588\u2588\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588\u2588
\u2588\u2588\u2588\u2588\u2588\u2588
", - 3 => "<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
", - 4 => "<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588<#ff6200>\u2588
", - _ => Utils.EmptyMessage - }; - - NetObject.RpcChangeSprite(newSprite); - } - - float range = Range - ((Phases - Phase) * 0.4f); - KillNearbyPlayers(PlayerState.DeathReason.Lava, range); - } - } - - public sealed class Tornado : Disaster - { - private static OptionItem DurationOpt; - private static OptionItem GoesThroughWalls; - private static OptionItem MovingSpeed; - - private float Angle = RandomAngle(); - private int Count = 2; - - private long LastAngleChange = Utils.TimeStamp; - - public Tornado(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - } - - public override int Duration { get; set; } = DurationOpt.GetInt(); - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void SetupOwnCustomOption() - { - const int id = 69_216_300; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); - - DurationOpt = new IntegerOptionItem(id, "ND_Tornado.DurationOpt", new(1, 120, 1), 40, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - - GoesThroughWalls = new BooleanOptionItem(id + 1, "ND_Tornado.GoesThroughWalls", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color); - - MovingSpeed = new FloatOptionItem(id + 2, "ND_Tornado.MovingSpeed", new(0f, 0.5f, 0.05f), 0.1f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Update() - { - if (RemoveIfExpired()) return; - - const float eyeRange = Range / 4f; - const float dragRange = Range * 1.25f; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - Vector2 pos = pc.Pos(); - - switch (Vector2.Distance(pos, Position)) - { - case <= eyeRange: - pc.Suicide(PlayerState.DeathReason.Tornado); - continue; - case <= dragRange: - Vector2 direction = (Position - pos).normalized; - Vector2 newPosition = pos + (direction * 0.1f); - pc.TP(newPosition); - continue; - } - } - - if (Count++ < 3) return; - - Count = 0; - - float angle; - long now = Utils.TimeStamp; - - if (LastAngleChange + 7 <= now) - { - angle = RandomAngle(); - LastAngleChange = now; - Angle = angle; - } - else angle = Angle; - - Vector2 newPos = Position + (new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * MovingSpeed.GetFloat()); - - if ((!GoesThroughWalls.GetBool() && PhysicsHelpers.AnythingBetween(NetObject.playerControl.Collider, Position, newPos, Constants.ShipOnlyMask, false)) || - newPos.x < MapBounds.X.Left || newPos.x > MapBounds.X.Right || newPos.y < MapBounds.Y.Bottom || newPos.y > MapBounds.Y.Top) - { - Angle = RandomAngle(); - LastAngleChange = now; - Count = 2; - return; - } - - Position = newPos; - NetObject.TP(Position); - } - - private static float RandomAngle() - { - return Random.Range(0, 2 * Mathf.PI); - } - } - - public sealed class Thunderstorm : Disaster - { - private static OptionItem HitFrequency; - private static OptionItem DurationOpt; - - private long LastHit; - - public Thunderstorm(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - - naturalDisaster.Despawn(); - - LateTask.New(() => Utils.NotifyRoles(), 0.2f, "Thunderstorm Notify"); - } - - public override int Duration { get; set; } = DurationOpt.GetInt(); - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void SetupOwnCustomOption() - { - const int id = 69_216_400; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); - - DurationOpt = new IntegerOptionItem(id, "ND_Thunderstorm.DurationOpt", new(1, 120, 1), 20, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - - HitFrequency = new IntegerOptionItem(id + 1, "ND_Thunderstorm.HitFrequency", new(1, 20, 1), 3, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Update() - { - if (RemoveIfExpired()) return; - - long now = Utils.TimeStamp; - - if (now - LastHit >= HitFrequency.GetInt()) - { - LastHit = now; - var hit = new Vector2(Random.Range(MapBounds.X.Left, MapBounds.X.Right), Random.Range(MapBounds.Y.Bottom, MapBounds.Y.Top)); - var cno = new Lightning(hit); - - if (cno.playerControl.GetPlainShipRoom() != default(PlainShipRoom)) - cno.Despawn(); - else - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (Vector2.Distance(pc.Pos(), hit) <= Range / 2f) - pc.Suicide(PlayerState.DeathReason.Lightning); - } - } - } - - public override bool RemoveIfExpired() - { - if (base.RemoveIfExpired()) - { - Utils.NotifyRoles(); - return true; - } - - return false; - } - } - - public sealed class SandStorm : Disaster - { - private static OptionItem DurationOpt; - private static OptionItem Vision; - - private readonly HashSet AffectedPlayers = []; - - public SandStorm(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - } - - public override int Duration { get; set; } = DurationOpt.GetInt(); - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void SetupOwnCustomOption() - { - const int id = 69_216_500; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); - - DurationOpt = new IntegerOptionItem(id, "ND_SandStorm.DurationOpt", new(1, 120, 1), 30, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Seconds); - - Vision = new FloatOptionItem(id + 1, "ND_SandStorm.Vision", new(0f, 1f, 0.05f), 0.2f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Update() - { - if (RemoveIfExpired()) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - bool inRange = Vector2.Distance(pc.Pos(), Position) <= Range; - if ((inRange && AffectedPlayers.Add(pc.PlayerId)) || (!inRange && AffectedPlayers.Remove(pc.PlayerId))) pc.MarkDirtySettings(); - } - } - - public override bool RemoveIfExpired() - { - if (base.RemoveIfExpired()) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (AffectedPlayers.Remove(pc.PlayerId)) - pc.MarkDirtySettings(); - - return true; - } - - return false; - } - - public override void ApplyOwnGameOptions(IGameOptions opt, byte id) - { - if (AffectedPlayers.Contains(id)) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); + if (Vector2.Distance(pc.Pos(), hit) <= Range / 2f) + pc.Suicide(PlayerState.DeathReason.Lightning); } } } - public sealed class Tsunami : Disaster + public override bool RemoveIfExpired() { - private static OptionItem MovingSpeed; - - private static readonly Dictionary Sprites = new() + if (base.RemoveIfExpired()) { - [MovingDirection.LeftToRight] = "<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
", - [MovingDirection.RightToLeft] = "<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
", - [MovingDirection.TopToBottom] = "<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588
<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588
<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
", - [MovingDirection.BottomToTop] = "<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588
<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588
<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588
" - }; - - private readonly MovingDirection Direction; - - private int Count = 1; - - public Tsunami(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - - Direction = Enum.GetValues().RandomElement(); - naturalDisaster.RpcChangeSprite(Sprites[Direction]); - } - - public override int Duration { get; set; } = int.MaxValue; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void SetupOwnCustomOption() - { - const int id = 69_216_600; - Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); - - MovingSpeed = new FloatOptionItem(id, "ND_Tsunami.MovingSpeed", new(0.05f, 0.5f, 0.05f), 0.1f, TabGroup.GameSettings) - .SetHeader(true) - .SetGameMode(CustomGameMode.NaturalDisasters) - .SetColor(color) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Update() - { - if (RemoveIfExpired()) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - Vector2 pos = pc.Pos(); - - bool inWay = Direction switch - { - MovingDirection.LeftToRight => pos.x >= Position.x, - MovingDirection.RightToLeft => pos.x <= Position.x, - MovingDirection.TopToBottom => pos.y <= Position.y, - MovingDirection.BottomToTop => pos.y >= Position.y, - _ => false - }; - - if (Vector2.Distance(pos, Position) <= Range && inWay) pc.Suicide(PlayerState.DeathReason.Drowned); - } - - if (Count++ < 2) return; - - Count = 0; - - float speed = MovingSpeed.GetFloat(); - Vector2 newPos = Position; - - switch (Direction) - { - case MovingDirection.LeftToRight: - newPos.x += speed; - break; - case MovingDirection.RightToLeft: - newPos.x -= speed; - break; - case MovingDirection.TopToBottom: - newPos.y -= speed; - break; - case MovingDirection.BottomToTop: - newPos.y += speed; - break; - } - - if (newPos.x < MapBounds.X.Left || newPos.x > MapBounds.X.Right || newPos.y < MapBounds.Y.Bottom || newPos.y > MapBounds.Y.Top) - { - Duration = 0; - RemoveIfExpired(); - return; - } - - Position = newPos; - NetObject.TP(Position); - } - - private enum MovingDirection - { - LeftToRight, - RightToLeft, - TopToBottom, - BottomToTop - } - } - - public sealed class Sinkhole : Disaster - { - public static readonly List<(Vector2 Position, NaturalDisaster NetObject)> Sinkholes = []; - - public Sinkhole(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - - KillNearbyPlayers(PlayerState.DeathReason.Sunken); - - Sinkholes.Add((position, naturalDisaster)); - } - - public override int Duration { get; set; } = int.MaxValue; - - public override void Update() - { - ActiveDisasters.Remove(this); - } - - public static void OnFixedUpdate() - { - if (Sinkholes.Count == 0) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - Vector2 pos = pc.Pos(); - - // ReSharper disable once ForCanBeConvertedToForeach - for (var i = 0; i < Sinkholes.Count; i++) - if (Vector2.Distance(pos, Sinkholes[i].Position) <= Range) - pc.Suicide(PlayerState.DeathReason.Sunken); - } - } - - public static void RemoveRandomSinkhole() - { - (Vector2 Position, NaturalDisaster NetObject) remove = Sinkholes.RandomElement(); - remove.NetObject.Despawn(); - Sinkholes.Remove(remove); - } - } - - public sealed class BuildingCollapse : Disaster - { - private static int Count = 1; - public static readonly List CollapsedRooms = []; - public static readonly Dictionary LastPosition = []; - - public BuildingCollapse(Vector2 position, NaturalDisaster naturalDisaster) : base(position) - { - NetObject = naturalDisaster; - Update(); - - PlainShipRoom room = ShipStatus.Instance.AllRooms.FirstOrDefault(x => x.RoomId == naturalDisaster.Room); - if (room == default(PlainShipRoom)) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (pc.GetPlainShipRoom() == room) - pc.Suicide(PlayerState.DeathReason.Collapsed); - - CollapsedRooms.Add(room); Utils.NotifyRoles(); + return true; } - public static string CollapsedBuildingsString => CollapsedRooms.Count > 0 - ? CollapsedRooms.Select(x => Translator.GetString($"{x.RoomId}")).Distinct().Join() - : Translator.GetString("None"); + return false; + } + } - public override void Update() + public sealed class SandStorm : Disaster + { + private static OptionItem DurationOpt; + private static OptionItem Vision; + + private readonly HashSet AffectedPlayers = []; + + public SandStorm(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + } + + public override int Duration { get; set; } = DurationOpt.GetInt(); + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void SetupOwnCustomOption() + { + const int id = 69_216_500; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + + DurationOpt = new IntegerOptionItem(id, "ND_SandStorm.DurationOpt", new(1, 120, 1), 30, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Seconds); + + Vision = new FloatOptionItem(id + 1, "ND_SandStorm.Vision", new(0f, 1f, 0.05f), 0.2f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Update() + { + if (RemoveIfExpired()) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + bool inRange = Vector2.Distance(pc.Pos(), Position) <= Range; + if ((inRange && AffectedPlayers.Add(pc.PlayerId)) || (!inRange && AffectedPlayers.Remove(pc.PlayerId))) pc.MarkDirtySettings(); + } + } + + public override bool RemoveIfExpired() + { + if (base.RemoveIfExpired()) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (AffectedPlayers.Remove(pc.PlayerId)) + pc.MarkDirtySettings(); + + return true; + } + + return false; + } + + public override void ApplyOwnGameOptions(IGameOptions opt, byte id) + { + if (AffectedPlayers.Contains(id)) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); + } + } + } + + public sealed class Tsunami : Disaster + { + private static OptionItem MovingSpeed; + + private static readonly Dictionary Sprites = new() + { + [MovingDirection.LeftToRight] = "<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
<#0073ff>\u2588<#0095ff>\u2588<#00ccff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588
", + [MovingDirection.RightToLeft] = "<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#9ce8ff>\u2588<#00ccff>\u2588<#0095ff>\u2588<#0073ff>\u2588
", + [MovingDirection.TopToBottom] = "<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588<#003cff>\u2588
<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588<#006aff>\u2588
<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588<#00bbff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588<#b8e1ff>\u2588
", + [MovingDirection.BottomToTop] = "<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588<#c2e2ff>\u2588
<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588<#00bfff>\u2588
<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588<#007bff>\u2588
<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588<#0033ff>\u2588
" + }; + + private readonly MovingDirection Direction; + + private int Count = 1; + + public Tsunami(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + + Direction = Enum.GetValues().RandomElement(); + naturalDisaster.RpcChangeSprite(Sprites[Direction]); + } + + public override int Duration { get; set; } = int.MaxValue; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void SetupOwnCustomOption() + { + const int id = 69_216_600; + Color color = Utils.GetRoleColor(CustomRoles.NDPlayer); + + MovingSpeed = new FloatOptionItem(id, "ND_Tsunami.MovingSpeed", new(0.05f, 0.5f, 0.05f), 0.1f, TabGroup.GameSettings) + .SetHeader(true) + .SetGameMode(CustomGameMode.NaturalDisasters) + .SetColor(color) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Update() + { + if (RemoveIfExpired()) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + Vector2 pos = pc.Pos(); + + bool inWay = Direction switch + { + MovingDirection.LeftToRight => pos.x >= Position.x, + MovingDirection.RightToLeft => pos.x <= Position.x, + MovingDirection.TopToBottom => pos.y <= Position.y, + MovingDirection.BottomToTop => pos.y >= Position.y, + _ => false + }; + + if (Vector2.Distance(pos, Position) <= Range && inWay) pc.Suicide(PlayerState.DeathReason.Drowned); + } + + if (Count++ < 2) return; + + Count = 0; + + float speed = MovingSpeed.GetFloat(); + Vector2 newPos = Position; + + switch (Direction) + { + case MovingDirection.LeftToRight: + newPos.x += speed; + break; + case MovingDirection.RightToLeft: + newPos.x -= speed; + break; + case MovingDirection.TopToBottom: + newPos.y -= speed; + break; + case MovingDirection.BottomToTop: + newPos.y += speed; + break; + } + + if (newPos.x < MapBounds.X.Left || newPos.x > MapBounds.X.Right || newPos.y < MapBounds.Y.Bottom || newPos.y > MapBounds.Y.Top) { Duration = 0; RemoveIfExpired(); + return; } - public static void OnFixedUpdate() + Position = newPos; + NetObject.TP(Position); + } + + private enum MovingDirection + { + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop + } + } + + public sealed class Sinkhole : Disaster + { + public static readonly List<(Vector2 Position, NaturalDisaster NetObject)> Sinkholes = []; + + public Sinkhole(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + + KillNearbyPlayers(PlayerState.DeathReason.Sunken); + + Sinkholes.Add((position, naturalDisaster)); + } + + public override int Duration { get; set; } = int.MaxValue; + + public override void Update() + { + ActiveDisasters.Remove(this); + } + + public static void OnFixedUpdate() + { + if (Sinkholes.Count == 0) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (CollapsedRooms.Count == 0) return; + Vector2 pos = pc.Pos(); - if (Count++ < 10) return; - Count = 0; + // ReSharper disable once ForCanBeConvertedToForeach + for (var i = 0; i < Sinkholes.Count; i++) + if (Vector2.Distance(pos, Sinkholes[i].Position) <= Range) + pc.Suicide(PlayerState.DeathReason.Sunken); + } + } - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + public static void RemoveRandomSinkhole() + { + (Vector2 Position, NaturalDisaster NetObject) remove = Sinkholes.RandomElement(); + remove.NetObject.Despawn(); + Sinkholes.Remove(remove); + } + } + + public sealed class BuildingCollapse : Disaster + { + private static int Count = 1; + public static readonly List CollapsedRooms = []; + public static readonly Dictionary LastPosition = []; + + public BuildingCollapse(Vector2 position, NaturalDisaster naturalDisaster) : base(position) + { + NetObject = naturalDisaster; + Update(); + + PlainShipRoom room = ShipStatus.Instance.AllRooms.FirstOrDefault(x => x.RoomId == naturalDisaster.Room); + if (room == default(PlainShipRoom)) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc.GetPlainShipRoom() == room) + pc.Suicide(PlayerState.DeathReason.Collapsed); + + CollapsedRooms.Add(room); + Utils.NotifyRoles(); + } + + public static string CollapsedBuildingsString => CollapsedRooms.Count > 0 + ? CollapsedRooms.Select(x => Translator.GetString($"{x.RoomId}")).Distinct().Join() + : Translator.GetString("None"); + + public override void Update() + { + Duration = 0; + RemoveIfExpired(); + } + + public static void OnFixedUpdate() + { + if (CollapsedRooms.Count == 0) return; + + if (Count++ < 10) return; + Count = 0; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + PlainShipRoom room = pc.GetPlainShipRoom(); + + if (room != default(PlainShipRoom) && CollapsedRooms.Exists(x => x == room)) { - PlainShipRoom room = pc.GetPlainShipRoom(); - - if (room != default(PlainShipRoom) && CollapsedRooms.Exists(x => x == room)) - { - if (LastPosition.TryGetValue(pc.PlayerId, out Vector2 lastPos)) pc.TP(lastPos); - else pc.Suicide(PlayerState.DeathReason.Collapsed); - } - else LastPosition[pc.PlayerId] = pc.Pos(); + if (LastPosition.TryGetValue(pc.PlayerId, out Vector2 lastPos)) pc.TP(lastPos); + else pc.Suicide(PlayerState.DeathReason.Collapsed); } + else LastPosition[pc.PlayerId] = pc.Pos(); } } } diff --git a/GameMode/RoomRush.cs b/GameMode/RoomRush.cs index d4699ed82..85a82243d 100644 --- a/GameMode/RoomRush.cs +++ b/GameMode/RoomRush.cs @@ -9,443 +9,442 @@ using HarmonyLib; using Hazel; using UnityEngine; -namespace EHR +namespace EHR; + +public static class RoomRush { - public static class RoomRush + private static OptionItem GlobalTimeMultiplier; + private static OptionItem TimeWhenFirstPlayerEntersRoom; + private static OptionItem VentTimes; + private static OptionItem DisplayRoomName; + private static OptionItem DisplayArrowToRoom; + + public static readonly HashSet HasPlayedFriendCodes = []; + + public static Dictionary VentLimit = []; + + private static HashSet AllRooms = []; + private static SystemTypes RoomGoal; + private static int TimeLeft; + private static HashSet DonePlayers = []; + + public static bool GameGoing; + private static DateTime GameStartDateTime; + + private static RandomSpawn.SpawnMap Map; + + private static readonly Dictionary> Multipliers = new() { - private static OptionItem GlobalTimeMultiplier; - private static OptionItem TimeWhenFirstPlayerEntersRoom; - private static OptionItem VentTimes; - private static OptionItem DisplayRoomName; - private static OptionItem DisplayArrowToRoom; - - public static readonly HashSet HasPlayedFriendCodes = []; - - public static Dictionary VentLimit = []; - - private static HashSet AllRooms = []; - private static SystemTypes RoomGoal; - private static int TimeLeft; - private static HashSet DonePlayers = []; - - public static bool GameGoing; - private static DateTime GameStartDateTime; - - private static RandomSpawn.SpawnMap Map; - - private static readonly Dictionary> Multipliers = new() + [MapNames.Skeld] = new() { - [MapNames.Skeld] = new() - { - [(SystemTypes.Admin, SystemTypes.Nav)] = 2, - [(SystemTypes.Admin, SystemTypes.Comms)] = 2, - [(SystemTypes.Admin, SystemTypes.Shields)] = 3, - [(SystemTypes.Admin, SystemTypes.LifeSupp)] = 4, - [(SystemTypes.Electrical, SystemTypes.MedBay)] = 3, - [(SystemTypes.Electrical, SystemTypes.Security)] = 3, - [(SystemTypes.MedBay, SystemTypes.Security)] = 3, - [(SystemTypes.Admin, SystemTypes.Security)] = 2, - [(SystemTypes.Storage, SystemTypes.Security)] = 2, - [(SystemTypes.Storage, SystemTypes.MedBay)] = 2 - }, - [MapNames.Mira] = new() - { - [(SystemTypes.Launchpad, SystemTypes.Reactor)] = 2, - [(SystemTypes.Greenhouse, SystemTypes.Laboratory)] = 2, - [(SystemTypes.Office, SystemTypes.Laboratory)] = 2, - [(SystemTypes.Storage, SystemTypes.Comms)] = 5, - [(SystemTypes.Cafeteria, SystemTypes.Comms)] = 3, - [(SystemTypes.Balcony, SystemTypes.Comms)] = 2, - [(SystemTypes.Storage, SystemTypes.MedBay)] = 4, - [(SystemTypes.Cafeteria, SystemTypes.MedBay)] = 3, - [(SystemTypes.Balcony, SystemTypes.MedBay)] = 5, - [(SystemTypes.Storage, SystemTypes.LockerRoom)] = 2, - [(SystemTypes.Balcony, SystemTypes.LockerRoom)] = 2, - [(SystemTypes.Launchpad, SystemTypes.Storage)] = 2 - }, - [MapNames.Polus] = new() - { - [(SystemTypes.Laboratory, SystemTypes.Admin)] = 2, - [(SystemTypes.Storage, SystemTypes.Comms)] = 2, - [(SystemTypes.Storage, SystemTypes.Office)] = 2, - [(SystemTypes.Storage, SystemTypes.Admin)] = 2, - [(SystemTypes.Security, SystemTypes.LifeSupp)] = 2, - [(SystemTypes.Security, SystemTypes.Comms)] = 2, - [(SystemTypes.Security, SystemTypes.Electrical)] = 2, - [(SystemTypes.Comms, SystemTypes.Electrical)] = 2 - }, - [MapNames.Airship] = new() - { - [(SystemTypes.MainHall, SystemTypes.GapRoom)] = 2, - [(SystemTypes.MainHall, SystemTypes.Kitchen)] = 2, - [(SystemTypes.Showers, SystemTypes.CargoBay)] = 2, - [(SystemTypes.Showers, SystemTypes.Lounge)] = 2, - [(SystemTypes.Showers, SystemTypes.Electrical)] = 2, - [(SystemTypes.Showers, SystemTypes.Medical)] = 2, - [(SystemTypes.Ventilation, SystemTypes.CargoBay)] = 2, - [(SystemTypes.Ventilation, SystemTypes.Lounge)] = 2, - [(SystemTypes.Ventilation, SystemTypes.Electrical)] = 2, - [(SystemTypes.Ventilation, SystemTypes.Medical)] = 2, - [(SystemTypes.Comms, SystemTypes.VaultRoom)] = 2, - [(SystemTypes.GapRoom, SystemTypes.Records)] = 3, - [(SystemTypes.GapRoom, SystemTypes.Lounge)] = 3, - [(SystemTypes.GapRoom, SystemTypes.Brig)] = 4, - [(SystemTypes.GapRoom, SystemTypes.VaultRoom)] = 3, - [(SystemTypes.GapRoom, SystemTypes.Engine)] = 2, - [(SystemTypes.MeetingRoom, SystemTypes.Records)] = 3, - [(SystemTypes.MeetingRoom, SystemTypes.Lounge)] = 3, - [(SystemTypes.MeetingRoom, SystemTypes.MainHall)] = 2, - [(SystemTypes.Engine, SystemTypes.Security)] = 2, - [(SystemTypes.MainHall, SystemTypes.Security)] = 2 - }, - [MapNames.Fungle] = new() - { - [(SystemTypes.Lookout, SystemTypes.SleepingQuarters)] = 3, - [(SystemTypes.Lookout, SystemTypes.MeetingRoom)] = 2, - [(SystemTypes.Lookout, SystemTypes.Storage)] = 2, - [(SystemTypes.Lookout, SystemTypes.Dropship)] = 2, - [(SystemTypes.Lookout, SystemTypes.FishingDock)] = 2, - [(SystemTypes.Lookout, SystemTypes.RecRoom)] = 2, - [(SystemTypes.Lookout, SystemTypes.Kitchen)] = 2, - [(SystemTypes.Lookout, SystemTypes.Cafeteria)] = 2, - [(SystemTypes.MiningPit, SystemTypes.SleepingQuarters)] = 3, - [(SystemTypes.MiningPit, SystemTypes.MeetingRoom)] = 2, - [(SystemTypes.MiningPit, SystemTypes.Storage)] = 2, - [(SystemTypes.MiningPit, SystemTypes.Dropship)] = 2, - [(SystemTypes.MiningPit, SystemTypes.FishingDock)] = 2, - [(SystemTypes.MiningPit, SystemTypes.RecRoom)] = 2, - [(SystemTypes.MiningPit, SystemTypes.Kitchen)] = 2, - [(SystemTypes.MiningPit, SystemTypes.Cafeteria)] = 2 - } + [(SystemTypes.Admin, SystemTypes.Nav)] = 2, + [(SystemTypes.Admin, SystemTypes.Comms)] = 2, + [(SystemTypes.Admin, SystemTypes.Shields)] = 3, + [(SystemTypes.Admin, SystemTypes.LifeSupp)] = 4, + [(SystemTypes.Electrical, SystemTypes.MedBay)] = 3, + [(SystemTypes.Electrical, SystemTypes.Security)] = 3, + [(SystemTypes.MedBay, SystemTypes.Security)] = 3, + [(SystemTypes.Admin, SystemTypes.Security)] = 2, + [(SystemTypes.Storage, SystemTypes.Security)] = 2, + [(SystemTypes.Storage, SystemTypes.MedBay)] = 2 + }, + [MapNames.Mira] = new() + { + [(SystemTypes.Launchpad, SystemTypes.Reactor)] = 2, + [(SystemTypes.Greenhouse, SystemTypes.Laboratory)] = 2, + [(SystemTypes.Office, SystemTypes.Laboratory)] = 2, + [(SystemTypes.Storage, SystemTypes.Comms)] = 5, + [(SystemTypes.Cafeteria, SystemTypes.Comms)] = 3, + [(SystemTypes.Balcony, SystemTypes.Comms)] = 2, + [(SystemTypes.Storage, SystemTypes.MedBay)] = 4, + [(SystemTypes.Cafeteria, SystemTypes.MedBay)] = 3, + [(SystemTypes.Balcony, SystemTypes.MedBay)] = 5, + [(SystemTypes.Storage, SystemTypes.LockerRoom)] = 2, + [(SystemTypes.Balcony, SystemTypes.LockerRoom)] = 2, + [(SystemTypes.Launchpad, SystemTypes.Storage)] = 2 + }, + [MapNames.Polus] = new() + { + [(SystemTypes.Laboratory, SystemTypes.Admin)] = 2, + [(SystemTypes.Storage, SystemTypes.Comms)] = 2, + [(SystemTypes.Storage, SystemTypes.Office)] = 2, + [(SystemTypes.Storage, SystemTypes.Admin)] = 2, + [(SystemTypes.Security, SystemTypes.LifeSupp)] = 2, + [(SystemTypes.Security, SystemTypes.Comms)] = 2, + [(SystemTypes.Security, SystemTypes.Electrical)] = 2, + [(SystemTypes.Comms, SystemTypes.Electrical)] = 2 + }, + [MapNames.Airship] = new() + { + [(SystemTypes.MainHall, SystemTypes.GapRoom)] = 2, + [(SystemTypes.MainHall, SystemTypes.Kitchen)] = 2, + [(SystemTypes.Showers, SystemTypes.CargoBay)] = 2, + [(SystemTypes.Showers, SystemTypes.Lounge)] = 2, + [(SystemTypes.Showers, SystemTypes.Electrical)] = 2, + [(SystemTypes.Showers, SystemTypes.Medical)] = 2, + [(SystemTypes.Ventilation, SystemTypes.CargoBay)] = 2, + [(SystemTypes.Ventilation, SystemTypes.Lounge)] = 2, + [(SystemTypes.Ventilation, SystemTypes.Electrical)] = 2, + [(SystemTypes.Ventilation, SystemTypes.Medical)] = 2, + [(SystemTypes.Comms, SystemTypes.VaultRoom)] = 2, + [(SystemTypes.GapRoom, SystemTypes.Records)] = 3, + [(SystemTypes.GapRoom, SystemTypes.Lounge)] = 3, + [(SystemTypes.GapRoom, SystemTypes.Brig)] = 4, + [(SystemTypes.GapRoom, SystemTypes.VaultRoom)] = 3, + [(SystemTypes.GapRoom, SystemTypes.Engine)] = 2, + [(SystemTypes.MeetingRoom, SystemTypes.Records)] = 3, + [(SystemTypes.MeetingRoom, SystemTypes.Lounge)] = 3, + [(SystemTypes.MeetingRoom, SystemTypes.MainHall)] = 2, + [(SystemTypes.Engine, SystemTypes.Security)] = 2, + [(SystemTypes.MainHall, SystemTypes.Security)] = 2 + }, + [MapNames.Fungle] = new() + { + [(SystemTypes.Lookout, SystemTypes.SleepingQuarters)] = 3, + [(SystemTypes.Lookout, SystemTypes.MeetingRoom)] = 2, + [(SystemTypes.Lookout, SystemTypes.Storage)] = 2, + [(SystemTypes.Lookout, SystemTypes.Dropship)] = 2, + [(SystemTypes.Lookout, SystemTypes.FishingDock)] = 2, + [(SystemTypes.Lookout, SystemTypes.RecRoom)] = 2, + [(SystemTypes.Lookout, SystemTypes.Kitchen)] = 2, + [(SystemTypes.Lookout, SystemTypes.Cafeteria)] = 2, + [(SystemTypes.MiningPit, SystemTypes.SleepingQuarters)] = 3, + [(SystemTypes.MiningPit, SystemTypes.MeetingRoom)] = 2, + [(SystemTypes.MiningPit, SystemTypes.Storage)] = 2, + [(SystemTypes.MiningPit, SystemTypes.Dropship)] = 2, + [(SystemTypes.MiningPit, SystemTypes.FishingDock)] = 2, + [(SystemTypes.MiningPit, SystemTypes.RecRoom)] = 2, + [(SystemTypes.MiningPit, SystemTypes.Kitchen)] = 2, + [(SystemTypes.MiningPit, SystemTypes.Cafeteria)] = 2 + } + }; + + public static void SetupCustomOption() + { + var id = 69_217_001; + Color color = Utils.GetRoleColor(CustomRoles.RRPlayer); + const CustomGameMode gameMode = CustomGameMode.RoomRush; + + GlobalTimeMultiplier = new FloatOptionItem(id++, "RR_GlobalTimeMultiplier", new(0.05f, 2f, 0.05f), 1f, TabGroup.GameSettings) + .SetHeader(true) + .SetColor(color) + .SetGameMode(gameMode); + + TimeWhenFirstPlayerEntersRoom = new IntegerOptionItem(id++, "RR_TimeWhenTwoPlayersEntersRoom", new(1, 30, 1), 5, TabGroup.GameSettings) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Seconds); + + VentTimes = new IntegerOptionItem(id++, "RR_VentTimes", new(0, 90, 1), 1, TabGroup.GameSettings) + .SetColor(color) + .SetGameMode(gameMode) + .SetValueFormat(OptionFormat.Times); + + DisplayRoomName = new BooleanOptionItem(id++, "RR_DisplayRoomName", true, TabGroup.GameSettings) + .SetColor(color) + .SetGameMode(gameMode); + + DisplayArrowToRoom = new BooleanOptionItem(id, "RR_DisplayArrowToRoom", false, TabGroup.GameSettings) + .SetColor(color) + .SetGameMode(gameMode); + } + + public static int GetSurvivalTime(byte id) + { + if (!Main.PlayerStates.TryGetValue(id, out PlayerState state)) return -1; + + if (!state.IsDead) return 0; + + DateTime died = state.RealKiller.TimeStamp; + TimeSpan time = died - GameStartDateTime; + return (int)time.TotalSeconds; + } + + public static void OnGameStart() + { + if (!CustomGameMode.RoomRush.IsActiveOrIntegrated()) return; + + Main.Instance.StartCoroutine(GameStartTasks()); + } + + private static IEnumerator GameStartTasks() + { + GameGoing = false; + + int ventLimit = VentTimes.GetInt(); + VentLimit = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, _ => ventLimit); + + AllRooms = ShipStatus.Instance.AllRooms.Select(x => x.RoomId).ToHashSet(); + AllRooms.Remove(SystemTypes.Hallway); + AllRooms.Remove(SystemTypes.Outside); + AllRooms.RemoveWhere(x => x.ToString().Contains("Decontamination")); + + DonePlayers = []; + + Map = Main.CurrentMap switch + { + MapNames.Skeld => new RandomSpawn.SkeldSpawnMap(), + MapNames.Mira => new RandomSpawn.MiraHQSpawnMap(), + MapNames.Polus => new RandomSpawn.PolusSpawnMap(), + MapNames.Dleks => new RandomSpawn.DleksSpawnMap(), + MapNames.Airship => new RandomSpawn.AirshipSpawnMap(), + MapNames.Fungle => new RandomSpawn.FungleSpawnMap(), + _ => throw new ArgumentOutOfRangeException() }; - public static void SetupCustomOption() + yield return new WaitForSeconds(Main.CurrentMap == MapNames.Airship ? 22f : 14f); + + PlayerControl[] aapc = Main.AllAlivePlayerControls; + aapc.Do(x => x.RpcSetCustomRole(CustomRoles.RRPlayer)); + + if (Options.CurrentGameMode == CustomGameMode.AllInOne) { - var id = 69_217_001; - Color color = Utils.GetRoleColor(CustomRoles.RRPlayer); - const CustomGameMode gameMode = CustomGameMode.RoomRush; - - GlobalTimeMultiplier = new FloatOptionItem(id++, "RR_GlobalTimeMultiplier", new(0.05f, 2f, 0.05f), 1f, TabGroup.GameSettings) - .SetHeader(true) - .SetColor(color) - .SetGameMode(gameMode); - - TimeWhenFirstPlayerEntersRoom = new IntegerOptionItem(id++, "RR_TimeWhenTwoPlayersEntersRoom", new(1, 30, 1), 5, TabGroup.GameSettings) - .SetColor(color) - .SetGameMode(gameMode) - .SetValueFormat(OptionFormat.Seconds); - - VentTimes = new IntegerOptionItem(id++, "RR_VentTimes", new(0, 90, 1), 1, TabGroup.GameSettings) - .SetColor(color) - .SetGameMode(gameMode) - .SetValueFormat(OptionFormat.Times); - - DisplayRoomName = new BooleanOptionItem(id++, "RR_DisplayRoomName", true, TabGroup.GameSettings) - .SetColor(color) - .SetGameMode(gameMode); - - DisplayArrowToRoom = new BooleanOptionItem(id, "RR_DisplayArrowToRoom", false, TabGroup.GameSettings) - .SetColor(color) - .SetGameMode(gameMode); + yield return new WaitForSeconds(4f); + goto Skip; } - public static int GetSurvivalTime(byte id) + bool showTutorial = aapc.ExceptBy(HasPlayedFriendCodes, x => x.FriendCode).Count() >= aapc.Length / 3; + + if (showTutorial) { - if (!Main.PlayerStates.TryGetValue(id, out PlayerState state)) return -1; + string[] notifies = [Translator.GetString("RR_Tutorial_1"), Translator.GetString("RR_Tutorial_2"), Translator.GetString("RR_Tutorial_3"), Translator.GetString("RR_Tutorial_4")]; - if (!state.IsDead) return 0; - - DateTime died = state.RealKiller.TimeStamp; - TimeSpan time = died - GameStartDateTime; - return (int)time.TotalSeconds; - } - - public static void OnGameStart() - { - if (!CustomGameMode.RoomRush.IsActiveOrIntegrated()) return; - - Main.Instance.StartCoroutine(GameStartTasks()); - } - - private static IEnumerator GameStartTasks() - { - GameGoing = false; - - int ventLimit = VentTimes.GetInt(); - VentLimit = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, _ => ventLimit); - - AllRooms = ShipStatus.Instance.AllRooms.Select(x => x.RoomId).ToHashSet(); - AllRooms.Remove(SystemTypes.Hallway); - AllRooms.Remove(SystemTypes.Outside); - AllRooms.RemoveWhere(x => x.ToString().Contains("Decontamination")); - - DonePlayers = []; - - Map = Main.CurrentMap switch + foreach (string notify in notifies) { - MapNames.Skeld => new RandomSpawn.SkeldSpawnMap(), - MapNames.Mira => new RandomSpawn.MiraHQSpawnMap(), - MapNames.Polus => new RandomSpawn.PolusSpawnMap(), - MapNames.Dleks => new RandomSpawn.DleksSpawnMap(), - MapNames.Airship => new RandomSpawn.AirshipSpawnMap(), - MapNames.Fungle => new RandomSpawn.FungleSpawnMap(), - _ => throw new ArgumentOutOfRangeException() + aapc.Do(x => x.Notify(notify, 8f)); + yield return new WaitForSeconds(3f); + } + + yield return new WaitForSeconds(4f); + } + + NameNotifyManager.Reset(); + aapc.Do(x => x.Notify(Translator.GetString("RR_ReadyQM"))); + + yield return new WaitForSeconds(2f); + + for (var i = 3; i > 0; i--) + { + int time = i; + NameNotifyManager.Reset(); + aapc.Do(x => x.Notify(time.ToString())); + yield return new WaitForSeconds(1f); + } + + Skip: + + if (ventLimit > 0) aapc.Do(x => x.RpcChangeRoleBasis(CustomRoles.EngineerEHR)); + + Utils.SendRPC(CustomRPC.RoomRushDataSync, 1); + + NameNotifyManager.Reset(); + StartNewRound(true); + GameGoing = true; + GameStartDateTime = DateTime.Now; + } + + private static void StartNewRound(bool initial = false) + { + if (GameStates.IsEnded) return; + + SystemTypes previous = !initial + ? RoomGoal + : Main.CurrentMap switch + { + MapNames.Skeld => SystemTypes.Cafeteria, + MapNames.Mira => SystemTypes.Launchpad, + MapNames.Dleks => SystemTypes.Cafeteria, + MapNames.Polus => SystemTypes.Dropship, + MapNames.Airship => SystemTypes.MainHall, + MapNames.Fungle => SystemTypes.Dropship, + _ => throw new ArgumentOutOfRangeException(Main.CurrentMap.ToString(), "Invalid map") }; - yield return new WaitForSeconds(Main.CurrentMap == MapNames.Airship ? 22f : 14f); + DonePlayers.Clear(); + RoomGoal = AllRooms.Without(previous).RandomElement(); + Vector2 goalPos = Map.Positions.GetValueOrDefault(RoomGoal, RoomGoal.GetRoomClass().transform.position); + Vector2 previousPos = Map.Positions.GetValueOrDefault(previous, initial ? Main.AllAlivePlayerControls.RandomElement().Pos() : previous.GetRoomClass().transform.position); + float distance = initial ? 50 : Vector2.Distance(goalPos, previousPos); + float speed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + var time = (int)Math.Ceiling(distance / speed); + MapNames map = Main.CurrentMap; + Dictionary<(SystemTypes, SystemTypes), int> multipliers = Multipliers[map == MapNames.Dleks ? MapNames.Skeld : map]; + time *= multipliers.GetValueOrDefault((previous, RoomGoal), multipliers.GetValueOrDefault((RoomGoal, previous), 1)); + bool involvesDecontamination = Main.CurrentMap switch + { + MapNames.Mira => !(previous is SystemTypes.Laboratory or SystemTypes.Reactor && RoomGoal is SystemTypes.Laboratory or SystemTypes.Reactor) && (previous is SystemTypes.Laboratory or SystemTypes.Reactor || RoomGoal is SystemTypes.Laboratory or SystemTypes.Reactor), + MapNames.Polus => previous == SystemTypes.Specimens || RoomGoal == SystemTypes.Specimens, + _ => false + }; + + if (involvesDecontamination) time += 15; + + switch (Main.CurrentMap) + { + case MapNames.Airship when RoomGoal == SystemTypes.Ventilation: + time = (int)(time * 0.4f); + break; + case MapNames.Fungle when RoomGoal == SystemTypes.Laboratory || previous == SystemTypes.Laboratory: + time += (int)(8 / speed); + break; + case MapNames.Polus when (RoomGoal == SystemTypes.Laboratory && previous is not SystemTypes.Storage and not SystemTypes.Specimens) || (previous == SystemTypes.Laboratory && RoomGoal is not SystemTypes.Office and not SystemTypes.Storage and not SystemTypes.Electrical): + time -= (int)(5 * speed); + break; + } + + TimeLeft = Math.Max((int)Math.Round(time * GlobalTimeMultiplier.GetFloat()), 4); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) TimeLeft *= AllInOneGameMode.RoomRushTimeLimitMultiplier.GetInt(); + Logger.Info($"Starting a new round - Goal = from: {Translator.GetString(previous.ToString())}, to: {Translator.GetString(RoomGoal.ToString())} - Time: {TimeLeft} ({Main.CurrentMap})", "RoomRush"); + Main.AllPlayerControls.Do(x => LocateArrow.RemoveAllTarget(x.PlayerId)); + if (DisplayArrowToRoom.GetBool()) Main.AllPlayerControls.Do(x => LocateArrow.Add(x.PlayerId, goalPos)); + + Utils.NotifyRoles(); + + Main.AllPlayerSpeed[PlayerControl.LocalPlayer.PlayerId] = Main.MinSpeed; + PlayerControl.LocalPlayer.SyncSettings(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[PlayerControl.LocalPlayer.PlayerId] = speed; + PlayerControl.LocalPlayer.SyncSettings(); + }, Utils.CalculatePingDelay() * 2f); + } + + private static PlainShipRoom GetRoomClass(this SystemTypes systemTypes) + { + return ShipStatus.Instance.AllRooms.First(x => x.RoomId == systemTypes); + } + + public static string GetSuffix(PlayerControl seer) + { + if (!GameGoing || Main.HasJustStarted || seer == null) return string.Empty; + + StringBuilder sb = new(); + bool dead = !seer.IsAlive(); + bool done = dead || DonePlayers.Contains(seer.PlayerId); + Color color = done ? Color.green : Color.yellow; + + if (DisplayRoomName.GetBool()) sb.Append(Utils.ColorString(color, Translator.GetString(RoomGoal.ToString())) + "\n"); + if (DisplayArrowToRoom.GetBool()) sb.Append(Utils.ColorString(color, LocateArrow.GetArrows(seer)) + "\n"); + + color = done ? Color.white : Color.yellow; + sb.Append(Utils.ColorString(color, TimeLeft.ToString()) + "\n"); + + if (VentTimes.GetInt() == 0 || dead || seer.IsModClient()) return sb.ToString().Trim(); + + sb.Append('\n'); + + int vents = VentLimit.GetValueOrDefault(seer.PlayerId); + sb.Append(string.Format(Translator.GetString("RR_VentsRemaining"), vents)); + + return sb.ToString().Trim(); + } + + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + int ventLimit = VentTimes.GetInt(); + VentLimit = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, _ => ventLimit); + break; + case 2: + int limit = reader.ReadPackedInt32(); + byte id = reader.ReadByte(); + VentLimit[id] = limit; + break; + } + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + private static long LastUpdate = Utils.TimeStamp; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + if (!GameGoing || Main.HasJustStarted || !CustomGameMode.RoomRush.IsActiveOrIntegrated() || !AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !__instance.IsHost()) return; + + long now = Utils.TimeStamp; PlayerControl[] aapc = Main.AllAlivePlayerControls; - aapc.Do(x => x.RpcSetCustomRole(CustomRoles.RRPlayer)); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) + foreach (PlayerControl pc in aapc) { - yield return new WaitForSeconds(4f); - goto Skip; - } + PlainShipRoom room = pc.GetPlainShipRoom(); - bool showTutorial = aapc.ExceptBy(HasPlayedFriendCodes, x => x.FriendCode).Count() >= aapc.Length / 3; + bool notAllInOne = Options.CurrentGameMode != CustomGameMode.AllInOne; - if (showTutorial) - { - string[] notifies = [Translator.GetString("RR_Tutorial_1"), Translator.GetString("RR_Tutorial_2"), Translator.GetString("RR_Tutorial_3"), Translator.GetString("RR_Tutorial_4")]; - - foreach (string notify in notifies) + if (pc.IsAlive() && !pc.inMovingPlat && room != null && room.RoomId == RoomGoal && DonePlayers.Add(pc.PlayerId)) { - aapc.Do(x => x.Notify(notify, 8f)); - yield return new WaitForSeconds(3f); + Logger.Info($"{pc.GetRealName()} entered the correct room", "RoomRush"); + pc.Notify($"{DonePlayers.Count}.", 2f); + + int timeLeft = TimeWhenFirstPlayerEntersRoom.GetInt(); + + if (DonePlayers.Count == 2 && timeLeft < TimeLeft && (notAllInOne || !AllInOneGameMode.RoomRushDontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom.GetBool())) + { + Logger.Info($"Two players entered the correct room, setting the timer to {timeLeft}", "RoomRush"); + TimeLeft = timeLeft; + LastUpdate = now; + + if (aapc.Length == 2 && pc.IsLocalPlayer()) + Achievements.Type.WheresTheBlueShell.CompleteAfterGameEnd(); + } + + if (DonePlayers.Count == aapc.Length - 1 && (notAllInOne || !AllInOneGameMode.RoomRushDontKillLastPlayer.GetBool())) + { + PlayerControl last = aapc.First(x => !DonePlayers.Contains(x.PlayerId)); + Logger.Info($"All players entered the correct room except one, killing the last player ({last.GetRealName()})", "RoomRush"); + last.Suicide(); + last.Notify(Translator.GetString("RR_YouWereLast")); + StartNewRound(); + return; + } } - - yield return new WaitForSeconds(4f); + else if ((room == null || room.RoomId != RoomGoal) && (notAllInOne || !AllInOneGameMode.RoomRushDontKillPlayersOutsideRoomWhenTimeRunsOut.GetBool())) + DonePlayers.Remove(pc.PlayerId); } - NameNotifyManager.Reset(); - aapc.Do(x => x.Notify(Translator.GetString("RR_ReadyQM"))); + if (LastUpdate == now) return; - yield return new WaitForSeconds(2f); - - for (var i = 3; i > 0; i--) - { - int time = i; - NameNotifyManager.Reset(); - aapc.Do(x => x.Notify(time.ToString())); - yield return new WaitForSeconds(1f); - } - - Skip: - - if (ventLimit > 0) aapc.Do(x => x.RpcChangeRoleBasis(CustomRoles.EngineerEHR)); - - Utils.SendRPC(CustomRPC.RoomRushDataSync, 1); - - NameNotifyManager.Reset(); - StartNewRound(true); - GameGoing = true; - GameStartDateTime = DateTime.Now; - } - - private static void StartNewRound(bool initial = false) - { - if (GameStates.IsEnded) return; - - SystemTypes previous = !initial - ? RoomGoal - : Main.CurrentMap switch - { - MapNames.Skeld => SystemTypes.Cafeteria, - MapNames.Mira => SystemTypes.Launchpad, - MapNames.Dleks => SystemTypes.Cafeteria, - MapNames.Polus => SystemTypes.Dropship, - MapNames.Airship => SystemTypes.MainHall, - MapNames.Fungle => SystemTypes.Dropship, - _ => throw new ArgumentOutOfRangeException(Main.CurrentMap.ToString(), "Invalid map") - }; - - DonePlayers.Clear(); - RoomGoal = AllRooms.Without(previous).RandomElement(); - Vector2 goalPos = Map.Positions.GetValueOrDefault(RoomGoal, RoomGoal.GetRoomClass().transform.position); - Vector2 previousPos = Map.Positions.GetValueOrDefault(previous, initial ? Main.AllAlivePlayerControls.RandomElement().Pos() : previous.GetRoomClass().transform.position); - float distance = initial ? 50 : Vector2.Distance(goalPos, previousPos); - float speed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - var time = (int)Math.Ceiling(distance / speed); - MapNames map = Main.CurrentMap; - Dictionary<(SystemTypes, SystemTypes), int> multipliers = Multipliers[map == MapNames.Dleks ? MapNames.Skeld : map]; - time *= multipliers.GetValueOrDefault((previous, RoomGoal), multipliers.GetValueOrDefault((RoomGoal, previous), 1)); - - bool involvesDecontamination = Main.CurrentMap switch - { - MapNames.Mira => !(previous is SystemTypes.Laboratory or SystemTypes.Reactor && RoomGoal is SystemTypes.Laboratory or SystemTypes.Reactor) && (previous is SystemTypes.Laboratory or SystemTypes.Reactor || RoomGoal is SystemTypes.Laboratory or SystemTypes.Reactor), - MapNames.Polus => previous == SystemTypes.Specimens || RoomGoal == SystemTypes.Specimens, - _ => false - }; - - if (involvesDecontamination) time += 15; - - switch (Main.CurrentMap) - { - case MapNames.Airship when RoomGoal == SystemTypes.Ventilation: - time = (int)(time * 0.4f); - break; - case MapNames.Fungle when RoomGoal == SystemTypes.Laboratory || previous == SystemTypes.Laboratory: - time += (int)(8 / speed); - break; - case MapNames.Polus when (RoomGoal == SystemTypes.Laboratory && previous is not SystemTypes.Storage and not SystemTypes.Specimens) || (previous == SystemTypes.Laboratory && RoomGoal is not SystemTypes.Office and not SystemTypes.Storage and not SystemTypes.Electrical): - time -= (int)(5 * speed); - break; - } - - TimeLeft = Math.Max((int)Math.Round(time * GlobalTimeMultiplier.GetFloat()), 4); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) TimeLeft *= AllInOneGameMode.RoomRushTimeLimitMultiplier.GetInt(); - Logger.Info($"Starting a new round - Goal = from: {Translator.GetString(previous.ToString())}, to: {Translator.GetString(RoomGoal.ToString())} - Time: {TimeLeft} ({Main.CurrentMap})", "RoomRush"); - Main.AllPlayerControls.Do(x => LocateArrow.RemoveAllTarget(x.PlayerId)); - if (DisplayArrowToRoom.GetBool()) Main.AllPlayerControls.Do(x => LocateArrow.Add(x.PlayerId, goalPos)); + LastUpdate = now; + TimeLeft--; Utils.NotifyRoles(); - Main.AllPlayerSpeed[PlayerControl.LocalPlayer.PlayerId] = Main.MinSpeed; - PlayerControl.LocalPlayer.SyncSettings(); - - LateTask.New(() => + if (TimeLeft <= 0) { - Main.AllPlayerSpeed[PlayerControl.LocalPlayer.PlayerId] = speed; - PlayerControl.LocalPlayer.SyncSettings(); - }, Utils.CalculatePingDelay() * 2f); - } + Logger.Info("Time is up, killing everyone who didn't enter the correct room", "RoomRush"); + PlayerControl[] lateAapc = Main.AllAlivePlayerControls; + PlayerControl[] playersOutsideRoom = lateAapc.ExceptBy(DonePlayers, x => x.PlayerId).ToArray(); + if (playersOutsideRoom.Length == lateAapc.Length) CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); - private static PlainShipRoom GetRoomClass(this SystemTypes systemTypes) - { - return ShipStatus.Instance.AllRooms.First(x => x.RoomId == systemTypes); - } + playersOutsideRoom.Do(x => x.Suicide()); + StartNewRound(); - public static string GetSuffix(PlayerControl seer) - { - if (!GameGoing || Main.HasJustStarted || seer == null) return string.Empty; - - StringBuilder sb = new(); - bool dead = !seer.IsAlive(); - bool done = dead || DonePlayers.Contains(seer.PlayerId); - Color color = done ? Color.green : Color.yellow; - - if (DisplayRoomName.GetBool()) sb.Append(Utils.ColorString(color, Translator.GetString(RoomGoal.ToString())) + "\n"); - if (DisplayArrowToRoom.GetBool()) sb.Append(Utils.ColorString(color, LocateArrow.GetArrows(seer)) + "\n"); - - color = done ? Color.white : Color.yellow; - sb.Append(Utils.ColorString(color, TimeLeft.ToString()) + "\n"); - - if (VentTimes.GetInt() == 0 || dead || seer.IsModClient()) return sb.ToString().Trim(); - - sb.Append('\n'); - - int vents = VentLimit.GetValueOrDefault(seer.PlayerId); - sb.Append(string.Format(Translator.GetString("RR_VentsRemaining"), vents)); - - return sb.ToString().Trim(); - } - - public static void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - int ventLimit = VentTimes.GetInt(); - VentLimit = Main.AllPlayerControls.ToDictionary(x => x.PlayerId, _ => ventLimit); - break; - case 2: - int limit = reader.ReadPackedInt32(); - byte id = reader.ReadByte(); - VentLimit[id] = limit; - break; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch - { - private static long LastUpdate = Utils.TimeStamp; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) - { - if (!GameGoing || Main.HasJustStarted || !CustomGameMode.RoomRush.IsActiveOrIntegrated() || !AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !__instance.IsHost()) return; - - long now = Utils.TimeStamp; - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - foreach (PlayerControl pc in aapc) - { - PlainShipRoom room = pc.GetPlainShipRoom(); - - bool notAllInOne = Options.CurrentGameMode != CustomGameMode.AllInOne; - - if (pc.IsAlive() && !pc.inMovingPlat && room != null && room.RoomId == RoomGoal && DonePlayers.Add(pc.PlayerId)) - { - Logger.Info($"{pc.GetRealName()} entered the correct room", "RoomRush"); - pc.Notify($"{DonePlayers.Count}.", 2f); - - int timeLeft = TimeWhenFirstPlayerEntersRoom.GetInt(); - - if (DonePlayers.Count == 2 && timeLeft < TimeLeft && (notAllInOne || !AllInOneGameMode.RoomRushDontLowerTimeLimitWhenTwoPlayersEnterCorrectRoom.GetBool())) - { - Logger.Info($"Two players entered the correct room, setting the timer to {timeLeft}", "RoomRush"); - TimeLeft = timeLeft; - LastUpdate = now; - - if (aapc.Length == 2 && pc.IsLocalPlayer()) - Achievements.Type.WheresTheBlueShell.CompleteAfterGameEnd(); - } - - if (DonePlayers.Count == aapc.Length - 1 && (notAllInOne || !AllInOneGameMode.RoomRushDontKillLastPlayer.GetBool())) - { - PlayerControl last = aapc.First(x => !DonePlayers.Contains(x.PlayerId)); - Logger.Info($"All players entered the correct room except one, killing the last player ({last.GetRealName()})", "RoomRush"); - last.Suicide(); - last.Notify(Translator.GetString("RR_YouWereLast")); - StartNewRound(); - return; - } - } - else if ((room == null || room.RoomId != RoomGoal) && (notAllInOne || !AllInOneGameMode.RoomRushDontKillPlayersOutsideRoomWhenTimeRunsOut.GetBool())) - DonePlayers.Remove(pc.PlayerId); - } - - if (LastUpdate == now) return; - - LastUpdate = now; - - TimeLeft--; - Utils.NotifyRoles(); - - if (TimeLeft <= 0) - { - Logger.Info("Time is up, killing everyone who didn't enter the correct room", "RoomRush"); - PlayerControl[] lateAapc = Main.AllAlivePlayerControls; - PlayerControl[] playersOutsideRoom = lateAapc.ExceptBy(DonePlayers, x => x.PlayerId).ToArray(); - if (playersOutsideRoom.Length == lateAapc.Length) CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); - - playersOutsideRoom.Do(x => x.Suicide()); - StartNewRound(); - - if (playersOutsideRoom.Any(x => x.IsLocalPlayer())) - Achievements.Type.OutOfTime.Complete(); - } + if (playersOutsideRoom.Any(x => x.IsLocalPlayer())) + Achievements.Type.OutOfTime.Complete(); } } } +} - public class RRPlayer : RoleBase +public class RRPlayer : RoleBase +{ + public override bool IsEnable => CustomGameMode.RoomRush.IsActiveOrIntegrated(); + + public override void Init() { } + + public override void Add(byte playerId) { } + + public override void SetupCustomOption() { } + + public override void OnExitVent(PlayerControl pc, Vent vent) { - public override bool IsEnable => CustomGameMode.RoomRush.IsActiveOrIntegrated(); - - public override void Init() { } - - public override void Add(byte playerId) { } - - public override void SetupCustomOption() { } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - RoomRush.VentLimit[pc.PlayerId]--; - Utils.SendRPC(CustomRPC.RoomRushDataSync, 2, RoomRush.VentLimit[pc.PlayerId], pc.PlayerId); - if (RoomRush.VentLimit[pc.PlayerId] <= 0) pc.RpcChangeRoleBasis(CustomRoles.RRPlayer); - } + RoomRush.VentLimit[pc.PlayerId]--; + Utils.SendRPC(CustomRPC.RoomRushDataSync, 2, RoomRush.VentLimit[pc.PlayerId], pc.PlayerId); + if (RoomRush.VentLimit[pc.PlayerId] <= 0) pc.RpcChangeRoleBasis(CustomRoles.RRPlayer); } } \ No newline at end of file diff --git a/GameMode/SoloKombatManager.cs b/GameMode/SoloKombatManager.cs index 6a9b7f78c..3d543f416 100644 --- a/GameMode/SoloKombatManager.cs +++ b/GameMode/SoloKombatManager.cs @@ -8,355 +8,354 @@ using HarmonyLib; using UnityEngine; using static EHR.RandomSpawn; -namespace EHR +namespace EHR; + +internal static class SoloKombatManager { - internal static class SoloKombatManager + private static Dictionary PlayerHPMax = []; + private static Dictionary PlayerHP = []; + public static Dictionary PlayerHPReco = []; + public static Dictionary PlayerATK = []; + public static Dictionary PlayerDF = []; + + private static Dictionary OriginalSpeed = []; + public static Dictionary KBScore = []; + public static int RoundTime; + + private static readonly Dictionary NameNotify = []; + + private static Dictionary BackCountdown = []; + private static Dictionary LastHurt = []; + + public static bool SoloAlive(this PlayerControl pc) { - private static Dictionary PlayerHPMax = []; - private static Dictionary PlayerHP = []; - public static Dictionary PlayerHPReco = []; - public static Dictionary PlayerATK = []; - public static Dictionary PlayerDF = []; - - private static Dictionary OriginalSpeed = []; - public static Dictionary KBScore = []; - public static int RoundTime; - - private static readonly Dictionary NameNotify = []; - - private static Dictionary BackCountdown = []; - private static Dictionary LastHurt = []; - - public static bool SoloAlive(this PlayerControl pc) - { - return PlayerHP[pc.PlayerId] > 0f; - } - - public static void SetupCustomOption() - { - KB_GameTime = new IntegerOptionItem(66_233_001, "KB_GameTime", new(30, 300, 5), 180, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds) - .SetHeader(true); - - KB_ATKCooldown = new FloatOptionItem(66_223_008, "KB_ATKCooldown", new(1f, 10f, 0.1f), 1f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - KB_HPMax = new FloatOptionItem(66_233_002, "KB_HPMax", new(10f, 990f, 5f), 100f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Health); - - KB_ATK = new FloatOptionItem(66_233_003, "KB_ATK", new(1f, 100f, 1f), 8f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Health); - - KB_RecoverPerSecond = new FloatOptionItem(66_233_005, "KB_RecoverPerSecond", new(1f, 180f, 1f), 2f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Health); - - KB_RecoverAfterSecond = new IntegerOptionItem(66_233_004, "KB_RecoverAfterSecond", new(0, 60, 1), 8, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - KB_ResurrectionWaitingTime = new IntegerOptionItem(66_233_006, "KB_ResurrectionWaitingTime", new(3, 990, 1), 15, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - KB_KillBonusMultiplier = new FloatOptionItem(66_233_007, "KB_KillBonusMultiplier", new(0.25f, 5f, 0.25f), 1.25f, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)) - .SetValueFormat(OptionFormat.Multiplier); - - KB_BootVentWhenDead = new BooleanOptionItem(66_233_009, "KB_BootVentWhenDead", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.SoloKombat) - .SetColor(new Color32(245, 82, 82, byte.MaxValue)); - } - - public static void Init() - { - if (!CustomGameMode.SoloKombat.IsActiveOrIntegrated()) return; - - PlayerHPMax = []; - PlayerHP = []; - PlayerHPReco = []; - PlayerATK = []; - PlayerDF = []; - - LastHurt = []; - OriginalSpeed = []; - BackCountdown = []; - KBScore = []; - RoundTime = KB_GameTime.GetInt() + 8; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - PlayerHPMax.TryAdd(pc.PlayerId, KB_HPMax.GetFloat()); - PlayerHP.TryAdd(pc.PlayerId, KB_HPMax.GetFloat()); - PlayerHPReco.TryAdd(pc.PlayerId, KB_RecoverPerSecond.GetFloat()); - PlayerATK.TryAdd(pc.PlayerId, KB_ATK.GetFloat()); - PlayerDF.TryAdd(pc.PlayerId, 0f); - - KBScore.TryAdd(pc.PlayerId, 0); - - LastHurt.TryAdd(pc.PlayerId, Utils.TimeStamp); - } - } - - public static string GetDisplayHealth(PlayerControl pc) - { - return pc.SoloAlive() ? Utils.ColorString(GetHealthColor(pc), $"{(int)PlayerHP[pc.PlayerId]}/{(int)PlayerHPMax[pc.PlayerId]}") : string.Empty; - } - - private static Color32 GetHealthColor(PlayerControl pc) - { - var x = (int)(PlayerHP[pc.PlayerId] / PlayerHPMax[pc.PlayerId] * 10 * 50); - var R = 255; - var G = 255; - var B = 0; - - if (x > 255) - R -= x - 255; - else - G = x; - - return new((byte)R, (byte)G, (byte)B, byte.MaxValue); - } - - public static void GetNameNotify(PlayerControl player, ref string name) - { - if (!CustomGameMode.SoloKombat.IsActiveOrIntegrated() || player == null) return; - - if (BackCountdown.TryGetValue(player.PlayerId, out int value)) - { - name = string.Format(Translator.GetString("KBBackCountDown"), value); - NameNotify.Remove(player.PlayerId); - return; - } - - if (NameNotify.TryGetValue(player.PlayerId, out (string Text, long TimeStamp) value1)) name = value1.Text; - } - - public static int GetRankOfScore(byte playerId) - { - try - { - int ms = KBScore[playerId]; - int rank = 1 + KBScore.Values.Count(x => x > ms); - rank += KBScore.Where(x => x.Value == ms).ToList().IndexOf(new(playerId, ms)); - return rank; - } - catch - { - return Main.AllPlayerControls.Length; - } - } - - public static string GetHudText() - { - return string.Format(Translator.GetString("KBTimeRemain"), RoundTime.ToString()); - } - - public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null || !CustomGameMode.SoloKombat.IsActiveOrIntegrated()) return; - - if (!killer.SoloAlive() || !target.SoloAlive() || target.inVent) return; - - float dmg = PlayerATK[killer.PlayerId] - PlayerDF[target.PlayerId]; - PlayerHP[target.PlayerId] = Math.Max(0f, PlayerHP[target.PlayerId] - dmg); - - if (!target.SoloAlive()) - { - OnPlayerDead(target); - OnPlayerKill(killer); - } - - LastHurt[target.PlayerId] = Utils.TimeStamp; - - float kcd = KB_ATKCooldown.GetFloat(); - if (killer.IsHost()) kcd += Utils.CalculatePingDelay() * 2f; - - killer.SetKillCooldown(kcd, target); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); - RPC.PlaySoundRPC(target.PlayerId, Sounds.KillSound); - if (!target.IsModClient() && !target.AmOwner) target.RpcGuardAndKill(); - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - } - - private static void OnPlayerBack(PlayerControl pc) - { - BackCountdown.Remove(pc.PlayerId); - PlayerHP[pc.PlayerId] = PlayerHPMax[pc.PlayerId]; - - LastHurt[pc.PlayerId] = Utils.TimeStamp; - Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed[pc.PlayerId]; - pc.MarkDirtySettings(); - - RPC.PlaySoundRPC(pc.PlayerId, Sounds.TaskComplete); - pc.RpcGuardAndKill(); - - PlayerRandomSpwan(pc); - } - - private static void PlayerRandomSpwan(PlayerControl pc) - { - SpawnMap map = Main.CurrentMap switch - { - MapNames.Skeld => new SkeldSpawnMap(), - MapNames.Mira => new MiraHQSpawnMap(), - MapNames.Polus => new PolusSpawnMap(), - MapNames.Airship => new AirshipSpawnMap(), - MapNames.Fungle => new FungleSpawnMap(), - MapNames.Dleks => new DleksSpawnMap(), - _ => null - }; - - map?.RandomTeleport(pc); - } - - private static void OnPlayerDead(PlayerControl target) - { - OriginalSpeed.Remove(target.PlayerId); - OriginalSpeed.Add(target.PlayerId, Main.AllPlayerSpeed[target.PlayerId]); - - PetsPatch.RpcRemovePet(target); - target.TP(Pelican.GetBlackRoomPS()); - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; - target.MarkDirtySettings(); - - BackCountdown.TryAdd(target.PlayerId, KB_ResurrectionWaitingTime.GetInt()); - } - - private static void OnPlayerKill(PlayerControl killer) - { - killer.KillFlash(); - if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) PlayerControl.LocalPlayer.KillFlash(); - - KBScore[killer.PlayerId]++; - - if (Options.CurrentGameMode == CustomGameMode.AllInOne) - SpeedrunManager.ResetTimer(killer); - - float addRate = IRandom.Instance.Next(3, 5 + GetRankOfScore(killer.PlayerId)) / 100f; - addRate *= KB_KillBonusMultiplier.GetFloat(); - if (killer.IsHost()) addRate /= 2f; - - float addin; - - switch (IRandom.Instance.Next(0, 4)) - { - case 0: - addin = PlayerHPMax[killer.PlayerId] * addRate; - PlayerHPMax[killer.PlayerId] += addin; - AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_HPMax"), addin.ToString("0.0#####"))); - break; - case 1: - addin = PlayerHPReco[killer.PlayerId] * addRate * 2; - PlayerHPReco[killer.PlayerId] += addin; - AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_HPReco"), addin.ToString("0.0#####"))); - break; - case 2: - addin = PlayerATK[killer.PlayerId] * addRate; - PlayerATK[killer.PlayerId] += addin; - AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_ATK"), addin.ToString("0.0#####"))); - break; - case 3: - addin = Math.Max(PlayerDF[killer.PlayerId], 1f) * addRate * 3; - PlayerDF[killer.PlayerId] += addin; - AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_DF"), addin.ToString("0.0#####"))); - break; - } - } - - private static void AddNameNotify(PlayerControl pc, string text, int time = 5) - { - NameNotify.Remove(pc.PlayerId); - NameNotify.Add(pc.PlayerId, (text, Utils.TimeStamp + time)); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private class FixedUpdatePatch - { - private static long LastFixedUpdate; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) - { - byte id = __instance.PlayerId; - if (!GameStates.IsInTask || !CustomGameMode.SoloKombat.IsActiveOrIntegrated() || !AmongUsClient.Instance.AmHost || id == 255) return; - - if (!__instance.SoloAlive()) - { - if (__instance.inVent && KB_BootVentWhenDead.GetBool()) __instance.MyPhysics.RpcExitVent(2); - - Vector2 pos = Pelican.GetBlackRoomPS(); - float dis = Vector2.Distance(pos, __instance.Pos()); - if (dis > 1f) __instance.TP(pos); - } - - var notifyRoles = false; - - if (LastHurt[id] + KB_RecoverAfterSecond.GetInt() < Utils.TimeStamp && PlayerHP[id] < PlayerHPMax[id] && __instance.SoloAlive() && !__instance.inVent) - { - PlayerHP[id] += PlayerHPReco[id]; - PlayerHP[id] = Math.Min(PlayerHPMax[id], PlayerHP[id]); - notifyRoles = true; - } - - if (__instance.SoloAlive() && !__instance.inVent) - { - Vector2 pos = Pelican.GetBlackRoomPS(); - float dis = Vector2.Distance(pos, __instance.Pos()); - if (dis < 1.1f) PlayerRandomSpwan(__instance); - } - - if (BackCountdown.ContainsKey(id)) - { - BackCountdown[id]--; - if (BackCountdown[id] <= 0) OnPlayerBack(__instance); - - notifyRoles = true; - } - - if (NameNotify.ContainsKey(id) && NameNotify[id].TimeStamp < Utils.TimeStamp) - { - NameNotify.Remove(id); - notifyRoles = true; - } - - if (notifyRoles) Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: __instance); - - - if (LastFixedUpdate == Utils.TimeStamp) return; - LastFixedUpdate = Utils.TimeStamp; - - RoundTime--; - } - } - - // Options - // ReSharper disable InconsistentNaming - private static OptionItem KB_GameTime; - public static OptionItem KB_ATKCooldown; - private static OptionItem KB_HPMax; - private static OptionItem KB_ATK; - private static OptionItem KB_RecoverAfterSecond; - private static OptionItem KB_RecoverPerSecond; - private static OptionItem KB_ResurrectionWaitingTime; - private static OptionItem KB_KillBonusMultiplier; - - private static OptionItem KB_BootVentWhenDead; - // ReSharper restore InconsistentNaming + return PlayerHP[pc.PlayerId] > 0f; } + + public static void SetupCustomOption() + { + KB_GameTime = new IntegerOptionItem(66_233_001, "KB_GameTime", new(30, 300, 5), 180, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetHeader(true); + + KB_ATKCooldown = new FloatOptionItem(66_223_008, "KB_ATKCooldown", new(1f, 10f, 0.1f), 1f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + KB_HPMax = new FloatOptionItem(66_233_002, "KB_HPMax", new(10f, 990f, 5f), 100f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Health); + + KB_ATK = new FloatOptionItem(66_233_003, "KB_ATK", new(1f, 100f, 1f), 8f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Health); + + KB_RecoverPerSecond = new FloatOptionItem(66_233_005, "KB_RecoverPerSecond", new(1f, 180f, 1f), 2f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Health); + + KB_RecoverAfterSecond = new IntegerOptionItem(66_233_004, "KB_RecoverAfterSecond", new(0, 60, 1), 8, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + KB_ResurrectionWaitingTime = new IntegerOptionItem(66_233_006, "KB_ResurrectionWaitingTime", new(3, 990, 1), 15, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); + + KB_KillBonusMultiplier = new FloatOptionItem(66_233_007, "KB_KillBonusMultiplier", new(0.25f, 5f, 0.25f), 1.25f, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)) + .SetValueFormat(OptionFormat.Multiplier); + + KB_BootVentWhenDead = new BooleanOptionItem(66_233_009, "KB_BootVentWhenDead", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.SoloKombat) + .SetColor(new Color32(245, 82, 82, byte.MaxValue)); + } + + public static void Init() + { + if (!CustomGameMode.SoloKombat.IsActiveOrIntegrated()) return; + + PlayerHPMax = []; + PlayerHP = []; + PlayerHPReco = []; + PlayerATK = []; + PlayerDF = []; + + LastHurt = []; + OriginalSpeed = []; + BackCountdown = []; + KBScore = []; + RoundTime = KB_GameTime.GetInt() + 8; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + PlayerHPMax.TryAdd(pc.PlayerId, KB_HPMax.GetFloat()); + PlayerHP.TryAdd(pc.PlayerId, KB_HPMax.GetFloat()); + PlayerHPReco.TryAdd(pc.PlayerId, KB_RecoverPerSecond.GetFloat()); + PlayerATK.TryAdd(pc.PlayerId, KB_ATK.GetFloat()); + PlayerDF.TryAdd(pc.PlayerId, 0f); + + KBScore.TryAdd(pc.PlayerId, 0); + + LastHurt.TryAdd(pc.PlayerId, Utils.TimeStamp); + } + } + + public static string GetDisplayHealth(PlayerControl pc) + { + return pc.SoloAlive() ? Utils.ColorString(GetHealthColor(pc), $"{(int)PlayerHP[pc.PlayerId]}/{(int)PlayerHPMax[pc.PlayerId]}") : string.Empty; + } + + private static Color32 GetHealthColor(PlayerControl pc) + { + var x = (int)(PlayerHP[pc.PlayerId] / PlayerHPMax[pc.PlayerId] * 10 * 50); + var R = 255; + var G = 255; + var B = 0; + + if (x > 255) + R -= x - 255; + else + G = x; + + return new((byte)R, (byte)G, (byte)B, byte.MaxValue); + } + + public static void GetNameNotify(PlayerControl player, ref string name) + { + if (!CustomGameMode.SoloKombat.IsActiveOrIntegrated() || player == null) return; + + if (BackCountdown.TryGetValue(player.PlayerId, out int value)) + { + name = string.Format(Translator.GetString("KBBackCountDown"), value); + NameNotify.Remove(player.PlayerId); + return; + } + + if (NameNotify.TryGetValue(player.PlayerId, out (string Text, long TimeStamp) value1)) name = value1.Text; + } + + public static int GetRankOfScore(byte playerId) + { + try + { + int ms = KBScore[playerId]; + int rank = 1 + KBScore.Values.Count(x => x > ms); + rank += KBScore.Where(x => x.Value == ms).ToList().IndexOf(new(playerId, ms)); + return rank; + } + catch + { + return Main.AllPlayerControls.Length; + } + } + + public static string GetHudText() + { + return string.Format(Translator.GetString("KBTimeRemain"), RoundTime.ToString()); + } + + public static void OnPlayerAttack(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || !CustomGameMode.SoloKombat.IsActiveOrIntegrated()) return; + + if (!killer.SoloAlive() || !target.SoloAlive() || target.inVent) return; + + float dmg = PlayerATK[killer.PlayerId] - PlayerDF[target.PlayerId]; + PlayerHP[target.PlayerId] = Math.Max(0f, PlayerHP[target.PlayerId] - dmg); + + if (!target.SoloAlive()) + { + OnPlayerDead(target); + OnPlayerKill(killer); + } + + LastHurt[target.PlayerId] = Utils.TimeStamp; + + float kcd = KB_ATKCooldown.GetFloat(); + if (killer.IsHost()) kcd += Utils.CalculatePingDelay() * 2f; + + killer.SetKillCooldown(kcd, target); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + RPC.PlaySoundRPC(target.PlayerId, Sounds.KillSound); + if (!target.IsModClient() && !target.AmOwner) target.RpcGuardAndKill(); + + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + } + + private static void OnPlayerBack(PlayerControl pc) + { + BackCountdown.Remove(pc.PlayerId); + PlayerHP[pc.PlayerId] = PlayerHPMax[pc.PlayerId]; + + LastHurt[pc.PlayerId] = Utils.TimeStamp; + Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed[pc.PlayerId]; + pc.MarkDirtySettings(); + + RPC.PlaySoundRPC(pc.PlayerId, Sounds.TaskComplete); + pc.RpcGuardAndKill(); + + PlayerRandomSpwan(pc); + } + + private static void PlayerRandomSpwan(PlayerControl pc) + { + SpawnMap map = Main.CurrentMap switch + { + MapNames.Skeld => new SkeldSpawnMap(), + MapNames.Mira => new MiraHQSpawnMap(), + MapNames.Polus => new PolusSpawnMap(), + MapNames.Airship => new AirshipSpawnMap(), + MapNames.Fungle => new FungleSpawnMap(), + MapNames.Dleks => new DleksSpawnMap(), + _ => null + }; + + map?.RandomTeleport(pc); + } + + private static void OnPlayerDead(PlayerControl target) + { + OriginalSpeed.Remove(target.PlayerId); + OriginalSpeed.Add(target.PlayerId, Main.AllPlayerSpeed[target.PlayerId]); + + PetsPatch.RpcRemovePet(target); + target.TP(Pelican.GetBlackRoomPS()); + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); + + BackCountdown.TryAdd(target.PlayerId, KB_ResurrectionWaitingTime.GetInt()); + } + + private static void OnPlayerKill(PlayerControl killer) + { + killer.KillFlash(); + if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) PlayerControl.LocalPlayer.KillFlash(); + + KBScore[killer.PlayerId]++; + + if (Options.CurrentGameMode == CustomGameMode.AllInOne) + SpeedrunManager.ResetTimer(killer); + + float addRate = IRandom.Instance.Next(3, 5 + GetRankOfScore(killer.PlayerId)) / 100f; + addRate *= KB_KillBonusMultiplier.GetFloat(); + if (killer.IsHost()) addRate /= 2f; + + float addin; + + switch (IRandom.Instance.Next(0, 4)) + { + case 0: + addin = PlayerHPMax[killer.PlayerId] * addRate; + PlayerHPMax[killer.PlayerId] += addin; + AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_HPMax"), addin.ToString("0.0#####"))); + break; + case 1: + addin = PlayerHPReco[killer.PlayerId] * addRate * 2; + PlayerHPReco[killer.PlayerId] += addin; + AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_HPReco"), addin.ToString("0.0#####"))); + break; + case 2: + addin = PlayerATK[killer.PlayerId] * addRate; + PlayerATK[killer.PlayerId] += addin; + AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_ATK"), addin.ToString("0.0#####"))); + break; + case 3: + addin = Math.Max(PlayerDF[killer.PlayerId], 1f) * addRate * 3; + PlayerDF[killer.PlayerId] += addin; + AddNameNotify(killer, string.Format(Translator.GetString("KB_Buff_DF"), addin.ToString("0.0#####"))); + break; + } + } + + private static void AddNameNotify(PlayerControl pc, string text, int time = 5) + { + NameNotify.Remove(pc.PlayerId); + NameNotify.Add(pc.PlayerId, (text, Utils.TimeStamp + time)); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private class FixedUpdatePatch + { + private static long LastFixedUpdate; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + byte id = __instance.PlayerId; + if (!GameStates.IsInTask || !CustomGameMode.SoloKombat.IsActiveOrIntegrated() || !AmongUsClient.Instance.AmHost || id == 255) return; + + if (!__instance.SoloAlive()) + { + if (__instance.inVent && KB_BootVentWhenDead.GetBool()) __instance.MyPhysics.RpcExitVent(2); + + Vector2 pos = Pelican.GetBlackRoomPS(); + float dis = Vector2.Distance(pos, __instance.Pos()); + if (dis > 1f) __instance.TP(pos); + } + + var notifyRoles = false; + + if (LastHurt[id] + KB_RecoverAfterSecond.GetInt() < Utils.TimeStamp && PlayerHP[id] < PlayerHPMax[id] && __instance.SoloAlive() && !__instance.inVent) + { + PlayerHP[id] += PlayerHPReco[id]; + PlayerHP[id] = Math.Min(PlayerHPMax[id], PlayerHP[id]); + notifyRoles = true; + } + + if (__instance.SoloAlive() && !__instance.inVent) + { + Vector2 pos = Pelican.GetBlackRoomPS(); + float dis = Vector2.Distance(pos, __instance.Pos()); + if (dis < 1.1f) PlayerRandomSpwan(__instance); + } + + if (BackCountdown.ContainsKey(id)) + { + BackCountdown[id]--; + if (BackCountdown[id] <= 0) OnPlayerBack(__instance); + + notifyRoles = true; + } + + if (NameNotify.ContainsKey(id) && NameNotify[id].TimeStamp < Utils.TimeStamp) + { + NameNotify.Remove(id); + notifyRoles = true; + } + + if (notifyRoles) Utils.NotifyRoles(SpecifySeer: __instance, SpecifyTarget: __instance); + + + if (LastFixedUpdate == Utils.TimeStamp) return; + LastFixedUpdate = Utils.TimeStamp; + + RoundTime--; + } + } + + // Options + // ReSharper disable InconsistentNaming + private static OptionItem KB_GameTime; + public static OptionItem KB_ATKCooldown; + private static OptionItem KB_HPMax; + private static OptionItem KB_ATK; + private static OptionItem KB_RecoverAfterSecond; + private static OptionItem KB_RecoverPerSecond; + private static OptionItem KB_ResurrectionWaitingTime; + private static OptionItem KB_KillBonusMultiplier; + + private static OptionItem KB_BootVentWhenDead; + // ReSharper restore InconsistentNaming } \ No newline at end of file diff --git a/GameMode/SpeedrunManager.cs b/GameMode/SpeedrunManager.cs index 15c9cd733..64beb295f 100644 --- a/GameMode/SpeedrunManager.cs +++ b/GameMode/SpeedrunManager.cs @@ -5,189 +5,188 @@ using EHR.Modules; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +public static class SpeedrunManager { - public static class SpeedrunManager + private static OptionItem TaskFinishWins; + private static OptionItem TimeStacksUp; + private static OptionItem TimeLimit; + private static OptionItem KillCooldown; + private static OptionItem KillersCanKillTaskingPlayers; + + public static HashSet CanKill = []; + + public static Dictionary Timers = []; + + public static void SetupCustomOption() { - private static OptionItem TaskFinishWins; - private static OptionItem TimeStacksUp; - private static OptionItem TimeLimit; - private static OptionItem KillCooldown; - private static OptionItem KillersCanKillTaskingPlayers; + const int id = 69_214_001; + Color color = Utils.GetRoleColor(CustomRoles.Speedrunner); - public static HashSet CanKill = []; + TaskFinishWins = new BooleanOptionItem(id, "Speedrun_TaskFinishWins", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Speedrun) + .SetColor(color); - public static Dictionary Timers = []; + TimeStacksUp = new BooleanOptionItem(id + 1, "Speedrun_TimeStacksUp", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Speedrun) + .SetColor(color); - public static void SetupCustomOption() - { - const int id = 69_214_001; - Color color = Utils.GetRoleColor(CustomRoles.Speedrunner); + TimeLimit = new IntegerOptionItem(id + 2, "Speedrun_TimeLimit", new(1, 90, 1), 25, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Speedrun) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(color); - TaskFinishWins = new BooleanOptionItem(id, "Speedrun_TaskFinishWins", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Speedrun) - .SetColor(color); + KillCooldown = new IntegerOptionItem(id + 3, "KillCooldown", new(0, 60, 1), 10, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Speedrun) + .SetValueFormat(OptionFormat.Seconds) + .SetColor(color); - TimeStacksUp = new BooleanOptionItem(id + 1, "Speedrun_TimeStacksUp", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Speedrun) - .SetColor(color); - - TimeLimit = new IntegerOptionItem(id + 2, "Speedrun_TimeLimit", new(1, 90, 1), 25, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Speedrun) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(color); - - KillCooldown = new IntegerOptionItem(id + 3, "KillCooldown", new(0, 60, 1), 10, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Speedrun) - .SetValueFormat(OptionFormat.Seconds) - .SetColor(color); - - KillersCanKillTaskingPlayers = new BooleanOptionItem(id + 4, "Speedrun_KillersCanKillTaskingPlayers", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Speedrun) - .SetColor(color); - } - - public static void Init() - { - CanKill = []; - Timers = Main.AllAlivePlayerControls.ToDictionary(x => x.PlayerId, _ => TimeLimit.GetInt() + 7); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) Timers.AdjustAllValues(x => x * 2); - } - - public static void ResetTimer(PlayerControl pc) - { - int timer = TimeLimit.GetInt(); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) timer *= AllInOneGameMode.SpeedrunTimeLimitMultiplier.GetInt(); - - if (TimeStacksUp.GetBool()) - Timers[pc.PlayerId] += timer; - else - Timers[pc.PlayerId] = timer; - - Logger.Info($" Timer for {pc.GetRealName()} set to {Timers[pc.PlayerId]}", "Speedrun"); - } - - public static void OnTaskFinish(PlayerControl pc) - { - if (TaskFinishWins.GetBool()) return; - - CanKill.Add(pc.PlayerId); - Main.AllPlayerKillCooldown[pc.PlayerId] = KillCooldown.GetInt(); - pc.RpcChangeRoleBasis(Options.CurrentGameMode == CustomGameMode.AllInOne ? CustomRoles.Killer : CustomRoles.Runner); - pc.Notify(Translator.GetString("Speedrun_CompletedTasks")); - pc.SyncSettings(); - pc.SetKillCooldown(KillCooldown.GetInt()); - } - - public static string GetTaskBarText() - { - return string.Join('\n', Main.PlayerStates - .Join(Main.AllAlivePlayerControls, x => x.Key, x => x.PlayerId, (kvp, pc) => ( - Name: Utils.ColorString(Main.PlayerColors.GetValueOrDefault(kvp.Key, Color.white), pc.GetRealName()), - CompletedTasks: kvp.Value.TaskState.CompletedTasksCount, - AllTasks: kvp.Value.TaskState.AllTasksCount, - Time: Timers.GetValueOrDefault(pc.PlayerId))) - .OrderByDescending(x => x.CompletedTasks) - .Select(x => x.CompletedTasks < x.AllTasks ? $"{x.Name}: {x.CompletedTasks}/{x.AllTasks} ({x.Time}s)" : $"{x.Name}: {Translator.GetString("Speedrun_KillingPlayer")} ({x.Time}s)")); - } - - public static string GetSuffixText(PlayerControl pc) - { - if (!pc.IsAlive()) return string.Empty; - - int time = Timers[pc.PlayerId]; - int alive = Main.AllAlivePlayerControls.Length; - int apc = Main.AllPlayerControls.Length; - int killers = CanKill.Count; - - // ReSharper disable once ConvertIfStatementToReturnStatement - if (CanKill.Contains(pc.PlayerId)) return string.Format(Translator.GetString("Speedrun_CanKillSuffixInfo"), alive, apc, killers - 1, time); - return string.Format(Translator.GetString("Speedrun_DoTasksSuffixInfo"), pc.GetTaskState().RemainingTasksCount, alive, apc, killers, time); - } - - public static bool CheckForGameEnd(out GameOverReason reason) - { - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - if (TaskFinishWins.GetBool()) - { - PlayerControl player = aapc.FirstOrDefault(x => x.GetTaskState().IsTaskFinished); - - if (player != null) - { - CustomWinnerHolder.WinnerIds = [player.PlayerId]; - reason = GameOverReason.HumansByTask; - return true; - } - } - - switch (aapc.Length) - { - case 1: - CustomWinnerHolder.WinnerIds = [aapc[0].PlayerId]; - reason = GameOverReason.ImpostorByKill; - return true; - case 0: - CustomWinnerHolder.WinnerIds = []; - reason = GameOverReason.HumansDisconnect; - return true; - } - - reason = GameOverReason.ImpostorByKill; - KeyCode[] keys = { KeyCode.LeftShift, KeyCode.L, KeyCode.Return }; - return keys.Any(Input.GetKeyDown) && keys.All(Input.GetKey); - } - - public static bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!CanKill.Contains(killer.PlayerId)) return false; - - return CanKill.Contains(target.PlayerId) || KillersCanKillTaskingPlayers.GetBool(); - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private static class FixedUpdatePatch - { - private static long LastUpdate; - - [SuppressMessage("ReSharper", "UnusedMember.Local")] - public static void Postfix(PlayerControl __instance) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.Speedrun.IsActiveOrIntegrated() || Main.HasJustStarted || __instance.Is(CustomRoles.Killer) || __instance.PlayerId == 255) return; - - if (__instance.IsAlive() && Timers[__instance.PlayerId] <= 0) - { - __instance.Suicide(); - - if (__instance.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - - long now = Utils.TimeStamp; - if (LastUpdate == now) return; - LastUpdate = now; - - Timers.AdjustAllValues(x => x - 1); - Utils.NotifyRoles(); - - CanKill.RemoveWhere(x => x.GetPlayer() == null || !x.GetPlayer().IsAlive()); - } - } + KillersCanKillTaskingPlayers = new BooleanOptionItem(id + 4, "Speedrun_KillersCanKillTaskingPlayers", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Speedrun) + .SetColor(color); } - public class Runner : RoleBase + public static void Init() { - public override bool IsEnable => false; + CanKill = []; + Timers = Main.AllAlivePlayerControls.ToDictionary(x => x.PlayerId, _ => TimeLimit.GetInt() + 7); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) Timers.AdjustAllValues(x => x * 2); + } - public override void Init() { } + public static void ResetTimer(PlayerControl pc) + { + int timer = TimeLimit.GetInt(); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) timer *= AllInOneGameMode.SpeedrunTimeLimitMultiplier.GetInt(); - public override void Add(byte playerId) { } + if (TimeStacksUp.GetBool()) + Timers[pc.PlayerId] += timer; + else + Timers[pc.PlayerId] = timer; - public override void SetupCustomOption() { } + Logger.Info($" Timer for {pc.GetRealName()} set to {Timers[pc.PlayerId]}", "Speedrun"); + } - public override bool CanUseVent(PlayerControl pc, int ventId) + public static void OnTaskFinish(PlayerControl pc) + { + if (TaskFinishWins.GetBool()) return; + + CanKill.Add(pc.PlayerId); + Main.AllPlayerKillCooldown[pc.PlayerId] = KillCooldown.GetInt(); + pc.RpcChangeRoleBasis(Options.CurrentGameMode == CustomGameMode.AllInOne ? CustomRoles.Killer : CustomRoles.Runner); + pc.Notify(Translator.GetString("Speedrun_CompletedTasks")); + pc.SyncSettings(); + pc.SetKillCooldown(KillCooldown.GetInt()); + } + + public static string GetTaskBarText() + { + return string.Join('\n', Main.PlayerStates + .Join(Main.AllAlivePlayerControls, x => x.Key, x => x.PlayerId, (kvp, pc) => ( + Name: Utils.ColorString(Main.PlayerColors.GetValueOrDefault(kvp.Key, Color.white), pc.GetRealName()), + CompletedTasks: kvp.Value.TaskState.CompletedTasksCount, + AllTasks: kvp.Value.TaskState.AllTasksCount, + Time: Timers.GetValueOrDefault(pc.PlayerId))) + .OrderByDescending(x => x.CompletedTasks) + .Select(x => x.CompletedTasks < x.AllTasks ? $"{x.Name}: {x.CompletedTasks}/{x.AllTasks} ({x.Time}s)" : $"{x.Name}: {Translator.GetString("Speedrun_KillingPlayer")} ({x.Time}s)")); + } + + public static string GetSuffixText(PlayerControl pc) + { + if (!pc.IsAlive()) return string.Empty; + + int time = Timers[pc.PlayerId]; + int alive = Main.AllAlivePlayerControls.Length; + int apc = Main.AllPlayerControls.Length; + int killers = CanKill.Count; + + // ReSharper disable once ConvertIfStatementToReturnStatement + if (CanKill.Contains(pc.PlayerId)) return string.Format(Translator.GetString("Speedrun_CanKillSuffixInfo"), alive, apc, killers - 1, time); + return string.Format(Translator.GetString("Speedrun_DoTasksSuffixInfo"), pc.GetTaskState().RemainingTasksCount, alive, apc, killers, time); + } + + public static bool CheckForGameEnd(out GameOverReason reason) + { + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + if (TaskFinishWins.GetBool()) { - return false; + PlayerControl player = aapc.FirstOrDefault(x => x.GetTaskState().IsTaskFinished); + + if (player != null) + { + CustomWinnerHolder.WinnerIds = [player.PlayerId]; + reason = GameOverReason.HumansByTask; + return true; + } } + + switch (aapc.Length) + { + case 1: + CustomWinnerHolder.WinnerIds = [aapc[0].PlayerId]; + reason = GameOverReason.ImpostorByKill; + return true; + case 0: + CustomWinnerHolder.WinnerIds = []; + reason = GameOverReason.HumansDisconnect; + return true; + } + + reason = GameOverReason.ImpostorByKill; + KeyCode[] keys = { KeyCode.LeftShift, KeyCode.L, KeyCode.Return }; + return keys.Any(Input.GetKeyDown) && keys.All(Input.GetKey); + } + + public static bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!CanKill.Contains(killer.PlayerId)) return false; + + return CanKill.Contains(target.PlayerId) || KillersCanKillTaskingPlayers.GetBool(); + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private static class FixedUpdatePatch + { + private static long LastUpdate; + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + public static void Postfix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || !CustomGameMode.Speedrun.IsActiveOrIntegrated() || Main.HasJustStarted || __instance.Is(CustomRoles.Killer) || __instance.PlayerId == 255) return; + + if (__instance.IsAlive() && Timers[__instance.PlayerId] <= 0) + { + __instance.Suicide(); + + if (__instance.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + + long now = Utils.TimeStamp; + if (LastUpdate == now) return; + LastUpdate = now; + + Timers.AdjustAllValues(x => x - 1); + Utils.NotifyRoles(); + + CanKill.RemoveWhere(x => x.GetPlayer() == null || !x.GetPlayer().IsAlive()); + } + } +} + +public class Runner : RoleBase +{ + public override bool IsEnable => false; + + public override void Init() { } + + public override void Add(byte playerId) { } + + public override void SetupCustomOption() { } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return false; } } \ No newline at end of file diff --git a/Modules/AFKDetector.cs b/Modules/AFKDetector.cs index 9ed424226..6439b73cb 100644 --- a/Modules/AFKDetector.cs +++ b/Modules/AFKDetector.cs @@ -3,146 +3,145 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -namespace EHR.Modules +namespace EHR.Modules; + +public static class AFKDetector { - public static class AFKDetector + private static OptionItem EnableDetector; + private static OptionItem ConsequenceOption; + private static OptionItem MinPlayersToActivate; + public static OptionItem ActivateOnStart; + + public static readonly Dictionary PlayerData = []; + public static readonly HashSet ExemptedPlayers = []; + public static readonly HashSet ShieldedPlayers = []; + public static int NumAFK; + + public static void SetupCustomOption() { - private static OptionItem EnableDetector; - private static OptionItem ConsequenceOption; - private static OptionItem MinPlayersToActivate; - public static OptionItem ActivateOnStart; + new TextOptionItem(100080, "MenuTitle.AFKDetection", TabGroup.GameSettings) + .SetColor(new Color32(0, 255, 165, 255)) + .SetHeader(true); - public static readonly Dictionary PlayerData = []; - public static readonly HashSet ExemptedPlayers = []; - public static readonly HashSet ShieldedPlayers = []; - public static int NumAFK; + EnableDetector = new BooleanOptionItem(90, "EnableAFKDetector", true, TabGroup.GameSettings) + .SetColor(new Color32(0, 255, 165, 255)); - public static void SetupCustomOption() + ConsequenceOption = new StringOptionItem(91, "AFKConsequence", Enum.GetNames().Select(x => $"AFKConsequence.{x}").ToArray(), 0, TabGroup.GameSettings) + .SetParent(EnableDetector) + .SetColor(new Color32(0, 255, 165, 255)); + + MinPlayersToActivate = new IntegerOptionItem(92, "AFKMinPlayersToActivate", new(3, 15, 1), 7, TabGroup.GameSettings) + .SetParent(EnableDetector) + .SetColor(new Color32(0, 255, 165, 255)); + + ActivateOnStart = new BooleanOptionItem(93, "AFKActivateOnStart", false, TabGroup.GameSettings) + .SetParent(EnableDetector) + .SetColor(new Color32(0, 255, 165, 255)); + } + + public static void RecordPosition(PlayerControl pc) + { + if (!EnableDetector.GetBool() || !GameStates.IsInTask || pc == null || ExemptedPlayers.Contains(pc.PlayerId)) return; + + PlayerData[pc.PlayerId] = new() { - new TextOptionItem(100080, "MenuTitle.AFKDetection", TabGroup.GameSettings) - .SetColor(new Color32(0, 255, 165, 255)) - .SetHeader(true); + LastPosition = pc.Pos(), + Timer = 10f + (pc.Is(CustomRoles.Truant) ? Options.TruantWaitingTime.GetFloat() : 0f) + }; + } - EnableDetector = new BooleanOptionItem(90, "EnableAFKDetector", true, TabGroup.GameSettings) - .SetColor(new Color32(0, 255, 165, 255)); + public static void OnFixedUpdate(PlayerControl pc) + { + if (!EnableDetector.GetBool() || !GameStates.IsInTask || Main.AllAlivePlayerControls.Length < MinPlayersToActivate.GetInt() || pc == null || !PlayerData.TryGetValue(pc.PlayerId, out Data data)) return; - ConsequenceOption = new StringOptionItem(91, "AFKConsequence", Enum.GetNames().Select(x => $"AFKConsequence.{x}").ToArray(), 0, TabGroup.GameSettings) - .SetParent(EnableDetector) - .SetColor(new Color32(0, 255, 165, 255)); - - MinPlayersToActivate = new IntegerOptionItem(92, "AFKMinPlayersToActivate", new(3, 15, 1), 7, TabGroup.GameSettings) - .SetParent(EnableDetector) - .SetColor(new Color32(0, 255, 165, 255)); - - ActivateOnStart = new BooleanOptionItem(93, "AFKActivateOnStart", false, TabGroup.GameSettings) - .SetParent(EnableDetector) - .SetColor(new Color32(0, 255, 165, 255)); - } - - public static void RecordPosition(PlayerControl pc) + if (Vector2.Distance(pc.Pos(), data.LastPosition) > 0.1f) { - if (!EnableDetector.GetBool() || !GameStates.IsInTask || pc == null || ExemptedPlayers.Contains(pc.PlayerId)) return; - - PlayerData[pc.PlayerId] = new() - { - LastPosition = pc.Pos(), - Timer = 10f + (pc.Is(CustomRoles.Truant) ? Options.TruantWaitingTime.GetFloat() : 0f) - }; - } - - public static void OnFixedUpdate(PlayerControl pc) - { - if (!EnableDetector.GetBool() || !GameStates.IsInTask || Main.AllAlivePlayerControls.Length < MinPlayersToActivate.GetInt() || pc == null || !PlayerData.TryGetValue(pc.PlayerId, out Data data)) return; - - if (Vector2.Distance(pc.Pos(), data.LastPosition) > 0.1f) - { - PlayerData.Remove(pc.PlayerId); - ShieldedPlayers.Remove(pc.PlayerId); - return; - } - - var lastTimer = (int)Math.Round(data.Timer); - data.Timer -= Time.fixedDeltaTime; - var currentTimer = (int)Math.Round(data.Timer); - - if (data.Timer <= 0f) - { - switch (data.CurrentPhase) - { - case Data.Phase.Detection: - if (!pc.IsModClient()) NumAFK++; - - data.CurrentPhase = Data.Phase.Warning; - data.Timer = 15f; - break; - case Data.Phase.Warning: - data.CurrentPhase = Data.Phase.Consequence; - HandleConsequence(pc, (Consequence)ConsequenceOption.GetInt()); - break; - } - - if (Main.AllAlivePlayerControls.Length / 2 <= NumAFK) - { - Logger.SendInGame(Translator.GetString("AFKTooMany")); - PlayerData.Clear(); - } - } - - if (data.CurrentPhase == Data.Phase.Warning && lastTimer != currentTimer) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public static string GetSuffix(PlayerControl seer, PlayerControl target) - { - if (seer.PlayerId == target.PlayerId && seer.IsAlive() && PlayerData.TryGetValue(seer.PlayerId, out Data seerData) && seerData.CurrentPhase > Data.Phase.Detection) return seerData.CurrentPhase == Data.Phase.Warning ? string.Format(Translator.GetString("AFKWarning"), (int)Math.Round(seerData.Timer)) : Translator.GetString("AFKSuffix"); - - if (target.IsAlive() && PlayerData.TryGetValue(target.PlayerId, out Data targetData) && targetData.CurrentPhase > Data.Phase.Detection) return Translator.GetString("AFKSuffix"); - - return string.Empty; - } - - private static void HandleConsequence(PlayerControl pc, Consequence consequence) - { - switch (consequence) - { - case Consequence.Shield: - ShieldedPlayers.Add(pc.PlayerId); - Utils.NotifyRoles(SpecifyTarget: pc); - break; - case Consequence.Kick: - AmongUsClient.Instance.KickPlayer(pc.GetClientId(), false); - Logger.SendInGame(string.Format(Translator.GetString("AFKKick"), pc.PlayerId.ColoredPlayerName())); - break; - case Consequence.Suicide: - pc.Suicide(PlayerState.DeathReason.AFK); - break; - case Consequence.Nothing: - Utils.NotifyRoles(SpecifyTarget: pc); - break; - } - PlayerData.Remove(pc.PlayerId); + ShieldedPlayers.Remove(pc.PlayerId); + return; } - private enum Consequence - { - Nothing, - Shield, - Suicide, - Kick - } + var lastTimer = (int)Math.Round(data.Timer); + data.Timer -= Time.fixedDeltaTime; + var currentTimer = (int)Math.Round(data.Timer); - public class Data + if (data.Timer <= 0f) { - public enum Phase + switch (data.CurrentPhase) { - Detection, - Warning, - Consequence + case Data.Phase.Detection: + if (!pc.IsModClient()) NumAFK++; + + data.CurrentPhase = Data.Phase.Warning; + data.Timer = 15f; + break; + case Data.Phase.Warning: + data.CurrentPhase = Data.Phase.Consequence; + HandleConsequence(pc, (Consequence)ConsequenceOption.GetInt()); + break; } - public Vector2 LastPosition { get; init; } - public float Timer { get; set; } - public Phase CurrentPhase { get; set; } + if (Main.AllAlivePlayerControls.Length / 2 <= NumAFK) + { + Logger.SendInGame(Translator.GetString("AFKTooMany")); + PlayerData.Clear(); + } } + + if (data.CurrentPhase == Data.Phase.Warning && lastTimer != currentTimer) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public static string GetSuffix(PlayerControl seer, PlayerControl target) + { + if (seer.PlayerId == target.PlayerId && seer.IsAlive() && PlayerData.TryGetValue(seer.PlayerId, out Data seerData) && seerData.CurrentPhase > Data.Phase.Detection) return seerData.CurrentPhase == Data.Phase.Warning ? string.Format(Translator.GetString("AFKWarning"), (int)Math.Round(seerData.Timer)) : Translator.GetString("AFKSuffix"); + + if (target.IsAlive() && PlayerData.TryGetValue(target.PlayerId, out Data targetData) && targetData.CurrentPhase > Data.Phase.Detection) return Translator.GetString("AFKSuffix"); + + return string.Empty; + } + + private static void HandleConsequence(PlayerControl pc, Consequence consequence) + { + switch (consequence) + { + case Consequence.Shield: + ShieldedPlayers.Add(pc.PlayerId); + Utils.NotifyRoles(SpecifyTarget: pc); + break; + case Consequence.Kick: + AmongUsClient.Instance.KickPlayer(pc.GetClientId(), false); + Logger.SendInGame(string.Format(Translator.GetString("AFKKick"), pc.PlayerId.ColoredPlayerName())); + break; + case Consequence.Suicide: + pc.Suicide(PlayerState.DeathReason.AFK); + break; + case Consequence.Nothing: + Utils.NotifyRoles(SpecifyTarget: pc); + break; + } + + PlayerData.Remove(pc.PlayerId); + } + + private enum Consequence + { + Nothing, + Shield, + Suicide, + Kick + } + + public class Data + { + public enum Phase + { + Detection, + Warning, + Consequence + } + + public Vector2 LastPosition { get; init; } + public float Timer { get; set; } + public Phase CurrentPhase { get; set; } } } \ No newline at end of file diff --git a/Modules/AURoleOptions.cs b/Modules/AURoleOptions.cs index a7f0c68a2..f3b85cc82 100644 --- a/Modules/AURoleOptions.cs +++ b/Modules/AURoleOptions.cs @@ -1,128 +1,127 @@ using AmongUs.GameOptions; -namespace EHR +namespace EHR; + +public static class AURoleOptions { - public static class AURoleOptions + private static IGameOptions Opt; + + public static float KillCooldown { - private static IGameOptions Opt; + get => Opt.GetFloat(FloatOptionNames.KillCooldown); + set => Opt.SetFloat(FloatOptionNames.KillCooldown, value); + } - public static float KillCooldown - { - get => Opt.GetFloat(FloatOptionNames.KillCooldown); - set => Opt.SetFloat(FloatOptionNames.KillCooldown, value); - } + public static float PlayerSpeedMod + { + get => Opt.GetFloat(FloatOptionNames.PlayerSpeedMod); + set => Opt.SetFloat(FloatOptionNames.PlayerSpeedMod, value); + } - public static float PlayerSpeedMod - { - get => Opt.GetFloat(FloatOptionNames.PlayerSpeedMod); - set => Opt.SetFloat(FloatOptionNames.PlayerSpeedMod, value); - } + public static float ScientistCooldown + { + get => Opt.GetFloat(FloatOptionNames.ScientistCooldown); + set => Opt.SetFloat(FloatOptionNames.ScientistCooldown, value); + } - public static float ScientistCooldown - { - get => Opt.GetFloat(FloatOptionNames.ScientistCooldown); - set => Opt.SetFloat(FloatOptionNames.ScientistCooldown, value); - } + public static float ScientistBatteryCharge + { + get => Opt.GetFloat(FloatOptionNames.ScientistBatteryCharge); + set => Opt.SetFloat(FloatOptionNames.ScientistBatteryCharge, value); + } - public static float ScientistBatteryCharge - { - get => Opt.GetFloat(FloatOptionNames.ScientistBatteryCharge); - set => Opt.SetFloat(FloatOptionNames.ScientistBatteryCharge, value); - } + public static float EngineerCooldown + { + get => Opt.GetFloat(FloatOptionNames.EngineerCooldown); + set => Opt.SetFloat(FloatOptionNames.EngineerCooldown, value); + } - public static float EngineerCooldown - { - get => Opt.GetFloat(FloatOptionNames.EngineerCooldown); - set => Opt.SetFloat(FloatOptionNames.EngineerCooldown, value); - } + public static float EngineerInVentMaxTime + { + get => Opt.GetFloat(FloatOptionNames.EngineerInVentMaxTime); + set => Opt.SetFloat(FloatOptionNames.EngineerInVentMaxTime, value); + } - public static float EngineerInVentMaxTime - { - get => Opt.GetFloat(FloatOptionNames.EngineerInVentMaxTime); - set => Opt.SetFloat(FloatOptionNames.EngineerInVentMaxTime, value); - } + public static float GuardianAngelCooldown + { + get => Opt.GetFloat(FloatOptionNames.GuardianAngelCooldown); + set => Opt.SetFloat(FloatOptionNames.GuardianAngelCooldown, value); + } - public static float GuardianAngelCooldown - { - get => Opt.GetFloat(FloatOptionNames.GuardianAngelCooldown); - set => Opt.SetFloat(FloatOptionNames.GuardianAngelCooldown, value); - } + public static float ProtectionDurationSeconds + { + get => Opt.GetFloat(FloatOptionNames.ProtectionDurationSeconds); + set => Opt.SetFloat(FloatOptionNames.ProtectionDurationSeconds, value); + } - public static float ProtectionDurationSeconds - { - get => Opt.GetFloat(FloatOptionNames.ProtectionDurationSeconds); - set => Opt.SetFloat(FloatOptionNames.ProtectionDurationSeconds, value); - } + public static bool ImpostorsCanSeeProtect + { + get => Opt.GetBool(BoolOptionNames.ImpostorsCanSeeProtect); + set => Opt.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, value); + } - public static bool ImpostorsCanSeeProtect - { - get => Opt.GetBool(BoolOptionNames.ImpostorsCanSeeProtect); - set => Opt.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, value); - } + public static float ShapeshifterDuration + { + get => Opt.GetFloat(FloatOptionNames.ShapeshifterDuration); + set => Opt.SetFloat(FloatOptionNames.ShapeshifterDuration, value); + } - public static float ShapeshifterDuration - { - get => Opt.GetFloat(FloatOptionNames.ShapeshifterDuration); - set => Opt.SetFloat(FloatOptionNames.ShapeshifterDuration, value); - } + public static float ShapeshifterCooldown + { + get => Opt.GetFloat(FloatOptionNames.ShapeshifterCooldown); + set => Opt.SetFloat(FloatOptionNames.ShapeshifterCooldown, value); + } - public static float ShapeshifterCooldown - { - get => Opt.GetFloat(FloatOptionNames.ShapeshifterCooldown); - set => Opt.SetFloat(FloatOptionNames.ShapeshifterCooldown, value); - } + public static bool ShapeshifterLeaveSkin + { + get => Opt.GetBool(BoolOptionNames.ShapeshifterLeaveSkin); + set => Opt.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, value); + } - public static bool ShapeshifterLeaveSkin - { - get => Opt.GetBool(BoolOptionNames.ShapeshifterLeaveSkin); - set => Opt.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, value); - } + public static bool NoisemakerImpostorAlert + { + get => Opt.GetBool(BoolOptionNames.NoisemakerImpostorAlert); + set => Opt.SetBool(BoolOptionNames.NoisemakerImpostorAlert, value); + } - public static bool NoisemakerImpostorAlert - { - get => Opt.GetBool(BoolOptionNames.NoisemakerImpostorAlert); - set => Opt.SetBool(BoolOptionNames.NoisemakerImpostorAlert, value); - } + public static float NoisemakerAlertDuration + { + get => Opt.GetFloat(FloatOptionNames.NoisemakerAlertDuration); + set => Opt.SetFloat(FloatOptionNames.NoisemakerAlertDuration, value); + } - public static float NoisemakerAlertDuration - { - get => Opt.GetFloat(FloatOptionNames.NoisemakerAlertDuration); - set => Opt.SetFloat(FloatOptionNames.NoisemakerAlertDuration, value); - } + public static float PhantomCooldown + { + get => Opt.GetFloat(FloatOptionNames.PhantomCooldown); + set => Opt.SetFloat(FloatOptionNames.PhantomCooldown, value); + } - public static float PhantomCooldown - { - get => Opt.GetFloat(FloatOptionNames.PhantomCooldown); - set => Opt.SetFloat(FloatOptionNames.PhantomCooldown, value); - } + public static float PhantomDuration + { + get => Opt.GetFloat(FloatOptionNames.PhantomDuration); + set => Opt.SetFloat(FloatOptionNames.PhantomDuration, value); + } - public static float PhantomDuration - { - get => Opt.GetFloat(FloatOptionNames.PhantomDuration); - set => Opt.SetFloat(FloatOptionNames.PhantomDuration, value); - } + public static float TrackerCooldown + { + get => Opt.GetFloat(FloatOptionNames.TrackerCooldown); + set => Opt.SetFloat(FloatOptionNames.TrackerCooldown, value); + } - public static float TrackerCooldown - { - get => Opt.GetFloat(FloatOptionNames.TrackerCooldown); - set => Opt.SetFloat(FloatOptionNames.TrackerCooldown, value); - } + public static float TrackerDuration + { + get => Opt.GetFloat(FloatOptionNames.TrackerDuration); + set => Opt.SetFloat(FloatOptionNames.TrackerDuration, value); + } - public static float TrackerDuration - { - get => Opt.GetFloat(FloatOptionNames.TrackerDuration); - set => Opt.SetFloat(FloatOptionNames.TrackerDuration, value); - } + public static float TrackerDelay + { + get => Opt.GetFloat(FloatOptionNames.TrackerDelay); + set => Opt.SetFloat(FloatOptionNames.TrackerDelay, value); + } - public static float TrackerDelay - { - get => Opt.GetFloat(FloatOptionNames.TrackerDelay); - set => Opt.SetFloat(FloatOptionNames.TrackerDelay, value); - } - - public static void SetOpt(IGameOptions opt) - { - Opt = opt; - } + public static void SetOpt(IGameOptions opt) + { + Opt = opt; } } \ No newline at end of file diff --git a/Modules/Achievements.cs b/Modules/Achievements.cs index 579cebc51..aae824f1b 100644 --- a/Modules/Achievements.cs +++ b/Modules/Achievements.cs @@ -5,185 +5,184 @@ using UnityEngine; // ReSharper disable InconsistentNaming -namespace EHR.Modules +namespace EHR.Modules; + +public static class Achievements { - public static class Achievements + public enum Type { - public enum Type + IdeasLiveOn, // As Undead, win with at least 3 Necromancers + HowCouldIBelieveThem, // Vote for Jester that was ejected + WhatKindOfGeniusCameUpWithThisRoleIdea, // Get Dad role + StabbingTheBack, // Kill a Crewmate as Backstabber + Honk, // Drag someone as Goose + Bruh, // Get vanilla Crewmate role + HowDoICraftThisAgain, // Craft something as Adventurer + ImCrewISwear, // Kill the last Impostor with all Neutral Killers dead + Covid20, // Infect 2 people as Virus + TheresThisGameMyDadTaughtMeItsCalledSwitch, // Swap as or be Swapped by Shifter at least 3 times in one round + FirstDayOnTheJob, // As an Amnesiac, remember that you were a Sidekick or Deathknight + UnderNewManagement, // As an Amnesiac, remember that you were a Cultist or Virus + OutOfTime, // Suicide or lose due to a time limit + TooCold, // Get frozen by Freezer, Beartrap, etc. + APerfectTimeToRewindIt, // Rewind time as Time Master + SerialKiller, // Kill everyone in FFA mode + PVPMaster, // Win in Solo PVP mode + HarderThanDrivingThroughTrafficLightsRight, // Win in Stop and Go mode + TwoFast4You, // Win in Speedrun mode + TooHotForMe, // Win in Hot Potato mode + SeekAndHide, // Win in Hide and Seek mode + Two012, // Win in Natural Disasters mode + YourFlagIsMine, // Win in Capture The Flag mode + BestReactionTime, // Win in Room Rush mode + KimJongUnExperience, // Vote for someone as Dictator + ImUnstoppable, // Get Medic's shield as Snitch + ThereCanOnlyBeOne, // Kill someone who had the same role as you + CantTouchThis, // As Detour, redirect at least 3 interactions + ItsMorbinTime, // Shapeshift into someone + WhatHaveIDone, // Kill your Lover, Romantic, Lawyer or Follower + BetrayalLevel100, // Kill Madmate as Impostor or Impostor as Madmate + TheRealTraitor, // Kill renegade as traitor (the neutral one) + DiePleaseDie, // Try to kill the same player with Medic's shield 2 times, both unsuccessfully + MasterDetective, // As Crewmate, vote only for Impostors and Neutral Killers + Superhero, // Kill all Impostors (including Renegade and Parasite) and Neutral Killers alone as Sheriff + TheKillingMachine2Point0, // Kill >= 7 players in the same game as Impostor/Neutral Killer + ALightInTheShadows, // Walk near the Beacon during the lights sabotage so they increase your vision + IForgotThisRoleExists, // Try to kill someone who is protected by Guardian Angel + TheLastSurvivor, // Win as a Crewmate, when you are the only alive player left + YouDidTheExactOppositeOfWhatYouWereSupposedToDo, // Die first as Maverick or Opportunist + WheresTheBlueShell, // Finish second in Room Rush + InnocentKiller, // Kill someone without having a kill button + YouWontTellAnyone, // Silence Snitch with all tasks done as Silencer + ExpertControl, // As Spurt, avoid reaching 0% and 100% speed for a round (If it was only to avoid 0 then they could just afk. Having to avoid both forces them to keep moving) + YoureMyFriendNow, // Recruit anyone as Gangster/Jackal/Infection/Virus/Cultist + ThatWasClose, // As a killer, kill Snitch with all their tasks done (or when Snitch is revealed) + CrewHero, // As Snitch, complete all your tasks, eject all Impostors and Neutral Killers, be alive until the end and win with crewmates + Transformer, // Complete at least 3 transformations in the same game (transformation is changing roles. For example Amnesiac => Sheriff => Sidekick) + InnocentImpostor, // Win as Impostor, not making any kill + ImpostorGang, // Win as Impostor with all your partners alive + FuriousAvenger, // Find yourself in a situation when Butcher kills Avenger and everyone dies + BrainStorm, // Get rid of all killers as a Crewmate and win with crewmates in less than 3 meetings (there must be at least 2 killers at the start of the game) + NothingCanStopLove, // Win as Lover/Loving Impostor with your partner alone (not with other teams) + SimonSays, // Complete the Simon's instruction + Unsuspected, // Win a game as a killer, not being voted by anyone in all meetings + YoureTooLate, // As Pestilence, have a killer try to kill you, but unsuccessfully for them + Armageddon, // Survive the Tremor's doom + GetDownMrPresident, // Protect the President as the Bodyguard + EconomicCompetition, // Kill Merchant as Bargainer, or kill Bargainer as Merchant + Gotcha, // Kill Bait and then get ejected + Speedrun, // Have a game end in under 1 minute + Carried, // Win as a madmate while the other impostors are dead + SorryToBurstYourBubble, // Explode 5 people with 1 encased player as the Bubble + GetLynched, // Successfully guess 3 roles as lyncher + FlagMaster, // Carry the flag the longest in CTF + Tag, // Tag the most players in CTF + Vectory, // Vent 50 times in one game + Bloodbath, // Kill 8 people in the same game as Killing Machine + AntiSaboteur, // Fix all types of sabotages (Reactor, o2, Lights, Comms) as Technician (not necessarily in the same game) + TheBestInSchool, // Be the first who answer the Mathematician's question correctly + Awww, // Drag Penguin as Goose (this achievement is gain after the game end) + Ohhh, // Drag Goose as Penguin (this achievement is gain after the game end) + Hypnosis, // Try to report a dead body during the Hypnotist's ability + Mimicry, // Change your appearance at least 3 times as a Doppelgänger + P2W, // Buy all items as Bargainer and win in the same game + LuckOrObservation, // Guess 1 player correctly + BeginnerGuesser, // Guess 2 players correctly in the same game + GuessMaster, // Guess 4 players correctly in the same game + BestGuesserAward, // Guess 6 players correctly in the same game + BadLuckOrBadObservation, // Guess incorrectly and die + Duel, // Find yourself in a situation when you are 1vs1 against another killer + Delicious, // Get Donut Delivery's donut or a Chef's dish + Inquisition, // Kill Vampire/Witch/Warlock/Hex Master as Crusader + Censorship, // Silence 5 people in the same game as Silencer + LiarLiar, // As Lawyer, fail to protect your client that is crewmate, and then win with impostors somehow + IWishIReported, // Revive an Impostor/Converted Crewmate/Neutral Killer as Altruist + WellMeetAgainSomeSunnyDay, // Mark someone as Hookshot + AndForWhatDidICodeTheseCommandsForIfYouDontUseThemAtAll, // Don't use any command for an entire game + Prankster, // Shapeshift as Disco + WhatTheHell, // Type 666 in chat as Demon. Will others notice this? >:) + WhyJustWhy, // As Dictator, vote out the Jester + CommonEnemyNo1, // Win as any NK + CoordinatedAttack, // As Jester, Executioner or Innocent, win with any of the other 2 roles + ItsJustAPrankBro // As Bomber, Kill half the lobby in 1 bomb + } + + const string SaveFilePath = "./EHR_DATA/Achievements.json"; + + public static readonly HashSet WaitingAchievements = []; + public static HashSet CompletedAchievements = []; + + public static void Complete(this Type type) + { + if (!CompletedAchievements.Add(type)) return; + + if (GameStates.IsEnded) WaitingAchievements.Add(type); + else ShowAchievementCompletion(type); + + SaveAllData(); + } + + public static void CompleteAfterGameEnd(this Type type) + { + if (!CompletedAchievements.Add(type)) return; + WaitingAchievements.Add(type); + + SaveAllData(); + } + + public static void CompleteAfterDelay(this Type type, float delay) + { + if (!CompletedAchievements.Add(type)) return; + Main.Instance.StartCoroutine(CompleteAchievementAfterDelayAsync()); + SaveAllData(); + return; + + IEnumerator CompleteAchievementAfterDelayAsync() { - IdeasLiveOn, // As Undead, win with at least 3 Necromancers - HowCouldIBelieveThem, // Vote for Jester that was ejected - WhatKindOfGeniusCameUpWithThisRoleIdea, // Get Dad role - StabbingTheBack, // Kill a Crewmate as Backstabber - Honk, // Drag someone as Goose - Bruh, // Get vanilla Crewmate role - HowDoICraftThisAgain, // Craft something as Adventurer - ImCrewISwear, // Kill the last Impostor with all Neutral Killers dead - Covid20, // Infect 2 people as Virus - TheresThisGameMyDadTaughtMeItsCalledSwitch, // Swap as or be Swapped by Shifter at least 3 times in one round - FirstDayOnTheJob, // As an Amnesiac, remember that you were a Sidekick or Deathknight - UnderNewManagement, // As an Amnesiac, remember that you were a Cultist or Virus - OutOfTime, // Suicide or lose due to a time limit - TooCold, // Get frozen by Freezer, Beartrap, etc. - APerfectTimeToRewindIt, // Rewind time as Time Master - SerialKiller, // Kill everyone in FFA mode - PVPMaster, // Win in Solo PVP mode - HarderThanDrivingThroughTrafficLightsRight, // Win in Stop and Go mode - TwoFast4You, // Win in Speedrun mode - TooHotForMe, // Win in Hot Potato mode - SeekAndHide, // Win in Hide and Seek mode - Two012, // Win in Natural Disasters mode - YourFlagIsMine, // Win in Capture The Flag mode - BestReactionTime, // Win in Room Rush mode - KimJongUnExperience, // Vote for someone as Dictator - ImUnstoppable, // Get Medic's shield as Snitch - ThereCanOnlyBeOne, // Kill someone who had the same role as you - CantTouchThis, // As Detour, redirect at least 3 interactions - ItsMorbinTime, // Shapeshift into someone - WhatHaveIDone, // Kill your Lover, Romantic, Lawyer or Follower - BetrayalLevel100, // Kill Madmate as Impostor or Impostor as Madmate - TheRealTraitor, // Kill renegade as traitor (the neutral one) - DiePleaseDie, // Try to kill the same player with Medic's shield 2 times, both unsuccessfully - MasterDetective, // As Crewmate, vote only for Impostors and Neutral Killers - Superhero, // Kill all Impostors (including Renegade and Parasite) and Neutral Killers alone as Sheriff - TheKillingMachine2Point0, // Kill >= 7 players in the same game as Impostor/Neutral Killer - ALightInTheShadows, // Walk near the Beacon during the lights sabotage so they increase your vision - IForgotThisRoleExists, // Try to kill someone who is protected by Guardian Angel - TheLastSurvivor, // Win as a Crewmate, when you are the only alive player left - YouDidTheExactOppositeOfWhatYouWereSupposedToDo, // Die first as Maverick or Opportunist - WheresTheBlueShell, // Finish second in Room Rush - InnocentKiller, // Kill someone without having a kill button - YouWontTellAnyone, // Silence Snitch with all tasks done as Silencer - ExpertControl, // As Spurt, avoid reaching 0% and 100% speed for a round (If it was only to avoid 0 then they could just afk. Having to avoid both forces them to keep moving) - YoureMyFriendNow, // Recruit anyone as Gangster/Jackal/Infection/Virus/Cultist - ThatWasClose, // As a killer, kill Snitch with all their tasks done (or when Snitch is revealed) - CrewHero, // As Snitch, complete all your tasks, eject all Impostors and Neutral Killers, be alive until the end and win with crewmates - Transformer, // Complete at least 3 transformations in the same game (transformation is changing roles. For example Amnesiac => Sheriff => Sidekick) - InnocentImpostor, // Win as Impostor, not making any kill - ImpostorGang, // Win as Impostor with all your partners alive - FuriousAvenger, // Find yourself in a situation when Butcher kills Avenger and everyone dies - BrainStorm, // Get rid of all killers as a Crewmate and win with crewmates in less than 3 meetings (there must be at least 2 killers at the start of the game) - NothingCanStopLove, // Win as Lover/Loving Impostor with your partner alone (not with other teams) - SimonSays, // Complete the Simon's instruction - Unsuspected, // Win a game as a killer, not being voted by anyone in all meetings - YoureTooLate, // As Pestilence, have a killer try to kill you, but unsuccessfully for them - Armageddon, // Survive the Tremor's doom - GetDownMrPresident, // Protect the President as the Bodyguard - EconomicCompetition, // Kill Merchant as Bargainer, or kill Bargainer as Merchant - Gotcha, // Kill Bait and then get ejected - Speedrun, // Have a game end in under 1 minute - Carried, // Win as a madmate while the other impostors are dead - SorryToBurstYourBubble, // Explode 5 people with 1 encased player as the Bubble - GetLynched, // Successfully guess 3 roles as lyncher - FlagMaster, // Carry the flag the longest in CTF - Tag, // Tag the most players in CTF - Vectory, // Vent 50 times in one game - Bloodbath, // Kill 8 people in the same game as Killing Machine - AntiSaboteur, // Fix all types of sabotages (Reactor, o2, Lights, Comms) as Technician (not necessarily in the same game) - TheBestInSchool, // Be the first who answer the Mathematician's question correctly - Awww, // Drag Penguin as Goose (this achievement is gain after the game end) - Ohhh, // Drag Goose as Penguin (this achievement is gain after the game end) - Hypnosis, // Try to report a dead body during the Hypnotist's ability - Mimicry, // Change your appearance at least 3 times as a Doppelgänger - P2W, // Buy all items as Bargainer and win in the same game - LuckOrObservation, // Guess 1 player correctly - BeginnerGuesser, // Guess 2 players correctly in the same game - GuessMaster, // Guess 4 players correctly in the same game - BestGuesserAward, // Guess 6 players correctly in the same game - BadLuckOrBadObservation, // Guess incorrectly and die - Duel, // Find yourself in a situation when you are 1vs1 against another killer - Delicious, // Get Donut Delivery's donut or a Chef's dish - Inquisition, // Kill Vampire/Witch/Warlock/Hex Master as Crusader - Censorship, // Silence 5 people in the same game as Silencer - LiarLiar, // As Lawyer, fail to protect your client that is crewmate, and then win with impostors somehow - IWishIReported, // Revive an Impostor/Converted Crewmate/Neutral Killer as Altruist - WellMeetAgainSomeSunnyDay, // Mark someone as Hookshot - AndForWhatDidICodeTheseCommandsForIfYouDontUseThemAtAll, // Don't use any command for an entire game - Prankster, // Shapeshift as Disco - WhatTheHell, // Type 666 in chat as Demon. Will others notice this? >:) - WhyJustWhy, // As Dictator, vote out the Jester - CommonEnemyNo1, // Win as any NK - CoordinatedAttack, // As Jester, Executioner or Innocent, win with any of the other 2 roles - ItsJustAPrankBro // As Bomber, Kill half the lobby in 1 bomb - } + float timer = 0f; - const string SaveFilePath = "./EHR_DATA/Achievements.json"; - - public static readonly HashSet WaitingAchievements = []; - public static HashSet CompletedAchievements = []; - - public static void Complete(this Type type) - { - if (!CompletedAchievements.Add(type)) return; - - if (GameStates.IsEnded) WaitingAchievements.Add(type); - else ShowAchievementCompletion(type); - - SaveAllData(); - } - - public static void CompleteAfterGameEnd(this Type type) - { - if (!CompletedAchievements.Add(type)) return; - WaitingAchievements.Add(type); - - SaveAllData(); - } - - public static void CompleteAfterDelay(this Type type, float delay) - { - if (!CompletedAchievements.Add(type)) return; - Main.Instance.StartCoroutine(CompleteAchievementAfterDelayAsync()); - SaveAllData(); - return; - - IEnumerator CompleteAchievementAfterDelayAsync() + while (!GameStates.IsEnded && timer < delay) { - float timer = 0f; - - while (!GameStates.IsEnded && timer < delay) - { - yield return null; - timer += Time.deltaTime; - } - - if (GameStates.IsEnded) - { - WaitingAchievements.Add(type); - yield break; - } - - ShowAchievementCompletion(type); + yield return null; + timer += Time.deltaTime; } - } - public static void ShowWaitingAchievements() - { - WaitingAchievements.Do(ShowAchievementCompletion); - WaitingAchievements.Clear(); - } + if (GameStates.IsEnded) + { + WaitingAchievements.Add(type); + yield break; + } - private static void ShowAchievementCompletion(Type type) - { - var title = Translator.GetString("AchievementCompletedTitle"); - var description = Translator.GetString($"Achievement.{type}.Description"); - var message = $"{Translator.GetString($"Achievement.{type}")}\n{description}"; - - ChatBubbleShower.ShowChatBubbleInRound(message, title); - } - - private static void SaveAllData() - { - var json = JsonSerializer.Serialize(CompletedAchievements); - File.WriteAllText(SaveFilePath, json); - } - - public static void LoadAllData() - { - if (!File.Exists(SaveFilePath)) return; - - var json = File.ReadAllText(SaveFilePath); - CompletedAchievements = JsonSerializer.Deserialize>(json); + ShowAchievementCompletion(type); } } + + public static void ShowWaitingAchievements() + { + WaitingAchievements.Do(ShowAchievementCompletion); + WaitingAchievements.Clear(); + } + + private static void ShowAchievementCompletion(Type type) + { + var title = Translator.GetString("AchievementCompletedTitle"); + var description = Translator.GetString($"Achievement.{type}.Description"); + var message = $"{Translator.GetString($"Achievement.{type}")}\n{description}"; + + ChatBubbleShower.ShowChatBubbleInRound(message, title); + } + + private static void SaveAllData() + { + var json = JsonSerializer.Serialize(CompletedAchievements); + File.WriteAllText(SaveFilePath, json); + } + + public static void LoadAllData() + { + if (!File.Exists(SaveFilePath)) return; + + var json = File.ReadAllText(SaveFilePath); + CompletedAchievements = JsonSerializer.Deserialize>(json); + } } \ No newline at end of file diff --git a/Modules/AddSteamID.cs b/Modules/AddSteamID.cs index ca77c7bb7..35adf726d 100644 --- a/Modules/AddSteamID.cs +++ b/Modules/AddSteamID.cs @@ -1,29 +1,28 @@ using System; using static EHR.Translator; -namespace EHR +namespace EHR; + +public static class AddSteamID { - public static class AddSteamID + private const string FilePath = "./steam_appid.txt"; + + public static void AddSteamAppIdFile() { - private const string FilePath = "./steam_appid.txt"; - - public static void AddSteamAppIdFile() + try { - try + if (!File.Exists(FilePath)) { - if (!File.Exists(FilePath)) - { - Logger.Warn("Creating a new steam_appid.txt file", "AddSteamID"); - File.Create(FilePath).Close(); - File.WriteAllText(FilePath, "945360"); + Logger.Warn("Creating a new steam_appid.txt file", "AddSteamID"); + File.Create(FilePath).Close(); + File.WriteAllText(FilePath, "945360"); - ModUpdater.ShowPopup(GetString("AppIDAdded"), StringNames.Close, true); - } - } - catch (Exception e) - { - Utils.ThrowException(e); + ModUpdater.ShowPopup(GetString("AppIDAdded"), StringNames.Close, true); } } + catch (Exception e) + { + Utils.ThrowException(e); + } } } \ No newline at end of file diff --git a/Modules/AntiBlackout.cs b/Modules/AntiBlackout.cs index def3c16c8..7ce1ce707 100644 --- a/Modules/AntiBlackout.cs +++ b/Modules/AntiBlackout.cs @@ -5,14 +5,14 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR +namespace EHR; + +public static class AntiBlackout { - public static class AntiBlackout - { - public static int ExilePlayerId = -1; - public static bool SkipTasks; - private static Dictionary IsDeadCache = []; - private static readonly LogHandler Logger = EHR.Logger.Handler("AntiBlackout"); + public static int ExilePlayerId = -1; + public static bool SkipTasks; + private static Dictionary IsDeadCache = []; + private static readonly LogHandler Logger = EHR.Logger.Handler("AntiBlackout"); /* public static bool CheckBlackOut() @@ -48,196 +48,195 @@ namespace EHR } */ - private static bool IsCached { get; set; } + private static bool IsCached { get; set; } - public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + public static void SetIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + { + SkipTasks = true; + RevivePlayersAndSetDummyImp(); + Logger.Info($"SetIsDead is called from {callerMethodName}"); + if (IsCached) return; + + IsDeadCache.Clear(); + + foreach (NetworkedPlayerInfo info in GameData.Instance.AllPlayers) { - SkipTasks = true; - RevivePlayersAndSetDummyImp(); - Logger.Info($"SetIsDead is called from {callerMethodName}"); - if (IsCached) return; + if (info == null) continue; - IsDeadCache.Clear(); - - foreach (NetworkedPlayerInfo info in GameData.Instance.AllPlayers) - { - if (info == null) continue; - - IsDeadCache[info.PlayerId] = (info.IsDead, info.Disconnected); - info.IsDead = false; - info.Disconnected = false; - } - - IsCached = true; - if (doSend) SendGameData(); + IsDeadCache[info.PlayerId] = (info.IsDead, info.Disconnected); + info.IsDead = false; + info.Disconnected = false; } - private static void RevivePlayersAndSetDummyImp() + IsCached = true; + if (doSend) SendGameData(); + } + + private static void RevivePlayersAndSetDummyImp() + { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + + PlayerControl dummyImp = Main.AllAlivePlayerControls.First(x => x.PlayerId != ExilePlayerId); + + foreach (PlayerControl seer in Main.AllPlayerControls) { - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + if (seer.IsHost() || seer.IsModClient()) continue; - PlayerControl dummyImp = Main.AllAlivePlayerControls.First(x => x.PlayerId != ExilePlayerId); - - foreach (PlayerControl seer in Main.AllPlayerControls) + foreach (PlayerControl target in Main.AllPlayerControls) { - if (seer.IsHost() || seer.IsModClient()) continue; + RoleTypes targetRoleType = target.PlayerId == dummyImp.PlayerId ? RoleTypes.Impostor : RoleTypes.Crewmate; + target.RpcSetRoleDesync(targetRoleType, seer.GetClientId()); + } + } + } - foreach (PlayerControl target in Main.AllPlayerControls) - { - RoleTypes targetRoleType = target.PlayerId == dummyImp.PlayerId ? RoleTypes.Impostor : RoleTypes.Crewmate; - target.RpcSetRoleDesync(targetRoleType, seer.GetClientId()); - } + public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + { + Logger.Info($"RestoreIsDead is called from {callerMethodName}"); + + foreach (NetworkedPlayerInfo info in GameData.Instance.AllPlayers) + { + if (info == null) continue; + + if (IsDeadCache.TryGetValue(info.PlayerId, out (bool IsDead, bool Disconnected) val)) + { + info.IsDead = val.IsDead; + info.Disconnected = val.Disconnected; } } - public static void RestoreIsDead(bool doSend = true, [CallerMemberName] string callerMethodName = "") + IsDeadCache.Clear(); + IsCached = false; + + if (doSend) { - Logger.Info($"RestoreIsDead is called from {callerMethodName}"); - - foreach (NetworkedPlayerInfo info in GameData.Instance.AllPlayers) - { - if (info == null) continue; - - if (IsDeadCache.TryGetValue(info.PlayerId, out (bool IsDead, bool Disconnected) val)) - { - info.IsDead = val.IsDead; - info.Disconnected = val.Disconnected; - } - } - - IsDeadCache.Clear(); - IsCached = false; - - if (doSend) - { - SendGameData(); - LateTask.New(RestoreIsDeadByExile, 0.3f, "AntiBlackOut.RestoreIsDeadByExile"); - } + SendGameData(); + LateTask.New(RestoreIsDeadByExile, 0.3f, "AntiBlackOut.RestoreIsDeadByExile"); } + } - private static void RestoreIsDeadByExile() + private static void RestoreIsDeadByExile() + { + var sender = CustomRpcSender.Create("AntiBlackout.RestoreIsDeadByExile", SendOption.Reliable); + Main.AllPlayerControls.DoIf(x => x.Data.IsDead && !x.Data.Disconnected, x => sender.AutoStartRpc(x.NetId, (byte)RpcCalls.Exiled).EndRpc()); + sender.SendMessage(); + } + + public static void SendGameData([CallerMemberName] string callerMethodName = "") + { + Logger.Info($"SendGameData is called from {callerMethodName}"); + + foreach (NetworkedPlayerInfo playerinfo in GameData.Instance.AllPlayers) { - var sender = CustomRpcSender.Create("AntiBlackout.RestoreIsDeadByExile", SendOption.Reliable); - Main.AllPlayerControls.DoIf(x => x.Data.IsDead && !x.Data.Disconnected, x => sender.AutoStartRpc(x.NetId, (byte)RpcCalls.Exiled).EndRpc()); - sender.SendMessage(); - } + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + writer.StartMessage(5); //0x05 GameData - public static void SendGameData([CallerMemberName] string callerMethodName = "") - { - Logger.Info($"SendGameData is called from {callerMethodName}"); - - foreach (NetworkedPlayerInfo playerinfo in GameData.Instance.AllPlayers) { - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(5); //0x05 GameData + writer.Write(AmongUsClient.Instance.GameId); + writer.StartMessage(1); //0x01 Data { - writer.Write(AmongUsClient.Instance.GameId); - writer.StartMessage(1); //0x01 Data - - { - writer.WritePacked(playerinfo.NetId); - playerinfo.Serialize(writer, true); - } - - writer.EndMessage(); + writer.WritePacked(playerinfo.NetId); + playerinfo.Serialize(writer, true); } writer.EndMessage(); - - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); } - } - public static void OnDisconnect(NetworkedPlayerInfo player) - { - // Execution conditions: Client is the host, IsDead is overridden, player is already disconnected - if (!AmongUsClient.Instance.AmHost || !IsCached || !player.Disconnected) return; + writer.EndMessage(); - IsDeadCache[player.PlayerId] = (true, true); - RevivePlayersAndSetDummyImp(); - player.IsDead = player.Disconnected = false; - SendGameData(); - } - - public static void SetRealPlayerRoles() - { - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - - foreach (((byte seerId, byte targetId), (RoleTypes roletype, _)) in StartGameHostPatch.RpcSetRoleReplacer.RoleMap) - { - if (seerId == 0) continue; // Skip the host - - PlayerControl seer = Utils.GetPlayerById(seerId); - PlayerControl target = Utils.GetPlayerById(targetId); - - if (seer == null || target == null) continue; - - if (seer.IsModClient()) continue; - - bool self = seerId == targetId; - RoleTypes changedRoleType = roletype; - - if (target.Data.IsDead) - { - if (self) - { - target.RpcExile(); - - if (target.GetCustomRole().IsGhostRole() || target.HasGhostRole()) - changedRoleType = RoleTypes.GuardianAngel; - else if (target.Is(Team.Impostor) || target.HasDesyncRole()) - changedRoleType = RoleTypes.ImpostorGhost; - else - changedRoleType = RoleTypes.CrewmateGhost; - } - else - { - bool seerIsKiller = seer.Is(Team.Impostor) || seer.HasDesyncRole(); - - if (!seerIsKiller && target.Is(Team.Impostor)) - changedRoleType = RoleTypes.ImpostorGhost; - else - changedRoleType = RoleTypes.CrewmateGhost; - } - } - - target.RpcSetRoleDesync(changedRoleType, seer.GetClientId()); - } - } - - private static void ResetAllCooldowns() - { - foreach (PlayerControl seer in Main.AllPlayerControls) - { - if (seer.IsAlive()) - { - seer.RpcResetAbilityCooldown(); - seer.ResetKillCooldown(); - - if (Main.AllPlayerKillCooldown.TryGetValue(seer.PlayerId, out float kcd) && kcd >= 2f) - seer.SetKillCooldown(kcd - 2f); - } - else if (seer.HasGhostRole()) seer.RpcResetAbilityCooldown(); - } - } - - public static void ResetAfterMeeting() - { - SkipTasks = false; - ExilePlayerId = -1; - ResetAllCooldowns(); - } - - public static void Reset() - { - Logger.Info("==Reset=="); - IsDeadCache ??= []; - IsDeadCache.Clear(); - IsCached = false; - ExilePlayerId = -1; - SkipTasks = false; + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); } } + + public static void OnDisconnect(NetworkedPlayerInfo player) + { + // Execution conditions: Client is the host, IsDead is overridden, player is already disconnected + if (!AmongUsClient.Instance.AmHost || !IsCached || !player.Disconnected) return; + + IsDeadCache[player.PlayerId] = (true, true); + RevivePlayersAndSetDummyImp(); + player.IsDead = player.Disconnected = false; + SendGameData(); + } + + public static void SetRealPlayerRoles() + { + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + + foreach (((byte seerId, byte targetId), (RoleTypes roletype, _)) in StartGameHostPatch.RpcSetRoleReplacer.RoleMap) + { + if (seerId == 0) continue; // Skip the host + + PlayerControl seer = Utils.GetPlayerById(seerId); + PlayerControl target = Utils.GetPlayerById(targetId); + + if (seer == null || target == null) continue; + + if (seer.IsModClient()) continue; + + bool self = seerId == targetId; + RoleTypes changedRoleType = roletype; + + if (target.Data.IsDead) + { + if (self) + { + target.RpcExile(); + + if (target.GetCustomRole().IsGhostRole() || target.HasGhostRole()) + changedRoleType = RoleTypes.GuardianAngel; + else if (target.Is(Team.Impostor) || target.HasDesyncRole()) + changedRoleType = RoleTypes.ImpostorGhost; + else + changedRoleType = RoleTypes.CrewmateGhost; + } + else + { + bool seerIsKiller = seer.Is(Team.Impostor) || seer.HasDesyncRole(); + + if (!seerIsKiller && target.Is(Team.Impostor)) + changedRoleType = RoleTypes.ImpostorGhost; + else + changedRoleType = RoleTypes.CrewmateGhost; + } + } + + target.RpcSetRoleDesync(changedRoleType, seer.GetClientId()); + } + } + + private static void ResetAllCooldowns() + { + foreach (PlayerControl seer in Main.AllPlayerControls) + { + if (seer.IsAlive()) + { + seer.RpcResetAbilityCooldown(); + seer.ResetKillCooldown(); + + if (Main.AllPlayerKillCooldown.TryGetValue(seer.PlayerId, out float kcd) && kcd >= 2f) + seer.SetKillCooldown(kcd - 2f); + } + else if (seer.HasGhostRole()) seer.RpcResetAbilityCooldown(); + } + } + + public static void ResetAfterMeeting() + { + SkipTasks = false; + ExilePlayerId = -1; + ResetAllCooldowns(); + } + + public static void Reset() + { + Logger.Info("==Reset=="); + IsDeadCache ??= []; + IsDeadCache.Clear(); + IsCached = false; + ExilePlayerId = -1; + SkipTasks = false; + } } \ No newline at end of file diff --git a/Modules/BanManager.cs b/Modules/BanManager.cs index 4c53b11e2..1e9e286c6 100644 --- a/Modules/BanManager.cs +++ b/Modules/BanManager.cs @@ -10,266 +10,265 @@ using HarmonyLib; using InnerNet; using static EHR.Translator; -namespace EHR +namespace EHR; + +public static class BanManager { - public static class BanManager + private const string DenyNameListPath = "./EHR_DATA/DenyName.txt"; + private const string BanListPath = "./EHR_DATA/BanList.txt"; + private const string ModeratorListPath = "./EHR_DATA/Moderators.txt"; + private const string WhiteListListPath = "./EHR_DATA/WhiteList.txt"; + private static readonly List EACList = []; + public static readonly List TempBanWhiteList = []; // To prevent writing to the banlist + + public static void Init() { - private const string DenyNameListPath = "./EHR_DATA/DenyName.txt"; - private const string BanListPath = "./EHR_DATA/BanList.txt"; - private const string ModeratorListPath = "./EHR_DATA/Moderators.txt"; - private const string WhiteListListPath = "./EHR_DATA/WhiteList.txt"; - private static readonly List EACList = []; - public static readonly List TempBanWhiteList = []; // To prevent writing to the banlist - - public static void Init() + try { - try + Directory.CreateDirectory("EHR_DATA"); + + if (!File.Exists(BanListPath)) { - Directory.CreateDirectory("EHR_DATA"); - - if (!File.Exists(BanListPath)) - { - Logger.Warn("Create a new BanList.txt file", "BanManager"); - File.Create(BanListPath).Close(); - } - - if (!File.Exists(DenyNameListPath)) - { - Logger.Warn("Create a new DenyName.txt file", "BanManager"); - File.Create(DenyNameListPath).Close(); - File.WriteAllText(DenyNameListPath, GetResourcesTxt("EHR.Resources.Config.DenyName.txt")); - } - - if (!File.Exists(ModeratorListPath)) - { - Logger.Warn("Creating a new Moderators.txt file", "BanManager"); - File.Create(ModeratorListPath).Close(); - } - - if (!File.Exists(WhiteListListPath)) - { - Logger.Warn("Creating a new WhiteList.txt file", "BanManager"); - File.Create(WhiteListListPath).Close(); - } - - // Read EAC List - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("EHR.Resources.Config.EACList.txt")!; - stream.Position = 0; - using StreamReader sr = new(stream, Encoding.UTF8); - - while (sr.ReadLine() is { } line) - { - if (line == "" || line.StartsWith("#")) continue; - - EACList.Add(line); - } + Logger.Warn("Create a new BanList.txt file", "BanManager"); + File.Create(BanListPath).Close(); } - catch (Exception ex) + + if (!File.Exists(DenyNameListPath)) { - Logger.Exception(ex, "BanManager"); + Logger.Warn("Create a new DenyName.txt file", "BanManager"); + File.Create(DenyNameListPath).Close(); + File.WriteAllText(DenyNameListPath, GetResourcesTxt("EHR.Resources.Config.DenyName.txt")); } - } - private static string GetResourcesTxt(string path) - { - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)!; + if (!File.Exists(ModeratorListPath)) + { + Logger.Warn("Creating a new Moderators.txt file", "BanManager"); + File.Create(ModeratorListPath).Close(); + } + + if (!File.Exists(WhiteListListPath)) + { + Logger.Warn("Creating a new WhiteList.txt file", "BanManager"); + File.Create(WhiteListListPath).Close(); + } + + // Read EAC List + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("EHR.Resources.Config.EACList.txt")!; stream.Position = 0; - using StreamReader reader = new(stream, Encoding.UTF8); - return reader.ReadToEnd(); - } + using StreamReader sr = new(stream, Encoding.UTF8); - public static string GetHashedPuid(this ClientData player) - { - if (player == null) return string.Empty; - - string puid = player.ProductUserId; - using var sha256 = SHA256.Create(); - // get sha-256 hash - byte[] sha256Bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(puid)); - string sha256Hash = BitConverter.ToString(sha256Bytes).Replace("-", "").ToLower(); - - // pick front 5 and last 4 - return string.Concat(sha256Hash.AsSpan(0, 5), sha256Hash.AsSpan(sha256Hash.Length - 4)); - } - - public static void AddBanPlayer(ClientData player) - { - if (!AmongUsClient.Instance.AmHost || player == null) return; - - string friendCode = player.FriendCode.Replace(':', '#'); - - if (!CheckBanList(friendCode, player.GetHashedPuid()) && !TempBanWhiteList.Contains(player.GetHashedPuid())) + while (sr.ReadLine() is { } line) { - if (player.GetHashedPuid() != "" && player.GetHashedPuid() != null && player.GetHashedPuid() != "e3b0cb855") + if (line == "" || line.StartsWith("#")) continue; + + EACList.Add(line); + } + } + catch (Exception ex) + { + Logger.Exception(ex, "BanManager"); + } + } + + private static string GetResourcesTxt(string path) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)!; + stream.Position = 0; + using StreamReader reader = new(stream, Encoding.UTF8); + return reader.ReadToEnd(); + } + + public static string GetHashedPuid(this ClientData player) + { + if (player == null) return string.Empty; + + string puid = player.ProductUserId; + using var sha256 = SHA256.Create(); + // get sha-256 hash + byte[] sha256Bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(puid)); + string sha256Hash = BitConverter.ToString(sha256Bytes).Replace("-", "").ToLower(); + + // pick front 5 and last 4 + return string.Concat(sha256Hash.AsSpan(0, 5), sha256Hash.AsSpan(sha256Hash.Length - 4)); + } + + public static void AddBanPlayer(ClientData player) + { + if (!AmongUsClient.Instance.AmHost || player == null) return; + + string friendCode = player.FriendCode.Replace(':', '#'); + + if (!CheckBanList(friendCode, player.GetHashedPuid()) && !TempBanWhiteList.Contains(player.GetHashedPuid())) + { + if (player.GetHashedPuid() != "" && player.GetHashedPuid() != null && player.GetHashedPuid() != "e3b0cb855") + { + File.AppendAllText(BanListPath, $"{friendCode},{player.GetHashedPuid()},{player.PlayerName.RemoveHtmlTags()}\n"); + Logger.SendInGame(string.Format(GetString("Message.AddedPlayerToBanList"), player.PlayerName)); + } + else + Logger.Info($"Failed to add player {player.PlayerName.RemoveHtmlTags()}/{friendCode}/{player.GetHashedPuid()} to ban list!", "AddBanPlayer"); + } + } + + public static bool CheckDenyNamePlayer(PlayerControl player, string name) + { + if (!AmongUsClient.Instance.AmHost || !Options.ApplyDenyNameList.GetBool()) return false; + + try + { + Directory.CreateDirectory("EHR_DATA"); + if (!File.Exists(DenyNameListPath)) File.Create(DenyNameListPath).Close(); + + using StreamReader sr = new(DenyNameListPath); + + while (sr.ReadLine() is { } line) + { + if (line == "") continue; + + if (line.Contains("Amogus") || line.Contains("Amogus V") || Regex.IsMatch(name, line)) { - File.AppendAllText(BanListPath, $"{friendCode},{player.GetHashedPuid()},{player.PlayerName.RemoveHtmlTags()}\n"); - Logger.SendInGame(string.Format(GetString("Message.AddedPlayerToBanList"), player.PlayerName)); + AmongUsClient.Instance.KickPlayer(player.OwnerId, false); + Logger.SendInGame(string.Format(GetString("Message.KickedByDenyName"), name, line)); + Logger.Info($"{name} was kicked because their name matched \"{line}\".", "Kick"); + return true; } - else - Logger.Info($"Failed to add player {player.PlayerName.RemoveHtmlTags()}/{friendCode}/{player.GetHashedPuid()} to ban list!", "AddBanPlayer"); - } - } - - public static bool CheckDenyNamePlayer(PlayerControl player, string name) - { - if (!AmongUsClient.Instance.AmHost || !Options.ApplyDenyNameList.GetBool()) return false; - - try - { - Directory.CreateDirectory("EHR_DATA"); - if (!File.Exists(DenyNameListPath)) File.Create(DenyNameListPath).Close(); - - using StreamReader sr = new(DenyNameListPath); - - while (sr.ReadLine() is { } line) - { - if (line == "") continue; - - if (line.Contains("Amogus") || line.Contains("Amogus V") || Regex.IsMatch(name, line)) - { - AmongUsClient.Instance.KickPlayer(player.OwnerId, false); - Logger.SendInGame(string.Format(GetString("Message.KickedByDenyName"), name, line)); - Logger.Info($"{name} was kicked because their name matched \"{line}\".", "Kick"); - return true; - } - } - - return false; - } - catch (Exception ex) - { - Logger.Exception(ex, "CheckDenyNamePlayer"); - return true; - } - } - - public static void CheckBanPlayer(ClientData player) - { - if (!AmongUsClient.Instance.AmHost || !Options.ApplyBanList.GetBool() || player == null) return; - - string friendcode = player.FriendCode.Replace(':', '#'); - - if (friendcode.Length < 7) // #1234 is 5 chars, and it's impossible for a friend code to only have 3 - { - AmongUsClient.Instance.KickPlayer(player.Id, true); - Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); - Logger.Info($"{player.PlayerName} banned by EAC because their friend code is too short.", "EAC"); - return; - } - - if (friendcode.Count(c => c == '#') != 1) - { - // This is part of eac, so that's why it will say banned by EAC list. - AmongUsClient.Instance.KickPlayer(player.Id, true); - Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); - Logger.Info($"{player.PlayerName} EAC Banned because friendcode contains more than 1 #", "EAC"); - return; - } - - // Contains any non-word character or digits - const string pattern = @"[\W\d]"; - - if (Regex.IsMatch(friendcode[..friendcode.IndexOf("#", StringComparison.Ordinal)], pattern)) - { - AmongUsClient.Instance.KickPlayer(player.Id, true); - Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); - Logger.Info($"{player.PlayerName} was banned because of a spoofed friend code", "EAC"); - return; - } - - if (CheckBanList(friendcode, player.GetHashedPuid())) - { - AmongUsClient.Instance.KickPlayer(player.Id, true); - Logger.SendInGame(string.Format(GetString("Message.BanedByBanList"), player.PlayerName)); - Logger.Info($"{player.PlayerName} is banned because he has been banned in the past.", "BAN"); - return; - } - - if (CheckEACList(friendcode, player.GetHashedPuid())) - { - AmongUsClient.Instance.KickPlayer(player.Id, true); - Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); - Logger.Info($"{player.PlayerName} is on the EAC ban list", "BAN"); - return; - } - - if (TempBanWhiteList.Contains(player.GetHashedPuid())) - { - AmongUsClient.Instance.KickPlayer(player.Id, true); - Logger.Info($"{player.PlayerName} was in temp ban list", "BAN"); - } - } - - public static bool CheckBanList(string code, string hashedpuid = "") - { - code = code.Replace(':', '#'); - - var OnlyCheckPuid = false; - - switch (code) - { - case "" when hashedpuid != "": - OnlyCheckPuid = true; - break; - case "": - return false; - } - - try - { - Directory.CreateDirectory("EHR_DATA"); - if (!File.Exists(BanListPath)) File.Create(BanListPath).Close(); - - using StreamReader sr = new(BanListPath); - - while (sr.ReadLine() is { } line) - { - if (line == "") continue; - - if (!OnlyCheckPuid) - if (line.Contains(code)) - return true; - - if (line.Contains(hashedpuid)) return true; - } - } - catch (Exception ex) - { - Logger.Exception(ex, "CheckBanList"); } return false; } - - public static bool CheckEACList(string code, string hashedPuid) + catch (Exception ex) { - code = code.Replace(':', '#'); - - var OnlyCheckPuid = false; - - switch (code) - { - case "" when hashedPuid == "": - OnlyCheckPuid = true; - break; - case "": - return false; - } - - return EACList.Any(x => x.Contains(code) && !OnlyCheckPuid) || EACList.Any(x => x.Contains(hashedPuid) && hashedPuid != ""); + Logger.Exception(ex, "CheckDenyNamePlayer"); + return true; } } - [HarmonyPatch(typeof(BanMenu), nameof(BanMenu.Select))] - internal class BanMenuSelectPatch + public static void CheckBanPlayer(ClientData player) { - public static void Postfix(BanMenu __instance, int clientId) - { - ClientData recentClient = AmongUsClient.Instance.GetRecentClient(clientId); - if (recentClient == null) return; + if (!AmongUsClient.Instance.AmHost || !Options.ApplyBanList.GetBool() || player == null) return; - if (!BanManager.CheckBanList(recentClient.FriendCode, recentClient.GetHashedPuid())) - __instance.BanButton.GetComponent().SetEnabledColors(); + string friendcode = player.FriendCode.Replace(':', '#'); + + if (friendcode.Length < 7) // #1234 is 5 chars, and it's impossible for a friend code to only have 3 + { + AmongUsClient.Instance.KickPlayer(player.Id, true); + Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); + Logger.Info($"{player.PlayerName} banned by EAC because their friend code is too short.", "EAC"); + return; } + + if (friendcode.Count(c => c == '#') != 1) + { + // This is part of eac, so that's why it will say banned by EAC list. + AmongUsClient.Instance.KickPlayer(player.Id, true); + Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); + Logger.Info($"{player.PlayerName} EAC Banned because friendcode contains more than 1 #", "EAC"); + return; + } + + // Contains any non-word character or digits + const string pattern = @"[\W\d]"; + + if (Regex.IsMatch(friendcode[..friendcode.IndexOf("#", StringComparison.Ordinal)], pattern)) + { + AmongUsClient.Instance.KickPlayer(player.Id, true); + Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); + Logger.Info($"{player.PlayerName} was banned because of a spoofed friend code", "EAC"); + return; + } + + if (CheckBanList(friendcode, player.GetHashedPuid())) + { + AmongUsClient.Instance.KickPlayer(player.Id, true); + Logger.SendInGame(string.Format(GetString("Message.BanedByBanList"), player.PlayerName)); + Logger.Info($"{player.PlayerName} is banned because he has been banned in the past.", "BAN"); + return; + } + + if (CheckEACList(friendcode, player.GetHashedPuid())) + { + AmongUsClient.Instance.KickPlayer(player.Id, true); + Logger.SendInGame(string.Format(GetString("Message.BanedByEACList"), player.PlayerName)); + Logger.Info($"{player.PlayerName} is on the EAC ban list", "BAN"); + return; + } + + if (TempBanWhiteList.Contains(player.GetHashedPuid())) + { + AmongUsClient.Instance.KickPlayer(player.Id, true); + Logger.Info($"{player.PlayerName} was in temp ban list", "BAN"); + } + } + + public static bool CheckBanList(string code, string hashedpuid = "") + { + code = code.Replace(':', '#'); + + var OnlyCheckPuid = false; + + switch (code) + { + case "" when hashedpuid != "": + OnlyCheckPuid = true; + break; + case "": + return false; + } + + try + { + Directory.CreateDirectory("EHR_DATA"); + if (!File.Exists(BanListPath)) File.Create(BanListPath).Close(); + + using StreamReader sr = new(BanListPath); + + while (sr.ReadLine() is { } line) + { + if (line == "") continue; + + if (!OnlyCheckPuid) + if (line.Contains(code)) + return true; + + if (line.Contains(hashedpuid)) return true; + } + } + catch (Exception ex) + { + Logger.Exception(ex, "CheckBanList"); + } + + return false; + } + + public static bool CheckEACList(string code, string hashedPuid) + { + code = code.Replace(':', '#'); + + var OnlyCheckPuid = false; + + switch (code) + { + case "" when hashedPuid == "": + OnlyCheckPuid = true; + break; + case "": + return false; + } + + return EACList.Any(x => x.Contains(code) && !OnlyCheckPuid) || EACList.Any(x => x.Contains(hashedPuid) && hashedPuid != ""); + } +} + +[HarmonyPatch(typeof(BanMenu), nameof(BanMenu.Select))] +internal class BanMenuSelectPatch +{ + public static void Postfix(BanMenu __instance, int clientId) + { + ClientData recentClient = AmongUsClient.Instance.GetRecentClient(clientId); + if (recentClient == null) return; + + if (!BanManager.CheckBanList(recentClient.FriendCode, recentClient.GetHashedPuid())) + __instance.BanButton.GetComponent().SetEnabledColors(); } } \ No newline at end of file diff --git a/Modules/Camouflague.cs b/Modules/Camouflague.cs index e4683c700..9140b6a75 100644 --- a/Modules/Camouflague.cs +++ b/Modules/Camouflague.cs @@ -3,242 +3,241 @@ using AmongUs.Data; using EHR.Impostor; using EHR.Neutral; -namespace EHR +namespace EHR; + +internal static class PlayerOutfitExtension { - internal static class PlayerOutfitExtension + public static NetworkedPlayerInfo.PlayerOutfit Set(this NetworkedPlayerInfo.PlayerOutfit instance, string playerName, int colorId, string hatId, string skinId, string visorId, string petId, string nameplateId) { - public static NetworkedPlayerInfo.PlayerOutfit Set(this NetworkedPlayerInfo.PlayerOutfit instance, string playerName, int colorId, string hatId, string skinId, string visorId, string petId, string nameplateId) - { - instance.PlayerName = playerName; - instance.ColorId = colorId; - instance.HatId = hatId; - instance.SkinId = skinId; - instance.VisorId = visorId; - instance.PetId = petId; - instance.NamePlateId = nameplateId; - return instance; - } + instance.PlayerName = playerName; + instance.ColorId = colorId; + instance.HatId = hatId; + instance.SkinId = skinId; + instance.VisorId = visorId; + instance.PetId = petId; + instance.NamePlateId = nameplateId; + return instance; + } - public static bool Compare(this NetworkedPlayerInfo.PlayerOutfit instance, NetworkedPlayerInfo.PlayerOutfit targetOutfit) - { - return instance.ColorId == targetOutfit.ColorId && - instance.HatId == targetOutfit.HatId && - instance.SkinId == targetOutfit.SkinId && - instance.VisorId == targetOutfit.VisorId && - instance.PetId == targetOutfit.PetId; - } + public static bool Compare(this NetworkedPlayerInfo.PlayerOutfit instance, NetworkedPlayerInfo.PlayerOutfit targetOutfit) + { + return instance.ColorId == targetOutfit.ColorId && + instance.HatId == targetOutfit.HatId && + instance.SkinId == targetOutfit.SkinId && + instance.VisorId == targetOutfit.VisorId && + instance.PetId == targetOutfit.PetId; + } - public static string GetString(this NetworkedPlayerInfo.PlayerOutfit instance) + public static string GetString(this NetworkedPlayerInfo.PlayerOutfit instance) + { + return $"{instance.PlayerName} Color:{instance.ColorId} {instance.HatId} {instance.SkinId} {instance.VisorId} {instance.PetId}"; + } +} + +public static class Camouflage +{ + private static NetworkedPlayerInfo.PlayerOutfit CamouflageOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "", "", ""); // Default + + public static bool IsCamouflage; + public static bool BlockCamouflage; + public static Dictionary PlayerSkins = []; + public static List ResetSkinAfterDeathPlayers = []; + private static HashSet WaitingForSkinChange = []; + + public static void Init() + { + IsCamouflage = false; + PlayerSkins = []; + ResetSkinAfterDeathPlayers = []; + WaitingForSkinChange = []; + + SkippedCamoTimes = 0; + CamoTimesThisGame = 0; + CamoTimesThisRound = 0; + + CamouflageOutfit = Options.KPDCamouflageMode.GetValue() switch { - return $"{instance.PlayerName} Color:{instance.ColorId} {instance.HatId} {instance.SkinId} {instance.VisorId} {instance.PetId}"; + 0 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "", "", ""), // Default + 1 => new NetworkedPlayerInfo.PlayerOutfit().Set("", DataManager.Player.Customization.Color, DataManager.Player.Customization.Hat, DataManager.Player.Customization.Skin, DataManager.Player.Customization.Visor, DataManager.Player.Customization.Pet, ""), // Host + 2 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 13, "hat_pk05_Plant", "", "visor_BubbleBumVisor", "", ""), // Karpe + 7 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 7, "hat_pk04_Snowman", "", "", "", ""), // Gurge44 + 8 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 17, "hat_baseball_Black", "skin_Scientist-Darkskin", "visor_pusheenSmileVisor", "pet_Pip", ""), // TommyXL + _ => CamouflageOutfit + }; + + SetPetForOutfitIfNecessary(CamouflageOutfit); + } + + public static void SetPetForOutfitIfNecessary(NetworkedPlayerInfo.PlayerOutfit outfit) + { + if (Options.UsePets.GetBool() && outfit.PetId == "") + { + outfit.PetId = PetsPatch.GetPetId(); } } - public static class Camouflage + private static int SkippedCamoTimes; + private static int CamoTimesThisGame; + public static int CamoTimesThisRound; + + private static bool ShouldCamouflage() { - private static NetworkedPlayerInfo.PlayerOutfit CamouflageOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "", "", ""); // Default + if (Camouflager.On && Camouflager.IsActive) return true; + + if (Main.CurrentMap == MapNames.Fungle && Options.CommsCamouflageDisableOnFungle.GetBool()) return false; - public static bool IsCamouflage; - public static bool BlockCamouflage; - public static Dictionary PlayerSkins = []; - public static List ResetSkinAfterDeathPlayers = []; - private static HashSet WaitingForSkinChange = []; - - public static void Init() + if (Utils.IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool()) { - IsCamouflage = false; - PlayerSkins = []; - ResetSkinAfterDeathPlayers = []; + if (Options.CommsCamouflageLimitSetChance.GetBool() && IRandom.Instance.Next(100) < Options.CommsCamouflageLimitChance.GetInt()) return false; + if (Options.CommsCamouflageLimitSetFrequency.GetBool() && ++SkippedCamoTimes < Options.CommsCamouflageLimitFrequency.GetInt()) return false; + + if (Options.CommsCamouflageLimitSetMaxTimes.GetBool()) + { + if (CamoTimesThisGame++ >= Options.CommsCamouflageLimitMaxTimesPerGame.GetInt()) return false; + if (CamoTimesThisRound++ >= Options.CommsCamouflageLimitMaxTimesPerRound.GetInt()) return false; + } + + return true; + } + + return false; + } + + public static bool CheckCamouflage() + { + if (!AmongUsClient.Instance.AmHost || (!Options.CommsCamouflage.GetBool() && !Camouflager.On)) return false; + + bool oldIsCamouflage = IsCamouflage; + + IsCamouflage = ShouldCamouflage(); + + if (oldIsCamouflage != IsCamouflage) + { + Logger.Info($"IsCamouflage: {IsCamouflage}", "CheckCamouflage"); + WaitingForSkinChange = []; - SkippedCamoTimes = 0; - CamoTimesThisGame = 0; - CamoTimesThisRound = 0; - - CamouflageOutfit = Options.KPDCamouflageMode.GetValue() switch + foreach (PlayerControl pc in Main.AllPlayerControls) { - 0 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "", "", ""), // Default - 1 => new NetworkedPlayerInfo.PlayerOutfit().Set("", DataManager.Player.Customization.Color, DataManager.Player.Customization.Hat, DataManager.Player.Customization.Skin, DataManager.Player.Customization.Visor, DataManager.Player.Customization.Pet, ""), // Host - 2 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 13, "hat_pk05_Plant", "", "visor_BubbleBumVisor", "", ""), // Karpe - 7 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 7, "hat_pk04_Snowman", "", "", "", ""), // Gurge44 - 8 => new NetworkedPlayerInfo.PlayerOutfit().Set("", 17, "hat_baseball_Black", "skin_Scientist-Darkskin", "visor_pusheenSmileVisor", "pet_Pip", ""), // TommyXL - _ => CamouflageOutfit - }; - - SetPetForOutfitIfNecessary(CamouflageOutfit); - } - - public static void SetPetForOutfitIfNecessary(NetworkedPlayerInfo.PlayerOutfit outfit) - { - if (Options.UsePets.GetBool() && outfit.PetId == "") - { - outfit.PetId = PetsPatch.GetPetId(); - } - } - - private static int SkippedCamoTimes; - private static int CamoTimesThisGame; - public static int CamoTimesThisRound; - - private static bool ShouldCamouflage() - { - if (Camouflager.On && Camouflager.IsActive) return true; - - if (Main.CurrentMap == MapNames.Fungle && Options.CommsCamouflageDisableOnFungle.GetBool()) return false; - - if (Utils.IsActive(SystemTypes.Comms) && Options.CommsCamouflage.GetBool()) - { - if (Options.CommsCamouflageLimitSetChance.GetBool() && IRandom.Instance.Next(100) < Options.CommsCamouflageLimitChance.GetInt()) return false; - if (Options.CommsCamouflageLimitSetFrequency.GetBool() && ++SkippedCamoTimes < Options.CommsCamouflageLimitFrequency.GetInt()) return false; - - if (Options.CommsCamouflageLimitSetMaxTimes.GetBool()) + if (pc.inVent || pc.walkingToVent || pc.onLadder || pc.inMovingPlat) { - if (CamoTimesThisGame++ >= Options.CommsCamouflageLimitMaxTimesPerGame.GetInt()) return false; - if (CamoTimesThisRound++ >= Options.CommsCamouflageLimitMaxTimesPerRound.GetInt()) return false; - } - - return true; - } - - return false; - } - - public static bool CheckCamouflage() - { - if (!AmongUsClient.Instance.AmHost || (!Options.CommsCamouflage.GetBool() && !Camouflager.On)) return false; - - bool oldIsCamouflage = IsCamouflage; - - IsCamouflage = ShouldCamouflage(); - - if (oldIsCamouflage != IsCamouflage) - { - Logger.Info($"IsCamouflage: {IsCamouflage}", "CheckCamouflage"); - - WaitingForSkinChange = []; - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.inVent || pc.walkingToVent || pc.onLadder || pc.inMovingPlat) - { - WaitingForSkinChange.Add(pc.PlayerId); - continue; - } - - RpcSetSkin(pc); - - if (!IsCamouflage && !pc.IsAlive()) PetsPatch.RpcRemovePet(pc); + WaitingForSkinChange.Add(pc.PlayerId); + continue; } - Utils.NotifyRoles(NoCache: true); - return true; + RpcSetSkin(pc); + + if (!IsCamouflage && !pc.IsAlive()) PetsPatch.RpcRemovePet(pc); } - return false; + Utils.NotifyRoles(NoCache: true); + return true; } - public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bool RevertToDefault = false, bool GameEnd = false) + return false; + } + + public static void RpcSetSkin(PlayerControl target, bool ForceRevert = false, bool RevertToDefault = false, bool GameEnd = false) + { + if (!AmongUsClient.Instance.AmHost || (!Options.CommsCamouflage.GetBool() && !Camouflager.On) || target == null || (BlockCamouflage && !ForceRevert && !RevertToDefault && !GameEnd)) return; + + Logger.Info($"New outfit for {target.GetNameWithRole()}", "Camouflage.RpcSetSkin"); + + byte id = target.PlayerId; + + if (IsCamouflage && !target.IsAlive() && target.Data.IsDead) { - if (!AmongUsClient.Instance.AmHost || (!Options.CommsCamouflage.GetBool() && !Camouflager.On) || target == null || (BlockCamouflage && !ForceRevert && !RevertToDefault && !GameEnd)) return; + Logger.Info("Player is dead, returning", "Camouflage.RpcSetSkin"); + return; + } - Logger.Info($"New outfit for {target.GetNameWithRole()}", "Camouflage.RpcSetSkin"); + NetworkedPlayerInfo.PlayerOutfit newOutfit = CamouflageOutfit; - byte id = target.PlayerId; + if (!IsCamouflage || ForceRevert) + { + if (id.IsPlayerShifted() && !RevertToDefault) id = Main.ShapeshiftTarget[id]; - if (IsCamouflage && !target.IsAlive() && target.Data.IsDead) + if (!GameEnd && Doppelganger.DoppelPresentSkin.TryGetValue(id, out NetworkedPlayerInfo.PlayerOutfit value)) + newOutfit = value; + else { - Logger.Info("Player is dead, returning", "Camouflage.RpcSetSkin"); - return; - } - - NetworkedPlayerInfo.PlayerOutfit newOutfit = CamouflageOutfit; - - if (!IsCamouflage || ForceRevert) - { - if (id.IsPlayerShifted() && !RevertToDefault) id = Main.ShapeshiftTarget[id]; - - if (!GameEnd && Doppelganger.DoppelPresentSkin.TryGetValue(id, out NetworkedPlayerInfo.PlayerOutfit value)) - newOutfit = value; - else + if (GameEnd && Doppelganger.DoppelVictim.TryGetValue(id, out string value1)) { - if (GameEnd && Doppelganger.DoppelVictim.TryGetValue(id, out string value1)) - { - PlayerControl dpc = Utils.GetPlayerById(id); - dpc?.RpcSetName(value1); - } - - newOutfit = PlayerSkins[id]; + PlayerControl dpc = Utils.GetPlayerById(id); + dpc?.RpcSetName(value1); } + + newOutfit = PlayerSkins[id]; } - - SetPetForOutfitIfNecessary(newOutfit); - - // if the current Outfit is the same, return - if (newOutfit.Compare(target.Data.DefaultOutfit)) - { - Logger.Info("Outfit is the same, returning", "Camouflage.RpcSetSkin"); - return; - } - - Logger.Info($"Setting new outfit: {newOutfit.GetString()}", "Camouflage.RpcSetSkin"); - - var sender = CustomRpcSender.Create($"Camouflage.RpcSetSkin({target.Data.PlayerName})"); - - target.SetColor(newOutfit.ColorId); - - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) - .Write(target.Data.NetId) - .Write((byte)newOutfit.ColorId) - .EndRpc(); - - target.SetHat(newOutfit.HatId, newOutfit.ColorId); - target.Data.DefaultOutfit.HatSequenceId += 10; - - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) - .Write(newOutfit.HatId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - target.SetSkin(newOutfit.SkinId, newOutfit.ColorId); - target.Data.DefaultOutfit.SkinSequenceId += 10; - - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) - .Write(newOutfit.SkinId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - target.SetVisor(newOutfit.VisorId, newOutfit.ColorId); - target.Data.DefaultOutfit.VisorSequenceId += 10; - - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) - .Write(newOutfit.VisorId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - target.SetPet(newOutfit.PetId); - target.Data.DefaultOutfit.PetSequenceId += 10; - - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) - .Write(newOutfit.PetId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - sender.SendMessage(); } - public static void OnFixedUpdate(PlayerControl pc) + SetPetForOutfitIfNecessary(newOutfit); + + // if the current Outfit is the same, return + if (newOutfit.Compare(target.Data.DefaultOutfit)) { - if (pc.IsLocalPlayer()) CheckCamouflage(); - - if (!WaitingForSkinChange.Contains(pc.PlayerId) || pc.inVent || pc.walkingToVent || pc.onLadder || pc.inMovingPlat) return; - - RpcSetSkin(pc); - WaitingForSkinChange.Remove(pc.PlayerId); - - if (!IsCamouflage && !pc.IsAlive()) PetsPatch.RpcRemovePet(pc); - - Utils.NotifyRoles(SpecifySeer: pc, NoCache: true); - Utils.NotifyRoles(SpecifyTarget: pc, NoCache: true); + Logger.Info("Outfit is the same, returning", "Camouflage.RpcSetSkin"); + return; } + + Logger.Info($"Setting new outfit: {newOutfit.GetString()}", "Camouflage.RpcSetSkin"); + + var sender = CustomRpcSender.Create($"Camouflage.RpcSetSkin({target.Data.PlayerName})"); + + target.SetColor(newOutfit.ColorId); + + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) + .Write(target.Data.NetId) + .Write((byte)newOutfit.ColorId) + .EndRpc(); + + target.SetHat(newOutfit.HatId, newOutfit.ColorId); + target.Data.DefaultOutfit.HatSequenceId += 10; + + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) + .Write(newOutfit.HatId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); + + target.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + target.Data.DefaultOutfit.SkinSequenceId += 10; + + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) + .Write(newOutfit.SkinId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); + + target.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + target.Data.DefaultOutfit.VisorSequenceId += 10; + + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) + .Write(newOutfit.VisorId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); + + target.SetPet(newOutfit.PetId); + target.Data.DefaultOutfit.PetSequenceId += 10; + + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) + .Write(newOutfit.PetId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + + sender.SendMessage(); + } + + public static void OnFixedUpdate(PlayerControl pc) + { + if (pc.IsLocalPlayer()) CheckCamouflage(); + + if (!WaitingForSkinChange.Contains(pc.PlayerId) || pc.inVent || pc.walkingToVent || pc.onLadder || pc.inMovingPlat) return; + + RpcSetSkin(pc); + WaitingForSkinChange.Remove(pc.PlayerId); + + if (!IsCamouflage && !pc.IsAlive()) PetsPatch.RpcRemovePet(pc); + + Utils.NotifyRoles(SpecifySeer: pc, NoCache: true); + Utils.NotifyRoles(SpecifyTarget: pc, NoCache: true); } } \ No newline at end of file diff --git a/Modules/ChatBubbleShower.cs b/Modules/ChatBubbleShower.cs index 97ddb1323..2c610357c 100644 --- a/Modules/ChatBubbleShower.cs +++ b/Modules/ChatBubbleShower.cs @@ -6,98 +6,97 @@ using EHR.Patches; using TMPro; using UnityEngine; -namespace EHR.Modules +namespace EHR.Modules; + +public static class ChatBubbleShower { - public static class ChatBubbleShower + private static long LastChatBubbleShowTS; + private static readonly HashSet<(string Message, string Title)> Queue = []; + + public static void Update() { - private static long LastChatBubbleShowTS; - private static readonly HashSet<(string Message, string Title)> Queue = []; - - public static void Update() + try { - try + if (Queue.Count == 0 || ExileController.Instance) return; + + long now = Utils.TimeStamp; + int wait = GameStates.IsInTask ? 8 : 4; + if (LastChatBubbleShowTS + wait > now) return; + LastChatBubbleShowTS = now; + + (string message, string title) = Queue.First(); + Queue.Remove((message, title)); + + ChatController chat = DestroyableSingleton.Instance.Chat; + + if (GameStates.IsMeeting || chat.IsOpenOrOpening) ShowBubbleWithoutPlayer(); + else if (GameStates.IsLobby) Utils.SendMessage(message, PlayerControl.LocalPlayer.PlayerId, title); + else Main.Instance.StartCoroutine(ShowTextOnHud()); + + void ShowBubbleWithoutPlayer() { - if (Queue.Count == 0 || ExileController.Instance) return; + NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data; + ChatBubble bubble = chat.GetPooledBubble(); - long now = Utils.TimeStamp; - int wait = GameStates.IsInTask ? 8 : 4; - if (LastChatBubbleShowTS + wait > now) return; - LastChatBubbleShowTS = now; - - (string message, string title) = Queue.First(); - Queue.Remove((message, title)); - - ChatController chat = DestroyableSingleton.Instance.Chat; - - if (GameStates.IsMeeting || chat.IsOpenOrOpening) ShowBubbleWithoutPlayer(); - else if (GameStates.IsLobby) Utils.SendMessage(message, PlayerControl.LocalPlayer.PlayerId, title); - else Main.Instance.StartCoroutine(ShowTextOnHud()); - - void ShowBubbleWithoutPlayer() + try { - NetworkedPlayerInfo data = PlayerControl.LocalPlayer.Data; - ChatBubble bubble = chat.GetPooledBubble(); - - try - { - bubble.transform.SetParent(chat.scroller.Inner); - bubble.transform.localScale = Vector3.one; - bubble.SetCosmetics(data); - bubble.gameObject.transform.Find("PoolablePlayer").gameObject.SetActive(false); - bubble.ColorBlindName.gameObject.SetActive(false); - bubble.SetLeft(); - bubble.gameObject.transform.Find("NameText (TMP)").transform.localPosition += new Vector3(-0.7f, 0f); - bubble.gameObject.transform.Find("ChatText (TMP)").transform.localPosition += new Vector3(-0.7f, 0f); - chat.SetChatBubbleName(bubble, data, data.IsDead, false, PlayerNameColor.Get(data)); - bubble.SetText(message); - bubble.AlignChildren(); - chat.AlignAllBubbles(); - bubble.NameText.text = title; - bubble.transform.Find("ChatText (TMP)").GetComponent().color = new(1f, 1f, 1f, 1f); - bubble.transform.Find("Background").GetComponent().color = new(0.05f, 0.05f, 0.05f, 1f); - Transform xMark = bubble.transform.Find("PoolablePlayer/xMark"); - if (xMark && xMark.GetComponent().enabled) bubble.transform.Find("Background").GetComponent().color = new(0.05f, 0.05f, 0.05f, 0.5f); - } - catch (Exception e) - { - chat.chatBubblePool.Reclaim(bubble); - Utils.ThrowException(e); - } + bubble.transform.SetParent(chat.scroller.Inner); + bubble.transform.localScale = Vector3.one; + bubble.SetCosmetics(data); + bubble.gameObject.transform.Find("PoolablePlayer").gameObject.SetActive(false); + bubble.ColorBlindName.gameObject.SetActive(false); + bubble.SetLeft(); + bubble.gameObject.transform.Find("NameText (TMP)").transform.localPosition += new Vector3(-0.7f, 0f); + bubble.gameObject.transform.Find("ChatText (TMP)").transform.localPosition += new Vector3(-0.7f, 0f); + chat.SetChatBubbleName(bubble, data, data.IsDead, false, PlayerNameColor.Get(data)); + bubble.SetText(message); + bubble.AlignChildren(); + chat.AlignAllBubbles(); + bubble.NameText.text = title; + bubble.transform.Find("ChatText (TMP)").GetComponent().color = new(1f, 1f, 1f, 1f); + bubble.transform.Find("Background").GetComponent().color = new(0.05f, 0.05f, 0.05f, 1f); + Transform xMark = bubble.transform.Find("PoolablePlayer/xMark"); + if (xMark && xMark.GetComponent().enabled) bubble.transform.Find("Background").GetComponent().color = new(0.05f, 0.05f, 0.05f, 0.5f); } - - IEnumerator ShowTextOnHud() + catch (Exception e) { - const string color1 = "#00FFA5"; - const string color2 = "#00A5FF"; - bool isColor1 = true; - - for (int i = 0; i < 4; i++) - { - var color = isColor1 ? color1 : color2; - isColor1 = !isColor1; - var text = $"{title}\n{message}"; - HudManagerPatch.AchievementUnlockedText = text; - yield return new WaitForSeconds(0.2f); - } - - yield return new WaitForSeconds(7.25f); - HudManagerPatch.AchievementUnlockedText = string.Empty; + chat.chatBubblePool.Reclaim(bubble); + Utils.ThrowException(e); } } - catch (Exception e) + + IEnumerator ShowTextOnHud() { - Utils.ThrowException(e); + const string color1 = "#00FFA5"; + const string color2 = "#00A5FF"; + bool isColor1 = true; + + for (int i = 0; i < 4; i++) + { + var color = isColor1 ? color1 : color2; + isColor1 = !isColor1; + var text = $"{title}\n{message}"; + HudManagerPatch.AchievementUnlockedText = text; + yield return new WaitForSeconds(0.2f); + } + + yield return new WaitForSeconds(7.25f); + HudManagerPatch.AchievementUnlockedText = string.Empty; } } - - /// - /// Displays a chat bubble with a message and a title during the round for the local player. - /// - /// The message to display in the chat bubble. - /// The title of the chat bubble. - public static void ShowChatBubbleInRound(string message, string title) + catch (Exception e) { - Queue.Add((message, title)); + Utils.ThrowException(e); } } + + /// + /// Displays a chat bubble with a message and a title during the round for the local player. + /// + /// The message to display in the chat bubble. + /// The title of the chat bubble. + public static void ShowChatBubbleInRound(string message, string title) + { + Queue.Add((message, title)); + } } \ No newline at end of file diff --git a/Modules/CheckAUVersion.cs b/Modules/CheckAUVersion.cs index b8e20424c..adf1bf2a5 100644 --- a/Modules/CheckAUVersion.cs +++ b/Modules/CheckAUVersion.cs @@ -1,25 +1,24 @@ using System; using UnityEngine; -namespace EHR.Modules +namespace EHR.Modules; + +// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/VersionChecker.cs +public static class VersionChecker { - // https://github.com/tukasa0001/TownOfHost/blob/main/Modules/VersionChecker.cs - public static class VersionChecker + public static bool IsSupported { get; private set; } = true; + + public static void Check() { - public static bool IsSupported { get; private set; } = true; + Version AmongUsVersion = Version.Parse(Application.version); + Logger.Info($" {AmongUsVersion}", "Among Us Version Check"); - public static void Check() - { - Version AmongUsVersion = Version.Parse(Application.version); - Logger.Info($" {AmongUsVersion}", "Among Us Version Check"); + Version SupportedVersion = Version.Parse(Main.SupportedAUVersion); + Logger.Info($" {SupportedVersion}", "Supported Version Check"); - Version SupportedVersion = Version.Parse(Main.SupportedAUVersion); - Logger.Info($" {SupportedVersion}", "Supported Version Check"); + IsSupported = AmongUsVersion >= SupportedVersion; + Logger.Info($" {IsSupported}", "Version Is Supported?"); - IsSupported = AmongUsVersion >= SupportedVersion; - Logger.Info($" {IsSupported}", "Version Is Supported?"); - - if (!IsSupported) ErrorText.Instance.AddError(ErrorCode.UnsupportedVersion); - } + if (!IsSupported) ErrorText.Instance.AddError(ErrorCode.UnsupportedVersion); } } \ No newline at end of file diff --git a/Modules/ClientOptionItem.cs b/Modules/ClientOptionItem.cs index 9aaf6dcc2..0cef8361d 100644 --- a/Modules/ClientOptionItem.cs +++ b/Modules/ClientOptionItem.cs @@ -2,126 +2,125 @@ using BepInEx.Configuration; using UnityEngine; -namespace EHR +namespace EHR; + +// Credit: https://github.com/tukasa0001/TownOfHost/pull/1265 +public class ClientOptionItem { - // Credit: https://github.com/tukasa0001/TownOfHost/pull/1265 - public class ClientOptionItem + public static SpriteRenderer CustomBackground; + private static int NumOptions; + private readonly ConfigEntry Config; + public readonly ToggleButtonBehaviour ToggleButton; + + private ClientOptionItem( + string name, + ConfigEntry config, + OptionsMenuBehaviour optionsMenuBehaviour, + Action additionalOnClickAction = null) { - public static SpriteRenderer CustomBackground; - private static int NumOptions; - private readonly ConfigEntry Config; - public readonly ToggleButtonBehaviour ToggleButton; - - private ClientOptionItem( - string name, - ConfigEntry config, - OptionsMenuBehaviour optionsMenuBehaviour, - Action additionalOnClickAction = null) + try { - try + Config = config; + + ToggleButtonBehaviour mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + + if (CustomBackground == null) { - Config = config; + NumOptions = 0; + CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); + CustomBackground.name = "CustomBackground"; + CustomBackground.transform.localScale = new(0.9f, 0.9f, 1f); + CustomBackground.transform.localPosition += Vector3.back * 8; + CustomBackground.gameObject.SetActive(false); - ToggleButtonBehaviour mouseMoveToggle = optionsMenuBehaviour.DisableMouseMovement; + ToggleButtonBehaviour closeButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); + closeButton.transform.localPosition = new(1.3f, -2.3f, -6f); + closeButton.name = "Back"; + closeButton.Text.text = Translator.GetString("Back"); + closeButton.Background.color = Palette.DisabledGrey; + var closePassiveButton = closeButton.GetComponent(); + closePassiveButton.OnClick = new(); + closePassiveButton.OnClick.AddListener(new Action(() => { CustomBackground.gameObject.SetActive(false); })); - if (CustomBackground == null) + UiElement[] selectableButtons = optionsMenuBehaviour.ControllerSelectable.ToArray(); + PassiveButton leaveButton = null; + PassiveButton returnButton = null; + + foreach (UiElement button in selectableButtons) { - NumOptions = 0; - CustomBackground = Object.Instantiate(optionsMenuBehaviour.Background, optionsMenuBehaviour.transform); - CustomBackground.name = "CustomBackground"; - CustomBackground.transform.localScale = new(0.9f, 0.9f, 1f); - CustomBackground.transform.localPosition += Vector3.back * 8; - CustomBackground.gameObject.SetActive(false); + if (button == null) continue; - ToggleButtonBehaviour closeButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); - closeButton.transform.localPosition = new(1.3f, -2.3f, -6f); - closeButton.name = "Back"; - closeButton.Text.text = Translator.GetString("Back"); - closeButton.Background.color = Palette.DisabledGrey; - var closePassiveButton = closeButton.GetComponent(); - closePassiveButton.OnClick = new(); - closePassiveButton.OnClick.AddListener(new Action(() => { CustomBackground.gameObject.SetActive(false); })); - - UiElement[] selectableButtons = optionsMenuBehaviour.ControllerSelectable.ToArray(); - PassiveButton leaveButton = null; - PassiveButton returnButton = null; - - foreach (UiElement button in selectableButtons) + switch (button.name) { - if (button == null) continue; - - switch (button.name) - { - case "LeaveGameButton": - leaveButton = button.GetComponent(); - break; - case "ReturnToGameButton": - returnButton = button.GetComponent(); - break; - } + case "LeaveGameButton": + leaveButton = button.GetComponent(); + break; + case "ReturnToGameButton": + returnButton = button.GetComponent(); + break; } - - Transform generalTab = mouseMoveToggle.transform.parent.parent.parent; - - ToggleButtonBehaviour modOptionsButton = Object.Instantiate(mouseMoveToggle, generalTab); - modOptionsButton.transform.localPosition = leaveButton?.transform.localPosition ?? new(0f, -2.4f, 1f); - modOptionsButton.name = "EHROptions"; - modOptionsButton.Text.text = Translator.GetString("EHROptions"); - modOptionsButton.Background.color = new Color32(0, 165, 255, byte.MaxValue); - var modOptionsPassiveButton = modOptionsButton.GetComponent(); - modOptionsPassiveButton.OnClick = new(); - modOptionsPassiveButton.OnClick.AddListener(new Action(() => CustomBackground.gameObject.SetActive(true))); - - if (leaveButton != null && leaveButton.transform != null) leaveButton.transform.localPosition = new(-1.35f, -2.411f, -1f); - - if (returnButton != null) returnButton.transform.localPosition = new(1.35f, -2.411f, -1f); } - ToggleButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); + Transform generalTab = mouseMoveToggle.transform.parent.parent.parent; - ToggleButton.transform.localPosition = new( - NumOptions % 2 == 0 ? -1.3f : 1.3f, - // ReSharper disable once PossibleLossOfFraction - 2.2f - (0.5f * (NumOptions / 2)), - -6f); + ToggleButtonBehaviour modOptionsButton = Object.Instantiate(mouseMoveToggle, generalTab); + modOptionsButton.transform.localPosition = leaveButton?.transform.localPosition ?? new(0f, -2.4f, 1f); + modOptionsButton.name = "EHROptions"; + modOptionsButton.Text.text = Translator.GetString("EHROptions"); + modOptionsButton.Background.color = new Color32(0, 165, 255, byte.MaxValue); + var modOptionsPassiveButton = modOptionsButton.GetComponent(); + modOptionsPassiveButton.OnClick = new(); + modOptionsPassiveButton.OnClick.AddListener(new Action(() => CustomBackground.gameObject.SetActive(true))); - ToggleButton.name = name; - ToggleButton.Text.text = Translator.GetString(name); - var passiveButton = ToggleButton.GetComponent(); - passiveButton.OnClick = new(); + if (leaveButton != null && leaveButton.transform != null) leaveButton.transform.localPosition = new(-1.35f, -2.411f, -1f); - passiveButton.OnClick.AddListener(new Action(() => - { - if (config != null) config.Value = !config.Value; + if (returnButton != null) returnButton.transform.localPosition = new(1.35f, -2.411f, -1f); + } - UpdateToggle(); - additionalOnClickAction?.Invoke(); - })); + ToggleButton = Object.Instantiate(mouseMoveToggle, CustomBackground.transform); + + ToggleButton.transform.localPosition = new( + NumOptions % 2 == 0 ? -1.3f : 1.3f, + // ReSharper disable once PossibleLossOfFraction + 2.2f - (0.5f * (NumOptions / 2)), + -6f); + + ToggleButton.name = name; + ToggleButton.Text.text = Translator.GetString(name); + var passiveButton = ToggleButton.GetComponent(); + passiveButton.OnClick = new(); + + passiveButton.OnClick.AddListener(new Action(() => + { + if (config != null) config.Value = !config.Value; UpdateToggle(); - } - finally - { - NumOptions++; - } + additionalOnClickAction?.Invoke(); + })); + + UpdateToggle(); } - - public static ClientOptionItem Create( - string name, - ConfigEntry config, - OptionsMenuBehaviour optionsMenuBehaviour, - Action additionalOnClickAction = null) + finally { - return new(name, config, optionsMenuBehaviour, additionalOnClickAction); - } - - public void UpdateToggle() - { - if (ToggleButton == null) return; - - Color32 color = Config is { Value: true } ? new(0, 165, 255, byte.MaxValue) : new Color32(77, 77, 77, byte.MaxValue); - ToggleButton.Background.color = color; - ToggleButton.Rollover?.ChangeOutColor(color); + NumOptions++; } } + + public static ClientOptionItem Create( + string name, + ConfigEntry config, + OptionsMenuBehaviour optionsMenuBehaviour, + Action additionalOnClickAction = null) + { + return new(name, config, optionsMenuBehaviour, additionalOnClickAction); + } + + public void UpdateToggle() + { + if (ToggleButton == null) return; + + Color32 color = Config is { Value: true } ? new(0, 165, 255, byte.MaxValue) : new Color32(77, 77, 77, byte.MaxValue); + ToggleButton.Background.color = color; + ToggleButton.Rollover?.ChangeOutColor(color); + } } \ No newline at end of file diff --git a/Modules/Cloud.cs b/Modules/Cloud.cs index 541fa18bd..920f4fa5c 100644 --- a/Modules/Cloud.cs +++ b/Modules/Cloud.cs @@ -5,142 +5,141 @@ using System.Reflection; using System.Text; using HarmonyLib; -namespace EHR +namespace EHR; + +internal class Cloud { - internal class Cloud + private static string IP; + + //private static int LOBBY_PORT = 0; + private static int EAC_PORT; + + //private static Socket ClientSocket; + private static Socket EacClientSocket; + + private static long LastRepotTimeStamp; + /*public static bool ShareLobby(bool command = false) { - private static string IP; - - //private static int LOBBY_PORT = 0; - private static int EAC_PORT; - - //private static Socket ClientSocket; - private static Socket EacClientSocket; - - private static long LastRepotTimeStamp; - /*public static bool ShareLobby(bool command = false) + try { - try + if (!Options.ShareLobby.GetBool() && !command) return false; + if (!Main.newLobby || (GameData.Instance.PlayerCount < Options.ShareLobbyMinPlayer.GetInt() && !command) || !GameStates.IsLobby) return false; + if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return false; + + if (IP == null || LOBBY_PORT == 0) throw new("Has no ip or port"); + + Main.newLobby = false; + string msg = $"{GameStartManager.Instance.GameRoomNameCode.text}|{Main.PluginVersion}|{GameData.Instance.PlayerCount + 1}|{TranslationController.Instance.currentLanguage.languageID}|{ServerManager.Instance.CurrentRegion.Name}|{DataManager.player.customization.name}"; + + if (msg.Length <= 60) { - if (!Options.ShareLobby.GetBool() && !command) return false; - if (!Main.newLobby || (GameData.Instance.PlayerCount < Options.ShareLobbyMinPlayer.GetInt() && !command) || !GameStates.IsLobby) return false; - if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return false; - - if (IP == null || LOBBY_PORT == 0) throw new("Has no ip or port"); - - Main.newLobby = false; - string msg = $"{GameStartManager.Instance.GameRoomNameCode.text}|{Main.PluginVersion}|{GameData.Instance.PlayerCount + 1}|{TranslationController.Instance.currentLanguage.languageID}|{ServerManager.Instance.CurrentRegion.Name}|{DataManager.player.customization.name}"; - - if (msg.Length <= 60) - { - byte[] buffer = Encoding.Default.GetBytes(msg); - ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - ClientSocket.Connect(IP, LOBBY_PORT); - ClientSocket.Send(buffer); - ClientSocket.Close(); - } - - Utils.SendMessage(Translator.GetString("Message.LobbyShared"), PlayerControl.LocalPlayer.PlayerId); - + byte[] buffer = Encoding.Default.GetBytes(msg); + ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + ClientSocket.Connect(IP, LOBBY_PORT); + ClientSocket.Send(buffer); + ClientSocket.Close(); } - catch (Exception e) - { - Utils.SendMessage(Translator.GetString("Message.LobbyShareFailed"), PlayerControl.LocalPlayer.PlayerId); - Logger.Exception(e, "SentLobbyToQQ"); - throw; - } - return true; - }*/ - private static bool connecting; + Utils.SendMessage(Translator.GetString("Message.LobbyShared"), PlayerControl.LocalPlayer.PlayerId); - public static void Init() - { - try - { - string content = GetResourcesTxt("EHR.Resources.Config.Port.txt"); - string[] ar = content.Split('|'); - IP = ar[0]; - //LOBBY_PORT = int.Parse(ar[1]); - EAC_PORT = int.Parse(ar[2]); - } - catch (Exception e) - { - Logger.Exception(e, "Cloud Init"); - } } - - private static string GetResourcesTxt(string path) + catch (Exception e) { - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); - stream.Position = 0; - using StreamReader reader = new(stream, Encoding.UTF8); - return reader.ReadToEnd(); + Utils.SendMessage(Translator.GetString("Message.LobbyShareFailed"), PlayerControl.LocalPlayer.PlayerId); + Logger.Exception(e, "SentLobbyToQQ"); + throw; } + return true; + }*/ - public static void StartConnect() + private static bool connecting; + + public static void Init() + { + try { - if (connecting || (EacClientSocket != null && EacClientSocket.Connected)) return; + string content = GetResourcesTxt("EHR.Resources.Config.Port.txt"); + string[] ar = content.Split('|'); + IP = ar[0]; + //LOBBY_PORT = int.Parse(ar[1]); + EAC_PORT = int.Parse(ar[2]); + } + catch (Exception e) + { + Logger.Exception(e, "Cloud Init"); + } + } - connecting = true; + private static string GetResourcesTxt(string path) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + stream.Position = 0; + using StreamReader reader = new(stream, Encoding.UTF8); + return reader.ReadToEnd(); + } - LateTask.New(() => + public static void StartConnect() + { + if (connecting || (EacClientSocket != null && EacClientSocket.Connected)) return; + + connecting = true; + + LateTask.New(() => + { + if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) { - if (!AmongUsClient.Instance.AmHost || !GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) - { - connecting = false; - return; - } - - try - { - if (IP == null || EAC_PORT == 0) throw new("Has no ip or port"); - - LastRepotTimeStamp = Utils.TimeStamp; - EacClientSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - EacClientSocket.Connect(IP, EAC_PORT); - Logger.Warn("已连接至EHR服务器", "EAC Cloud"); - } - catch (Exception e) - { - connecting = false; - Logger.Exception(e, "EAC Cloud"); - throw; - } - connecting = false; - }, 3.5f, "EAC Cloud Connect"); - } - - public static void StopConnect() - { - if (EacClientSocket != null && EacClientSocket.Connected) EacClientSocket.Close(); - } - - public static void SendData(string msg) - { - StartConnect(); - - if (EacClientSocket == null || !EacClientSocket.Connected) - { - Logger.Warn("未连接至EHR服务器,报告被取消", "EAC Cloud"); return; } - EacClientSocket.Send(Encoding.Default.GetBytes(msg)); + try + { + if (IP == null || EAC_PORT == 0) throw new("Has no ip or port"); + + LastRepotTimeStamp = Utils.TimeStamp; + EacClientSocket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + EacClientSocket.Connect(IP, EAC_PORT); + Logger.Warn("已连接至EHR服务器", "EAC Cloud"); + } + catch (Exception e) + { + connecting = false; + Logger.Exception(e, "EAC Cloud"); + throw; + } + + connecting = false; + }, 3.5f, "EAC Cloud Connect"); + } + + public static void StopConnect() + { + if (EacClientSocket != null && EacClientSocket.Connected) EacClientSocket.Close(); + } + + public static void SendData(string msg) + { + StartConnect(); + + if (EacClientSocket == null || !EacClientSocket.Connected) + { + Logger.Warn("未连接至EHR服务器,报告被取消", "EAC Cloud"); + return; } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - private class EACConnectTimeOut + EacClientSocket.Send(Encoding.Default.GetBytes(msg)); + } + + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] + private class EACConnectTimeOut + { + public static void Postfix() { - public static void Postfix() + if (LastRepotTimeStamp != 0 && LastRepotTimeStamp + 8 < Utils.TimeStamp && !GameStates.IsInTask) { - if (LastRepotTimeStamp != 0 && LastRepotTimeStamp + 8 < Utils.TimeStamp && !GameStates.IsInTask) - { - LastRepotTimeStamp = 0; - StopConnect(); - Logger.Warn("超时自动断开与EHR服务器的连接", "EAC Cloud"); - } + LastRepotTimeStamp = 0; + StopConnect(); + Logger.Warn("超时自动断开与EHR服务器的连接", "EAC Cloud"); } } } diff --git a/Modules/CustomRoleSelector.cs b/Modules/CustomRoleSelector.cs index d752b5ea9..337c4e3f5 100644 --- a/Modules/CustomRoleSelector.cs +++ b/Modules/CustomRoleSelector.cs @@ -5,717 +5,716 @@ using EHR.AddOns.Common; using EHR.Impostor; using EHR.Neutral; -namespace EHR.Modules +namespace EHR.Modules; + +internal static class CustomRoleSelector { - internal static class CustomRoleSelector + public static Dictionary RoleResult; + + public static int AddScientistNum; + public static int AddEngineerNum; + public static int AddShapeshifterNum; + public static int AddNoisemakerNum; + public static int AddTrackerNum; + public static int AddPhantomNum; + + public static List AddonRolesList = []; + + public static void SelectCustomRoles() { - public static Dictionary RoleResult; + RoleResult = []; - public static int AddScientistNum; - public static int AddEngineerNum; - public static int AddShapeshifterNum; - public static int AddNoisemakerNum; - public static int AddTrackerNum; - public static int AddPhantomNum; + if (Main.GM.Value && Main.AllPlayerControls.Length == 1) return; - public static List AddonRolesList = []; + // switch (Options.CurrentGameMode) + // { + // case CustomGameMode.SoloKombat: + // AssignRoleToEveryone(CustomRoles.KB_Normal); + // return; + // case CustomGameMode.FFA: + // AssignRoleToEveryone(CustomRoles.Killer); + // return; + // case CustomGameMode.MoveAndStop: + // AssignRoleToEveryone(CustomRoles.Tasker); + // return; + // case CustomGameMode.HotPotato: + // AssignRoleToEveryone(CustomRoles.Potato); + // return; + // case CustomGameMode.Speedrun: + // AssignRoleToEveryone(CustomRoles.Runner); + // return; + // case CustomGameMode.CaptureTheFlag: + // AssignRoleToEveryone(CustomRoles.CTFPlayer); + // return; + // case CustomGameMode.NaturalDisasters: + // AssignRoleToEveryone(CustomRoles.NDPlayer); + // return; + // case CustomGameMode.RoomRush: + // AssignRoleToEveryone(CustomRoles.RRPlayer); + // return; + // case CustomGameMode.HideAndSeek: + // HnSManager.AssignRoles(); + // RoleResult = HnSManager.PlayerRoles.ToDictionary(x => x.Key, x => x.Value.Role); + // return; + // } - public static void SelectCustomRoles() + if (Options.CurrentGameMode != CustomGameMode.Standard) { - RoleResult = []; - - if (Main.GM.Value && Main.AllPlayerControls.Length == 1) return; - - // switch (Options.CurrentGameMode) - // { - // case CustomGameMode.SoloKombat: - // AssignRoleToEveryone(CustomRoles.KB_Normal); - // return; - // case CustomGameMode.FFA: - // AssignRoleToEveryone(CustomRoles.Killer); - // return; - // case CustomGameMode.MoveAndStop: - // AssignRoleToEveryone(CustomRoles.Tasker); - // return; - // case CustomGameMode.HotPotato: - // AssignRoleToEveryone(CustomRoles.Potato); - // return; - // case CustomGameMode.Speedrun: - // AssignRoleToEveryone(CustomRoles.Runner); - // return; - // case CustomGameMode.CaptureTheFlag: - // AssignRoleToEveryone(CustomRoles.CTFPlayer); - // return; - // case CustomGameMode.NaturalDisasters: - // AssignRoleToEveryone(CustomRoles.NDPlayer); - // return; - // case CustomGameMode.RoomRush: - // AssignRoleToEveryone(CustomRoles.RRPlayer); - // return; - // case CustomGameMode.HideAndSeek: - // HnSManager.AssignRoles(); - // RoleResult = HnSManager.PlayerRoles.ToDictionary(x => x.Key, x => x.Value.Role); - // return; - // } - - if (Options.CurrentGameMode != CustomGameMode.Standard) + Dictionary gameModeRoles = new() { - Dictionary gameModeRoles = new() - { - { CustomGameMode.SoloKombat, CustomRoles.KB_Normal }, - { CustomGameMode.FFA, CustomRoles.Killer }, - { CustomGameMode.MoveAndStop, CustomRoles.Tasker }, - { CustomGameMode.HotPotato, CustomRoles.Potato }, - { CustomGameMode.Speedrun, CustomRoles.Runner }, - { CustomGameMode.CaptureTheFlag, CustomRoles.CTFPlayer }, - { CustomGameMode.NaturalDisasters, CustomRoles.NDPlayer }, - { CustomGameMode.RoomRush, CustomRoles.RRPlayer } - }; + { CustomGameMode.SoloKombat, CustomRoles.KB_Normal }, + { CustomGameMode.FFA, CustomRoles.Killer }, + { CustomGameMode.MoveAndStop, CustomRoles.Tasker }, + { CustomGameMode.HotPotato, CustomRoles.Potato }, + { CustomGameMode.Speedrun, CustomRoles.Runner }, + { CustomGameMode.CaptureTheFlag, CustomRoles.CTFPlayer }, + { CustomGameMode.NaturalDisasters, CustomRoles.NDPlayer }, + { CustomGameMode.RoomRush, CustomRoles.RRPlayer } + }; - if (gameModeRoles.TryGetValue(Options.CurrentGameMode, out var role)) - { - AssignRoleToEveryone(role); - return; - } - - bool hns = Options.CurrentGameMode == CustomGameMode.HideAndSeek; - - if (Options.CurrentGameMode == CustomGameMode.AllInOne) - { - var prioritizedGameMode = AllInOneGameMode.GetPrioritizedGameModeForRoles(); - - if (gameModeRoles.TryGetValue(prioritizedGameMode, out var allInOneRole)) - { - AssignRoleToEveryone(allInOneRole); - return; - } - - hns = prioritizedGameMode == CustomGameMode.HideAndSeek; - } - - if (hns) - { - HnSManager.AssignRoles(); - RoleResult = HnSManager.PlayerRoles.ToDictionary(x => x.Key, x => x.Value.Role); - return; - } + if (gameModeRoles.TryGetValue(Options.CurrentGameMode, out var role)) + { + AssignRoleToEveryone(role); + return; } - var rd = IRandom.Instance; - int playerCount = Main.AllAlivePlayerControls.Length; + bool hns = Options.CurrentGameMode == CustomGameMode.HideAndSeek; + + if (Options.CurrentGameMode == CustomGameMode.AllInOne) + { + var prioritizedGameMode = AllInOneGameMode.GetPrioritizedGameModeForRoles(); + + if (gameModeRoles.TryGetValue(prioritizedGameMode, out var allInOneRole)) + { + AssignRoleToEveryone(allInOneRole); + return; + } + + hns = prioritizedGameMode == CustomGameMode.HideAndSeek; + } + + if (hns) + { + HnSManager.AssignRoles(); + RoleResult = HnSManager.PlayerRoles.ToDictionary(x => x.Key, x => x.Value.Role); + return; + } + } + + var rd = IRandom.Instance; + int playerCount = Main.AllAlivePlayerControls.Length; - var impLimits = Options.FactionMinMaxSettings[Team.Impostor]; - int optImpNum = rd.Next(impLimits.MinSetting.GetInt(), impLimits.MaxSetting.GetInt()); + var impLimits = Options.FactionMinMaxSettings[Team.Impostor]; + int optImpNum = rd.Next(impLimits.MinSetting.GetInt(), impLimits.MaxSetting.GetInt()); - var readyRoleNum = 0; - var readyImpNum = 0; - var readyNonNeutralKillingNum = 0; - var readyNeutralKillingNum = 0; + var readyRoleNum = 0; + var readyImpNum = 0; + var readyNonNeutralKillingNum = 0; + var readyNeutralKillingNum = 0; - List FinalRolesList = []; + List FinalRolesList = []; - Dictionary> Roles = []; - Enum.GetValues().Do(x => Roles[x] = []); + Dictionary> Roles = []; + Enum.GetValues().Do(x => Roles[x] = []); - foreach (byte id in Main.SetRoles.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) Main.SetRoles.Remove(id); + foreach (byte id in Main.SetRoles.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) Main.SetRoles.Remove(id); - (bool Spawning, bool OneIsImp) LoversData = (Lovers.LegacyLovers.GetBool(), rd.Next(100) < Lovers.LovingImpostorSpawnChance.GetInt()); - LoversData.Spawning &= rd.Next(100) < Options.CustomAdtRoleSpawnRate[CustomRoles.Lovers].GetInt(); + (bool Spawning, bool OneIsImp) LoversData = (Lovers.LegacyLovers.GetBool(), rd.Next(100) < Lovers.LovingImpostorSpawnChance.GetInt()); + LoversData.Spawning &= rd.Next(100) < Options.CustomAdtRoleSpawnRate[CustomRoles.Lovers].GetInt(); - foreach (CustomRoles role in Enum.GetValues()) + foreach (CustomRoles role in Enum.GetValues()) + { + int chance = role.GetMode(); + if (role.IsVanilla() || chance == 0 || role.IsAdditionRole() || (role.OnlySpawnsWithPets() && !Options.UsePets.GetBool()) || (role != CustomRoles.Randomizer && role.IsCrewmate() && Options.AprilFoolsMode.GetBool()) || HnSManager.AllHnSRoles.Contains(role)) continue; + + switch (role) { - int chance = role.GetMode(); - if (role.IsVanilla() || chance == 0 || role.IsAdditionRole() || (role.OnlySpawnsWithPets() && !Options.UsePets.GetBool()) || (role != CustomRoles.Randomizer && role.IsCrewmate() && Options.AprilFoolsMode.GetBool()) || HnSManager.AllHnSRoles.Contains(role)) continue; + case CustomRoles.Doctor when Options.EveryoneSeesDeathReasons.GetBool(): + case CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor when !LoversData.Spawning: + case CustomRoles.Commander when optImpNum <= 1 && Commander.CannotSpawnAsSoloImp.GetBool(): + case CustomRoles.Changeling when Changeling.GetAvailableRoles(true).Count == 0: + case CustomRoles.Camouflager when Camouflager.DoesntSpawnOnFungle.GetBool() && Main.CurrentMap == MapNames.Fungle: + case CustomRoles.DarkHide when Main.CurrentMap == MapNames.Fungle: + case CustomRoles.Pelican when Roles[RoleAssignType.Impostor].Any(x => x.Role == CustomRoles.Duellist): + case CustomRoles.Duellist when Roles[RoleAssignType.NeutralKilling].Any(x => x.Role == CustomRoles.Pelican): + case CustomRoles.VengefulRomantic: + case CustomRoles.RuthlessRomantic: + case CustomRoles.Deathknight: + case CustomRoles.GM: + case CustomRoles.NotAssigned: + continue; + } - switch (role) + int count = role.GetCount(); + + if (role == CustomRoles.Randomizer && Options.AprilFoolsMode.GetBool()) + { + chance = 100; + count = 15; + } + + RoleAssignInfo info = new(role, chance, count); + + if (role.IsImpostor()) Roles[RoleAssignType.Impostor].Add(info); + else if (role.IsNK()) Roles[RoleAssignType.NeutralKilling].Add(info); + else if (role.IsNonNK()) Roles[RoleAssignType.NonKillingNeutral].Add(info); + else Roles[RoleAssignType.Crewmate].Add(info); + } + + LoversData.OneIsImp &= Roles[RoleAssignType.Impostor].Count(x => x.SpawnChance == 100) < optImpNum; + + if (LoversData.Spawning) + { + if (LoversData.OneIsImp) + { + Roles[RoleAssignType.Crewmate].Add(new(CustomRoles.LovingCrewmate, 100, 1)); + Roles[RoleAssignType.Impostor].Add(new(CustomRoles.LovingImpostor, 100, 1)); + } + else Roles[RoleAssignType.Crewmate].Add(new(CustomRoles.LovingCrewmate, 100, 2)); + } + + var neutralLimits = Options.FactionMinMaxSettings[Team.Neutral]; + + if (Roles[RoleAssignType.Impostor].Count == 0 && neutralLimits.MaxSetting.GetInt() == 0 && !Main.SetRoles.Values.Any(x => x.IsImpostor() || x.IsNK())) + { + Roles[RoleAssignType.Impostor].Add(new(CustomRoles.ImpostorEHR, 100, optImpNum)); + Logger.Warn("Adding Vanilla Impostor", "CustomRoleSelector"); + } + + if (Roles[RoleAssignType.Crewmate].Count == 0 && neutralLimits.MaxSetting.GetInt() == 0 && !Main.SetRoles.Values.Any(x => x.IsCrewmate())) + { + Roles[RoleAssignType.Crewmate].Add(new(CustomRoles.CrewmateEHR, 100, playerCount)); + Logger.Warn("Adding Vanilla Crewmates", "CustomRoleSelector"); + } + + Logger.Msg("=====================================================", "AllActiveRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.Impostor].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "ImpRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NKRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NNKRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "CrewRoles"); + Logger.Msg("=====================================================", "AllActiveRoles"); + + Dictionary subCategoryLimits = Options.RoleSubCategoryLimits + .Where(x => x.Value[0].GetBool()) + .ToDictionary(x => x.Key, x => IRandom.Instance.Next(x.Value[1].GetInt(), x.Value[2].GetInt() + 1)); + + if (subCategoryLimits.Count > 0) Logger.Info($"Sub-Category Limits: {string.Join(", ", subCategoryLimits.Select(x => $"{x.Key}: {x.Value}"))}", "SubCategoryLimits"); + + foreach (RoleAssignType type in Roles.Keys.ToArray()) + { + Roles[type] = Roles[type] + .Shuffle() + .OrderBy(x => x.SpawnChance != 100) + .DistinctBy(x => x.Role) + .Select(x => ( + Info: x, + Limit: subCategoryLimits.TryGetValue(x.OptionType, out var limit) + ? (Exists: true, Value: limit) + : (Exists: false, Value: 0))) + .GroupBy(x => x.Info.OptionType) + .Select(x => (Grouping: x, x.FirstOrDefault().Limit)) + .SelectMany(x => x.Limit.Exists ? x.Grouping.Take(x.Limit.Value) : x.Grouping) + .OrderByDescending(x => x.Limit is { Exists: true, Value: > 0 }) + .Select(x => x.Info) + .Take(type switch { - case CustomRoles.Doctor when Options.EveryoneSeesDeathReasons.GetBool(): - case CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor when !LoversData.Spawning: - case CustomRoles.Commander when optImpNum <= 1 && Commander.CannotSpawnAsSoloImp.GetBool(): - case CustomRoles.Changeling when Changeling.GetAvailableRoles(true).Count == 0: - case CustomRoles.Camouflager when Camouflager.DoesntSpawnOnFungle.GetBool() && Main.CurrentMap == MapNames.Fungle: - case CustomRoles.DarkHide when Main.CurrentMap == MapNames.Fungle: - case CustomRoles.Pelican when Roles[RoleAssignType.Impostor].Any(x => x.Role == CustomRoles.Duellist): - case CustomRoles.Duellist when Roles[RoleAssignType.NeutralKilling].Any(x => x.Role == CustomRoles.Pelican): - case CustomRoles.VengefulRomantic: - case CustomRoles.RuthlessRomantic: - case CustomRoles.Deathknight: - case CustomRoles.GM: - case CustomRoles.NotAssigned: - continue; - } + RoleAssignType.Impostor => optImpNum, + RoleAssignType.NeutralKilling or RoleAssignType.NonKillingNeutral => neutralLimits.MaxSetting.GetInt(), + RoleAssignType.Crewmate => playerCount, + _ => 0 + }) + .ToList(); + } - int count = role.GetCount(); + int attempts = 0; - if (role == CustomRoles.Randomizer && Options.AprilFoolsMode.GetBool()) - { - chance = 100; - count = 15; - } + while (Roles[RoleAssignType.NeutralKilling].Count + Roles[RoleAssignType.NonKillingNeutral].Count > neutralLimits.MaxSetting.GetInt()) + { + if (attempts++ > 100) break; - RoleAssignInfo info = new(role, chance, count); - - if (role.IsImpostor()) Roles[RoleAssignType.Impostor].Add(info); - else if (role.IsNK()) Roles[RoleAssignType.NeutralKilling].Add(info); - else if (role.IsNonNK()) Roles[RoleAssignType.NonKillingNeutral].Add(info); - else Roles[RoleAssignType.Crewmate].Add(info); - } - - LoversData.OneIsImp &= Roles[RoleAssignType.Impostor].Count(x => x.SpawnChance == 100) < optImpNum; - - if (LoversData.Spawning) - { - if (LoversData.OneIsImp) - { - Roles[RoleAssignType.Crewmate].Add(new(CustomRoles.LovingCrewmate, 100, 1)); - Roles[RoleAssignType.Impostor].Add(new(CustomRoles.LovingImpostor, 100, 1)); - } - else Roles[RoleAssignType.Crewmate].Add(new(CustomRoles.LovingCrewmate, 100, 2)); - } - - var neutralLimits = Options.FactionMinMaxSettings[Team.Neutral]; - - if (Roles[RoleAssignType.Impostor].Count == 0 && neutralLimits.MaxSetting.GetInt() == 0 && !Main.SetRoles.Values.Any(x => x.IsImpostor() || x.IsNK())) - { - Roles[RoleAssignType.Impostor].Add(new(CustomRoles.ImpostorEHR, 100, optImpNum)); - Logger.Warn("Adding Vanilla Impostor", "CustomRoleSelector"); - } - - if (Roles[RoleAssignType.Crewmate].Count == 0 && neutralLimits.MaxSetting.GetInt() == 0 && !Main.SetRoles.Values.Any(x => x.IsCrewmate())) - { - Roles[RoleAssignType.Crewmate].Add(new(CustomRoles.CrewmateEHR, 100, playerCount)); - Logger.Warn("Adding Vanilla Crewmates", "CustomRoleSelector"); - } - - Logger.Msg("=====================================================", "AllActiveRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.Impostor].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "ImpRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NKRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "NNKRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => $"{x.Role}: {x.SpawnChance}% - {x.MaxCount}")), "CrewRoles"); - Logger.Msg("=====================================================", "AllActiveRoles"); - - Dictionary subCategoryLimits = Options.RoleSubCategoryLimits - .Where(x => x.Value[0].GetBool()) - .ToDictionary(x => x.Key, x => IRandom.Instance.Next(x.Value[1].GetInt(), x.Value[2].GetInt() + 1)); - - if (subCategoryLimits.Count > 0) Logger.Info($"Sub-Category Limits: {string.Join(", ", subCategoryLimits.Select(x => $"{x.Key}: {x.Value}"))}", "SubCategoryLimits"); - - foreach (RoleAssignType type in Roles.Keys.ToArray()) - { - Roles[type] = Roles[type] - .Shuffle() - .OrderBy(x => x.SpawnChance != 100) - .DistinctBy(x => x.Role) - .Select(x => ( - Info: x, - Limit: subCategoryLimits.TryGetValue(x.OptionType, out var limit) - ? (Exists: true, Value: limit) - : (Exists: false, Value: 0))) - .GroupBy(x => x.Info.OptionType) - .Select(x => (Grouping: x, x.FirstOrDefault().Limit)) - .SelectMany(x => x.Limit.Exists ? x.Grouping.Take(x.Limit.Value) : x.Grouping) - .OrderByDescending(x => x.Limit is { Exists: true, Value: > 0 }) - .Select(x => x.Info) - .Take(type switch - { - RoleAssignType.Impostor => optImpNum, - RoleAssignType.NeutralKilling or RoleAssignType.NonKillingNeutral => neutralLimits.MaxSetting.GetInt(), - RoleAssignType.Crewmate => playerCount, - _ => 0 - }) - .ToList(); - } - - int attempts = 0; - - while (Roles[RoleAssignType.NeutralKilling].Count + Roles[RoleAssignType.NonKillingNeutral].Count > neutralLimits.MaxSetting.GetInt()) - { - if (attempts++ > 100) break; - - RoleAssignType type = rd.Next(2) == 0 ? RoleAssignType.NeutralKilling : RoleAssignType.NonKillingNeutral; - var toRemove = Roles[type].RandomElement(); - Roles[type].Remove(toRemove); + RoleAssignType type = rd.Next(2) == 0 ? RoleAssignType.NeutralKilling : RoleAssignType.NonKillingNeutral; + var toRemove = Roles[type].RandomElement(); + Roles[type].Remove(toRemove); - Logger.Warn($"Removed {toRemove.Role} from {type}", "CustomRoleSelector"); + Logger.Warn($"Removed {toRemove.Role} from {type}", "CustomRoleSelector"); + } + + int nnkNum = Roles[RoleAssignType.NonKillingNeutral].Count; + int nkNum = Roles[RoleAssignType.NeutralKilling].Count; + + Logger.Msg("======================================================", "SelectedRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.Impostor].Select(x => x.Role.ToString())), "SelectedImpostorRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => x.Role.ToString())), "SelectedNKRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => x.Role.ToString())), "SelectedNNKRoles"); + Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => x.Role.ToString())), "SelectedCrewRoles"); + Logger.Msg("======================================================", "SelectedRoles"); + + List AllPlayers = Main.AllAlivePlayerControls.ToList(); + + // Players on the EAC banned list will be assigned as GM when opening rooms + if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid())) + { + Main.GM.Value = true; + RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; + AllPlayers.Remove(PlayerControl.LocalPlayer); + } + + if (Main.GM.Value) + { + Logger.Warn("Host: GM", "CustomRoleSelector"); + AllPlayers.RemoveAll(x => x.IsHost()); + RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; + } + + // Pre-Assigned Roles By Host Are Selected First + foreach ((byte id, CustomRoles role) in Main.SetRoles.AddRange(ChatCommands.DraftResult, false)) + { + PlayerControl pc = AllPlayers.FirstOrDefault(x => x.PlayerId == id); + if (pc == null) continue; + + RoleResult[pc.PlayerId] = role; + AllPlayers.Remove(pc); + + if (role.IsImpostor()) + { + Roles[RoleAssignType.Impostor].DoIf(x => x.Role == role, x => x.AssignedCount++); + readyImpNum++; + } + else if (role.IsNK()) + { + Roles[RoleAssignType.NeutralKilling].DoIf(x => x.Role == role, x => x.AssignedCount++); + readyNeutralKillingNum++; + } + else if (role.IsNonNK()) + { + Roles[RoleAssignType.NonKillingNeutral].DoIf(x => x.Role == role, x => x.AssignedCount++); + readyNonNeutralKillingNum++; + } + else + Roles[RoleAssignType.Crewmate].DoIf(x => x.Role == role, x => x.AssignedCount++); + + readyRoleNum++; + + Logger.Warn($"Pre-Set Role Assigned: {pc.GetRealName()} => {role}", "CustomRoleSelector"); + } + + Roles.Values.Do(l => l.DoIf(x => x.AssignedCount >= x.MaxCount, x => l.Remove(x), false)); + + RoleAssignInfo[] Imps; + RoleAssignInfo[] NNKs = []; + RoleAssignInfo[] NKs = []; + RoleAssignInfo[] Crews = []; + + // Impostor Roles + { + List AlwaysImpRoles = []; + List ChanceImpRoles = []; + + for (var i = 0; i < Roles[RoleAssignType.Impostor].Count; i++) + { + RoleAssignInfo item = Roles[RoleAssignType.Impostor][i]; + + if (item.SpawnChance == 100) + for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) + AlwaysImpRoles.Add(item.Role); + else + { + for (var j = 0; j < item.SpawnChance / 5; j++) + for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) + ChanceImpRoles.Add(item.Role); + } } - int nnkNum = Roles[RoleAssignType.NonKillingNeutral].Count; - int nkNum = Roles[RoleAssignType.NeutralKilling].Count; + RoleAssignInfo[] ImpRoleCounts = AlwaysImpRoles.Distinct().Select(GetAssignInfo).Concat(ChanceImpRoles.Distinct().Select(GetAssignInfo)).ToArray(); + Imps = ImpRoleCounts; - Logger.Msg("======================================================", "SelectedRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.Impostor].Select(x => x.Role.ToString())), "SelectedImpostorRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.NeutralKilling].Select(x => x.Role.ToString())), "SelectedNKRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.NonKillingNeutral].Select(x => x.Role.ToString())), "SelectedNNKRoles"); - Logger.Info(string.Join(", ", Roles[RoleAssignType.Crewmate].Select(x => x.Role.ToString())), "SelectedCrewRoles"); - Logger.Msg("======================================================", "SelectedRoles"); - - List AllPlayers = Main.AllAlivePlayerControls.ToList(); - - // Players on the EAC banned list will be assigned as GM when opening rooms - if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid())) + // Assign roles set to ALWAYS + if (readyImpNum < optImpNum) { - Main.GM.Value = true; - RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; - AllPlayers.Remove(PlayerControl.LocalPlayer); + while (AlwaysImpRoles.Count > 0) + { + CustomRoles selected = AlwaysImpRoles.RandomElement(); + RoleAssignInfo info = ImpRoleCounts.FirstOrDefault(x => x.Role == selected); + AlwaysImpRoles.Remove(selected); + if (info.AssignedCount >= info.MaxCount) continue; + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyImpNum++; + + Imps = ImpRoleCounts; + + if (readyRoleNum >= playerCount) goto EndOfAssign; + + if (readyImpNum >= optImpNum) break; + } } - if (Main.GM.Value) + // Assign other roles when needed + if (readyRoleNum < playerCount && readyImpNum < optImpNum) { - Logger.Warn("Host: GM", "CustomRoleSelector"); - AllPlayers.RemoveAll(x => x.IsHost()); - RoleResult[PlayerControl.LocalPlayer.PlayerId] = CustomRoles.GM; + while (ChanceImpRoles.Count > 0) + { + CustomRoles selected = ChanceImpRoles.RandomElement(); + RoleAssignInfo info = ImpRoleCounts.FirstOrDefault(x => x.Role == selected); + for (var i = 0; i < info.SpawnChance / 5; i++) ChanceImpRoles.Remove(selected); + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyImpNum++; + + Imps = ImpRoleCounts; + + if (info.AssignedCount >= info.MaxCount) + while (ChanceImpRoles.Contains(selected)) + ChanceImpRoles.Remove(selected); + + if (readyRoleNum >= playerCount) goto EndOfAssign; + + if (readyImpNum >= optImpNum) break; + } + } + } + + // Neutral Roles + { + // Neutral Non-Killing Roles + { + List AlwaysNNKRoles = []; + List ChanceNNKRoles = []; + + for (var i = 0; i < Roles[RoleAssignType.NonKillingNeutral].Count; i++) + { + RoleAssignInfo item = Roles[RoleAssignType.NonKillingNeutral][i]; + + if (item.SpawnChance == 100) + for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) + AlwaysNNKRoles.Add(item.Role); + else + { + for (var j = 0; j < item.SpawnChance / 5; j++) + for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) + ChanceNNKRoles.Add(item.Role); + } + } + + RoleAssignInfo[] NNKRoleCounts = AlwaysNNKRoles.Distinct().Select(GetAssignInfo).Concat(ChanceNNKRoles.Distinct().Select(GetAssignInfo)).ToArray(); + NNKs = NNKRoleCounts; + + // Assign roles set to ALWAYS + if (readyNonNeutralKillingNum < nnkNum) + { + while (AlwaysNNKRoles.Count > 0 && nnkNum > 0) + { + CustomRoles selected = AlwaysNNKRoles.RandomElement(); + RoleAssignInfo info = NNKRoleCounts.FirstOrDefault(x => x.Role == selected); + AlwaysNNKRoles.Remove(selected); + if (info.AssignedCount >= info.MaxCount) continue; + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyNonNeutralKillingNum++; + + NNKs = NNKRoleCounts; + + if (readyRoleNum >= playerCount) goto EndOfAssign; + + if (readyNonNeutralKillingNum >= nnkNum) break; + } + } + + // Assign other roles when needed + if (readyRoleNum < playerCount && readyNonNeutralKillingNum < nnkNum) + { + while (ChanceNNKRoles.Count > 0 && nnkNum > 0) + { + CustomRoles selected = ChanceNNKRoles.RandomElement(); + RoleAssignInfo info = NNKRoleCounts.FirstOrDefault(x => x.Role == selected); + for (var i = 0; i < info.SpawnChance / 5; i++) ChanceNNKRoles.Remove(selected); + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyNonNeutralKillingNum++; + + NNKs = NNKRoleCounts; + + if (info.AssignedCount >= info.MaxCount) + while (ChanceNNKRoles.Contains(selected)) + ChanceNNKRoles.Remove(selected); + + if (readyRoleNum >= playerCount) goto EndOfAssign; + + if (readyNonNeutralKillingNum >= nnkNum) break; + } + } } - // Pre-Assigned Roles By Host Are Selected First - foreach ((byte id, CustomRoles role) in Main.SetRoles.AddRange(ChatCommands.DraftResult, false)) + // Neutral Killing Roles { - PlayerControl pc = AllPlayers.FirstOrDefault(x => x.PlayerId == id); - if (pc == null) continue; + List AlwaysNKRoles = []; + List ChanceNKRoles = []; + + for (var i = 0; i < Roles[RoleAssignType.NeutralKilling].Count; i++) + { + RoleAssignInfo item = Roles[RoleAssignType.NeutralKilling][i]; + + if (item.SpawnChance == 100) + for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) + AlwaysNKRoles.Add(item.Role); + else + { + for (var j = 0; j < item.SpawnChance / 5; j++) + for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) + ChanceNKRoles.Add(item.Role); + } + } + + RoleAssignInfo[] NKRoleCounts = AlwaysNKRoles.Distinct().Select(GetAssignInfo).Concat(ChanceNKRoles.Distinct().Select(GetAssignInfo)).ToArray(); + NKs = NKRoleCounts; + + // Assign roles set to ALWAYS + if (readyNeutralKillingNum < nkNum) + { + while (AlwaysNKRoles.Count > 0 && nkNum > 0) + { + CustomRoles selected = AlwaysNKRoles.RandomElement(); + RoleAssignInfo info = NKRoleCounts.FirstOrDefault(x => x.Role == selected); + AlwaysNKRoles.Remove(selected); + if (info.AssignedCount >= info.MaxCount) continue; + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyNeutralKillingNum++; + + NKs = NKRoleCounts; + + if (readyRoleNum >= playerCount) goto EndOfAssign; + + if (readyNeutralKillingNum >= nkNum) break; + } + } + + // Assign other roles when needed + if (readyRoleNum < playerCount && readyNeutralKillingNum < nkNum) + { + while (ChanceNKRoles.Count > 0 && nkNum > 0) + { + CustomRoles selected = ChanceNKRoles.RandomElement(); + RoleAssignInfo info = NKRoleCounts.FirstOrDefault(x => x.Role == selected); + for (var i = 0; i < info.SpawnChance / 5; i++) ChanceNKRoles.Remove(selected); + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + readyNeutralKillingNum++; + + NKs = NKRoleCounts; + + if (info.AssignedCount >= info.MaxCount) + while (ChanceNKRoles.Contains(selected)) + ChanceNKRoles.Remove(selected); + + if (readyRoleNum >= playerCount) goto EndOfAssign; + + if (readyNeutralKillingNum >= nkNum) break; + } + } + } + } + + // Crewmate Roles + { + List AlwaysCrewRoles = []; + List ChanceCrewRoles = []; + + for (var i = 0; i < Roles[RoleAssignType.Crewmate].Count; i++) + { + RoleAssignInfo item = Roles[RoleAssignType.Crewmate][i]; + + if (item.SpawnChance == 100) + for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) + AlwaysCrewRoles.Add(item.Role); + else + { + for (var j = 0; j < item.SpawnChance / 5; j++) + for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) + ChanceCrewRoles.Add(item.Role); + } + } + + RoleAssignInfo[] CrewRoleCounts = AlwaysCrewRoles.Distinct().Select(GetAssignInfo).Concat(ChanceCrewRoles.Distinct().Select(GetAssignInfo)).ToArray(); + Crews = CrewRoleCounts; + + // Assign roles set to ALWAYS + if (readyRoleNum < playerCount) + { + while (AlwaysCrewRoles.Count > 0) + { + CustomRoles selected = AlwaysCrewRoles.RandomElement(); + RoleAssignInfo info = CrewRoleCounts.FirstOrDefault(x => x.Role == selected); + AlwaysCrewRoles.Remove(selected); + if (info.AssignedCount >= info.MaxCount) continue; + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + + Crews = CrewRoleCounts; + + if (readyRoleNum >= playerCount) goto EndOfAssign; + } + } + + // Assign other roles when needed + if (readyRoleNum < playerCount) + { + while (ChanceCrewRoles.Count > 0) + { + CustomRoles selected = ChanceCrewRoles.RandomElement(); + RoleAssignInfo info = CrewRoleCounts.FirstOrDefault(x => x.Role == selected); + for (var i = 0; i < info.SpawnChance / 5; i++) ChanceCrewRoles.Remove(selected); + + FinalRolesList.Add(selected); + info.AssignedCount++; + readyRoleNum++; + + Crews = CrewRoleCounts; + + if (info.AssignedCount >= info.MaxCount) + while (ChanceCrewRoles.Contains(selected)) + ChanceCrewRoles.Remove(selected); + + if (readyRoleNum >= playerCount) goto EndOfAssign; + } + } + } + + EndOfAssign: + + if (Imps.Length > 0) Logger.Info(string.Join(", ", Imps.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "ImpRoleResult"); + if (NNKs.Length > 0) Logger.Info(string.Join(", ", NNKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NNKRoleResult"); + if (NKs.Length > 0) Logger.Info(string.Join(", ", NKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NKRoleResult"); + if (Crews.Length > 0) Logger.Info(string.Join(", ", Crews.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "CrewRoleResult"); + + if (rd.Next(0, 100) < Jester.SunnyboyChance.GetInt() && FinalRolesList.Remove(CustomRoles.Jester)) FinalRolesList.Add(CustomRoles.Sunnyboy); + if (rd.Next(0, 100) < Sans.BardChance.GetInt() && FinalRolesList.Remove(CustomRoles.Sans)) FinalRolesList.Add(CustomRoles.Bard); + if (rd.Next(0, 100) < Bomber.NukerChance.GetInt() && FinalRolesList.Remove(CustomRoles.Bomber)) FinalRolesList.Add(CustomRoles.Nuker); + + Logger.Info(string.Join(", ", FinalRolesList.Select(x => x.ToString())), "RoleResults"); + + Dictionary preResult = RoleResult.ToDictionary(x => x.Key, x => x.Value); + RoleResult = AllPlayers.Zip(FinalRolesList.Shuffle()).ToDictionary(x => x.First.PlayerId, x => x.Second); + RoleResult.AddRange(preResult); + + if (RoleResult.Count < AllPlayers.Count) Logger.Error("Role assignment error: There are players who have not been assigned a role", "CustomRoleSelector"); + + return; + + void AssignRoleToEveryone(CustomRoles role) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (Main.GM.Value && pc.IsHost()) + { + RoleResult[pc.PlayerId] = CustomRoles.GM; + continue; + } RoleResult[pc.PlayerId] = role; - AllPlayers.Remove(pc); - - if (role.IsImpostor()) - { - Roles[RoleAssignType.Impostor].DoIf(x => x.Role == role, x => x.AssignedCount++); - readyImpNum++; - } - else if (role.IsNK()) - { - Roles[RoleAssignType.NeutralKilling].DoIf(x => x.Role == role, x => x.AssignedCount++); - readyNeutralKillingNum++; - } - else if (role.IsNonNK()) - { - Roles[RoleAssignType.NonKillingNeutral].DoIf(x => x.Role == role, x => x.AssignedCount++); - readyNonNeutralKillingNum++; - } - else - Roles[RoleAssignType.Crewmate].DoIf(x => x.Role == role, x => x.AssignedCount++); - - readyRoleNum++; - - Logger.Warn($"Pre-Set Role Assigned: {pc.GetRealName()} => {role}", "CustomRoleSelector"); - } - - Roles.Values.Do(l => l.DoIf(x => x.AssignedCount >= x.MaxCount, x => l.Remove(x), false)); - - RoleAssignInfo[] Imps; - RoleAssignInfo[] NNKs = []; - RoleAssignInfo[] NKs = []; - RoleAssignInfo[] Crews = []; - - // Impostor Roles - { - List AlwaysImpRoles = []; - List ChanceImpRoles = []; - - for (var i = 0; i < Roles[RoleAssignType.Impostor].Count; i++) - { - RoleAssignInfo item = Roles[RoleAssignType.Impostor][i]; - - if (item.SpawnChance == 100) - for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) - AlwaysImpRoles.Add(item.Role); - else - { - for (var j = 0; j < item.SpawnChance / 5; j++) - for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) - ChanceImpRoles.Add(item.Role); - } - } - - RoleAssignInfo[] ImpRoleCounts = AlwaysImpRoles.Distinct().Select(GetAssignInfo).Concat(ChanceImpRoles.Distinct().Select(GetAssignInfo)).ToArray(); - Imps = ImpRoleCounts; - - // Assign roles set to ALWAYS - if (readyImpNum < optImpNum) - { - while (AlwaysImpRoles.Count > 0) - { - CustomRoles selected = AlwaysImpRoles.RandomElement(); - RoleAssignInfo info = ImpRoleCounts.FirstOrDefault(x => x.Role == selected); - AlwaysImpRoles.Remove(selected); - if (info.AssignedCount >= info.MaxCount) continue; - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - readyImpNum++; - - Imps = ImpRoleCounts; - - if (readyRoleNum >= playerCount) goto EndOfAssign; - - if (readyImpNum >= optImpNum) break; - } - } - - // Assign other roles when needed - if (readyRoleNum < playerCount && readyImpNum < optImpNum) - { - while (ChanceImpRoles.Count > 0) - { - CustomRoles selected = ChanceImpRoles.RandomElement(); - RoleAssignInfo info = ImpRoleCounts.FirstOrDefault(x => x.Role == selected); - for (var i = 0; i < info.SpawnChance / 5; i++) ChanceImpRoles.Remove(selected); - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - readyImpNum++; - - Imps = ImpRoleCounts; - - if (info.AssignedCount >= info.MaxCount) - while (ChanceImpRoles.Contains(selected)) - ChanceImpRoles.Remove(selected); - - if (readyRoleNum >= playerCount) goto EndOfAssign; - - if (readyImpNum >= optImpNum) break; - } - } - } - - // Neutral Roles - { - // Neutral Non-Killing Roles - { - List AlwaysNNKRoles = []; - List ChanceNNKRoles = []; - - for (var i = 0; i < Roles[RoleAssignType.NonKillingNeutral].Count; i++) - { - RoleAssignInfo item = Roles[RoleAssignType.NonKillingNeutral][i]; - - if (item.SpawnChance == 100) - for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) - AlwaysNNKRoles.Add(item.Role); - else - { - for (var j = 0; j < item.SpawnChance / 5; j++) - for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) - ChanceNNKRoles.Add(item.Role); - } - } - - RoleAssignInfo[] NNKRoleCounts = AlwaysNNKRoles.Distinct().Select(GetAssignInfo).Concat(ChanceNNKRoles.Distinct().Select(GetAssignInfo)).ToArray(); - NNKs = NNKRoleCounts; - - // Assign roles set to ALWAYS - if (readyNonNeutralKillingNum < nnkNum) - { - while (AlwaysNNKRoles.Count > 0 && nnkNum > 0) - { - CustomRoles selected = AlwaysNNKRoles.RandomElement(); - RoleAssignInfo info = NNKRoleCounts.FirstOrDefault(x => x.Role == selected); - AlwaysNNKRoles.Remove(selected); - if (info.AssignedCount >= info.MaxCount) continue; - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - readyNonNeutralKillingNum++; - - NNKs = NNKRoleCounts; - - if (readyRoleNum >= playerCount) goto EndOfAssign; - - if (readyNonNeutralKillingNum >= nnkNum) break; - } - } - - // Assign other roles when needed - if (readyRoleNum < playerCount && readyNonNeutralKillingNum < nnkNum) - { - while (ChanceNNKRoles.Count > 0 && nnkNum > 0) - { - CustomRoles selected = ChanceNNKRoles.RandomElement(); - RoleAssignInfo info = NNKRoleCounts.FirstOrDefault(x => x.Role == selected); - for (var i = 0; i < info.SpawnChance / 5; i++) ChanceNNKRoles.Remove(selected); - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - readyNonNeutralKillingNum++; - - NNKs = NNKRoleCounts; - - if (info.AssignedCount >= info.MaxCount) - while (ChanceNNKRoles.Contains(selected)) - ChanceNNKRoles.Remove(selected); - - if (readyRoleNum >= playerCount) goto EndOfAssign; - - if (readyNonNeutralKillingNum >= nnkNum) break; - } - } - } - - // Neutral Killing Roles - { - List AlwaysNKRoles = []; - List ChanceNKRoles = []; - - for (var i = 0; i < Roles[RoleAssignType.NeutralKilling].Count; i++) - { - RoleAssignInfo item = Roles[RoleAssignType.NeutralKilling][i]; - - if (item.SpawnChance == 100) - for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) - AlwaysNKRoles.Add(item.Role); - else - { - for (var j = 0; j < item.SpawnChance / 5; j++) - for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) - ChanceNKRoles.Add(item.Role); - } - } - - RoleAssignInfo[] NKRoleCounts = AlwaysNKRoles.Distinct().Select(GetAssignInfo).Concat(ChanceNKRoles.Distinct().Select(GetAssignInfo)).ToArray(); - NKs = NKRoleCounts; - - // Assign roles set to ALWAYS - if (readyNeutralKillingNum < nkNum) - { - while (AlwaysNKRoles.Count > 0 && nkNum > 0) - { - CustomRoles selected = AlwaysNKRoles.RandomElement(); - RoleAssignInfo info = NKRoleCounts.FirstOrDefault(x => x.Role == selected); - AlwaysNKRoles.Remove(selected); - if (info.AssignedCount >= info.MaxCount) continue; - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - readyNeutralKillingNum++; - - NKs = NKRoleCounts; - - if (readyRoleNum >= playerCount) goto EndOfAssign; - - if (readyNeutralKillingNum >= nkNum) break; - } - } - - // Assign other roles when needed - if (readyRoleNum < playerCount && readyNeutralKillingNum < nkNum) - { - while (ChanceNKRoles.Count > 0 && nkNum > 0) - { - CustomRoles selected = ChanceNKRoles.RandomElement(); - RoleAssignInfo info = NKRoleCounts.FirstOrDefault(x => x.Role == selected); - for (var i = 0; i < info.SpawnChance / 5; i++) ChanceNKRoles.Remove(selected); - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - readyNeutralKillingNum++; - - NKs = NKRoleCounts; - - if (info.AssignedCount >= info.MaxCount) - while (ChanceNKRoles.Contains(selected)) - ChanceNKRoles.Remove(selected); - - if (readyRoleNum >= playerCount) goto EndOfAssign; - - if (readyNeutralKillingNum >= nkNum) break; - } - } - } - } - - // Crewmate Roles - { - List AlwaysCrewRoles = []; - List ChanceCrewRoles = []; - - for (var i = 0; i < Roles[RoleAssignType.Crewmate].Count; i++) - { - RoleAssignInfo item = Roles[RoleAssignType.Crewmate][i]; - - if (item.SpawnChance == 100) - for (var j = 0; j < item.MaxCount - item.AssignedCount; j++) - AlwaysCrewRoles.Add(item.Role); - else - { - for (var j = 0; j < item.SpawnChance / 5; j++) - for (var k = 0; k < item.MaxCount - item.AssignedCount; k++) - ChanceCrewRoles.Add(item.Role); - } - } - - RoleAssignInfo[] CrewRoleCounts = AlwaysCrewRoles.Distinct().Select(GetAssignInfo).Concat(ChanceCrewRoles.Distinct().Select(GetAssignInfo)).ToArray(); - Crews = CrewRoleCounts; - - // Assign roles set to ALWAYS - if (readyRoleNum < playerCount) - { - while (AlwaysCrewRoles.Count > 0) - { - CustomRoles selected = AlwaysCrewRoles.RandomElement(); - RoleAssignInfo info = CrewRoleCounts.FirstOrDefault(x => x.Role == selected); - AlwaysCrewRoles.Remove(selected); - if (info.AssignedCount >= info.MaxCount) continue; - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - - Crews = CrewRoleCounts; - - if (readyRoleNum >= playerCount) goto EndOfAssign; - } - } - - // Assign other roles when needed - if (readyRoleNum < playerCount) - { - while (ChanceCrewRoles.Count > 0) - { - CustomRoles selected = ChanceCrewRoles.RandomElement(); - RoleAssignInfo info = CrewRoleCounts.FirstOrDefault(x => x.Role == selected); - for (var i = 0; i < info.SpawnChance / 5; i++) ChanceCrewRoles.Remove(selected); - - FinalRolesList.Add(selected); - info.AssignedCount++; - readyRoleNum++; - - Crews = CrewRoleCounts; - - if (info.AssignedCount >= info.MaxCount) - while (ChanceCrewRoles.Contains(selected)) - ChanceCrewRoles.Remove(selected); - - if (readyRoleNum >= playerCount) goto EndOfAssign; - } - } - } - - EndOfAssign: - - if (Imps.Length > 0) Logger.Info(string.Join(", ", Imps.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "ImpRoleResult"); - if (NNKs.Length > 0) Logger.Info(string.Join(", ", NNKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NNKRoleResult"); - if (NKs.Length > 0) Logger.Info(string.Join(", ", NKs.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "NKRoleResult"); - if (Crews.Length > 0) Logger.Info(string.Join(", ", Crews.Select(x => $"{x.Role} - {x.AssignedCount}/{x.MaxCount} ({x.SpawnChance}%)")), "CrewRoleResult"); - - if (rd.Next(0, 100) < Jester.SunnyboyChance.GetInt() && FinalRolesList.Remove(CustomRoles.Jester)) FinalRolesList.Add(CustomRoles.Sunnyboy); - if (rd.Next(0, 100) < Sans.BardChance.GetInt() && FinalRolesList.Remove(CustomRoles.Sans)) FinalRolesList.Add(CustomRoles.Bard); - if (rd.Next(0, 100) < Bomber.NukerChance.GetInt() && FinalRolesList.Remove(CustomRoles.Bomber)) FinalRolesList.Add(CustomRoles.Nuker); - - Logger.Info(string.Join(", ", FinalRolesList.Select(x => x.ToString())), "RoleResults"); - - Dictionary preResult = RoleResult.ToDictionary(x => x.Key, x => x.Value); - RoleResult = AllPlayers.Zip(FinalRolesList.Shuffle()).ToDictionary(x => x.First.PlayerId, x => x.Second); - RoleResult.AddRange(preResult); - - if (RoleResult.Count < AllPlayers.Count) Logger.Error("Role assignment error: There are players who have not been assigned a role", "CustomRoleSelector"); - - return; - - void AssignRoleToEveryone(CustomRoles role) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (Main.GM.Value && pc.IsHost()) - { - RoleResult[pc.PlayerId] = CustomRoles.GM; - continue; - } - - RoleResult[pc.PlayerId] = role; - } - } - - RoleAssignInfo GetAssignInfo(CustomRoles role) - { - return Roles.Values.FirstOrDefault(x => x.Any(y => y.Role == role))?.FirstOrDefault(x => x.Role == role); } } - public static void CalculateVanillaRoleCount() + RoleAssignInfo GetAssignInfo(CustomRoles role) { - // Calculate the number of base roles - AddEngineerNum = 0; - AddScientistNum = 0; - AddShapeshifterNum = 0; - AddNoisemakerNum = 0; - AddTrackerNum = 0; - AddPhantomNum = 0; - - foreach (CustomRoles role in RoleResult.Values) - { - switch (role.GetVNRole()) - { - case CustomRoles.Scientist: - AddScientistNum++; - break; - case CustomRoles.Engineer: - AddEngineerNum++; - break; - case CustomRoles.Shapeshifter: - AddShapeshifterNum++; - break; - case CustomRoles.Noisemaker: - AddNoisemakerNum++; - break; - case CustomRoles.Tracker: - AddTrackerNum++; - break; - case CustomRoles.Phantom: - AddPhantomNum++; - break; - } - } - } - - public static void SelectAddonRoles() - { - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; - - foreach (byte id in Main.SetAddOns.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) Main.SetAddOns.Remove(id); - - AddonRolesList = []; - - foreach (CustomRoles role in Enum.GetValues()) - { - if (!role.IsAdditionRole() || role.IsGhostRole()) continue; - - switch (role) - { - case CustomRoles.Autopsy when Options.EveryoneSeesDeathReasons.GetBool(): - case CustomRoles.Mare or CustomRoles.Glow or CustomRoles.Sleep when Main.CurrentMap == MapNames.Fungle: - case CustomRoles.Madmate when Options.MadmateSpawnMode.GetInt() != 0: - case CustomRoles.Lovers or CustomRoles.LastImpostor or CustomRoles.Workhorse or CustomRoles.Undead: - case CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy: // Assigned at a different function due to role base change - continue; - } - - AddonRolesList.Add(role); - } - } - - private enum RoleAssignType - { - Impostor, - NeutralKilling, - NonKillingNeutral, - Crewmate - } - - private class RoleAssignInfo(CustomRoles role, int spawnChance, int maxCount) - { - public CustomRoles Role => role; - - public int SpawnChance => spawnChance; - - public int MaxCount => maxCount; - - public int AssignedCount { get; set; } - - public RoleOptionType OptionType { get; } = role.GetRoleOptionType(); + return Roles.Values.FirstOrDefault(x => x.Any(y => y.Role == role))?.FirstOrDefault(x => x.Role == role); } } + + public static void CalculateVanillaRoleCount() + { + // Calculate the number of base roles + AddEngineerNum = 0; + AddScientistNum = 0; + AddShapeshifterNum = 0; + AddNoisemakerNum = 0; + AddTrackerNum = 0; + AddPhantomNum = 0; + + foreach (CustomRoles role in RoleResult.Values) + { + switch (role.GetVNRole()) + { + case CustomRoles.Scientist: + AddScientistNum++; + break; + case CustomRoles.Engineer: + AddEngineerNum++; + break; + case CustomRoles.Shapeshifter: + AddShapeshifterNum++; + break; + case CustomRoles.Noisemaker: + AddNoisemakerNum++; + break; + case CustomRoles.Tracker: + AddTrackerNum++; + break; + case CustomRoles.Phantom: + AddPhantomNum++; + break; + } + } + } + + public static void SelectAddonRoles() + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + foreach (byte id in Main.SetAddOns.Keys.Where(id => Utils.GetPlayerById(id) == null).ToArray()) Main.SetAddOns.Remove(id); + + AddonRolesList = []; + + foreach (CustomRoles role in Enum.GetValues()) + { + if (!role.IsAdditionRole() || role.IsGhostRole()) continue; + + switch (role) + { + case CustomRoles.Autopsy when Options.EveryoneSeesDeathReasons.GetBool(): + case CustomRoles.Mare or CustomRoles.Glow or CustomRoles.Sleep when Main.CurrentMap == MapNames.Fungle: + case CustomRoles.Madmate when Options.MadmateSpawnMode.GetInt() != 0: + case CustomRoles.Lovers or CustomRoles.LastImpostor or CustomRoles.Workhorse or CustomRoles.Undead: + case CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy: // Assigned at a different function due to role base change + continue; + } + + AddonRolesList.Add(role); + } + } + + private enum RoleAssignType + { + Impostor, + NeutralKilling, + NonKillingNeutral, + Crewmate + } + + private class RoleAssignInfo(CustomRoles role, int spawnChance, int maxCount) + { + public CustomRoles Role => role; + + public int SpawnChance => spawnChance; + + public int MaxCount => maxCount; + + public int AssignedCount { get; set; } + + public RoleOptionType OptionType { get; } = role.GetRoleOptionType(); + } } \ No newline at end of file diff --git a/Modules/CustomRolesHelper.cs b/Modules/CustomRolesHelper.cs index 7009512b8..451d99160 100644 --- a/Modules/CustomRolesHelper.cs +++ b/Modules/CustomRolesHelper.cs @@ -10,1735 +10,1734 @@ using EHR.Neutral; using UnityEngine; using Monitor = EHR.Crewmate.Monitor; -namespace EHR +namespace EHR; + +internal static class CustomRolesHelper { - internal static class CustomRolesHelper + public static bool CanCheck = false; + + private static readonly List OnlySpawnsWithPetsRoleList = + [ + CustomRoles.Tunneler, + CustomRoles.Tornado, + CustomRoles.Swiftclaw, + CustomRoles.Adventurer, + CustomRoles.Sentry, + CustomRoles.Cherokious, + CustomRoles.Chemist, + CustomRoles.Evolver, + CustomRoles.ToiletMaster, + CustomRoles.Telekinetic, + CustomRoles.Dad, + CustomRoles.Whisperer, + CustomRoles.Wizard, + CustomRoles.NoteKiller, + CustomRoles.Weatherman, + + // Add-ons + CustomRoles.Energetic, + + // HnS + CustomRoles.Jet, + CustomRoles.Dasher + ]; + + public static bool IsForOtherGameMode(this CustomRoles role) { - public static bool CanCheck = false; - - private static readonly List OnlySpawnsWithPetsRoleList = - [ - CustomRoles.Tunneler, - CustomRoles.Tornado, - CustomRoles.Swiftclaw, - CustomRoles.Adventurer, - CustomRoles.Sentry, - CustomRoles.Cherokious, - CustomRoles.Chemist, - CustomRoles.Evolver, - CustomRoles.ToiletMaster, - CustomRoles.Telekinetic, - CustomRoles.Dad, - CustomRoles.Whisperer, - CustomRoles.Wizard, - CustomRoles.NoteKiller, - CustomRoles.Weatherman, - - // Add-ons - CustomRoles.Energetic, - - // HnS - CustomRoles.Jet, - CustomRoles.Dasher - ]; - - public static bool IsForOtherGameMode(this CustomRoles role) - { - return HnSManager.AllHnSRoles.Contains(role) || role is - CustomRoles.KB_Normal or - CustomRoles.Killer or - CustomRoles.Tasker or - CustomRoles.Potato or - CustomRoles.Runner or - CustomRoles.CTFPlayer or - CustomRoles.NDPlayer or - CustomRoles.RRPlayer; - } - - public static RoleBase GetRoleClass(this CustomRoles role) - { - RoleBase roleClass = role switch - { - // Roles that use the same code as another role need to be handled here - CustomRoles.Nuker => new Bomber(), - CustomRoles.Undertaker => new Assassin(), - CustomRoles.Chameleon => new Swooper(), - CustomRoles.BloodKnight => new Wildling(), - CustomRoles.HexMaster => new Witch(), - CustomRoles.Imitator => new Greedier(), - CustomRoles.Jinx => new CursedWolf(), - CustomRoles.Juggernaut => new Sans(), - CustomRoles.Medusa => new Cleaner(), - CustomRoles.Poisoner => new Vampire(), - CustomRoles.Reckless => new Sans(), - CustomRoles.Ritualist => new EvilDiviner(), - CustomRoles.Wraith => new Swooper(), - CustomRoles.Goose => new Penguin(), - CustomRoles.Monitor => new AntiAdminer(), - - // Else, the role class is the role name - if the class doesn't exist, it defaults to VanillaRole - _ => Main.AllRoleClasses.FirstOrDefault(x => x.GetType().Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)) ?? new VanillaRole() - }; - - return Activator.CreateInstance(roleClass.GetType()) as RoleBase; - } - - public static CustomRoles GetVNRole(this CustomRoles role, bool checkDesyncRole = false) - { - if (role.IsGhostRole()) return CustomRoles.GuardianAngel; - if (role.IsVanilla()) return role; - if (checkDesyncRole && role.IsDesyncRole()) return Enum.Parse(role.GetDYRole() + "EHR"); - if (Options.UsePhantomBasis.GetBool() && role.SimpleAbilityTrigger()) return CustomRoles.Phantom; - if ((Options.UseUnshiftTrigger.GetBool() || role.AlwaysUsesUnshift()) && role.SimpleAbilityTrigger()) return CustomRoles.Shapeshifter; - - bool UsePets = Options.UsePets.GetBool(); - - return role switch - { - CustomRoles.Sniper => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Jester => Jester.JesterCanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Mayor => Mayor.MayorHasPortableButton.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Monitor => Monitor.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Vulture => Vulture.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Opportunist => Opportunist.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Vindicator => CustomRoles.Impostor, - CustomRoles.Snitch => CustomRoles.Crewmate, - CustomRoles.ParityCop => CustomRoles.Crewmate, - CustomRoles.Marshall => CustomRoles.Crewmate, - CustomRoles.SabotageMaster => CustomRoles.Engineer, - CustomRoles.Mafia => Options.LegacyMafia.GetBool() ? CustomRoles.Shapeshifter : CustomRoles.Impostor, - CustomRoles.Terrorist => CustomRoles.Engineer, - CustomRoles.Executioner => CustomRoles.Crewmate, - CustomRoles.Lawyer => CustomRoles.Crewmate, - CustomRoles.NiceSwapper => CustomRoles.Crewmate, - CustomRoles.Ignitor => CustomRoles.Crewmate, - CustomRoles.Jailor => UsePets && Jailor.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.Vampire => CustomRoles.Impostor, - CustomRoles.BountyHunter => CustomRoles.Impostor, - CustomRoles.Trickster => CustomRoles.Impostor, - CustomRoles.Witch => CustomRoles.Impostor, - CustomRoles.Agitater => CustomRoles.Impostor, - CustomRoles.EvilDiviner => CustomRoles.Impostor, - CustomRoles.Wildling => Wildling.CanShapeshiftOpt.GetBool() ? CustomRoles.Shapeshifter : CustomRoles.Impostor, - CustomRoles.Morphling => CustomRoles.Shapeshifter, - CustomRoles.Warlock => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.SerialKiller => CustomRoles.Impostor, - CustomRoles.FireWorks => CustomRoles.Shapeshifter, - CustomRoles.SpeedBooster => CustomRoles.Crewmate, - CustomRoles.Dictator => CustomRoles.Crewmate, - CustomRoles.DoubleAgent => CustomRoles.Crewmate, - CustomRoles.Inhibitor => CustomRoles.Impostor, - CustomRoles.Occultist => CustomRoles.Impostor, - CustomRoles.Kidnapper => CustomRoles.Shapeshifter, - CustomRoles.Wasp => CustomRoles.Impostor, - CustomRoles.Assumer => CustomRoles.Impostor, - CustomRoles.Augmenter => CustomRoles.Shapeshifter, - CustomRoles.Ventriloquist => CustomRoles.Impostor, - CustomRoles.Echo => CustomRoles.Shapeshifter, - CustomRoles.Abyssbringer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Overheat => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Hypnotist => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Generator => CustomRoles.Shapeshifter, - CustomRoles.Blackmailer => CustomRoles.Impostor, - CustomRoles.Commander => CustomRoles.Shapeshifter, - CustomRoles.Freezer => CustomRoles.Shapeshifter, - CustomRoles.Changeling => CustomRoles.Shapeshifter, - CustomRoles.Swapster => CustomRoles.Shapeshifter, - CustomRoles.Kamikaze => CustomRoles.Impostor, - CustomRoles.Librarian => CustomRoles.Shapeshifter, - CustomRoles.Cantankerous => CustomRoles.Impostor, - CustomRoles.Swiftclaw => CustomRoles.Impostor, - CustomRoles.YinYanger => CustomRoles.Impostor, - CustomRoles.Duellist => CustomRoles.Shapeshifter, - CustomRoles.Consort => CustomRoles.Impostor, - CustomRoles.Framer => CustomRoles.Impostor, - CustomRoles.Mafioso => CustomRoles.Impostor, - CustomRoles.Chronomancer => CustomRoles.Impostor, - CustomRoles.Nullifier => CustomRoles.Impostor, - CustomRoles.Stealth => CustomRoles.Impostor, - CustomRoles.Penguin => CustomRoles.Impostor, - CustomRoles.Sapper => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Mastermind => CustomRoles.Impostor, - CustomRoles.RiftMaker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Gambler => CustomRoles.Impostor, - CustomRoles.Hitman => CustomRoles.Shapeshifter, - CustomRoles.Saboteur => CustomRoles.Impostor, - CustomRoles.Doctor => CustomRoles.Scientist, - CustomRoles.Tracefinder => CustomRoles.Scientist, - CustomRoles.Puppeteer => CustomRoles.Impostor, - CustomRoles.TimeThief => CustomRoles.Impostor, - CustomRoles.EvilTracker => CustomRoles.Shapeshifter, - CustomRoles.Paranoia => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.TimeMaster => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Cleanser => CustomRoles.Crewmate, - CustomRoles.Miner => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Psychic => CustomRoles.Crewmate, - CustomRoles.Needy => CustomRoles.Crewmate, - CustomRoles.Twister => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.SuperStar => CustomRoles.Crewmate, - CustomRoles.Hacker => CustomRoles.Shapeshifter, - CustomRoles.Visionary => CustomRoles.Shapeshifter, - CustomRoles.Assassin => CustomRoles.Phantom, - CustomRoles.Undertaker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Luckey => CustomRoles.Crewmate, - CustomRoles.CyberStar => CustomRoles.Crewmate, - CustomRoles.Demolitionist => CustomRoles.Crewmate, - CustomRoles.Ventguard => CustomRoles.Engineer, - CustomRoles.Express => CustomRoles.Crewmate, - CustomRoles.NiceEraser => CustomRoles.Crewmate, - CustomRoles.TaskManager => CustomRoles.Crewmate, - CustomRoles.Adventurer => CustomRoles.Engineer, - CustomRoles.Randomizer => CustomRoles.Crewmate, - CustomRoles.Beacon => CustomRoles.Crewmate, - CustomRoles.Rabbit => CustomRoles.Crewmate, - CustomRoles.Shiftguard => CustomRoles.Crewmate, - CustomRoles.Mole => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Markseeker => CustomRoles.Crewmate, - CustomRoles.Sentinel => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Electric => CustomRoles.Crewmate, - CustomRoles.Philantropist => CustomRoles.Crewmate, - CustomRoles.Tornado => CustomRoles.Crewmate, - CustomRoles.Druid => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Catcher => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Insight => CustomRoles.Crewmate, - CustomRoles.Tunneler => CustomRoles.Crewmate, - CustomRoles.Detour => CustomRoles.Crewmate, - CustomRoles.Dad => CustomRoles.Engineer, - CustomRoles.Drainer => CustomRoles.Engineer, - CustomRoles.Benefactor => CustomRoles.Crewmate, - CustomRoles.GuessManagerRole => CustomRoles.Crewmate, - CustomRoles.Bane => CustomRoles.Crewmate, - CustomRoles.Transmitter => CustomRoles.Crewmate, - CustomRoles.Ankylosaurus => CustomRoles.Crewmate, - CustomRoles.Leery => CustomRoles.Crewmate, - CustomRoles.Altruist => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Negotiator => CustomRoles.Crewmate, - CustomRoles.Grappler => CustomRoles.Crewmate, - CustomRoles.Journalist => CustomRoles.Crewmate, - CustomRoles.Whisperer => CustomRoles.Crewmate, - CustomRoles.Car => CustomRoles.Crewmate, - CustomRoles.President => CustomRoles.Crewmate, - CustomRoles.Oxyman => CustomRoles.Engineer, - CustomRoles.Chef => CustomRoles.Crewmate, - CustomRoles.Lyncher => CustomRoles.Crewmate, - CustomRoles.Adrenaline => CustomRoles.Crewmate, - CustomRoles.Safeguard => CustomRoles.Crewmate, - CustomRoles.Clairvoyant => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Rhapsode => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Telekinetic => CustomRoles.Engineer, - CustomRoles.Autocrat => CustomRoles.Crewmate, - CustomRoles.Inquirer => CustomRoles.Crewmate, - CustomRoles.Soothsayer => CustomRoles.Crewmate, - CustomRoles.LovingCrewmate => CustomRoles.Crewmate, - CustomRoles.LovingImpostor => CustomRoles.Impostor, - CustomRoles.ToiletMaster => CustomRoles.Crewmate, - CustomRoles.Sentry => CustomRoles.Crewmate, - CustomRoles.Perceiver => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Convener => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Mathematician => CustomRoles.Crewmate, - CustomRoles.Nightmare => CustomRoles.Crewmate, - CustomRoles.CameraMan => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Spy => CustomRoles.Crewmate, - CustomRoles.Ricochet => CustomRoles.Crewmate, - CustomRoles.Tether => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Doormaster => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Aid => UsePets && Aid.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.Socialite => UsePets && Socialite.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.Escort => UsePets && Escort.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.DonutDelivery => UsePets && DonutDelivery.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.Gaulois => UsePets && Gaulois.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.Analyst => UsePets && Analyst.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, - CustomRoles.Escapee => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.NiceGuesser => CustomRoles.Crewmate, - CustomRoles.EvilGuesser => CustomRoles.Impostor, - CustomRoles.Detective => CustomRoles.Crewmate, - CustomRoles.God => CustomRoles.Crewmate, - CustomRoles.Tank => CustomRoles.Engineer, - CustomRoles.Technician => Technician.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.GuardianAngelEHR => CustomRoles.GuardianAngel, - CustomRoles.Zombie => CustomRoles.Impostor, - CustomRoles.Mario => CustomRoles.Engineer, - CustomRoles.AntiAdminer => AntiAdminer.EnableExtraAbility.GetBool() ? CustomRoles.Shapeshifter : CustomRoles.Impostor, - CustomRoles.Sans => CustomRoles.Impostor, - CustomRoles.Bomber => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Nuker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.BoobyTrap => CustomRoles.Impostor, - CustomRoles.Scavenger => CustomRoles.Impostor, - CustomRoles.Transporter => CustomRoles.Crewmate, - CustomRoles.Veteran => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Capitalism => CustomRoles.Impostor, - CustomRoles.Bodyguard => CustomRoles.Crewmate, - CustomRoles.Grenadier => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Lighter => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.SecurityGuard => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Gangster => CustomRoles.Impostor, - CustomRoles.Cleaner => CustomRoles.Impostor, - CustomRoles.Konan => CustomRoles.Crewmate, - CustomRoles.Divinator => CustomRoles.Crewmate, - CustomRoles.Oracle => CustomRoles.Crewmate, - CustomRoles.BallLightning => CustomRoles.Impostor, - CustomRoles.Greedier => CustomRoles.Impostor, - CustomRoles.Workaholic => CustomRoles.Engineer, - CustomRoles.Amnesiac => Amnesiac.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Speedrunner => CustomRoles.Crewmate, - CustomRoles.CursedWolf => CustomRoles.Impostor, - CustomRoles.Collector => CustomRoles.Crewmate, - CustomRoles.NecroGuesser => CustomRoles.Crewmate, - CustomRoles.SchrodingersCat => CustomRoles.Crewmate, - CustomRoles.ImperiusCurse => CustomRoles.Shapeshifter, - CustomRoles.QuickShooter => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Eraser => CustomRoles.Impostor, - CustomRoles.OverKiller => CustomRoles.Impostor, - CustomRoles.Hangman => CustomRoles.Shapeshifter, - CustomRoles.Sunnyboy => CustomRoles.Scientist, - CustomRoles.Phantasm => Options.PhantomCanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, - CustomRoles.Judge => CustomRoles.Crewmate, - CustomRoles.Councillor => CustomRoles.Impostor, - CustomRoles.Mortician => CustomRoles.Crewmate, - CustomRoles.Mediumshiper => CustomRoles.Crewmate, - CustomRoles.Bard => CustomRoles.Impostor, - CustomRoles.Swooper => CustomRoles.Impostor, - CustomRoles.Crewpostor => CustomRoles.Engineer, - CustomRoles.Cherokious => CustomRoles.Engineer, - CustomRoles.Observer => CustomRoles.Crewmate, - CustomRoles.DovesOfNeace => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, - CustomRoles.Disperser => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, - CustomRoles.Camouflager => CustomRoles.Shapeshifter, - CustomRoles.Dazzler => CustomRoles.Shapeshifter, - CustomRoles.Devourer => CustomRoles.Shapeshifter, - CustomRoles.Deathpact => CustomRoles.Shapeshifter, - CustomRoles.Bloodhound => CustomRoles.Crewmate, - CustomRoles.Tracker => CustomRoles.Crewmate, - CustomRoles.Merchant => CustomRoles.Crewmate, - CustomRoles.Lookout => CustomRoles.Crewmate, - CustomRoles.Guardian => CustomRoles.Crewmate, - CustomRoles.Enigma => CustomRoles.Crewmate, - CustomRoles.Addict => CustomRoles.Engineer, - CustomRoles.Alchemist => CustomRoles.Engineer, // Needs to vent to use the invisibility potion - CustomRoles.Chameleon => CustomRoles.Engineer, - CustomRoles.Lurker => CustomRoles.Impostor, - CustomRoles.Doomsayer => CustomRoles.Crewmate, - CustomRoles.Godfather => CustomRoles.Impostor, - CustomRoles.Silencer => Silencer.SilenceMode.GetValue() == 1 ? CustomRoles.Shapeshifter : CustomRoles.Impostor, - CustomRoles.NoteKiller => CustomRoles.Crewmate, - - // Vanilla roles (just in case) - CustomRoles.ImpostorEHR => CustomRoles.Impostor, - CustomRoles.PhantomEHR => CustomRoles.Phantom, - CustomRoles.ShapeshifterEHR => CustomRoles.Shapeshifter, - CustomRoles.CrewmateEHR => CustomRoles.Crewmate, - CustomRoles.EngineerEHR => CustomRoles.Engineer, - CustomRoles.ScientistEHR => CustomRoles.Scientist, - CustomRoles.TrackerEHR => CustomRoles.Tracker, - CustomRoles.NoisemakerEHR => CustomRoles.Noisemaker, - - // Hide And Seek - CustomRoles.Hider => CustomRoles.Crewmate, - CustomRoles.Seeker => CustomRoles.Impostor, - CustomRoles.Fox => CustomRoles.Crewmate, - CustomRoles.Troll => CustomRoles.Crewmate, - CustomRoles.Jumper => CustomRoles.Engineer, - CustomRoles.Detector => CustomRoles.Crewmate, - CustomRoles.Jet => CustomRoles.Crewmate, - CustomRoles.Dasher => CustomRoles.Impostor, - CustomRoles.Locator => CustomRoles.Impostor, - CustomRoles.Venter => CustomRoles.Impostor, - CustomRoles.Agent => CustomRoles.Impostor, - CustomRoles.Taskinator => CustomRoles.Crewmate, - - // Room Rush - CustomRoles.RRPlayer => CustomRoles.Crewmate, - - _ => role.IsImpostor() ? CustomRoles.Impostor : CustomRoles.Crewmate - }; - } - - public static CustomRoles GetErasedRole(this CustomRoles role) - { - if (role.IsVanilla()) return role; - - CustomRoles vnRole = role.GetVNRole(true); - - return vnRole switch - { - CustomRoles.Crewmate => CustomRoles.CrewmateEHR, - CustomRoles.Engineer => CustomRoles.EngineerEHR, - CustomRoles.Noisemaker => CustomRoles.NoisemakerEHR, - CustomRoles.Tracker => CustomRoles.TrackerEHR, - CustomRoles.Scientist => CustomRoles.ScientistEHR, - CustomRoles.Impostor when role.IsCrewmate() => CustomRoles.CrewmateEHR, - CustomRoles.Impostor => CustomRoles.ImpostorEHR, - CustomRoles.Phantom => CustomRoles.PhantomEHR, - CustomRoles.Shapeshifter => CustomRoles.ShapeshifterEHR, - _ => role.IsImpostor() ? CustomRoles.ImpostorEHR : CustomRoles.CrewmateEHR - }; - } - - public static RoleTypes GetDYRole(this CustomRoles role, bool load = false) - { - if (!load && Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool() && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Phantom; - - if (!load && Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool() && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Shapeshifter; - - bool UsePets = !load && Options.UsePets.GetBool(); - - return role switch - { - // SoloKombat - CustomRoles.KB_Normal => RoleTypes.Impostor, - // FFA - CustomRoles.Killer => RoleTypes.Impostor, - // Move And Stop - CustomRoles.Tasker => RoleTypes.Crewmate, - // Hot Potato - CustomRoles.Potato => RoleTypes.Crewmate, - // Speedrun - CustomRoles.Runner => RoleTypes.Crewmate, - // Capture The Flag - CustomRoles.CTFPlayer => RoleTypes.Shapeshifter, - // Natural Disasters - CustomRoles.NDPlayer => RoleTypes.Crewmate, - // Standard - CustomRoles.Sheriff => UsePets && Sheriff.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Crusader => UsePets && Crusader.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.CopyCat => UsePets && CopyCat.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Refugee => RoleTypes.Impostor, - CustomRoles.Agitater => RoleTypes.Impostor, - CustomRoles.Monarch => UsePets && Monarch.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Deputy => UsePets && Deputy.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Arsonist => RoleTypes.Impostor, - CustomRoles.Jackal => RoleTypes.Impostor, - CustomRoles.Medusa => RoleTypes.Impostor, - CustomRoles.Sidekick => RoleTypes.Impostor, - CustomRoles.SwordsMan => UsePets && SwordsMan.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Innocent => RoleTypes.Impostor, - CustomRoles.Amnesiac when Amnesiac.RememberMode.GetValue() == 1 => RoleTypes.Impostor, - CustomRoles.Pelican => RoleTypes.Impostor, - CustomRoles.Aid => UsePets && Aid.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Socialite => UsePets && Socialite.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Escort => UsePets && Escort.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.DonutDelivery => UsePets && DonutDelivery.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Gaulois => UsePets && Gaulois.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Analyst => UsePets && Analyst.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Witness => UsePets && Options.WitnessUsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Goose => RoleTypes.Impostor, - CustomRoles.Pursuer => RoleTypes.Impostor, - CustomRoles.Revolutionist => RoleTypes.Impostor, - CustomRoles.FFF => RoleTypes.Impostor, - CustomRoles.Medic => UsePets && Medic.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Gamer => RoleTypes.Impostor, - CustomRoles.HexMaster => RoleTypes.Impostor, - CustomRoles.Wraith => RoleTypes.Impostor, - CustomRoles.Glitch => RoleTypes.Impostor, - CustomRoles.Jailor => UsePets && Jailor.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Juggernaut => RoleTypes.Impostor, - CustomRoles.Jinx => RoleTypes.Impostor, - CustomRoles.DarkHide => RoleTypes.Impostor, - CustomRoles.Provocateur => RoleTypes.Impostor, - CustomRoles.BloodKnight => RoleTypes.Impostor, - CustomRoles.Poisoner => RoleTypes.Impostor, - CustomRoles.NSerialKiller => RoleTypes.Impostor, - CustomRoles.Weatherman => RoleTypes.Impostor, - CustomRoles.Vortex => RoleTypes.Impostor, - CustomRoles.Beehive => RoleTypes.Impostor, - CustomRoles.RouleteGrandeur => RoleTypes.Impostor, - CustomRoles.Nonplus => RoleTypes.Impostor, - CustomRoles.Tremor => RoleTypes.Impostor, - CustomRoles.Evolver => RoleTypes.Impostor, - CustomRoles.Rogue => RoleTypes.Impostor, - CustomRoles.Patroller => RoleTypes.Impostor, - CustomRoles.Simon => RoleTypes.Impostor, - CustomRoles.Chemist => RoleTypes.Impostor, - CustomRoles.Samurai => RoleTypes.Impostor, - CustomRoles.QuizMaster => RoleTypes.Impostor, - CustomRoles.Bargainer => RoleTypes.Impostor, - CustomRoles.Tiger => RoleTypes.Impostor, - CustomRoles.SoulHunter => RoleTypes.Impostor, - CustomRoles.Enderman => RoleTypes.Impostor, - CustomRoles.Mycologist => RoleTypes.Impostor, - CustomRoles.Bubble => RoleTypes.Impostor, - CustomRoles.Hookshot => RoleTypes.Impostor, - CustomRoles.Sprayer => RoleTypes.Impostor, - CustomRoles.PlagueDoctor => RoleTypes.Impostor, - CustomRoles.Curser => RoleTypes.Impostor, - CustomRoles.Postman => RoleTypes.Impostor, - CustomRoles.Shifter => RoleTypes.Impostor, - CustomRoles.Impartial => RoleTypes.Impostor, - CustomRoles.Gaslighter => RoleTypes.Impostor, - CustomRoles.Backstabber => RoleTypes.Impostor, - CustomRoles.Predator => RoleTypes.Impostor, - CustomRoles.Reckless => RoleTypes.Impostor, - CustomRoles.Magician => RoleTypes.Impostor, - CustomRoles.WeaponMaster => RoleTypes.Impostor, - CustomRoles.Pyromaniac => RoleTypes.Impostor, - CustomRoles.Eclipse => RoleTypes.Impostor, - CustomRoles.Vengeance => RoleTypes.Impostor, - CustomRoles.HeadHunter => RoleTypes.Impostor, - CustomRoles.Imitator => RoleTypes.Impostor, - CustomRoles.Werewolf => RoleTypes.Impostor, - CustomRoles.Bandit => RoleTypes.Impostor, - CustomRoles.Maverick => RoleTypes.Impostor, - CustomRoles.Parasite => RoleTypes.Shapeshifter, - CustomRoles.Totocalcio => RoleTypes.Impostor, - CustomRoles.Romantic => RoleTypes.Impostor, - CustomRoles.VengefulRomantic => RoleTypes.Impostor, - CustomRoles.RuthlessRomantic => RoleTypes.Impostor, - CustomRoles.Succubus => RoleTypes.Impostor, - CustomRoles.Necromancer => RoleTypes.Impostor, - CustomRoles.Deathknight => RoleTypes.Impostor, - CustomRoles.Virus => RoleTypes.Impostor, - CustomRoles.Farseer => UsePets && Farseer.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, - CustomRoles.Ritualist => RoleTypes.Impostor, - CustomRoles.Pickpocket => RoleTypes.Impostor, - CustomRoles.Traitor => RoleTypes.Impostor, - CustomRoles.PlagueBearer => RoleTypes.Impostor, - CustomRoles.Pestilence => RoleTypes.Impostor, - CustomRoles.Spiritcaller => RoleTypes.Impostor, - CustomRoles.Doppelganger => RoleTypes.Impostor, - CustomRoles.Wizard => RoleTypes.Shapeshifter, - _ => RoleTypes.GuardianAngel - }; - } - - public static bool IsAdditionRole(this CustomRoles role) - { - return role > CustomRoles.NotAssigned; - } - - public static bool IsNonNK(this CustomRoles role, bool check = false) - { - return (!check && role == CustomRoles.Arsonist && CanCheck && Options.IsLoaded && Options.ArsonistCanIgniteAnytime != null && !Options.ArsonistCanIgniteAnytime.GetBool()) || role.GetNeutralRoleCategory() is RoleOptionType.Neutral_Benign or RoleOptionType.Neutral_Evil; - } - - public static bool IsNK(this CustomRoles role, bool check = false) - { - return (role == CustomRoles.Arsonist && (check || !CanCheck || !Options.IsLoaded || Options.ArsonistCanIgniteAnytime == null || Options.ArsonistCanIgniteAnytime.GetBool())) || role is - CustomRoles.Jackal or - CustomRoles.Glitch or - CustomRoles.Sidekick or - CustomRoles.HexMaster or - CustomRoles.Doppelganger or - CustomRoles.Succubus or - CustomRoles.Gamer or - CustomRoles.Crewpostor or - CustomRoles.Cherokious or - CustomRoles.Necromancer or - CustomRoles.Agitater or - CustomRoles.Wraith or - CustomRoles.Bandit or - CustomRoles.Medusa or - CustomRoles.Pelican or - CustomRoles.Convict or - CustomRoles.DarkHide or - CustomRoles.Juggernaut or - CustomRoles.Jinx or - CustomRoles.Poisoner or - CustomRoles.Refugee or - CustomRoles.Gaslighter or - CustomRoles.Simon or - CustomRoles.Patroller or - CustomRoles.Rogue or - CustomRoles.Parasite or - CustomRoles.NSerialKiller or - CustomRoles.Weatherman or - CustomRoles.NoteKiller or - CustomRoles.Vortex or - CustomRoles.Beehive or - CustomRoles.RouleteGrandeur or - CustomRoles.Nonplus or - CustomRoles.Tremor or - CustomRoles.Evolver or - CustomRoles.Chemist or - CustomRoles.QuizMaster or - CustomRoles.Samurai or - CustomRoles.Bargainer or - CustomRoles.Tiger or - CustomRoles.Enderman or - CustomRoles.Mycologist or - CustomRoles.Bubble or - CustomRoles.Hookshot or - CustomRoles.Sprayer or - CustomRoles.Magician or - CustomRoles.WeaponMaster or - CustomRoles.Reckless or - CustomRoles.Eclipse or - CustomRoles.Pyromaniac or - CustomRoles.Vengeance or - CustomRoles.HeadHunter or - CustomRoles.Imitator or - CustomRoles.Werewolf or - CustomRoles.Ritualist or - CustomRoles.Pickpocket or - CustomRoles.PlagueDoctor or - CustomRoles.Traitor or - CustomRoles.Virus or - CustomRoles.BloodKnight or - CustomRoles.Spiritcaller or - CustomRoles.RuthlessRomantic or - CustomRoles.PlagueBearer or - CustomRoles.Pestilence; - } - - public static bool IsSnitchTarget(this CustomRoles role) - { - return role.IsNK() || role.Is(Team.Impostor); - } - - public static bool IsGhostRole(this CustomRoles role) - { - return role == CustomRoles.EvilSpirit || GhostRolesManager.CreateGhostRoleInstance(role, true) != null; - } - - public static bool IsImpostor(this CustomRoles role) - { - return (role == CustomRoles.DoubleAgent && Main.HasJustStarted) || role is - CustomRoles.Impostor or - CustomRoles.ImpostorEHR or - CustomRoles.Phantom or - CustomRoles.PhantomEHR or - CustomRoles.Shapeshifter or - CustomRoles.ShapeshifterEHR or - CustomRoles.LovingImpostor or - CustomRoles.Godfather or - CustomRoles.EvilDiviner or - CustomRoles.Wildling or - CustomRoles.Silencer or - CustomRoles.Morphling or - CustomRoles.BountyHunter or - CustomRoles.Vampire or - CustomRoles.Witch or - CustomRoles.Vindicator or - CustomRoles.Zombie or - CustomRoles.Warlock or - CustomRoles.Assassin or - CustomRoles.Undertaker or - CustomRoles.Hacker or - CustomRoles.Visionary or - CustomRoles.Miner or - CustomRoles.Escapee or - CustomRoles.SerialKiller or - CustomRoles.Overheat or - CustomRoles.Underdog or - CustomRoles.Abyssbringer or - CustomRoles.Echo or - CustomRoles.Ventriloquist or - CustomRoles.Augmenter or - CustomRoles.Inhibitor or - CustomRoles.Kidnapper or - CustomRoles.Occultist or - CustomRoles.Wasp or - CustomRoles.Hypnotist or - CustomRoles.Assumer or - CustomRoles.Generator or - CustomRoles.Blackmailer or - CustomRoles.Commander or - CustomRoles.Freezer or - CustomRoles.Changeling or - CustomRoles.Swapster or - CustomRoles.Kamikaze or - CustomRoles.Librarian or - CustomRoles.Cantankerous or - CustomRoles.Swiftclaw or - CustomRoles.Duellist or - CustomRoles.YinYanger or - CustomRoles.Consort or - CustomRoles.Framer or - CustomRoles.Mafioso or - CustomRoles.Nullifier or - CustomRoles.Chronomancer or - CustomRoles.Stealth or - CustomRoles.Penguin or - CustomRoles.Sapper or - CustomRoles.Hitman or - CustomRoles.RiftMaker or - CustomRoles.Mastermind or - CustomRoles.Gambler or - CustomRoles.Councillor or - CustomRoles.Saboteur or - CustomRoles.Puppeteer or - CustomRoles.TimeThief or - CustomRoles.Trickster or - CustomRoles.Mafia or - CustomRoles.Minimalism or - CustomRoles.FireWorks or - CustomRoles.Sniper or - CustomRoles.EvilTracker or - CustomRoles.EvilGuesser or - CustomRoles.AntiAdminer or - CustomRoles.Sans or - CustomRoles.Bomber or - CustomRoles.Nuker or - CustomRoles.Scavenger or - CustomRoles.BoobyTrap or - CustomRoles.Capitalism or - CustomRoles.Gangster or - CustomRoles.Cleaner or - CustomRoles.BallLightning or - CustomRoles.Greedier or - CustomRoles.CursedWolf or - CustomRoles.ImperiusCurse or - CustomRoles.QuickShooter or - CustomRoles.Eraser or - CustomRoles.OverKiller or - CustomRoles.Hangman or - CustomRoles.Bard or - CustomRoles.Swooper or - CustomRoles.Disperser or - CustomRoles.Dazzler or - CustomRoles.Deathpact or - CustomRoles.Devourer or - CustomRoles.Camouflager or - CustomRoles.Twister or - CustomRoles.Lurker; - } - - public static bool IsNeutral(this CustomRoles role, bool check = false) - { - return role.IsNK(check) || role.IsNonNK(check); - } - - public static bool IsAbleToBeSidekicked(this CustomRoles role) - { - return !role.IsRecruitingRole() && role is not - CustomRoles.Deathknight; - } - - public static bool IsEvilAddon(this CustomRoles role) - { - return role is - CustomRoles.Madmate or - CustomRoles.Egoist or - CustomRoles.Charmed or - CustomRoles.Recruit or - CustomRoles.Contagious or - CustomRoles.Rascal; - } - - public static bool IsRecruitingRole(this CustomRoles role) - { - return role is - CustomRoles.Jackal or - CustomRoles.Succubus or - CustomRoles.Necromancer or - CustomRoles.Virus or - CustomRoles.Spiritcaller; - } - - public static bool IsMadmate(this CustomRoles role) - { - return role is - CustomRoles.Crewpostor or - CustomRoles.Convict or - CustomRoles.Refugee or - CustomRoles.Parasite; - } - - public static bool IsTasklessCrewmate(this CustomRoles role) - { - return !role.UsesPetInsteadOfKill() && role.IsCrewmate() && role.IsDesyncRole(); - } - - public static bool PetActivatedAbility(this CustomRoles role) - { - if (CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated()) return true; - - if (!Options.UsePets.GetBool()) return false; - - if (role.UsesPetInsteadOfKill()) return true; - - Type type = role.GetRoleClass().GetType(); - return type.GetMethod("OnPet")?.DeclaringType == type; - } - - public static bool UsesPetInsteadOfKill(this CustomRoles role) - { - return Options.UsePets.GetBool() && role switch - { - CustomRoles.Gaulois when Gaulois.UsePet.GetBool() => true, - CustomRoles.Aid when Aid.UsePet.GetBool() => true, - CustomRoles.Socialite when Socialite.UsePet.GetBool() => true, - CustomRoles.Escort when Escort.UsePet.GetBool() => true, - CustomRoles.DonutDelivery when DonutDelivery.UsePet.GetBool() => true, - CustomRoles.Analyst when Analyst.UsePet.GetBool() => true, - CustomRoles.Jailor when Jailor.UsePet.GetBool() => true, - CustomRoles.Sheriff when Sheriff.UsePet.GetBool() => true, - CustomRoles.SwordsMan when SwordsMan.UsePet.GetBool() => true, - CustomRoles.Medic when Medic.UsePet.GetBool() => true, - CustomRoles.Monarch when Monarch.UsePet.GetBool() => true, - CustomRoles.CopyCat when CopyCat.UsePet.GetBool() => true, - CustomRoles.Farseer when Farseer.UsePet.GetBool() => true, - CustomRoles.Deputy when Deputy.UsePet.GetBool() => true, - CustomRoles.Crusader when Crusader.UsePet.GetBool() => true, - CustomRoles.Witness when Options.WitnessUsePet.GetBool() => true, - - // Speedrun - CustomRoles.Runner => true, - - _ => false - }; - } - - public static bool CancelsVote(this CustomRoles role) - { - return role switch - { - CustomRoles.Divinator when Divinator.CancelVote.GetBool() => true, - CustomRoles.Soothsayer when Soothsayer.CancelVote.GetBool() => true, - CustomRoles.Oracle when Oracle.CancelVote.GetBool() => true, - CustomRoles.Eraser when Eraser.CancelVote.GetBool() => true, - CustomRoles.Tether when Tether.CancelVote.GetBool() => true, - CustomRoles.Ricochet when Ricochet.CancelVote.GetBool() => true, - CustomRoles.Cleanser when Cleanser.CancelVote.GetBool() => true, - CustomRoles.NiceEraser when NiceEraser.CancelVote.GetBool() => true, - CustomRoles.Scout when Scout.CancelVote.GetBool() => true, - CustomRoles.Markseeker when Markseeker.CancelVote.GetBool() => true, - CustomRoles.Godfather when Options.GodfatherCancelVote.GetBool() => true, - CustomRoles.Socialite when Socialite.CancelVote.GetBool() => true, - - CustomRoles.President => true, - - _ => false - }; - } - - public static bool OnlySpawnsWithPets(this CustomRoles role) - { - return !(Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()) && role.SimpleAbilityTrigger() && role != CustomRoles.Chemist && !role.AlwaysUsesUnshift()) && OnlySpawnsWithPetsRoleList.Contains(role); - } - - public static bool NeedUpdateOnLights(this CustomRoles role) - { - return !role.UsesPetInsteadOfKill() && (role.IsDesyncRole() || role is - CustomRoles.Convict or - CustomRoles.Parasite or - CustomRoles.Refugee or - CustomRoles.Lighter or - CustomRoles.SecurityGuard or - CustomRoles.Ignitor or - CustomRoles.Saboteur or - CustomRoles.Inhibitor or - CustomRoles.Gambler); - } - - public static bool IsBetrayalAddon(this CustomRoles role) - { - return role is - CustomRoles.Charmed or - CustomRoles.Recruit or - CustomRoles.Contagious or - CustomRoles.Lovers or - CustomRoles.Madmate or - CustomRoles.Undead or - CustomRoles.Egoist; - } - - public static bool IsImpOnlyAddon(this CustomRoles role) - { - return Options.GroupedAddons[AddonTypes.ImpOnly].Contains(role); - } - - public static bool NeedsUpdateAfterDeath(this CustomRoles role) - { - return role is - CustomRoles.Weatherman or - CustomRoles.Altruist or - CustomRoles.Duellist; - } - - public static bool IsTaskBasedCrewmate(this CustomRoles role) - { - return role is - CustomRoles.Snitch or - CustomRoles.Speedrunner or - CustomRoles.Marshall or - CustomRoles.TimeManager or - CustomRoles.Ignitor or - CustomRoles.Guardian or - CustomRoles.Merchant or - CustomRoles.Mayor or - CustomRoles.Insight or - CustomRoles.Lyncher or - CustomRoles.Transporter; - } - - public static bool IsNotKnightable(this CustomRoles role) - { - return role is - CustomRoles.Mayor or - CustomRoles.Vindicator or - CustomRoles.Dictator or - CustomRoles.Knighted or - CustomRoles.Glitch or - CustomRoles.Pickpocket or - CustomRoles.TicketsStealer; - } - - public static bool IsNotAssignableMidGame(this CustomRoles role) - { - return role is - CustomRoles.Egoist or - CustomRoles.Workhorse or - CustomRoles.Cleansed or - CustomRoles.Busy or - CustomRoles.Lovers or - CustomRoles.Stressed or - CustomRoles.Lazy or - CustomRoles.Rascal or - CustomRoles.LastImpostor; - } - - public static bool ForceCancelShapeshift(this CustomRoles role) - { - return role is - CustomRoles.Swapster or - CustomRoles.Echo or - CustomRoles.Hangman or - CustomRoles.Generator; - } - - public static bool IsNoAnimationShifter(this CustomRoles role) - { - return role is - CustomRoles.Generator or - CustomRoles.Echo; - } - - public static bool AlwaysUsesUnshift(this CustomRoles role) - { - return role is - CustomRoles.Wizard; - } - - public static bool SimpleAbilityTrigger(this CustomRoles role) - { - return role is - CustomRoles.Wizard or - CustomRoles.Occultist or - CustomRoles.Warlock or - CustomRoles.Swiftclaw or - CustomRoles.Undertaker or - CustomRoles.Abyssbringer or - CustomRoles.Bomber or - CustomRoles.Nuker or - CustomRoles.Camouflager or - CustomRoles.Disperser or - CustomRoles.Escapee or - CustomRoles.FireWorks or - CustomRoles.Hypnotist or - CustomRoles.Librarian or - CustomRoles.Miner or - CustomRoles.RiftMaker or - CustomRoles.Assassin or - CustomRoles.QuickShooter or - CustomRoles.Sapper or - CustomRoles.Sniper or - CustomRoles.Twister or - CustomRoles.RouleteGrandeur or - CustomRoles.Enderman or - CustomRoles.Hookshot or - CustomRoles.Mycologist or - CustomRoles.Magician or - CustomRoles.Sprayer or - CustomRoles.Werewolf or - CustomRoles.WeaponMaster or - CustomRoles.Tiger or - CustomRoles.Bargainer or - CustomRoles.Chemist or - CustomRoles.Simon or - CustomRoles.Patroller or - CustomRoles.Weatherman or - CustomRoles.NoteKiller; - } - - public static bool CheckAddonConflict(CustomRoles role, PlayerControl pc) - { - return role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out List bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out (OptionItem Imp, OptionItem Neutral, OptionItem Crew) o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()))) && (!role.IsImpOnlyAddon() || pc.IsImpostor() || (pc.Is(CustomRoles.Traitor) && Traitor.CanGetImpostorOnlyAddons.GetBool())) && role switch - { - CustomRoles.Sleuth when pc.Is(CustomRoles.NecroGuesser) => false, - CustomRoles.Introvert when pc.GetCustomRole() is CustomRoles.Leery or CustomRoles.Samurai or CustomRoles.Arsonist or CustomRoles.Revolutionist or CustomRoles.Farseer or CustomRoles.Scavenger or CustomRoles.Analyst => false, - CustomRoles.Circumvent when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.RiftMaker => false, - CustomRoles.Oblivious when pc.Is(CustomRoles.Altruist) => false, - CustomRoles.AntiTP when pc.GetCustomRole() is CustomRoles.Transmitter or CustomRoles.Miner or CustomRoles.Escapee or CustomRoles.Tunneler => false, - CustomRoles.Swift when pc.Is(CustomRoles.Stealth) => false, - CustomRoles.Disco when pc.GetCustomRole() is CustomRoles.Chameleon or CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Alchemist => false, - CustomRoles.Egoist when pc.Is(CustomRoles.Gangster) => false, - CustomRoles.Nimble when pc.Is(CustomRoles.Oxyman) => false, - CustomRoles.Magnet when pc.Is(Team.Impostor) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Magnet) => false, - CustomRoles.Oblivious when pc.Is(CustomRoles.Amnesiac) && Amnesiac.RememberMode.GetValue() == 0 => false, - CustomRoles.Rookie when !pc.CanUseKillButton() => false, - CustomRoles.Energetic when !Options.UsePets.GetBool() => false, - CustomRoles.Madmate when pc.Is(CustomRoles.Sidekick) => false, - CustomRoles.Autopsy when pc.Is(CustomRoles.Doctor) || pc.Is(CustomRoles.Tracefinder) || pc.Is(CustomRoles.Scientist) || pc.Is(CustomRoles.ScientistEHR) || pc.Is(CustomRoles.Sunnyboy) => false, - CustomRoles.Necroview when pc.Is(CustomRoles.Doctor) => false, - CustomRoles.Lazy when pc.Is(CustomRoles.Speedrunner) => false, - CustomRoles.Mischievous when pc.Is(Team.Impostor) || (!pc.GetCustomRole().IsDesyncRole() && !pc.Is(CustomRoles.Bloodlust)) || !pc.IsNeutralKiller() || Main.PlayerStates[pc.PlayerId].Role.CanUseSabotage(pc) => false, - CustomRoles.Loyal when pc.IsCrewmate() && !Options.CrewCanBeLoyal.GetBool() => false, - CustomRoles.Lazy when pc.Is(CustomRoles.Needy) || pc.Is(CustomRoles.Snitch) || pc.Is(CustomRoles.Marshall) || pc.Is(CustomRoles.Transporter) || pc.Is(CustomRoles.Guardian) => false, - CustomRoles.Brakar when pc.Is(CustomRoles.Dictator) => false, - CustomRoles.Stressed when !pc.IsCrewmate() || pc.GetCustomRole().IsTasklessCrewmate() => false, - CustomRoles.Lazy when pc.GetCustomRole().IsNeutral() || pc.IsImpostor() || (pc.GetCustomRole().IsTasklessCrewmate() && !Options.TasklessCrewCanBeLazy.GetBool()) || (pc.GetCustomRole().IsTaskBasedCrewmate() && !Options.TaskBasedCrewCanBeLazy.GetBool()) => false, - CustomRoles.TicketsStealer or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when pc.Is(CustomRoles.Bomber) || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.BoobyTrap) || pc.Is(CustomRoles.Capitalism) => false, - CustomRoles.Torch when !pc.IsCrewmate() || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Sunglasses) || pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Bewilder when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Sunglasses) => false, - CustomRoles.Dynamo when pc.Is(CustomRoles.Spurt) => false, - CustomRoles.Spurt when pc.GetCustomSubRoles().Any(x => x is CustomRoles.Dynamo or CustomRoles.Swiftclaw or CustomRoles.Giant or CustomRoles.Flashman) => false, - CustomRoles.Sunglasses when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Bewilder) => false, - CustomRoles.Guesser when pc.GetCustomRole() is CustomRoles.EvilGuesser or CustomRoles.NiceGuesser or CustomRoles.Doomsayer or CustomRoles.CopyCat => false, - CustomRoles.Madmate when !pc.CanBeMadmate() || pc.Is(CustomRoles.Egoist) || pc.Is(CustomRoles.Rascal) => false, - CustomRoles.Oblivious when pc.Is(CustomRoles.Detective) || pc.Is(CustomRoles.Cleaner) || pc.Is(CustomRoles.Medusa) || pc.Is(CustomRoles.Mortician) || pc.Is(CustomRoles.Mediumshiper) || pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Youtuber when !pc.IsCrewmate() || pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.Sheriff) || pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Egoist when !pc.IsImpostor() => false, - CustomRoles.Damocles when pc.GetCustomRole() is CustomRoles.Bomber or CustomRoles.Nuker or CustomRoles.SerialKiller or CustomRoles.Cantankerous => false, - CustomRoles.Damocles when !pc.CanUseKillButton() => false, - CustomRoles.Flashman when pc.Is(CustomRoles.Swiftclaw) || pc.Is(CustomRoles.Giant) || pc.Is(CustomRoles.Spurt) => false, - CustomRoles.Giant when pc.Is(CustomRoles.Flashman) || pc.Is(CustomRoles.Spurt) => false, - CustomRoles.Necroview when pc.Is(CustomRoles.Visionary) => false, - CustomRoles.Mimic when pc.Is(CustomRoles.Mafia) => false, - CustomRoles.Rascal when !pc.IsCrewmate() => false, - CustomRoles.Needy when pc.GetCustomRole().IsAdditionRole() => false, - CustomRoles.TicketsStealer when pc.Is(CustomRoles.Vindicator) => false, - CustomRoles.Bloodlust when !pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsTaskBasedCrewmate() || pc.GetCustomRole() is CustomRoles.Medic => false, - CustomRoles.Mare when pc.Is(CustomRoles.Underdog) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Inhibitor) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Saboteur) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Swift) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Mafia) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Sniper) => false, - CustomRoles.Mare when pc.Is(CustomRoles.FireWorks) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Swooper) => false, - CustomRoles.Mare when pc.Is(CustomRoles.Vampire) => false, - CustomRoles.Torch when pc.Is(CustomRoles.Lighter) => false, - CustomRoles.Torch when pc.Is(CustomRoles.Ignitor) => false, - CustomRoles.Bewilder when pc.Is(CustomRoles.Lighter) => false, - CustomRoles.Bewilder when pc.Is(CustomRoles.Ignitor) => false, - CustomRoles.Sunglasses when pc.Is(CustomRoles.Lighter) => false, - CustomRoles.Sunglasses when pc.Is(CustomRoles.Ignitor) => false, - CustomRoles.Bait when pc.Is(CustomRoles.Demolitionist) => false, - CustomRoles.Trapper when pc.Is(CustomRoles.Demolitionist) => false, - CustomRoles.Lovers when pc.Is(CustomRoles.Romantic) => false, - CustomRoles.Mare when pc.Is(CustomRoles.LastImpostor) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Mare) => false, - CustomRoles.DualPersonality when (!pc.IsImpostor() && !pc.IsCrewmate()) || pc.Is(CustomRoles.Madmate) => false, - CustomRoles.Loyal when (!pc.IsImpostor() && !pc.IsCrewmate()) || pc.Is(CustomRoles.Madmate) => false, - CustomRoles.Loyal when pc.IsImpostor() && !Options.ImpCanBeLoyal.GetBool() => false, - CustomRoles.Seer when pc.Is(CustomRoles.Mortician) => false, - CustomRoles.Onbound when pc.Is(CustomRoles.SuperStar) => false, - CustomRoles.Rascal when pc.Is(CustomRoles.SuperStar) || pc.Is(CustomRoles.Madmate) => false, - CustomRoles.Madmate when pc.Is(CustomRoles.SuperStar) => false, - CustomRoles.Gravestone when pc.Is(CustomRoles.SuperStar) => false, - CustomRoles.Lucky when pc.Is(CustomRoles.Guardian) => false, - CustomRoles.Bait when pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Bait when pc.Is(CustomRoles.Trapper) => false, - CustomRoles.Trapper when pc.Is(CustomRoles.Bait) => false, - CustomRoles.DualPersonality when pc.Is(CustomRoles.Dictator) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Swooper) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Vampire) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Scavenger) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Puppeteer) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Warlock) => false, - CustomRoles.Swift when pc.Is(CustomRoles.EvilDiviner) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Witch) => false, - CustomRoles.Swift when pc.Is(CustomRoles.Mafia) => false, - CustomRoles.Reach when pc.Is(CustomRoles.Mafioso) => false, - CustomRoles.Trapper when pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Reach when !pc.CanUseKillButton() => false, - CustomRoles.Magnet when !pc.CanUseKillButton() => false, - CustomRoles.Haste when !pc.CanUseKillButton() || !pc.CanUseImpostorVentButton() => false, - CustomRoles.Diseased when pc.Is(CustomRoles.Antidote) => false, - CustomRoles.Antidote when pc.Is(CustomRoles.Diseased) => false, - CustomRoles.Flashman or CustomRoles.Giant when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Chameleon or CustomRoles.Alchemist => false, - CustomRoles.Bait when pc.Is(CustomRoles.Unreportable) => false, - CustomRoles.Busy when !pc.GetTaskState().HasTasks => false, - CustomRoles.Truant when pc.Is(CustomRoles.SoulHunter) => false, - CustomRoles.Nimble when !pc.IsCrewmate() => false, - CustomRoles.Physicist when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, - CustomRoles.Finder when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, - CustomRoles.Noisy when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, - CustomRoles.Unreportable when pc.Is(CustomRoles.Bait) => false, - CustomRoles.Oblivious when pc.Is(CustomRoles.Bloodhound) => false, - CustomRoles.Oblivious when pc.Is(CustomRoles.Vulture) => false, - CustomRoles.DoubleShot when pc.Is(CustomRoles.CopyCat) => false, - CustomRoles.Brakar when pc.Is(CustomRoles.Dictator) => false, - CustomRoles.Lucky when pc.Is(CustomRoles.Luckey) => false, - CustomRoles.Unlucky when pc.Is(CustomRoles.Luckey) => false, - CustomRoles.Unlucky when pc.Is(CustomRoles.Lucky) => false, - CustomRoles.Lucky when pc.Is(CustomRoles.Unlucky) => false, - CustomRoles.Fool when pc.Is(CustomRoles.SabotageMaster) || pc.Is(CustomRoles.GuardianAngelEHR) => false, - CustomRoles.Bloodhound when pc.Is(CustomRoles.Oblivious) => false, - CustomRoles.DoubleShot when !pc.Is(CustomRoles.EvilGuesser) && !pc.Is(CustomRoles.NiceGuesser) && !Options.GuesserMode.GetBool() => false, - CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && pc.Is(CustomRoleTypes.Impostor) && !Options.ImpostorsCanGuess.GetBool() => false, - CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && pc.Is(CustomRoleTypes.Crewmate) && !Options.CrewmatesCanGuess.GetBool() => false, - CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.IsNeutralKiller() && !Options.NeutralKillersCanGuess.GetBool())) => false, - _ => true - }; - } - - public static Team GetTeam(this CustomRoles role) - { - if (role.IsImpostorTeamV2()) return Team.Impostor; - - if (role.IsNeutralTeamV2()) return Team.Neutral; - - return role.IsCrewmateTeamV2() ? Team.Crewmate : Team.None; - } - - public static bool Is(this CustomRoles role, Team team) - { - return team switch - { - Team.Impostor => role.IsImpostorTeamV2(), - Team.Neutral => role.IsNeutralTeamV2(), - Team.Crewmate => role.IsCrewmateTeamV2(), - Team.None => role.GetCountTypes() is CountTypes.OutOfGame or CountTypes.None || role == CustomRoles.GM, - _ => false - }; - } - - public static RoleTypes GetRoleTypes(this CustomRoles role) - { - if (Enum.TryParse(role.GetVNRole(true).ToString().Replace("EHR", ""), true, out RoleTypes type)) return type; - - return role.IsImpostor() ? RoleTypes.Impostor : RoleTypes.Crewmate; - } - - public static bool IsDesyncRole(this CustomRoles role) - { - return role.GetDYRole() != RoleTypes.GuardianAngel; - } - - public static bool IsImpostorTeam(this CustomRoles role) - { - return role.IsImpostor() || role == CustomRoles.Madmate; - } - - public static bool IsCrewmate(this CustomRoles role) - { - return !role.IsImpostor() && !role.IsNeutral() && !role.IsMadmate(); - } - - public static bool IsImpostorTeamV2(this CustomRoles role) - { - return (role.IsImpostorTeam() && role != CustomRoles.Trickster && !role.IsConverted()) || role is CustomRoles.Rascal or CustomRoles.Madmate || role.IsMadmate(); - } - - public static bool IsNeutralTeamV2(this CustomRoles role) - { - return role.IsConverted() || (role.IsNeutral() && role != CustomRoles.Madmate); - } - - public static bool IsCrewmateTeamV2(this CustomRoles role) - { - return (!role.IsImpostorTeamV2() && !role.IsNeutralTeamV2()) || (role == CustomRoles.Trickster && !role.IsConverted()); - } - - public static bool IsConverted(this CustomRoles role) - { - return (role == CustomRoles.Egoist && ParityCop.ParityCheckEgoistInt() == 1) || role is - CustomRoles.Charmed or - CustomRoles.Recruit or - CustomRoles.Contagious or - CustomRoles.Undead; - } - - public static bool IsRevealingRole(this CustomRoles role, PlayerControl target) - { - return (role is CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.AllTasksCompleted()) || - (role is CustomRoles.SuperStar && Options.EveryOneKnowSuperStar.GetBool()) || - (role is CustomRoles.Marshall && target.AllTasksCompleted()) || - (role is CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || - (role is CustomRoles.Doctor && Options.DoctorVisibleToEveryone.GetBool()) || - (role is CustomRoles.Bait && Options.BaitNotification.GetBool() && ParityCop.ParityCheckBaitCountType.GetBool()); - } - - - public static bool IsVanilla(this CustomRoles role) - { - return role is - CustomRoles.Crewmate or - CustomRoles.Engineer or - CustomRoles.Noisemaker or - CustomRoles.Tracker or - CustomRoles.Scientist or - CustomRoles.GuardianAngel or - CustomRoles.Impostor or - CustomRoles.Phantom or - CustomRoles.Shapeshifter; - } - - public static CustomRoleTypes GetCustomRoleTypes(this CustomRoles role) - { - var type = CustomRoleTypes.Crewmate; - if (role.IsImpostor() || role.IsMadmate()) type = CustomRoleTypes.Impostor; - - if (role.IsNeutral()) type = CustomRoleTypes.Neutral; - - if (role.IsAdditionRole()) type = CustomRoleTypes.Addon; - - return type; - } - - public static bool RoleExist(this CustomRoles role, bool countDead = false) - { - return Main.AllPlayerControls.Any(x => x.Is(role) && (countDead || x.IsAlive())); - } - - public static int GetCount(this CustomRoles role) - { - if (role.IsVanilla()) - { - if (Options.DisableVanillaRoles.GetBool()) return 0; - - IRoleOptionsCollection roleOpt = Main.NormalOptions.RoleOptions; - - return role switch - { - CustomRoles.Engineer => roleOpt.GetNumPerGame(RoleTypes.Engineer), - CustomRoles.Noisemaker => roleOpt.GetNumPerGame(RoleTypes.Noisemaker), - CustomRoles.Tracker => roleOpt.GetNumPerGame(RoleTypes.Tracker), - CustomRoles.Scientist => roleOpt.GetNumPerGame(RoleTypes.Scientist), - CustomRoles.Impostor => roleOpt.GetNumPerGame(RoleTypes.Impostor), - CustomRoles.Phantom => roleOpt.GetNumPerGame(RoleTypes.Phantom), - CustomRoles.Shapeshifter => roleOpt.GetNumPerGame(RoleTypes.Shapeshifter), - CustomRoles.GuardianAngel => roleOpt.GetNumPerGame(RoleTypes.GuardianAngel), - CustomRoles.Crewmate => roleOpt.GetNumPerGame(RoleTypes.Crewmate), - _ => 0 - }; - } - - return Options.GetRoleCount(role); - } - - public static int GetMode(this CustomRoles role) - { - return Options.GetRoleSpawnMode(role); - } - - public static float GetChance(this CustomRoles role) - { - if (role.IsVanilla()) - { - IRoleOptionsCollection roleOpt = Main.NormalOptions.RoleOptions; - - return role switch - { - CustomRoles.Engineer => roleOpt.GetChancePerGame(RoleTypes.Engineer), - CustomRoles.Noisemaker => roleOpt.GetChancePerGame(RoleTypes.Noisemaker), - CustomRoles.Tracker => roleOpt.GetChancePerGame(RoleTypes.Tracker), - CustomRoles.Scientist => roleOpt.GetChancePerGame(RoleTypes.Scientist), - CustomRoles.Impostor => roleOpt.GetChancePerGame(RoleTypes.Impostor), - CustomRoles.Phantom => roleOpt.GetChancePerGame(RoleTypes.Phantom), - CustomRoles.Shapeshifter => roleOpt.GetChancePerGame(RoleTypes.Shapeshifter), - CustomRoles.GuardianAngel => roleOpt.GetChancePerGame(RoleTypes.GuardianAngel), - CustomRoles.Crewmate => roleOpt.GetChancePerGame(RoleTypes.Crewmate), - _ => 0 - } / 100f; - } - - return Options.GetRoleChance(role); - } - - public static bool IsEnable(this CustomRoles role) - { - return role.GetCount() > 0; - } - - public static CountTypes GetCountTypes(this CustomRoles role) - { - return role switch - { - CustomRoles.GM => CountTypes.OutOfGame, - CustomRoles.Sidekick when Jackal.SidekickCountMode.GetValue() == 0 => CountTypes.Jackal, - CustomRoles.Sidekick when Jackal.SidekickCountMode.GetValue() == 1 => CountTypes.OutOfGame, - CustomRoles.Deathknight => CountTypes.Necromancer, - CustomRoles.Parasite => CountTypes.Impostor, - CustomRoles.Crewpostor => CountTypes.Impostor, - CustomRoles.Refugee => CountTypes.Impostor, - CustomRoles.Gaslighter => Gaslighter.WinCondition.GetValue() == 2 ? CountTypes.Gaslighter : CountTypes.Crew, - CustomRoles.DarkHide when DarkHide.SnatchesWin.GetBool() => CountTypes.Crew, - CustomRoles.Arsonist when !Options.ArsonistKeepsGameGoing.GetBool() => CountTypes.Crew, - CustomRoles.SchrodingersCat => SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool() ? CountTypes.Crew : CountTypes.OutOfGame, - CustomRoles.DarkHide => !DarkHide.SnatchesWin.GetBool() ? CountTypes.DarkHide : CountTypes.Crew, - CustomRoles.Arsonist => Options.ArsonistKeepsGameGoing.GetBool() ? CountTypes.Arsonist : CountTypes.Crew, - CustomRoles.Sheriff => Sheriff.KeepsGameGoing.GetBool() ? CountTypes.Sheriff : CountTypes.Crew, - CustomRoles.Shifter => CountTypes.OutOfGame, - CustomRoles.NoteKiller when !NoteKiller.CountsAsNeutralKiller => CountTypes.Crew, - - _ => Enum.TryParse(role.ToString(), true, out CountTypes type) - ? type - : role.Is(Team.Impostor) || role == CustomRoles.Trickster - ? CountTypes.Impostor - : CountTypes.Crew - }; - } - - public static Color GetTeamColor(this Team team) - { - return ColorUtility.TryParseHtmlString(team switch - { - Team.Crewmate => Main.CrewmateColor, - Team.Neutral => Main.NeutralColor, - Team.Impostor => Main.ImpostorColor, - _ => string.Empty - }, out Color color) - ? color - : Color.clear; - } - - public static string ToColoredString(this CustomRoles role) - { - return Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString($"{role}")); - } - - #region OptionsAndCategories - - public static RoleOptionType GetRoleOptionType(this CustomRoles role) - { - if (role.IsImpostor()) return role.GetImpostorRoleCategory(); - if (role.IsCrewmate()) return role.GetCrewmateRoleCategory(); - if (role.IsNeutral(true)) return role.GetNeutralRoleCategory(); - - return RoleOptionType.Crewmate_Miscellaneous; - } - - public static Color GetRoleOptionTypeColor(this RoleOptionType type) - { - return type switch - { - RoleOptionType.Impostor_Killing => Utils.GetRoleColor(CustomRoles.Witness), - RoleOptionType.Impostor_Support => Utils.GetRoleColor(CustomRoles.Bubble), - RoleOptionType.Impostor_Concealing => Utils.GetRoleColor(CustomRoles.CyberStar), - RoleOptionType.Impostor_Miscellaneous => Palette.ImpostorRed, - RoleOptionType.Crewmate_Miscellaneous => Palette.CrewmateBlue, - RoleOptionType.Crewmate_Investigate => Utils.GetRoleColor(CustomRoles.Detective), - RoleOptionType.Crewmate_Support => Utils.GetRoleColor(CustomRoles.NiceEraser), - RoleOptionType.Crewmate_Killing => Utils.GetRoleColor(CustomRoles.Sheriff), - RoleOptionType.Crewmate_Power => Utils.GetRoleColor(CustomRoles.Electric), - RoleOptionType.Crewmate_Chaos => Utils.GetRoleColor(CustomRoles.Tornado), - RoleOptionType.Neutral_Benign => Utils.GetRoleColor(CustomRoles.Chameleon), - RoleOptionType.Neutral_Evil => Utils.GetRoleColor(CustomRoles.Mario), - RoleOptionType.Neutral_Killing => Palette.ImpostorRed, - _ => Utils.GetRoleColor(CustomRoles.SwordsMan) - }; - } - - public static TabGroup GetTabFromOptionType(this RoleOptionType type) - { - return type switch - { - RoleOptionType.Impostor_Killing => TabGroup.ImpostorRoles, - RoleOptionType.Impostor_Support => TabGroup.ImpostorRoles, - RoleOptionType.Impostor_Concealing => TabGroup.ImpostorRoles, - RoleOptionType.Impostor_Miscellaneous => TabGroup.ImpostorRoles, - RoleOptionType.Crewmate_Miscellaneous => TabGroup.CrewmateRoles, - RoleOptionType.Crewmate_Investigate => TabGroup.CrewmateRoles, - RoleOptionType.Crewmate_Support => TabGroup.CrewmateRoles, - RoleOptionType.Crewmate_Killing => TabGroup.CrewmateRoles, - RoleOptionType.Crewmate_Power => TabGroup.CrewmateRoles, - RoleOptionType.Crewmate_Chaos => TabGroup.CrewmateRoles, - RoleOptionType.Neutral_Benign => TabGroup.NeutralRoles, - RoleOptionType.Neutral_Evil => TabGroup.NeutralRoles, - RoleOptionType.Neutral_Killing => TabGroup.NeutralRoles, - _ => TabGroup.OtherRoles - }; - } - - public static Color GetAddonTypeColor(this AddonTypes type) - { - return type switch - { - AddonTypes.ImpOnly => Palette.ImpostorRed, - AddonTypes.Helpful => Palette.CrewmateBlue, - AddonTypes.Harmful => Utils.GetRoleColor(CustomRoles.Sprayer), - AddonTypes.Mixed => Utils.GetRoleColor(CustomRoles.TaskManager), - _ => Palette.CrewmateBlue - }; - } - - public static RoleOptionType GetNeutralRoleCategory(this CustomRoles role) - { - return role switch - { - CustomRoles.Opportunist => RoleOptionType.Neutral_Benign, - CustomRoles.Lawyer => RoleOptionType.Neutral_Benign, - CustomRoles.Amnesiac => RoleOptionType.Neutral_Benign, - CustomRoles.Postman => RoleOptionType.Neutral_Benign, - CustomRoles.SchrodingersCat => RoleOptionType.Neutral_Benign, - CustomRoles.Predator => RoleOptionType.Neutral_Benign, - CustomRoles.Pursuer => RoleOptionType.Neutral_Benign, - CustomRoles.FFF => RoleOptionType.Neutral_Benign, - CustomRoles.Revolutionist => RoleOptionType.Neutral_Benign, - CustomRoles.Impartial => RoleOptionType.Neutral_Benign, - CustomRoles.Backstabber => RoleOptionType.Neutral_Benign, - CustomRoles.Shifter => RoleOptionType.Neutral_Benign, - CustomRoles.Sunnyboy => RoleOptionType.Neutral_Benign, - CustomRoles.Maverick => RoleOptionType.Neutral_Benign, - CustomRoles.Romantic => RoleOptionType.Neutral_Benign, - CustomRoles.VengefulRomantic => RoleOptionType.Neutral_Benign, - CustomRoles.Phantasm => RoleOptionType.Neutral_Benign, - CustomRoles.Provocateur => RoleOptionType.Neutral_Benign, - CustomRoles.SoulHunter => RoleOptionType.Neutral_Benign, - CustomRoles.Technician => RoleOptionType.Neutral_Benign, - CustomRoles.Tank => RoleOptionType.Neutral_Benign, - CustomRoles.Totocalcio => RoleOptionType.Neutral_Benign, - CustomRoles.Arsonist => RoleOptionType.Neutral_Evil, - CustomRoles.Curser => RoleOptionType.Neutral_Evil, - CustomRoles.Jester => RoleOptionType.Neutral_Evil, - CustomRoles.Gaslighter => RoleOptionType.Neutral_Evil, - CustomRoles.God => RoleOptionType.Neutral_Evil, - CustomRoles.Executioner => RoleOptionType.Neutral_Evil, - CustomRoles.Doomsayer => RoleOptionType.Neutral_Evil, - CustomRoles.Mario => RoleOptionType.Neutral_Evil, - CustomRoles.Terrorist => RoleOptionType.Neutral_Evil, - CustomRoles.Collector => RoleOptionType.Neutral_Evil, - CustomRoles.NecroGuesser => RoleOptionType.Neutral_Evil, - CustomRoles.Vulture => RoleOptionType.Neutral_Evil, - CustomRoles.Workaholic => RoleOptionType.Neutral_Evil, - CustomRoles.Deathknight => RoleOptionType.Neutral_Evil, - CustomRoles.Innocent => RoleOptionType.Neutral_Evil, - _ => role.IsNK(true) ? RoleOptionType.Neutral_Killing : role.IsImpostor() ? RoleOptionType.Impostor_Miscellaneous : RoleOptionType.Crewmate_Miscellaneous - }; - } - - public static RoleOptionType GetImpostorRoleCategory(this CustomRoles role) - { - return role switch - { - CustomRoles.Sans => RoleOptionType.Impostor_Killing, - CustomRoles.Assassin => RoleOptionType.Impostor_Killing, - CustomRoles.Assumer => RoleOptionType.Impostor_Killing, - CustomRoles.Augmenter => RoleOptionType.Impostor_Killing, - CustomRoles.Bomber => RoleOptionType.Impostor_Killing, - CustomRoles.BountyHunter => RoleOptionType.Impostor_Killing, - CustomRoles.Cantankerous => RoleOptionType.Impostor_Killing, - CustomRoles.Chronomancer => RoleOptionType.Impostor_Killing, - CustomRoles.Councillor => RoleOptionType.Impostor_Killing, - CustomRoles.EvilGuesser => RoleOptionType.Impostor_Killing, - CustomRoles.FireWorks => RoleOptionType.Impostor_Killing, - CustomRoles.Greedier => RoleOptionType.Impostor_Killing, - CustomRoles.Hitman => RoleOptionType.Impostor_Killing, - CustomRoles.Inhibitor => RoleOptionType.Impostor_Killing, - CustomRoles.Kamikaze => RoleOptionType.Impostor_Killing, - CustomRoles.Minimalism => RoleOptionType.Impostor_Killing, - CustomRoles.Lurker => RoleOptionType.Impostor_Killing, - CustomRoles.Mafioso => RoleOptionType.Impostor_Killing, - CustomRoles.Mafia => RoleOptionType.Impostor_Killing, - CustomRoles.SerialKiller => RoleOptionType.Impostor_Killing, - CustomRoles.Nuker => RoleOptionType.Impostor_Killing, - CustomRoles.Overheat => RoleOptionType.Impostor_Killing, - CustomRoles.OverKiller => RoleOptionType.Impostor_Killing, - CustomRoles.QuickShooter => RoleOptionType.Impostor_Killing, - CustomRoles.Saboteur => RoleOptionType.Impostor_Killing, - CustomRoles.Sapper => RoleOptionType.Impostor_Killing, - CustomRoles.Sniper => RoleOptionType.Impostor_Killing, - CustomRoles.BoobyTrap => RoleOptionType.Impostor_Killing, - CustomRoles.Underdog => RoleOptionType.Impostor_Killing, - CustomRoles.Wasp => RoleOptionType.Impostor_Killing, - CustomRoles.Witch => RoleOptionType.Impostor_Killing, - CustomRoles.Zombie => RoleOptionType.Impostor_Killing, - CustomRoles.Bard => RoleOptionType.Impostor_Support, - CustomRoles.Blackmailer => RoleOptionType.Impostor_Support, - CustomRoles.Camouflager => RoleOptionType.Impostor_Support, - CustomRoles.Capitalism => RoleOptionType.Impostor_Support, - CustomRoles.Cleaner => RoleOptionType.Impostor_Support, - CustomRoles.Commander => RoleOptionType.Impostor_Support, - CustomRoles.Consort => RoleOptionType.Impostor_Support, - CustomRoles.Framer => RoleOptionType.Impostor_Support, - CustomRoles.Deathpact => RoleOptionType.Impostor_Support, - CustomRoles.Devourer => RoleOptionType.Impostor_Support, - CustomRoles.Disperser => RoleOptionType.Impostor_Support, - CustomRoles.Dazzler => RoleOptionType.Impostor_Support, - CustomRoles.Eraser => RoleOptionType.Impostor_Support, - CustomRoles.Freezer => RoleOptionType.Impostor_Support, - CustomRoles.Gangster => RoleOptionType.Impostor_Support, - CustomRoles.Godfather => RoleOptionType.Impostor_Support, - CustomRoles.Hypnotist => RoleOptionType.Impostor_Support, - CustomRoles.Librarian => RoleOptionType.Impostor_Support, - CustomRoles.Nullifier => RoleOptionType.Impostor_Support, - CustomRoles.Occultist => RoleOptionType.Impostor_Support, - CustomRoles.Silencer => RoleOptionType.Impostor_Support, - CustomRoles.Swapster => RoleOptionType.Impostor_Support, - CustomRoles.TimeThief => RoleOptionType.Impostor_Support, - CustomRoles.Twister => RoleOptionType.Impostor_Support, - CustomRoles.Ventriloquist => RoleOptionType.Impostor_Support, - CustomRoles.Vindicator => RoleOptionType.Impostor_Support, - CustomRoles.YinYanger => RoleOptionType.Impostor_Support, - CustomRoles.Hacker => RoleOptionType.Impostor_Concealing, - CustomRoles.Duellist => RoleOptionType.Impostor_Concealing, - CustomRoles.Echo => RoleOptionType.Impostor_Concealing, - CustomRoles.Escapee => RoleOptionType.Impostor_Concealing, - CustomRoles.Hangman => RoleOptionType.Impostor_Concealing, - CustomRoles.Kidnapper => RoleOptionType.Impostor_Concealing, - CustomRoles.BallLightning => RoleOptionType.Impostor_Concealing, - CustomRoles.Mastermind => RoleOptionType.Impostor_Concealing, - CustomRoles.Miner => RoleOptionType.Impostor_Concealing, - CustomRoles.Morphling => RoleOptionType.Impostor_Concealing, - CustomRoles.Penguin => RoleOptionType.Impostor_Concealing, - CustomRoles.Puppeteer => RoleOptionType.Impostor_Concealing, - CustomRoles.RiftMaker => RoleOptionType.Impostor_Concealing, - CustomRoles.Scavenger => RoleOptionType.Impostor_Concealing, - CustomRoles.ImperiusCurse => RoleOptionType.Impostor_Concealing, - CustomRoles.Swiftclaw => RoleOptionType.Impostor_Concealing, - CustomRoles.Swooper => RoleOptionType.Impostor_Concealing, - CustomRoles.Stealth => RoleOptionType.Impostor_Concealing, - CustomRoles.Trickster => RoleOptionType.Impostor_Concealing, - CustomRoles.Undertaker => RoleOptionType.Impostor_Concealing, - CustomRoles.Vampire => RoleOptionType.Impostor_Concealing, - CustomRoles.Warlock => RoleOptionType.Impostor_Concealing, - CustomRoles.Abyssbringer => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.AntiAdminer => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.Changeling => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.EvilDiviner => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.CursedWolf => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.EvilTracker => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.Gambler => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.Generator => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.Visionary => RoleOptionType.Impostor_Miscellaneous, - CustomRoles.Wildling => RoleOptionType.Impostor_Miscellaneous, - _ => role.IsCrewmate() ? RoleOptionType.Crewmate_Miscellaneous : RoleOptionType.Neutral_Benign - }; - } - - public static RoleOptionType GetCrewmateRoleCategory(this CustomRoles role) - { - return role switch - { - CustomRoles.Addict => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.CameraMan => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.CyberStar => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Demolitionist => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Express => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Needy => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Journalist => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Luckey => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Mole => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Nightmare => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Paranoia => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Ricochet => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Safeguard => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.SuperStar => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Tether => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Transmitter => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Tracefinder => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Tunneler => RoleOptionType.Crewmate_Miscellaneous, - CustomRoles.Analyst => RoleOptionType.Crewmate_Investigate, - CustomRoles.Catcher => RoleOptionType.Crewmate_Investigate, - CustomRoles.Chameleon => RoleOptionType.Crewmate_Investigate, - CustomRoles.Clairvoyant => RoleOptionType.Crewmate_Investigate, - CustomRoles.Bloodhound => RoleOptionType.Crewmate_Investigate, - CustomRoles.Detective => RoleOptionType.Crewmate_Investigate, - CustomRoles.Doctor => RoleOptionType.Crewmate_Investigate, - CustomRoles.DoubleAgent => RoleOptionType.Crewmate_Investigate, - CustomRoles.Druid => RoleOptionType.Crewmate_Investigate, - CustomRoles.Enigma => RoleOptionType.Crewmate_Investigate, - CustomRoles.Farseer => RoleOptionType.Crewmate_Investigate, - CustomRoles.Divinator => RoleOptionType.Crewmate_Investigate, - CustomRoles.GuessManagerRole => RoleOptionType.Crewmate_Investigate, - CustomRoles.Ignitor => RoleOptionType.Crewmate_Investigate, - CustomRoles.Insight => RoleOptionType.Crewmate_Investigate, - CustomRoles.ParityCop => RoleOptionType.Crewmate_Investigate, - CustomRoles.Inquirer => RoleOptionType.Crewmate_Investigate, - CustomRoles.Leery => RoleOptionType.Crewmate_Investigate, - CustomRoles.Lighter => RoleOptionType.Crewmate_Investigate, - CustomRoles.Lookout => RoleOptionType.Crewmate_Investigate, - CustomRoles.Lyncher => RoleOptionType.Crewmate_Investigate, - CustomRoles.Markseeker => RoleOptionType.Crewmate_Investigate, - CustomRoles.Mediumshiper => RoleOptionType.Crewmate_Investigate, - CustomRoles.Monitor => RoleOptionType.Crewmate_Investigate, - CustomRoles.Mortician => RoleOptionType.Crewmate_Investigate, - CustomRoles.NiceHacker => RoleOptionType.Crewmate_Investigate, - CustomRoles.Observer => RoleOptionType.Crewmate_Investigate, - CustomRoles.Oracle => RoleOptionType.Crewmate_Investigate, - CustomRoles.Perceiver => RoleOptionType.Crewmate_Investigate, - CustomRoles.Psychic => RoleOptionType.Crewmate_Investigate, - CustomRoles.Rabbit => RoleOptionType.Crewmate_Investigate, - CustomRoles.Scout => RoleOptionType.Crewmate_Investigate, - CustomRoles.Sentry => RoleOptionType.Crewmate_Investigate, - CustomRoles.Shiftguard => RoleOptionType.Crewmate_Investigate, - CustomRoles.Snitch => RoleOptionType.Crewmate_Investigate, - CustomRoles.Socialite => RoleOptionType.Crewmate_Investigate, - CustomRoles.Soothsayer => RoleOptionType.Crewmate_Investigate, - CustomRoles.Spiritualist => RoleOptionType.Crewmate_Investigate, - CustomRoles.Spy => RoleOptionType.Crewmate_Investigate, - CustomRoles.TaskManager => RoleOptionType.Crewmate_Investigate, - CustomRoles.Whisperer => RoleOptionType.Crewmate_Investigate, - CustomRoles.Witness => RoleOptionType.Crewmate_Investigate, - CustomRoles.Aid => RoleOptionType.Crewmate_Support, - CustomRoles.Altruist => RoleOptionType.Crewmate_Support, - CustomRoles.Autocrat => RoleOptionType.Crewmate_Support, - CustomRoles.Bane => RoleOptionType.Crewmate_Support, - CustomRoles.Beacon => RoleOptionType.Crewmate_Support, - CustomRoles.Benefactor => RoleOptionType.Crewmate_Support, - CustomRoles.Bodyguard => RoleOptionType.Crewmate_Support, - CustomRoles.Chef => RoleOptionType.Crewmate_Support, - CustomRoles.Cleanser => RoleOptionType.Crewmate_Support, - CustomRoles.Convener => RoleOptionType.Crewmate_Support, - CustomRoles.Crusader => RoleOptionType.Crewmate_Support, - CustomRoles.Deputy => RoleOptionType.Crewmate_Support, - CustomRoles.DonutDelivery => RoleOptionType.Crewmate_Support, - CustomRoles.Doormaster => RoleOptionType.Crewmate_Support, - CustomRoles.DovesOfNeace => RoleOptionType.Crewmate_Support, - CustomRoles.Electric => RoleOptionType.Crewmate_Support, - CustomRoles.Escort => RoleOptionType.Crewmate_Support, - CustomRoles.Gaulois => RoleOptionType.Crewmate_Support, - CustomRoles.Grappler => RoleOptionType.Crewmate_Support, - CustomRoles.Grenadier => RoleOptionType.Crewmate_Support, - CustomRoles.Jailor => RoleOptionType.Crewmate_Support, - CustomRoles.Mathematician => RoleOptionType.Crewmate_Support, - CustomRoles.SabotageMaster => RoleOptionType.Crewmate_Support, - CustomRoles.Medic => RoleOptionType.Crewmate_Support, - CustomRoles.Merchant => RoleOptionType.Crewmate_Support, - CustomRoles.Monarch => RoleOptionType.Crewmate_Support, - CustomRoles.NiceSwapper => RoleOptionType.Crewmate_Support, - CustomRoles.Negotiator => RoleOptionType.Crewmate_Support, - CustomRoles.Rhapsode => RoleOptionType.Crewmate_Support, - CustomRoles.SecurityGuard => RoleOptionType.Crewmate_Support, - CustomRoles.SpeedBooster => RoleOptionType.Crewmate_Support, - CustomRoles.TimeManager => RoleOptionType.Crewmate_Support, - CustomRoles.TimeMaster => RoleOptionType.Crewmate_Support, - CustomRoles.Transporter => RoleOptionType.Crewmate_Support, - CustomRoles.Ventguard => RoleOptionType.Crewmate_Support, - CustomRoles.Wizard => RoleOptionType.Crewmate_Support, - CustomRoles.Drainer => RoleOptionType.Crewmate_Killing, - CustomRoles.Judge => RoleOptionType.Crewmate_Killing, - CustomRoles.NiceGuesser => RoleOptionType.Crewmate_Killing, - CustomRoles.Sentinel => RoleOptionType.Crewmate_Killing, - CustomRoles.Sheriff => RoleOptionType.Crewmate_Killing, - CustomRoles.Veteran => RoleOptionType.Crewmate_Killing, - CustomRoles.SwordsMan => RoleOptionType.Crewmate_Killing, - CustomRoles.Adrenaline => RoleOptionType.Crewmate_Power, - CustomRoles.Adventurer => RoleOptionType.Crewmate_Power, - CustomRoles.Alchemist => RoleOptionType.Crewmate_Power, - CustomRoles.CopyCat => RoleOptionType.Crewmate_Power, - CustomRoles.Detour => RoleOptionType.Crewmate_Power, - CustomRoles.Dictator => RoleOptionType.Crewmate_Power, - CustomRoles.Guardian => RoleOptionType.Crewmate_Power, - CustomRoles.Marshall => RoleOptionType.Crewmate_Power, - CustomRoles.Mayor => RoleOptionType.Crewmate_Power, - CustomRoles.NiceEraser => RoleOptionType.Crewmate_Power, - CustomRoles.Oxyman => RoleOptionType.Crewmate_Power, - CustomRoles.President => RoleOptionType.Crewmate_Power, - CustomRoles.Speedrunner => RoleOptionType.Crewmate_Power, - CustomRoles.Telekinetic => RoleOptionType.Crewmate_Power, - CustomRoles.Ankylosaurus => RoleOptionType.Crewmate_Chaos, - CustomRoles.Car => RoleOptionType.Crewmate_Chaos, - CustomRoles.Dad => RoleOptionType.Crewmate_Chaos, - CustomRoles.Goose => RoleOptionType.Crewmate_Chaos, - CustomRoles.Randomizer => RoleOptionType.Crewmate_Chaos, - CustomRoles.ToiletMaster => RoleOptionType.Crewmate_Chaos, - CustomRoles.Tornado => RoleOptionType.Crewmate_Chaos, - _ => role.IsImpostor() ? RoleOptionType.Impostor_Miscellaneous : RoleOptionType.Neutral_Benign - }; - } - - #endregion + return HnSManager.AllHnSRoles.Contains(role) || role is + CustomRoles.KB_Normal or + CustomRoles.Killer or + CustomRoles.Tasker or + CustomRoles.Potato or + CustomRoles.Runner or + CustomRoles.CTFPlayer or + CustomRoles.NDPlayer or + CustomRoles.RRPlayer; } + public static RoleBase GetRoleClass(this CustomRoles role) + { + RoleBase roleClass = role switch + { + // Roles that use the same code as another role need to be handled here + CustomRoles.Nuker => new Bomber(), + CustomRoles.Undertaker => new Assassin(), + CustomRoles.Chameleon => new Swooper(), + CustomRoles.BloodKnight => new Wildling(), + CustomRoles.HexMaster => new Witch(), + CustomRoles.Imitator => new Greedier(), + CustomRoles.Jinx => new CursedWolf(), + CustomRoles.Juggernaut => new Sans(), + CustomRoles.Medusa => new Cleaner(), + CustomRoles.Poisoner => new Vampire(), + CustomRoles.Reckless => new Sans(), + CustomRoles.Ritualist => new EvilDiviner(), + CustomRoles.Wraith => new Swooper(), + CustomRoles.Goose => new Penguin(), + CustomRoles.Monitor => new AntiAdminer(), + + // Else, the role class is the role name - if the class doesn't exist, it defaults to VanillaRole + _ => Main.AllRoleClasses.FirstOrDefault(x => x.GetType().Name.Equals(role.ToString(), StringComparison.OrdinalIgnoreCase)) ?? new VanillaRole() + }; + + return Activator.CreateInstance(roleClass.GetType()) as RoleBase; + } + + public static CustomRoles GetVNRole(this CustomRoles role, bool checkDesyncRole = false) + { + if (role.IsGhostRole()) return CustomRoles.GuardianAngel; + if (role.IsVanilla()) return role; + if (checkDesyncRole && role.IsDesyncRole()) return Enum.Parse(role.GetDYRole() + "EHR"); + if (Options.UsePhantomBasis.GetBool() && role.SimpleAbilityTrigger()) return CustomRoles.Phantom; + if ((Options.UseUnshiftTrigger.GetBool() || role.AlwaysUsesUnshift()) && role.SimpleAbilityTrigger()) return CustomRoles.Shapeshifter; + + bool UsePets = Options.UsePets.GetBool(); + + return role switch + { + CustomRoles.Sniper => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Jester => Jester.JesterCanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Mayor => Mayor.MayorHasPortableButton.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Monitor => Monitor.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Vulture => Vulture.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Opportunist => Opportunist.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Vindicator => CustomRoles.Impostor, + CustomRoles.Snitch => CustomRoles.Crewmate, + CustomRoles.ParityCop => CustomRoles.Crewmate, + CustomRoles.Marshall => CustomRoles.Crewmate, + CustomRoles.SabotageMaster => CustomRoles.Engineer, + CustomRoles.Mafia => Options.LegacyMafia.GetBool() ? CustomRoles.Shapeshifter : CustomRoles.Impostor, + CustomRoles.Terrorist => CustomRoles.Engineer, + CustomRoles.Executioner => CustomRoles.Crewmate, + CustomRoles.Lawyer => CustomRoles.Crewmate, + CustomRoles.NiceSwapper => CustomRoles.Crewmate, + CustomRoles.Ignitor => CustomRoles.Crewmate, + CustomRoles.Jailor => UsePets && Jailor.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.Vampire => CustomRoles.Impostor, + CustomRoles.BountyHunter => CustomRoles.Impostor, + CustomRoles.Trickster => CustomRoles.Impostor, + CustomRoles.Witch => CustomRoles.Impostor, + CustomRoles.Agitater => CustomRoles.Impostor, + CustomRoles.EvilDiviner => CustomRoles.Impostor, + CustomRoles.Wildling => Wildling.CanShapeshiftOpt.GetBool() ? CustomRoles.Shapeshifter : CustomRoles.Impostor, + CustomRoles.Morphling => CustomRoles.Shapeshifter, + CustomRoles.Warlock => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.SerialKiller => CustomRoles.Impostor, + CustomRoles.FireWorks => CustomRoles.Shapeshifter, + CustomRoles.SpeedBooster => CustomRoles.Crewmate, + CustomRoles.Dictator => CustomRoles.Crewmate, + CustomRoles.DoubleAgent => CustomRoles.Crewmate, + CustomRoles.Inhibitor => CustomRoles.Impostor, + CustomRoles.Occultist => CustomRoles.Impostor, + CustomRoles.Kidnapper => CustomRoles.Shapeshifter, + CustomRoles.Wasp => CustomRoles.Impostor, + CustomRoles.Assumer => CustomRoles.Impostor, + CustomRoles.Augmenter => CustomRoles.Shapeshifter, + CustomRoles.Ventriloquist => CustomRoles.Impostor, + CustomRoles.Echo => CustomRoles.Shapeshifter, + CustomRoles.Abyssbringer => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Overheat => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Hypnotist => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Generator => CustomRoles.Shapeshifter, + CustomRoles.Blackmailer => CustomRoles.Impostor, + CustomRoles.Commander => CustomRoles.Shapeshifter, + CustomRoles.Freezer => CustomRoles.Shapeshifter, + CustomRoles.Changeling => CustomRoles.Shapeshifter, + CustomRoles.Swapster => CustomRoles.Shapeshifter, + CustomRoles.Kamikaze => CustomRoles.Impostor, + CustomRoles.Librarian => CustomRoles.Shapeshifter, + CustomRoles.Cantankerous => CustomRoles.Impostor, + CustomRoles.Swiftclaw => CustomRoles.Impostor, + CustomRoles.YinYanger => CustomRoles.Impostor, + CustomRoles.Duellist => CustomRoles.Shapeshifter, + CustomRoles.Consort => CustomRoles.Impostor, + CustomRoles.Framer => CustomRoles.Impostor, + CustomRoles.Mafioso => CustomRoles.Impostor, + CustomRoles.Chronomancer => CustomRoles.Impostor, + CustomRoles.Nullifier => CustomRoles.Impostor, + CustomRoles.Stealth => CustomRoles.Impostor, + CustomRoles.Penguin => CustomRoles.Impostor, + CustomRoles.Sapper => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Mastermind => CustomRoles.Impostor, + CustomRoles.RiftMaker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Gambler => CustomRoles.Impostor, + CustomRoles.Hitman => CustomRoles.Shapeshifter, + CustomRoles.Saboteur => CustomRoles.Impostor, + CustomRoles.Doctor => CustomRoles.Scientist, + CustomRoles.Tracefinder => CustomRoles.Scientist, + CustomRoles.Puppeteer => CustomRoles.Impostor, + CustomRoles.TimeThief => CustomRoles.Impostor, + CustomRoles.EvilTracker => CustomRoles.Shapeshifter, + CustomRoles.Paranoia => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.TimeMaster => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Cleanser => CustomRoles.Crewmate, + CustomRoles.Miner => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Psychic => CustomRoles.Crewmate, + CustomRoles.Needy => CustomRoles.Crewmate, + CustomRoles.Twister => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.SuperStar => CustomRoles.Crewmate, + CustomRoles.Hacker => CustomRoles.Shapeshifter, + CustomRoles.Visionary => CustomRoles.Shapeshifter, + CustomRoles.Assassin => CustomRoles.Phantom, + CustomRoles.Undertaker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Luckey => CustomRoles.Crewmate, + CustomRoles.CyberStar => CustomRoles.Crewmate, + CustomRoles.Demolitionist => CustomRoles.Crewmate, + CustomRoles.Ventguard => CustomRoles.Engineer, + CustomRoles.Express => CustomRoles.Crewmate, + CustomRoles.NiceEraser => CustomRoles.Crewmate, + CustomRoles.TaskManager => CustomRoles.Crewmate, + CustomRoles.Adventurer => CustomRoles.Engineer, + CustomRoles.Randomizer => CustomRoles.Crewmate, + CustomRoles.Beacon => CustomRoles.Crewmate, + CustomRoles.Rabbit => CustomRoles.Crewmate, + CustomRoles.Shiftguard => CustomRoles.Crewmate, + CustomRoles.Mole => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Markseeker => CustomRoles.Crewmate, + CustomRoles.Sentinel => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Electric => CustomRoles.Crewmate, + CustomRoles.Philantropist => CustomRoles.Crewmate, + CustomRoles.Tornado => CustomRoles.Crewmate, + CustomRoles.Druid => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Catcher => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Insight => CustomRoles.Crewmate, + CustomRoles.Tunneler => CustomRoles.Crewmate, + CustomRoles.Detour => CustomRoles.Crewmate, + CustomRoles.Dad => CustomRoles.Engineer, + CustomRoles.Drainer => CustomRoles.Engineer, + CustomRoles.Benefactor => CustomRoles.Crewmate, + CustomRoles.GuessManagerRole => CustomRoles.Crewmate, + CustomRoles.Bane => CustomRoles.Crewmate, + CustomRoles.Transmitter => CustomRoles.Crewmate, + CustomRoles.Ankylosaurus => CustomRoles.Crewmate, + CustomRoles.Leery => CustomRoles.Crewmate, + CustomRoles.Altruist => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Negotiator => CustomRoles.Crewmate, + CustomRoles.Grappler => CustomRoles.Crewmate, + CustomRoles.Journalist => CustomRoles.Crewmate, + CustomRoles.Whisperer => CustomRoles.Crewmate, + CustomRoles.Car => CustomRoles.Crewmate, + CustomRoles.President => CustomRoles.Crewmate, + CustomRoles.Oxyman => CustomRoles.Engineer, + CustomRoles.Chef => CustomRoles.Crewmate, + CustomRoles.Lyncher => CustomRoles.Crewmate, + CustomRoles.Adrenaline => CustomRoles.Crewmate, + CustomRoles.Safeguard => CustomRoles.Crewmate, + CustomRoles.Clairvoyant => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Rhapsode => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Telekinetic => CustomRoles.Engineer, + CustomRoles.Autocrat => CustomRoles.Crewmate, + CustomRoles.Inquirer => CustomRoles.Crewmate, + CustomRoles.Soothsayer => CustomRoles.Crewmate, + CustomRoles.LovingCrewmate => CustomRoles.Crewmate, + CustomRoles.LovingImpostor => CustomRoles.Impostor, + CustomRoles.ToiletMaster => CustomRoles.Crewmate, + CustomRoles.Sentry => CustomRoles.Crewmate, + CustomRoles.Perceiver => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Convener => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Mathematician => CustomRoles.Crewmate, + CustomRoles.Nightmare => CustomRoles.Crewmate, + CustomRoles.CameraMan => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Spy => CustomRoles.Crewmate, + CustomRoles.Ricochet => CustomRoles.Crewmate, + CustomRoles.Tether => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Doormaster => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Aid => UsePets && Aid.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.Socialite => UsePets && Socialite.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.Escort => UsePets && Escort.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.DonutDelivery => UsePets && DonutDelivery.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.Gaulois => UsePets && Gaulois.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.Analyst => UsePets && Analyst.UsePet.GetBool() ? CustomRoles.Crewmate : CustomRoles.Impostor, + CustomRoles.Escapee => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.NiceGuesser => CustomRoles.Crewmate, + CustomRoles.EvilGuesser => CustomRoles.Impostor, + CustomRoles.Detective => CustomRoles.Crewmate, + CustomRoles.God => CustomRoles.Crewmate, + CustomRoles.Tank => CustomRoles.Engineer, + CustomRoles.Technician => Technician.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.GuardianAngelEHR => CustomRoles.GuardianAngel, + CustomRoles.Zombie => CustomRoles.Impostor, + CustomRoles.Mario => CustomRoles.Engineer, + CustomRoles.AntiAdminer => AntiAdminer.EnableExtraAbility.GetBool() ? CustomRoles.Shapeshifter : CustomRoles.Impostor, + CustomRoles.Sans => CustomRoles.Impostor, + CustomRoles.Bomber => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Nuker => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.BoobyTrap => CustomRoles.Impostor, + CustomRoles.Scavenger => CustomRoles.Impostor, + CustomRoles.Transporter => CustomRoles.Crewmate, + CustomRoles.Veteran => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Capitalism => CustomRoles.Impostor, + CustomRoles.Bodyguard => CustomRoles.Crewmate, + CustomRoles.Grenadier => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Lighter => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.SecurityGuard => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Gangster => CustomRoles.Impostor, + CustomRoles.Cleaner => CustomRoles.Impostor, + CustomRoles.Konan => CustomRoles.Crewmate, + CustomRoles.Divinator => CustomRoles.Crewmate, + CustomRoles.Oracle => CustomRoles.Crewmate, + CustomRoles.BallLightning => CustomRoles.Impostor, + CustomRoles.Greedier => CustomRoles.Impostor, + CustomRoles.Workaholic => CustomRoles.Engineer, + CustomRoles.Amnesiac => Amnesiac.CanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Speedrunner => CustomRoles.Crewmate, + CustomRoles.CursedWolf => CustomRoles.Impostor, + CustomRoles.Collector => CustomRoles.Crewmate, + CustomRoles.NecroGuesser => CustomRoles.Crewmate, + CustomRoles.SchrodingersCat => CustomRoles.Crewmate, + CustomRoles.ImperiusCurse => CustomRoles.Shapeshifter, + CustomRoles.QuickShooter => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Eraser => CustomRoles.Impostor, + CustomRoles.OverKiller => CustomRoles.Impostor, + CustomRoles.Hangman => CustomRoles.Shapeshifter, + CustomRoles.Sunnyboy => CustomRoles.Scientist, + CustomRoles.Phantasm => Options.PhantomCanVent.GetBool() ? CustomRoles.Engineer : CustomRoles.Crewmate, + CustomRoles.Judge => CustomRoles.Crewmate, + CustomRoles.Councillor => CustomRoles.Impostor, + CustomRoles.Mortician => CustomRoles.Crewmate, + CustomRoles.Mediumshiper => CustomRoles.Crewmate, + CustomRoles.Bard => CustomRoles.Impostor, + CustomRoles.Swooper => CustomRoles.Impostor, + CustomRoles.Crewpostor => CustomRoles.Engineer, + CustomRoles.Cherokious => CustomRoles.Engineer, + CustomRoles.Observer => CustomRoles.Crewmate, + CustomRoles.DovesOfNeace => UsePets ? CustomRoles.Crewmate : CustomRoles.Engineer, + CustomRoles.Disperser => UsePets ? CustomRoles.Impostor : CustomRoles.Shapeshifter, + CustomRoles.Camouflager => CustomRoles.Shapeshifter, + CustomRoles.Dazzler => CustomRoles.Shapeshifter, + CustomRoles.Devourer => CustomRoles.Shapeshifter, + CustomRoles.Deathpact => CustomRoles.Shapeshifter, + CustomRoles.Bloodhound => CustomRoles.Crewmate, + CustomRoles.Tracker => CustomRoles.Crewmate, + CustomRoles.Merchant => CustomRoles.Crewmate, + CustomRoles.Lookout => CustomRoles.Crewmate, + CustomRoles.Guardian => CustomRoles.Crewmate, + CustomRoles.Enigma => CustomRoles.Crewmate, + CustomRoles.Addict => CustomRoles.Engineer, + CustomRoles.Alchemist => CustomRoles.Engineer, // Needs to vent to use the invisibility potion + CustomRoles.Chameleon => CustomRoles.Engineer, + CustomRoles.Lurker => CustomRoles.Impostor, + CustomRoles.Doomsayer => CustomRoles.Crewmate, + CustomRoles.Godfather => CustomRoles.Impostor, + CustomRoles.Silencer => Silencer.SilenceMode.GetValue() == 1 ? CustomRoles.Shapeshifter : CustomRoles.Impostor, + CustomRoles.NoteKiller => CustomRoles.Crewmate, + + // Vanilla roles (just in case) + CustomRoles.ImpostorEHR => CustomRoles.Impostor, + CustomRoles.PhantomEHR => CustomRoles.Phantom, + CustomRoles.ShapeshifterEHR => CustomRoles.Shapeshifter, + CustomRoles.CrewmateEHR => CustomRoles.Crewmate, + CustomRoles.EngineerEHR => CustomRoles.Engineer, + CustomRoles.ScientistEHR => CustomRoles.Scientist, + CustomRoles.TrackerEHR => CustomRoles.Tracker, + CustomRoles.NoisemakerEHR => CustomRoles.Noisemaker, + + // Hide And Seek + CustomRoles.Hider => CustomRoles.Crewmate, + CustomRoles.Seeker => CustomRoles.Impostor, + CustomRoles.Fox => CustomRoles.Crewmate, + CustomRoles.Troll => CustomRoles.Crewmate, + CustomRoles.Jumper => CustomRoles.Engineer, + CustomRoles.Detector => CustomRoles.Crewmate, + CustomRoles.Jet => CustomRoles.Crewmate, + CustomRoles.Dasher => CustomRoles.Impostor, + CustomRoles.Locator => CustomRoles.Impostor, + CustomRoles.Venter => CustomRoles.Impostor, + CustomRoles.Agent => CustomRoles.Impostor, + CustomRoles.Taskinator => CustomRoles.Crewmate, + + // Room Rush + CustomRoles.RRPlayer => CustomRoles.Crewmate, + + _ => role.IsImpostor() ? CustomRoles.Impostor : CustomRoles.Crewmate + }; + } + + public static CustomRoles GetErasedRole(this CustomRoles role) + { + if (role.IsVanilla()) return role; + + CustomRoles vnRole = role.GetVNRole(true); + + return vnRole switch + { + CustomRoles.Crewmate => CustomRoles.CrewmateEHR, + CustomRoles.Engineer => CustomRoles.EngineerEHR, + CustomRoles.Noisemaker => CustomRoles.NoisemakerEHR, + CustomRoles.Tracker => CustomRoles.TrackerEHR, + CustomRoles.Scientist => CustomRoles.ScientistEHR, + CustomRoles.Impostor when role.IsCrewmate() => CustomRoles.CrewmateEHR, + CustomRoles.Impostor => CustomRoles.ImpostorEHR, + CustomRoles.Phantom => CustomRoles.PhantomEHR, + CustomRoles.Shapeshifter => CustomRoles.ShapeshifterEHR, + _ => role.IsImpostor() ? CustomRoles.ImpostorEHR : CustomRoles.CrewmateEHR + }; + } + + public static RoleTypes GetDYRole(this CustomRoles role, bool load = false) + { + if (!load && Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool() && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Phantom; + + if (!load && Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool() && !role.IsImpostor() && role.SimpleAbilityTrigger()) return RoleTypes.Shapeshifter; + + bool UsePets = !load && Options.UsePets.GetBool(); + + return role switch + { + // SoloKombat + CustomRoles.KB_Normal => RoleTypes.Impostor, + // FFA + CustomRoles.Killer => RoleTypes.Impostor, + // Move And Stop + CustomRoles.Tasker => RoleTypes.Crewmate, + // Hot Potato + CustomRoles.Potato => RoleTypes.Crewmate, + // Speedrun + CustomRoles.Runner => RoleTypes.Crewmate, + // Capture The Flag + CustomRoles.CTFPlayer => RoleTypes.Shapeshifter, + // Natural Disasters + CustomRoles.NDPlayer => RoleTypes.Crewmate, + // Standard + CustomRoles.Sheriff => UsePets && Sheriff.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Crusader => UsePets && Crusader.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.CopyCat => UsePets && CopyCat.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Refugee => RoleTypes.Impostor, + CustomRoles.Agitater => RoleTypes.Impostor, + CustomRoles.Monarch => UsePets && Monarch.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Deputy => UsePets && Deputy.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Arsonist => RoleTypes.Impostor, + CustomRoles.Jackal => RoleTypes.Impostor, + CustomRoles.Medusa => RoleTypes.Impostor, + CustomRoles.Sidekick => RoleTypes.Impostor, + CustomRoles.SwordsMan => UsePets && SwordsMan.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Innocent => RoleTypes.Impostor, + CustomRoles.Amnesiac when Amnesiac.RememberMode.GetValue() == 1 => RoleTypes.Impostor, + CustomRoles.Pelican => RoleTypes.Impostor, + CustomRoles.Aid => UsePets && Aid.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Socialite => UsePets && Socialite.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Escort => UsePets && Escort.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.DonutDelivery => UsePets && DonutDelivery.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Gaulois => UsePets && Gaulois.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Analyst => UsePets && Analyst.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Witness => UsePets && Options.WitnessUsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Goose => RoleTypes.Impostor, + CustomRoles.Pursuer => RoleTypes.Impostor, + CustomRoles.Revolutionist => RoleTypes.Impostor, + CustomRoles.FFF => RoleTypes.Impostor, + CustomRoles.Medic => UsePets && Medic.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Gamer => RoleTypes.Impostor, + CustomRoles.HexMaster => RoleTypes.Impostor, + CustomRoles.Wraith => RoleTypes.Impostor, + CustomRoles.Glitch => RoleTypes.Impostor, + CustomRoles.Jailor => UsePets && Jailor.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Juggernaut => RoleTypes.Impostor, + CustomRoles.Jinx => RoleTypes.Impostor, + CustomRoles.DarkHide => RoleTypes.Impostor, + CustomRoles.Provocateur => RoleTypes.Impostor, + CustomRoles.BloodKnight => RoleTypes.Impostor, + CustomRoles.Poisoner => RoleTypes.Impostor, + CustomRoles.NSerialKiller => RoleTypes.Impostor, + CustomRoles.Weatherman => RoleTypes.Impostor, + CustomRoles.Vortex => RoleTypes.Impostor, + CustomRoles.Beehive => RoleTypes.Impostor, + CustomRoles.RouleteGrandeur => RoleTypes.Impostor, + CustomRoles.Nonplus => RoleTypes.Impostor, + CustomRoles.Tremor => RoleTypes.Impostor, + CustomRoles.Evolver => RoleTypes.Impostor, + CustomRoles.Rogue => RoleTypes.Impostor, + CustomRoles.Patroller => RoleTypes.Impostor, + CustomRoles.Simon => RoleTypes.Impostor, + CustomRoles.Chemist => RoleTypes.Impostor, + CustomRoles.Samurai => RoleTypes.Impostor, + CustomRoles.QuizMaster => RoleTypes.Impostor, + CustomRoles.Bargainer => RoleTypes.Impostor, + CustomRoles.Tiger => RoleTypes.Impostor, + CustomRoles.SoulHunter => RoleTypes.Impostor, + CustomRoles.Enderman => RoleTypes.Impostor, + CustomRoles.Mycologist => RoleTypes.Impostor, + CustomRoles.Bubble => RoleTypes.Impostor, + CustomRoles.Hookshot => RoleTypes.Impostor, + CustomRoles.Sprayer => RoleTypes.Impostor, + CustomRoles.PlagueDoctor => RoleTypes.Impostor, + CustomRoles.Curser => RoleTypes.Impostor, + CustomRoles.Postman => RoleTypes.Impostor, + CustomRoles.Shifter => RoleTypes.Impostor, + CustomRoles.Impartial => RoleTypes.Impostor, + CustomRoles.Gaslighter => RoleTypes.Impostor, + CustomRoles.Backstabber => RoleTypes.Impostor, + CustomRoles.Predator => RoleTypes.Impostor, + CustomRoles.Reckless => RoleTypes.Impostor, + CustomRoles.Magician => RoleTypes.Impostor, + CustomRoles.WeaponMaster => RoleTypes.Impostor, + CustomRoles.Pyromaniac => RoleTypes.Impostor, + CustomRoles.Eclipse => RoleTypes.Impostor, + CustomRoles.Vengeance => RoleTypes.Impostor, + CustomRoles.HeadHunter => RoleTypes.Impostor, + CustomRoles.Imitator => RoleTypes.Impostor, + CustomRoles.Werewolf => RoleTypes.Impostor, + CustomRoles.Bandit => RoleTypes.Impostor, + CustomRoles.Maverick => RoleTypes.Impostor, + CustomRoles.Parasite => RoleTypes.Shapeshifter, + CustomRoles.Totocalcio => RoleTypes.Impostor, + CustomRoles.Romantic => RoleTypes.Impostor, + CustomRoles.VengefulRomantic => RoleTypes.Impostor, + CustomRoles.RuthlessRomantic => RoleTypes.Impostor, + CustomRoles.Succubus => RoleTypes.Impostor, + CustomRoles.Necromancer => RoleTypes.Impostor, + CustomRoles.Deathknight => RoleTypes.Impostor, + CustomRoles.Virus => RoleTypes.Impostor, + CustomRoles.Farseer => UsePets && Farseer.UsePet.GetBool() ? RoleTypes.GuardianAngel : RoleTypes.Impostor, + CustomRoles.Ritualist => RoleTypes.Impostor, + CustomRoles.Pickpocket => RoleTypes.Impostor, + CustomRoles.Traitor => RoleTypes.Impostor, + CustomRoles.PlagueBearer => RoleTypes.Impostor, + CustomRoles.Pestilence => RoleTypes.Impostor, + CustomRoles.Spiritcaller => RoleTypes.Impostor, + CustomRoles.Doppelganger => RoleTypes.Impostor, + CustomRoles.Wizard => RoleTypes.Shapeshifter, + _ => RoleTypes.GuardianAngel + }; + } + + public static bool IsAdditionRole(this CustomRoles role) + { + return role > CustomRoles.NotAssigned; + } + + public static bool IsNonNK(this CustomRoles role, bool check = false) + { + return (!check && role == CustomRoles.Arsonist && CanCheck && Options.IsLoaded && Options.ArsonistCanIgniteAnytime != null && !Options.ArsonistCanIgniteAnytime.GetBool()) || role.GetNeutralRoleCategory() is RoleOptionType.Neutral_Benign or RoleOptionType.Neutral_Evil; + } + + public static bool IsNK(this CustomRoles role, bool check = false) + { + return (role == CustomRoles.Arsonist && (check || !CanCheck || !Options.IsLoaded || Options.ArsonistCanIgniteAnytime == null || Options.ArsonistCanIgniteAnytime.GetBool())) || role is + CustomRoles.Jackal or + CustomRoles.Glitch or + CustomRoles.Sidekick or + CustomRoles.HexMaster or + CustomRoles.Doppelganger or + CustomRoles.Succubus or + CustomRoles.Gamer or + CustomRoles.Crewpostor or + CustomRoles.Cherokious or + CustomRoles.Necromancer or + CustomRoles.Agitater or + CustomRoles.Wraith or + CustomRoles.Bandit or + CustomRoles.Medusa or + CustomRoles.Pelican or + CustomRoles.Convict or + CustomRoles.DarkHide or + CustomRoles.Juggernaut or + CustomRoles.Jinx or + CustomRoles.Poisoner or + CustomRoles.Refugee or + CustomRoles.Gaslighter or + CustomRoles.Simon or + CustomRoles.Patroller or + CustomRoles.Rogue or + CustomRoles.Parasite or + CustomRoles.NSerialKiller or + CustomRoles.Weatherman or + CustomRoles.NoteKiller or + CustomRoles.Vortex or + CustomRoles.Beehive or + CustomRoles.RouleteGrandeur or + CustomRoles.Nonplus or + CustomRoles.Tremor or + CustomRoles.Evolver or + CustomRoles.Chemist or + CustomRoles.QuizMaster or + CustomRoles.Samurai or + CustomRoles.Bargainer or + CustomRoles.Tiger or + CustomRoles.Enderman or + CustomRoles.Mycologist or + CustomRoles.Bubble or + CustomRoles.Hookshot or + CustomRoles.Sprayer or + CustomRoles.Magician or + CustomRoles.WeaponMaster or + CustomRoles.Reckless or + CustomRoles.Eclipse or + CustomRoles.Pyromaniac or + CustomRoles.Vengeance or + CustomRoles.HeadHunter or + CustomRoles.Imitator or + CustomRoles.Werewolf or + CustomRoles.Ritualist or + CustomRoles.Pickpocket or + CustomRoles.PlagueDoctor or + CustomRoles.Traitor or + CustomRoles.Virus or + CustomRoles.BloodKnight or + CustomRoles.Spiritcaller or + CustomRoles.RuthlessRomantic or + CustomRoles.PlagueBearer or + CustomRoles.Pestilence; + } + + public static bool IsSnitchTarget(this CustomRoles role) + { + return role.IsNK() || role.Is(Team.Impostor); + } + + public static bool IsGhostRole(this CustomRoles role) + { + return role == CustomRoles.EvilSpirit || GhostRolesManager.CreateGhostRoleInstance(role, true) != null; + } + + public static bool IsImpostor(this CustomRoles role) + { + return (role == CustomRoles.DoubleAgent && Main.HasJustStarted) || role is + CustomRoles.Impostor or + CustomRoles.ImpostorEHR or + CustomRoles.Phantom or + CustomRoles.PhantomEHR or + CustomRoles.Shapeshifter or + CustomRoles.ShapeshifterEHR or + CustomRoles.LovingImpostor or + CustomRoles.Godfather or + CustomRoles.EvilDiviner or + CustomRoles.Wildling or + CustomRoles.Silencer or + CustomRoles.Morphling or + CustomRoles.BountyHunter or + CustomRoles.Vampire or + CustomRoles.Witch or + CustomRoles.Vindicator or + CustomRoles.Zombie or + CustomRoles.Warlock or + CustomRoles.Assassin or + CustomRoles.Undertaker or + CustomRoles.Hacker or + CustomRoles.Visionary or + CustomRoles.Miner or + CustomRoles.Escapee or + CustomRoles.SerialKiller or + CustomRoles.Overheat or + CustomRoles.Underdog or + CustomRoles.Abyssbringer or + CustomRoles.Echo or + CustomRoles.Ventriloquist or + CustomRoles.Augmenter or + CustomRoles.Inhibitor or + CustomRoles.Kidnapper or + CustomRoles.Occultist or + CustomRoles.Wasp or + CustomRoles.Hypnotist or + CustomRoles.Assumer or + CustomRoles.Generator or + CustomRoles.Blackmailer or + CustomRoles.Commander or + CustomRoles.Freezer or + CustomRoles.Changeling or + CustomRoles.Swapster or + CustomRoles.Kamikaze or + CustomRoles.Librarian or + CustomRoles.Cantankerous or + CustomRoles.Swiftclaw or + CustomRoles.Duellist or + CustomRoles.YinYanger or + CustomRoles.Consort or + CustomRoles.Framer or + CustomRoles.Mafioso or + CustomRoles.Nullifier or + CustomRoles.Chronomancer or + CustomRoles.Stealth or + CustomRoles.Penguin or + CustomRoles.Sapper or + CustomRoles.Hitman or + CustomRoles.RiftMaker or + CustomRoles.Mastermind or + CustomRoles.Gambler or + CustomRoles.Councillor or + CustomRoles.Saboteur or + CustomRoles.Puppeteer or + CustomRoles.TimeThief or + CustomRoles.Trickster or + CustomRoles.Mafia or + CustomRoles.Minimalism or + CustomRoles.FireWorks or + CustomRoles.Sniper or + CustomRoles.EvilTracker or + CustomRoles.EvilGuesser or + CustomRoles.AntiAdminer or + CustomRoles.Sans or + CustomRoles.Bomber or + CustomRoles.Nuker or + CustomRoles.Scavenger or + CustomRoles.BoobyTrap or + CustomRoles.Capitalism or + CustomRoles.Gangster or + CustomRoles.Cleaner or + CustomRoles.BallLightning or + CustomRoles.Greedier or + CustomRoles.CursedWolf or + CustomRoles.ImperiusCurse or + CustomRoles.QuickShooter or + CustomRoles.Eraser or + CustomRoles.OverKiller or + CustomRoles.Hangman or + CustomRoles.Bard or + CustomRoles.Swooper or + CustomRoles.Disperser or + CustomRoles.Dazzler or + CustomRoles.Deathpact or + CustomRoles.Devourer or + CustomRoles.Camouflager or + CustomRoles.Twister or + CustomRoles.Lurker; + } + + public static bool IsNeutral(this CustomRoles role, bool check = false) + { + return role.IsNK(check) || role.IsNonNK(check); + } + + public static bool IsAbleToBeSidekicked(this CustomRoles role) + { + return !role.IsRecruitingRole() && role is not + CustomRoles.Deathknight; + } + + public static bool IsEvilAddon(this CustomRoles role) + { + return role is + CustomRoles.Madmate or + CustomRoles.Egoist or + CustomRoles.Charmed or + CustomRoles.Recruit or + CustomRoles.Contagious or + CustomRoles.Rascal; + } + + public static bool IsRecruitingRole(this CustomRoles role) + { + return role is + CustomRoles.Jackal or + CustomRoles.Succubus or + CustomRoles.Necromancer or + CustomRoles.Virus or + CustomRoles.Spiritcaller; + } + + public static bool IsMadmate(this CustomRoles role) + { + return role is + CustomRoles.Crewpostor or + CustomRoles.Convict or + CustomRoles.Refugee or + CustomRoles.Parasite; + } + + public static bool IsTasklessCrewmate(this CustomRoles role) + { + return !role.UsesPetInsteadOfKill() && role.IsCrewmate() && role.IsDesyncRole(); + } + + public static bool PetActivatedAbility(this CustomRoles role) + { + if (CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated()) return true; + + if (!Options.UsePets.GetBool()) return false; + + if (role.UsesPetInsteadOfKill()) return true; + + Type type = role.GetRoleClass().GetType(); + return type.GetMethod("OnPet")?.DeclaringType == type; + } + + public static bool UsesPetInsteadOfKill(this CustomRoles role) + { + return Options.UsePets.GetBool() && role switch + { + CustomRoles.Gaulois when Gaulois.UsePet.GetBool() => true, + CustomRoles.Aid when Aid.UsePet.GetBool() => true, + CustomRoles.Socialite when Socialite.UsePet.GetBool() => true, + CustomRoles.Escort when Escort.UsePet.GetBool() => true, + CustomRoles.DonutDelivery when DonutDelivery.UsePet.GetBool() => true, + CustomRoles.Analyst when Analyst.UsePet.GetBool() => true, + CustomRoles.Jailor when Jailor.UsePet.GetBool() => true, + CustomRoles.Sheriff when Sheriff.UsePet.GetBool() => true, + CustomRoles.SwordsMan when SwordsMan.UsePet.GetBool() => true, + CustomRoles.Medic when Medic.UsePet.GetBool() => true, + CustomRoles.Monarch when Monarch.UsePet.GetBool() => true, + CustomRoles.CopyCat when CopyCat.UsePet.GetBool() => true, + CustomRoles.Farseer when Farseer.UsePet.GetBool() => true, + CustomRoles.Deputy when Deputy.UsePet.GetBool() => true, + CustomRoles.Crusader when Crusader.UsePet.GetBool() => true, + CustomRoles.Witness when Options.WitnessUsePet.GetBool() => true, + + // Speedrun + CustomRoles.Runner => true, + + _ => false + }; + } + + public static bool CancelsVote(this CustomRoles role) + { + return role switch + { + CustomRoles.Divinator when Divinator.CancelVote.GetBool() => true, + CustomRoles.Soothsayer when Soothsayer.CancelVote.GetBool() => true, + CustomRoles.Oracle when Oracle.CancelVote.GetBool() => true, + CustomRoles.Eraser when Eraser.CancelVote.GetBool() => true, + CustomRoles.Tether when Tether.CancelVote.GetBool() => true, + CustomRoles.Ricochet when Ricochet.CancelVote.GetBool() => true, + CustomRoles.Cleanser when Cleanser.CancelVote.GetBool() => true, + CustomRoles.NiceEraser when NiceEraser.CancelVote.GetBool() => true, + CustomRoles.Scout when Scout.CancelVote.GetBool() => true, + CustomRoles.Markseeker when Markseeker.CancelVote.GetBool() => true, + CustomRoles.Godfather when Options.GodfatherCancelVote.GetBool() => true, + CustomRoles.Socialite when Socialite.CancelVote.GetBool() => true, + + CustomRoles.President => true, + + _ => false + }; + } + + public static bool OnlySpawnsWithPets(this CustomRoles role) + { + return !(Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()) && role.SimpleAbilityTrigger() && role != CustomRoles.Chemist && !role.AlwaysUsesUnshift()) && OnlySpawnsWithPetsRoleList.Contains(role); + } + + public static bool NeedUpdateOnLights(this CustomRoles role) + { + return !role.UsesPetInsteadOfKill() && (role.IsDesyncRole() || role is + CustomRoles.Convict or + CustomRoles.Parasite or + CustomRoles.Refugee or + CustomRoles.Lighter or + CustomRoles.SecurityGuard or + CustomRoles.Ignitor or + CustomRoles.Saboteur or + CustomRoles.Inhibitor or + CustomRoles.Gambler); + } + + public static bool IsBetrayalAddon(this CustomRoles role) + { + return role is + CustomRoles.Charmed or + CustomRoles.Recruit or + CustomRoles.Contagious or + CustomRoles.Lovers or + CustomRoles.Madmate or + CustomRoles.Undead or + CustomRoles.Egoist; + } + + public static bool IsImpOnlyAddon(this CustomRoles role) + { + return Options.GroupedAddons[AddonTypes.ImpOnly].Contains(role); + } + + public static bool NeedsUpdateAfterDeath(this CustomRoles role) + { + return role is + CustomRoles.Weatherman or + CustomRoles.Altruist or + CustomRoles.Duellist; + } + + public static bool IsTaskBasedCrewmate(this CustomRoles role) + { + return role is + CustomRoles.Snitch or + CustomRoles.Speedrunner or + CustomRoles.Marshall or + CustomRoles.TimeManager or + CustomRoles.Ignitor or + CustomRoles.Guardian or + CustomRoles.Merchant or + CustomRoles.Mayor or + CustomRoles.Insight or + CustomRoles.Lyncher or + CustomRoles.Transporter; + } + + public static bool IsNotKnightable(this CustomRoles role) + { + return role is + CustomRoles.Mayor or + CustomRoles.Vindicator or + CustomRoles.Dictator or + CustomRoles.Knighted or + CustomRoles.Glitch or + CustomRoles.Pickpocket or + CustomRoles.TicketsStealer; + } + + public static bool IsNotAssignableMidGame(this CustomRoles role) + { + return role is + CustomRoles.Egoist or + CustomRoles.Workhorse or + CustomRoles.Cleansed or + CustomRoles.Busy or + CustomRoles.Lovers or + CustomRoles.Stressed or + CustomRoles.Lazy or + CustomRoles.Rascal or + CustomRoles.LastImpostor; + } + + public static bool ForceCancelShapeshift(this CustomRoles role) + { + return role is + CustomRoles.Swapster or + CustomRoles.Echo or + CustomRoles.Hangman or + CustomRoles.Generator; + } + + public static bool IsNoAnimationShifter(this CustomRoles role) + { + return role is + CustomRoles.Generator or + CustomRoles.Echo; + } + + public static bool AlwaysUsesUnshift(this CustomRoles role) + { + return role is + CustomRoles.Wizard; + } + + public static bool SimpleAbilityTrigger(this CustomRoles role) + { + return role is + CustomRoles.Wizard or + CustomRoles.Occultist or + CustomRoles.Warlock or + CustomRoles.Swiftclaw or + CustomRoles.Undertaker or + CustomRoles.Abyssbringer or + CustomRoles.Bomber or + CustomRoles.Nuker or + CustomRoles.Camouflager or + CustomRoles.Disperser or + CustomRoles.Escapee or + CustomRoles.FireWorks or + CustomRoles.Hypnotist or + CustomRoles.Librarian or + CustomRoles.Miner or + CustomRoles.RiftMaker or + CustomRoles.Assassin or + CustomRoles.QuickShooter or + CustomRoles.Sapper or + CustomRoles.Sniper or + CustomRoles.Twister or + CustomRoles.RouleteGrandeur or + CustomRoles.Enderman or + CustomRoles.Hookshot or + CustomRoles.Mycologist or + CustomRoles.Magician or + CustomRoles.Sprayer or + CustomRoles.Werewolf or + CustomRoles.WeaponMaster or + CustomRoles.Tiger or + CustomRoles.Bargainer or + CustomRoles.Chemist or + CustomRoles.Simon or + CustomRoles.Patroller or + CustomRoles.Weatherman or + CustomRoles.NoteKiller; + } + + public static bool CheckAddonConflict(CustomRoles role, PlayerControl pc) + { + return role.IsAdditionRole() && (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) || !neverList.TryGetValue(pc.GetCustomRole(), out List bannedAddonList) || !bannedAddonList.Contains(role)) && pc.GetCustomRole() is not CustomRoles.GuardianAngelEHR and not CustomRoles.God && !pc.Is(CustomRoles.Madmate) && !pc.Is(CustomRoles.GM) && role is not CustomRoles.Lovers && !pc.Is(CustomRoles.Needy) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && (!Options.AddonCanBeSettings.TryGetValue(role, out (OptionItem Imp, OptionItem Neutral, OptionItem Crew) o) || ((o.Imp.GetBool() || !pc.GetCustomRole().IsImpostor()) && (o.Neutral.GetBool() || !pc.GetCustomRole().IsNeutral()) && (o.Crew.GetBool() || !pc.IsCrewmate()))) && (!role.IsImpOnlyAddon() || pc.IsImpostor() || (pc.Is(CustomRoles.Traitor) && Traitor.CanGetImpostorOnlyAddons.GetBool())) && role switch + { + CustomRoles.Sleuth when pc.Is(CustomRoles.NecroGuesser) => false, + CustomRoles.Introvert when pc.GetCustomRole() is CustomRoles.Leery or CustomRoles.Samurai or CustomRoles.Arsonist or CustomRoles.Revolutionist or CustomRoles.Farseer or CustomRoles.Scavenger or CustomRoles.Analyst => false, + CustomRoles.Circumvent when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.RiftMaker => false, + CustomRoles.Oblivious when pc.Is(CustomRoles.Altruist) => false, + CustomRoles.AntiTP when pc.GetCustomRole() is CustomRoles.Transmitter or CustomRoles.Miner or CustomRoles.Escapee or CustomRoles.Tunneler => false, + CustomRoles.Swift when pc.Is(CustomRoles.Stealth) => false, + CustomRoles.Disco when pc.GetCustomRole() is CustomRoles.Chameleon or CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Alchemist => false, + CustomRoles.Egoist when pc.Is(CustomRoles.Gangster) => false, + CustomRoles.Nimble when pc.Is(CustomRoles.Oxyman) => false, + CustomRoles.Magnet when pc.Is(Team.Impostor) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Magnet) => false, + CustomRoles.Oblivious when pc.Is(CustomRoles.Amnesiac) && Amnesiac.RememberMode.GetValue() == 0 => false, + CustomRoles.Rookie when !pc.CanUseKillButton() => false, + CustomRoles.Energetic when !Options.UsePets.GetBool() => false, + CustomRoles.Madmate when pc.Is(CustomRoles.Sidekick) => false, + CustomRoles.Autopsy when pc.Is(CustomRoles.Doctor) || pc.Is(CustomRoles.Tracefinder) || pc.Is(CustomRoles.Scientist) || pc.Is(CustomRoles.ScientistEHR) || pc.Is(CustomRoles.Sunnyboy) => false, + CustomRoles.Necroview when pc.Is(CustomRoles.Doctor) => false, + CustomRoles.Lazy when pc.Is(CustomRoles.Speedrunner) => false, + CustomRoles.Mischievous when pc.Is(Team.Impostor) || (!pc.GetCustomRole().IsDesyncRole() && !pc.Is(CustomRoles.Bloodlust)) || !pc.IsNeutralKiller() || Main.PlayerStates[pc.PlayerId].Role.CanUseSabotage(pc) => false, + CustomRoles.Loyal when pc.IsCrewmate() && !Options.CrewCanBeLoyal.GetBool() => false, + CustomRoles.Lazy when pc.Is(CustomRoles.Needy) || pc.Is(CustomRoles.Snitch) || pc.Is(CustomRoles.Marshall) || pc.Is(CustomRoles.Transporter) || pc.Is(CustomRoles.Guardian) => false, + CustomRoles.Brakar when pc.Is(CustomRoles.Dictator) => false, + CustomRoles.Stressed when !pc.IsCrewmate() || pc.GetCustomRole().IsTasklessCrewmate() => false, + CustomRoles.Lazy when pc.GetCustomRole().IsNeutral() || pc.IsImpostor() || (pc.GetCustomRole().IsTasklessCrewmate() && !Options.TasklessCrewCanBeLazy.GetBool()) || (pc.GetCustomRole().IsTaskBasedCrewmate() && !Options.TaskBasedCrewCanBeLazy.GetBool()) => false, + CustomRoles.TicketsStealer or CustomRoles.Swift or CustomRoles.DeadlyQuota or CustomRoles.Damocles or CustomRoles.Mare when pc.Is(CustomRoles.Bomber) || pc.Is(CustomRoles.Nuker) || pc.Is(CustomRoles.BoobyTrap) || pc.Is(CustomRoles.Capitalism) => false, + CustomRoles.Torch when !pc.IsCrewmate() || pc.Is(CustomRoles.Bewilder) || pc.Is(CustomRoles.Sunglasses) || pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Bewilder when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Sunglasses) => false, + CustomRoles.Dynamo when pc.Is(CustomRoles.Spurt) => false, + CustomRoles.Spurt when pc.GetCustomSubRoles().Any(x => x is CustomRoles.Dynamo or CustomRoles.Swiftclaw or CustomRoles.Giant or CustomRoles.Flashman) => false, + CustomRoles.Sunglasses when pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.GuardianAngelEHR) || pc.Is(CustomRoles.Bewilder) => false, + CustomRoles.Guesser when pc.GetCustomRole() is CustomRoles.EvilGuesser or CustomRoles.NiceGuesser or CustomRoles.Doomsayer or CustomRoles.CopyCat => false, + CustomRoles.Madmate when !pc.CanBeMadmate() || pc.Is(CustomRoles.Egoist) || pc.Is(CustomRoles.Rascal) => false, + CustomRoles.Oblivious when pc.Is(CustomRoles.Detective) || pc.Is(CustomRoles.Cleaner) || pc.Is(CustomRoles.Medusa) || pc.Is(CustomRoles.Mortician) || pc.Is(CustomRoles.Mediumshiper) || pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Youtuber when !pc.IsCrewmate() || pc.Is(CustomRoles.Madmate) || pc.Is(CustomRoles.Sheriff) || pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Egoist when !pc.IsImpostor() => false, + CustomRoles.Damocles when pc.GetCustomRole() is CustomRoles.Bomber or CustomRoles.Nuker or CustomRoles.SerialKiller or CustomRoles.Cantankerous => false, + CustomRoles.Damocles when !pc.CanUseKillButton() => false, + CustomRoles.Flashman when pc.Is(CustomRoles.Swiftclaw) || pc.Is(CustomRoles.Giant) || pc.Is(CustomRoles.Spurt) => false, + CustomRoles.Giant when pc.Is(CustomRoles.Flashman) || pc.Is(CustomRoles.Spurt) => false, + CustomRoles.Necroview when pc.Is(CustomRoles.Visionary) => false, + CustomRoles.Mimic when pc.Is(CustomRoles.Mafia) => false, + CustomRoles.Rascal when !pc.IsCrewmate() => false, + CustomRoles.Needy when pc.GetCustomRole().IsAdditionRole() => false, + CustomRoles.TicketsStealer when pc.Is(CustomRoles.Vindicator) => false, + CustomRoles.Bloodlust when !pc.GetCustomRole().IsCrewmate() || pc.GetCustomRole().IsTaskBasedCrewmate() || pc.GetCustomRole() is CustomRoles.Medic => false, + CustomRoles.Mare when pc.Is(CustomRoles.Underdog) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Inhibitor) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Saboteur) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Swift) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Mafia) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Sniper) => false, + CustomRoles.Mare when pc.Is(CustomRoles.FireWorks) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Swooper) => false, + CustomRoles.Mare when pc.Is(CustomRoles.Vampire) => false, + CustomRoles.Torch when pc.Is(CustomRoles.Lighter) => false, + CustomRoles.Torch when pc.Is(CustomRoles.Ignitor) => false, + CustomRoles.Bewilder when pc.Is(CustomRoles.Lighter) => false, + CustomRoles.Bewilder when pc.Is(CustomRoles.Ignitor) => false, + CustomRoles.Sunglasses when pc.Is(CustomRoles.Lighter) => false, + CustomRoles.Sunglasses when pc.Is(CustomRoles.Ignitor) => false, + CustomRoles.Bait when pc.Is(CustomRoles.Demolitionist) => false, + CustomRoles.Trapper when pc.Is(CustomRoles.Demolitionist) => false, + CustomRoles.Lovers when pc.Is(CustomRoles.Romantic) => false, + CustomRoles.Mare when pc.Is(CustomRoles.LastImpostor) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Mare) => false, + CustomRoles.DualPersonality when (!pc.IsImpostor() && !pc.IsCrewmate()) || pc.Is(CustomRoles.Madmate) => false, + CustomRoles.Loyal when (!pc.IsImpostor() && !pc.IsCrewmate()) || pc.Is(CustomRoles.Madmate) => false, + CustomRoles.Loyal when pc.IsImpostor() && !Options.ImpCanBeLoyal.GetBool() => false, + CustomRoles.Seer when pc.Is(CustomRoles.Mortician) => false, + CustomRoles.Onbound when pc.Is(CustomRoles.SuperStar) => false, + CustomRoles.Rascal when pc.Is(CustomRoles.SuperStar) || pc.Is(CustomRoles.Madmate) => false, + CustomRoles.Madmate when pc.Is(CustomRoles.SuperStar) => false, + CustomRoles.Gravestone when pc.Is(CustomRoles.SuperStar) => false, + CustomRoles.Lucky when pc.Is(CustomRoles.Guardian) => false, + CustomRoles.Bait when pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Bait when pc.Is(CustomRoles.Trapper) => false, + CustomRoles.Trapper when pc.Is(CustomRoles.Bait) => false, + CustomRoles.DualPersonality when pc.Is(CustomRoles.Dictator) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Swooper) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Vampire) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Scavenger) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Puppeteer) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Warlock) => false, + CustomRoles.Swift when pc.Is(CustomRoles.EvilDiviner) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Witch) => false, + CustomRoles.Swift when pc.Is(CustomRoles.Mafia) => false, + CustomRoles.Reach when pc.Is(CustomRoles.Mafioso) => false, + CustomRoles.Trapper when pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Reach when !pc.CanUseKillButton() => false, + CustomRoles.Magnet when !pc.CanUseKillButton() => false, + CustomRoles.Haste when !pc.CanUseKillButton() || !pc.CanUseImpostorVentButton() => false, + CustomRoles.Diseased when pc.Is(CustomRoles.Antidote) => false, + CustomRoles.Antidote when pc.Is(CustomRoles.Diseased) => false, + CustomRoles.Flashman or CustomRoles.Giant when pc.GetCustomRole() is CustomRoles.Swooper or CustomRoles.Wraith or CustomRoles.Chameleon or CustomRoles.Alchemist => false, + CustomRoles.Bait when pc.Is(CustomRoles.Unreportable) => false, + CustomRoles.Busy when !pc.GetTaskState().HasTasks => false, + CustomRoles.Truant when pc.Is(CustomRoles.SoulHunter) => false, + CustomRoles.Nimble when !pc.IsCrewmate() => false, + CustomRoles.Physicist when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, + CustomRoles.Finder when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, + CustomRoles.Noisy when !pc.IsCrewmate() || pc.GetCustomRole().IsDesyncRole() => false, + CustomRoles.Unreportable when pc.Is(CustomRoles.Bait) => false, + CustomRoles.Oblivious when pc.Is(CustomRoles.Bloodhound) => false, + CustomRoles.Oblivious when pc.Is(CustomRoles.Vulture) => false, + CustomRoles.DoubleShot when pc.Is(CustomRoles.CopyCat) => false, + CustomRoles.Brakar when pc.Is(CustomRoles.Dictator) => false, + CustomRoles.Lucky when pc.Is(CustomRoles.Luckey) => false, + CustomRoles.Unlucky when pc.Is(CustomRoles.Luckey) => false, + CustomRoles.Unlucky when pc.Is(CustomRoles.Lucky) => false, + CustomRoles.Lucky when pc.Is(CustomRoles.Unlucky) => false, + CustomRoles.Fool when pc.Is(CustomRoles.SabotageMaster) || pc.Is(CustomRoles.GuardianAngelEHR) => false, + CustomRoles.Bloodhound when pc.Is(CustomRoles.Oblivious) => false, + CustomRoles.DoubleShot when !pc.Is(CustomRoles.EvilGuesser) && !pc.Is(CustomRoles.NiceGuesser) && !Options.GuesserMode.GetBool() => false, + CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.EvilGuesser) && pc.Is(CustomRoleTypes.Impostor) && !Options.ImpostorsCanGuess.GetBool() => false, + CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.NiceGuesser) && pc.Is(CustomRoleTypes.Crewmate) && !Options.CrewmatesCanGuess.GetBool() => false, + CustomRoles.DoubleShot when !pc.Is(CustomRoles.Guesser) && ((pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool()) || (pc.IsNeutralKiller() && !Options.NeutralKillersCanGuess.GetBool())) => false, + _ => true + }; + } + + public static Team GetTeam(this CustomRoles role) + { + if (role.IsImpostorTeamV2()) return Team.Impostor; + + if (role.IsNeutralTeamV2()) return Team.Neutral; + + return role.IsCrewmateTeamV2() ? Team.Crewmate : Team.None; + } + + public static bool Is(this CustomRoles role, Team team) + { + return team switch + { + Team.Impostor => role.IsImpostorTeamV2(), + Team.Neutral => role.IsNeutralTeamV2(), + Team.Crewmate => role.IsCrewmateTeamV2(), + Team.None => role.GetCountTypes() is CountTypes.OutOfGame or CountTypes.None || role == CustomRoles.GM, + _ => false + }; + } + + public static RoleTypes GetRoleTypes(this CustomRoles role) + { + if (Enum.TryParse(role.GetVNRole(true).ToString().Replace("EHR", ""), true, out RoleTypes type)) return type; + + return role.IsImpostor() ? RoleTypes.Impostor : RoleTypes.Crewmate; + } + + public static bool IsDesyncRole(this CustomRoles role) + { + return role.GetDYRole() != RoleTypes.GuardianAngel; + } + + public static bool IsImpostorTeam(this CustomRoles role) + { + return role.IsImpostor() || role == CustomRoles.Madmate; + } + + public static bool IsCrewmate(this CustomRoles role) + { + return !role.IsImpostor() && !role.IsNeutral() && !role.IsMadmate(); + } + + public static bool IsImpostorTeamV2(this CustomRoles role) + { + return (role.IsImpostorTeam() && role != CustomRoles.Trickster && !role.IsConverted()) || role is CustomRoles.Rascal or CustomRoles.Madmate || role.IsMadmate(); + } + + public static bool IsNeutralTeamV2(this CustomRoles role) + { + return role.IsConverted() || (role.IsNeutral() && role != CustomRoles.Madmate); + } + + public static bool IsCrewmateTeamV2(this CustomRoles role) + { + return (!role.IsImpostorTeamV2() && !role.IsNeutralTeamV2()) || (role == CustomRoles.Trickster && !role.IsConverted()); + } + + public static bool IsConverted(this CustomRoles role) + { + return (role == CustomRoles.Egoist && ParityCop.ParityCheckEgoistInt() == 1) || role is + CustomRoles.Charmed or + CustomRoles.Recruit or + CustomRoles.Contagious or + CustomRoles.Undead; + } + + public static bool IsRevealingRole(this CustomRoles role, PlayerControl target) + { + return (role is CustomRoles.Mayor && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.AllTasksCompleted()) || + (role is CustomRoles.SuperStar && Options.EveryOneKnowSuperStar.GetBool()) || + (role is CustomRoles.Marshall && target.AllTasksCompleted()) || + (role is CustomRoles.Workaholic && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || + (role is CustomRoles.Doctor && Options.DoctorVisibleToEveryone.GetBool()) || + (role is CustomRoles.Bait && Options.BaitNotification.GetBool() && ParityCop.ParityCheckBaitCountType.GetBool()); + } + + + public static bool IsVanilla(this CustomRoles role) + { + return role is + CustomRoles.Crewmate or + CustomRoles.Engineer or + CustomRoles.Noisemaker or + CustomRoles.Tracker or + CustomRoles.Scientist or + CustomRoles.GuardianAngel or + CustomRoles.Impostor or + CustomRoles.Phantom or + CustomRoles.Shapeshifter; + } + + public static CustomRoleTypes GetCustomRoleTypes(this CustomRoles role) + { + var type = CustomRoleTypes.Crewmate; + if (role.IsImpostor() || role.IsMadmate()) type = CustomRoleTypes.Impostor; + + if (role.IsNeutral()) type = CustomRoleTypes.Neutral; + + if (role.IsAdditionRole()) type = CustomRoleTypes.Addon; + + return type; + } + + public static bool RoleExist(this CustomRoles role, bool countDead = false) + { + return Main.AllPlayerControls.Any(x => x.Is(role) && (countDead || x.IsAlive())); + } + + public static int GetCount(this CustomRoles role) + { + if (role.IsVanilla()) + { + if (Options.DisableVanillaRoles.GetBool()) return 0; + + IRoleOptionsCollection roleOpt = Main.NormalOptions.RoleOptions; + + return role switch + { + CustomRoles.Engineer => roleOpt.GetNumPerGame(RoleTypes.Engineer), + CustomRoles.Noisemaker => roleOpt.GetNumPerGame(RoleTypes.Noisemaker), + CustomRoles.Tracker => roleOpt.GetNumPerGame(RoleTypes.Tracker), + CustomRoles.Scientist => roleOpt.GetNumPerGame(RoleTypes.Scientist), + CustomRoles.Impostor => roleOpt.GetNumPerGame(RoleTypes.Impostor), + CustomRoles.Phantom => roleOpt.GetNumPerGame(RoleTypes.Phantom), + CustomRoles.Shapeshifter => roleOpt.GetNumPerGame(RoleTypes.Shapeshifter), + CustomRoles.GuardianAngel => roleOpt.GetNumPerGame(RoleTypes.GuardianAngel), + CustomRoles.Crewmate => roleOpt.GetNumPerGame(RoleTypes.Crewmate), + _ => 0 + }; + } + + return Options.GetRoleCount(role); + } + + public static int GetMode(this CustomRoles role) + { + return Options.GetRoleSpawnMode(role); + } + + public static float GetChance(this CustomRoles role) + { + if (role.IsVanilla()) + { + IRoleOptionsCollection roleOpt = Main.NormalOptions.RoleOptions; + + return role switch + { + CustomRoles.Engineer => roleOpt.GetChancePerGame(RoleTypes.Engineer), + CustomRoles.Noisemaker => roleOpt.GetChancePerGame(RoleTypes.Noisemaker), + CustomRoles.Tracker => roleOpt.GetChancePerGame(RoleTypes.Tracker), + CustomRoles.Scientist => roleOpt.GetChancePerGame(RoleTypes.Scientist), + CustomRoles.Impostor => roleOpt.GetChancePerGame(RoleTypes.Impostor), + CustomRoles.Phantom => roleOpt.GetChancePerGame(RoleTypes.Phantom), + CustomRoles.Shapeshifter => roleOpt.GetChancePerGame(RoleTypes.Shapeshifter), + CustomRoles.GuardianAngel => roleOpt.GetChancePerGame(RoleTypes.GuardianAngel), + CustomRoles.Crewmate => roleOpt.GetChancePerGame(RoleTypes.Crewmate), + _ => 0 + } / 100f; + } + + return Options.GetRoleChance(role); + } + + public static bool IsEnable(this CustomRoles role) + { + return role.GetCount() > 0; + } + + public static CountTypes GetCountTypes(this CustomRoles role) + { + return role switch + { + CustomRoles.GM => CountTypes.OutOfGame, + CustomRoles.Sidekick when Jackal.SidekickCountMode.GetValue() == 0 => CountTypes.Jackal, + CustomRoles.Sidekick when Jackal.SidekickCountMode.GetValue() == 1 => CountTypes.OutOfGame, + CustomRoles.Deathknight => CountTypes.Necromancer, + CustomRoles.Parasite => CountTypes.Impostor, + CustomRoles.Crewpostor => CountTypes.Impostor, + CustomRoles.Refugee => CountTypes.Impostor, + CustomRoles.Gaslighter => Gaslighter.WinCondition.GetValue() == 2 ? CountTypes.Gaslighter : CountTypes.Crew, + CustomRoles.DarkHide when DarkHide.SnatchesWin.GetBool() => CountTypes.Crew, + CustomRoles.Arsonist when !Options.ArsonistKeepsGameGoing.GetBool() => CountTypes.Crew, + CustomRoles.SchrodingersCat => SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool() ? CountTypes.Crew : CountTypes.OutOfGame, + CustomRoles.DarkHide => !DarkHide.SnatchesWin.GetBool() ? CountTypes.DarkHide : CountTypes.Crew, + CustomRoles.Arsonist => Options.ArsonistKeepsGameGoing.GetBool() ? CountTypes.Arsonist : CountTypes.Crew, + CustomRoles.Sheriff => Sheriff.KeepsGameGoing.GetBool() ? CountTypes.Sheriff : CountTypes.Crew, + CustomRoles.Shifter => CountTypes.OutOfGame, + CustomRoles.NoteKiller when !NoteKiller.CountsAsNeutralKiller => CountTypes.Crew, + + _ => Enum.TryParse(role.ToString(), true, out CountTypes type) + ? type + : role.Is(Team.Impostor) || role == CustomRoles.Trickster + ? CountTypes.Impostor + : CountTypes.Crew + }; + } + + public static Color GetTeamColor(this Team team) + { + return ColorUtility.TryParseHtmlString(team switch + { + Team.Crewmate => Main.CrewmateColor, + Team.Neutral => Main.NeutralColor, + Team.Impostor => Main.ImpostorColor, + _ => string.Empty + }, out Color color) + ? color + : Color.clear; + } + + public static string ToColoredString(this CustomRoles role) + { + return Utils.ColorString(Utils.GetRoleColor(role), Translator.GetString($"{role}")); + } + + #region OptionsAndCategories + + public static RoleOptionType GetRoleOptionType(this CustomRoles role) + { + if (role.IsImpostor()) return role.GetImpostorRoleCategory(); + if (role.IsCrewmate()) return role.GetCrewmateRoleCategory(); + if (role.IsNeutral(true)) return role.GetNeutralRoleCategory(); + + return RoleOptionType.Crewmate_Miscellaneous; + } + + public static Color GetRoleOptionTypeColor(this RoleOptionType type) + { + return type switch + { + RoleOptionType.Impostor_Killing => Utils.GetRoleColor(CustomRoles.Witness), + RoleOptionType.Impostor_Support => Utils.GetRoleColor(CustomRoles.Bubble), + RoleOptionType.Impostor_Concealing => Utils.GetRoleColor(CustomRoles.CyberStar), + RoleOptionType.Impostor_Miscellaneous => Palette.ImpostorRed, + RoleOptionType.Crewmate_Miscellaneous => Palette.CrewmateBlue, + RoleOptionType.Crewmate_Investigate => Utils.GetRoleColor(CustomRoles.Detective), + RoleOptionType.Crewmate_Support => Utils.GetRoleColor(CustomRoles.NiceEraser), + RoleOptionType.Crewmate_Killing => Utils.GetRoleColor(CustomRoles.Sheriff), + RoleOptionType.Crewmate_Power => Utils.GetRoleColor(CustomRoles.Electric), + RoleOptionType.Crewmate_Chaos => Utils.GetRoleColor(CustomRoles.Tornado), + RoleOptionType.Neutral_Benign => Utils.GetRoleColor(CustomRoles.Chameleon), + RoleOptionType.Neutral_Evil => Utils.GetRoleColor(CustomRoles.Mario), + RoleOptionType.Neutral_Killing => Palette.ImpostorRed, + _ => Utils.GetRoleColor(CustomRoles.SwordsMan) + }; + } + + public static TabGroup GetTabFromOptionType(this RoleOptionType type) + { + return type switch + { + RoleOptionType.Impostor_Killing => TabGroup.ImpostorRoles, + RoleOptionType.Impostor_Support => TabGroup.ImpostorRoles, + RoleOptionType.Impostor_Concealing => TabGroup.ImpostorRoles, + RoleOptionType.Impostor_Miscellaneous => TabGroup.ImpostorRoles, + RoleOptionType.Crewmate_Miscellaneous => TabGroup.CrewmateRoles, + RoleOptionType.Crewmate_Investigate => TabGroup.CrewmateRoles, + RoleOptionType.Crewmate_Support => TabGroup.CrewmateRoles, + RoleOptionType.Crewmate_Killing => TabGroup.CrewmateRoles, + RoleOptionType.Crewmate_Power => TabGroup.CrewmateRoles, + RoleOptionType.Crewmate_Chaos => TabGroup.CrewmateRoles, + RoleOptionType.Neutral_Benign => TabGroup.NeutralRoles, + RoleOptionType.Neutral_Evil => TabGroup.NeutralRoles, + RoleOptionType.Neutral_Killing => TabGroup.NeutralRoles, + _ => TabGroup.OtherRoles + }; + } + + public static Color GetAddonTypeColor(this AddonTypes type) + { + return type switch + { + AddonTypes.ImpOnly => Palette.ImpostorRed, + AddonTypes.Helpful => Palette.CrewmateBlue, + AddonTypes.Harmful => Utils.GetRoleColor(CustomRoles.Sprayer), + AddonTypes.Mixed => Utils.GetRoleColor(CustomRoles.TaskManager), + _ => Palette.CrewmateBlue + }; + } + + public static RoleOptionType GetNeutralRoleCategory(this CustomRoles role) + { + return role switch + { + CustomRoles.Opportunist => RoleOptionType.Neutral_Benign, + CustomRoles.Lawyer => RoleOptionType.Neutral_Benign, + CustomRoles.Amnesiac => RoleOptionType.Neutral_Benign, + CustomRoles.Postman => RoleOptionType.Neutral_Benign, + CustomRoles.SchrodingersCat => RoleOptionType.Neutral_Benign, + CustomRoles.Predator => RoleOptionType.Neutral_Benign, + CustomRoles.Pursuer => RoleOptionType.Neutral_Benign, + CustomRoles.FFF => RoleOptionType.Neutral_Benign, + CustomRoles.Revolutionist => RoleOptionType.Neutral_Benign, + CustomRoles.Impartial => RoleOptionType.Neutral_Benign, + CustomRoles.Backstabber => RoleOptionType.Neutral_Benign, + CustomRoles.Shifter => RoleOptionType.Neutral_Benign, + CustomRoles.Sunnyboy => RoleOptionType.Neutral_Benign, + CustomRoles.Maverick => RoleOptionType.Neutral_Benign, + CustomRoles.Romantic => RoleOptionType.Neutral_Benign, + CustomRoles.VengefulRomantic => RoleOptionType.Neutral_Benign, + CustomRoles.Phantasm => RoleOptionType.Neutral_Benign, + CustomRoles.Provocateur => RoleOptionType.Neutral_Benign, + CustomRoles.SoulHunter => RoleOptionType.Neutral_Benign, + CustomRoles.Technician => RoleOptionType.Neutral_Benign, + CustomRoles.Tank => RoleOptionType.Neutral_Benign, + CustomRoles.Totocalcio => RoleOptionType.Neutral_Benign, + CustomRoles.Arsonist => RoleOptionType.Neutral_Evil, + CustomRoles.Curser => RoleOptionType.Neutral_Evil, + CustomRoles.Jester => RoleOptionType.Neutral_Evil, + CustomRoles.Gaslighter => RoleOptionType.Neutral_Evil, + CustomRoles.God => RoleOptionType.Neutral_Evil, + CustomRoles.Executioner => RoleOptionType.Neutral_Evil, + CustomRoles.Doomsayer => RoleOptionType.Neutral_Evil, + CustomRoles.Mario => RoleOptionType.Neutral_Evil, + CustomRoles.Terrorist => RoleOptionType.Neutral_Evil, + CustomRoles.Collector => RoleOptionType.Neutral_Evil, + CustomRoles.NecroGuesser => RoleOptionType.Neutral_Evil, + CustomRoles.Vulture => RoleOptionType.Neutral_Evil, + CustomRoles.Workaholic => RoleOptionType.Neutral_Evil, + CustomRoles.Deathknight => RoleOptionType.Neutral_Evil, + CustomRoles.Innocent => RoleOptionType.Neutral_Evil, + _ => role.IsNK(true) ? RoleOptionType.Neutral_Killing : role.IsImpostor() ? RoleOptionType.Impostor_Miscellaneous : RoleOptionType.Crewmate_Miscellaneous + }; + } + + public static RoleOptionType GetImpostorRoleCategory(this CustomRoles role) + { + return role switch + { + CustomRoles.Sans => RoleOptionType.Impostor_Killing, + CustomRoles.Assassin => RoleOptionType.Impostor_Killing, + CustomRoles.Assumer => RoleOptionType.Impostor_Killing, + CustomRoles.Augmenter => RoleOptionType.Impostor_Killing, + CustomRoles.Bomber => RoleOptionType.Impostor_Killing, + CustomRoles.BountyHunter => RoleOptionType.Impostor_Killing, + CustomRoles.Cantankerous => RoleOptionType.Impostor_Killing, + CustomRoles.Chronomancer => RoleOptionType.Impostor_Killing, + CustomRoles.Councillor => RoleOptionType.Impostor_Killing, + CustomRoles.EvilGuesser => RoleOptionType.Impostor_Killing, + CustomRoles.FireWorks => RoleOptionType.Impostor_Killing, + CustomRoles.Greedier => RoleOptionType.Impostor_Killing, + CustomRoles.Hitman => RoleOptionType.Impostor_Killing, + CustomRoles.Inhibitor => RoleOptionType.Impostor_Killing, + CustomRoles.Kamikaze => RoleOptionType.Impostor_Killing, + CustomRoles.Minimalism => RoleOptionType.Impostor_Killing, + CustomRoles.Lurker => RoleOptionType.Impostor_Killing, + CustomRoles.Mafioso => RoleOptionType.Impostor_Killing, + CustomRoles.Mafia => RoleOptionType.Impostor_Killing, + CustomRoles.SerialKiller => RoleOptionType.Impostor_Killing, + CustomRoles.Nuker => RoleOptionType.Impostor_Killing, + CustomRoles.Overheat => RoleOptionType.Impostor_Killing, + CustomRoles.OverKiller => RoleOptionType.Impostor_Killing, + CustomRoles.QuickShooter => RoleOptionType.Impostor_Killing, + CustomRoles.Saboteur => RoleOptionType.Impostor_Killing, + CustomRoles.Sapper => RoleOptionType.Impostor_Killing, + CustomRoles.Sniper => RoleOptionType.Impostor_Killing, + CustomRoles.BoobyTrap => RoleOptionType.Impostor_Killing, + CustomRoles.Underdog => RoleOptionType.Impostor_Killing, + CustomRoles.Wasp => RoleOptionType.Impostor_Killing, + CustomRoles.Witch => RoleOptionType.Impostor_Killing, + CustomRoles.Zombie => RoleOptionType.Impostor_Killing, + CustomRoles.Bard => RoleOptionType.Impostor_Support, + CustomRoles.Blackmailer => RoleOptionType.Impostor_Support, + CustomRoles.Camouflager => RoleOptionType.Impostor_Support, + CustomRoles.Capitalism => RoleOptionType.Impostor_Support, + CustomRoles.Cleaner => RoleOptionType.Impostor_Support, + CustomRoles.Commander => RoleOptionType.Impostor_Support, + CustomRoles.Consort => RoleOptionType.Impostor_Support, + CustomRoles.Framer => RoleOptionType.Impostor_Support, + CustomRoles.Deathpact => RoleOptionType.Impostor_Support, + CustomRoles.Devourer => RoleOptionType.Impostor_Support, + CustomRoles.Disperser => RoleOptionType.Impostor_Support, + CustomRoles.Dazzler => RoleOptionType.Impostor_Support, + CustomRoles.Eraser => RoleOptionType.Impostor_Support, + CustomRoles.Freezer => RoleOptionType.Impostor_Support, + CustomRoles.Gangster => RoleOptionType.Impostor_Support, + CustomRoles.Godfather => RoleOptionType.Impostor_Support, + CustomRoles.Hypnotist => RoleOptionType.Impostor_Support, + CustomRoles.Librarian => RoleOptionType.Impostor_Support, + CustomRoles.Nullifier => RoleOptionType.Impostor_Support, + CustomRoles.Occultist => RoleOptionType.Impostor_Support, + CustomRoles.Silencer => RoleOptionType.Impostor_Support, + CustomRoles.Swapster => RoleOptionType.Impostor_Support, + CustomRoles.TimeThief => RoleOptionType.Impostor_Support, + CustomRoles.Twister => RoleOptionType.Impostor_Support, + CustomRoles.Ventriloquist => RoleOptionType.Impostor_Support, + CustomRoles.Vindicator => RoleOptionType.Impostor_Support, + CustomRoles.YinYanger => RoleOptionType.Impostor_Support, + CustomRoles.Hacker => RoleOptionType.Impostor_Concealing, + CustomRoles.Duellist => RoleOptionType.Impostor_Concealing, + CustomRoles.Echo => RoleOptionType.Impostor_Concealing, + CustomRoles.Escapee => RoleOptionType.Impostor_Concealing, + CustomRoles.Hangman => RoleOptionType.Impostor_Concealing, + CustomRoles.Kidnapper => RoleOptionType.Impostor_Concealing, + CustomRoles.BallLightning => RoleOptionType.Impostor_Concealing, + CustomRoles.Mastermind => RoleOptionType.Impostor_Concealing, + CustomRoles.Miner => RoleOptionType.Impostor_Concealing, + CustomRoles.Morphling => RoleOptionType.Impostor_Concealing, + CustomRoles.Penguin => RoleOptionType.Impostor_Concealing, + CustomRoles.Puppeteer => RoleOptionType.Impostor_Concealing, + CustomRoles.RiftMaker => RoleOptionType.Impostor_Concealing, + CustomRoles.Scavenger => RoleOptionType.Impostor_Concealing, + CustomRoles.ImperiusCurse => RoleOptionType.Impostor_Concealing, + CustomRoles.Swiftclaw => RoleOptionType.Impostor_Concealing, + CustomRoles.Swooper => RoleOptionType.Impostor_Concealing, + CustomRoles.Stealth => RoleOptionType.Impostor_Concealing, + CustomRoles.Trickster => RoleOptionType.Impostor_Concealing, + CustomRoles.Undertaker => RoleOptionType.Impostor_Concealing, + CustomRoles.Vampire => RoleOptionType.Impostor_Concealing, + CustomRoles.Warlock => RoleOptionType.Impostor_Concealing, + CustomRoles.Abyssbringer => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.AntiAdminer => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.Changeling => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.EvilDiviner => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.CursedWolf => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.EvilTracker => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.Gambler => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.Generator => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.Visionary => RoleOptionType.Impostor_Miscellaneous, + CustomRoles.Wildling => RoleOptionType.Impostor_Miscellaneous, + _ => role.IsCrewmate() ? RoleOptionType.Crewmate_Miscellaneous : RoleOptionType.Neutral_Benign + }; + } + + public static RoleOptionType GetCrewmateRoleCategory(this CustomRoles role) + { + return role switch + { + CustomRoles.Addict => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.CameraMan => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.CyberStar => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Demolitionist => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Express => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Needy => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Journalist => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Luckey => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Mole => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Nightmare => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Paranoia => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Ricochet => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Safeguard => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.SuperStar => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Tether => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Transmitter => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Tracefinder => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Tunneler => RoleOptionType.Crewmate_Miscellaneous, + CustomRoles.Analyst => RoleOptionType.Crewmate_Investigate, + CustomRoles.Catcher => RoleOptionType.Crewmate_Investigate, + CustomRoles.Chameleon => RoleOptionType.Crewmate_Investigate, + CustomRoles.Clairvoyant => RoleOptionType.Crewmate_Investigate, + CustomRoles.Bloodhound => RoleOptionType.Crewmate_Investigate, + CustomRoles.Detective => RoleOptionType.Crewmate_Investigate, + CustomRoles.Doctor => RoleOptionType.Crewmate_Investigate, + CustomRoles.DoubleAgent => RoleOptionType.Crewmate_Investigate, + CustomRoles.Druid => RoleOptionType.Crewmate_Investigate, + CustomRoles.Enigma => RoleOptionType.Crewmate_Investigate, + CustomRoles.Farseer => RoleOptionType.Crewmate_Investigate, + CustomRoles.Divinator => RoleOptionType.Crewmate_Investigate, + CustomRoles.GuessManagerRole => RoleOptionType.Crewmate_Investigate, + CustomRoles.Ignitor => RoleOptionType.Crewmate_Investigate, + CustomRoles.Insight => RoleOptionType.Crewmate_Investigate, + CustomRoles.ParityCop => RoleOptionType.Crewmate_Investigate, + CustomRoles.Inquirer => RoleOptionType.Crewmate_Investigate, + CustomRoles.Leery => RoleOptionType.Crewmate_Investigate, + CustomRoles.Lighter => RoleOptionType.Crewmate_Investigate, + CustomRoles.Lookout => RoleOptionType.Crewmate_Investigate, + CustomRoles.Lyncher => RoleOptionType.Crewmate_Investigate, + CustomRoles.Markseeker => RoleOptionType.Crewmate_Investigate, + CustomRoles.Mediumshiper => RoleOptionType.Crewmate_Investigate, + CustomRoles.Monitor => RoleOptionType.Crewmate_Investigate, + CustomRoles.Mortician => RoleOptionType.Crewmate_Investigate, + CustomRoles.NiceHacker => RoleOptionType.Crewmate_Investigate, + CustomRoles.Observer => RoleOptionType.Crewmate_Investigate, + CustomRoles.Oracle => RoleOptionType.Crewmate_Investigate, + CustomRoles.Perceiver => RoleOptionType.Crewmate_Investigate, + CustomRoles.Psychic => RoleOptionType.Crewmate_Investigate, + CustomRoles.Rabbit => RoleOptionType.Crewmate_Investigate, + CustomRoles.Scout => RoleOptionType.Crewmate_Investigate, + CustomRoles.Sentry => RoleOptionType.Crewmate_Investigate, + CustomRoles.Shiftguard => RoleOptionType.Crewmate_Investigate, + CustomRoles.Snitch => RoleOptionType.Crewmate_Investigate, + CustomRoles.Socialite => RoleOptionType.Crewmate_Investigate, + CustomRoles.Soothsayer => RoleOptionType.Crewmate_Investigate, + CustomRoles.Spiritualist => RoleOptionType.Crewmate_Investigate, + CustomRoles.Spy => RoleOptionType.Crewmate_Investigate, + CustomRoles.TaskManager => RoleOptionType.Crewmate_Investigate, + CustomRoles.Whisperer => RoleOptionType.Crewmate_Investigate, + CustomRoles.Witness => RoleOptionType.Crewmate_Investigate, + CustomRoles.Aid => RoleOptionType.Crewmate_Support, + CustomRoles.Altruist => RoleOptionType.Crewmate_Support, + CustomRoles.Autocrat => RoleOptionType.Crewmate_Support, + CustomRoles.Bane => RoleOptionType.Crewmate_Support, + CustomRoles.Beacon => RoleOptionType.Crewmate_Support, + CustomRoles.Benefactor => RoleOptionType.Crewmate_Support, + CustomRoles.Bodyguard => RoleOptionType.Crewmate_Support, + CustomRoles.Chef => RoleOptionType.Crewmate_Support, + CustomRoles.Cleanser => RoleOptionType.Crewmate_Support, + CustomRoles.Convener => RoleOptionType.Crewmate_Support, + CustomRoles.Crusader => RoleOptionType.Crewmate_Support, + CustomRoles.Deputy => RoleOptionType.Crewmate_Support, + CustomRoles.DonutDelivery => RoleOptionType.Crewmate_Support, + CustomRoles.Doormaster => RoleOptionType.Crewmate_Support, + CustomRoles.DovesOfNeace => RoleOptionType.Crewmate_Support, + CustomRoles.Electric => RoleOptionType.Crewmate_Support, + CustomRoles.Escort => RoleOptionType.Crewmate_Support, + CustomRoles.Gaulois => RoleOptionType.Crewmate_Support, + CustomRoles.Grappler => RoleOptionType.Crewmate_Support, + CustomRoles.Grenadier => RoleOptionType.Crewmate_Support, + CustomRoles.Jailor => RoleOptionType.Crewmate_Support, + CustomRoles.Mathematician => RoleOptionType.Crewmate_Support, + CustomRoles.SabotageMaster => RoleOptionType.Crewmate_Support, + CustomRoles.Medic => RoleOptionType.Crewmate_Support, + CustomRoles.Merchant => RoleOptionType.Crewmate_Support, + CustomRoles.Monarch => RoleOptionType.Crewmate_Support, + CustomRoles.NiceSwapper => RoleOptionType.Crewmate_Support, + CustomRoles.Negotiator => RoleOptionType.Crewmate_Support, + CustomRoles.Rhapsode => RoleOptionType.Crewmate_Support, + CustomRoles.SecurityGuard => RoleOptionType.Crewmate_Support, + CustomRoles.SpeedBooster => RoleOptionType.Crewmate_Support, + CustomRoles.TimeManager => RoleOptionType.Crewmate_Support, + CustomRoles.TimeMaster => RoleOptionType.Crewmate_Support, + CustomRoles.Transporter => RoleOptionType.Crewmate_Support, + CustomRoles.Ventguard => RoleOptionType.Crewmate_Support, + CustomRoles.Wizard => RoleOptionType.Crewmate_Support, + CustomRoles.Drainer => RoleOptionType.Crewmate_Killing, + CustomRoles.Judge => RoleOptionType.Crewmate_Killing, + CustomRoles.NiceGuesser => RoleOptionType.Crewmate_Killing, + CustomRoles.Sentinel => RoleOptionType.Crewmate_Killing, + CustomRoles.Sheriff => RoleOptionType.Crewmate_Killing, + CustomRoles.Veteran => RoleOptionType.Crewmate_Killing, + CustomRoles.SwordsMan => RoleOptionType.Crewmate_Killing, + CustomRoles.Adrenaline => RoleOptionType.Crewmate_Power, + CustomRoles.Adventurer => RoleOptionType.Crewmate_Power, + CustomRoles.Alchemist => RoleOptionType.Crewmate_Power, + CustomRoles.CopyCat => RoleOptionType.Crewmate_Power, + CustomRoles.Detour => RoleOptionType.Crewmate_Power, + CustomRoles.Dictator => RoleOptionType.Crewmate_Power, + CustomRoles.Guardian => RoleOptionType.Crewmate_Power, + CustomRoles.Marshall => RoleOptionType.Crewmate_Power, + CustomRoles.Mayor => RoleOptionType.Crewmate_Power, + CustomRoles.NiceEraser => RoleOptionType.Crewmate_Power, + CustomRoles.Oxyman => RoleOptionType.Crewmate_Power, + CustomRoles.President => RoleOptionType.Crewmate_Power, + CustomRoles.Speedrunner => RoleOptionType.Crewmate_Power, + CustomRoles.Telekinetic => RoleOptionType.Crewmate_Power, + CustomRoles.Ankylosaurus => RoleOptionType.Crewmate_Chaos, + CustomRoles.Car => RoleOptionType.Crewmate_Chaos, + CustomRoles.Dad => RoleOptionType.Crewmate_Chaos, + CustomRoles.Goose => RoleOptionType.Crewmate_Chaos, + CustomRoles.Randomizer => RoleOptionType.Crewmate_Chaos, + CustomRoles.ToiletMaster => RoleOptionType.Crewmate_Chaos, + CustomRoles.Tornado => RoleOptionType.Crewmate_Chaos, + _ => role.IsImpostor() ? RoleOptionType.Impostor_Miscellaneous : RoleOptionType.Neutral_Benign + }; + } + + #endregion +} + #pragma warning disable IDE0079 - [SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "InconsistentNaming")] #pragma warning restore IDE0079 - public enum RoleOptionType - { - Impostor_Killing, - Impostor_Support, - Impostor_Concealing, - Impostor_Miscellaneous, - Crewmate_Miscellaneous, - Crewmate_Investigate, - Crewmate_Support, - Crewmate_Killing, - Crewmate_Power, - Crewmate_Chaos, - Neutral_Benign, - Neutral_Evil, - Neutral_Killing - } +public enum RoleOptionType +{ + Impostor_Killing, + Impostor_Support, + Impostor_Concealing, + Impostor_Miscellaneous, + Crewmate_Miscellaneous, + Crewmate_Investigate, + Crewmate_Support, + Crewmate_Killing, + Crewmate_Power, + Crewmate_Chaos, + Neutral_Benign, + Neutral_Evil, + Neutral_Killing +} - public enum AddonTypes - { - ImpOnly, - Helpful, - Harmful, - Mixed - } +public enum AddonTypes +{ + ImpOnly, + Helpful, + Harmful, + Mixed +} - public enum CustomRoleTypes - { - Crewmate, - Impostor, - Neutral, - Addon - } +public enum CustomRoleTypes +{ + Crewmate, + Impostor, + Neutral, + Addon +} - [SuppressMessage("ReSharper", "UnusedMember.Global")] - public enum CountTypes - { - OutOfGame, - None, - Crew, - Impostor, - CustomTeam, - Bloodlust, - Jackal, - Doppelganger, - Pelican, - Gamer, - BloodKnight, - Poisoner, - Succubus, - Necromancer, - HexMaster, - Wraith, - NSerialKiller, - Weatherman, - NoteKiller, - Vortex, - Beehive, - RouleteGrandeur, - Nonplus, - Tremor, - Evolver, - Rogue, - Patroller, - Simon, - Chemist, - QuizMaster, - Samurai, - Bargainer, - Tiger, - Enderman, - Mycologist, - Bubble, - Hookshot, - Sprayer, - PlagueDoctor, - Magician, - WeaponMaster, - Reckless, - Pyromaniac, - Eclipse, - Vengeance, - HeadHunter, - Gaslighter, - Imitator, - Werewolf, - Juggernaut, - Agitater, - Virus, - DarkHide, - Jinx, - Ritualist, - Pickpocket, - Traitor, - Medusa, - Bandit, - Spiritcaller, - RuthlessRomantic, - Pestilence, - PlagueBearer, - Glitch, - Arsonist, - Cherokious, - Sheriff - } +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public enum CountTypes +{ + OutOfGame, + None, + Crew, + Impostor, + CustomTeam, + Bloodlust, + Jackal, + Doppelganger, + Pelican, + Gamer, + BloodKnight, + Poisoner, + Succubus, + Necromancer, + HexMaster, + Wraith, + NSerialKiller, + Weatherman, + NoteKiller, + Vortex, + Beehive, + RouleteGrandeur, + Nonplus, + Tremor, + Evolver, + Rogue, + Patroller, + Simon, + Chemist, + QuizMaster, + Samurai, + Bargainer, + Tiger, + Enderman, + Mycologist, + Bubble, + Hookshot, + Sprayer, + PlagueDoctor, + Magician, + WeaponMaster, + Reckless, + Pyromaniac, + Eclipse, + Vengeance, + HeadHunter, + Gaslighter, + Imitator, + Werewolf, + Juggernaut, + Agitater, + Virus, + DarkHide, + Jinx, + Ritualist, + Pickpocket, + Traitor, + Medusa, + Bandit, + Spiritcaller, + RuthlessRomantic, + Pestilence, + PlagueBearer, + Glitch, + Arsonist, + Cherokious, + Sheriff } \ No newline at end of file diff --git a/Modules/CustomRpcSender.cs b/Modules/CustomRpcSender.cs index 3944d1bb6..3a15a3ead 100644 --- a/Modules/CustomRpcSender.cs +++ b/Modules/CustomRpcSender.cs @@ -5,335 +5,334 @@ using Hazel; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; -namespace EHR +namespace EHR; + +public class CustomRpcSender { - public class CustomRpcSender + public enum State { - public enum State + BeforeInit = 0, // Cannot do anything before initialization + Ready, // Ready to send StartMessage and SendMessage can be executed + InRootMessage, // State between StartMessage and EndMessage StartRpc and EndMessage can be executed + InRpc, // State between StartRpc and EndRpc Write and EndRpc can be executed + Finished // Nothing can be done after sending + } + + private readonly bool isUnsafe; + private readonly string name; + + private readonly OnSendDelegateType onSendDelegate; + public readonly MessageWriter stream; + + // 0~: targetClientId (GameDataTo) + // -1: All players (GameData) + // -2: Not set + private int currentRpcTarget; + + private State currentState = State.BeforeInit; + + private CustomRpcSender() { } + + public CustomRpcSender(string name, SendOption sendOption, bool isUnsafe) + { + stream = MessageWriter.Get(sendOption); + + this.name = name; + this.isUnsafe = isUnsafe; + currentRpcTarget = -2; + onSendDelegate = () => Logger.Info($"{this.name}'s onSendDelegate =>", "CustomRpcSender"); + + currentState = State.Ready; + Logger.Info($"\"{name}\" is ready", "CustomRpcSender"); + } + + public State CurrentState + { + get => currentState; + set { - BeforeInit = 0, // Cannot do anything before initialization - Ready, // Ready to send StartMessage and SendMessage can be executed - InRootMessage, // State between StartMessage and EndMessage StartRpc and EndMessage can be executed - InRpc, // State between StartRpc and EndRpc Write and EndRpc can be executed - Finished // Nothing can be done after sending + if (isUnsafe) + currentState = value; + else + Logger.Warn("CurrentState can only be overwritten when isUnsafe is true", "CustomRpcSender"); + } + } + + public static CustomRpcSender Create(string name = "No Name Sender", SendOption sendOption = SendOption.None, bool isUnsafe = false) + { + return new(name, sendOption, isUnsafe); + } + + public CustomRpcSender AutoStartRpc( + uint targetNetId, + byte callId, + int targetClientId = -1) + { + if (targetClientId == -2) targetClientId = -1; + + if (currentState is not State.Ready and not State.InRootMessage) + { + var errorMsg = $"Tried to start RPC automatically, but State is not Ready or InRootMessage (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); } - private readonly bool isUnsafe; - private readonly string name; - - private readonly OnSendDelegateType onSendDelegate; - public readonly MessageWriter stream; - - // 0~: targetClientId (GameDataTo) - // -1: All players (GameData) - // -2: Not set - private int currentRpcTarget; - - private State currentState = State.BeforeInit; - - private CustomRpcSender() { } - - public CustomRpcSender(string name, SendOption sendOption, bool isUnsafe) - { - stream = MessageWriter.Get(sendOption); - - this.name = name; - this.isUnsafe = isUnsafe; - currentRpcTarget = -2; - onSendDelegate = () => Logger.Info($"{this.name}'s onSendDelegate =>", "CustomRpcSender"); - - currentState = State.Ready; - Logger.Info($"\"{name}\" is ready", "CustomRpcSender"); - } - - public State CurrentState - { - get => currentState; - set - { - if (isUnsafe) - currentState = value; - else - Logger.Warn("CurrentState can only be overwritten when isUnsafe is true", "CustomRpcSender"); - } - } - - public static CustomRpcSender Create(string name = "No Name Sender", SendOption sendOption = SendOption.None, bool isUnsafe = false) - { - return new(name, sendOption, isUnsafe); - } - - public CustomRpcSender AutoStartRpc( - uint targetNetId, - byte callId, - int targetClientId = -1) - { - if (targetClientId == -2) targetClientId = -1; - - if (currentState is not State.Ready and not State.InRootMessage) - { - var errorMsg = $"Tried to start RPC automatically, but State is not Ready or InRootMessage (in: \"{name}\")"; - - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - if (currentRpcTarget != targetClientId) - { - // StartMessage processing - if (currentState == State.InRootMessage) EndMessage(); - - StartMessage(targetClientId); - } - - StartRpc(targetNetId, callId); - - return this; - } - - public void SendMessage() + if (currentRpcTarget != targetClientId) { + // StartMessage processing if (currentState == State.InRootMessage) EndMessage(); - if (currentState != State.Ready) - { - var errorMsg = $"Tried to send RPC but State is not Ready (in: \"{name}\")"; - - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - AmongUsClient.Instance.SendOrDisconnect(stream); - onSendDelegate(); - currentState = State.Finished; - Logger.Info($"\"{name}\" is finished", "CustomRpcSender"); - stream.Recycle(); + StartMessage(targetClientId); } - private CustomRpcSender Write(Action action) - { - if (currentState != State.InRpc) - { - var errorMsg = $"Tried to write RPC, but State is not Write (in: \"{name}\")"; + StartRpc(targetNetId, callId); - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - action(stream); - - return this; - } - - private delegate void OnSendDelegateType(); - - #region Start/End Message - - public CustomRpcSender StartMessage(int targetClientId = -1) - { - if (currentState != State.Ready) - { - var errorMsg = $"Tried to start Message but State is not Ready (in: \"{name}\")"; - - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - if (targetClientId < 0) - { - // RPC for everyone - stream.StartMessage(5); - stream.Write(AmongUsClient.Instance.GameId); - } - else - { - // RPC (Desync) to a specific client - stream.StartMessage(6); - stream.Write(AmongUsClient.Instance.GameId); - stream.WritePacked(targetClientId); - } - - currentRpcTarget = targetClientId; - currentState = State.InRootMessage; - return this; - } - - [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "")] - public CustomRpcSender EndMessage(int targetClientId = -1) - { - if (currentState != State.InRootMessage) - { - var errorMsg = $"Tried to exit Message but State is not InRootMessage (in: \"{name}\")"; - - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - stream.EndMessage(); - - currentRpcTarget = -2; - currentState = State.Ready; - return this; - } - - #endregion - - #region Start/End Rpc - - public CustomRpcSender StartRpc(uint targetNetId, RpcCalls rpcCall) - { - return StartRpc(targetNetId, (byte)rpcCall); - } - - public CustomRpcSender StartRpc( - uint targetNetId, - byte callId) - { - if (currentState != State.InRootMessage) - { - var errorMsg = $"Tried to start RPC but State is not InRootMessage (in: \"{name}\")"; - - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - stream.StartMessage(2); - stream.WritePacked(targetNetId); - stream.Write(callId); - - currentState = State.InRpc; - return this; - } - - public CustomRpcSender EndRpc() - { - if (currentState != State.InRpc) - { - var errorMsg = $"Tried to terminate RPC but State is not InRpc (in: \"{name}\")"; - - if (isUnsafe) - Logger.Warn(errorMsg, "CustomRpcSender.Warn"); - else - throw new InvalidOperationException(errorMsg); - } - - stream.EndMessage(); - currentState = State.InRootMessage; - return this; - } - - #endregion - - // Write - - #region PublicWriteMethods - - public CustomRpcSender Write(float val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(string val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(ulong val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(int val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(uint val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(ushort val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(byte val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(sbyte val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(bool val) - { - return Write(w => w.Write(val)); - } - - public CustomRpcSender Write(Il2CppStructArray bytes) - { - return Write(w => w.Write(bytes)); - } - - public CustomRpcSender Write(Il2CppStructArray bytes, int offset, int length) - { - return Write(w => w.Write(bytes, offset, length)); - } - - public CustomRpcSender WriteBytesAndSize(Il2CppStructArray bytes) - { - return Write(w => w.WriteBytesAndSize(bytes)); - } - - public CustomRpcSender WritePacked(int val) - { - return Write(w => w.WritePacked(val)); - } - - public CustomRpcSender WritePacked(uint val) - { - return Write(w => w.WritePacked(val)); - } - - public CustomRpcSender WriteNetObject(InnerNetObject obj) - { - return Write(w => w.WriteNetObject(obj)); - } - - #endregion + return this; } - public static class CustomRpcSenderExtensions + public void SendMessage() { - public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) + if (currentState == State.InRootMessage) EndMessage(); + + if (currentState != State.Ready) { - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) - .Write((ushort)role) - .Write(true) - .EndRpc(); + var errorMsg = $"Tried to send RPC but State is not Ready (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); } - public static void RpcMurderPlayerV3(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) + AmongUsClient.Instance.SendOrDisconnect(stream); + onSendDelegate(); + currentState = State.Finished; + Logger.Info($"\"{name}\" is finished", "CustomRpcSender"); + stream.Recycle(); + } + + private CustomRpcSender Write(Action action) + { + if (currentState != State.InRpc) { - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.MurderPlayer, targetClientId) - .WriteNetObject(target) - .Write((byte)ExtendedPlayerControl.ResultFlags) - .EndRpc(); + var errorMsg = $"Tried to write RPC, but State is not Write (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); } + + action(stream); + + return this; + } + + private delegate void OnSendDelegateType(); + + #region Start/End Message + + public CustomRpcSender StartMessage(int targetClientId = -1) + { + if (currentState != State.Ready) + { + var errorMsg = $"Tried to start Message but State is not Ready (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + if (targetClientId < 0) + { + // RPC for everyone + stream.StartMessage(5); + stream.Write(AmongUsClient.Instance.GameId); + } + else + { + // RPC (Desync) to a specific client + stream.StartMessage(6); + stream.Write(AmongUsClient.Instance.GameId); + stream.WritePacked(targetClientId); + } + + currentRpcTarget = targetClientId; + currentState = State.InRootMessage; + return this; + } + + [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "")] + public CustomRpcSender EndMessage(int targetClientId = -1) + { + if (currentState != State.InRootMessage) + { + var errorMsg = $"Tried to exit Message but State is not InRootMessage (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + stream.EndMessage(); + + currentRpcTarget = -2; + currentState = State.Ready; + return this; + } + + #endregion + + #region Start/End Rpc + + public CustomRpcSender StartRpc(uint targetNetId, RpcCalls rpcCall) + { + return StartRpc(targetNetId, (byte)rpcCall); + } + + public CustomRpcSender StartRpc( + uint targetNetId, + byte callId) + { + if (currentState != State.InRootMessage) + { + var errorMsg = $"Tried to start RPC but State is not InRootMessage (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + stream.StartMessage(2); + stream.WritePacked(targetNetId); + stream.Write(callId); + + currentState = State.InRpc; + return this; + } + + public CustomRpcSender EndRpc() + { + if (currentState != State.InRpc) + { + var errorMsg = $"Tried to terminate RPC but State is not InRpc (in: \"{name}\")"; + + if (isUnsafe) + Logger.Warn(errorMsg, "CustomRpcSender.Warn"); + else + throw new InvalidOperationException(errorMsg); + } + + stream.EndMessage(); + currentState = State.InRootMessage; + return this; + } + + #endregion + + // Write + + #region PublicWriteMethods + + public CustomRpcSender Write(float val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(string val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(ulong val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(int val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(uint val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(ushort val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(byte val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(sbyte val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(bool val) + { + return Write(w => w.Write(val)); + } + + public CustomRpcSender Write(Il2CppStructArray bytes) + { + return Write(w => w.Write(bytes)); + } + + public CustomRpcSender Write(Il2CppStructArray bytes, int offset, int length) + { + return Write(w => w.Write(bytes, offset, length)); + } + + public CustomRpcSender WriteBytesAndSize(Il2CppStructArray bytes) + { + return Write(w => w.WriteBytesAndSize(bytes)); + } + + public CustomRpcSender WritePacked(int val) + { + return Write(w => w.WritePacked(val)); + } + + public CustomRpcSender WritePacked(uint val) + { + return Write(w => w.WritePacked(val)); + } + + public CustomRpcSender WriteNetObject(InnerNetObject obj) + { + return Write(w => w.WriteNetObject(obj)); + } + + #endregion +} + +public static class CustomRpcSenderExtensions +{ + public static void RpcSetRole(this CustomRpcSender sender, PlayerControl player, RoleTypes role, int targetClientId = -1) + { + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.SetRole, targetClientId) + .Write((ushort)role) + .Write(true) + .EndRpc(); + } + + public static void RpcMurderPlayerV3(this CustomRpcSender sender, PlayerControl player, PlayerControl target, int targetClientId = -1) + { + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.MurderPlayer, targetClientId) + .WriteNetObject(target) + .Write((byte)ExtendedPlayerControl.ResultFlags) + .EndRpc(); } } \ No newline at end of file diff --git a/Modules/CustomSounds.cs b/Modules/CustomSounds.cs index 095d57227..cf65f28a9 100644 --- a/Modules/CustomSounds.cs +++ b/Modules/CustomSounds.cs @@ -4,80 +4,79 @@ using System.Reflection; using System.Runtime.InteropServices; using Hazel; -namespace EHR.Modules +namespace EHR.Modules; + +public static class CustomSoundsManager { - public static class CustomSoundsManager + private static readonly string SOUNDS_PATH = @$"{Environment.CurrentDirectory.Replace(@"\", "/")}/BepInEx/resources/"; + + public static void RPCPlayCustomSound(this PlayerControl pc, string sound, bool force = false) { - private static readonly string SOUNDS_PATH = @$"{Environment.CurrentDirectory.Replace(@"\", "/")}/BepInEx/resources/"; + if (!force) + if (!AmongUsClient.Instance.AmHost || !pc.IsModClient()) + return; - public static void RPCPlayCustomSound(this PlayerControl pc, string sound, bool force = false) + if (pc == null || PlayerControl.LocalPlayer.PlayerId == pc.PlayerId) { - if (!force) - if (!AmongUsClient.Instance.AmHost || !pc.IsModClient()) - return; + Play(sound); + return; + } - if (pc == null || PlayerControl.LocalPlayer.PlayerId == pc.PlayerId) + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlayCustomSound, SendOption.Reliable, pc.GetClientId()); + writer.Write(sound); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void RPCPlayCustomSoundAll(string sound) + { + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlayCustomSound, SendOption.Reliable); + writer.Write(sound); + AmongUsClient.Instance.FinishRpcImmediately(writer); + Play(sound); + } + + public static void ReceiveRPC(MessageReader reader) + { + Play(reader.ReadString()); + } + + public static void Play(string sound) + { + if (!Constants.ShouldPlaySfx() || !Main.EnableCustomSoundEffect.Value) return; + + string path = SOUNDS_PATH + sound + ".wav"; + if (!Directory.Exists(SOUNDS_PATH)) Directory.CreateDirectory(SOUNDS_PATH); + + DirectoryInfo folder = new(SOUNDS_PATH); + if ((folder.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) folder.Attributes = FileAttributes.Hidden; + + if (!File.Exists(path)) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("EHR.Resources.Sounds." + sound + ".wav"); + + if (stream == null) { - Play(sound); + Logger.Warn($"声音文件缺失:{sound}", "CustomSounds"); return; } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlayCustomSound, SendOption.Reliable, pc.GetClientId()); - writer.Write(sound); - AmongUsClient.Instance.FinishRpcImmediately(writer); + FileStream fs = File.Create(path); + stream.CopyTo(fs); + fs.Close(); } - public static void RPCPlayCustomSoundAll(string sound) - { - if (!AmongUsClient.Instance.AmHost) return; + StartPlay(path); + Logger.Msg($"Playing sound:{sound}", "CustomSounds"); + } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlayCustomSound, SendOption.Reliable); - writer.Write(sound); - AmongUsClient.Instance.FinishRpcImmediately(writer); - Play(sound); - } + [DllImport("winmm.dll", CharSet = CharSet.Unicode)] + private static extern bool PlaySound(string Filename, int Mod, int Flags); - public static void ReceiveRPC(MessageReader reader) - { - Play(reader.ReadString()); - } - - public static void Play(string sound) - { - if (!Constants.ShouldPlaySfx() || !Main.EnableCustomSoundEffect.Value) return; - - string path = SOUNDS_PATH + sound + ".wav"; - if (!Directory.Exists(SOUNDS_PATH)) Directory.CreateDirectory(SOUNDS_PATH); - - DirectoryInfo folder = new(SOUNDS_PATH); - if ((folder.Attributes & FileAttributes.Hidden) != FileAttributes.Hidden) folder.Attributes = FileAttributes.Hidden; - - if (!File.Exists(path)) - { - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("EHR.Resources.Sounds." + sound + ".wav"); - - if (stream == null) - { - Logger.Warn($"声音文件缺失:{sound}", "CustomSounds"); - return; - } - - FileStream fs = File.Create(path); - stream.CopyTo(fs); - fs.Close(); - } - - StartPlay(path); - Logger.Msg($"Playing sound:{sound}", "CustomSounds"); - } - - [DllImport("winmm.dll", CharSet = CharSet.Unicode)] - private static extern bool PlaySound(string Filename, int Mod, int Flags); - - public static void StartPlay(string path) - { - PlaySound($"{path}", 0, 1); - // The third parameter, replace 1 with 9, and play continuously - } + public static void StartPlay(string path) + { + PlaySound($"{path}", 0, 1); + // The third parameter, replace 1 with 9, and play continuously } } \ No newline at end of file diff --git a/Modules/CustomTeamManager.cs b/Modules/CustomTeamManager.cs index 9f8e105c7..ca18ffad6 100644 --- a/Modules/CustomTeamManager.cs +++ b/Modules/CustomTeamManager.cs @@ -3,239 +3,238 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -namespace EHR.Modules -{ - internal static class CustomTeamManager - { - public static HashSet CustomTeams = []; - public static HashSet EnabledCustomTeams = []; - public static CustomTeam WinnerTeam; - public static Dictionary> CustomTeamPlayerIds = []; - public static List CustomTeamOptions = []; +namespace EHR.Modules; - public static void LoadCustomTeams() +internal static class CustomTeamManager +{ + public static HashSet CustomTeams = []; + public static HashSet EnabledCustomTeams = []; + public static CustomTeam WinnerTeam; + public static Dictionary> CustomTeamPlayerIds = []; + public static List CustomTeamOptions = []; + + public static void LoadCustomTeams() + { + try + { + if (!File.Exists("./EHR_DATA/CTA_Data.txt")) return; + + CustomTeams = File.ReadAllLines("./EHR_DATA/CTA_Data.txt").Select(x => new CustomTeam(x)).ToHashSet(); + RefreshCustomOptions(); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + private static void RefreshCustomOptions() + { + CustomTeamOptions.Clear(); + EnabledCustomTeams.Clear(); + + const int startId = 647000; + const TabGroup tab = TabGroup.GameSettings; + CustomTeamOptions = CustomTeams.Select((x, i) => CreateSetting(x, startId + (6 * i))).ToList(); + UpdateEnabledTeams(); + + return; + + static CustomTeamOptionGroup CreateSetting(CustomTeam team, int id) + { + var enabled = new BooleanOptionItem(id++, "CTA.FLAG" + team.TeamName, true, tab); + var knowRoles = new BooleanOptionItem(id++, "CTA.KnowRoles", true, tab); + var winWithOriginalTeam = new BooleanOptionItem(id++, "CTA.WinWithOriginalTeam", false, tab); + var killEachOther = new BooleanOptionItem(id++, "CTA.KillEachOther", false, tab); + var guessEachOther = new BooleanOptionItem(id++, "CTA.GuessEachOther", false, tab); + var arrows = new BooleanOptionItem(id, "CTA.Arrows", true, tab); + + CustomTeamOptionGroup group = new(team, enabled, knowRoles, winWithOriginalTeam, killEachOther, guessEachOther, arrows); + group.AllOptions.Skip(1).Do(x => x.SetParent(enabled)); + group.AllOptions.ForEach(x => x.SetColor(new Color32(215, 227, 84, byte.MaxValue))); + group.AllOptions.ForEach(x => x.SetGameMode(CustomGameMode.Standard)); + if (ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color color)) enabled.SetColor(color); + + enabled.RegisterUpdateValueEvent((_, _) => UpdateEnabledTeams()); + return group; + } + } + + private static void UpdateEnabledTeams() + { + EnabledCustomTeams = CustomTeamOptions.Where(x => x.Enabled.GetBool()).Select(x => x.Team).ToHashSet(); + } + + public static void InitializeCustomTeamPlayers() + { + UpdateEnabledTeams(); + if (EnabledCustomTeams.Count == 0) return; + + CustomTeamPlayerIds = Main.PlayerStates + .IntersectBy(Main.AllAlivePlayerControls.Select(x => x.PlayerId), x => x.Key) + .GroupBy(x => EnabledCustomTeams.FirstOrDefault(t => t.TeamMembers.Contains(x.Value.MainRole)), x => x.Key) + .Where(x => x.Key != null) + .ToDictionary(x => x.Key, x => x.ToHashSet()); + + foreach ((CustomTeam team, HashSet players) in CustomTeamPlayerIds) + { + if (!IsSettingEnabledForTeam(team, CTAOption.Arrows)) continue; + + foreach (byte player in players) + { + foreach (byte target in players) + { + if (player == target) continue; + + TargetArrow.Add(player, target); + } + } + } + } + + public static string GetSuffix(PlayerControl seer) + { + if (seer == null || EnabledCustomTeams.Count == 0 || Main.HasJustStarted || !IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.Arrows)) return string.Empty; + + return CustomTeamPlayerIds[GetCustomTeam(seer.PlayerId)].Aggregate(string.Empty, (s, id) => s + Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), TargetArrow.GetArrows(seer, id))); + } + + public static bool CheckCustomTeamGameEnd() + { + if (EnabledCustomTeams.Count == 0 || CustomTeamPlayerIds.Count == 0) return false; + + try + { + CustomTeamPlayerIds.Do(x => x.Value.RemoveWhere(p => + { + PlayerControl pc = Utils.GetPlayerById(p); + return pc == null || pc.Data.Disconnected; + })); + + Dictionary> aliveTeamPlayers = CustomTeamPlayerIds.ToDictionary(x => x.Key, x => x.Value); + aliveTeamPlayers.Do(x => x.Value.RemoveWhere(p => !Utils.GetPlayerById(p).IsAlive())); + + CustomTeam team = aliveTeamPlayers.Keys.First(); + + if (aliveTeamPlayers.Count == 1 && Main.AllAlivePlayerControls.All(x => + { + CustomTeam customTeam = GetCustomTeam(x.PlayerId); + return customTeam != null && customTeam.Equals(team); + })) + { + WinnerTeam = team; + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.CustomTeam); + CustomWinnerHolder.WinnerIds = aliveTeamPlayers.Values.First(); + return true; + } + } + catch + { + return false; + } + + return false; + } + + public static CustomTeam GetCustomTeam(byte id) + { + if (!Main.PlayerStates.TryGetValue(id, out PlayerState state)) return null; + + foreach (CustomTeam team in EnabledCustomTeams) + if (team.TeamMembers.Contains(state.MainRole)) + return team; + + return null; + } + + public static bool AreInSameCustomTeam(byte id1, byte id2) + { + if (EnabledCustomTeams.Count == 0) return false; + + CustomTeam team1 = GetCustomTeam(id1); + CustomTeam team2 = GetCustomTeam(id2); + + return team1 != null && team2 != null && team1.Equals(team2); + } + + public static bool IsSettingEnabledForPlayerTeam(byte id, CTAOption setting) + { + CustomTeam team = GetCustomTeam(id); + return team != null && IsSettingEnabledForTeam(team, setting); + } + + public static bool IsSettingEnabledForTeam(CustomTeam team, CTAOption setting) + { + CustomTeamOptionGroup optionsGroup = CustomTeamOptions.First(x => x.Team.Equals(team)); + List values = optionsGroup.AllOptions.ConvertAll(x => x.GetBool()); + return values[(int)setting]; + } + + internal class CustomTeam + { + public readonly string RoleRevealScreenBackgroundColor; + public readonly string RoleRevealScreenSubtitle; + public readonly string RoleRevealScreenTitle; + public readonly string TeamName; + + public CustomTeam(string line) { try { - if (!File.Exists("./EHR_DATA/CTA_Data.txt")) return; + string[] parts = line.Split(';'); - CustomTeams = File.ReadAllLines("./EHR_DATA/CTA_Data.txt").Select(x => new CustomTeam(x)).ToHashSet(); - RefreshCustomOptions(); + TeamName = parts[0]; + RoleRevealScreenTitle = parts[1]; + RoleRevealScreenSubtitle = parts[2]; + RoleRevealScreenBackgroundColor = parts[3]; + + TeamMembers = parts[4].Split(',').Select(x => Enum.Parse(x, true)).ToList(); } catch (Exception e) { Utils.ThrowException(e); } - } - - private static void RefreshCustomOptions() - { - CustomTeamOptions.Clear(); - EnabledCustomTeams.Clear(); - - const int startId = 647000; - const TabGroup tab = TabGroup.GameSettings; - CustomTeamOptions = CustomTeams.Select((x, i) => CreateSetting(x, startId + (6 * i))).ToList(); - UpdateEnabledTeams(); - - return; - - static CustomTeamOptionGroup CreateSetting(CustomTeam team, int id) + finally { - var enabled = new BooleanOptionItem(id++, "CTA.FLAG" + team.TeamName, true, tab); - var knowRoles = new BooleanOptionItem(id++, "CTA.KnowRoles", true, tab); - var winWithOriginalTeam = new BooleanOptionItem(id++, "CTA.WinWithOriginalTeam", false, tab); - var killEachOther = new BooleanOptionItem(id++, "CTA.KillEachOther", false, tab); - var guessEachOther = new BooleanOptionItem(id++, "CTA.GuessEachOther", false, tab); - var arrows = new BooleanOptionItem(id, "CTA.Arrows", true, tab); - - CustomTeamOptionGroup group = new(team, enabled, knowRoles, winWithOriginalTeam, killEachOther, guessEachOther, arrows); - group.AllOptions.Skip(1).Do(x => x.SetParent(enabled)); - group.AllOptions.ForEach(x => x.SetColor(new Color32(215, 227, 84, byte.MaxValue))); - group.AllOptions.ForEach(x => x.SetGameMode(CustomGameMode.Standard)); - if (ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color color)) enabled.SetColor(color); - - enabled.RegisterUpdateValueEvent((_, _) => UpdateEnabledTeams()); - return group; + CustomTeams.Add(this); } } - private static void UpdateEnabledTeams() + public List TeamMembers { get; } = []; + + public override bool Equals(object obj) { - EnabledCustomTeams = CustomTeamOptions.Where(x => x.Enabled.GetBool()).Select(x => x.Team).ToHashSet(); + if (obj is not CustomTeam team) return false; + + return TeamName == team.TeamName; } - public static void InitializeCustomTeamPlayers() + public override int GetHashCode() { - UpdateEnabledTeams(); - if (EnabledCustomTeams.Count == 0) return; - - CustomTeamPlayerIds = Main.PlayerStates - .IntersectBy(Main.AllAlivePlayerControls.Select(x => x.PlayerId), x => x.Key) - .GroupBy(x => EnabledCustomTeams.FirstOrDefault(t => t.TeamMembers.Contains(x.Value.MainRole)), x => x.Key) - .Where(x => x.Key != null) - .ToDictionary(x => x.Key, x => x.ToHashSet()); - - foreach ((CustomTeam team, HashSet players) in CustomTeamPlayerIds) - { - if (!IsSettingEnabledForTeam(team, CTAOption.Arrows)) continue; - - foreach (byte player in players) - { - foreach (byte target in players) - { - if (player == target) continue; - - TargetArrow.Add(player, target); - } - } - } - } - - public static string GetSuffix(PlayerControl seer) - { - if (seer == null || EnabledCustomTeams.Count == 0 || Main.HasJustStarted || !IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.Arrows)) return string.Empty; - - return CustomTeamPlayerIds[GetCustomTeam(seer.PlayerId)].Aggregate(string.Empty, (s, id) => s + Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), TargetArrow.GetArrows(seer, id))); - } - - public static bool CheckCustomTeamGameEnd() - { - if (EnabledCustomTeams.Count == 0 || CustomTeamPlayerIds.Count == 0) return false; - - try - { - CustomTeamPlayerIds.Do(x => x.Value.RemoveWhere(p => - { - PlayerControl pc = Utils.GetPlayerById(p); - return pc == null || pc.Data.Disconnected; - })); - - Dictionary> aliveTeamPlayers = CustomTeamPlayerIds.ToDictionary(x => x.Key, x => x.Value); - aliveTeamPlayers.Do(x => x.Value.RemoveWhere(p => !Utils.GetPlayerById(p).IsAlive())); - - CustomTeam team = aliveTeamPlayers.Keys.First(); - - if (aliveTeamPlayers.Count == 1 && Main.AllAlivePlayerControls.All(x => - { - CustomTeam customTeam = GetCustomTeam(x.PlayerId); - return customTeam != null && customTeam.Equals(team); - })) - { - WinnerTeam = team; - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.CustomTeam); - CustomWinnerHolder.WinnerIds = aliveTeamPlayers.Values.First(); - return true; - } - } - catch - { - return false; - } - - return false; - } - - public static CustomTeam GetCustomTeam(byte id) - { - if (!Main.PlayerStates.TryGetValue(id, out PlayerState state)) return null; - - foreach (CustomTeam team in EnabledCustomTeams) - if (team.TeamMembers.Contains(state.MainRole)) - return team; - - return null; - } - - public static bool AreInSameCustomTeam(byte id1, byte id2) - { - if (EnabledCustomTeams.Count == 0) return false; - - CustomTeam team1 = GetCustomTeam(id1); - CustomTeam team2 = GetCustomTeam(id2); - - return team1 != null && team2 != null && team1.Equals(team2); - } - - public static bool IsSettingEnabledForPlayerTeam(byte id, CTAOption setting) - { - CustomTeam team = GetCustomTeam(id); - return team != null && IsSettingEnabledForTeam(team, setting); - } - - public static bool IsSettingEnabledForTeam(CustomTeam team, CTAOption setting) - { - CustomTeamOptionGroup optionsGroup = CustomTeamOptions.First(x => x.Team.Equals(team)); - List values = optionsGroup.AllOptions.ConvertAll(x => x.GetBool()); - return values[(int)setting]; - } - - internal class CustomTeam - { - public readonly string RoleRevealScreenBackgroundColor; - public readonly string RoleRevealScreenSubtitle; - public readonly string RoleRevealScreenTitle; - public readonly string TeamName; - - public CustomTeam(string line) - { - try - { - string[] parts = line.Split(';'); - - TeamName = parts[0]; - RoleRevealScreenTitle = parts[1]; - RoleRevealScreenSubtitle = parts[2]; - RoleRevealScreenBackgroundColor = parts[3]; - - TeamMembers = parts[4].Split(',').Select(x => Enum.Parse(x, true)).ToList(); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - finally - { - CustomTeams.Add(this); - } - } - - public List TeamMembers { get; } = []; - - public override bool Equals(object obj) - { - if (obj is not CustomTeam team) return false; - - return TeamName == team.TeamName; - } - - public override int GetHashCode() - { - return TeamName.GetHashCode(); - } - } - - internal class CustomTeamOptionGroup(CustomTeam team, BooleanOptionItem enabled, BooleanOptionItem knowRoles, BooleanOptionItem winWithOriginalTeam, BooleanOptionItem killEachOther, BooleanOptionItem guessEachOther, BooleanOptionItem arrows) - { - public readonly List AllOptions = [enabled, knowRoles, winWithOriginalTeam, killEachOther, guessEachOther, arrows]; - public CustomTeam Team { get; set; } = team; - - public BooleanOptionItem Enabled { get; set; } = enabled; - public BooleanOptionItem KnowRoles { get; set; } = knowRoles; - public BooleanOptionItem WinWithOriginalTeam { get; set; } = winWithOriginalTeam; - public BooleanOptionItem KillEachOther { get; set; } = killEachOther; - public BooleanOptionItem GuessEachOther { get; set; } = guessEachOther; - public BooleanOptionItem Arrows { get; set; } = arrows; + return TeamName.GetHashCode(); } } - public enum CTAOption + internal class CustomTeamOptionGroup(CustomTeam team, BooleanOptionItem enabled, BooleanOptionItem knowRoles, BooleanOptionItem winWithOriginalTeam, BooleanOptionItem killEachOther, BooleanOptionItem guessEachOther, BooleanOptionItem arrows) { - Enabled, - KnowRoles, - WinWithOriginalTeam, - KillEachOther, - GuessEachOther, - Arrows + public readonly List AllOptions = [enabled, knowRoles, winWithOriginalTeam, killEachOther, guessEachOther, arrows]; + public CustomTeam Team { get; set; } = team; + + public BooleanOptionItem Enabled { get; set; } = enabled; + public BooleanOptionItem KnowRoles { get; set; } = knowRoles; + public BooleanOptionItem WinWithOriginalTeam { get; set; } = winWithOriginalTeam; + public BooleanOptionItem KillEachOther { get; set; } = killEachOther; + public BooleanOptionItem GuessEachOther { get; set; } = guessEachOther; + public BooleanOptionItem Arrows { get; set; } = arrows; } +} + +public enum CTAOption +{ + Enabled, + KnowRoles, + WinWithOriginalTeam, + KillEachOther, + GuessEachOther, + Arrows } \ No newline at end of file diff --git a/Modules/CustomWinnerHolder.cs b/Modules/CustomWinnerHolder.cs index 08deccd8b..75c509885 100644 --- a/Modules/CustomWinnerHolder.cs +++ b/Modules/CustomWinnerHolder.cs @@ -1,110 +1,109 @@ using System.Collections.Generic; using Hazel; -namespace EHR +namespace EHR; + +public static class CustomWinnerHolder { - public static class CustomWinnerHolder + // The winning team will be stored. + // Used to determine background color of results, etc. + // Note: When changing this variable, if you do not change WinnerRoles and WinnerIds at the same time, unexpected winners may appear. + public static CustomWinner WinnerTeam; + + // Stores the team of additional winning players. + // Used to display results. + public static HashSet AdditionalWinnerTeams; + + // The winning role is stored, and all players whose roles are stored in this variable win. + // Ideal for handling team neutrals. + public static HashSet WinnerRoles; + + // Stores the winner's PlayerID, all players with this ID win. + // Ideal for handling neutrals that win alone. + public static HashSet WinnerIds; + + public static void Reset() { - // The winning team will be stored. - // Used to determine background color of results, etc. - // Note: When changing this variable, if you do not change WinnerRoles and WinnerIds at the same time, unexpected winners may appear. - public static CustomWinner WinnerTeam; + WinnerTeam = CustomWinner.Default; + AdditionalWinnerTeams = []; + WinnerRoles = []; + WinnerIds = []; + Logger.Info("Reset", "CustomWinnerHolder"); + } - // Stores the team of additional winning players. - // Used to display results. - public static HashSet AdditionalWinnerTeams; - - // The winning role is stored, and all players whose roles are stored in this variable win. - // Ideal for handling team neutrals. - public static HashSet WinnerRoles; - - // Stores the winner's PlayerID, all players with this ID win. - // Ideal for handling neutrals that win alone. - public static HashSet WinnerIds; - - public static void Reset() - { - WinnerTeam = CustomWinner.Default; - AdditionalWinnerTeams = []; - WinnerRoles = []; - WinnerIds = []; - Logger.Info("Reset", "CustomWinnerHolder"); - } - - public static void ClearWinners() - { - WinnerRoles.Clear(); - WinnerIds.Clear(); - Logger.Info("ClearWinners", "CustomWinnerHolder"); - } - - /// - /// Assign a value to WinnerTeam. - /// Add to AdditionalWinnerTeams if already assigned. - /// - public static void SetWinnerOrAdditonalWinner(CustomWinner winner) - { - if (WinnerTeam == CustomWinner.Default) - WinnerTeam = winner; - else - AdditionalWinnerTeams.Add((AdditionalWinners)winner); - - Logger.Info($"WinnerTeam: {WinnerTeam}, AdditionalWinnerTeams: {string.Join(", ", AdditionalWinnerTeams)}", "CustomWinnerHolder.SetWinnerOrAdditonalWinner"); - } - - /// - /// Assign a value to WinnerTeam. - /// If it is already assigned, add the existing value to AdditionalWinnerTeams and then assign it. - /// - public static void ShiftWinnerAndSetWinner(CustomWinner winner) - { - if (WinnerTeam != CustomWinner.Default) AdditionalWinnerTeams.Add((AdditionalWinners)WinnerTeam); + public static void ClearWinners() + { + WinnerRoles.Clear(); + WinnerIds.Clear(); + Logger.Info("ClearWinners", "CustomWinnerHolder"); + } + /// + /// Assign a value to WinnerTeam. + /// Add to AdditionalWinnerTeams if already assigned. + /// + public static void SetWinnerOrAdditonalWinner(CustomWinner winner) + { + if (WinnerTeam == CustomWinner.Default) WinnerTeam = winner; - Logger.Info($"WinnerTeam: {WinnerTeam}, AdditionalWinnerTeams: {string.Join(", ", AdditionalWinnerTeams)}", "CustomWinnerHolder.ShiftWinnerAndSetWinner"); - } + else + AdditionalWinnerTeams.Add((AdditionalWinners)winner); - /// - /// Delete any existing values and then assign the values to WinnerTeam. - /// - public static void ResetAndSetWinner(CustomWinner winner) - { - Reset(); - WinnerTeam = winner; - Logger.Info($"WinnerTeam: {WinnerTeam}", "CustomWinnerHolder.ResetAndSetWinner"); - } + Logger.Info($"WinnerTeam: {WinnerTeam}, AdditionalWinnerTeams: {string.Join(", ", AdditionalWinnerTeams)}", "CustomWinnerHolder.SetWinnerOrAdditonalWinner"); + } - public static MessageWriter WriteTo(MessageWriter writer) - { - writer.WritePacked((int)WinnerTeam); + /// + /// Assign a value to WinnerTeam. + /// If it is already assigned, add the existing value to AdditionalWinnerTeams and then assign it. + /// + public static void ShiftWinnerAndSetWinner(CustomWinner winner) + { + if (WinnerTeam != CustomWinner.Default) AdditionalWinnerTeams.Add((AdditionalWinners)WinnerTeam); - writer.WritePacked(AdditionalWinnerTeams.Count); - foreach (AdditionalWinners wt in AdditionalWinnerTeams) writer.WritePacked((int)wt); + WinnerTeam = winner; + Logger.Info($"WinnerTeam: {WinnerTeam}, AdditionalWinnerTeams: {string.Join(", ", AdditionalWinnerTeams)}", "CustomWinnerHolder.ShiftWinnerAndSetWinner"); + } - writer.WritePacked(WinnerRoles.Count); - foreach (CustomRoles wr in WinnerRoles) writer.WritePacked((int)wr); + /// + /// Delete any existing values and then assign the values to WinnerTeam. + /// + public static void ResetAndSetWinner(CustomWinner winner) + { + Reset(); + WinnerTeam = winner; + Logger.Info($"WinnerTeam: {WinnerTeam}", "CustomWinnerHolder.ResetAndSetWinner"); + } - writer.WritePacked(WinnerIds.Count); - foreach (byte id in WinnerIds) writer.Write(id); + public static MessageWriter WriteTo(MessageWriter writer) + { + writer.WritePacked((int)WinnerTeam); - return writer; - } + writer.WritePacked(AdditionalWinnerTeams.Count); + foreach (AdditionalWinners wt in AdditionalWinnerTeams) writer.WritePacked((int)wt); - public static void ReadFrom(MessageReader reader) - { - WinnerTeam = (CustomWinner)reader.ReadPackedInt32(); + writer.WritePacked(WinnerRoles.Count); + foreach (CustomRoles wr in WinnerRoles) writer.WritePacked((int)wr); - AdditionalWinnerTeams = []; - int AdditionalWinnerTeamsCount = reader.ReadPackedInt32(); - for (var i = 0; i < AdditionalWinnerTeamsCount; i++) AdditionalWinnerTeams.Add((AdditionalWinners)reader.ReadPackedInt32()); + writer.WritePacked(WinnerIds.Count); + foreach (byte id in WinnerIds) writer.Write(id); - WinnerRoles = []; - int WinnerRolesCount = reader.ReadPackedInt32(); - for (var i = 0; i < WinnerRolesCount; i++) WinnerRoles.Add((CustomRoles)reader.ReadPackedInt32()); + return writer; + } - WinnerIds = []; - int WinnerIdsCount = reader.ReadPackedInt32(); - for (var i = 0; i < WinnerIdsCount; i++) WinnerIds.Add(reader.ReadByte()); - } + public static void ReadFrom(MessageReader reader) + { + WinnerTeam = (CustomWinner)reader.ReadPackedInt32(); + + AdditionalWinnerTeams = []; + int AdditionalWinnerTeamsCount = reader.ReadPackedInt32(); + for (var i = 0; i < AdditionalWinnerTeamsCount; i++) AdditionalWinnerTeams.Add((AdditionalWinners)reader.ReadPackedInt32()); + + WinnerRoles = []; + int WinnerRolesCount = reader.ReadPackedInt32(); + for (var i = 0; i < WinnerRolesCount; i++) WinnerRoles.Add((CustomRoles)reader.ReadPackedInt32()); + + WinnerIds = []; + int WinnerIdsCount = reader.ReadPackedInt32(); + for (var i = 0; i < WinnerIdsCount; i++) WinnerIds.Add(reader.ReadByte()); } } \ No newline at end of file diff --git a/Modules/DebugModeManager.cs b/Modules/DebugModeManager.cs index 3db3f9732..c1df6ba19 100644 --- a/Modules/DebugModeManager.cs +++ b/Modules/DebugModeManager.cs @@ -1,18 +1,17 @@ -namespace EHR +namespace EHR; + +public static class DebugModeManager { - public static class DebugModeManager - { - public static OptionItem EnableDebugMode; - public static bool AmDebugger { get; private set; } = + public static OptionItem EnableDebugMode; + public static bool AmDebugger { get; private set; } = #if DEBUG true; #else false; #endif - public static void Auth(HashAuth auth, string input) - { - AmDebugger |= auth.CheckString(input); - } + public static void Auth(HashAuth auth, string input) + { + AmDebugger |= auth.CheckString(input); } } \ No newline at end of file diff --git a/Modules/Debugger.cs b/Modules/Debugger.cs index 1f38926e2..777647fd2 100644 --- a/Modules/Debugger.cs +++ b/Modules/Debugger.cs @@ -10,319 +10,318 @@ using EHR.Modules; using UnityEngine; using LogLevel = BepInEx.Logging.LogLevel; -namespace EHR +namespace EHR; + +internal class Webhook { - internal class Webhook + public static void Send(string text) { - public static void Send(string text) + if (Main.WebhookUrl.Value == "none") return; + + HttpClient httpClient = new(); + + Dictionary strs = new() { - if (Main.WebhookUrl.Value == "none") return; + { "content", text }, + { "username", "EHR-Debugger" }, + { "avatar_url", "https://npm.elemecdn.com/hexo-static@1.0.1/img/avatar.webp" } + }; - HttpClient httpClient = new(); + TaskAwaiter awaiter = httpClient.PostAsync( + Main.WebhookUrl.Value, new FormUrlEncodedContent(strs)).GetAwaiter(); - Dictionary strs = new() - { - { "content", text }, - { "username", "EHR-Debugger" }, - { "avatar_url", "https://npm.elemecdn.com/hexo-static@1.0.1/img/avatar.webp" } - }; + awaiter.GetResult(); + } +} - TaskAwaiter awaiter = httpClient.PostAsync( - Main.WebhookUrl.Value, new FormUrlEncodedContent(strs)).GetAwaiter(); +internal static class Logger +{ + private static bool IsEnable; + private static readonly List DisableList = []; + private static readonly List SendToGameList = []; + public static bool IsAlsoInGame; - awaiter.GetResult(); + private static readonly HashSet NowDetailedErrorLog = []; + + public static void Enable() + { + IsEnable = true; + } + + public static void Disable() + { + IsEnable = false; + } + + public static void Enable(string tag, bool toGame = false) + { + DisableList.Remove(tag); + + if (toGame && !SendToGameList.Contains(tag)) + SendToGameList.Add(tag); + else + SendToGameList.Remove(tag); + } + + public static void Disable(string tag) + { + if (!DisableList.Contains(tag)) DisableList.Add(tag); + } + + public static void SendInGame(string text /*, bool isAlways = false*/) + { + if (!IsEnable) return; + + if (DestroyableSingleton._instance) + { + DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(text); + Warn(text, "SendInGame"); } } - internal static class Logger + private static void SendToFile(string text, LogLevel level = LogLevel.Info, string tag = "", bool escapeCRLF = true, int lineNumber = 0, string fileName = "", bool multiLine = false) { - private static bool IsEnable; - private static readonly List DisableList = []; - private static readonly List SendToGameList = []; - public static bool IsAlsoInGame; + if (!IsEnable || DisableList.Contains(tag) || (level == LogLevel.Debug && !DebugModeManager.AmDebugger)) return; - private static readonly HashSet NowDetailedErrorLog = []; + if (SendToGameList.Contains(tag) || IsAlsoInGame) SendInGame($"[{tag}]{text}"); - public static void Enable() + string log_text; + + if (level is LogLevel.Error or LogLevel.Fatal && !multiLine && !NowDetailedErrorLog.Contains(tag)) { - IsEnable = true; + var t = DateTime.Now.ToString("HH:mm:ss"); + StackFrame stack = new(2); + string className = stack.GetMethod()?.ReflectedType?.Name; + string memberName = stack.GetMethod()?.Name; + log_text = $"[{t}][{className}.{memberName}({Path.GetFileName(fileName)}:{lineNumber})][{tag}]{text}"; + NowDetailedErrorLog.Add(tag); + LateTask.New(() => NowDetailedErrorLog.Remove(tag), 3f, log: false); + } + else + { + if (escapeCRLF) text = text.Replace("\r", "\\r").Replace("\n", "\\n"); + + var t = DateTime.Now.ToString("HH:mm:ss"); + log_text = $"[{t}][{tag}]{text}"; } - public static void Disable() + CustomLogger.Instance.Log(level.ToString(), log_text, multiLine); + } + + public static void Test(object content, string tag = "======= Test =======", bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(content.ToString(), LogLevel.Debug, tag, escapeCRLF, lineNumber, fileName, multiLine); + } + + public static void Info(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(text, LogLevel.Info, tag, escapeCRLF, lineNumber, fileName, multiLine); + } + + public static void Warn(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(text, LogLevel.Warning, tag, escapeCRLF, lineNumber, fileName, multiLine); + } + + public static void Error(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(text, LogLevel.Error, tag, escapeCRLF, lineNumber, fileName, multiLine); + } + + public static void Fatal(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(text, LogLevel.Fatal, tag, escapeCRLF, lineNumber, fileName, multiLine); + } + + public static void Msg(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(text, LogLevel.Message, tag, escapeCRLF, lineNumber, fileName, multiLine); + } + + public static void Exception(Exception ex, string tag, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) + { + SendToFile(ex.ToString(), LogLevel.Error, tag, false, lineNumber, fileName); + } + + public static void CurrentMethod([CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") + { + StackFrame stack = new(1); + MethodBase method = stack.GetMethod(); + Msg($"\"{method?.ReflectedType?.Name}.{method?.Name}\" Called in \"{Path.GetFileName(fileName)}({lineNumber})\"", "Method"); + } + + public static LogHandler Handler(string tag) + { + return new(tag); + } +} + +public class CustomLogger +{ + private const string LOGFilePath = "./BepInEx/log.html"; + + private const string HtmlHeader = + """ + + + + + + EHR Log File + + + +

EHR Log File

+
+ + """; + + private const string HtmlFooter = + """ +
+ + + """; + + private static CustomLogger PrivateInstance; + private float timer = 3f; + + private CustomLogger() + { + if (!File.Exists(LOGFilePath)) File.WriteAllText(LOGFilePath, HtmlHeader); + Main.Instance.StartCoroutine(InactivityCheck()); + } + + // Singleton to ensure a single logger instance + public static CustomLogger Instance + { + get { return PrivateInstance ??= new(); } + } + + public static void ClearLog() + { + File.WriteAllText(LOGFilePath, HtmlHeader); + } + + public void Log(string level, string message, bool multiLine = false) + { + if (multiLine) message = message.Replace("\\n", "
"); + + string logEntry = $""" +
+ {message} +
+ """; + + File.AppendAllText(LOGFilePath, logEntry); + + timer = 3f; + } + + private IEnumerator InactivityCheck() + { + while (timer > 0) { - IsEnable = false; + timer -= Time.deltaTime; + yield return null; } - public static void Enable(string tag, bool toGame = false) + File.AppendAllText(LOGFilePath, HtmlFooter); + PrivateInstance = null; + } +} + +public static class LogMonitor +{ + private const string SourceLogFilePath = "./BepInEx/LogOutput.log"; + private const string HtmlLogFilePath = "./EHR_DATA/log.html"; + private static long LastProcessedLength; + + public static void StartMonitoring() + { + Main.Instance.StartCoroutine(MonitorLog()); + return; + + IEnumerator MonitorLog() { - DisableList.Remove(tag); - - if (toGame && !SendToGameList.Contains(tag)) - SendToGameList.Add(tag); - else - SendToGameList.Remove(tag); - } - - public static void Disable(string tag) - { - if (!DisableList.Contains(tag)) DisableList.Add(tag); - } - - public static void SendInGame(string text /*, bool isAlways = false*/) - { - if (!IsEnable) return; - - if (DestroyableSingleton._instance) + while (true) { - DestroyableSingleton.Instance.Notifier.AddDisconnectMessage(text); - Warn(text, "SendInGame"); - } - } - - private static void SendToFile(string text, LogLevel level = LogLevel.Info, string tag = "", bool escapeCRLF = true, int lineNumber = 0, string fileName = "", bool multiLine = false) - { - if (!IsEnable || DisableList.Contains(tag) || (level == LogLevel.Debug && !DebugModeManager.AmDebugger)) return; - - if (SendToGameList.Contains(tag) || IsAlsoInGame) SendInGame($"[{tag}]{text}"); - - string log_text; - - if (level is LogLevel.Error or LogLevel.Fatal && !multiLine && !NowDetailedErrorLog.Contains(tag)) - { - var t = DateTime.Now.ToString("HH:mm:ss"); - StackFrame stack = new(2); - string className = stack.GetMethod()?.ReflectedType?.Name; - string memberName = stack.GetMethod()?.Name; - log_text = $"[{t}][{className}.{memberName}({Path.GetFileName(fileName)}:{lineNumber})][{tag}]{text}"; - NowDetailedErrorLog.Add(tag); - LateTask.New(() => NowDetailedErrorLog.Remove(tag), 3f, log: false); - } - else - { - if (escapeCRLF) text = text.Replace("\r", "\\r").Replace("\n", "\\n"); - - var t = DateTime.Now.ToString("HH:mm:ss"); - log_text = $"[{t}][{tag}]{text}"; + ProcessLogUpdates(); + yield return new WaitForSeconds(1f); } - CustomLogger.Instance.Log(level.ToString(), log_text, multiLine); - } - - public static void Test(object content, string tag = "======= Test =======", bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(content.ToString(), LogLevel.Debug, tag, escapeCRLF, lineNumber, fileName, multiLine); - } - - public static void Info(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(text, LogLevel.Info, tag, escapeCRLF, lineNumber, fileName, multiLine); - } - - public static void Warn(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(text, LogLevel.Warning, tag, escapeCRLF, lineNumber, fileName, multiLine); - } - - public static void Error(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(text, LogLevel.Error, tag, escapeCRLF, lineNumber, fileName, multiLine); - } - - public static void Fatal(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(text, LogLevel.Fatal, tag, escapeCRLF, lineNumber, fileName, multiLine); - } - - public static void Msg(string text, string tag, bool escapeCRLF = true, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(text, LogLevel.Message, tag, escapeCRLF, lineNumber, fileName, multiLine); - } - - public static void Exception(Exception ex, string tag, [CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "", bool multiLine = false) - { - SendToFile(ex.ToString(), LogLevel.Error, tag, false, lineNumber, fileName); - } - - public static void CurrentMethod([CallerLineNumber] int lineNumber = 0, [CallerFilePath] string fileName = "") - { - StackFrame stack = new(1); - MethodBase method = stack.GetMethod(); - Msg($"\"{method?.ReflectedType?.Name}.{method?.Name}\" Called in \"{Path.GetFileName(fileName)}({lineNumber})\"", "Method"); - } - - public static LogHandler Handler(string tag) - { - return new(tag); + // ReSharper disable once IteratorNeverReturns } } - public class CustomLogger + private static void ProcessLogUpdates() { - private const string LOGFilePath = "./BepInEx/log.html"; - - private const string HtmlHeader = - """ - - - - - - EHR Log File - - - -

EHR Log File

-
- - """; - - private const string HtmlFooter = - """ -
- - - """; - - private static CustomLogger PrivateInstance; - private float timer = 3f; - - private CustomLogger() + try { - if (!File.Exists(LOGFilePath)) File.WriteAllText(LOGFilePath, HtmlHeader); - Main.Instance.StartCoroutine(InactivityCheck()); - } + using FileStream stream = new FileStream(SourceLogFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using StreamReader reader = new StreamReader(stream); - // Singleton to ensure a single logger instance - public static CustomLogger Instance - { - get { return PrivateInstance ??= new(); } - } + if (stream.Length == LastProcessedLength) return; - public static void ClearLog() - { - File.WriteAllText(LOGFilePath, HtmlHeader); - } + stream.Seek(LastProcessedLength, SeekOrigin.Begin); - public void Log(string level, string message, bool multiLine = false) - { - if (multiLine) message = message.Replace("\\n", "
"); + string currentGroup = null; + string currentClass = "info"; - string logEntry = $""" -
- {message} -
- """; - - File.AppendAllText(LOGFilePath, logEntry); - - timer = 3f; - } - - private IEnumerator InactivityCheck() - { - while (timer > 0) + while (reader.ReadLine() is { } line) { - timer -= Time.deltaTime; - yield return null; - } - - File.AppendAllText(LOGFilePath, HtmlFooter); - PrivateInstance = null; - } - } - - public static class LogMonitor - { - private const string SourceLogFilePath = "./BepInEx/LogOutput.log"; - private const string HtmlLogFilePath = "./EHR_DATA/log.html"; - private static long LastProcessedLength; - - public static void StartMonitoring() - { - Main.Instance.StartCoroutine(MonitorLog()); - return; - - IEnumerator MonitorLog() - { - while (true) + if (line.StartsWith("[")) { - ProcessLogUpdates(); - yield return new WaitForSeconds(1f); - } - - // ReSharper disable once IteratorNeverReturns - } - } - - private static void ProcessLogUpdates() - { - try - { - using FileStream stream = new FileStream(SourceLogFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new StreamReader(stream); - - if (stream.Length == LastProcessedLength) return; - - stream.Seek(LastProcessedLength, SeekOrigin.Begin); - - string currentGroup = null; - string currentClass = "info"; - - while (reader.ReadLine() is { } line) - { - if (line.StartsWith("[")) + if (currentGroup != null) { - if (currentGroup != null) - { - AppendLogToHtml($"
{currentGroup}
"); - } + AppendLogToHtml($"
{currentGroup}
"); + } - currentClass = line.Split(':')[0].TrimStart('[').Replace(" ", "").ToLower(); - currentGroup = line; - } - else if (currentGroup != null) - { - currentGroup += $"
{line}"; - } + currentClass = line.Split(':')[0].TrimStart('[').Replace(" ", "").ToLower(); + currentGroup = line; } - - if (currentGroup != null) + else if (currentGroup != null) { - AppendLogToHtml($"
{currentGroup}
"); + currentGroup += $"
{line}"; } - - LastProcessedLength = stream.Length; } - catch (Exception ex) + + if (currentGroup != null) { - Utils.ThrowException(ex); + AppendLogToHtml($"
{currentGroup}
"); } - } - private static void AppendLogToHtml(string line) + LastProcessedLength = stream.Length; + } + catch (Exception ex) { - string htmlEntry = ParseLogLine(line); - File.AppendAllText(HtmlLogFilePath, htmlEntry); + Utils.ThrowException(ex); } + } - private static string ParseLogLine(string line) - { - string cssClass = line.Split(':')[0].TrimStart('[').Replace(" ", "").ToLower(); + private static void AppendLogToHtml(string line) + { + string htmlEntry = ParseLogLine(line); + File.AppendAllText(HtmlLogFilePath, htmlEntry); + } - return $""" -
- {line} -
- """; - } + private static string ParseLogLine(string line) + { + string cssClass = line.Split(':')[0].TrimStart('[').Replace(" ", "").ToLower(); + + return $""" +
+ {line} +
+ """; } } \ No newline at end of file diff --git a/Modules/DevManager.cs b/Modules/DevManager.cs index c8de79da1..ff8d2c7e7 100644 --- a/Modules/DevManager.cs +++ b/Modules/DevManager.cs @@ -1,171 +1,170 @@ using System.Collections.Generic; using System.Linq; -namespace EHR +namespace EHR; + +public class DevUser(string code = "", string color = "null", string tag = "null", bool isUp = false, bool deBug = false) { - public class DevUser(string code = "", string color = "null", string tag = "null", bool isUp = false, bool deBug = false) + public string Code { get; } = code; + private string Color { get; } = color; + private string Tag { get; } = tag; + public bool IsUp { get; } = isUp; + public bool DeBug { get; } = deBug; + + public bool HasTag() { - public string Code { get; } = code; - private string Color { get; } = color; - private string Tag { get; } = tag; - public bool IsUp { get; } = isUp; - public bool DeBug { get; } = deBug; - - public bool HasTag() - { - return Tag != "null"; - } - - public string GetTag() - { - return Color == "null" ? $"{Tag}\r\n" : $"{(Tag == "#Dev" ? Translator.GetString("Developer") : Tag)}\r\n"; - } + return Tag != "null"; } - public static class DevManager + public string GetTag() { - private static readonly DevUser DefaultDevUser = new(); - private static List DevUserList = []; + return Color == "null" ? $"{Tag}\r\n" : $"{(Tag == "#Dev" ? Translator.GetString("Developer") : Tag)}\r\n"; + } +} - public static void Init() - { - DevUserList = - [ - // Dev - new("actorour#0029", "#ffc0cb", "TOHE/TONX Developer", true, true), // KARPED1EM - new("pinklaze#1776", isUp: true), // NCSIMON - new("keepchirpy#6354", "#1FF3C6", "TOHEN Developer", true), // TommyXL - new("motelchief#4112", "#eb57af", "Twilight Raven", true, true), // Drakos - new("eagergaol#1562", "#5534eb", "Drakos Alt", true, true), // Drakos Steam Alt - new("timedapper#9496"), //阿龍 - new("sofaagile#3120"), //天寸 - new("keyscreech#2151"), //Endrmen40409 +public static class DevManager +{ + private static readonly DevUser DefaultDevUser = new(); + private static List DevUserList = []; - // Up - new("primether#5348", tag: "YouTuber/Streamer", isUp: true), // AnonWorks - new("truantwarm#9165", isUp: true), // 萧暮不姓萧 - new("drilldinky#1386", isUp: true), // 爱玩AU的河豚 - new("farardour#6818", isUp: true), // -提米SaMa- - new("vealused#8192", isUp: true), // lag丶xy - new("storyeager#0815", isUp: true), // 航娜丽莎 - new("versegame#3885", isUp: true), // 柴唔cw - new("closegrub#6217", isUp: true), // 警长不会玩 - new("frownnatty#7935", isUp: true), // 鬼灵official - new("veryscarf#5368", isUp: true), // 小武同学102 - new("sparklybee#0275", isUp: true), // --红包SaMa-- - new("endingyon#3175", isUp: true), // 游侠开摆 - new("firmine#0232", isUp: true), // YH永恒_ - new("storkfey#3570", isUp: true), // Calypso - new("fellowsand#1003", isUp: true), // C-Faust - new("jetsafe#8512", isUp: true), // Hoream是好人 - new("spoonkey#0792", isUp: true), // 没好康的 - new("beakedmire#6099", isUp: true), // 茄-au - new("doggedsize#7892", isUp: true), // TronAndRey - new("openlanded#9533", "#9e2424", "God Of Death Love Apples", true), // ryuk - new("unlikecity#4086", "#eD2F91", "Ward", true), // Ward - new("iconicdrop#2727", isUp: true), // jackler + public static void Init() + { + DevUserList = + [ + // Dev + new("actorour#0029", "#ffc0cb", "TOHE/TONX Developer", true, true), // KARPED1EM + new("pinklaze#1776", isUp: true), // NCSIMON + new("keepchirpy#6354", "#1FF3C6", "TOHEN Developer", true), // TommyXL + new("motelchief#4112", "#eb57af", "Twilight Raven", true, true), // Drakos + new("eagergaol#1562", "#5534eb", "Drakos Alt", true, true), // Drakos Steam Alt + new("timedapper#9496"), //阿龍 + new("sofaagile#3120"), //天寸 + new("keyscreech#2151"), //Endrmen40409 + + // Up + new("primether#5348", tag: "YouTuber/Streamer", isUp: true), // AnonWorks + new("truantwarm#9165", isUp: true), // 萧暮不姓萧 + new("drilldinky#1386", isUp: true), // 爱玩AU的河豚 + new("farardour#6818", isUp: true), // -提米SaMa- + new("vealused#8192", isUp: true), // lag丶xy + new("storyeager#0815", isUp: true), // 航娜丽莎 + new("versegame#3885", isUp: true), // 柴唔cw + new("closegrub#6217", isUp: true), // 警长不会玩 + new("frownnatty#7935", isUp: true), // 鬼灵official + new("veryscarf#5368", isUp: true), // 小武同学102 + new("sparklybee#0275", isUp: true), // --红包SaMa-- + new("endingyon#3175", isUp: true), // 游侠开摆 + new("firmine#0232", isUp: true), // YH永恒_ + new("storkfey#3570", isUp: true), // Calypso + new("fellowsand#1003", isUp: true), // C-Faust + new("jetsafe#8512", isUp: true), // Hoream是好人 + new("spoonkey#0792", isUp: true), // 没好康的 + new("beakedmire#6099", isUp: true), // 茄-au + new("doggedsize#7892", isUp: true), // TronAndRey + new("openlanded#9533", "#9e2424", "God Of Death Love Apples", true), // ryuk + new("unlikecity#4086", "#eD2F91", "Ward", true), // Ward + new("iconicdrop#2727", isUp: true), // jackler - new("goneria#8334", "#FFFF00", "The Dev on Phone", true, true), // [Developer] The 200IQ guy // Me on phone - new("neatnet#5851", "#FFFF00", "[Developer] The 200IQ guy", true, true), // [Developer] The 200IQ guy - new("contenthue#0404", "#FFFF00", "[Developer] The 200IQ guy", true, true), // [Developer] The 200IQ guy - new("theseform#5686", tag: "PTBR-Translator/Role Idea Creator", isUp: true), // Tomix - new("heavyclod#2286", "#FFFF00", "小叨.exe已停止运行", true), // 小叨院长 - new("storeroan#0331", "#FF0066", "Night_瓜", true), // Night_瓜 - new("teamelder#5856", "#1379bf", "屑Slok(没信誉的鸽子)", true), // Slok7565 + new("goneria#8334", "#FFFF00", "The Dev on Phone", true, true), // [Developer] The 200IQ guy // Me on phone + new("neatnet#5851", "#FFFF00", "[Developer] The 200IQ guy", true, true), // [Developer] The 200IQ guy + new("contenthue#0404", "#FFFF00", "[Developer] The 200IQ guy", true, true), // [Developer] The 200IQ guy + new("theseform#5686", tag: "PTBR-Translator/Role Idea Creator", isUp: true), // Tomix + new("heavyclod#2286", "#FFFF00", "小叨.exe已停止运行", true), // 小叨院长 + new("storeroan#0331", "#FF0066", "Night_瓜", true), // Night_瓜 + new("teamelder#5856", "#1379bf", "屑Slok(没信誉的鸽子)", true), // Slok7565 - new("radarright#2509"), + new("radarright#2509"), - // EHR players - new("ravenknurl#4562", "#008000", "Moderador do FH", true), // RicardoFumante - new("crustzonal#9589", "#00FFFF", "Translator PT-BR", true), // artyleague01 - new("tinedpun#6584", "#0000ff", "Translator PT-BR", true), // Dechis - new("swiftlord#8072", isUp: true), // But What About - new("ovalinstep#2984", isUp: true), // Seleneous - new("seleneous#6930", isUp: true), // Seleneous - new("innerruler#4140", isUp: true), // thewhiskas27 - new("urbanecalf#4975", "#420773", "Streamer", true), // Gugutsik - new("crustzonal#9589", isUp: true), // aviiiv0102 - new("tubedilute#9062", isUp: true), // Xcl4udioX - new("ponyholey#5532", "#0000FF", "desenvolvedor", true), // DrawingsZz - new("akinlaptop#2206", isUp: true), // MR Carr - new("fursilty#4676", "#0000ff", "arthurzin", true), // arthurzin - new("stonefuzzy#8673", "#ff0062", "Ru Translator", true, true), // HyperAtill - new("momenthale#7626", "#6f3bd9", "Amiran", true), // Amiran - new("valuecubic#5819", "#6f3bd9", "Kate", true), // Kate Cat - new("frizzytram#2508", "#1C87FF", "RafaelBIT50", true), // RafaelBIT50 - new("ruefulscar#0287", isUp: true), // Zendena - new("foggyzing#6238", isUp: true), // LdZinnn - new("onsideblur#3929", "#fc3a51", "YouTuber", isUp: true), // Manelzin - new("modestspan#7071", "null", "null", true), // Shark - new("divotbusy#0624", isUp: true), // PreCeptorBR - new("pinsrustic#5496", "#fc3a51", "YouTuber", isUp: true), // FHgameplay - new("opaquedot#5610", isUp: true), // Deh_66 - new("furpolitic#3380", "#a020f0", "EuOncologico o impostor", isUp: true), // EuOncologico - new("pithfierce#5073", isUp: true), // OrigeTv - new("cannylink#0564", isUp: true), // SpicyPoops - new("ghostapt#7243", "#a48d6b", "AUME", true), // MasterKy - new("planegame#5847", "#44fff7", "Kopp56", true), // Kopp56 PRO - new("clovesorry#6973", "#191970", "Master", true), // MAT - new("ivorywish#3580", "#ff0000", "YouTuber", true), // Erik Carr - new("cleardress#6310", "#ffffff", "<#00BFFF>一<#48D1CC>个<#7CFC00>热狗<#32CD32>uwu", true), // ABoringCat (CN Translator Team Leader) - new("alphacook#6624", "#ff0000", "Translator CN", true), // Dispenser (CN Translator Member) - new("rainypearl#9545", "#A020F0", "YouTuber", true), // bielModzs - new("kiltedbill#4145", isUp: true), // tsaki84 - new("onlyfax#3941", "#ff0000", "YouTuber", true), // The Nick AG - new("somewallet#5521", "#ffff00", "Artist", true), // YoshiBertil - new("pagersane#4064", isUp: true), // Bluejava3 - new("pocketdoor#9080", isUp: true), // Imnot - new("motorstack#2287", "#E34234", "Foxfire", true), // Esrazraft - new("motorlace#4741", "#DFB722", "\u2756 Assistant Tester \u2756", true), // PEPPERcula - new("kohlcuboid#5702", "#DFB722", "\u2756 Assistant Tester \u2756", true), // PEPPERcula - new("keyrunning#8720", "#00ffff", "Miku", true), // EverMortal_1455 - new("yellowjoy#3138", "#FFE87C", "Sunshine", true), // Becksy - new("stuckclaw#5717", "#FF474C", "The biggest SUS", true), // BIG SUS - new("funnytiger#8420", "#ffd4ec", "Registered Bozo", true), // Differntperson - new("raritykey#6021", "#08f638", "DESPAIR", true), // Junko - new("stiltedgap#2406", "#ffff00", "Youtuber", true), // Marcopolo1982 - new("spaglad#3866", "#ff0000", "YouTuber", true), // spider_191 - new("stapleecho#3635", isUp: true), // Thaylor1819 - new("coralcolt#2815", "#ed5356", "YouTuber", true), // TheDarkYT - new("deltasunny#0515", "#ff0000", "YouTuber", true), // LuffyFelx - new("strangeant#6543", isUp: true), // after.edits - new("ninjanice#3990", isUp: true), // void_daora - new("magicmill#4146", isUp: true), // Sr.Angel - new("grassyprey#9593", "#ff0000", "YouTuber", true), // LucasCarr - new("blessedtea#6684", "#ff0000", "YouTuber", true), // Rvimpostor - new("seriouslug#1532", "#FF2400", "ZEN", true), // Zypherus - new("potatotall#5573", isUp: true), // V.K - new("kamedexter#7241", "#ff0000", "YouTuber", true), // Lean + // EHR players + new("ravenknurl#4562", "#008000", "Moderador do FH", true), // RicardoFumante + new("crustzonal#9589", "#00FFFF", "Translator PT-BR", true), // artyleague01 + new("tinedpun#6584", "#0000ff", "Translator PT-BR", true), // Dechis + new("swiftlord#8072", isUp: true), // But What About + new("ovalinstep#2984", isUp: true), // Seleneous + new("seleneous#6930", isUp: true), // Seleneous + new("innerruler#4140", isUp: true), // thewhiskas27 + new("urbanecalf#4975", "#420773", "Streamer", true), // Gugutsik + new("crustzonal#9589", isUp: true), // aviiiv0102 + new("tubedilute#9062", isUp: true), // Xcl4udioX + new("ponyholey#5532", "#0000FF", "desenvolvedor", true), // DrawingsZz + new("akinlaptop#2206", isUp: true), // MR Carr + new("fursilty#4676", "#0000ff", "arthurzin", true), // arthurzin + new("stonefuzzy#8673", "#ff0062", "Ru Translator", true, true), // HyperAtill + new("momenthale#7626", "#6f3bd9", "Amiran", true), // Amiran + new("valuecubic#5819", "#6f3bd9", "Kate", true), // Kate Cat + new("frizzytram#2508", "#1C87FF", "RafaelBIT50", true), // RafaelBIT50 + new("ruefulscar#0287", isUp: true), // Zendena + new("foggyzing#6238", isUp: true), // LdZinnn + new("onsideblur#3929", "#fc3a51", "YouTuber", isUp: true), // Manelzin + new("modestspan#7071", "null", "null", true), // Shark + new("divotbusy#0624", isUp: true), // PreCeptorBR + new("pinsrustic#5496", "#fc3a51", "YouTuber", isUp: true), // FHgameplay + new("opaquedot#5610", isUp: true), // Deh_66 + new("furpolitic#3380", "#a020f0", "EuOncologico o impostor", isUp: true), // EuOncologico + new("pithfierce#5073", isUp: true), // OrigeTv + new("cannylink#0564", isUp: true), // SpicyPoops + new("ghostapt#7243", "#a48d6b", "AUME", true), // MasterKy + new("planegame#5847", "#44fff7", "Kopp56", true), // Kopp56 PRO + new("clovesorry#6973", "#191970", "Master", true), // MAT + new("ivorywish#3580", "#ff0000", "YouTuber", true), // Erik Carr + new("cleardress#6310", "#ffffff", "<#00BFFF>一<#48D1CC>个<#7CFC00>热狗<#32CD32>uwu", true), // ABoringCat (CN Translator Team Leader) + new("alphacook#6624", "#ff0000", "Translator CN", true), // Dispenser (CN Translator Member) + new("rainypearl#9545", "#A020F0", "YouTuber", true), // bielModzs + new("kiltedbill#4145", isUp: true), // tsaki84 + new("onlyfax#3941", "#ff0000", "YouTuber", true), // The Nick AG + new("somewallet#5521", "#ffff00", "Artist", true), // YoshiBertil + new("pagersane#4064", isUp: true), // Bluejava3 + new("pocketdoor#9080", isUp: true), // Imnot + new("motorstack#2287", "#E34234", "Foxfire", true), // Esrazraft + new("motorlace#4741", "#DFB722", "\u2756 Assistant Tester \u2756", true), // PEPPERcula + new("kohlcuboid#5702", "#DFB722", "\u2756 Assistant Tester \u2756", true), // PEPPERcula + new("keyrunning#8720", "#00ffff", "Miku", true), // EverMortal_1455 + new("yellowjoy#3138", "#FFE87C", "Sunshine", true), // Becksy + new("stuckclaw#5717", "#FF474C", "The biggest SUS", true), // BIG SUS + new("funnytiger#8420", "#ffd4ec", "Registered Bozo", true), // Differntperson + new("raritykey#6021", "#08f638", "DESPAIR", true), // Junko + new("stiltedgap#2406", "#ffff00", "Youtuber", true), // Marcopolo1982 + new("spaglad#3866", "#ff0000", "YouTuber", true), // spider_191 + new("stapleecho#3635", isUp: true), // Thaylor1819 + new("coralcolt#2815", "#ed5356", "YouTuber", true), // TheDarkYT + new("deltasunny#0515", "#ff0000", "YouTuber", true), // LuffyFelx + new("strangeant#6543", isUp: true), // after.edits + new("ninjanice#3990", isUp: true), // void_daora + new("magicmill#4146", isUp: true), // Sr.Angel + new("grassyprey#9593", "#ff0000", "YouTuber", true), // LucasCarr + new("blessedtea#6684", "#ff0000", "YouTuber", true), // Rvimpostor + new("seriouslug#1532", "#FF2400", "ZEN", true), // Zypherus + new("potatotall#5573", isUp: true), // V.K + new("kamedexter#7241", "#ff0000", "YouTuber", true), // Lean - // Sponsor - new("recentduct#6068", "#FF00FF", "高冷男模法师", false, true), - new("canneddrum#2370", "#fffcbe", "我是喜唉awa"), - new("dovefitted#5329", "#1379bf", "不要首刀我"), - new("luckylogo#7352", "#f30000", "林@林"), - new("axefitful#8788", "#8e8171", "寄才是真理"), - new("raftzonal#8893", "#8e8171", "寄才是真理"), - new("twainrobin#8089", "#0000FF", "啊哈修maker"), - new("mallcasual#6075", "#f89ccb", "波奇酱"), - new("beamelfin#9478", "#6495ED", "Amaster-1111"), - new("lordcosy#8966", "#FFD6EC", "HostEHR"), // K - new("honestsofa#2870", "#D381D9", "Discord: SolarFlare#0700", true), // SolarFlare - new("caseeast#7194", "#1c2451", "disc.gg/maul"), // laikrai + // Sponsor + new("recentduct#6068", "#FF00FF", "高冷男模法师", false, true), + new("canneddrum#2370", "#fffcbe", "我是喜唉awa"), + new("dovefitted#5329", "#1379bf", "不要首刀我"), + new("luckylogo#7352", "#f30000", "林@林"), + new("axefitful#8788", "#8e8171", "寄才是真理"), + new("raftzonal#8893", "#8e8171", "寄才是真理"), + new("twainrobin#8089", "#0000FF", "啊哈修maker"), + new("mallcasual#6075", "#f89ccb", "波奇酱"), + new("beamelfin#9478", "#6495ED", "Amaster-1111"), + new("lordcosy#8966", "#FFD6EC", "HostEHR"), // K + new("honestsofa#2870", "#D381D9", "Discord: SolarFlare#0700", true), // SolarFlare + new("caseeast#7194", "#1c2451", "disc.gg/maul"), // laikrai - // Other - new("peakcrown#8292", isUp: true) // Hakaka - ]; - } + // Other + new("peakcrown#8292", isUp: true) // Hakaka + ]; + } - private static bool IsDevUser(this string code) - { - return DevUserList.Any(x => x.Code == code); - } + private static bool IsDevUser(this string code) + { + return DevUserList.Any(x => x.Code == code); + } - public static DevUser GetDevUser(this string code) - { - code = code.Replace(':', '#'); - return code.IsDevUser() ? DevUserList.Find(x => x.Code == code) : DefaultDevUser; - } + public static DevUser GetDevUser(this string code) + { + code = code.Replace(':', '#'); + return code.IsDevUser() ? DevUserList.Find(x => x.Code == code) : DefaultDevUser; } } \ No newline at end of file diff --git a/Modules/DisableDevice.cs b/Modules/DisableDevice.cs index 4424de3fc..0c9abd7ef 100644 --- a/Modules/DisableDevice.cs +++ b/Modules/DisableDevice.cs @@ -6,221 +6,220 @@ using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using UnityEngine; -namespace EHR +namespace EHR; + +//参考元 : https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Mode/SuperHostRoles/BlockTool.cs +internal static class DisableDevice { - //参考元 : https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Mode/SuperHostRoles/BlockTool.cs - internal static class DisableDevice + private static readonly List DesyncComms = []; + private static int frame; + + public static readonly Dictionary DevicePos = new() { - private static readonly List DesyncComms = []; - private static int frame; + ["SkeldAdmin"] = new(3.48f, -8.62f), + ["SkeldCamera"] = new(-13.06f, -2.45f), + ["MiraHQAdmin"] = new(21.02f, 19.09f), + ["MiraHQDoorLog"] = new(16.22f, 5.82f), + ["PolusLeftAdmin"] = new(22.80f, -21.52f), + ["PolusRightAdmin"] = new(24.66f, -21.52f), + ["PolusCamera"] = new(2.96f, -12.74f), + ["PolusVital"] = new(26.70f, -15.94f), + ["DleksAdmin"] = new(-3.48f, -8.62f), + ["DleksCamera"] = new(13.06f, -2.45f), + ["AirshipCockpitAdmin"] = new(-22.32f, 0.91f), + ["AirshipRecordsAdmin"] = new(19.89f, 12.60f), + ["AirshipCamera"] = new(8.10f, -9.63f), + ["AirshipVital"] = new(25.24f, -7.94f), + ["FungleCamera"] = new(6.20f, 0.10f), + ["FungleVital"] = new(-2.50f, -9.80f) + }; - public static readonly Dictionary DevicePos = new() + public static bool DoDisable => Options.DisableDevices.GetBool(); + + public static float UsableDistance => Main.CurrentMap switch + { + MapNames.Skeld => 1.8f, + MapNames.Mira => 2.4f, + MapNames.Polus => 1.8f, + MapNames.Dleks => 1.5f, + MapNames.Airship => 1.8f, + MapNames.Fungle => 1.8f, + _ => 0.0f + }; + + public static void FixedUpdate() + { + frame = frame == 3 ? 0 : ++frame; + if (frame != 0) return; + + bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); + + if (!DoDisable && !rogueForce) return; + + foreach (PlayerControl pc in Main.AllPlayerControls) { - ["SkeldAdmin"] = new(3.48f, -8.62f), - ["SkeldCamera"] = new(-13.06f, -2.45f), - ["MiraHQAdmin"] = new(21.02f, 19.09f), - ["MiraHQDoorLog"] = new(16.22f, 5.82f), - ["PolusLeftAdmin"] = new(22.80f, -21.52f), - ["PolusRightAdmin"] = new(24.66f, -21.52f), - ["PolusCamera"] = new(2.96f, -12.74f), - ["PolusVital"] = new(26.70f, -15.94f), - ["DleksAdmin"] = new(-3.48f, -8.62f), - ["DleksCamera"] = new(13.06f, -2.45f), - ["AirshipCockpitAdmin"] = new(-22.32f, 0.91f), - ["AirshipRecordsAdmin"] = new(19.89f, 12.60f), - ["AirshipCamera"] = new(8.10f, -9.63f), - ["AirshipVital"] = new(25.24f, -7.94f), - ["FungleCamera"] = new(6.20f, 0.10f), - ["FungleVital"] = new(-2.50f, -9.80f) - }; - - public static bool DoDisable => Options.DisableDevices.GetBool(); - - public static float UsableDistance => Main.CurrentMap switch - { - MapNames.Skeld => 1.8f, - MapNames.Mira => 2.4f, - MapNames.Polus => 1.8f, - MapNames.Dleks => 1.5f, - MapNames.Airship => 1.8f, - MapNames.Fungle => 1.8f, - _ => 0.0f - }; - - public static void FixedUpdate() - { - frame = frame == 3 ? 0 : ++frame; - if (frame != 0) return; - - bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); - - if (!DoDisable && !rogueForce) return; - - foreach (PlayerControl pc in Main.AllPlayerControls) + try { - try + if (pc.IsModClient()) continue; + + var doComms = false; + Vector2 PlayerPos = pc.Pos(); + + bool ignore = (Options.DisableDevicesIgnoreImpostors.GetBool() && pc.Is(CustomRoleTypes.Impostor)) || + (Options.DisableDevicesIgnoreNeutrals.GetBool() && pc.Is(CustomRoleTypes.Neutral)) || + (Options.DisableDevicesIgnoreCrewmates.GetBool() && pc.Is(CustomRoleTypes.Crewmate)) || + (Options.DisableDevicesIgnoreAfterAnyoneDied.GetBool() && GameStates.AlreadyDied); + + ignore &= !rogueForce; + + if (pc.IsAlive() && !Utils.IsActive(SystemTypes.Comms)) { - if (pc.IsModClient()) continue; - - var doComms = false; - Vector2 PlayerPos = pc.Pos(); - - bool ignore = (Options.DisableDevicesIgnoreImpostors.GetBool() && pc.Is(CustomRoleTypes.Impostor)) || - (Options.DisableDevicesIgnoreNeutrals.GetBool() && pc.Is(CustomRoleTypes.Neutral)) || - (Options.DisableDevicesIgnoreCrewmates.GetBool() && pc.Is(CustomRoleTypes.Crewmate)) || - (Options.DisableDevicesIgnoreAfterAnyoneDied.GetBool() && GameStates.AlreadyDied); - - ignore &= !rogueForce; - - if (pc.IsAlive() && !Utils.IsActive(SystemTypes.Comms)) + switch (Main.NormalOptions.MapId) { - switch (Main.NormalOptions.MapId) - { - case 0: - if (Options.DisableSkeldAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["SkeldAdmin"]) <= UsableDistance; + case 0: + if (Options.DisableSkeldAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["SkeldAdmin"]) <= UsableDistance; - if (Options.DisableSkeldCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["SkeldCamera"]) <= UsableDistance; + if (Options.DisableSkeldCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["SkeldCamera"]) <= UsableDistance; - break; - case 1: - if (Options.DisableMiraHQAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["MiraHQAdmin"]) <= UsableDistance; + break; + case 1: + if (Options.DisableMiraHQAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["MiraHQAdmin"]) <= UsableDistance; - if (Options.DisableMiraHQDoorLog.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["MiraHQDoorLog"]) <= UsableDistance; + if (Options.DisableMiraHQDoorLog.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["MiraHQDoorLog"]) <= UsableDistance; - break; - case 2: - if (Options.DisablePolusAdmin.GetBool() || rogueForce) - { - doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusLeftAdmin"]) <= UsableDistance; - doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusRightAdmin"]) <= UsableDistance; - } + break; + case 2: + if (Options.DisablePolusAdmin.GetBool() || rogueForce) + { + doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusLeftAdmin"]) <= UsableDistance; + doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusRightAdmin"]) <= UsableDistance; + } - if (Options.DisablePolusCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusCamera"]) <= UsableDistance; + if (Options.DisablePolusCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusCamera"]) <= UsableDistance; - if (Options.DisablePolusVital.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusVital"]) <= UsableDistance; + if (Options.DisablePolusVital.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["PolusVital"]) <= UsableDistance; - break; - case 3: - if (Options.DisableSkeldAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["DleksAdmin"]) <= UsableDistance; + break; + case 3: + if (Options.DisableSkeldAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["DleksAdmin"]) <= UsableDistance; - if (Options.DisableSkeldCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["DleksCamera"]) <= UsableDistance; + if (Options.DisableSkeldCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["DleksCamera"]) <= UsableDistance; - break; - case 4: - if (Options.DisableAirshipCockpitAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipCockpitAdmin"]) <= UsableDistance; + break; + case 4: + if (Options.DisableAirshipCockpitAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipCockpitAdmin"]) <= UsableDistance; - if (Options.DisableAirshipRecordsAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipRecordsAdmin"]) <= UsableDistance; + if (Options.DisableAirshipRecordsAdmin.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipRecordsAdmin"]) <= UsableDistance; - if (Options.DisableAirshipCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipCamera"]) <= UsableDistance; + if (Options.DisableAirshipCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipCamera"]) <= UsableDistance; - if (Options.DisableAirshipVital.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipVital"]) <= UsableDistance; + if (Options.DisableAirshipVital.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["AirshipVital"]) <= UsableDistance; - break; - case 5: - if (Options.DisableFungleCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["FungleCamera"]) <= UsableDistance; + break; + case 5: + if (Options.DisableFungleCamera.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["FungleCamera"]) <= UsableDistance; - if (Options.DisableFungleVital.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["FungleVital"]) <= UsableDistance; + if (Options.DisableFungleVital.GetBool() || rogueForce) doComms |= Vector2.Distance(PlayerPos, DevicePos["FungleVital"]) <= UsableDistance; - break; - } - } - - doComms &= !ignore; - - if (doComms && !pc.inVent) - { - if (!DesyncComms.Contains(pc.PlayerId)) DesyncComms.Add(pc.PlayerId); - - pc.RpcDesyncRepairSystem(SystemTypes.Comms, 128); - } - else if (!Utils.IsActive(SystemTypes.Comms) && DesyncComms.Contains(pc.PlayerId)) - { - DesyncComms.Remove(pc.PlayerId); - pc.RpcDesyncRepairSystem(SystemTypes.Comms, 16); - - if (Main.NormalOptions.MapId is 1 or 5) // Mira HQ or The Fungle - pc.RpcDesyncRepairSystem(SystemTypes.Comms, 17); + break; } } - catch (Exception ex) + + doComms &= !ignore; + + if (doComms && !pc.inVent) { - Logger.Exception(ex, "DisableDevice"); + if (!DesyncComms.Contains(pc.PlayerId)) DesyncComms.Add(pc.PlayerId); + + pc.RpcDesyncRepairSystem(SystemTypes.Comms, 128); } + else if (!Utils.IsActive(SystemTypes.Comms) && DesyncComms.Contains(pc.PlayerId)) + { + DesyncComms.Remove(pc.PlayerId); + pc.RpcDesyncRepairSystem(SystemTypes.Comms, 16); + + if (Main.NormalOptions.MapId is 1 or 5) // Mira HQ or The Fungle + pc.RpcDesyncRepairSystem(SystemTypes.Comms, 17); + } + } + catch (Exception ex) + { + Logger.Exception(ex, "DisableDevice"); } } } +} - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] - public class RemoveDisableDevicesPatch +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] +public class RemoveDisableDevicesPatch +{ + public static void Postfix() { - public static void Postfix() + bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); + if (!Options.DisableDevices.GetBool() && !rogueForce) return; + + UpdateDisableDevices(); + } + + public static void UpdateDisableDevices() + { + PlayerControl player = PlayerControl.LocalPlayer; + bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); + + bool ignore = player.Is(CustomRoles.GM) || + !player.IsAlive() || + (Options.DisableDevicesIgnoreImpostors.GetBool() && player.Is(CustomRoleTypes.Impostor)) || + (Options.DisableDevicesIgnoreNeutrals.GetBool() && player.Is(CustomRoleTypes.Neutral)) || + (Options.DisableDevicesIgnoreCrewmates.GetBool() && player.Is(CustomRoleTypes.Crewmate)) || + (Options.DisableDevicesIgnoreAfterAnyoneDied.GetBool() && GameStates.AlreadyDied); + + ignore &= !rogueForce; + Il2CppArrayBase admins = Object.FindObjectsOfType(true); + Il2CppArrayBase consoles = Object.FindObjectsOfType(true); + if (admins == null || consoles == null) return; + + switch (Main.NormalOptions.MapId) { - bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); - if (!Options.DisableDevices.GetBool() && !rogueForce) return; + case 3: + case 0: + if (Options.DisableSkeldAdmin.GetBool() || rogueForce) admins[0].gameObject.GetComponent().enabled = ignore; - UpdateDisableDevices(); - } + if (Options.DisableSkeldCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "SurvConsole", x => x.gameObject.GetComponent().enabled = ignore); - public static void UpdateDisableDevices() - { - PlayerControl player = PlayerControl.LocalPlayer; - bool rogueForce = Rogue.On && Main.PlayerStates.Values.Any(x => x.Role is Rogue { DisableDevices: true }); + break; + case 1: + if (Options.DisableMiraHQAdmin.GetBool() || rogueForce) admins[0].gameObject.GetComponent().enabled = ignore; - bool ignore = player.Is(CustomRoles.GM) || - !player.IsAlive() || - (Options.DisableDevicesIgnoreImpostors.GetBool() && player.Is(CustomRoleTypes.Impostor)) || - (Options.DisableDevicesIgnoreNeutrals.GetBool() && player.Is(CustomRoleTypes.Neutral)) || - (Options.DisableDevicesIgnoreCrewmates.GetBool() && player.Is(CustomRoleTypes.Crewmate)) || - (Options.DisableDevicesIgnoreAfterAnyoneDied.GetBool() && GameStates.AlreadyDied); + if (Options.DisableMiraHQDoorLog.GetBool() || rogueForce) consoles.DoIf(x => x.name == "SurvLogConsole", x => x.gameObject.GetComponent().enabled = ignore); - ignore &= !rogueForce; - Il2CppArrayBase admins = Object.FindObjectsOfType(true); - Il2CppArrayBase consoles = Object.FindObjectsOfType(true); - if (admins == null || consoles == null) return; + break; + case 2: + if (Options.DisablePolusAdmin.GetBool() || rogueForce) admins.Do(x => x.gameObject.GetComponent().enabled = ignore); - switch (Main.NormalOptions.MapId) - { - case 3: - case 0: - if (Options.DisableSkeldAdmin.GetBool() || rogueForce) admins[0].gameObject.GetComponent().enabled = ignore; + if (Options.DisablePolusCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "Surv_Panel", x => x.gameObject.GetComponent().enabled = ignore); - if (Options.DisableSkeldCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "SurvConsole", x => x.gameObject.GetComponent().enabled = ignore); + if (Options.DisablePolusVital.GetBool() || rogueForce) consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore); - break; - case 1: - if (Options.DisableMiraHQAdmin.GetBool() || rogueForce) admins[0].gameObject.GetComponent().enabled = ignore; + break; + case 4: + admins.Do(x => + { + if ((Options.DisableAirshipCockpitAdmin.GetBool() && x.name == "panel_cockpit_map") || + (Options.DisableAirshipRecordsAdmin.GetBool() && x.name == "records_admin_map") || rogueForce) + x.gameObject.GetComponent().enabled = ignore; + }); - if (Options.DisableMiraHQDoorLog.GetBool() || rogueForce) consoles.DoIf(x => x.name == "SurvLogConsole", x => x.gameObject.GetComponent().enabled = ignore); + if (Options.DisableAirshipCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "task_cams", x => x.gameObject.GetComponent().enabled = ignore); - break; - case 2: - if (Options.DisablePolusAdmin.GetBool() || rogueForce) admins.Do(x => x.gameObject.GetComponent().enabled = ignore); + if (Options.DisableAirshipVital.GetBool() || rogueForce) consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore); - if (Options.DisablePolusCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "Surv_Panel", x => x.gameObject.GetComponent().enabled = ignore); + break; + case 5: + if (Options.DisableFungleCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "BinocularsSecurityConsole", x => x.gameObject.GetComponent().enabled = ignore); - if (Options.DisablePolusVital.GetBool() || rogueForce) consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore); + if (Options.DisableFungleCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "VitalsConsole", x => x.gameObject.GetComponent().enabled = ignore); - break; - case 4: - admins.Do(x => - { - if ((Options.DisableAirshipCockpitAdmin.GetBool() && x.name == "panel_cockpit_map") || - (Options.DisableAirshipRecordsAdmin.GetBool() && x.name == "records_admin_map") || rogueForce) - x.gameObject.GetComponent().enabled = ignore; - }); - - if (Options.DisableAirshipCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "task_cams", x => x.gameObject.GetComponent().enabled = ignore); - - if (Options.DisableAirshipVital.GetBool() || rogueForce) consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent().enabled = ignore); - - break; - case 5: - if (Options.DisableFungleCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "BinocularsSecurityConsole", x => x.gameObject.GetComponent().enabled = ignore); - - if (Options.DisableFungleCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "VitalsConsole", x => x.gameObject.GetComponent().enabled = ignore); - - break; - } + break; } } } \ No newline at end of file diff --git a/Modules/DoorsReset.cs b/Modules/DoorsReset.cs index 184652ac9..4dec73838 100644 --- a/Modules/DoorsReset.cs +++ b/Modules/DoorsReset.cs @@ -1,108 +1,107 @@ -namespace EHR.Modules +namespace EHR.Modules; + +public static class DoorsReset { - public static class DoorsReset + public enum ResetMode { - public enum ResetMode + AllOpen, + AllClosed, + RandomByDoor + } + + private static bool IsEnabled; + private static ResetMode Mode; + private static DoorsSystemType DoorsSystem => ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Doors, out ISystemType system) ? system.TryCast() : null; + + public static void Initialize() + { + // Not supported except for Airship, Polus and Fungle + if ((MapNames)Main.NormalOptions.MapId is not (MapNames.Airship or MapNames.Polus or MapNames.Fungle)) { - AllOpen, - AllClosed, - RandomByDoor + IsEnabled = false; + return; } - private static bool IsEnabled; - private static ResetMode Mode; - private static DoorsSystemType DoorsSystem => ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Doors, out ISystemType system) ? system.TryCast() : null; + IsEnabled = Options.ResetDoorsEveryTurns.GetBool(); + Mode = (ResetMode)Options.DoorsResetMode.GetValue(); + Logger.Info($"Initalization: [ {IsEnabled}, {Mode} ]", "DoorsReset"); + } - public static void Initialize() + /// Reset door status according to settings + public static void ResetDoors() + { + if (!IsEnabled || DoorsSystem == null) return; + + Logger.Info("Reset Completed", "DoorsReset"); + + switch (Mode) { - // Not supported except for Airship, Polus and Fungle - if ((MapNames)Main.NormalOptions.MapId is not (MapNames.Airship or MapNames.Polus or MapNames.Fungle)) - { - IsEnabled = false; - return; - } - - IsEnabled = Options.ResetDoorsEveryTurns.GetBool(); - Mode = (ResetMode)Options.DoorsResetMode.GetValue(); - Logger.Info($"Initalization: [ {IsEnabled}, {Mode} ]", "DoorsReset"); - } - - /// Reset door status according to settings - public static void ResetDoors() - { - if (!IsEnabled || DoorsSystem == null) return; - - Logger.Info("Reset Completed", "DoorsReset"); - - switch (Mode) - { - case ResetMode.AllOpen: - OpenAllDoors(); - break; - case ResetMode.AllClosed: - CloseAllDoors(); - break; - case ResetMode.RandomByDoor: - OpenOrCloseAllDoorsRandomly(); - break; - default: - Logger.Warn($"Invalid Reset Doors Mode: {Mode}", "DoorsReset"); - break; - } - } - - /// Open all doors on the map - public static void OpenAllDoors() - { - foreach (OpenableDoor door in ShipStatus.Instance.AllDoors) - { - if (door == null) continue; - - SetDoorOpenState(door, true); - } - - DoorsSystem.IsDirty = true; - } - - /// Close all doors on the map - public static void CloseAllDoors() - { - foreach (OpenableDoor door in ShipStatus.Instance.AllDoors) - { - if (door == null) continue; - - SetDoorOpenState(door, false); - } - - DoorsSystem.IsDirty = true; - } - - /// Randomly opens and closes all doors on the map - public static void OpenOrCloseAllDoorsRandomly() - { - foreach (OpenableDoor door in ShipStatus.Instance.AllDoors) - { - bool isOpen = IRandom.Instance.Next(2) > 0; - SetDoorOpenState(door, isOpen); - } - - DoorsSystem.IsDirty = true; - } - - /// Sets the open/close status of the door. Do nothing for doors that cannot be closed by sabotage - /// Target door - /// true for open, false for close - private static void SetDoorOpenState(OpenableDoor door, bool isOpen) - { - if (IsValidDoor(door)) door.SetDoorway(isOpen); - } - - /// Determine if the door is subject to reset - /// true if it is subject to reset - private static bool IsValidDoor(OpenableDoor door) - { - // Airship lounge toilets and Polus decontamination room doors are not closed - return door.Room is not (SystemTypes.Lounge or SystemTypes.Decontamination); + case ResetMode.AllOpen: + OpenAllDoors(); + break; + case ResetMode.AllClosed: + CloseAllDoors(); + break; + case ResetMode.RandomByDoor: + OpenOrCloseAllDoorsRandomly(); + break; + default: + Logger.Warn($"Invalid Reset Doors Mode: {Mode}", "DoorsReset"); + break; } } + + /// Open all doors on the map + public static void OpenAllDoors() + { + foreach (OpenableDoor door in ShipStatus.Instance.AllDoors) + { + if (door == null) continue; + + SetDoorOpenState(door, true); + } + + DoorsSystem.IsDirty = true; + } + + /// Close all doors on the map + public static void CloseAllDoors() + { + foreach (OpenableDoor door in ShipStatus.Instance.AllDoors) + { + if (door == null) continue; + + SetDoorOpenState(door, false); + } + + DoorsSystem.IsDirty = true; + } + + /// Randomly opens and closes all doors on the map + public static void OpenOrCloseAllDoorsRandomly() + { + foreach (OpenableDoor door in ShipStatus.Instance.AllDoors) + { + bool isOpen = IRandom.Instance.Next(2) > 0; + SetDoorOpenState(door, isOpen); + } + + DoorsSystem.IsDirty = true; + } + + /// Sets the open/close status of the door. Do nothing for doors that cannot be closed by sabotage + /// Target door + /// true for open, false for close + private static void SetDoorOpenState(OpenableDoor door, bool isOpen) + { + if (IsValidDoor(door)) door.SetDoorway(isOpen); + } + + /// Determine if the door is subject to reset + /// true if it is subject to reset + private static bool IsValidDoor(OpenableDoor door) + { + // Airship lounge toilets and Polus decontamination room doors are not closed + return door.Room is not (SystemTypes.Lounge or SystemTypes.Decontamination); + } } \ No newline at end of file diff --git a/Modules/DoubleTrigger.cs b/Modules/DoubleTrigger.cs index cee63a57d..d92f24d78 100644 --- a/Modules/DoubleTrigger.cs +++ b/Modules/DoubleTrigger.cs @@ -2,85 +2,84 @@ using System; using System.Collections.Generic; using UnityEngine; -namespace EHR +namespace EHR; + +internal static class DoubleTrigger { - internal static class DoubleTrigger + public static List PlayerIdList = []; + + public static Dictionary FirstTriggerTimer = []; + public static Dictionary FirstTriggerTarget = []; + public static Dictionary FirstTriggerAction = []; + + public static void Init() { - public static List PlayerIdList = []; + PlayerIdList = []; + FirstTriggerTimer = []; + FirstTriggerAction = []; + } - public static Dictionary FirstTriggerTimer = []; - public static Dictionary FirstTriggerTarget = []; - public static Dictionary FirstTriggerAction = []; - - public static void Init() + /// + /// Checks for whether the killer pressed their kill button twice on the same player + /// + /// Who the killer is + /// Who the killer is targeting with the kill button + /// The action that should be done if the killer only presses their kill button once + /// Whether the Action should be done or not + /// + /// Returns true if the kill button is clicked twice within 1 second. Otherwise, does the Action specified in the + /// parameter firstAction and returns false + /// + public static bool CheckDoubleTrigger(this PlayerControl killer, PlayerControl target, Action firstAction, bool doAction = true) + { + if (FirstTriggerTimer.ContainsKey(killer.PlayerId)) { - PlayerIdList = []; - FirstTriggerTimer = []; - FirstTriggerAction = []; + if (FirstTriggerTarget[killer.PlayerId] != target.PlayerId) + { + // If the second target is off target, single action on the first opponent. + return false; + } + + Logger.Info($"{killer.name} - Do Double Action", "DoubleTrigger"); + FirstTriggerTimer.Remove(killer.PlayerId); + FirstTriggerTarget.Remove(killer.PlayerId); + if (doAction) FirstTriggerAction.Remove(killer.PlayerId); + + return true; } - /// - /// Checks for whether the killer pressed their kill button twice on the same player - /// - /// Who the killer is - /// Who the killer is targeting with the kill button - /// The action that should be done if the killer only presses their kill button once - /// Whether the Action should be done or not - /// - /// Returns true if the kill button is clicked twice within 1 second. Otherwise, does the Action specified in the - /// parameter firstAction and returns false - /// - public static bool CheckDoubleTrigger(this PlayerControl killer, PlayerControl target, Action firstAction, bool doAction = true) + // Ignore the kill interval during single action + CheckMurderPatch.TimeSinceLastKill.Remove(killer.PlayerId); + FirstTriggerTimer.Add(killer.PlayerId, 1f); + FirstTriggerTarget.Add(killer.PlayerId, target.PlayerId); + if (doAction) FirstTriggerAction.Add(killer.PlayerId, firstAction); + + return false; + } + + public static void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) { - if (FirstTriggerTimer.ContainsKey(killer.PlayerId)) - { - if (FirstTriggerTarget[killer.PlayerId] != target.PlayerId) - { - // If the second target is off target, single action on the first opponent. - return false; - } - - Logger.Info($"{killer.name} - Do Double Action", "DoubleTrigger"); - FirstTriggerTimer.Remove(killer.PlayerId); - FirstTriggerTarget.Remove(killer.PlayerId); - if (doAction) FirstTriggerAction.Remove(killer.PlayerId); - - return true; - } - - // Ignore the kill interval during single action - CheckMurderPatch.TimeSinceLastKill.Remove(killer.PlayerId); - FirstTriggerTimer.Add(killer.PlayerId, 1f); - FirstTriggerTarget.Add(killer.PlayerId, target.PlayerId); - if (doAction) FirstTriggerAction.Add(killer.PlayerId, firstAction); - - return false; + FirstTriggerTimer.Clear(); + FirstTriggerTarget.Clear(); + FirstTriggerAction.Clear(); + return; } - public static void OnFixedUpdate(PlayerControl player) + byte playerId = player.PlayerId; + if (!FirstTriggerTimer.ContainsKey(playerId)) return; + + FirstTriggerTimer[playerId] -= Time.fixedDeltaTime; + + if (FirstTriggerTimer[playerId] <= 0) { - if (!GameStates.IsInTask) - { - FirstTriggerTimer.Clear(); - FirstTriggerTarget.Clear(); - FirstTriggerAction.Clear(); - return; - } + Logger.Info($"{player.name} - Do Single Action", "DoubleTrigger"); + if (FirstTriggerAction.ContainsKey(playerId)) FirstTriggerAction[playerId](); - byte playerId = player.PlayerId; - if (!FirstTriggerTimer.ContainsKey(playerId)) return; - - FirstTriggerTimer[playerId] -= Time.fixedDeltaTime; - - if (FirstTriggerTimer[playerId] <= 0) - { - Logger.Info($"{player.name} - Do Single Action", "DoubleTrigger"); - if (FirstTriggerAction.ContainsKey(playerId)) FirstTriggerAction[playerId](); - - FirstTriggerTimer.Remove(playerId); - FirstTriggerTarget.Remove(playerId); - FirstTriggerAction.Remove(playerId); - } + FirstTriggerTimer.Remove(playerId); + FirstTriggerTarget.Remove(playerId); + FirstTriggerAction.Remove(playerId); } } } \ No newline at end of file diff --git a/Modules/EAC.cs b/Modules/EAC.cs index a230b747d..8c6de03a1 100644 --- a/Modules/EAC.cs +++ b/Modules/EAC.cs @@ -7,758 +7,757 @@ using Hazel; using InnerNet; using static EHR.Translator; -namespace EHR +namespace EHR; + +internal static class EAC { - internal static class EAC + public static int DeNum; + + public static void WarnHost(int denum = 1) { - public static int DeNum; + DeNum += denum; - public static void WarnHost(int denum = 1) + if (ErrorText.Instance) { - DeNum += denum; + ErrorText.Instance.CheatDetected = DeNum > 3; + ErrorText.Instance.SBDetected = DeNum > 10; - if (ErrorText.Instance) - { - ErrorText.Instance.CheatDetected = DeNum > 3; - ErrorText.Instance.SBDetected = DeNum > 10; - - if (ErrorText.Instance.CheatDetected) - ErrorText.Instance.AddError(ErrorText.Instance.SBDetected ? ErrorCode.SBDetected : ErrorCode.CheatDetected); - else - ErrorText.Instance.Clear(); - } + if (ErrorText.Instance.CheatDetected) + ErrorText.Instance.AddError(ErrorText.Instance.SBDetected ? ErrorCode.SBDetected : ErrorCode.CheatDetected); + else + ErrorText.Instance.Clear(); } + } - public static bool ReceiveRpc(PlayerControl pc, byte callId, MessageReader reader) + public static bool ReceiveRpc(PlayerControl pc, byte callId, MessageReader reader) + { + if (!AmongUsClient.Instance.AmHost) return false; + + if (pc == null || reader == null) return false; + + try { - if (!AmongUsClient.Instance.AmHost) return false; + MessageReader sr = MessageReader.Get(reader); + var rpc = (RpcCalls)callId; - if (pc == null || reader == null) return false; - - try + switch (rpc) { - MessageReader sr = MessageReader.Get(reader); - var rpc = (RpcCalls)callId; + case RpcCalls.CheckName: + if (!GameStates.IsLobby) + { + WarnHost(); + Report(pc, "CheckName out of Lobby"); + HandleCheat(pc, "CheckName out of Lobby"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] CheckName out of lobby, rejected", "EAC"); + return true; + } - switch (rpc) + break; + case RpcCalls.SendChat when !pc.IsHost(): + string text = sr.ReadString(); + + if (text.Contains('░') || + text.Contains('▄') || + text.Contains('█') || + text.Contains('▌') || + text.Contains('▒') || + text.Contains("习近平")) + { + Report(pc, "Illegal messages"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent an illegal message, which has been rejected", "EAC"); + return true; + } + + break; + case RpcCalls.ReportDeadBody: + sr.ReadByte(); + + if (GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating && !pc.IsHost()) + { + WarnHost(); + Report(pc, "Report dead body in meeting"); + HandleCheat(pc, "Report dead body in meeting"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] attempted to report a body in a meeting, rejected", "EAC"); + return true; + } + + if (!GameStates.IsInGame) + { + WarnHost(); + Report(pc, "Try to Report body out of game B"); + HandleCheat(pc, "Try to Report body out of game B"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] attempted to report a body that may have been illegally killed, but was rejected", "EAC"); + return true; + } + + break; + case RpcCalls.CheckColor when !pc.IsHost(): + if (!GameStates.IsLobby) + { + WarnHost(); + Report(pc, "CheckColor out of Lobby"); + HandleCheat(pc, "CheckColor out of Lobby"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] check color out of lobby, rejected", "EAC"); + return true; + } + + break; + case RpcCalls.SetColor when !pc.IsHost() && (!pc.IsModClient() || !Options.PlayerCanSetColor.GetBool() || !GameStates.IsLobby): + Report(pc, "Directly SetColor"); + HandleCheat(pc, "Directly SetColor"); + Logger.Fatal($"Directly SetColor【{pc.GetClientId()}:{pc.GetRealName()}】has been rejected", "EAC"); + return true; + case RpcCalls.CheckMurder: + if (GameStates.IsLobby) + { + WarnHost(); + Report(pc, "CheckMurder in Lobby"); + HandleCheat(pc, "CheckMurder in Lobby"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] Illegal check kill, rejected", "EAC"); + return true; + } + + break; + case RpcCalls.MurderPlayer: + sr.ReadNetObject(); + + if (GameStates.IsLobby) + { + Report(pc, "Directly Murder Player In Lobby"); + HandleCheat(pc, "Directly Murder Player In Lobby"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] was killed directly in the lobby, rejected", "EAC"); + return true; + } + + Report(pc, "Directly Murder Player"); + HandleCheat(pc, "Directly Murder Player"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly killed, rejected", "EAC"); + return true; + case RpcCalls.CheckShapeshift: + if (GameStates.IsLobby) + { + Report(pc, "Lobby Check Shapeshift"); + HandleCheat(pc, "Lobby Check Shapeshift"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly transformed in lobby, rejected", "EAC"); + return true; + } + + break; + case RpcCalls.Shapeshift when !pc.IsHost(): { - case RpcCalls.CheckName: - if (!GameStates.IsLobby) - { - WarnHost(); - Report(pc, "CheckName out of Lobby"); - HandleCheat(pc, "CheckName out of Lobby"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] CheckName out of lobby, rejected", "EAC"); - return true; - } - - break; - case RpcCalls.SendChat when !pc.IsHost(): - string text = sr.ReadString(); - - if (text.Contains('░') || - text.Contains('▄') || - text.Contains('█') || - text.Contains('▌') || - text.Contains('▒') || - text.Contains("习近平")) - { - Report(pc, "Illegal messages"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent an illegal message, which has been rejected", "EAC"); - return true; - } - - break; - case RpcCalls.ReportDeadBody: - sr.ReadByte(); - - if (GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating && !pc.IsHost()) - { - WarnHost(); - Report(pc, "Report dead body in meeting"); - HandleCheat(pc, "Report dead body in meeting"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] attempted to report a body in a meeting, rejected", "EAC"); - return true; - } - - if (!GameStates.IsInGame) - { - WarnHost(); - Report(pc, "Try to Report body out of game B"); - HandleCheat(pc, "Try to Report body out of game B"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] attempted to report a body that may have been illegally killed, but was rejected", "EAC"); - return true; - } - - break; - case RpcCalls.CheckColor when !pc.IsHost(): - if (!GameStates.IsLobby) - { - WarnHost(); - Report(pc, "CheckColor out of Lobby"); - HandleCheat(pc, "CheckColor out of Lobby"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] check color out of lobby, rejected", "EAC"); - return true; - } - - break; - case RpcCalls.SetColor when !pc.IsHost() && (!pc.IsModClient() || !Options.PlayerCanSetColor.GetBool() || !GameStates.IsLobby): - Report(pc, "Directly SetColor"); - HandleCheat(pc, "Directly SetColor"); - Logger.Fatal($"Directly SetColor【{pc.GetClientId()}:{pc.GetRealName()}】has been rejected", "EAC"); - return true; - case RpcCalls.CheckMurder: - if (GameStates.IsLobby) - { - WarnHost(); - Report(pc, "CheckMurder in Lobby"); - HandleCheat(pc, "CheckMurder in Lobby"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] Illegal check kill, rejected", "EAC"); - return true; - } - - break; - case RpcCalls.MurderPlayer: - sr.ReadNetObject(); - - if (GameStates.IsLobby) - { - Report(pc, "Directly Murder Player In Lobby"); - HandleCheat(pc, "Directly Murder Player In Lobby"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] was killed directly in the lobby, rejected", "EAC"); - return true; - } - - Report(pc, "Directly Murder Player"); - HandleCheat(pc, "Directly Murder Player"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly killed, rejected", "EAC"); - return true; - case RpcCalls.CheckShapeshift: - if (GameStates.IsLobby) - { - Report(pc, "Lobby Check Shapeshift"); - HandleCheat(pc, "Lobby Check Shapeshift"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly transformed in lobby, rejected", "EAC"); - return true; - } - - break; - case RpcCalls.Shapeshift when !pc.IsHost(): - { - Report(pc, "Directly Shapeshift"); - MessageWriter swriter = AmongUsClient.Instance.StartRpcImmediately(pc.NetId, (byte)RpcCalls.Shapeshift, SendOption.Reliable); - swriter.WriteNetObject(pc); - swriter.Write(false); - AmongUsClient.Instance.FinishRpcImmediately(swriter); - HandleCheat(pc, "Directly Shapeshift"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly transformed, rejected", "EAC"); - return true; - } - case RpcCalls.StartVanish: - case RpcCalls.StartAppear: - { - string sreason = "Direct Phantom RPCs " + rpc; - Report(pc, sreason); - MessageWriter swriter = AmongUsClient.Instance.StartRpcImmediately(pc.NetId, (byte)RpcCalls.StartAppear, SendOption.Reliable); - swriter.Write(false); - AmongUsClient.Instance.FinishRpcImmediately(swriter); - HandleCheat(pc, sreason); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()} {sreason}, rejected", "EAC"); - return true; - } - case RpcCalls.SendChatNote: - Report(pc, "Directly Send ChatNote"); - HandleCheat(pc, "Directly Send ChatNote"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly sent ChatNote, which has been rejected", "EAC"); - return true; - case RpcCalls.CompleteTask when GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating && !pc.IsHost(): - Report(pc, "Complete Task in Meeting"); - HandleCheat(pc, "Complete Task in Meeting"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] completed a task in a meeting, which has been rejected", "EAC"); - return true; + Report(pc, "Directly Shapeshift"); + MessageWriter swriter = AmongUsClient.Instance.StartRpcImmediately(pc.NetId, (byte)RpcCalls.Shapeshift, SendOption.Reliable); + swriter.WriteNetObject(pc); + swriter.Write(false); + AmongUsClient.Instance.FinishRpcImmediately(swriter); + HandleCheat(pc, "Directly Shapeshift"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly transformed, rejected", "EAC"); + return true; } - - switch (callId) + case RpcCalls.StartVanish: + case RpcCalls.StartAppear: { - case 101: // Aum Chat - try - { - string firstString = sr.ReadString(); - string secondString = sr.ReadString(); - sr.ReadInt32(); - - bool flag = string.IsNullOrEmpty(firstString) && string.IsNullOrEmpty(secondString); - - if (!flag) - { - Report(pc, "Aum Chat RPC"); - HandleCheat(pc, "Aum Chat RPC"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent AUM chat, rejected", "EAC"); - return true; - } - } - catch { } - - break; - case unchecked((byte)42069): // 85 AUM - try - { - byte aumid = sr.ReadByte(); - - if (aumid == pc.PlayerId) - { - Report(pc, "AUM RPC"); - HandleCheat(pc, "AUM RPC"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent AUM RPC, rejected", "EAC"); - return true; - } - } - catch { } - - break; - case unchecked((byte)420): // 164 Sicko - if (sr.BytesRemaining == 0) - { - Report(pc, "Sicko RPC"); - HandleCheat(pc, "Sicko RPC"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent Sicko RPC, rejected", "EAC"); - return true; - } - - break; - case 7 when !pc.IsHost(): - case 8 when !pc.IsHost(): - if (!GameStates.IsLobby) - { - WarnHost(); - Report(pc, "Illegal setting of color"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] illegally set the color, rejected", "EAC"); - return true; - } - - break; - case 5 when !pc.IsHost(): - sr.ReadString(); - - if (GameStates.IsInGame) - { - WarnHost(); - Report(pc, "Illegal setting of game name"); - Logger.Fatal($"Illegal modification of the game name of the player [{pc.GetClientId()}:{pc.GetRealName()}] has been rejected", "EAC"); - return true; - } - - break; - case 47: - if (GameStates.IsLobby) - { - WarnHost(); - Report(pc, "Illegal Killing"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] illegally killed, rejected", "EAC"); - return true; - } - - break; - case 38 when !pc.IsHost(): - if (GameStates.IsInGame) - { - WarnHost(); - Report(pc, "Set level in game"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] changed the level in the game, which has been rejected", "EAC"); - return true; - } - - break; - case 39 when !pc.IsHost(): - case 40 when !pc.IsHost(): - case 41 when !pc.IsHost(): - case 42 when !pc.IsHost(): - case 43 when !pc.IsHost(): - if (GameStates.IsInGame) - { - WarnHost(); - Report(pc, "Change skin in game"); - Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] changed skin in the game, rejected", "EAC"); - return true; - } - - break; + string sreason = "Direct Phantom RPCs " + rpc; + Report(pc, sreason); + MessageWriter swriter = AmongUsClient.Instance.StartRpcImmediately(pc.NetId, (byte)RpcCalls.StartAppear, SendOption.Reliable); + swriter.Write(false); + AmongUsClient.Instance.FinishRpcImmediately(swriter); + HandleCheat(pc, sreason); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()} {sreason}, rejected", "EAC"); + return true; } - } - catch (Exception e) - { - Logger.Exception(e, "EAC"); - } - - WarnHost(-1); - return false; - } - - public static bool PlayerPhysicsRpcCheck(PlayerPhysics __instance, byte callId, MessageReader reader) // Credit: NikoCat233 - { - if (!AmongUsClient.Instance.AmHost) return false; - - var rpcType = (RpcCalls)callId; - MessageReader subReader = MessageReader.Get(reader); - - PlayerControl player = __instance.myPlayer; - - if (!player) - { - Logger.Warn("Received Physics RPC without a player", "EAC_PlayerPhysics"); - return true; - } - - if (GameStates.IsLobby && rpcType is not RpcCalls.Pet and not RpcCalls.CancelPet) - { - WarnHost(); - Report(player, $"Physics {rpcType} in lobby (can be spoofed by others)"); - HandleCheat(player, $"Physics {rpcType} in lobby (can be spoofed by others)"); - Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to {rpcType} in lobby.", "EAC_physics"); - return true; - } - - switch (rpcType) - { - case RpcCalls.EnterVent: - case RpcCalls.ExitVent: - int ventid = subReader.ReadPackedInt32(); - - if (!HasVent(ventid)) - { - if (AmongUsClient.Instance.AmHost) - { - WarnHost(); - Report(player, "Vent null vent (can be spoofed by others)"); - HandleCheat(player, "Vent null vent (can be spoofed by others)"); - Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to enter an unexisting vent. {ventid}", "EAC_physics"); - } - else - { - // Not sure whether host will send null vent to a player huh - Logger.Warn($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to enter an unexisting vent. {ventid}", "EAC_physics"); - - if (rpcType is RpcCalls.ExitVent) - { - player.Visible = true; - player.inVent = false; - player.moveable = true; - player.NetTransform.SetPaused(false); - } - } - - return true; - } - - break; - - case RpcCalls.BootFromVent: - // BootFromVent can only be sent by host - WarnHost(); - Report(player, "Got boot from vent from clients, can be spoofed"); - HandleCheat(player, "Got boot from vent from clients, can be spoofed"); - Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 sent boot from vent, can be spoofed.", "EAC_physics"); - break; - - case RpcCalls.ClimbLadder: - int ladderId = subReader.ReadPackedInt32(); - - if (!HasLadder(ladderId)) - { - if (AmongUsClient.Instance.AmHost) - { - WarnHost(); - Report(player, "climb null ladder (can be spoofed by others)"); - HandleCheat(player, "climb null ladder (can be spoofed by others)"); - Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to climb an unexisting ladder.", "EAC_physics"); - } - - return true; - } - - if (player.AmOwner) - { - Logger.Fatal("Got climb ladder for myself, this is impossible", "EAC_physics"); - return true; - } - - break; - - case RpcCalls.Pet: - if (player.AmOwner) - { - Logger.Fatal("Got pet pet for myself, this is impossible", "EAC_physics"); - return true; - } - - break; - } - - return false; - - bool HasLadder(int ladderId) - { - return ShipStatus.Instance.Ladders.Any(l => l.Id == ladderId); - } - - bool HasVent(int ventId) - { - return ShipStatus.Instance.AllVents.Any(v => v.Id == ventId); - } - } - - internal static void Report(PlayerControl pc, string reason) - { - var msg = $"{pc.GetClientId()}|{pc.FriendCode}|{pc.Data.PlayerName}|{pc.GetClient().GetHashedPuid()}|{reason}"; - //Cloud.SendData(msg); - Logger.Fatal($"EAC report: {msg}", "EAC Cloud"); - if (Options.CheatResponses.GetInt() != 5) Logger.SendInGame(string.Format(GetString("Message.NoticeByEAC"), $"{pc.Data?.PlayerName} | {pc.GetClient().GetHashedPuid()}", reason)); - } - - public static bool ReceiveInvalidRpc(PlayerControl pc, byte callId) - { - switch (callId) - { - case unchecked((byte)42069): - Report(pc, "AUM"); - HandleCheat(pc, GetString("EAC.CheatDetected.EAC")); + case RpcCalls.SendChatNote: + Report(pc, "Directly Send ChatNote"); + HandleCheat(pc, "Directly Send ChatNote"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] directly sent ChatNote, which has been rejected", "EAC"); + return true; + case RpcCalls.CompleteTask when GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating && !pc.IsHost(): + Report(pc, "Complete Task in Meeting"); + HandleCheat(pc, "Complete Task in Meeting"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] completed a task in a meeting, which has been rejected", "EAC"); return true; } - return true; - } - - private static void HandleCheat(PlayerControl pc, string text) - { - switch (Options.CheatResponses.GetInt()) + switch (callId) { - case 0: - AmongUsClient.Instance.KickPlayer(pc.GetClientId(), true); - string msg0 = string.Format(GetString("Message.BannedByEAC"), pc?.Data?.PlayerName, text); - Logger.Warn(msg0, "EAC"); - Logger.SendInGame(msg0); - break; - case 1: - AmongUsClient.Instance.KickPlayer(pc.GetClientId(), false); - string msg1 = string.Format(GetString("Message.KickedByEAC"), pc?.Data?.PlayerName, text); - Logger.Warn(msg1, "EAC"); - Logger.SendInGame(msg1); - break; - case 2: - Utils.SendMessage(string.Format(GetString("Message.NoticeByEAC"), pc?.Data?.PlayerName, text), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("MessageFromEAC"))); - break; - case 3: - foreach (PlayerControl player in Main.AllPlayerControls) + case 101: // Aum Chat + try { - if (player.PlayerId != pc?.Data?.PlayerId) + string firstString = sr.ReadString(); + string secondString = sr.ReadString(); + sr.ReadInt32(); + + bool flag = string.IsNullOrEmpty(firstString) && string.IsNullOrEmpty(secondString); + + if (!flag) { - string message = string.Format(GetString("Message.NoticeByEAC"), pc?.Data?.PlayerName, text); - string title = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("MessageFromEAC")); - Utils.SendMessage(message, player.PlayerId, title); + Report(pc, "Aum Chat RPC"); + HandleCheat(pc, "Aum Chat RPC"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent AUM chat, rejected", "EAC"); + return true; } } + catch { } break; - case 4: - string hashedPuid = pc.GetClient().GetHashedPuid(); - if (!BanManager.TempBanWhiteList.Contains(hashedPuid)) BanManager.TempBanWhiteList.Add(hashedPuid); - - AmongUsClient.Instance.KickPlayer(pc.GetClientId(), true); - string msg2 = string.Format(GetString("Message.TempBannedByEAC"), pc?.Data?.PlayerName, text); - Logger.Warn(msg2, "EAC"); - Logger.SendInGame(msg2); - break; - } - } - - internal static bool CheckInvalidSabotage(SystemTypes systemType, PlayerControl player, byte amount) - { - if (player.IsHost()) return false; - - if ((GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating) || ExileController.Instance) - { - WarnHost(); - Report(player, "Bad Sabotage D : In Meeting"); - HandleCheat(player, "Bad Sabotage D : In Meeting"); - Logger.Fatal($"Player [{player.GetClientId()}:{player.GetRealName()}] Bad Sabotage D, rejected", "EAC"); - return true; - } - - byte Mapid = Main.NormalOptions.MapId; - - switch (systemType) - { - case SystemTypes.LifeSupp: - if (Mapid != 0 && Mapid != 1 && Mapid != 3) goto Cheat; - - if (amount != 64 && amount != 65) goto Cheat; - - break; - case SystemTypes.Comms: - switch (amount) + case unchecked((byte)42069): // 85 AUM + try { - case 0: - if (Mapid is 1 or 5) goto Cheat; + byte aumid = sr.ReadByte(); - break; - case 64: - case 65: - case 32: - case 33: - case 16: - case 17: - if (Mapid is not (1 or 5)) goto Cheat; - - break; - default: - goto Cheat; - } - - break; - case SystemTypes.Electrical: - if (Mapid == 5) goto Cheat; - - if (amount >= 5) goto Cheat; - - break; - case SystemTypes.Laboratory: - if (Mapid != 2) goto Cheat; - - if (amount is not (64 or 65 or 32 or 33)) goto Cheat; - - break; - case SystemTypes.Reactor: - if (Mapid is 2 or 4) goto Cheat; - - if (amount is not (64 or 65 or 32 or 33)) goto Cheat; - - break; - case SystemTypes.HeliSabotage: - if (Mapid != 4) goto Cheat; - - if (amount is not (64 or 65 or 16 or 17 or 32 or 33)) goto Cheat; - - break; - case SystemTypes.MushroomMixupSabotage: - goto Cheat; - } - - return false; - - Cheat: - - { - WarnHost(); - Report(player, "Bad Sabotage C : Hack send RPC"); - HandleCheat(player, "Bad Sabotage C"); - Logger.Fatal($"Player [{player.GetClientId()}:{player.GetRealName()}] Bad Sabotage C, rejected", "EAC"); - return true; - } - } - } - -// https://github.com/0xDrMoe/TownofHost-Enhanced/blob/main/Patches/InnerNetClientPatch.cs - internal enum GameDataTag : byte - { - DataFlag = 1, - RpcFlag = 2, - SpawnFlag = 4, - DespawnFlag = 5, - SceneChangeFlag = 6, - ReadyFlag = 7, - ChangeSettingsFlag = 8, - ConsoleDeclareClientPlatformFlag = 205, - PS4RoomRequest = 206, - XboxDeclareXuid = 207 - } - - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleGameDataInner))] - internal class GameDataHandlerPatch - { - public static bool Prefix(InnerNetClient __instance, MessageReader reader, int msgNum) - { - var tag = (GameDataTag)reader.Tag; - - switch (tag) - { - case GameDataTag.DataFlag: - { - uint netId = reader.ReadPackedUInt32(); - - if (__instance.allObjectsFast.TryGetValue(netId, out InnerNetObject obj)) - { - if (obj.AmOwner) + if (aumid == pc.PlayerId) { - Logger.Warn($"Received DataFlag for object {netId.ToString()} {obj.name} that we own.", "GameDataHandlerPatch"); - EAC.WarnHost(); - return false; - } - - if (AmongUsClient.Instance.AmHost) - { - if (obj == MeetingHud.Instance) - { - Logger.Warn($"Received DataFlag for MeetingHud {netId.ToString()} that we own.", "GameDataHandlerPatch"); - EAC.WarnHost(); - return false; - } - - if (obj == VoteBanSystem.Instance) - { - Logger.Warn($"Received DataFlag for VoteBanSystem {netId.ToString()} that we own.", "GameDataHandlerPatch"); - EAC.WarnHost(); - return false; - } - - if (obj is NetworkedPlayerInfo) - { - Logger.Warn($"Received DataFlag for NetworkedPlayerInfo {netId.ToString()} that we own.", "GameDataHandlerPatch"); - EAC.WarnHost(); - return false; - } + Report(pc, "AUM RPC"); + HandleCheat(pc, "AUM RPC"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent AUM RPC, rejected", "EAC"); + return true; } } + catch { } break; - } - - case GameDataTag.RpcFlag: - break; - - case GameDataTag.SpawnFlag: - break; - - case GameDataTag.DespawnFlag: - break; - - case GameDataTag.SceneChangeFlag: - { - // Sender is only allowed to change his own scene. - int clientId = reader.ReadPackedInt32(); - string scene = reader.ReadString(); - - ClientData client = Utils.GetClientById(clientId); - - if (client == null) + case unchecked((byte)420): // 164 Sicko + if (sr.BytesRemaining == 0) { - Logger.Warn($"Received SceneChangeFlag for unknown client {clientId}.", "GameDataHandlerPatch"); - return false; + Report(pc, "Sicko RPC"); + HandleCheat(pc, "Sicko RPC"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] sent Sicko RPC, rejected", "EAC"); + return true; } - if (string.IsNullOrEmpty(scene)) + break; + case 7 when !pc.IsHost(): + case 8 when !pc.IsHost(): + if (!GameStates.IsLobby) { - Logger.Warn($"Client {client.PlayerName} ({client.Id}) tried to send SceneChangeFlag with null scene.", "GameDataHandlerPatch"); - EAC.WarnHost(); - return false; + WarnHost(); + Report(pc, "Illegal setting of color"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] illegally set the color, rejected", "EAC"); + return true; } - if (scene.ToLower() == "tutorial") - { - Logger.Warn($"Client {client.PlayerName} ({client.Id}) tried to send SceneChangeFlag to Tutorial.", "GameDataHandlerPatch"); - EAC.WarnHost(100); - - if (GameStates.IsOnlineGame && AmongUsClient.Instance.AmHost) Utils.ErrorEnd("SceneChange Tutorial Hack"); - - return false; - } + break; + case 5 when !pc.IsHost(): + sr.ReadString(); if (GameStates.IsInGame) { - Logger.Warn($"Client {client.PlayerName} ({client.Id}) tried to send SceneChangeFlag during mid of game.", "GameDataHandlerPatch"); - return false; + WarnHost(); + Report(pc, "Illegal setting of game name"); + Logger.Fatal($"Illegal modification of the game name of the player [{pc.GetClientId()}:{pc.GetRealName()}] has been rejected", "EAC"); + return true; } break; + case 47: + if (GameStates.IsLobby) + { + WarnHost(); + Report(pc, "Illegal Killing"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] illegally killed, rejected", "EAC"); + return true; + } + + break; + case 38 when !pc.IsHost(): + if (GameStates.IsInGame) + { + WarnHost(); + Report(pc, "Set level in game"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] changed the level in the game, which has been rejected", "EAC"); + return true; + } + + break; + case 39 when !pc.IsHost(): + case 40 when !pc.IsHost(): + case 41 when !pc.IsHost(): + case 42 when !pc.IsHost(): + case 43 when !pc.IsHost(): + if (GameStates.IsInGame) + { + WarnHost(); + Report(pc, "Change skin in game"); + Logger.Fatal($"Player [{pc.GetClientId()}:{pc.GetRealName()}] changed skin in the game, rejected", "EAC"); + return true; + } + + break; + } + } + catch (Exception e) + { + Logger.Exception(e, "EAC"); + } + + WarnHost(-1); + return false; + } + + public static bool PlayerPhysicsRpcCheck(PlayerPhysics __instance, byte callId, MessageReader reader) // Credit: NikoCat233 + { + if (!AmongUsClient.Instance.AmHost) return false; + + var rpcType = (RpcCalls)callId; + MessageReader subReader = MessageReader.Get(reader); + + PlayerControl player = __instance.myPlayer; + + if (!player) + { + Logger.Warn("Received Physics RPC without a player", "EAC_PlayerPhysics"); + return true; + } + + if (GameStates.IsLobby && rpcType is not RpcCalls.Pet and not RpcCalls.CancelPet) + { + WarnHost(); + Report(player, $"Physics {rpcType} in lobby (can be spoofed by others)"); + HandleCheat(player, $"Physics {rpcType} in lobby (can be spoofed by others)"); + Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to {rpcType} in lobby.", "EAC_physics"); + return true; + } + + switch (rpcType) + { + case RpcCalls.EnterVent: + case RpcCalls.ExitVent: + int ventid = subReader.ReadPackedInt32(); + + if (!HasVent(ventid)) + { + if (AmongUsClient.Instance.AmHost) + { + WarnHost(); + Report(player, "Vent null vent (can be spoofed by others)"); + HandleCheat(player, "Vent null vent (can be spoofed by others)"); + Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to enter an unexisting vent. {ventid}", "EAC_physics"); + } + else + { + // Not sure whether host will send null vent to a player huh + Logger.Warn($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to enter an unexisting vent. {ventid}", "EAC_physics"); + + if (rpcType is RpcCalls.ExitVent) + { + player.Visible = true; + player.inVent = false; + player.moveable = true; + player.NetTransform.SetPaused(false); + } + } + + return true; } - case GameDataTag.ReadyFlag: - { - int clientId = reader.ReadPackedInt32(); - ClientData client = Utils.GetClientById(clientId); + break; - if (client == null) + case RpcCalls.BootFromVent: + // BootFromVent can only be sent by host + WarnHost(); + Report(player, "Got boot from vent from clients, can be spoofed"); + HandleCheat(player, "Got boot from vent from clients, can be spoofed"); + Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 sent boot from vent, can be spoofed.", "EAC_physics"); + break; + + case RpcCalls.ClimbLadder: + int ladderId = subReader.ReadPackedInt32(); + + if (!HasLadder(ladderId)) + { + if (AmongUsClient.Instance.AmHost) { - Logger.Warn($"Received ReadyFlag for unknown client {clientId}.", "GameDataHandlerPatch"); + WarnHost(); + Report(player, "climb null ladder (can be spoofed by others)"); + HandleCheat(player, "climb null ladder (can be spoofed by others)"); + Logger.Fatal($"【{player.GetClientId()}:{player.GetRealName()}】 attempted to climb an unexisting ladder.", "EAC_physics"); + } + + return true; + } + + if (player.AmOwner) + { + Logger.Fatal("Got climb ladder for myself, this is impossible", "EAC_physics"); + return true; + } + + break; + + case RpcCalls.Pet: + if (player.AmOwner) + { + Logger.Fatal("Got pet pet for myself, this is impossible", "EAC_physics"); + return true; + } + + break; + } + + return false; + + bool HasLadder(int ladderId) + { + return ShipStatus.Instance.Ladders.Any(l => l.Id == ladderId); + } + + bool HasVent(int ventId) + { + return ShipStatus.Instance.AllVents.Any(v => v.Id == ventId); + } + } + + internal static void Report(PlayerControl pc, string reason) + { + var msg = $"{pc.GetClientId()}|{pc.FriendCode}|{pc.Data.PlayerName}|{pc.GetClient().GetHashedPuid()}|{reason}"; + //Cloud.SendData(msg); + Logger.Fatal($"EAC report: {msg}", "EAC Cloud"); + if (Options.CheatResponses.GetInt() != 5) Logger.SendInGame(string.Format(GetString("Message.NoticeByEAC"), $"{pc.Data?.PlayerName} | {pc.GetClient().GetHashedPuid()}", reason)); + } + + public static bool ReceiveInvalidRpc(PlayerControl pc, byte callId) + { + switch (callId) + { + case unchecked((byte)42069): + Report(pc, "AUM"); + HandleCheat(pc, GetString("EAC.CheatDetected.EAC")); + return true; + } + + return true; + } + + private static void HandleCheat(PlayerControl pc, string text) + { + switch (Options.CheatResponses.GetInt()) + { + case 0: + AmongUsClient.Instance.KickPlayer(pc.GetClientId(), true); + string msg0 = string.Format(GetString("Message.BannedByEAC"), pc?.Data?.PlayerName, text); + Logger.Warn(msg0, "EAC"); + Logger.SendInGame(msg0); + break; + case 1: + AmongUsClient.Instance.KickPlayer(pc.GetClientId(), false); + string msg1 = string.Format(GetString("Message.KickedByEAC"), pc?.Data?.PlayerName, text); + Logger.Warn(msg1, "EAC"); + Logger.SendInGame(msg1); + break; + case 2: + Utils.SendMessage(string.Format(GetString("Message.NoticeByEAC"), pc?.Data?.PlayerName, text), PlayerControl.LocalPlayer.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("MessageFromEAC"))); + break; + case 3: + foreach (PlayerControl player in Main.AllPlayerControls) + { + if (player.PlayerId != pc?.Data?.PlayerId) + { + string message = string.Format(GetString("Message.NoticeByEAC"), pc?.Data?.PlayerName, text); + string title = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("MessageFromEAC")); + Utils.SendMessage(message, player.PlayerId, title); + } + } + + break; + case 4: + string hashedPuid = pc.GetClient().GetHashedPuid(); + if (!BanManager.TempBanWhiteList.Contains(hashedPuid)) BanManager.TempBanWhiteList.Add(hashedPuid); + + AmongUsClient.Instance.KickPlayer(pc.GetClientId(), true); + string msg2 = string.Format(GetString("Message.TempBannedByEAC"), pc?.Data?.PlayerName, text); + Logger.Warn(msg2, "EAC"); + Logger.SendInGame(msg2); + break; + } + } + + internal static bool CheckInvalidSabotage(SystemTypes systemType, PlayerControl player, byte amount) + { + if (player.IsHost()) return false; + + if ((GameStates.IsMeeting && MeetingHud.Instance.state != MeetingHud.VoteStates.Animating) || ExileController.Instance) + { + WarnHost(); + Report(player, "Bad Sabotage D : In Meeting"); + HandleCheat(player, "Bad Sabotage D : In Meeting"); + Logger.Fatal($"Player [{player.GetClientId()}:{player.GetRealName()}] Bad Sabotage D, rejected", "EAC"); + return true; + } + + byte Mapid = Main.NormalOptions.MapId; + + switch (systemType) + { + case SystemTypes.LifeSupp: + if (Mapid != 0 && Mapid != 1 && Mapid != 3) goto Cheat; + + if (amount != 64 && amount != 65) goto Cheat; + + break; + case SystemTypes.Comms: + switch (amount) + { + case 0: + if (Mapid is 1 or 5) goto Cheat; + + break; + case 64: + case 65: + case 32: + case 33: + case 16: + case 17: + if (Mapid is not (1 or 5)) goto Cheat; + + break; + default: + goto Cheat; + } + + break; + case SystemTypes.Electrical: + if (Mapid == 5) goto Cheat; + + if (amount >= 5) goto Cheat; + + break; + case SystemTypes.Laboratory: + if (Mapid != 2) goto Cheat; + + if (amount is not (64 or 65 or 32 or 33)) goto Cheat; + + break; + case SystemTypes.Reactor: + if (Mapid is 2 or 4) goto Cheat; + + if (amount is not (64 or 65 or 32 or 33)) goto Cheat; + + break; + case SystemTypes.HeliSabotage: + if (Mapid != 4) goto Cheat; + + if (amount is not (64 or 65 or 16 or 17 or 32 or 33)) goto Cheat; + + break; + case SystemTypes.MushroomMixupSabotage: + goto Cheat; + } + + return false; + + Cheat: + + { + WarnHost(); + Report(player, "Bad Sabotage C : Hack send RPC"); + HandleCheat(player, "Bad Sabotage C"); + Logger.Fatal($"Player [{player.GetClientId()}:{player.GetRealName()}] Bad Sabotage C, rejected", "EAC"); + return true; + } + } +} + +// https://github.com/0xDrMoe/TownofHost-Enhanced/blob/main/Patches/InnerNetClientPatch.cs +internal enum GameDataTag : byte +{ + DataFlag = 1, + RpcFlag = 2, + SpawnFlag = 4, + DespawnFlag = 5, + SceneChangeFlag = 6, + ReadyFlag = 7, + ChangeSettingsFlag = 8, + ConsoleDeclareClientPlatformFlag = 205, + PS4RoomRequest = 206, + XboxDeclareXuid = 207 +} + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HandleGameDataInner))] +internal class GameDataHandlerPatch +{ + public static bool Prefix(InnerNetClient __instance, MessageReader reader, int msgNum) + { + var tag = (GameDataTag)reader.Tag; + + switch (tag) + { + case GameDataTag.DataFlag: + { + uint netId = reader.ReadPackedUInt32(); + + if (__instance.allObjectsFast.TryGetValue(netId, out InnerNetObject obj)) + { + if (obj.AmOwner) + { + Logger.Warn($"Received DataFlag for object {netId.ToString()} {obj.name} that we own.", "GameDataHandlerPatch"); EAC.WarnHost(); return false; } if (AmongUsClient.Instance.AmHost) { - if (!StartGameHostPatchEAC.IsStartingAsHost) + if (obj == MeetingHud.Instance) { - Logger.Warn($"Received ReadyFlag while game is started from {clientId}.", "GameDataHandlerPatch"); + Logger.Warn($"Received DataFlag for MeetingHud {netId.ToString()} that we own.", "GameDataHandlerPatch"); + EAC.WarnHost(); + return false; + } + + if (obj == VoteBanSystem.Instance) + { + Logger.Warn($"Received DataFlag for VoteBanSystem {netId.ToString()} that we own.", "GameDataHandlerPatch"); + EAC.WarnHost(); + return false; + } + + if (obj is NetworkedPlayerInfo) + { + Logger.Warn($"Received DataFlag for NetworkedPlayerInfo {netId.ToString()} that we own.", "GameDataHandlerPatch"); EAC.WarnHost(); return false; } } - - break; } - case GameDataTag.ConsoleDeclareClientPlatformFlag: - break; + break; } - return true; - } - } + case GameDataTag.RpcFlag: + break; - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGameHost))] - internal static class StartGameHostPatchEAC - { - public static bool IsStartingAsHost; + case GameDataTag.SpawnFlag: + break; - public static void Prefix() - { - if (LobbyBehaviour.Instance != null) IsStartingAsHost = true; - } + case GameDataTag.DespawnFlag: + break; - public static void Postfix() - { - if (ShipStatus.Instance != null) IsStartingAsHost = false; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - internal static class CheckInvalidMovementPatch - { - private static readonly Dictionary LastCheck = []; - public static readonly Dictionary LastPosition = []; - public static readonly HashSet ExemptedPlayers = []; - - public static void Postfix(PlayerControl __instance) - { - if (!GameStates.IsInTask || ExileController.Instance || !Options.EnableMovementChecking.GetBool() || Main.HasJustStarted || MeetingStates.FirstMeeting || Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod) >= 1.9f || AmongUsClient.Instance.Ping >= 300 || CustomGameMode.NaturalDisasters.IsActiveOrIntegrated() || Utils.GetRegionName() is not ("EU" or "NA" or "AS") || __instance == null || __instance.PlayerId == 255 || !__instance.IsAlive() || __instance.inVent) return; - - Vector2 pos = __instance.Pos(); - long now = Utils.TimeStamp; - - if (!LastPosition.TryGetValue(__instance.PlayerId, out Vector2 lastPosition)) + case GameDataTag.SceneChangeFlag: { - SetCurrentData(); - return; + // Sender is only allowed to change his own scene. + int clientId = reader.ReadPackedInt32(); + string scene = reader.ReadString(); + + ClientData client = Utils.GetClientById(clientId); + + if (client == null) + { + Logger.Warn($"Received SceneChangeFlag for unknown client {clientId}.", "GameDataHandlerPatch"); + return false; + } + + if (string.IsNullOrEmpty(scene)) + { + Logger.Warn($"Client {client.PlayerName} ({client.Id}) tried to send SceneChangeFlag with null scene.", "GameDataHandlerPatch"); + EAC.WarnHost(); + return false; + } + + if (scene.ToLower() == "tutorial") + { + Logger.Warn($"Client {client.PlayerName} ({client.Id}) tried to send SceneChangeFlag to Tutorial.", "GameDataHandlerPatch"); + EAC.WarnHost(100); + + if (GameStates.IsOnlineGame && AmongUsClient.Instance.AmHost) Utils.ErrorEnd("SceneChange Tutorial Hack"); + + return false; + } + + if (GameStates.IsInGame) + { + Logger.Warn($"Client {client.PlayerName} ({client.Id}) tried to send SceneChangeFlag during mid of game.", "GameDataHandlerPatch"); + return false; + } + + break; } - if (LastCheck.TryGetValue(__instance.PlayerId, out long lastCheck) && lastCheck == now) return; + case GameDataTag.ReadyFlag: + { + int clientId = reader.ReadPackedInt32(); + ClientData client = Utils.GetClientById(clientId); + if (client == null) + { + Logger.Warn($"Received ReadyFlag for unknown client {clientId}.", "GameDataHandlerPatch"); + EAC.WarnHost(); + return false; + } + + if (AmongUsClient.Instance.AmHost) + { + if (!StartGameHostPatchEAC.IsStartingAsHost) + { + Logger.Warn($"Received ReadyFlag while game is started from {clientId}.", "GameDataHandlerPatch"); + EAC.WarnHost(); + return false; + } + } + + break; + } + + case GameDataTag.ConsoleDeclareClientPlatformFlag: + break; + } + + return true; + } +} + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGameHost))] +internal static class StartGameHostPatchEAC +{ + public static bool IsStartingAsHost; + + public static void Prefix() + { + if (LobbyBehaviour.Instance != null) IsStartingAsHost = true; + } + + public static void Postfix() + { + if (ShipStatus.Instance != null) IsStartingAsHost = false; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] +internal static class CheckInvalidMovementPatch +{ + private static readonly Dictionary LastCheck = []; + public static readonly Dictionary LastPosition = []; + public static readonly HashSet ExemptedPlayers = []; + + public static void Postfix(PlayerControl __instance) + { + if (!GameStates.IsInTask || ExileController.Instance || !Options.EnableMovementChecking.GetBool() || Main.HasJustStarted || MeetingStates.FirstMeeting || Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod) >= 1.9f || AmongUsClient.Instance.Ping >= 300 || CustomGameMode.NaturalDisasters.IsActiveOrIntegrated() || Utils.GetRegionName() is not ("EU" or "NA" or "AS") || __instance == null || __instance.PlayerId == 255 || !__instance.IsAlive() || __instance.inVent) return; + + Vector2 pos = __instance.Pos(); + long now = Utils.TimeStamp; + + if (!LastPosition.TryGetValue(__instance.PlayerId, out Vector2 lastPosition)) + { SetCurrentData(); - - if (Vector2.Distance(lastPosition, pos) > 10f && PhysicsHelpers.AnythingBetween(__instance.Collider, lastPosition, pos, Constants.ShipOnlyMask, false)) - { - if (ExemptedPlayers.Remove(__instance.PlayerId)) return; - - EAC.WarnHost(); - EAC.Report(__instance, "This player is moving too fast, possibly using a speed hack."); - } - return; + } - void SetCurrentData() - { - LastPosition[__instance.PlayerId] = pos; - LastCheck[__instance.PlayerId] = now; - } + if (LastCheck.TryGetValue(__instance.PlayerId, out long lastCheck) && lastCheck == now) return; + + SetCurrentData(); + + if (Vector2.Distance(lastPosition, pos) > 10f && PhysicsHelpers.AnythingBetween(__instance.Collider, lastPosition, pos, Constants.ShipOnlyMask, false)) + { + if (ExemptedPlayers.Remove(__instance.PlayerId)) return; + + EAC.WarnHost(); + EAC.Report(__instance, "This player is moving too fast, possibly using a speed hack."); + } + + return; + + void SetCurrentData() + { + LastPosition[__instance.PlayerId] = pos; + LastCheck[__instance.PlayerId] = now; } } } \ No newline at end of file diff --git a/Modules/ErrorText.cs b/Modules/ErrorText.cs index 04e38a825..a93361a54 100644 --- a/Modules/ErrorText.cs +++ b/Modules/ErrorText.cs @@ -6,216 +6,215 @@ using TMPro; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +public class ErrorText : MonoBehaviour { - public class ErrorText : MonoBehaviour + public TextMeshPro Text; + public Camera Camera; + public Vector3 TextOffset = new(0, 0.3f, -1000f); + + public bool HnSFlag; + public bool CheatDetected; + public bool SBDetected; + private readonly List AllErrors = []; + private Camera _camera; + private Camera _camera1; + + public static bool HasHint => Instance.AllErrors.Any(err => err.Code == ErrorCode.LoadingHint); + + public void Update() { - public TextMeshPro Text; - public Camera Camera; - public Vector3 TextOffset = new(0, 0.3f, -1000f); + AllErrors.ForEach(err => err.IncreaseTimer()); + ErrorData[] ToRemove = AllErrors.Where(err => err.ErrorLevel <= 1 && 30f < err.Timer).ToArray(); - public bool HnSFlag; - public bool CheatDetected; - public bool SBDetected; - private readonly List AllErrors = []; - private Camera _camera; - private Camera _camera1; - - public static bool HasHint => Instance.AllErrors.Any(err => err.Code == ErrorCode.LoadingHint); - - public void Update() + if (ToRemove.Length > 0) { - AllErrors.ForEach(err => err.IncreaseTimer()); - ErrorData[] ToRemove = AllErrors.Where(err => err.ErrorLevel <= 1 && 30f < err.Timer).ToArray(); - - if (ToRemove.Length > 0) - { - AllErrors.RemoveAll(err => ToRemove.Contains(err)); - UpdateText(); - if (HnSFlag) Destroy(gameObject); - } - } - - public void LateUpdate() - { - if (!Text.enabled) return; - - if (!Camera) Camera = !HudManager.InstanceExists ? _camera : _camera1; - - if (Camera) transform.position = AspectPosition.ComputeWorldPosition(Camera, AspectPosition.EdgeAlignments.Top, TextOffset); - } - - public static void Create(TextMeshPro baseText) - { - TextMeshPro Text = Instantiate(baseText); - var instance = Text.gameObject.AddComponent(); - instance.Text = Text; - instance.name = "ErrorText"; - - Text.enabled = false; - Text.text = "NO ERROR"; - Text.color = Color.red; - Text.outlineColor = Color.black; - Text.alignment = TextAlignmentOptions.Top; - } - - public static void RemoveHint() - { - Instance.AllErrors.RemoveAll(err => err.Code == ErrorCode.LoadingHint); - Instance.UpdateText(); - } - - public void AddError(ErrorCode code) - { - var error = new ErrorData(code); - if (0 < error.ErrorLevel && code != ErrorCode.LoadingHint) Logger.Error($"Error: {error}: {error.Message}", "ErrorText"); - - if (AllErrors.All(e => e.Code != code)) AllErrors.Add(error); - + AllErrors.RemoveAll(err => ToRemove.Contains(err)); UpdateText(); + if (HnSFlag) Destroy(gameObject); } + } - public void UpdateText() + public void LateUpdate() + { + if (!Text.enabled) return; + + if (!Camera) Camera = !HudManager.InstanceExists ? _camera : _camera1; + + if (Camera) transform.position = AspectPosition.ComputeWorldPosition(Camera, AspectPosition.EdgeAlignments.Top, TextOffset); + } + + public static void Create(TextMeshPro baseText) + { + TextMeshPro Text = Instantiate(baseText); + var instance = Text.gameObject.AddComponent(); + instance.Text = Text; + instance.name = "ErrorText"; + + Text.enabled = false; + Text.text = "NO ERROR"; + Text.color = Color.red; + Text.outlineColor = Color.black; + Text.alignment = TextAlignmentOptions.Top; + } + + public static void RemoveHint() + { + Instance.AllErrors.RemoveAll(err => err.Code == ErrorCode.LoadingHint); + Instance.UpdateText(); + } + + public void AddError(ErrorCode code) + { + var error = new ErrorData(code); + if (0 < error.ErrorLevel && code != ErrorCode.LoadingHint) Logger.Error($"Error: {error}: {error.Message}", "ErrorText"); + + if (AllErrors.All(e => e.Code != code)) AllErrors.Add(error); + + UpdateText(); + } + + public void UpdateText() + { + try { - try + var text = string.Empty; + var maxLevel = 0; + var hint = false; + + foreach (ErrorData err in AllErrors) { - var text = string.Empty; - var maxLevel = 0; - var hint = false; + if (err.Code == ErrorCode.LoadingHint) hint = true; - foreach (ErrorData err in AllErrors) - { - if (err.Code == ErrorCode.LoadingHint) hint = true; - - text += hint ? LoadingScreen.Hint : $"{err}: {err.Message}\n"; - if (maxLevel < err.ErrorLevel) maxLevel = err.ErrorLevel; - } - - if (maxLevel == 0) - Text.enabled = false; - else - { - if (!HnSFlag && !hint) text += $"{GetString($"ErrorLevel{maxLevel}")}"; - - if (CheatDetected) text = SBDetected ? GetString("EAC.CheatDetected.HighLevel") : GetString("EAC.CheatDetected.LowLevel"); - - Text.enabled = true; - } - - if (GameStates.IsInGame && maxLevel != 3 && !CheatDetected) text += $"\n{GetString("TerminateCommand")}: Shift+L+Enter"; - - Text.text = text; - } - catch (NullReferenceException) { } - catch (Exception e) - { - Logger.Error(e.ToString(), "ErrorText.UpdateText"); - } - } - - public void Clear() - { - AllErrors.RemoveAll(err => err.ErrorLevel != 3); - UpdateText(); - } - - private class ErrorData - { - public readonly ErrorCode Code; - public readonly int ErrorLevel; - private readonly int ErrorType1; - private readonly int ErrorType2; - - public ErrorData(ErrorCode code) - { - Code = code; - ErrorType1 = (int)code / 10000; - ErrorType2 = ((int)code / 10) - (ErrorType1 * 1000); // xxxyyy - xxx000 - ErrorLevel = (int)code - ((int)code / 10 * 10); - Timer = 0f; + text += hint ? LoadingScreen.Hint : $"{err}: {err.Message}\n"; + if (maxLevel < err.ErrorLevel) maxLevel = err.ErrorLevel; } - public float Timer { get; private set; } - public string Message => $"{GetString(ToString())}"; - - public override string ToString() - { - // ERR-xxx-yyy-z - return $"ERR-{ErrorType1:000}-{ErrorType2:000}-{ErrorLevel:0}"; - } - - public void IncreaseTimer() - { - Timer += Time.deltaTime; - } - } - - #region Singleton - - public static ErrorText Instance { get; private set; } - - private void Awake() - { - if (Instance) - Destroy(gameObject); + if (maxLevel == 0) + Text.enabled = false; else { - Instance = this; - DontDestroyOnLoad(this); + if (!HnSFlag && !hint) text += $"{GetString($"ErrorLevel{maxLevel}")}"; + + if (CheatDetected) text = SBDetected ? GetString("EAC.CheatDetected.HighLevel") : GetString("EAC.CheatDetected.LowLevel"); + + Text.enabled = true; } + + if (GameStates.IsInGame && maxLevel != 3 && !CheatDetected) text += $"\n{GetString("TerminateCommand")}: Shift+L+Enter"; + + Text.text = text; } - - private int Frame; - - private void FixedUpdate() + catch (NullReferenceException) { } + catch (Exception e) { - if (Frame++ < 40) return; + Logger.Error(e.ToString(), "ErrorText.UpdateText"); + } + } - Frame = 0; + public void Clear() + { + AllErrors.RemoveAll(err => err.ErrorLevel != 3); + UpdateText(); + } - if (_camera != null && _camera1 != null) return; + private class ErrorData + { + public readonly ErrorCode Code; + public readonly int ErrorLevel; + private readonly int ErrorType1; + private readonly int ErrorType2; - try - { - _camera1 = HudManager.Instance.PlayerCam.GetComponent(); - _camera = Camera.main; - } - catch { } + public ErrorData(ErrorCode code) + { + Code = code; + ErrorType1 = (int)code / 10000; + ErrorType2 = ((int)code / 10) - (ErrorType1 * 1000); // xxxyyy - xxx000 + ErrorLevel = (int)code - ((int)code / 10 * 10); + Timer = 0f; } - #endregion + public float Timer { get; private set; } + public string Message => $"{GetString(ToString())}"; + + public override string ToString() + { + // ERR-xxx-yyy-z + return $"ERR-{ErrorType1:000}-{ErrorType2:000}-{ErrorLevel:0}"; + } + + public void IncreaseTimer() + { + Timer += Time.deltaTime; + } } - public enum ErrorCode + #region Singleton + + public static ErrorText Instance { get; private set; } + + private void Awake() { - //xxxyyyz: ERR-xxx-yyy-z - // xxx: General type of error (HUD-related, banishment-related, etc.) - // yyy: Detailed type of error (BoutyHunter processing, SerialKiller processing, etc.) - // z: Severity - // 0: No action required (hide) - // 1: Abandon village if not working properly (hide after a certain period of time) - // 2: Abandon village recommended (hide as abandoned village) - // 3: Unable to handle on user side (do not delete) - // ========== - // 001 Main - Main_DictionaryError = 0010003, // 001-000-3 Main Dictionary Error - - // 002 Support related - UnsupportedVersion = 002_000_3, // 002-000-1 AmongUs version is outdated - UnsupportedMap = 002_000_2, // 002-000-2 Unsupported Map - - // ========== - // 000 Test - NoError = 0000000, // 000-000-0 No Error - TestError0 = 0009000, // 000-900-0 Test Error 0 - TestError1 = 0009101, // 000-910-1 Test Error 1 - TestError2 = 0009202, // 000-920-2 Test Error 2 - TestError3 = 0009303, // 000-930-3 Test Error 3 - HnsUnload = 000_804_1, // 000-804-1 Unloaded By HnS - CheatDetected = 000_666_2, // 000-666-2 - SBDetected = 000_666_1, // 000-666-1 - - // ========== - LoadingHint = 000_999_3 // 000-999-3 Loading Hint + if (Instance) + Destroy(gameObject); + else + { + Instance = this; + DontDestroyOnLoad(this); + } } + + private int Frame; + + private void FixedUpdate() + { + if (Frame++ < 40) return; + + Frame = 0; + + if (_camera != null && _camera1 != null) return; + + try + { + _camera1 = HudManager.Instance.PlayerCam.GetComponent(); + _camera = Camera.main; + } + catch { } + } + + #endregion +} + +public enum ErrorCode +{ + //xxxyyyz: ERR-xxx-yyy-z + // xxx: General type of error (HUD-related, banishment-related, etc.) + // yyy: Detailed type of error (BoutyHunter processing, SerialKiller processing, etc.) + // z: Severity + // 0: No action required (hide) + // 1: Abandon village if not working properly (hide after a certain period of time) + // 2: Abandon village recommended (hide as abandoned village) + // 3: Unable to handle on user side (do not delete) + // ========== + // 001 Main + Main_DictionaryError = 0010003, // 001-000-3 Main Dictionary Error + + // 002 Support related + UnsupportedVersion = 002_000_3, // 002-000-1 AmongUs version is outdated + UnsupportedMap = 002_000_2, // 002-000-2 Unsupported Map + + // ========== + // 000 Test + NoError = 0000000, // 000-000-0 No Error + TestError0 = 0009000, // 000-900-0 Test Error 0 + TestError1 = 0009101, // 000-910-1 Test Error 1 + TestError2 = 0009202, // 000-920-2 Test Error 2 + TestError3 = 0009303, // 000-930-3 Test Error 3 + HnsUnload = 000_804_1, // 000-804-1 Unloaded By HnS + CheatDetected = 000_666_2, // 000-666-2 + SBDetected = 000_666_1, // 000-666-1 + + // ========== + LoadingHint = 000_999_3 // 000-999-3 Loading Hint } \ No newline at end of file diff --git a/Modules/ExtendedPlayerControl.cs b/Modules/ExtendedPlayerControl.cs index 1e716f55a..0527cf760 100644 --- a/Modules/ExtendedPlayerControl.cs +++ b/Modules/ExtendedPlayerControl.cs @@ -18,1801 +18,1800 @@ using UnityEngine; using static EHR.Translator; using static EHR.Utils; -namespace EHR +namespace EHR; + +internal static class ExtendedPlayerControl { - internal static class ExtendedPlayerControl + public const MurderResultFlags ResultFlags = MurderResultFlags.Succeeded; + + public static void SetRole(this PlayerControl player, RoleTypes role, bool canOverride = true) { - public const MurderResultFlags ResultFlags = MurderResultFlags.Succeeded; + player.StartCoroutine(player.CoSetRole(role, canOverride)); + } - public static void SetRole(this PlayerControl player, RoleTypes role, bool canOverride = true) + public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role, bool replaceAllAddons = false) + { + if (role < CustomRoles.NotAssigned) + Main.PlayerStates[player.PlayerId].SetMainRole(role); + else { - player.StartCoroutine(player.CoSetRole(role, canOverride)); + if (!Cleanser.CleansedCanGetAddon.GetBool() && player.Is(CustomRoles.Cleansed)) return; + + Main.PlayerStates[player.PlayerId].SetSubRole(role, replaceAllAddons); } - public static void RpcSetCustomRole(this PlayerControl player, CustomRoles role, bool replaceAllAddons = false) + if (AmongUsClient.Instance.AmHost) { - if (role < CustomRoles.NotAssigned) - Main.PlayerStates[player.PlayerId].SetMainRole(role); - else - { - if (!Cleanser.CleansedCanGetAddon.GetBool() && player.Is(CustomRoles.Cleansed)) return; - - Main.PlayerStates[player.PlayerId].SetSubRole(role, replaceAllAddons); - } - - if (AmongUsClient.Instance.AmHost) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCustomRole, SendOption.Reliable); - writer.Write(player.PlayerId); - writer.WritePacked((int)role); - writer.Write(replaceAllAddons); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } - - public static void RpcSetCustomRole(byte PlayerId, CustomRoles role) - { - if (AmongUsClient.Instance.AmHost) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCustomRole, SendOption.Reliable); - writer.Write(PlayerId); - writer.WritePacked((int)role); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } - - public static bool CanUseVent(this PlayerControl player) - { - return CanUseVent(player, GetClosestVent(player)?.Id ?? int.MaxValue); - } - - public static bool CanUseVent(this PlayerControl player, int ventId) - { - if (CustomGameMode.RoomRush.IsActiveOrIntegrated()) return true; - if (player.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) return false; - return GameStates.IsInTask && ((player.inVent && player.GetClosestVent()?.Id == ventId) || ((player.CanUseImpostorVentButton() || player.GetRoleTypes() == RoleTypes.Engineer) && Main.PlayerStates.Values.All(x => x.Role.CanUseVent(player, ventId)))); - } - - // Next 3: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/ExtendedPlayerControl.cs - public static Vent GetClosestVent(this PlayerControl player) - { - Vector2 pos = player.Pos(); - return ShipStatus.Instance?.AllVents?.Where(x => x != null).MinBy(x => Vector2.Distance(pos, x.transform.position)); - } - - public static List GetVentsFromClosest(this PlayerControl player) - { - Vector2 playerpos = player.transform.position; - List vents = ShipStatus.Instance.AllVents.ToList(); - vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); - - if ((player.walkingToVent || player.inVent) && vents[0] != null) - { - List nextvents = vents[0].NearbyVents.ToList(); - nextvents.RemoveAll(v => v == null); - nextvents.ForEach(v => vents.Remove(v)); - vents.InsertRange(0, nextvents); - } - - return vents; - } - - public static void RpcSetVentInteraction(this PlayerControl player) - { - VentilationSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast(), player); - } - - public static void SetChatVisible(this PlayerControl player) // Credit: NikoCat233 - { - if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost || GameStates.IsMeeting) return; - - if (player.IsHost()) - { - HudManager.Instance.Chat.SetVisible(true); - return; - } - - if (player.IsModClient()) - { - MessageWriter modsend = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShowChat, SendOption.Reliable, player.OwnerId); - modsend.WritePacked(player.OwnerId); - modsend.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(modsend); - return; - } - - uint customNetId = AmongUsClient.Instance.NetIdCnt++; - MessageWriter vanillasend = MessageWriter.Get(SendOption.Reliable); - vanillasend.StartMessage(6); - vanillasend.Write(AmongUsClient.Instance.GameId); - vanillasend.Write(player.OwnerId); - - vanillasend.StartMessage((byte)GameDataTag.SpawnFlag); - vanillasend.WritePacked(1); // 1 Meeting Hud Spawn id - vanillasend.WritePacked(-2); // Owned by host - vanillasend.Write((byte)SpawnFlags.None); - vanillasend.WritePacked(1); - vanillasend.WritePacked(customNetId); - - vanillasend.StartMessage(1); - vanillasend.WritePacked(0); - vanillasend.EndMessage(); - - vanillasend.EndMessage(); - vanillasend.EndMessage(); - - vanillasend.StartMessage(6); - vanillasend.Write(AmongUsClient.Instance.GameId); - vanillasend.Write(player.OwnerId); - vanillasend.StartMessage((byte)GameDataTag.RpcFlag); - vanillasend.WritePacked(customNetId); - vanillasend.Write((byte)RpcCalls.CloseMeeting); - vanillasend.EndMessage(); - vanillasend.EndMessage(); - - AmongUsClient.Instance.SendOrDisconnect(vanillasend); - vanillasend.Recycle(); - } - - public static void RpcExile(this PlayerControl player) - { - RPC.ExileAsync(player); - } - - public static ClientData GetClient(this PlayerControl player) - { - try - { - return AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Character.PlayerId == player.PlayerId); - } - catch - { - return null; - } - } - - public static int GetClientId(this PlayerControl player) - { - if (player == null) return -1; - - ClientData client = player.GetClient(); - return client?.Id ?? -1; - } - - public static CustomRoles GetCustomRole(this NetworkedPlayerInfo player) - { - return player == null || player.Object == null ? CustomRoles.Crewmate : player.Object.GetCustomRole(); - } - - /// - /// *Sub-roles cannot be obtained. - /// - public static CustomRoles GetCustomRole(this PlayerControl player) - { - if (player == null) - { - MethodBase callerMethod = new StackFrame(1, false).GetMethod(); - string callerMethodName = callerMethod?.Name; - Logger.Warn(callerMethod?.DeclaringType?.FullName + "." + callerMethodName + " tried to get a CustomRole, but the target was null.", "GetCustomRole"); - return CustomRoles.Crewmate; - } - - return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState State) ? State.MainRole : CustomRoles.Crewmate; - } - - public static List GetCustomSubRoles(this PlayerControl player) - { - if (GameStates.IsLobby) return []; - - if (player == null) - { - Logger.Warn("The player is null", "GetCustomSubRoles"); - return []; - } - - return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) ? state.SubRoles : []; - } - - public static CountTypes GetCountTypes(this PlayerControl player) - { - if (player == null) - { - StackFrame caller = new(1, false); - MethodBase callerMethod = caller.GetMethod(); - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - Logger.Warn($"{callerClassName}.{callerMethodName} tried to get a CountType, but the player was null", "GetCountTypes"); - return CountTypes.None; - } - - if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state)) return CountTypes.None; - - if (!player.IsConverted() && state.SubRoles.Contains(CustomRoles.Bloodlust)) return CountTypes.Bloodlust; - - return state.countTypes; - } - - public static void RpcSetNameEx(this PlayerControl player, string name) - { - try - { - if (player == null) - { - Logger.Warn("The player is null", "RpcSetNameEx"); - return; - } - - foreach (PlayerControl seer in Main.AllPlayerControls) - { - try - { - Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; - } - catch { } - } - - if (player.Data != null) Logger.Info($"Set: {player.Data.PlayerName} => {name} for Everyone", "RpcSetNameEx"); - - player.RpcSetName(name); - } - catch (Exception e) - { - ThrowException(e); - } - } - - public static void RpcSetNamePrivate(this PlayerControl player, string name, PlayerControl seer = null, bool force = false) - { - if (player == null || name == null || !AmongUsClient.Instance.AmHost) return; - - if (seer == null) seer = player; - - if (!force && Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] == name) return; - - Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; - Logger.Info($"Set: {player.Data?.PlayerName} => {name} for {seer.GetNameWithRole().RemoveHtmlTags()}", "RpcSetNamePrivate"); - - if (seer == null || player == null) return; - - int clientId = seer.GetClientId(); - if (clientId == -1) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, clientId); - writer.Write(seer.Data.NetId); - writer.Write(name); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - // By TommyXL - public static void RpcSetPetDesync(this PlayerControl player, string petId, PlayerControl seer) - { - int clientId = seer.GetClientId(); - if (clientId == -1) return; - - if (AmongUsClient.Instance.ClientId == clientId) - { - player.SetPet(petId); - return; - } - - player.Data.DefaultOutfit.PetSequenceId += 10; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetPetStr, SendOption.Reliable, clientId); - writer.Write(petId); - writer.Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, int clientId) - { - if (player == null) return; - - if (AmongUsClient.Instance.ClientId == clientId) - { - player.SetRole(role); - return; - } - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); - writer.Write((ushort)role); - writer.Write(true); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static (RoleTypes RoleType, CustomRoles CustomRole) GetRoleMap(this PlayerControl player, byte targetId = byte.MaxValue) - { - return Utils.GetRoleMap(player.PlayerId, targetId); - } - - // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs - public static void RpcRevive(this PlayerControl player) - { - if (player == null) return; - - if (!player.Data.IsDead) - { - Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); - return; - } - - GhostRolesManager.RemoveGhostRole(player.PlayerId); - Main.PlayerStates[player.PlayerId].IsDead = false; - Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; - player.RpcChangeRoleBasis(player.GetRoleMap().CustomRole, true); - player.ResetKillCooldown(); - player.SyncSettings(); - player.SetKillCooldown(); - player.RpcResetAbilityCooldown(); - player.SyncGeneralOptions(); - - if (Camouflage.IsCamouflage) Camouflage.RpcSetSkin(player); - - NotifyRoles(SpecifySeer: player, NoCache: true); - NotifyRoles(SpecifyTarget: player, NoCache: true); - } - - // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs - public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null || !player.IsAlive()) return; - - if (AntiBlackout.SkipTasks || ExileController.Instance) - { - StackTrace stackTrace = new(1, true); - MethodBase callerMethod = stackTrace.GetFrame(0)?.GetMethod(); - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - Logger.Warn($"{callerClassName}.{callerMethodName} tried to change the role basis of {player.GetNameWithRole()} during anti-blackout processing or ejection screen showing, delaying the code to run after these tasks are complete", "RpcChangeRoleBasis"); - Main.Instance.StartCoroutine(DelayBasisChange()); - return; - - IEnumerator DelayBasisChange() - { - while (AntiBlackout.SkipTasks || ExileController.Instance) yield return null; - yield return new WaitForSeconds(1f); - Logger.Msg($"Now that the anti-blackout processing or ejection screen showing is complete, the role basis of {player.GetNameWithRole()} will be changed", "RpcChangeRoleBasis"); - player.RpcChangeRoleBasis(newCustomRole, loggerRoleMap); - } - } - - byte playerId = player.PlayerId; - int playerClientId = player.GetClientId(); - CustomRoles playerRole = Utils.GetRoleMap(playerId).CustomRole; - RoleTypes newRoleType = newCustomRole.GetRoleTypes(); - RoleTypes remeberRoleType; - - newRoleType = Options.CurrentGameMode switch - { - CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => SpeedrunManager.CanKill.Contains(playerId) ? RoleTypes.Impostor : RoleTypes.Crewmate, - CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out KeyValuePair> kvp) => kvp.Key switch - { - CustomRoles.Bloodlust when newRoleType is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist => RoleTypes.Impostor, - CustomRoles.Nimble when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Engineer, - CustomRoles.Physicist when newRoleType == RoleTypes.Crewmate => RoleTypes.Scientist, - CustomRoles.Finder when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Tracker, - CustomRoles.Noisy when newRoleType == RoleTypes.Crewmate => RoleTypes.Noisemaker, - _ => newRoleType - }, - _ => newRoleType - }; - - bool oldRoleIsDesync = playerRole.IsDesyncRole(); - bool newRoleIsDesync = newCustomRole.IsDesyncRole(); - - CustomRoles newRoleVN = newCustomRole.GetVNRole(); - RoleTypes newRoleDY = newCustomRole.GetDYRole(); - - switch (oldRoleIsDesync, newRoleIsDesync) - { - // Desync role to normal role - case (true, false): - { - foreach (PlayerControl seer in Main.AllPlayerControls) - { - int seerClientId = seer.GetClientId(); - if (seerClientId == -1) continue; - - bool seerIsHost = seer.IsHost(); - bool self = playerId == seer.PlayerId; - - if (!self && seer.HasDesyncRole() && !seerIsHost) - remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; - else - remeberRoleType = newRoleType; - - // Set role type for seer - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seerClientId); - - if (self) continue; - - (RoleTypes seerRoleType, CustomRoles seerCustomRole) = seer.GetRoleMap(); - - if (seer.IsAlive()) - { - if (seerCustomRole.IsDesyncRole()) - remeberRoleType = seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist; - else - remeberRoleType = seerRoleType; - } - else - { - bool playerIsKiller = playerRole.IsImpostor(); - - remeberRoleType = RoleTypes.CrewmateGhost; - if (!playerIsKiller && seer.Is(Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; - - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist : seerRoleType, seerCustomRole); - seer.RpcSetRoleDesync(remeberRoleType, playerClientId); - continue; - } - - // Set role type for player - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole); - seer.RpcSetRoleDesync(remeberRoleType, playerClientId); - } - - break; - } - // Normal role to desync role - case (false, true): - { - foreach (PlayerControl seer in Main.AllPlayerControls) - { - int seerClientId = seer.GetClientId(); - if (seerClientId == -1) continue; - - bool self = playerId == seer.PlayerId; - - if (self) - { - remeberRoleType = player.IsHost() ? RoleTypes.Crewmate : RoleTypes.Impostor; - - // For Desync Shapeshifter - if (newRoleDY is RoleTypes.Shapeshifter) remeberRoleType = RoleTypes.Shapeshifter; - } - else - remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; - - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seerClientId); - - if (self) continue; - - CustomRoles seerCustomRole = seer.GetRoleMap().CustomRole; - - if (seer.IsAlive()) - remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; - else - { - remeberRoleType = RoleTypes.CrewmateGhost; - - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist, seerCustomRole); - seer.RpcSetRoleDesync(remeberRoleType, playerClientId); - continue; - } - - // Set role type for player - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole); - seer.RpcSetRoleDesync(remeberRoleType, playerClientId); - } - - break; - } - // Desync role to desync role - // Normal role to normal role - default: - { - bool playerIsDesync = player.HasDesyncRole(); - - foreach (PlayerControl seer in Main.AllPlayerControls) - { - int seerClientId = seer.GetClientId(); - if (seerClientId == -1) continue; - - if ((playerIsDesync || seer.HasDesyncRole()) && seer.PlayerId != playerId) - remeberRoleType = Utils.GetRoleMap(seer.PlayerId, playerId).RoleType; - else - remeberRoleType = newRoleType; - - StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); - player.RpcSetRoleDesync(remeberRoleType, seerClientId); - } - - break; - } - } - - if (loggerRoleMap) - { - foreach (PlayerControl seer in Main.AllPlayerControls) - { - NetworkedPlayerInfo seerData = seer.Data; - - foreach (PlayerControl target in Main.AllPlayerControls) - { - NetworkedPlayerInfo targetData = target.Data; - (RoleTypes roleType, CustomRoles customRole) = seer.GetRoleMap(targetData.PlayerId); - Logger.Info($"seer {seerData?.PlayerName}-{seerData?.PlayerId}, target {targetData.PlayerName}-{targetData.PlayerId} => {roleType}, {customRole}", "Role Map"); - } - } - } - - Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis"); - - Main.ChangedRole = true; - } - - // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/Utils.cs - public static void SyncGeneralOptions(this PlayerControl player) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions, SendOption.Reliable); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCustomRole, SendOption.Reliable); writer.Write(player.PlayerId); - writer.WritePacked((int)player.GetCustomRole()); - writer.Write(Main.PlayerStates[player.PlayerId].IsDead); - writer.WritePacked((int)Main.PlayerStates[player.PlayerId].deathReason); - writer.Write(Main.AllPlayerKillCooldown[player.PlayerId]); - writer.Write(Main.AllPlayerSpeed[player.PlayerId]); + writer.WritePacked((int)role); + writer.Write(replaceAllAddons); AmongUsClient.Instance.FinishRpcImmediately(writer); } + } - // https://github.com/0xDrMoe/TownofHost-Enhanced/pull/1189/files - public static void RpcExileDesync(this PlayerControl player, PlayerControl seer) + public static void RpcSetCustomRole(byte PlayerId, CustomRoles role) + { + if (AmongUsClient.Instance.AmHost) { - int clientId = seer.GetClientId(); - - if (AmongUsClient.Instance.ClientId == clientId) - { - player.Exiled(); - return; - } - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None, clientId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCustomRole, SendOption.Reliable); + writer.Write(PlayerId); + writer.WritePacked((int)role); AmongUsClient.Instance.FinishRpcImmediately(writer); } + } + + public static bool CanUseVent(this PlayerControl player) + { + return CanUseVent(player, GetClosestVent(player)?.Id ?? int.MaxValue); + } - // Next 8: https://github.com/0xDrMoe/TownofHost-Enhanced/blob/12487ce1aa7e4f5087f2300be452b5af7c04d1ff/Modules/ExtendedPlayerControl.cs#L239 - public static void RpcEnterVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) - { - if (physics == null) return; + public static bool CanUseVent(this PlayerControl player, int ventId) + { + if (CustomGameMode.RoomRush.IsActiveOrIntegrated()) return true; + if (player.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) return false; + return GameStates.IsInTask && ((player.inVent && player.GetClosestVent()?.Id == ventId) || ((player.CanUseImpostorVentButton() || player.GetRoleTypes() == RoleTypes.Engineer) && Main.PlayerStates.Values.All(x => x.Role.CanUseVent(player, ventId)))); + } - int clientId = seer.GetClientId(); + // Next 3: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/ExtendedPlayerControl.cs + public static Vent GetClosestVent(this PlayerControl player) + { + Vector2 pos = player.Pos(); + return ShipStatus.Instance?.AllVents?.Where(x => x != null).MinBy(x => Vector2.Distance(pos, x.transform.position)); + } - if (AmongUsClient.Instance.ClientId == clientId) - { - physics.StopAllCoroutines(); - physics.StartCoroutine(physics.CoEnterVent(ventId)); - return; - } + public static List GetVentsFromClosest(this PlayerControl player) + { + Vector2 playerpos = player.transform.position; + List vents = ShipStatus.Instance.AllVents.ToList(); + vents.Sort((v1, v2) => Vector2.Distance(playerpos, v1.transform.position).CompareTo(Vector2.Distance(playerpos, v2.transform.position))); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.EnterVent, SendOption.Reliable, seer.GetClientId()); - writer.WritePacked(ventId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + if ((player.walkingToVent || player.inVent) && vents[0] != null) + { + List nextvents = vents[0].NearbyVents.ToList(); + nextvents.RemoveAll(v => v == null); + nextvents.ForEach(v => vents.Remove(v)); + vents.InsertRange(0, nextvents); } - public static void RpcExitVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) - { - if (physics == null) return; + return vents; + } - int clientId = seer.GetClientId(); + public static void RpcSetVentInteraction(this PlayerControl player) + { + VentilationSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast(), player); + } - if (AmongUsClient.Instance.ClientId == clientId) - { - physics.StopAllCoroutines(); - physics.StartCoroutine(physics.CoExitVent(ventId)); - return; - } + public static void SetChatVisible(this PlayerControl player) // Credit: NikoCat233 + { + if (!GameStates.IsInGame || !AmongUsClient.Instance.AmHost || GameStates.IsMeeting) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.ExitVent, SendOption.Reliable, seer.GetClientId()); - writer.WritePacked(ventId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + if (player.IsHost()) + { + HudManager.Instance.Chat.SetVisible(true); + return; } - public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) + if (player.IsModClient()) { - if (physics == null) return; + MessageWriter modsend = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShowChat, SendOption.Reliable, player.OwnerId); + modsend.WritePacked(player.OwnerId); + modsend.Write(true); + AmongUsClient.Instance.FinishRpcImmediately(modsend); + return; + } - int clientId = seer.GetClientId(); + uint customNetId = AmongUsClient.Instance.NetIdCnt++; + MessageWriter vanillasend = MessageWriter.Get(SendOption.Reliable); + vanillasend.StartMessage(6); + vanillasend.Write(AmongUsClient.Instance.GameId); + vanillasend.Write(player.OwnerId); - if (AmongUsClient.Instance.ClientId == clientId) - { - physics.BootFromVent(ventId); - return; - } + vanillasend.StartMessage((byte)GameDataTag.SpawnFlag); + vanillasend.WritePacked(1); // 1 Meeting Hud Spawn id + vanillasend.WritePacked(-2); // Owned by host + vanillasend.Write((byte)SpawnFlags.None); + vanillasend.WritePacked(1); + vanillasend.WritePacked(customNetId); + + vanillasend.StartMessage(1); + vanillasend.WritePacked(0); + vanillasend.EndMessage(); + + vanillasend.EndMessage(); + vanillasend.EndMessage(); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, seer.GetClientId()); - writer.WritePacked(ventId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + vanillasend.StartMessage(6); + vanillasend.Write(AmongUsClient.Instance.GameId); + vanillasend.Write(player.OwnerId); + vanillasend.StartMessage((byte)GameDataTag.RpcFlag); + vanillasend.WritePacked(customNetId); + vanillasend.Write((byte)RpcCalls.CloseMeeting); + vanillasend.EndMessage(); + vanillasend.EndMessage(); - public static void RpcCheckVanishDesync(this PlayerControl player, PlayerControl seer) - { - if (AmongUsClient.Instance.ClientId == seer.GetClientId()) - { - player.CheckVanish(); - return; - } + AmongUsClient.Instance.SendOrDisconnect(vanillasend); + vanillasend.Recycle(); + } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckVanish, SendOption.None, seer.GetClientId()); - messageWriter.Write(0); // not used, lol - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } + public static void RpcExile(this PlayerControl player) + { + RPC.ExileAsync(player); + } - public static void RpcStartVanishDesync(this PlayerControl player, PlayerControl seer) + public static ClientData GetClient(this PlayerControl player) + { + try { - if (AmongUsClient.Instance.ClientId == seer.GetClientId()) - { - player.SetRoleInvisibility(true, false, true); - return; - } - - MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartVanish, SendOption.None, seer.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(msg); + return AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Character.PlayerId == player.PlayerId); } - - public static void RpcCheckAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) + catch { - if (AmongUsClient.Instance.ClientId == seer.GetClientId()) - { - player.CheckAppear(shouldAnimate); - return; - } + return null; + } + } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None, seer.GetClientId()); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } + public static int GetClientId(this PlayerControl player) + { + if (player == null) return -1; - public static void RpcStartAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) - { - if (AmongUsClient.Instance.ClientId == seer.GetClientId()) - { - player.SetRoleInvisibility(false, shouldAnimate, true); - return; - } + ClientData client = player.GetClient(); + return client?.Id ?? -1; + } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartAppear, SendOption.None, seer.GetClientId()); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } + public static CustomRoles GetCustomRole(this NetworkedPlayerInfo player) + { + return player == null || player.Object == null ? CustomRoles.Crewmate : player.Object.GetCustomRole(); + } - public static void RpcCheckAppear(this PlayerControl player, bool shouldAnimate) + /// + /// *Sub-roles cannot be obtained. + /// + public static CustomRoles GetCustomRole(this PlayerControl player) + { + if (player == null) { - player.CheckAppear(shouldAnimate); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + MethodBase callerMethod = new StackFrame(1, false).GetMethod(); + string callerMethodName = callerMethod?.Name; + Logger.Warn(callerMethod?.DeclaringType?.FullName + "." + callerMethodName + " tried to get a CustomRole, but the target was null.", "GetCustomRole"); + return CustomRoles.Crewmate; } + + return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState State) ? State.MainRole : CustomRoles.Crewmate; + } + + public static List GetCustomSubRoles(this PlayerControl player) + { + if (GameStates.IsLobby) return []; - public static bool HasDesyncRole(this PlayerControl player) + if (player == null) { - return player.Is(CustomRoles.Bloodlust) || player.GetCustomRole().IsDesyncRole(); + Logger.Warn("The player is null", "GetCustomSubRoles"); + return []; } - public static void KillFlash(this PlayerControl player) + return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) ? state.SubRoles : []; + } + + public static CountTypes GetCountTypes(this PlayerControl player) + { + if (player == null) { - if (GameStates.IsLobby) return; + StackFrame caller = new(1, false); + MethodBase callerMethod = caller.GetMethod(); + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + Logger.Warn($"{callerClassName}.{callerMethodName} tried to get a CountType, but the player was null", "GetCountTypes"); + return CountTypes.None; + } - //Kill flash (blackout + reactor flash) processing + if (!Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state)) return CountTypes.None; - SystemTypes systemtypes = (MapNames)Main.NormalOptions.MapId switch - { - MapNames.Polus => SystemTypes.Laboratory, - MapNames.Airship => SystemTypes.HeliSabotage, - _ => SystemTypes.Reactor - }; + if (!player.IsConverted() && state.SubRoles.Contains(CustomRoles.Bloodlust)) return CountTypes.Bloodlust; - bool ReactorCheck = IsActive(systemtypes); //Checking whether the reactor sabotage is active + return state.countTypes; + } - float Duration = Options.KillFlashDuration.GetFloat(); - if (ReactorCheck) Duration += 0.2f; // Extend blackout during reactor - - // Execution - Main.PlayerStates[player.PlayerId].IsBlackOut = true; // Blackout - - if (player.AmOwner) + public static void RpcSetNameEx(this PlayerControl player, string name) + { + try + { + if (player == null) { - FlashColor(new(1f, 0f, 0f, 0.3f)); - if (Constants.ShouldPlaySfx()) RPC.PlaySound(player.PlayerId, Sounds.KillSound); + Logger.Warn("The player is null", "RpcSetNameEx"); + return; } - else if (player.IsModClient()) + + foreach (PlayerControl seer in Main.AllPlayerControls) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.KillFlash, SendOption.Reliable, player.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(writer); + try + { + Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; + } + catch { } } - else if (!ReactorCheck) player.ReactorFlash(); // Reactor flash - player.MarkDirtySettings(); + if (player.Data != null) Logger.Info($"Set: {player.Data.PlayerName} => {name} for Everyone", "RpcSetNameEx"); - LateTask.New(() => - { - Main.PlayerStates[player.PlayerId].IsBlackOut = false; // Cancel blackout - player.MarkDirtySettings(); - }, Duration, "RemoveKillFlash"); + player.RpcSetName(name); + } + catch (Exception e) + { + ThrowException(e); } + } - public static void RpcGuardAndKill(this PlayerControl killer, PlayerControl target = null, bool forObserver = false, bool fromSetKCD = false) - { - if (!AmongUsClient.Instance.AmHost) - { - StackFrame caller = new(1, false); - MethodBase callerMethod = caller.GetMethod(); - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - Logger.Warn($"Modded non-host client activated RpcGuardAndKill from {callerClassName}.{callerMethodName}", "RpcGuardAndKill"); - return; - } + public static void RpcSetNamePrivate(this PlayerControl player, string name, PlayerControl seer = null, bool force = false) + { + if (player == null || name == null || !AmongUsClient.Instance.AmHost) return; - if (target == null) target = killer; + if (seer == null) seer = player; - // Check Observer - if (!forObserver && !MeetingStates.FirstMeeting) Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Observer) && killer.PlayerId != x.PlayerId).Do(x => x.RpcGuardAndKill(target, true)); + if (!force && Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] == name) return; - // Host - if (killer.AmOwner) killer.MurderPlayer(target, MurderResultFlags.FailedProtected); + Main.LastNotifyNames[(player.PlayerId, seer.PlayerId)] = name; + Logger.Info($"Set: {player.Data?.PlayerName} => {name} for {seer.GetNameWithRole().RemoveHtmlTags()}", "RpcSetNamePrivate"); - // Other Clients - if (!killer.IsHost()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable, killer.GetClientId()); - writer.WriteNetObject(target); - writer.Write((int)MurderResultFlags.FailedProtected); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + if (seer == null || player == null) return; - if (!fromSetKCD) killer.AddKillTimerToDict(true); - } + int clientId = seer.GetClientId(); + if (clientId == -1) return; - public static void RpcSpecificVanish(this PlayerControl player, PlayerControl seer) - { - /* - * Unluckily the vanishing animation cannot be disabled - * For vanila client seer side, the player must be with Phantom Role behavior, or the rpc will do nothing - */ - if (!AmongUsClient.Instance.AmHost) return; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetName, SendOption.Reliable, clientId); + writer.Write(seer.Data.NetId); + writer.Write(name); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartVanish, SendOption.None, seer.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(msg); - } + // By TommyXL + public static void RpcSetPetDesync(this PlayerControl player, string petId, PlayerControl seer) + { + int clientId = seer.GetClientId(); + if (clientId == -1) return; - public static void RpcSpecificAppear(this PlayerControl player, PlayerControl seer, bool shouldAnimate) + if (AmongUsClient.Instance.ClientId == clientId) { - /* - * For vanila client seer side, the player must be with Phantom Role behavior, or the rpc will do nothing - */ - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartAppear, SendOption.None, seer.GetClientId()); - msg.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(msg); + player.SetPet(petId); + return; } - public static bool HasAbilityCD(this PlayerControl pc) - { - return Main.AbilityCD.ContainsKey(pc.PlayerId); - } + player.Data.DefaultOutfit.PetSequenceId += 10; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetPetStr, SendOption.Reliable, clientId); + writer.Write(petId); + writer.Write(player.GetNextRpcSequenceId(RpcCalls.SetPetStr)); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public static void AddKCDAsAbilityCD(this PlayerControl pc) - { - AddAbilityCD(pc, (int)Math.Round(Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out float KCD) ? KCD : Options.DefaultKillCooldown)); - } + public static void RpcSetRoleDesync(this PlayerControl player, RoleTypes role, int clientId) + { + if (player == null) return; - public static void AddAbilityCD(this PlayerControl pc, bool includeDuration = true) + if (AmongUsClient.Instance.ClientId == clientId) { - Utils.AddAbilityCD(pc.GetCustomRole(), pc.PlayerId, includeDuration); + player.SetRole(role); + return; } - public static void AddAbilityCD(this PlayerControl pc, int CD) - { - Main.AbilityCD[pc.PlayerId] = (TimeStamp, CD); - SendRPC(CustomRPC.SyncAbilityCD, 1, pc.PlayerId, CD); - } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.SetRole, SendOption.Reliable, clientId); + writer.Write((ushort)role); + writer.Write(true); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public static void RemoveAbilityCD(this PlayerControl pc) - { - if (Main.AbilityCD.Remove(pc.PlayerId)) SendRPC(CustomRPC.SyncAbilityCD, 3, pc.PlayerId); - } + public static (RoleTypes RoleType, CustomRoles CustomRole) GetRoleMap(this PlayerControl player, byte targetId = byte.MaxValue) + { + return Utils.GetRoleMap(player.PlayerId, targetId); + } - public static float GetAbilityUseLimit(this PlayerControl pc) - { - return Main.AbilityUseLimit.GetValueOrDefault(pc.PlayerId, float.NaN); - } + // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs + public static void RpcRevive(this PlayerControl player) + { + if (player == null) return; - public static float GetAbilityUseLimit(this byte playerId) + if (!player.Data.IsDead) { - return Main.AbilityUseLimit.GetValueOrDefault(playerId, float.NaN); + Logger.Warn($"Invalid Revive for {player.GetRealName()} / Player was already alive? {!player.Data.IsDead}", "RpcRevive"); + return; } - public static void RpcRemoveAbilityUse(this PlayerControl pc, bool log = true) - { - float current = pc.GetAbilityUseLimit(); - if (float.IsNaN(current) || current <= 0f) return; - - pc.SetAbilityUseLimit(current - 1, log: log); - } + GhostRolesManager.RemoveGhostRole(player.PlayerId); + Main.PlayerStates[player.PlayerId].IsDead = false; + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.etc; + player.RpcChangeRoleBasis(player.GetRoleMap().CustomRole, true); + player.ResetKillCooldown(); + player.SyncSettings(); + player.SetKillCooldown(); + player.RpcResetAbilityCooldown(); + player.SyncGeneralOptions(); - public static void RpcIncreaseAbilityUseLimitBy(this PlayerControl pc, float get, bool log = true) - { - float current = pc.GetAbilityUseLimit(); - if (float.IsNaN(current)) return; + if (Camouflage.IsCamouflage) Camouflage.RpcSetSkin(player); - pc.SetAbilityUseLimit(current + get, log: log); - } + NotifyRoles(SpecifySeer: player, NoCache: true); + NotifyRoles(SpecifyTarget: player, NoCache: true); + } - public static void SetAbilityUseLimit(this PlayerControl pc, float limit, bool rpc = true, bool log = true) - { - pc.PlayerId.SetAbilityUseLimit(limit, rpc, log); - } + // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/ExtendedPlayerControl.cs + public static void RpcChangeRoleBasis(this PlayerControl player, CustomRoles newCustomRole, bool loggerRoleMap = false) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || player == null || !player.IsAlive()) return; - public static void SetAbilityUseLimit(this byte playerId, float limit, bool rpc = true, bool log = true) + if (AntiBlackout.SkipTasks || ExileController.Instance) { - limit = (float)Math.Round(limit, 1); - - if (float.IsNaN(limit) || limit is < 0f or > 100f || (Main.AbilityUseLimit.TryGetValue(playerId, out float beforeLimit) && Math.Abs(beforeLimit - limit) < 0.01f)) return; + StackTrace stackTrace = new(1, true); + MethodBase callerMethod = stackTrace.GetFrame(0)?.GetMethod(); + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + Logger.Warn($"{callerClassName}.{callerMethodName} tried to change the role basis of {player.GetNameWithRole()} during anti-blackout processing or ejection screen showing, delaying the code to run after these tasks are complete", "RpcChangeRoleBasis"); + Main.Instance.StartCoroutine(DelayBasisChange()); + return; - Main.AbilityUseLimit[playerId] = limit; - - if (AmongUsClient.Instance.AmHost && playerId.IsPlayerModClient() && !playerId.IsHost() && rpc) + IEnumerator DelayBasisChange() { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncAbilityUseLimit, SendOption.Reliable); - writer.Write(playerId); - writer.Write(limit); - AmongUsClient.Instance.FinishRpcImmediately(writer); + while (AntiBlackout.SkipTasks || ExileController.Instance) yield return null; + yield return new WaitForSeconds(1f); + Logger.Msg($"Now that the anti-blackout processing or ejection screen showing is complete, the role basis of {player.GetNameWithRole()} will be changed", "RpcChangeRoleBasis"); + player.RpcChangeRoleBasis(newCustomRole, loggerRoleMap); } + } - PlayerControl pc = GetPlayerById(playerId); - NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - if (log) Logger.Info($" {pc.GetNameWithRole()} => {Math.Round(limit, 1)}", "SetAbilityUseLimit"); - } + byte playerId = player.PlayerId; + int playerClientId = player.GetClientId(); + CustomRoles playerRole = Utils.GetRoleMap(playerId).CustomRole; + RoleTypes newRoleType = newCustomRole.GetRoleTypes(); + RoleTypes remeberRoleType; - public static void Suicide(this PlayerControl pc, PlayerState.DeathReason deathReason = PlayerState.DeathReason.Suicide, PlayerControl realKiller = null) + newRoleType = Options.CurrentGameMode switch { - if (!pc.IsAlive() || pc.Data.IsDead || !GameStates.IsInTask || ExileController.Instance) return; - - PlayerState state = Main.PlayerStates[pc.PlayerId]; - - if (realKiller != null && state.Role is SchrodingersCat cat) + CustomGameMode.Speedrun when newCustomRole == CustomRoles.Runner => SpeedrunManager.CanKill.Contains(playerId) ? RoleTypes.Impostor : RoleTypes.Crewmate, + CustomGameMode.Standard when StartGameHostPatch.BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out KeyValuePair> kvp) => kvp.Key switch { - cat.OnCheckMurderAsTarget(realKiller, pc); - return; - } - - state.deathReason = deathReason; - state.SetDead(); - - Medic.IsDead(pc); - - if (realKiller != null) + CustomRoles.Bloodlust when newRoleType is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist => RoleTypes.Impostor, + CustomRoles.Nimble when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Engineer, + CustomRoles.Physicist when newRoleType == RoleTypes.Crewmate => RoleTypes.Scientist, + CustomRoles.Finder when newRoleType is RoleTypes.Crewmate or RoleTypes.Scientist => RoleTypes.Tracker, + CustomRoles.Noisy when newRoleType == RoleTypes.Crewmate => RoleTypes.Noisemaker, + _ => newRoleType + }, + _ => newRoleType + }; + + bool oldRoleIsDesync = playerRole.IsDesyncRole(); + bool newRoleIsDesync = newCustomRole.IsDesyncRole(); + + CustomRoles newRoleVN = newCustomRole.GetVNRole(); + RoleTypes newRoleDY = newCustomRole.GetDYRole(); + + switch (oldRoleIsDesync, newRoleIsDesync) + { + // Desync role to normal role + case (true, false): { - pc.SetRealKiller(realKiller); - if (realKiller.Is(CustomRoles.Damocles)) Damocles.OnMurder(realKiller.PlayerId); + foreach (PlayerControl seer in Main.AllPlayerControls) + { + int seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + + bool seerIsHost = seer.IsHost(); + bool self = playerId == seer.PlayerId; + + if (!self && seer.HasDesyncRole() && !seerIsHost) + remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + else + remeberRoleType = newRoleType; + + // Set role type for seer + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); + + if (self) continue; + + (RoleTypes seerRoleType, CustomRoles seerCustomRole) = seer.GetRoleMap(); + + if (seer.IsAlive()) + { + if (seerCustomRole.IsDesyncRole()) + remeberRoleType = seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist; + else + remeberRoleType = seerRoleType; + } + else + { + bool playerIsKiller = playerRole.IsImpostor(); + + remeberRoleType = RoleTypes.CrewmateGhost; + if (!playerIsKiller && seer.Is(Team.Impostor)) remeberRoleType = RoleTypes.ImpostorGhost; + + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.IsDesyncRole() ? seerIsHost ? RoleTypes.Crewmate : RoleTypes.Scientist : seerRoleType, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); + continue; + } + + // Set role type for player + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); + } - IncreaseAbilityUseLimitOnKill(realKiller); + break; } + // Normal role to desync role + case (false, true): + { + foreach (PlayerControl seer in Main.AllPlayerControls) + { + int seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; - pc.Kill(pc); + bool self = playerId == seer.PlayerId; - if (Options.CurrentGameMode == CustomGameMode.NaturalDisasters) - NaturalDisasters.RecordDeath(pc, deathReason); - } + if (self) + { + remeberRoleType = player.IsHost() ? RoleTypes.Crewmate : RoleTypes.Impostor; - public static void SetKillCooldown(this PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) - { - if (player == null) return; + // For Desync Shapeshifter + if (newRoleDY is RoleTypes.Shapeshifter) remeberRoleType = RoleTypes.Shapeshifter; + } + else + remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; - Logger.Info($"{player.GetNameWithRole()}'s KCD set to {(Math.Abs(time - -1f) < 0.5f ? Main.AllPlayerKillCooldown[player.PlayerId] : time)}s", "SetKCD"); + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); - if (player.GetCustomRole().UsesPetInsteadOfKill()) - { - if (Math.Abs(time - -1f) < 0.5f) - player.AddKCDAsAbilityCD(); - else - player.AddAbilityCD((int)Math.Round(time)); + if (self) continue; - if (player.GetCustomRole() is not CustomRoles.Necromancer and not CustomRoles.Deathknight and not CustomRoles.Refugee and not CustomRoles.Sidekick) return; - } + CustomRoles seerCustomRole = seer.GetRoleMap().CustomRole; - if (!player.CanUseKillButton()) return; + if (seer.IsAlive()) + remeberRoleType = newRoleVN is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist; + else + { + remeberRoleType = RoleTypes.CrewmateGhost; - player.AddKillTimerToDict(CD: time); - if (target == null) target = player; + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (seerCustomRole.GetVNRole() is CustomRoles.Noisemaker ? RoleTypes.Noisemaker : RoleTypes.Scientist, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); + continue; + } - if (time >= 0f) - Main.AllPlayerKillCooldown[player.PlayerId] = time * 2; - else - Main.AllPlayerKillCooldown[player.PlayerId] *= 2; + // Set role type for player + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(playerId, seer.PlayerId)] = (remeberRoleType, seerCustomRole); + seer.RpcSetRoleDesync(remeberRoleType, playerClientId); + } - if (player.Is(CustomRoles.Glitch) && Main.PlayerStates[player.PlayerId].Role is Glitch gc) - { - gc.LastKill = TimeStamp + ((int)(time / 2) - Glitch.KillCooldown.GetInt()); - gc.KCDTimer = (int)(time / 2); + break; } - else if (forceAnime || !player.IsModClient() || !Options.DisableShieldAnimations.GetBool()) + // Desync role to desync role + // Normal role to normal role + default: { - player.SyncSettings(); - player.RpcGuardAndKill(target, fromSetKCD: true); - } - else - { - time = Main.AllPlayerKillCooldown[player.PlayerId] / 2; + bool playerIsDesync = player.HasDesyncRole(); - if (player.AmOwner) - PlayerControl.LocalPlayer.SetKillTimer(time); - else + foreach (PlayerControl seer in Main.AllPlayerControls) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillTimer, SendOption.Reliable, player.GetClientId()); - writer.Write(time); - AmongUsClient.Instance.FinishRpcImmediately(writer); + int seerClientId = seer.GetClientId(); + if (seerClientId == -1) continue; + + if ((playerIsDesync || seer.HasDesyncRole()) && seer.PlayerId != playerId) + remeberRoleType = Utils.GetRoleMap(seer.PlayerId, playerId).RoleType; + else + remeberRoleType = newRoleType; + + StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seer.PlayerId, playerId)] = (remeberRoleType, newCustomRole); + player.RpcSetRoleDesync(remeberRoleType, seerClientId); } - Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Observer) && target.PlayerId != x.PlayerId).Do(x => x.RpcGuardAndKill(target, true, true)); + break; } - - if (player.GetCustomRole() is not CustomRoles.Inhibitor and not CustomRoles.Saboteur) player.ResetKillCooldown(); } - public static void RpcSpecificMurderPlayer(this PlayerControl killer, PlayerControl target = null) + if (loggerRoleMap) { - if (target == null) target = killer; + foreach (PlayerControl seer in Main.AllPlayerControls) + { + NetworkedPlayerInfo seerData = seer.Data; - if (killer.AmOwner) - killer.MurderPlayer(target, ResultFlags); - else - { - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable, killer.GetClientId()); - messageWriter.WriteNetObject(target); - messageWriter.Write((byte)ResultFlags); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + foreach (PlayerControl target in Main.AllPlayerControls) + { + NetworkedPlayerInfo targetData = target.Data; + (RoleTypes roleType, CustomRoles customRole) = seer.GetRoleMap(targetData.PlayerId); + Logger.Info($"seer {seerData?.PlayerName}-{seerData?.PlayerId}, target {targetData.PlayerName}-{targetData.PlayerId} => {roleType}, {customRole}", "Role Map"); + } } } + + Logger.Info($"{player.GetNameWithRole()}'s role basis was changed to {newRoleType} ({newCustomRole}) (from role: {playerRole}) - oldRoleIsDesync: {oldRoleIsDesync}, newRoleIsDesync: {newRoleIsDesync}", "RpcChangeRoleBasis"); + + Main.ChangedRole = true; + } - public static void RpcResetAbilityCooldown(this PlayerControl target) + // https://github.com/Ultradragon005/TownofHost-Enhanced/blob/ea5f1e8ea87e6c19466231c305d6d36d511d5b2d/Modules/Utils.cs + public static void SyncGeneralOptions(this PlayerControl player) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGeneralOptions, SendOption.Reliable); + writer.Write(player.PlayerId); + writer.WritePacked((int)player.GetCustomRole()); + writer.Write(Main.PlayerStates[player.PlayerId].IsDead); + writer.WritePacked((int)Main.PlayerStates[player.PlayerId].deathReason); + writer.Write(Main.AllPlayerKillCooldown[player.PlayerId]); + writer.Write(Main.AllPlayerSpeed[player.PlayerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + // https://github.com/0xDrMoe/TownofHost-Enhanced/pull/1189/files + public static void RpcExileDesync(this PlayerControl player, PlayerControl seer) + { + int clientId = seer.GetClientId(); + + if (AmongUsClient.Instance.ClientId == clientId) { - if (!AmongUsClient.Instance.AmHost) return; + player.Exiled(); + return; + } + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.None, clientId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - Logger.Info($"Reset Ability Cooldown for {target.name} (ID: {target.PlayerId})", "RpcResetAbilityCooldown"); + // Next 8: https://github.com/0xDrMoe/TownofHost-Enhanced/blob/12487ce1aa7e4f5087f2300be452b5af7c04d1ff/Modules/ExtendedPlayerControl.cs#L239 + public static void RpcEnterVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) + { + if (physics == null) return; - if (target.Is(CustomRoles.Glitch) && Main.PlayerStates[target.PlayerId].Role is Glitch gc) - { - gc.LastHack = TimeStamp; - gc.LastMimic = TimeStamp; - gc.MimicCDTimer = 10; - gc.HackCDTimer = 10; - } - else if (PlayerControl.LocalPlayer == target) - { - // If target is host - PlayerControl.LocalPlayer.Data.Role.SetCooldown(); - } - else - { - // If target is not host - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.ProtectPlayer, SendOption.None, target.GetClientId()); - writer.WriteNetObject(target); - writer.Write(0); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - /* - When a player guards someone, ability cooldowns are reset regardless of that player's role. - Due to the addition of logs, it is no longer possible to guard no one, so it has been changed to the player guarding themselves for 0 seconds instead. - This change disables Guardian Angel as a position. - Reset host cooldown directly. - */ + int clientId = seer.GetClientId(); + + if (AmongUsClient.Instance.ClientId == clientId) + { + physics.StopAllCoroutines(); + physics.StartCoroutine(physics.CoEnterVent(ventId)); + return; } + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.EnterVent, SendOption.Reliable, seer.GetClientId()); + writer.WritePacked(ventId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void RpcExitVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) + { + if (physics == null) return; + + int clientId = seer.GetClientId(); - public static void RpcDesyncRepairSystem(this PlayerControl target, SystemTypes systemType, int amount) + if (AmongUsClient.Instance.ClientId == clientId) { - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, target.GetClientId()); - messageWriter.Write((byte)systemType); - messageWriter.WriteNetObject(target); - messageWriter.Write((byte)amount); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + physics.StopAllCoroutines(); + physics.StartCoroutine(physics.CoExitVent(ventId)); + return; } + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.ExitVent, SendOption.Reliable, seer.GetClientId()); + writer.WritePacked(ventId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public static void MarkDirtySettings(this PlayerControl player) + public static void RpcBootFromVentDesync(this PlayerPhysics physics, int ventId, PlayerControl seer) + { + if (physics == null) return; + + int clientId = seer.GetClientId(); + + if (AmongUsClient.Instance.ClientId == clientId) { - PlayerGameOptionsSender.SetDirty(player.PlayerId); + physics.BootFromVent(ventId); + return; } + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.BootFromVent, SendOption.Reliable, seer.GetClientId()); + writer.WritePacked(ventId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public static void SyncSettings(this PlayerControl player) + public static void RpcCheckVanishDesync(this PlayerControl player, PlayerControl seer) + { + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) { - PlayerGameOptionsSender.SetDirty(player.PlayerId); - GameOptionsSender.SendAllGameOptions(); + player.CheckVanish(); + return; } - public static TaskState GetTaskState(this PlayerControl player) + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckVanish, SendOption.None, seer.GetClientId()); + messageWriter.Write(0); // not used, lol + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + + public static void RpcStartVanishDesync(this PlayerControl player, PlayerControl seer) + { + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) { - return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) ? state.TaskState : new(); + player.SetRoleInvisibility(true, false, true); + return; } + + MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartVanish, SendOption.None, seer.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(msg); + } - public static bool IsNonHostModClient(this PlayerControl pc) + public static void RpcCheckAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) + { + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) { - return pc.IsModClient() && !pc.IsHost(); + player.CheckAppear(shouldAnimate); + return; } - public static string GetDisplayRoleName(this PlayerControl player, bool pure = false, bool seeTargetBetrayalAddons = false) + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None, seer.GetClientId()); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + + public static void RpcStartAppearDesync(this PlayerControl player, bool shouldAnimate, PlayerControl seer) + { + if (AmongUsClient.Instance.ClientId == seer.GetClientId()) { - return Utils.GetDisplayRoleName(player.PlayerId, pure, seeTargetBetrayalAddons); + player.SetRoleInvisibility(false, shouldAnimate, true); + return; } + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartAppear, SendOption.None, seer.GetClientId()); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + + public static void RpcCheckAppear(this PlayerControl player, bool shouldAnimate) + { + player.CheckAppear(shouldAnimate); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.CheckAppear, SendOption.None); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } - public static string GetSubRoleNames(this PlayerControl player, bool forUser = false) + public static bool HasDesyncRole(this PlayerControl player) + { + return player.Is(CustomRoles.Bloodlust) || player.GetCustomRole().IsDesyncRole(); + } + + public static void KillFlash(this PlayerControl player) + { + if (GameStates.IsLobby) return; + + //Kill flash (blackout + reactor flash) processing + + SystemTypes systemtypes = (MapNames)Main.NormalOptions.MapId switch { - List SubRoles = Main.PlayerStates[player.PlayerId].SubRoles; - if (SubRoles.Count == 0) return string.Empty; + MapNames.Polus => SystemTypes.Laboratory, + MapNames.Airship => SystemTypes.HeliSabotage, + _ => SystemTypes.Reactor + }; - StringBuilder sb = new(); + bool ReactorCheck = IsActive(systemtypes); //Checking whether the reactor sabotage is active - foreach (CustomRoles role in SubRoles) - { - if (role == CustomRoles.NotAssigned) continue; + float Duration = Options.KillFlashDuration.GetFloat(); + if (ReactorCheck) Duration += 0.2f; // Extend blackout during reactor - sb.Append($"{ColorString(Color.white, "\n")}{GetRoleName(role, forUser)}"); - } + // Execution + Main.PlayerStates[player.PlayerId].IsBlackOut = true; // Blackout - return sb.ToString(); + if (player.AmOwner) + { + FlashColor(new(1f, 0f, 0f, 0.3f)); + if (Constants.ShouldPlaySfx()) RPC.PlaySound(player.PlayerId, Sounds.KillSound); } + else if (player.IsModClient()) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.KillFlash, SendOption.Reliable, player.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + else if (!ReactorCheck) player.ReactorFlash(); // Reactor flash - public static string GetAllRoleName(this PlayerControl player, bool forUser = true) + player.MarkDirtySettings(); + + LateTask.New(() => { - if (!player) return null; + Main.PlayerStates[player.PlayerId].IsBlackOut = false; // Cancel blackout + player.MarkDirtySettings(); + }, Duration, "RemoveKillFlash"); + } - string text = GetRoleName(player.GetCustomRole(), forUser); - text += player.GetSubRoleNames(forUser); - return text; + public static void RpcGuardAndKill(this PlayerControl killer, PlayerControl target = null, bool forObserver = false, bool fromSetKCD = false) + { + if (!AmongUsClient.Instance.AmHost) + { + StackFrame caller = new(1, false); + MethodBase callerMethod = caller.GetMethod(); + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + Logger.Warn($"Modded non-host client activated RpcGuardAndKill from {callerClassName}.{callerMethodName}", "RpcGuardAndKill"); + return; } - public static string GetNameWithRole(this PlayerControl player, bool forUser = false) + if (target == null) target = killer; + + // Check Observer + if (!forObserver && !MeetingStates.FirstMeeting) Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Observer) && killer.PlayerId != x.PlayerId).Do(x => x.RpcGuardAndKill(target, true)); + + // Host + if (killer.AmOwner) killer.MurderPlayer(target, MurderResultFlags.FailedProtected); + + // Other Clients + if (!killer.IsHost()) { - return $"{player?.Data?.PlayerName}" + (GameStates.IsInGame && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.MoveAndStop and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.AllInOne ? $" ({player?.GetAllRoleName(forUser).RemoveHtmlTags().Replace('\n', ' ')})" : string.Empty); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable, killer.GetClientId()); + writer.WriteNetObject(target); + writer.Write((int)MurderResultFlags.FailedProtected); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static string GetRoleColorCode(this PlayerControl player) - { - return Utils.GetRoleColorCode(player.GetCustomRole()); + if (!fromSetKCD) killer.AddKillTimerToDict(true); + } + + public static void RpcSpecificVanish(this PlayerControl player, PlayerControl seer) + { + /* + * Unluckily the vanishing animation cannot be disabled + * For vanila client seer side, the player must be with Phantom Role behavior, or the rpc will do nothing + */ + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartVanish, SendOption.None, seer.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(msg); + } + + public static void RpcSpecificAppear(this PlayerControl player, PlayerControl seer, bool shouldAnimate) + { + /* + * For vanila client seer side, the player must be with Phantom Role behavior, or the rpc will do nothing + */ + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter msg = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.StartAppear, SendOption.None, seer.GetClientId()); + msg.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(msg); + } + + public static bool HasAbilityCD(this PlayerControl pc) + { + return Main.AbilityCD.ContainsKey(pc.PlayerId); + } + + public static void AddKCDAsAbilityCD(this PlayerControl pc) + { + AddAbilityCD(pc, (int)Math.Round(Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out float KCD) ? KCD : Options.DefaultKillCooldown)); + } + + public static void AddAbilityCD(this PlayerControl pc, bool includeDuration = true) + { + Utils.AddAbilityCD(pc.GetCustomRole(), pc.PlayerId, includeDuration); + } + + public static void AddAbilityCD(this PlayerControl pc, int CD) + { + Main.AbilityCD[pc.PlayerId] = (TimeStamp, CD); + SendRPC(CustomRPC.SyncAbilityCD, 1, pc.PlayerId, CD); + } + + public static void RemoveAbilityCD(this PlayerControl pc) + { + if (Main.AbilityCD.Remove(pc.PlayerId)) SendRPC(CustomRPC.SyncAbilityCD, 3, pc.PlayerId); + } + + public static float GetAbilityUseLimit(this PlayerControl pc) + { + return Main.AbilityUseLimit.GetValueOrDefault(pc.PlayerId, float.NaN); + } + + public static float GetAbilityUseLimit(this byte playerId) + { + return Main.AbilityUseLimit.GetValueOrDefault(playerId, float.NaN); + } + + public static void RpcRemoveAbilityUse(this PlayerControl pc, bool log = true) + { + float current = pc.GetAbilityUseLimit(); + if (float.IsNaN(current) || current <= 0f) return; + + pc.SetAbilityUseLimit(current - 1, log: log); + } + + public static void RpcIncreaseAbilityUseLimitBy(this PlayerControl pc, float get, bool log = true) + { + float current = pc.GetAbilityUseLimit(); + if (float.IsNaN(current)) return; + + pc.SetAbilityUseLimit(current + get, log: log); + } + + public static void SetAbilityUseLimit(this PlayerControl pc, float limit, bool rpc = true, bool log = true) + { + pc.PlayerId.SetAbilityUseLimit(limit, rpc, log); + } + + public static void SetAbilityUseLimit(this byte playerId, float limit, bool rpc = true, bool log = true) + { + limit = (float)Math.Round(limit, 1); + + if (float.IsNaN(limit) || limit is < 0f or > 100f || (Main.AbilityUseLimit.TryGetValue(playerId, out float beforeLimit) && Math.Abs(beforeLimit - limit) < 0.01f)) return; + + Main.AbilityUseLimit[playerId] = limit; + + if (AmongUsClient.Instance.AmHost && playerId.IsPlayerModClient() && !playerId.IsHost() && rpc) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncAbilityUseLimit, SendOption.Reliable); + writer.Write(playerId); + writer.Write(limit); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - public static Color GetRoleColor(this PlayerControl player) + PlayerControl pc = GetPlayerById(playerId); + NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (log) Logger.Info($" {pc.GetNameWithRole()} => {Math.Round(limit, 1)}", "SetAbilityUseLimit"); + } + + public static void Suicide(this PlayerControl pc, PlayerState.DeathReason deathReason = PlayerState.DeathReason.Suicide, PlayerControl realKiller = null) + { + if (!pc.IsAlive() || pc.Data.IsDead || !GameStates.IsInTask || ExileController.Instance) return; + + PlayerState state = Main.PlayerStates[pc.PlayerId]; + + if (realKiller != null && state.Role is SchrodingersCat cat) { - return Utils.GetRoleColor(player.GetCustomRole()); + cat.OnCheckMurderAsTarget(realKiller, pc); + return; } - public static void ResetPlayerCam(this PlayerControl pc, float delay = 0f) + state.deathReason = deathReason; + state.SetDead(); + + Medic.IsDead(pc); + + if (realKiller != null) { - if (pc == null || !AmongUsClient.Instance.AmHost || pc.AmOwner) return; + pc.SetRealKiller(realKiller); + if (realKiller.Is(CustomRoles.Damocles)) Damocles.OnMurder(realKiller.PlayerId); - SystemTypes systemtypes = (MapNames)Main.NormalOptions.MapId switch - { - MapNames.Polus => SystemTypes.Laboratory, - MapNames.Airship => SystemTypes.HeliSabotage, - _ => SystemTypes.Reactor - }; + IncreaseAbilityUseLimitOnKill(realKiller); + } - LateTask.New(() => pc.RpcDesyncRepairSystem(systemtypes, 128), 0f + delay, "Reactor Desync"); + pc.Kill(pc); - LateTask.New(() => pc.RpcSpecificMurderPlayer(), 0.2f + delay, "Murder To Reset Cam"); + if (Options.CurrentGameMode == CustomGameMode.NaturalDisasters) + NaturalDisasters.RecordDeath(pc, deathReason); + } - LateTask.New(() => - { - pc.RpcDesyncRepairSystem(systemtypes, 16); + public static void SetKillCooldown(this PlayerControl player, float time = -1f, PlayerControl target = null, bool forceAnime = false) + { + if (player == null) return; - if (Main.NormalOptions.MapId == 4) // Airship only - pc.RpcDesyncRepairSystem(systemtypes, 17); - }, 0.4f + delay, "Fix Desync Reactor"); - } + Logger.Info($"{player.GetNameWithRole()}'s KCD set to {(Math.Abs(time - -1f) < 0.5f ? Main.AllPlayerKillCooldown[player.PlayerId] : time)}s", "SetKCD"); - public static void ReactorFlash(this PlayerControl pc, float delay = 0f) + if (player.GetCustomRole().UsesPetInsteadOfKill()) { - if (pc == null) return; + if (Math.Abs(time - -1f) < 0.5f) + player.AddKCDAsAbilityCD(); + else + player.AddAbilityCD((int)Math.Round(time)); - Logger.Info($"Reactor Flash for {pc.GetNameWithRole()}", "ReactorFlash"); + if (player.GetCustomRole() is not CustomRoles.Necromancer and not CustomRoles.Deathknight and not CustomRoles.Refugee and not CustomRoles.Sidekick) return; + } - SystemTypes systemtypes = (MapNames)Main.NormalOptions.MapId switch - { - MapNames.Polus => SystemTypes.Laboratory, - MapNames.Airship => SystemTypes.HeliSabotage, - _ => SystemTypes.Reactor - }; + if (!player.CanUseKillButton()) return; - float FlashDuration = Options.KillFlashDuration.GetFloat(); + player.AddKillTimerToDict(CD: time); + if (target == null) target = player; - pc.RpcDesyncRepairSystem(systemtypes, 128); + if (time >= 0f) + Main.AllPlayerKillCooldown[player.PlayerId] = time * 2; + else + Main.AllPlayerKillCooldown[player.PlayerId] *= 2; - LateTask.New(() => - { - pc.RpcDesyncRepairSystem(systemtypes, 16); - - if (Main.NormalOptions.MapId == 4) // on Airship - pc.RpcDesyncRepairSystem(systemtypes, 17); - }, FlashDuration + delay, "Fix Desync Reactor"); + if (player.Is(CustomRoles.Glitch) && Main.PlayerStates[player.PlayerId].Role is Glitch gc) + { + gc.LastKill = TimeStamp + ((int)(time / 2) - Glitch.KillCooldown.GetInt()); + gc.KCDTimer = (int)(time / 2); + } + else if (forceAnime || !player.IsModClient() || !Options.DisableShieldAnimations.GetBool()) + { + player.SyncSettings(); + player.RpcGuardAndKill(target, fromSetKCD: true); } + else + { + time = Main.AllPlayerKillCooldown[player.PlayerId] / 2; - public static string GetRealName(this PlayerControl player, bool isMeeting = false) - { - try + if (player.AmOwner) + PlayerControl.LocalPlayer.SetKillTimer(time); + else { - string name = isMeeting ? player.Data.PlayerName : player.name; - return name.RemoveHtmlTags(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillTimer, SendOption.Reliable, player.GetClientId()); + writer.Write(time); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - catch (NullReferenceException nullReferenceException) - { - Logger.Error($"{nullReferenceException.Message} - player is null? {player == null}", "GetRealName"); - return string.Empty; - } - catch (Exception exception) - { - ThrowException(exception); - return string.Empty; - } + + Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Observer) && target.PlayerId != x.PlayerId).Do(x => x.RpcGuardAndKill(target, true, true)); } - public static bool IsRoleBlocked(this PlayerControl pc) + if (player.GetCustomRole() is not CustomRoles.Inhibitor and not CustomRoles.Saboteur) player.ResetKillCooldown(); + } + + public static void RpcSpecificMurderPlayer(this PlayerControl killer, PlayerControl target = null) + { + if (target == null) target = killer; + + if (killer.AmOwner) + killer.MurderPlayer(target, ResultFlags); + else { - return RoleBlockManager.RoleBlockedPlayers.ContainsKey(pc.PlayerId); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable, killer.GetClientId()); + messageWriter.WriteNetObject(target); + messageWriter.Write((byte)ResultFlags); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); } + } + + public static void RpcResetAbilityCooldown(this PlayerControl target) + { + if (!AmongUsClient.Instance.AmHost) return; - public static bool IsPlayerRoleBlocked(this byte id) - { - return RoleBlockManager.RoleBlockedPlayers.ContainsKey(id); - } + Logger.Info($"Reset Ability Cooldown for {target.name} (ID: {target.PlayerId})", "RpcResetAbilityCooldown"); - public static void BlockRole(this PlayerControl pc, float duration) + if (target.Is(CustomRoles.Glitch) && Main.PlayerStates[target.PlayerId].Role is Glitch gc) { - RoleBlockManager.AddRoleBlock(pc, duration); + gc.LastHack = TimeStamp; + gc.LastMimic = TimeStamp; + gc.MimicCDTimer = 10; + gc.HackCDTimer = 10; } - - public static bool IsHost(this InnerNetObject ino) + else if (PlayerControl.LocalPlayer == target) { - return ino.OwnerId == AmongUsClient.Instance.HostId; + // If target is host + PlayerControl.LocalPlayer.Data.Role.SetCooldown(); } - - public static bool IsHost(this byte id) + else { - return GetPlayerById(id)?.OwnerId == AmongUsClient.Instance.HostId; + // If target is not host + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.ProtectPlayer, SendOption.None, target.GetClientId()); + writer.WriteNetObject(target); + writer.Write(0); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - - public static void RpcShowScanAnimationDesync(this PlayerControl target, PlayerControl seer, bool IsActive) - { - if (!AmongUsClient.Instance.AmHost) - { - StackFrame caller = new(1, false); - MethodBase callerMethod = caller.GetMethod(); - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - Logger.Warn($"Modded non-host client activated RpcShowScanAnimation from {callerClassName}.{callerMethodName}", "RpcShowScanAnimation"); - return; - } + /* + When a player guards someone, ability cooldowns are reset regardless of that player's role. + Due to the addition of logs, it is no longer possible to guard no one, so it has been changed to the player guarding themselves for 0 seconds instead. + This change disables Guardian Angel as a position. + Reset host cooldown directly. + */ + } - if (target == null || seer == null) return; + public static void RpcDesyncRepairSystem(this PlayerControl target, SystemTypes systemType, int amount) + { + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, target.GetClientId()); + messageWriter.Write((byte)systemType); + messageWriter.WriteNetObject(target); + messageWriter.Write((byte)amount); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } - int seerClientId = seer.GetClientId(); - if (seerClientId == -1) return; + public static void MarkDirtySettings(this PlayerControl player) + { + PlayerGameOptionsSender.SetDirty(player.PlayerId); + } - byte cnt = ++target.scannerCount; + public static void SyncSettings(this PlayerControl player) + { + PlayerGameOptionsSender.SetDirty(player.PlayerId); + GameOptionsSender.SendAllGameOptions(); + } - if (AmongUsClient.Instance.ClientId == seerClientId) - { - target.SetScanner(IsActive, cnt); - return; - } + public static TaskState GetTaskState(this PlayerControl player) + { + return Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) ? state.TaskState : new(); + } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.SetScanner, SendOption.Reliable, seerClientId); - messageWriter.Write(IsActive); - messageWriter.Write(cnt); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - target.scannerCount = cnt; - } + public static bool IsNonHostModClient(this PlayerControl pc) + { + return pc.IsModClient() && !pc.IsHost(); + } - public static bool HasKillButton(this PlayerControl pc) - { - CustomRoles role = pc.GetCustomRole(); - if (pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; + public static string GetDisplayRoleName(this PlayerControl player, bool pure = false, bool seeTargetBetrayalAddons = false) + { + return Utils.GetDisplayRoleName(player.PlayerId, pure, seeTargetBetrayalAddons); + } - if (role.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom) return true; + public static string GetSubRoleNames(this PlayerControl player, bool forUser = false) + { + List SubRoles = Main.PlayerStates[player.PlayerId].SubRoles; + if (SubRoles.Count == 0) return string.Empty; - if (pc.GetRoleTypes() is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) return true; + StringBuilder sb = new(); - if (pc.Is(CustomRoles.Bloodlust)) return true; + foreach (CustomRoles role in SubRoles) + { + if (role == CustomRoles.NotAssigned) continue; - return pc.Is(CustomRoleTypes.Impostor) || pc.IsNeutralKiller() || role.IsTasklessCrewmate(); + sb.Append($"{ColorString(Color.white, "\n")}{GetRoleName(role, forUser)}"); } - - public static bool CanUseKillButton(this PlayerControl pc) - { - if (!pc.IsAlive()) return false; - switch (Options.CurrentGameMode) - { - case CustomGameMode.HotPotato or CustomGameMode.MoveAndStop or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush: - case CustomGameMode.Speedrun when !SpeedrunManager.CanKill.Contains(pc.PlayerId): - return false; - case CustomGameMode.CaptureTheFlag: - return true; - case CustomGameMode.AllInOne: - return !AllInOneGameMode.Taskers.Contains(pc.PlayerId); - } + return sb.ToString(); + } - if (Mastermind.ManipulatedPlayers.ContainsKey(pc.PlayerId)) return true; + public static string GetAllRoleName(this PlayerControl player, bool forUser = true) + { + if (!player) return null; - if (Penguin.IsVictim(pc)) return false; + string text = GetRoleName(player.GetCustomRole(), forUser); + text += player.GetSubRoleNames(forUser); + return text; + } - if (Pelican.IsEaten(pc.PlayerId)) return false; + public static string GetNameWithRole(this PlayerControl player, bool forUser = false) + { + return $"{player?.Data?.PlayerName}" + (GameStates.IsInGame && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.MoveAndStop and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.AllInOne ? $" ({player?.GetAllRoleName(forUser).RemoveHtmlTags().Replace('\n', ' ')})" : string.Empty); + } - if (pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; + public static string GetRoleColorCode(this PlayerControl player) + { + return Utils.GetRoleColorCode(player.GetCustomRole()); + } - if (pc.Is(CustomRoles.Bloodlust)) return true; + public static Color GetRoleColor(this PlayerControl player) + { + return Utils.GetRoleColor(player.GetCustomRole()); + } - return pc.GetCustomRole() switch - { - // SoloKombat - CustomRoles.KB_Normal => pc.SoloAlive(), - // FFA - CustomRoles.Killer => pc.IsAlive(), - // Move And Stop - CustomRoles.Tasker => false, - // Hot Potato - CustomRoles.Potato => false, - // Speedrun - CustomRoles.Runner => SpeedrunManager.CanKill.Contains(pc.PlayerId), - // Hide And Seek - CustomRoles.Seeker => true, - CustomRoles.Hider => false, - CustomRoles.Troll => false, - CustomRoles.Fox => false, - CustomRoles.Jumper => false, - CustomRoles.Detector => false, - CustomRoles.Jet => false, - CustomRoles.Dasher => true, - CustomRoles.Locator => true, - CustomRoles.Venter => true, - CustomRoles.Agent => true, - CustomRoles.Taskinator => false, - - _ => Main.PlayerStates.TryGetValue(pc.PlayerId, out PlayerState state) && state.Role.CanUseKillButton(pc) - }; - } + public static void ResetPlayerCam(this PlayerControl pc, float delay = 0f) + { + if (pc == null || !AmongUsClient.Instance.AmHost || pc.AmOwner) return; - public static bool CanUseImpostorVentButton(this PlayerControl pc) + SystemTypes systemtypes = (MapNames)Main.NormalOptions.MapId switch { - if (!pc.IsAlive() || pc.Data.Role.Role == RoleTypes.GuardianAngel || Penguin.IsVictim(pc)) return false; + MapNames.Polus => SystemTypes.Laboratory, + MapNames.Airship => SystemTypes.HeliSabotage, + _ => SystemTypes.Reactor + }; - if (pc.GetRoleTypes() == RoleTypes.Engineer) return false; + LateTask.New(() => pc.RpcDesyncRepairSystem(systemtypes, 128), 0f + delay, "Reactor Desync"); - return Options.CurrentGameMode switch - { - CustomGameMode.SoloKombat => true, - CustomGameMode.FFA => true, - CustomGameMode.MoveAndStop => false, - CustomGameMode.HotPotato => false, - CustomGameMode.Speedrun => false, - CustomGameMode.CaptureTheFlag => false, - CustomGameMode.NaturalDisasters => false, - CustomGameMode.RoomRush => false, - CustomGameMode.AllInOne => false, - - CustomGameMode.Standard when CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == pc.PlayerId) => true, - CustomGameMode.Standard when pc.Is(CustomRoles.Nimble) || Options.EveryoneCanVent.GetBool() => true, - CustomGameMode.Standard when pc.Is(CustomRoles.Bloodlust) || pc.Is(CustomRoles.Refugee) => true, - - _ => Main.PlayerStates.TryGetValue(pc.PlayerId, out PlayerState state) && state.Role.CanUseImpostorVentButton(pc) - }; - } + LateTask.New(() => pc.RpcSpecificMurderPlayer(), 0.2f + delay, "Murder To Reset Cam"); - public static bool CanUseSabotage(this PlayerControl pc) + LateTask.New(() => { - if (!pc.IsAlive() || pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; + pc.RpcDesyncRepairSystem(systemtypes, 16); - if (pc.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) - { - pc.Notify(GetString("TraineeNotify")); - return false; - } + if (Main.NormalOptions.MapId == 4) // Airship only + pc.RpcDesyncRepairSystem(systemtypes, 17); + }, 0.4f + delay, "Fix Desync Reactor"); + } - return Main.PlayerStates.TryGetValue(pc.PlayerId, out PlayerState state) && state.Role.CanUseSabotage(pc); - } + public static void ReactorFlash(this PlayerControl pc, float delay = 0f) + { + if (pc == null) return; - public static Vector2 Pos(this PlayerControl pc) - { - return new(pc.transform.position.x, pc.transform.position.y); - } + Logger.Info($"Reactor Flash for {pc.GetNameWithRole()}", "ReactorFlash"); - public static void AddKillTimerToDict(this PlayerControl pc, bool half = false, float CD = -1f) + SystemTypes systemtypes = (MapNames)Main.NormalOptions.MapId switch { - float resultKCD; + MapNames.Polus => SystemTypes.Laboratory, + MapNames.Airship => SystemTypes.HeliSabotage, + _ => SystemTypes.Reactor + }; - if (Math.Abs(CD - -1f) < 0.5f) - { - resultKCD = Main.AllPlayerKillCooldown.GetValueOrDefault(pc.PlayerId, 0f); + float FlashDuration = Options.KillFlashDuration.GetFloat(); - if (half) resultKCD /= 2f; - } - else - resultKCD = CD; + pc.RpcDesyncRepairSystem(systemtypes, 128); - if (pc.GetCustomRole().UsesPetInsteadOfKill() && resultKCD > 0f) pc.AddAbilityCD((int)Math.Round(resultKCD)); + LateTask.New(() => + { + pc.RpcDesyncRepairSystem(systemtypes, 16); - if (Main.KillTimers.TryGetValue(pc.PlayerId, out float timer) && timer > resultKCD) return; + if (Main.NormalOptions.MapId == 4) // on Airship + pc.RpcDesyncRepairSystem(systemtypes, 17); + }, FlashDuration + delay, "Fix Desync Reactor"); + } - Main.KillTimers[pc.PlayerId] = resultKCD; + public static string GetRealName(this PlayerControl player, bool isMeeting = false) + { + try + { + string name = isMeeting ? player.Data.PlayerName : player.name; + return name.RemoveHtmlTags(); } - - public static bool IsDousedPlayer(this PlayerControl arsonist, PlayerControl target) + catch (NullReferenceException nullReferenceException) { - if (arsonist == null || target == null || Arsonist.IsDoused == null) return false; - - Arsonist.IsDoused.TryGetValue((arsonist.PlayerId, target.PlayerId), out bool isDoused); - return isDoused; + Logger.Error($"{nullReferenceException.Message} - player is null? {player == null}", "GetRealName"); + return string.Empty; } - - public static bool IsDrawPlayer(this PlayerControl arsonist, PlayerControl target) + catch (Exception exception) { - if (arsonist == null || target == null || Revolutionist.IsDraw == null) return false; + ThrowException(exception); + return string.Empty; + } + } - Revolutionist.IsDraw.TryGetValue((arsonist.PlayerId, target.PlayerId), out bool isDraw); - return isDraw; - } + public static bool IsRoleBlocked(this PlayerControl pc) + { + return RoleBlockManager.RoleBlockedPlayers.ContainsKey(pc.PlayerId); + } - public static bool IsRevealedPlayer(this PlayerControl player, PlayerControl target) - { - if (player == null || target == null || Farseer.IsRevealed == null) return false; + public static bool IsPlayerRoleBlocked(this byte id) + { + return RoleBlockManager.RoleBlockedPlayers.ContainsKey(id); + } - Farseer.IsRevealed.TryGetValue((player.PlayerId, target.PlayerId), out bool isDoused); - return isDoused; - } + public static void BlockRole(this PlayerControl pc, float duration) + { + RoleBlockManager.AddRoleBlock(pc, duration); + } - public static void RpcSetDousedPlayer(this PlayerControl player, PlayerControl target, bool isDoused) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDousedPlayer, SendOption.Reliable); //RPCによる同期 - writer.Write(player.PlayerId); - writer.Write(target.PlayerId); - writer.Write(isDoused); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + public static bool IsHost(this InnerNetObject ino) + { + return ino.OwnerId == AmongUsClient.Instance.HostId; + } - public static void RpcSetDrawPlayer(this PlayerControl player, PlayerControl target, bool isDoused) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDrawPlayer, SendOption.Reliable); //RPCによる同期 - writer.Write(player.PlayerId); - writer.Write(target.PlayerId); - writer.Write(isDoused); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + public static bool IsHost(this byte id) + { + return GetPlayerById(id)?.OwnerId == AmongUsClient.Instance.HostId; + } - public static void RpcSetRevealtPlayer(this PlayerControl player, PlayerControl target, bool isDoused) + public static void RpcShowScanAnimationDesync(this PlayerControl target, PlayerControl seer, bool IsActive) + { + if (!AmongUsClient.Instance.AmHost) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRevealedPlayer, SendOption.Reliable); //RPCによる同期 - writer.Write(player.PlayerId); - writer.Write(target.PlayerId); - writer.Write(isDoused); - AmongUsClient.Instance.FinishRpcImmediately(writer); + StackFrame caller = new(1, false); + MethodBase callerMethod = caller.GetMethod(); + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + Logger.Warn($"Modded non-host client activated RpcShowScanAnimation from {callerClassName}.{callerMethodName}", "RpcShowScanAnimation"); + return; } - public static bool IsShifted(this PlayerControl pc) - { - return Main.CheckShapeshift.TryGetValue(pc.PlayerId, out bool shifted) && shifted; - } + if (target == null || seer == null) return; - public static bool IsPlayerShifted(this byte id) - { - return Main.CheckShapeshift.TryGetValue(id, out bool shifted) && shifted; - } + int seerClientId = seer.GetClientId(); + if (seerClientId == -1) return; - public static bool HasSubRole(this PlayerControl pc) - { - return Main.PlayerStates[pc.PlayerId].SubRoles.Count > 0; - } + byte cnt = ++target.scannerCount; - public static bool HasEvilAddon(this PlayerControl pc) + if (AmongUsClient.Instance.ClientId == seerClientId) { - return Main.PlayerStates[pc.PlayerId].SubRoles.Any(x => x.IsEvilAddon()); + target.SetScanner(IsActive, cnt); + return; } - - public static void ResetKillCooldown(this PlayerControl player) - { - Main.PlayerStates[player.PlayerId].Role.SetKillCooldown(player.PlayerId); - Main.AllPlayerKillCooldown[player.PlayerId] = player.GetCustomRole() switch - { - CustomRoles.KB_Normal => SoloKombatManager.KB_ATKCooldown.GetFloat(), - CustomRoles.Killer => FFAManager.FFAKcd.GetFloat(), - _ => Main.AllPlayerKillCooldown[player.PlayerId] - }; + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(target.NetId, (byte)RpcCalls.SetScanner, SendOption.Reliable, seerClientId); + messageWriter.Write(IsActive); + messageWriter.Write(cnt); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + target.scannerCount = cnt; + } - if (player.PlayerId == LastImpostor.CurrentId) LastImpostor.SetKillCooldown(); + public static bool HasKillButton(this PlayerControl pc) + { + CustomRoles role = pc.GetCustomRole(); + if (pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; - if (player.Is(CustomRoles.Mare)) - { - if (IsActive(SystemTypes.Electrical)) - Main.AllPlayerKillCooldown[player.PlayerId] = Options.MareKillCD.GetFloat(); - else - Main.AllPlayerKillCooldown[player.PlayerId] = Options.MareKillCDNormally.GetFloat(); - } + if (role.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.Shapeshifter or CustomRoles.Phantom) return true; - if (player.Is(CustomRoles.Bloodlust)) Main.AllPlayerKillCooldown[player.PlayerId] = Bloodlust.KCD.GetFloat(); + if (pc.GetRoleTypes() is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) return true; - if (Main.KilledDiseased.TryGetValue(player.PlayerId, out int value)) - { - Main.AllPlayerKillCooldown[player.PlayerId] += value * Options.DiseasedCDOpt.GetFloat(); - Logger.Info($"KCD of player set to {Main.AllPlayerKillCooldown[player.PlayerId]}", "Diseased"); - } + if (pc.Is(CustomRoles.Bloodlust)) return true; - if (Main.KilledAntidote.TryGetValue(player.PlayerId, out int value1)) - { - float kcd = Main.AllPlayerKillCooldown[player.PlayerId] - (value1 * Options.AntidoteCDOpt.GetFloat()); - if (kcd < 0) kcd = 0; + return pc.Is(CustomRoleTypes.Impostor) || pc.IsNeutralKiller() || role.IsTasklessCrewmate(); + } - Main.AllPlayerKillCooldown[player.PlayerId] = kcd; - Logger.Info($"KCD of player set to {Main.AllPlayerKillCooldown[player.PlayerId]}", "Antidote"); - } - } + public static bool CanUseKillButton(this PlayerControl pc) + { + if (!pc.IsAlive()) return false; - public static void TrapperKilled(this PlayerControl killer, PlayerControl target) + switch (Options.CurrentGameMode) { - Logger.Info($"{target?.Data?.PlayerName} was Trapper", "Trapper"); - float tmpSpeed = Main.AllPlayerSpeed[killer.PlayerId]; - Main.AllPlayerSpeed[killer.PlayerId] = Main.MinSpeed; - ReportDeadBodyPatch.CanReport[killer.PlayerId] = false; - killer.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[killer.PlayerId] = tmpSpeed; - ReportDeadBodyPatch.CanReport[killer.PlayerId] = true; - killer.MarkDirtySettings(); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.TaskComplete); - }, Options.TrapperBlockMoveTime.GetFloat(), "Trapper BlockMove"); - - if (killer.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); + case CustomGameMode.HotPotato or CustomGameMode.MoveAndStop or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush: + case CustomGameMode.Speedrun when !SpeedrunManager.CanKill.Contains(pc.PlayerId): + return false; + case CustomGameMode.CaptureTheFlag: + return true; + case CustomGameMode.AllInOne: + return !AllInOneGameMode.Taskers.Contains(pc.PlayerId); } - public static bool IsDouseDone(this PlayerControl player) - { - if (!player.Is(CustomRoles.Arsonist)) return false; + if (Mastermind.ManipulatedPlayers.ContainsKey(pc.PlayerId)) return true; - (int, int) count = GetDousedPlayerCount(player.PlayerId); - return count.Item1 >= count.Item2; - } + if (Penguin.IsVictim(pc)) return false; - public static bool IsDrawDone(this PlayerControl player) // Determine whether the conditions to win are met - { - if (!player.Is(CustomRoles.Revolutionist)) return false; + if (Pelican.IsEaten(pc.PlayerId)) return false; - (int, int) count = GetDrawPlayerCount(player.PlayerId, out _); - return count.Item1 >= count.Item2; - } + if (pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; - public static void RpcExileV2(this PlayerControl player) - { - player.Exiled(); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable); - AmongUsClient.Instance.FinishRpcImmediately(writer); - FixedUpdatePatch.LoversSuicide(player.PlayerId); - } + if (pc.Is(CustomRoles.Bloodlust)) return true; - public static (Vector2 Location, string RoomName) GetPositionInfo(this PlayerControl pc) + return pc.GetCustomRole() switch { - PlainShipRoom room = pc.GetPlainShipRoom(); - string roomName = room == null ? "Outside" : $"@{GetString($"{room.RoomId}")}"; - Vector2 pos = pc.Pos(); + // SoloKombat + CustomRoles.KB_Normal => pc.SoloAlive(), + // FFA + CustomRoles.Killer => pc.IsAlive(), + // Move And Stop + CustomRoles.Tasker => false, + // Hot Potato + CustomRoles.Potato => false, + // Speedrun + CustomRoles.Runner => SpeedrunManager.CanKill.Contains(pc.PlayerId), + // Hide And Seek + CustomRoles.Seeker => true, + CustomRoles.Hider => false, + CustomRoles.Troll => false, + CustomRoles.Fox => false, + CustomRoles.Jumper => false, + CustomRoles.Detector => false, + CustomRoles.Jet => false, + CustomRoles.Dasher => true, + CustomRoles.Locator => true, + CustomRoles.Venter => true, + CustomRoles.Agent => true, + CustomRoles.Taskinator => false, - return (pos, roomName); - } + _ => Main.PlayerStates.TryGetValue(pc.PlayerId, out PlayerState state) && state.Role.CanUseKillButton(pc) + }; + } - public static bool TP(this PlayerControl pc, PlayerControl target, bool noCheckState = false, bool log = true) - { - return Utils.TP(pc.NetTransform, target.Pos(), noCheckState, log); - } + public static bool CanUseImpostorVentButton(this PlayerControl pc) + { + if (!pc.IsAlive() || pc.Data.Role.Role == RoleTypes.GuardianAngel || Penguin.IsVictim(pc)) return false; - public static bool TP(this PlayerControl pc, Vector2 location, bool noCheckState = false, bool log = true) - { - return Utils.TP(pc.NetTransform, location, noCheckState, log); - } + if (pc.GetRoleTypes() == RoleTypes.Engineer) return false; - public static bool TPToRandomVent(this PlayerControl pc, bool log = true) + return Options.CurrentGameMode switch { - return Utils.TPToRandomVent(pc.NetTransform, log); - } + CustomGameMode.SoloKombat => true, + CustomGameMode.FFA => true, + CustomGameMode.MoveAndStop => false, + CustomGameMode.HotPotato => false, + CustomGameMode.Speedrun => false, + CustomGameMode.CaptureTheFlag => false, + CustomGameMode.NaturalDisasters => false, + CustomGameMode.RoomRush => false, + CustomGameMode.AllInOne => false, + + CustomGameMode.Standard when CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == pc.PlayerId) => true, + CustomGameMode.Standard when pc.Is(CustomRoles.Nimble) || Options.EveryoneCanVent.GetBool() => true, + CustomGameMode.Standard when pc.Is(CustomRoles.Bloodlust) || pc.Is(CustomRoles.Refugee) => true, + + _ => Main.PlayerStates.TryGetValue(pc.PlayerId, out PlayerState state) && state.Role.CanUseImpostorVentButton(pc) + }; + } + + public static bool CanUseSabotage(this PlayerControl pc) + { + if (!pc.IsAlive() || pc.Data.Role.Role == RoleTypes.GuardianAngel) return false; + + if (pc.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) + { + pc.Notify(GetString("TraineeNotify")); + return false; + } + + return Main.PlayerStates.TryGetValue(pc.PlayerId, out PlayerState state) && state.Role.CanUseSabotage(pc); + } + + public static Vector2 Pos(this PlayerControl pc) + { + return new(pc.transform.position.x, pc.transform.position.y); + } + + public static void AddKillTimerToDict(this PlayerControl pc, bool half = false, float CD = -1f) + { + float resultKCD; + + if (Math.Abs(CD - -1f) < 0.5f) + { + resultKCD = Main.AllPlayerKillCooldown.GetValueOrDefault(pc.PlayerId, 0f); + + if (half) resultKCD /= 2f; + } + else + resultKCD = CD; + + if (pc.GetCustomRole().UsesPetInsteadOfKill() && resultKCD > 0f) pc.AddAbilityCD((int)Math.Round(resultKCD)); - public static void Kill(this PlayerControl killer, PlayerControl target) - { - if (Options.CurrentGameMode == CustomGameMode.SoloKombat) return; + if (Main.KillTimers.TryGetValue(pc.PlayerId, out float timer) && timer > resultKCD) return; + + Main.KillTimers[pc.PlayerId] = resultKCD; + } + + public static bool IsDousedPlayer(this PlayerControl arsonist, PlayerControl target) + { + if (arsonist == null || target == null || Arsonist.IsDoused == null) return false; + + Arsonist.IsDoused.TryGetValue((arsonist.PlayerId, target.PlayerId), out bool isDoused); + return isDoused; + } + + public static bool IsDrawPlayer(this PlayerControl arsonist, PlayerControl target) + { + if (arsonist == null || target == null || Revolutionist.IsDraw == null) return false; + + Revolutionist.IsDraw.TryGetValue((arsonist.PlayerId, target.PlayerId), out bool isDraw); + return isDraw; + } + + public static bool IsRevealedPlayer(this PlayerControl player, PlayerControl target) + { + if (player == null || target == null || Farseer.IsRevealed == null) return false; + + Farseer.IsRevealed.TryGetValue((player.PlayerId, target.PlayerId), out bool isDoused); + return isDoused; + } + + public static void RpcSetDousedPlayer(this PlayerControl player, PlayerControl target, bool isDoused) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDousedPlayer, SendOption.Reliable); //RPCによる同期 + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + writer.Write(isDoused); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - if (target == null) target = killer; + public static void RpcSetDrawPlayer(this PlayerControl player, PlayerControl target, bool isDoused) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDrawPlayer, SendOption.Reliable); //RPCによる同期 + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + writer.Write(isDoused); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - CheckAndSpawnAdditionalRefugee(target.Data); + public static void RpcSetRevealtPlayer(this PlayerControl player, PlayerControl target, bool isDoused) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRevealedPlayer, SendOption.Reliable); //RPCによる同期 + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + writer.Write(isDoused); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - if (target.GetTeam() is Team.Impostor or Team.Neutral) Stressed.OnNonCrewmateDead(); + public static bool IsShifted(this PlayerControl pc) + { + return Main.CheckShapeshift.TryGetValue(pc.PlayerId, out bool shifted) && shifted; + } - if (killer.Is(CustomRoles.Damocles)) - Damocles.OnMurder(killer.PlayerId); - else if (killer.Is(Team.Impostor)) - Damocles.OnOtherImpostorMurder(); - else if (target.Is(Team.Impostor)) Damocles.OnImpostorDeath(); + public static bool IsPlayerShifted(this byte id) + { + return Main.CheckShapeshift.TryGetValue(id, out bool shifted) && shifted; + } - if (killer.Is(CustomRoles.Bloodlust)) - FixedUpdatePatch.AddExtraAbilityUsesOnFinishedTasks(killer); - else - IncreaseAbilityUseLimitOnKill(killer); + public static bool HasSubRole(this PlayerControl pc) + { + return Main.PlayerStates[pc.PlayerId].SubRoles.Count > 0; + } - target.SetRealKiller(killer, true); + public static bool HasEvilAddon(this PlayerControl pc) + { + return Main.PlayerStates[pc.PlayerId].SubRoles.Any(x => x.IsEvilAddon()); + } - if (target.Is(CustomRoles.Jackal)) Jackal.Instances.Do(x => x.PromoteSidekick()); + public static void ResetKillCooldown(this PlayerControl player) + { + Main.PlayerStates[player.PlayerId].Role.SetKillCooldown(player.PlayerId); - Main.DiedThisRound.Add(target.PlayerId); + Main.AllPlayerKillCooldown[player.PlayerId] = player.GetCustomRole() switch + { + CustomRoles.KB_Normal => SoloKombatManager.KB_ATKCooldown.GetFloat(), + CustomRoles.Killer => FFAManager.FFAKcd.GetFloat(), + _ => Main.AllPlayerKillCooldown[player.PlayerId] + }; - if (killer.IsLocalPlayer() && !killer.HasKillButton() && killer.PlayerId != target.PlayerId && CustomGameMode.Standard.IsActiveOrIntegrated()) - Achievements.Type.InnocentKiller.Complete(); + if (player.PlayerId == LastImpostor.CurrentId) LastImpostor.SetKillCooldown(); - switch (killer.PlayerId == target.PlayerId) - { - case true when killer.shapeshifting: - LateTask.New(() => killer.RpcMurderPlayer(target, true), 1.5f, "Shapeshifting Suicide Delay"); - return; - case false when !killer.Is(CustomRoles.Pestilence) && Main.PlayerStates[target.PlayerId].Role is SchrodingersCat cat: - cat.OnCheckMurderAsTarget(killer, target); - return; - default: - killer.RpcMurderPlayer(target, true); - break; - } + if (player.Is(CustomRoles.Mare)) + { + if (IsActive(SystemTypes.Electrical)) + Main.AllPlayerKillCooldown[player.PlayerId] = Options.MareKillCD.GetFloat(); + else + Main.AllPlayerKillCooldown[player.PlayerId] = Options.MareKillCDNormally.GetFloat(); } + + if (player.Is(CustomRoles.Bloodlust)) Main.AllPlayerKillCooldown[player.PlayerId] = Bloodlust.KCD.GetFloat(); - public static bool RpcCheckAndMurder(this PlayerControl killer, PlayerControl target, bool check = false) + if (Main.KilledDiseased.TryGetValue(player.PlayerId, out int value)) { - return CheckMurderPatch.RpcCheckAndMurder(killer, target, check); + Main.AllPlayerKillCooldown[player.PlayerId] += value * Options.DiseasedCDOpt.GetFloat(); + Logger.Info($"KCD of player set to {Main.AllPlayerKillCooldown[player.PlayerId]}", "Diseased"); } - public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPlayerInfo target, bool force = false) + if (Main.KilledAntidote.TryGetValue(player.PlayerId, out int value1)) { - if (Options.DisableMeeting.GetBool() && !force) return; + float kcd = Main.AllPlayerKillCooldown[player.PlayerId] - (value1 * Options.AntidoteCDOpt.GetFloat()); + if (kcd < 0) kcd = 0; - ReportDeadBodyPatch.AfterReportTasks(reporter, target, true); - MeetingRoomManager.Instance.AssignSelf(reporter, target); - DestroyableSingleton.Instance.OpenMeetingRoom(reporter); - reporter.RpcStartMeeting(target); + Main.AllPlayerKillCooldown[player.PlayerId] = kcd; + Logger.Info($"KCD of player set to {Main.AllPlayerKillCooldown[player.PlayerId]}", "Antidote"); } + } + + public static void TrapperKilled(this PlayerControl killer, PlayerControl target) + { + Logger.Info($"{target?.Data?.PlayerName} was Trapper", "Trapper"); + float tmpSpeed = Main.AllPlayerSpeed[killer.PlayerId]; + Main.AllPlayerSpeed[killer.PlayerId] = Main.MinSpeed; + ReportDeadBodyPatch.CanReport[killer.PlayerId] = false; + killer.MarkDirtySettings(); - public static void CheckAndSetUnshiftState(this PlayerControl pc, bool notify = true, bool force = false) + LateTask.New(() => { - CustomRoles role = pc.GetCustomRole(); + Main.AllPlayerSpeed[killer.PlayerId] = tmpSpeed; + ReportDeadBodyPatch.CanReport[killer.PlayerId] = true; + killer.MarkDirtySettings(); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.TaskComplete); + }, Options.TrapperBlockMoveTime.GetFloat(), "Trapper BlockMove"); + + if (killer.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); + } + + public static bool IsDouseDone(this PlayerControl player) + { + if (!player.Is(CustomRoles.Arsonist)) return false; - if (force || role.AlwaysUsesUnshift() || (role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool()))) - { - Logger.Info($"Set Unshift State For {pc.GetNameWithRole()}", "CheckAndSetUnshiftState"); - PlayerControl target = Main.AllAlivePlayerControls.Without(pc).RandomElement(); - NetworkedPlayerInfo.PlayerOutfit outfit = pc.Data.DefaultOutfit; - bool process = Main.ProcessShapeshifts; - Main.ProcessShapeshifts = false; - pc.RpcShapeshift(target, false); - Main.ProcessShapeshifts = process; - Main.CheckShapeshift[pc.PlayerId] = false; - - LateTask.New(() => - { - RpcChangeSkin(pc, outfit); - if (notify) NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, NoCache: true); - }, 0.1f, log: false); - } - } + (int, int) count = GetDousedPlayerCount(player.PlayerId); + return count.Item1 >= count.Item2; + } - public static bool IsLocalPlayer(this PlayerControl pc) => pc.PlayerId == PlayerControl.LocalPlayer.PlayerId; - public static bool IsLocalPlayer(this NetworkedPlayerInfo npi) => npi.PlayerId == PlayerControl.LocalPlayer.PlayerId; + public static bool IsDrawDone(this PlayerControl player) // Determine whether the conditions to win are met + { + if (!player.Is(CustomRoles.Revolutionist)) return false; - public static bool IsModClient(this PlayerControl player) - { - return player.IsLocalPlayer() || player.IsHost() || Main.PlayerVersion.ContainsKey(player.PlayerId); - } + (int, int) count = GetDrawPlayerCount(player.PlayerId, out _); + return count.Item1 >= count.Item2; + } - public static List GetPlayersInAbilityRangeSorted(this PlayerControl player, bool ignoreColliders = false) - { - return GetPlayersInAbilityRangeSorted(player, _ => true, ignoreColliders); - } + public static void RpcExileV2(this PlayerControl player) + { + player.Exiled(); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable); + AmongUsClient.Instance.FinishRpcImmediately(writer); + FixedUpdatePatch.LoversSuicide(player.PlayerId); + } - public static List GetPlayersInAbilityRangeSorted(this PlayerControl player, Predicate predicate, bool ignoreColliders = false) - { - Il2CppSystem.Collections.Generic.List rangePlayersIL = RoleBehaviour.GetTempPlayerList(); - List rangePlayers = []; - player.Data.Role.GetPlayersInAbilityRangeSorted(rangePlayersIL, ignoreColliders); + public static (Vector2 Location, string RoomName) GetPositionInfo(this PlayerControl pc) + { + PlainShipRoom room = pc.GetPlainShipRoom(); + string roomName = room == null ? "Outside" : $"@{GetString($"{room.RoomId}")}"; + Vector2 pos = pc.Pos(); - foreach (PlayerControl pc in rangePlayersIL) - if (predicate(pc)) - rangePlayers.Add(pc); + return (pos, roomName); + } - return rangePlayers; - } + public static bool TP(this PlayerControl pc, PlayerControl target, bool noCheckState = false, bool log = true) + { + return Utils.TP(pc.NetTransform, target.Pos(), noCheckState, log); + } - public static bool IsNeutralKiller(this PlayerControl player) - { - return player.Is(CustomRoles.Bloodlust) || (player.GetCustomRole().IsNK() && !player.IsMadmate()); - } + public static bool TP(this PlayerControl pc, Vector2 location, bool noCheckState = false, bool log = true) + { + return Utils.TP(pc.NetTransform, location, noCheckState, log); + } - public static bool IsNeutralBenign(this PlayerControl player) - { - return player.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign; - } + public static bool TPToRandomVent(this PlayerControl pc, bool log = true) + { + return Utils.TPToRandomVent(pc.NetTransform, log); + } - public static bool IsNeutralEvil(this PlayerControl player) - { - return player.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil; - } + public static void Kill(this PlayerControl killer, PlayerControl target) + { + if (Options.CurrentGameMode == CustomGameMode.SoloKombat) return; - public static bool IsSnitchTarget(this PlayerControl player) - { - return player.Is(CustomRoles.Bloodlust) || Framer.FramedPlayers.Contains(player.PlayerId) || player.GetCustomRole().IsSnitchTarget(); - } + if (target == null) target = killer; - public static bool IsMadmate(this PlayerControl player) - { - return player.Is(CustomRoles.Madmate) || player.GetCustomRole().IsMadmate(); - } + CheckAndSpawnAdditionalRefugee(target.Data); - public static bool HasGhostRole(this PlayerControl player) - { - return GhostRolesManager.AssignedGhostRoles.ContainsKey(player.PlayerId) || (Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) && state.SubRoles.Any(x => x.IsGhostRole())); - } + if (target.GetTeam() is Team.Impostor or Team.Neutral) Stressed.OnNonCrewmateDead(); - public static bool KnowDeathReason(this PlayerControl seer, PlayerControl target) - { - return (seer.Is(CustomRoles.Doctor) - || seer.Is(CustomRoles.Autopsy) - || Options.EveryoneSeesDeathReasons.GetBool() - || target.Is(CustomRoles.Gravestone) - || (seer.Data.IsDead && Options.GhostCanSeeDeathReason.GetBool())) - && target.Data.IsDead; - } + if (killer.Is(CustomRoles.Damocles)) + Damocles.OnMurder(killer.PlayerId); + else if (killer.Is(Team.Impostor)) + Damocles.OnOtherImpostorMurder(); + else if (target.Is(Team.Impostor)) Damocles.OnImpostorDeath(); - public static string GetRoleInfo(this PlayerControl player, bool InfoLong = false) - { - CustomRoles role = player.GetCustomRole(); - if (role is CustomRoles.Crewmate or CustomRoles.Impostor) InfoLong = false; + if (killer.Is(CustomRoles.Bloodlust)) + FixedUpdatePatch.AddExtraAbilityUsesOnFinishedTasks(killer); + else + IncreaseAbilityUseLimitOnKill(killer); - var text = role.ToString(); + target.SetRealKiller(killer, true); - var Prefix = string.Empty; + if (target.Is(CustomRoles.Jackal)) Jackal.Instances.Do(x => x.PromoteSidekick()); - if (!InfoLong) - { - Prefix = role switch - { - CustomRoles.Mafia => CanMafiaKill() ? "After" : "Before", - _ => Prefix - }; - } + Main.DiedThisRound.Add(target.PlayerId); - string Info = (role.IsVanilla() ? "Blurb" : "Info") + (InfoLong ? "Long" : string.Empty); - return GetString($"{Prefix}{text}{Info}"); - } + if (killer.IsLocalPlayer() && !killer.HasKillButton() && killer.PlayerId != target.PlayerId && CustomGameMode.Standard.IsActiveOrIntegrated()) + Achievements.Type.InnocentKiller.Complete(); - public static void SetRealKiller(this PlayerControl target, PlayerControl killer, bool NotOverRide = false) + switch (killer.PlayerId == target.PlayerId) { - if (target == null) - { - Logger.Info("target is null", "SetRealKiller"); + case true when killer.shapeshifting: + LateTask.New(() => killer.RpcMurderPlayer(target, true), 1.5f, "Shapeshifting Suicide Delay"); + return; + case false when !killer.Is(CustomRoles.Pestilence) && Main.PlayerStates[target.PlayerId].Role is SchrodingersCat cat: + cat.OnCheckMurderAsTarget(killer, target); return; - } + default: + killer.RpcMurderPlayer(target, true); + break; + } + } + + public static bool RpcCheckAndMurder(this PlayerControl killer, PlayerControl target, bool check = false) + { + return CheckMurderPatch.RpcCheckAndMurder(killer, target, check); + } + + public static void NoCheckStartMeeting(this PlayerControl reporter, NetworkedPlayerInfo target, bool force = false) + { + if (Options.DisableMeeting.GetBool() && !force) return; + + ReportDeadBodyPatch.AfterReportTasks(reporter, target, true); + MeetingRoomManager.Instance.AssignSelf(reporter, target); + DestroyableSingleton.Instance.OpenMeetingRoom(reporter); + reporter.RpcStartMeeting(target); + } + + public static void CheckAndSetUnshiftState(this PlayerControl pc, bool notify = true, bool force = false) + { + CustomRoles role = pc.GetCustomRole(); + + if (force || role.AlwaysUsesUnshift() || (role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool()))) + { + Logger.Info($"Set Unshift State For {pc.GetNameWithRole()}", "CheckAndSetUnshiftState"); + PlayerControl target = Main.AllAlivePlayerControls.Without(pc).RandomElement(); + NetworkedPlayerInfo.PlayerOutfit outfit = pc.Data.DefaultOutfit; + bool process = Main.ProcessShapeshifts; + Main.ProcessShapeshifts = false; + pc.RpcShapeshift(target, false); + Main.ProcessShapeshifts = process; + Main.CheckShapeshift[pc.PlayerId] = false; - PlayerState State = Main.PlayerStates[target.PlayerId]; - if (State.RealKiller.TimeStamp != DateTime.MinValue && NotOverRide) return; // Do not overwrite if value already exists + LateTask.New(() => + { + RpcChangeSkin(pc, outfit); + if (notify) NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, NoCache: true); + }, 0.1f, log: false); + } + } + + public static bool IsLocalPlayer(this PlayerControl pc) => pc.PlayerId == PlayerControl.LocalPlayer.PlayerId; + public static bool IsLocalPlayer(this NetworkedPlayerInfo npi) => npi.PlayerId == PlayerControl.LocalPlayer.PlayerId; + + public static bool IsModClient(this PlayerControl player) + { + return player.IsLocalPlayer() || player.IsHost() || Main.PlayerVersion.ContainsKey(player.PlayerId); + } + + public static List GetPlayersInAbilityRangeSorted(this PlayerControl player, bool ignoreColliders = false) + { + return GetPlayersInAbilityRangeSorted(player, _ => true, ignoreColliders); + } + + public static List GetPlayersInAbilityRangeSorted(this PlayerControl player, Predicate predicate, bool ignoreColliders = false) + { + Il2CppSystem.Collections.Generic.List rangePlayersIL = RoleBehaviour.GetTempPlayerList(); + List rangePlayers = []; + player.Data.Role.GetPlayersInAbilityRangeSorted(rangePlayersIL, ignoreColliders); + + foreach (PlayerControl pc in rangePlayersIL) + if (predicate(pc)) + rangePlayers.Add(pc); + + return rangePlayers; + } + + public static bool IsNeutralKiller(this PlayerControl player) + { + return player.Is(CustomRoles.Bloodlust) || (player.GetCustomRole().IsNK() && !player.IsMadmate()); + } + + public static bool IsNeutralBenign(this PlayerControl player) + { + return player.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign; + } + + public static bool IsNeutralEvil(this PlayerControl player) + { + return player.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil; + } + + public static bool IsSnitchTarget(this PlayerControl player) + { + return player.Is(CustomRoles.Bloodlust) || Framer.FramedPlayers.Contains(player.PlayerId) || player.GetCustomRole().IsSnitchTarget(); + } + + public static bool IsMadmate(this PlayerControl player) + { + return player.Is(CustomRoles.Madmate) || player.GetCustomRole().IsMadmate(); + } + + public static bool HasGhostRole(this PlayerControl player) + { + return GhostRolesManager.AssignedGhostRoles.ContainsKey(player.PlayerId) || (Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState state) && state.SubRoles.Any(x => x.IsGhostRole())); + } + + public static bool KnowDeathReason(this PlayerControl seer, PlayerControl target) + { + return (seer.Is(CustomRoles.Doctor) + || seer.Is(CustomRoles.Autopsy) + || Options.EveryoneSeesDeathReasons.GetBool() + || target.Is(CustomRoles.Gravestone) + || (seer.Data.IsDead && Options.GhostCanSeeDeathReason.GetBool())) + && target.Data.IsDead; + } + + public static string GetRoleInfo(this PlayerControl player, bool InfoLong = false) + { + CustomRoles role = player.GetCustomRole(); + if (role is CustomRoles.Crewmate or CustomRoles.Impostor) InfoLong = false; + + var text = role.ToString(); + + var Prefix = string.Empty; + + if (!InfoLong) + { + Prefix = role switch + { + CustomRoles.Mafia => CanMafiaKill() ? "After" : "Before", + _ => Prefix + }; + } - byte killerId = killer == null ? byte.MaxValue : killer.PlayerId; - RPC.SetRealKiller(target.PlayerId, killerId); - } + string Info = (role.IsVanilla() ? "Blurb" : "Info") + (InfoLong ? "Long" : string.Empty); + return GetString($"{Prefix}{text}{Info}"); + } - public static PlayerControl GetRealKiller(this PlayerControl target) + public static void SetRealKiller(this PlayerControl target, PlayerControl killer, bool NotOverRide = false) + { + if (target == null) { - byte killerId = Main.PlayerStates[target.PlayerId].GetRealKiller(); - return killerId == byte.MaxValue ? null : GetPlayerById(killerId); + Logger.Info("target is null", "SetRealKiller"); + return; } + + PlayerState State = Main.PlayerStates[target.PlayerId]; + if (State.RealKiller.TimeStamp != DateTime.MinValue && NotOverRide) return; // Do not overwrite if value already exists + + byte killerId = killer == null ? byte.MaxValue : killer.PlayerId; + RPC.SetRealKiller(target.PlayerId, killerId); + } - public static PlainShipRoom GetPlainShipRoom(this PlayerControl pc) - { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return default; + public static PlayerControl GetRealKiller(this PlayerControl target) + { + byte killerId = Main.PlayerStates[target.PlayerId].GetRealKiller(); + return killerId == byte.MaxValue ? null : GetPlayerById(killerId); + } + + public static PlainShipRoom GetPlainShipRoom(this PlayerControl pc) + { + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return default; + + Il2CppReferenceArray Rooms = ShipStatus.Instance.AllRooms; + return Rooms.Where(room => room.roomArea).FirstOrDefault(room => pc.Collider.IsTouching(room.roomArea)); + } - Il2CppReferenceArray Rooms = ShipStatus.Instance.AllRooms; - return Rooms.Where(room => room.roomArea).FirstOrDefault(room => pc.Collider.IsTouching(room.roomArea)); - } + public static bool IsImpostor(this PlayerControl pc) + { + return !pc.Is(CustomRoles.Bloodlust) && pc.GetCustomRole().IsImpostor(); + } - public static bool IsImpostor(this PlayerControl pc) - { - return !pc.Is(CustomRoles.Bloodlust) && pc.GetCustomRole().IsImpostor(); - } + public static bool IsCrewmate(this PlayerControl pc) + { + return !pc.Is(CustomRoles.Bloodlust) && pc.GetCustomRole().IsCrewmate(); + } - public static bool IsCrewmate(this PlayerControl pc) - { - return !pc.Is(CustomRoles.Bloodlust) && pc.GetCustomRole().IsCrewmate(); - } + public static CustomRoleTypes GetCustomRoleTypes(this PlayerControl pc) + { + return pc.Is(CustomRoles.Bloodlust) ? CustomRoleTypes.Neutral : pc.GetCustomRole().GetCustomRoleTypes(); + } - public static CustomRoleTypes GetCustomRoleTypes(this PlayerControl pc) + public static RoleTypes GetRoleTypes(this PlayerControl pc) + { + try { - return pc.Is(CustomRoles.Bloodlust) ? CustomRoleTypes.Neutral : pc.GetCustomRole().GetCustomRoleTypes(); + if (Main.HasJustStarted) throw new("HasJustStarted"); + return pc.GetRoleMap().RoleType; } - - public static RoleTypes GetRoleTypes(this PlayerControl pc) + catch { - try + return pc.GetCustomSubRoles() switch { - if (Main.HasJustStarted) throw new("HasJustStarted"); - return pc.GetRoleMap().RoleType; - } - catch - { - return pc.GetCustomSubRoles() switch - { - { } x when x.Contains(CustomRoles.Bloodlust) => RoleTypes.Impostor, - { } x when x.Contains(CustomRoles.Nimble) && !pc.HasDesyncRole() => RoleTypes.Engineer, - { } x when x.Contains(CustomRoles.Physicist) => RoleTypes.Scientist, - { } x when x.Contains(CustomRoles.Finder) => RoleTypes.Tracker, - { } x when x.Contains(CustomRoles.Noisy) => RoleTypes.Noisemaker, - _ => pc.GetCustomRole().GetRoleTypes() - }; - } + { } x when x.Contains(CustomRoles.Bloodlust) => RoleTypes.Impostor, + { } x when x.Contains(CustomRoles.Nimble) && !pc.HasDesyncRole() => RoleTypes.Engineer, + { } x when x.Contains(CustomRoles.Physicist) => RoleTypes.Scientist, + { } x when x.Contains(CustomRoles.Finder) => RoleTypes.Tracker, + { } x when x.Contains(CustomRoles.Noisy) => RoleTypes.Noisemaker, + _ => pc.GetCustomRole().GetRoleTypes() + }; } + } - public static bool Is(this PlayerControl target, CustomRoles role) - { - return role > CustomRoles.NotAssigned ? target.GetCustomSubRoles().Contains(role) : target.GetCustomRole() == role; - } + public static bool Is(this PlayerControl target, CustomRoles role) + { + return role > CustomRoles.NotAssigned ? target.GetCustomSubRoles().Contains(role) : target.GetCustomRole() == role; + } - public static bool Is(this PlayerControl target, CustomRoleTypes type) - { - return target.GetCustomRoleTypes() == type; - } + public static bool Is(this PlayerControl target, CustomRoleTypes type) + { + return target.GetCustomRoleTypes() == type; + } - public static bool Is(this PlayerControl target, RoleTypes type) - { - return (target.Is(CustomRoles.Bloodlust) && type == RoleTypes.Impostor) || target.GetCustomRole().GetRoleTypes() == type; - } + public static bool Is(this PlayerControl target, RoleTypes type) + { + return (target.Is(CustomRoles.Bloodlust) && type == RoleTypes.Impostor) || target.GetCustomRole().GetRoleTypes() == type; + } - public static bool Is(this PlayerControl target, CountTypes type) - { - return target.GetCountTypes() == type; - } + public static bool Is(this PlayerControl target, CountTypes type) + { + return target.GetCountTypes() == type; + } - public static bool Is(this PlayerControl target, Team team) + public static bool Is(this PlayerControl target, Team team) + { + return team switch { - return team switch - { - Team.Impostor => (target.IsMadmate() || target.GetCustomRole().IsImpostorTeamV2() || Framer.FramedPlayers.Contains(target.PlayerId)) && !target.Is(CustomRoles.Bloodlust), - Team.Neutral => target.GetCustomRole().IsNeutralTeamV2() || target.Is(CustomRoles.Bloodlust) || target.IsConverted(), - Team.Crewmate => target.GetCustomRole().IsCrewmateTeamV2(), - Team.None => target.Is(CustomRoles.GM) || target.Is(CountTypes.None) || target.Is(CountTypes.OutOfGame), - _ => false - }; - } + Team.Impostor => (target.IsMadmate() || target.GetCustomRole().IsImpostorTeamV2() || Framer.FramedPlayers.Contains(target.PlayerId)) && !target.Is(CustomRoles.Bloodlust), + Team.Neutral => target.GetCustomRole().IsNeutralTeamV2() || target.Is(CustomRoles.Bloodlust) || target.IsConverted(), + Team.Crewmate => target.GetCustomRole().IsCrewmateTeamV2(), + Team.None => target.Is(CustomRoles.GM) || target.Is(CountTypes.None) || target.Is(CountTypes.OutOfGame), + _ => false + }; + } - public static Team GetTeam(this PlayerControl target) - { - if (Framer.FramedPlayers.Contains(target.PlayerId)) return Team.Impostor; + public static Team GetTeam(this PlayerControl target) + { + if (Framer.FramedPlayers.Contains(target.PlayerId)) return Team.Impostor; - List subRoles = target.GetCustomSubRoles(); - if (subRoles.Contains(CustomRoles.Bloodlust) || target.IsConverted()) return Team.Neutral; + List subRoles = target.GetCustomSubRoles(); + if (subRoles.Contains(CustomRoles.Bloodlust) || target.IsConverted()) return Team.Neutral; - if (subRoles.Contains(CustomRoles.Madmate)) return Team.Impostor; + if (subRoles.Contains(CustomRoles.Madmate)) return Team.Impostor; - CustomRoles role = target.GetCustomRole(); - if (role.IsImpostorTeamV2()) return Team.Impostor; + CustomRoles role = target.GetCustomRole(); + if (role.IsImpostorTeamV2()) return Team.Impostor; - if (role.IsNeutralTeamV2()) return Team.Neutral; + if (role.IsNeutralTeamV2()) return Team.Neutral; - return role.IsCrewmateTeamV2() ? Team.Crewmate : Team.None; - } + return role.IsCrewmateTeamV2() ? Team.Crewmate : Team.None; + } - public static bool IsConverted(this PlayerControl target) - { - return target.GetCustomSubRoles().Any(x => x.IsConverted()); - } + public static bool IsConverted(this PlayerControl target) + { + return target.GetCustomSubRoles().Any(x => x.IsConverted()); + } - public static bool IsAlive(this PlayerControl target) - { - if (target == null || target.Is(CustomRoles.GM)) return false; + public static bool IsAlive(this PlayerControl target) + { + if (target == null || target.Is(CustomRoles.GM)) return false; - return GameStates.IsLobby || !Main.PlayerStates.TryGetValue(target.PlayerId, out PlayerState ps) || !ps.IsDead; - } + return GameStates.IsLobby || !Main.PlayerStates.TryGetValue(target.PlayerId, out PlayerState ps) || !ps.IsDead; + } - public static bool IsProtected(this PlayerControl self) - { - return self.protectedByGuardianId > -1; - } + public static bool IsProtected(this PlayerControl self) + { + return self.protectedByGuardianId > -1; } } \ No newline at end of file diff --git a/Modules/Extensions/CollectionExtensions.cs b/Modules/Extensions/CollectionExtensions.cs index 132878a80..7ad34db6e 100644 --- a/Modules/Extensions/CollectionExtensions.cs +++ b/Modules/Extensions/CollectionExtensions.cs @@ -5,247 +5,234 @@ using System.Linq; // ReSharper disable ConvertIfStatementToReturnStatement // ReSharper disable ForCanBeConvertedToForeach -namespace EHR +namespace EHR; + +public static class CollectionExtensions { - public static class CollectionExtensions + /// + /// Returns the key of a dictionary by its value + /// + /// The to search + /// The used to search for the corresponding key + /// The type of the keys in the + /// The type of the values in the + /// + /// The key of the that corresponds to the given , or the + /// default value of if the is not found in the + /// + /// + public static TKey GetKeyByValue(this Dictionary dictionary, TValue value) { - /// - /// Returns the key of a dictionary by its value - /// - /// The to search - /// The used to search for the corresponding key - /// The type of the keys in the - /// The type of the values in the - /// - /// The key of the that corresponds to the given , or the - /// default value of if the is not found in the - /// - /// - public static TKey GetKeyByValue(this Dictionary dictionary, TValue value) - { - foreach (KeyValuePair pair in dictionary) - if (pair.Value.Equals(value)) - return pair.Key; + foreach (KeyValuePair pair in dictionary) + if (pair.Value.Equals(value)) + return pair.Key; - return default; + return default; + } + + /// + /// Sets the value for all existing keys in a dictionary to a specific value + /// + /// + /// + /// + /// + public static void SetAllValues(this Dictionary dictionary, TValue value) + { + foreach (TKey key in dictionary.Keys.ToArray()) dictionary[key] = value; + } + + /// + /// Adjusts the value for all existing keys in a dictionary + /// + /// The dictionary to adjust the values of + /// The function to adjust the values with + /// + /// + public static void AdjustAllValues(this Dictionary dictionary, Func adjust) + { + foreach (TKey key in dictionary.Keys.ToArray()) dictionary[key] = adjust(dictionary[key]); + } + + /// + /// Returns a random element from a collection + /// + /// The collection + /// The type of the collection + /// + /// A random element from the collection, or the default value of if the collection is + /// empty + /// + public static T RandomElement(this IList collection) + { + if (collection.Count == 0) return default; + + return collection[IRandom.Instance.Next(collection.Count)]; + } + + /// + /// Returns a random element from a collection + /// + /// The collection + /// The type of the collection + /// + /// A random element from the collection, or the default value of if the collection is + /// empty + /// + public static T RandomElement(this IEnumerable collection) + { + if (collection is IList list) return list.RandomElement(); + + return collection.ToList().RandomElement(); + } + + /// + /// Combines multiple collections into a single collection + /// + /// The collection to start with + /// The other collections to add to + /// The type of the elements in the collections to combine + /// + /// A collection containing all elements of and all + /// + /// + public static IEnumerable CombineWith(this IEnumerable firstCollection, params IEnumerable[] collections) + { + return firstCollection.Concat(collections.Flatten()); + } + + /// + /// Executes an action for each element in a collection + /// + /// The collection to iterate over + /// The action to execute for each element + /// The type of the elements in the collection + public static void Do(this IEnumerable collection, Action action) + { + if (collection is List list) + { + for (var i = 0; i < list.Count; i++) action(list[i]); + + return; } - /// - /// Sets the value for all existing keys in a dictionary to a specific value - /// - /// - /// - /// - /// - public static void SetAllValues(this Dictionary dictionary, TValue value) - { - foreach (TKey key in dictionary.Keys.ToArray()) dictionary[key] = value; - } + foreach (T element in collection) action(element); + } - /// - /// Adjusts the value for all existing keys in a dictionary - /// - /// The dictionary to adjust the values of - /// The function to adjust the values with - /// - /// - public static void AdjustAllValues(this Dictionary dictionary, Func adjust) - { - foreach (TKey key in dictionary.Keys.ToArray()) dictionary[key] = adjust(dictionary[key]); - } - - /// - /// Returns a random element from a collection - /// - /// The collection - /// The type of the collection - /// - /// A random element from the collection, or the default value of if the collection is - /// empty - /// - public static T RandomElement(this IList collection) - { - if (collection.Count == 0) return default; - - return collection[IRandom.Instance.Next(collection.Count)]; - } - - /// - /// Returns a random element from a collection - /// - /// The collection - /// The type of the collection - /// - /// A random element from the collection, or the default value of if the collection is - /// empty - /// - public static T RandomElement(this IEnumerable collection) - { - if (collection is IList list) return list.RandomElement(); - - return collection.ToList().RandomElement(); - } - - /// - /// Combines multiple collections into a single collection - /// - /// The collection to start with - /// The other collections to add to - /// The type of the elements in the collections to combine - /// - /// A collection containing all elements of and all - /// - /// - public static IEnumerable CombineWith(this IEnumerable firstCollection, params IEnumerable[] collections) - { - return firstCollection.Concat(collections.Flatten()); - } - - /// - /// Executes an action for each element in a collection - /// - /// The collection to iterate over - /// The action to execute for each element - /// The type of the elements in the collection - public static void Do(this IEnumerable collection, Action action) - { - if (collection is List list) - { - for (var i = 0; i < list.Count; i++) action(list[i]); - - return; - } - - foreach (T element in collection) action(element); - } - - /// - /// Executes an action for each element in a collection if the predicate is true - /// - /// The collection to iterate over - /// Whether to use a fast loop or linq - /// The predicate to check for each element - /// The action to execute for each element that satisfies the predicate - /// The type of the elements in the collection - public static void DoIf(this IEnumerable collection, Func predicate, Action action, bool fast = true) - { - if (fast) - { - if (collection is List list) - { - for (var i = 0; i < list.Count; i++) - { - T element = list[i]; - if (predicate(element)) action(element); - } - - return; - } - - foreach (T element in collection) - if (predicate(element)) - action(element); - - return; - } - - collection.Where(predicate).ToArray().Do(action); - } - - /// - /// Splits a collection into two collections based on a predicate - /// - /// The collection to split - /// The predicate to split the collection by - /// The type of the elements in the collection - /// - /// A tuple containing two collections: one with elements that satisfy the predicate, and one with elements that - /// do not - /// - public static (List TrueList, List FalseList) Split(this IEnumerable collection, Func predicate) - { - var list1 = new List(); - var list2 = new List(); - - foreach (T element in collection) - { - if (predicate(element)) - list1.Add(element); - else - list2.Add(element); - } - - return (list1, list2); - } - - /// - /// Adds a range of elements to a dictionary - /// - /// The dictionary to add elements to - /// The dictionary containing the elements to add - /// - /// Whether to override existing keys in the with the - /// same keys in the dictionary. If true, the same keys in the - /// will be overwritten with the values from the dictionary. - /// If false, the same keys in the will be kept and the values from the - /// dictionary will be ignored - /// - /// The type of the keys in the dictionaries - /// The type of the values in the dictionaries - /// The with the elements from the dictionary added - public static Dictionary AddRange(this Dictionary dictionary, Dictionary other, bool overrideExistingKeys = true) - { - foreach ((TKey key, TValue value) in other) - if (overrideExistingKeys || !dictionary.ContainsKey(key)) - dictionary[key] = value; - - return dictionary; - } - - /// - /// Flattens a collection of collections into a single collection - /// - /// The collection of collections to flatten - /// The type of the elements in the collections - /// A single collection containing all elements of the collections in - public static IEnumerable Flatten(this IEnumerable> collection) - { - return collection.SelectMany(x => x); - } - - /// - /// Determines whether a collection contains any elements that satisfy a predicate and returns the first element that - /// satisfies the predicate - /// - /// The collection to search - /// The predicate to check for each element - /// - /// The first element that satisfies the predicate, or the default value of - /// if no elements satisfy the predicate - /// - /// The type of the elements in the collection - /// true if the collection contains any elements that satisfy the predicate, false otherwise - public static bool FindFirst(this IEnumerable collection, Func predicate, out T element) + /// + /// Executes an action for each element in a collection if the predicate is true + /// + /// The collection to iterate over + /// Whether to use a fast loop or linq + /// The predicate to check for each element + /// The action to execute for each element that satisfies the predicate + /// The type of the elements in the collection + public static void DoIf(this IEnumerable collection, Func predicate, Action action, bool fast = true) + { + if (fast) { if (collection is List list) { for (var i = 0; i < list.Count; i++) { - T item = list[i]; - - if (predicate(item)) - { - element = item; - return true; - } + T element = list[i]; + if (predicate(element)) action(element); } - element = default; - return false; + return; } - foreach (T item in collection) + foreach (T element in collection) + if (predicate(element)) + action(element); + + return; + } + + collection.Where(predicate).ToArray().Do(action); + } + + /// + /// Splits a collection into two collections based on a predicate + /// + /// The collection to split + /// The predicate to split the collection by + /// The type of the elements in the collection + /// + /// A tuple containing two collections: one with elements that satisfy the predicate, and one with elements that + /// do not + /// + public static (List TrueList, List FalseList) Split(this IEnumerable collection, Func predicate) + { + var list1 = new List(); + var list2 = new List(); + + foreach (T element in collection) + { + if (predicate(element)) + list1.Add(element); + else + list2.Add(element); + } + + return (list1, list2); + } + + /// + /// Adds a range of elements to a dictionary + /// + /// The dictionary to add elements to + /// The dictionary containing the elements to add + /// + /// Whether to override existing keys in the with the + /// same keys in the dictionary. If true, the same keys in the + /// will be overwritten with the values from the dictionary. + /// If false, the same keys in the will be kept and the values from the + /// dictionary will be ignored + /// + /// The type of the keys in the dictionaries + /// The type of the values in the dictionaries + /// The with the elements from the dictionary added + public static Dictionary AddRange(this Dictionary dictionary, Dictionary other, bool overrideExistingKeys = true) + { + foreach ((TKey key, TValue value) in other) + if (overrideExistingKeys || !dictionary.ContainsKey(key)) + dictionary[key] = value; + + return dictionary; + } + + /// + /// Flattens a collection of collections into a single collection + /// + /// The collection of collections to flatten + /// The type of the elements in the collections + /// A single collection containing all elements of the collections in + public static IEnumerable Flatten(this IEnumerable> collection) + { + return collection.SelectMany(x => x); + } + + /// + /// Determines whether a collection contains any elements that satisfy a predicate and returns the first element that + /// satisfies the predicate + /// + /// The collection to search + /// The predicate to check for each element + /// + /// The first element that satisfies the predicate, or the default value of + /// if no elements satisfy the predicate + /// + /// The type of the elements in the collection + /// true if the collection contains any elements that satisfy the predicate, false otherwise + public static bool FindFirst(this IEnumerable collection, Func predicate, out T element) + { + if (collection is List list) + { + for (var i = 0; i < list.Count; i++) { + T item = list[i]; + if (predicate(item)) { element = item; @@ -257,205 +244,217 @@ namespace EHR return false; } - #region ToValidPlayers - - /// - /// Converts a collection of player IDs to a collection of instances - /// - /// - /// - public static IEnumerable ToValidPlayers(this IEnumerable playerIds) + foreach (T item in collection) { - return playerIds.Select(Utils.GetPlayer).Where(x => x != null); - } - - /// - /// Converts a list of player IDs to a collection of instances - /// - /// - /// - public static List ToValidPlayers(this List playerIds) - { - return playerIds.ConvertAll(Utils.GetPlayer).FindAll(x => x != null); - } - - #endregion - - #region Without - - /// - /// Removes an element from a collection - /// - /// The collection to remove the element from - /// The element to remove - /// The type of the elements in the collection - /// - /// A collection containing all elements of except for - /// - public static IEnumerable Without(this IEnumerable collection, T element) - { - return collection.Where(x => !x.Equals(element)); - } - - /// - /// Removes an element from a collection - /// - /// The collection to remove the element from - /// The element to remove - /// - /// A collection containing all elements of except for - /// - public static IEnumerable Without(this IEnumerable collection, PlayerControl element) - { - return collection.Where(x => x.PlayerId != element.PlayerId); - } - - /// - /// Removes an element from a collection - /// - /// The collection to remove the element from - /// The element to remove - /// - /// A collection containing all elements of except for - /// - public static IEnumerable Without(this IEnumerable collection, PlainShipRoom element) - { - return collection.Where(x => x != element); - } - - #endregion - - #region Shuffle - - /// - /// Shuffles all elements in a collection randomly - /// - /// The type of the collection - /// The collection to be shuffled - /// A new, shuffled collection as a - public static List Shuffle(this IEnumerable collection) - { - List list = collection.ToList(); - int n = list.Count; - var r = IRandom.Instance; - - while (n > 1) + if (predicate(item)) { - n--; - int k = r.Next(n + 1); - (list[n], list[k]) = (list[k], list[n]); - } - - return list; - } - - /// - /// Shuffles all elements in a collection randomly - /// - /// The collection to be shuffled - /// The type of the collection - /// The same collection with its elements shuffled - public static List Shuffle(this List collection) - { - int n = collection.Count; - var r = IRandom.Instance; - - while (n > 1) - { - n--; - int k = r.Next(n + 1); - (collection[n], collection[k]) = (collection[k], collection[n]); - } - - return collection; - } - - /// - /// Shuffles all elements in an array randomly - /// - /// The array to be shuffled - /// The type of the array - /// The same array with its elements shuffled - public static T[] Shuffle(this T[] collection) - { - int n = collection.Length; - var r = IRandom.Instance; - - while (n > 1) - { - n--; - int k = r.Next(n + 1); - (collection[n], collection[k]) = (collection[k], collection[n]); - } - - return collection; - } - - #endregion - - #region Partition - - /// - /// Partitions a collection into a specified number of parts - /// - /// The collection to partition - /// The number of parts to partition the collection into - /// The type of the elements in the collection - /// A collection of collections, each containing a part of the original collection - public static IEnumerable> Partition(this IEnumerable collection, int parts) - { - List list = collection.ToList(); - int length = list.Count; - if (parts <= 0 || length == 0) yield break; - - if (parts > length) parts = length; - - int size = length / parts; - int remainder = length % parts; - var index = 0; - - for (var i = 0; i < parts; i++) - { - int partSize = size + (i < remainder ? 1 : 0); - yield return list.Skip(index).Take(partSize); - index += partSize; + element = item; + return true; } } - /// - /// Partitions a list into a specified number of parts - /// - /// The list to partition - /// The number of parts to partition the list into - /// The type of the elements in the list - /// A list of lists, each containing a part of the original list - public static IEnumerable> Partition(this IList collection, int parts) - { - int length = collection.Count; - if (parts <= 0 || length == 0) yield break; - - if (parts > length) parts = length; - - int size = length / parts; - int remainder = length % parts; - var index = 0; - - for (var i = 0; i < parts; i++) - { - int partSize = size + (i < remainder ? 1 : 0); - yield return collection.Skip(index).Take(partSize); - index += partSize; - } - } - - #endregion + element = default; + return false; } - public static class Loop + #region ToValidPlayers + + /// + /// Converts a collection of player IDs to a collection of instances + /// + /// + /// + public static IEnumerable ToValidPlayers(this IEnumerable playerIds) { - public static void Times(int count, Action action) + return playerIds.Select(Utils.GetPlayer).Where(x => x != null); + } + + /// + /// Converts a list of player IDs to a collection of instances + /// + /// + /// + public static List ToValidPlayers(this List playerIds) + { + return playerIds.ConvertAll(Utils.GetPlayer).FindAll(x => x != null); + } + + #endregion + + #region Without + + /// + /// Removes an element from a collection + /// + /// The collection to remove the element from + /// The element to remove + /// The type of the elements in the collection + /// + /// A collection containing all elements of except for + /// + public static IEnumerable Without(this IEnumerable collection, T element) + { + return collection.Where(x => !x.Equals(element)); + } + + /// + /// Removes an element from a collection + /// + /// The collection to remove the element from + /// The element to remove + /// + /// A collection containing all elements of except for + /// + public static IEnumerable Without(this IEnumerable collection, PlayerControl element) + { + return collection.Where(x => x.PlayerId != element.PlayerId); + } + + /// + /// Removes an element from a collection + /// + /// The collection to remove the element from + /// The element to remove + /// + /// A collection containing all elements of except for + /// + public static IEnumerable Without(this IEnumerable collection, PlainShipRoom element) + { + return collection.Where(x => x != element); + } + + #endregion + + #region Shuffle + + /// + /// Shuffles all elements in a collection randomly + /// + /// The type of the collection + /// The collection to be shuffled + /// A new, shuffled collection as a + public static List Shuffle(this IEnumerable collection) + { + List list = collection.ToList(); + int n = list.Count; + var r = IRandom.Instance; + + while (n > 1) { - for (var i = 0; i < count; i++) action(i); + n--; + int k = r.Next(n + 1); + (list[n], list[k]) = (list[k], list[n]); } + + return list; + } + + /// + /// Shuffles all elements in a collection randomly + /// + /// The collection to be shuffled + /// The type of the collection + /// The same collection with its elements shuffled + public static List Shuffle(this List collection) + { + int n = collection.Count; + var r = IRandom.Instance; + + while (n > 1) + { + n--; + int k = r.Next(n + 1); + (collection[n], collection[k]) = (collection[k], collection[n]); + } + + return collection; + } + + /// + /// Shuffles all elements in an array randomly + /// + /// The array to be shuffled + /// The type of the array + /// The same array with its elements shuffled + public static T[] Shuffle(this T[] collection) + { + int n = collection.Length; + var r = IRandom.Instance; + + while (n > 1) + { + n--; + int k = r.Next(n + 1); + (collection[n], collection[k]) = (collection[k], collection[n]); + } + + return collection; + } + + #endregion + + #region Partition + + /// + /// Partitions a collection into a specified number of parts + /// + /// The collection to partition + /// The number of parts to partition the collection into + /// The type of the elements in the collection + /// A collection of collections, each containing a part of the original collection + public static IEnumerable> Partition(this IEnumerable collection, int parts) + { + List list = collection.ToList(); + int length = list.Count; + if (parts <= 0 || length == 0) yield break; + + if (parts > length) parts = length; + + int size = length / parts; + int remainder = length % parts; + var index = 0; + + for (var i = 0; i < parts; i++) + { + int partSize = size + (i < remainder ? 1 : 0); + yield return list.Skip(index).Take(partSize); + index += partSize; + } + } + + /// + /// Partitions a list into a specified number of parts + /// + /// The list to partition + /// The number of parts to partition the list into + /// The type of the elements in the list + /// A list of lists, each containing a part of the original list + public static IEnumerable> Partition(this IList collection, int parts) + { + int length = collection.Count; + if (parts <= 0 || length == 0) yield break; + + if (parts > length) parts = length; + + int size = length / parts; + int remainder = length % parts; + var index = 0; + + for (var i = 0; i < parts; i++) + { + int partSize = size + (i < remainder ? 1 : 0); + yield return collection.Skip(index).Take(partSize); + index += partSize; + } + } + + #endregion +} + +public static class Loop +{ + public static void Times(int count, Action action) + { + for (var i = 0; i < count; i++) action(i); } } \ No newline at end of file diff --git a/Modules/Extensions/HazelExtensions.cs b/Modules/Extensions/HazelExtensions.cs index 30ef05849..9d81d4550 100644 --- a/Modules/Extensions/HazelExtensions.cs +++ b/Modules/Extensions/HazelExtensions.cs @@ -1,52 +1,51 @@ using Hazel; using UnityEngine; -namespace EHR +namespace EHR; + +public static class HazelExtensions { - public static class HazelExtensions + public static void Write(this MessageWriter writer, Vector2 vector) { - public static void Write(this MessageWriter writer, Vector2 vector) - { - NetHelpers.WriteVector2(vector, writer); - } + NetHelpers.WriteVector2(vector, writer); + } - public static void Write(this MessageWriter writer, Vector3 vector) - { - writer.Write(vector.x); - writer.Write(vector.y); - writer.Write(vector.z); - } + public static void Write(this MessageWriter writer, Vector3 vector) + { + writer.Write(vector.x); + writer.Write(vector.y); + writer.Write(vector.z); + } - public static void Write(this MessageWriter writer, Color color) - { - writer.Write(color.r); - writer.Write(color.g); - writer.Write(color.b); - writer.Write(color.a); - } + public static void Write(this MessageWriter writer, Color color) + { + writer.Write(color.r); + writer.Write(color.g); + writer.Write(color.b); + writer.Write(color.a); + } - // ------------------------------------------------------------------------------------------------------------------------- + // ------------------------------------------------------------------------------------------------------------------------- - public static Vector2 ReadVector2(this MessageReader reader) - { - return NetHelpers.ReadVector2(reader); - } + public static Vector2 ReadVector2(this MessageReader reader) + { + return NetHelpers.ReadVector2(reader); + } - public static Vector3 ReadVector3(this MessageReader reader) - { - float x = reader.ReadSingle(); - float y = reader.ReadSingle(); - float z = reader.ReadSingle(); - return new(x, y, z); - } + public static Vector3 ReadVector3(this MessageReader reader) + { + float x = reader.ReadSingle(); + float y = reader.ReadSingle(); + float z = reader.ReadSingle(); + return new(x, y, z); + } - public static Color ReadColor(this MessageReader reader) - { - float r = reader.ReadSingle(); - float g = reader.ReadSingle(); - float b = reader.ReadSingle(); - float a = reader.ReadSingle(); - return new(r, g, b, a); - } + public static Color ReadColor(this MessageReader reader) + { + float r = reader.ReadSingle(); + float g = reader.ReadSingle(); + float b = reader.ReadSingle(); + float a = reader.ReadSingle(); + return new(r, g, b, a); } } \ No newline at end of file diff --git a/Modules/Extensions/IGameManagerEx.cs b/Modules/Extensions/IGameManagerEx.cs index a24773cf9..f0d4da371 100644 --- a/Modules/Extensions/IGameManagerEx.cs +++ b/Modules/Extensions/IGameManagerEx.cs @@ -1,82 +1,81 @@ using AmongUs.GameOptions; -namespace EHR.Modules.Extensions +namespace EHR.Modules.Extensions; + +public static class IGameManagerEx { - public static class IGameManagerEx + public static void Set(this BoolOptionNames name, bool value, IGameOptions opt) { - public static void Set(this BoolOptionNames name, bool value, IGameOptions opt) - { - opt.SetBool(name, value); - } + opt.SetBool(name, value); + } - public static void Set(this BoolOptionNames name, bool value, NormalGameOptionsV08 opt) - { - if (name != BoolOptionNames.GhostsDoTasks) opt.SetBool(name, value); - } + public static void Set(this BoolOptionNames name, bool value, NormalGameOptionsV08 opt) + { + if (name != BoolOptionNames.GhostsDoTasks) opt.SetBool(name, value); + } - public static void Set(this BoolOptionNames name, bool value, HideNSeekGameOptionsV08 opt) - { - opt.SetBool(name, value); - } + public static void Set(this BoolOptionNames name, bool value, HideNSeekGameOptionsV08 opt) + { + opt.SetBool(name, value); + } - public static void Set(this Int32OptionNames name, int value, IGameOptions opt) - { - opt.SetInt(name, value); - } + public static void Set(this Int32OptionNames name, int value, IGameOptions opt) + { + opt.SetInt(name, value); + } - public static void Set(this Int32OptionNames name, int value, NormalGameOptionsV08 opt) - { - opt.SetInt(name, value); - } + public static void Set(this Int32OptionNames name, int value, NormalGameOptionsV08 opt) + { + opt.SetInt(name, value); + } - public static void Set(this Int32OptionNames name, int value, HideNSeekGameOptionsV08 opt) - { - opt.SetInt(name, value); - } + public static void Set(this Int32OptionNames name, int value, HideNSeekGameOptionsV08 opt) + { + opt.SetInt(name, value); + } - public static void Set(this FloatOptionNames name, float value, IGameOptions opt) - { - opt.SetFloat(name, value); - } + public static void Set(this FloatOptionNames name, float value, IGameOptions opt) + { + opt.SetFloat(name, value); + } - public static void Set(this FloatOptionNames name, float value, NormalGameOptionsV08 opt) - { - opt.SetFloat(name, value); - } + public static void Set(this FloatOptionNames name, float value, NormalGameOptionsV08 opt) + { + opt.SetFloat(name, value); + } - public static void Set(this FloatOptionNames name, float value, HideNSeekGameOptionsV08 opt) - { - opt.SetFloat(name, value); - } + public static void Set(this FloatOptionNames name, float value, HideNSeekGameOptionsV08 opt) + { + opt.SetFloat(name, value); + } - public static void Set(this ByteOptionNames name, byte value, IGameOptions opt) - { - opt.SetByte(name, value); - } + public static void Set(this ByteOptionNames name, byte value, IGameOptions opt) + { + opt.SetByte(name, value); + } - public static void Set(this ByteOptionNames name, byte value, NormalGameOptionsV08 opt) - { - opt.SetByte(name, value); - } + public static void Set(this ByteOptionNames name, byte value, NormalGameOptionsV08 opt) + { + opt.SetByte(name, value); + } - public static void Set(this ByteOptionNames name, byte value, HideNSeekGameOptionsV08 opt) - { - opt.SetByte(name, value); - } + public static void Set(this ByteOptionNames name, byte value, HideNSeekGameOptionsV08 opt) + { + opt.SetByte(name, value); + } - public static void Set(this UInt32OptionNames name, uint value, IGameOptions opt) - { - opt.SetUInt(name, value); - } + public static void Set(this UInt32OptionNames name, uint value, IGameOptions opt) + { + opt.SetUInt(name, value); + } - public static void Set(this UInt32OptionNames name, uint value, NormalGameOptionsV08 opt) - { - opt.SetUInt(name, value); - } + public static void Set(this UInt32OptionNames name, uint value, NormalGameOptionsV08 opt) + { + opt.SetUInt(name, value); + } - public static void Set(this UInt32OptionNames name, uint value, HideNSeekGameOptionsV08 opt) - { - opt.SetUInt(name, value); - } + public static void Set(this UInt32OptionNames name, uint value, HideNSeekGameOptionsV08 opt) + { + opt.SetUInt(name, value); } } \ No newline at end of file diff --git a/Modules/Extensions/ObjectHelper.cs b/Modules/Extensions/ObjectHelper.cs index 8be720214..87534d198 100644 --- a/Modules/Extensions/ObjectHelper.cs +++ b/Modules/Extensions/ObjectHelper.cs @@ -1,44 +1,43 @@ using Il2CppSystem; using UnityEngine; -namespace EHR +namespace EHR; + +public static class ObjectHelper { - public static class ObjectHelper + public static void DestroyTranslator(this GameObject obj) { - public static void DestroyTranslator(this GameObject obj) + if (obj == null) return; + + obj.ForEachChild((Action)DestroyTranslator); // False error + TextTranslatorTMP[] translator = obj.GetComponentsInChildren(true); + translator?.Do(Object.Destroy); + } + + public static void DestroyTranslator(this MonoBehaviour obj) + { + obj?.gameObject.DestroyTranslator(); + } + + // From: Project Lotus - by Discussions + public static bool HasParentInHierarchy(this GameObject obj, string parentPath) + { + string[] pathParts = parentPath.Split('/'); + int pathIndex = pathParts.Length - 1; + Transform current = obj.transform; + + while (current != null) { - if (obj == null) return; - - obj.ForEachChild((Action)DestroyTranslator); // False error - TextTranslatorTMP[] translator = obj.GetComponentsInChildren(true); - translator?.Do(Object.Destroy); - } - - public static void DestroyTranslator(this MonoBehaviour obj) - { - obj?.gameObject.DestroyTranslator(); - } - - // From: Project Lotus - by Discussions - public static bool HasParentInHierarchy(this GameObject obj, string parentPath) - { - string[] pathParts = parentPath.Split('/'); - int pathIndex = pathParts.Length - 1; - Transform current = obj.transform; - - while (current != null) + if (current.name == pathParts[pathIndex]) { - if (current.name == pathParts[pathIndex]) - { - pathIndex--; - if (pathIndex < 0) return true; - } - else pathIndex = pathParts.Length - 1; - - current = current.parent; + pathIndex--; + if (pathIndex < 0) return true; } + else pathIndex = pathParts.Length - 1; - return false; + current = current.parent; } + + return false; } } \ No newline at end of file diff --git a/Modules/GameOptionsSender/GameOptionsSender.cs b/Modules/GameOptionsSender/GameOptionsSender.cs index 9ae8a09ff..eaeaefece 100644 --- a/Modules/GameOptionsSender/GameOptionsSender.cs +++ b/Modules/GameOptionsSender/GameOptionsSender.cs @@ -8,132 +8,131 @@ using InnerNet; using Array = Il2CppSystem.Array; using Buffer = Il2CppSystem.Buffer; -namespace EHR.Modules +namespace EHR.Modules; + +public abstract class GameOptionsSender { - public abstract class GameOptionsSender + protected abstract bool IsDirty { get; set; } + + protected virtual void SendGameOptions() { - protected abstract bool IsDirty { get; set; } + IGameOptions opt = BuildGameOptions(); - protected virtual void SendGameOptions() + // option => byte[] + MessageWriter writer = MessageWriter.Get(); + writer.Write(opt.Version); + writer.StartMessage(0); + writer.Write((byte)opt.GameMode); + + if (opt.TryCast(out NormalGameOptionsV08 normalOpt)) + NormalGameOptionsV08.Serialize(writer, normalOpt); + else if (opt.TryCast(out HideNSeekGameOptionsV08 hnsOpt)) + HideNSeekGameOptionsV08.Serialize(writer, hnsOpt); + else { - IGameOptions opt = BuildGameOptions(); - - // option => byte[] - MessageWriter writer = MessageWriter.Get(); - writer.Write(opt.Version); - writer.StartMessage(0); - writer.Write((byte)opt.GameMode); - - if (opt.TryCast(out NormalGameOptionsV08 normalOpt)) - NormalGameOptionsV08.Serialize(writer, normalOpt); - else if (opt.TryCast(out HideNSeekGameOptionsV08 hnsOpt)) - HideNSeekGameOptionsV08.Serialize(writer, hnsOpt); - else - { - writer.Recycle(); - Logger.Error("Option cast failed", ToString()); - } - - writer.EndMessage(); - - // Array & Send - var byteArray = new Il2CppStructArray(writer.Length - 1); - // MessageWriter.ToByteArray - Buffer.BlockCopy(writer.Buffer.Cast(), 1, byteArray.Cast(), 0, writer.Length - 1); - - SendOptionsArray(byteArray); writer.Recycle(); + Logger.Error("Option cast failed", ToString()); } - protected virtual void SendOptionsArray(Il2CppStructArray optionArray) + writer.EndMessage(); + + // Array & Send + var byteArray = new Il2CppStructArray(writer.Length - 1); + // MessageWriter.ToByteArray + Buffer.BlockCopy(writer.Buffer.Cast(), 1, byteArray.Cast(), 0, writer.Length - 1); + + SendOptionsArray(byteArray); + writer.Recycle(); + } + + protected virtual void SendOptionsArray(Il2CppStructArray optionArray) + { + for (byte i = 0; i < GameManager.Instance.LogicComponents.Count; i++) { - for (byte i = 0; i < GameManager.Instance.LogicComponents.Count; i++) - { - Il2CppSystem.Object logicComponent = GameManager.Instance.LogicComponents[(Index)i]; - if (logicComponent.TryCast(out _)) SendOptionsArray(optionArray, i, -1); - } + Il2CppSystem.Object logicComponent = GameManager.Instance.LogicComponents[(Index)i]; + if (logicComponent.TryCast(out _)) SendOptionsArray(optionArray, i, -1); } + } - protected static void SendOptionsArray(Il2CppStructArray optionArray, byte LogicOptionsIndex, int targetClientId) + protected static void SendOptionsArray(Il2CppStructArray optionArray, byte LogicOptionsIndex, int targetClientId) + { + try { - try - { - MessageWriter writer = MessageWriter.Get(SendOption.Reliable); + MessageWriter writer = MessageWriter.Get(SendOption.Reliable); - writer.StartMessage(targetClientId == -1 ? Tags.GameData : Tags.GameDataTo); + writer.StartMessage(targetClientId == -1 ? Tags.GameData : Tags.GameDataTo); + + { + writer.Write(AmongUsClient.Instance.GameId); + if (targetClientId != -1) writer.WritePacked(targetClientId); + + writer.StartMessage(1); { - writer.Write(AmongUsClient.Instance.GameId); - if (targetClientId != -1) writer.WritePacked(targetClientId); - - writer.StartMessage(1); + writer.WritePacked(GameManager.Instance.NetId); + writer.StartMessage(LogicOptionsIndex); { - writer.WritePacked(GameManager.Instance.NetId); - writer.StartMessage(LogicOptionsIndex); - - { - writer.WriteBytesAndSize(optionArray); - } - - writer.EndMessage(); + writer.WriteBytesAndSize(optionArray); } writer.EndMessage(); } writer.EndMessage(); + } - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); - } - catch (Exception ex) - { - Logger.Fatal(ex.ToString(), "GameOptionsSender.SendOptionsArray"); - } + writer.EndMessage(); + + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); } - - protected abstract IGameOptions BuildGameOptions(); - - protected virtual bool AmValid() + catch (Exception ex) { - return true; + Logger.Fatal(ex.ToString(), "GameOptionsSender.SendOptionsArray"); } - - #region Static - - public static readonly List AllSenders = new(15) { new NormalGameOptionsSender() }; - - public static IEnumerator SendAllGameOptionsAsync() - { - AllSenders.RemoveAll(s => s == null || !s.AmValid()); - - foreach (GameOptionsSender sender in AllSenders.ToArray()) - { - if (sender.IsDirty) - { - sender.SendGameOptions(); - yield return null; - } - - sender.IsDirty = false; - } - } - - public static void SendAllGameOptions() - { - AllSenders.RemoveAll(s => s == null || !s.AmValid()); - - // ReSharper disable once ForCanBeConvertedToForeach - for (var index = 0; index < AllSenders.Count; index++) - { - GameOptionsSender sender = AllSenders[index]; - if (sender.IsDirty) sender.SendGameOptions(); - - sender.IsDirty = false; - } - } - - #endregion } + + protected abstract IGameOptions BuildGameOptions(); + + protected virtual bool AmValid() + { + return true; + } + + #region Static + + public static readonly List AllSenders = new(15) { new NormalGameOptionsSender() }; + + public static IEnumerator SendAllGameOptionsAsync() + { + AllSenders.RemoveAll(s => s == null || !s.AmValid()); + + foreach (GameOptionsSender sender in AllSenders.ToArray()) + { + if (sender.IsDirty) + { + sender.SendGameOptions(); + yield return null; + } + + sender.IsDirty = false; + } + } + + public static void SendAllGameOptions() + { + AllSenders.RemoveAll(s => s == null || !s.AmValid()); + + // ReSharper disable once ForCanBeConvertedToForeach + for (var index = 0; index < AllSenders.Count; index++) + { + GameOptionsSender sender = AllSenders[index]; + if (sender.IsDirty) sender.SendGameOptions(); + + sender.IsDirty = false; + } + } + + #endregion } \ No newline at end of file diff --git a/Modules/GameOptionsSender/NormalGameOptionsSender.cs b/Modules/GameOptionsSender/NormalGameOptionsSender.cs index 371592e0a..d482dcad9 100644 --- a/Modules/GameOptionsSender/NormalGameOptionsSender.cs +++ b/Modules/GameOptionsSender/NormalGameOptionsSender.cs @@ -1,40 +1,39 @@ using System; using AmongUs.GameOptions; -namespace EHR.Modules +namespace EHR.Modules; + +public sealed class NormalGameOptionsSender : GameOptionsSender { - public sealed class NormalGameOptionsSender : GameOptionsSender + private LogicOptions _logicOptions; + private static IGameOptions BasedGameOptions => GameOptionsManager.Instance.CurrentGameOptions; + + protected override bool IsDirty { - private LogicOptions _logicOptions; - private static IGameOptions BasedGameOptions => GameOptionsManager.Instance.CurrentGameOptions; - - protected override bool IsDirty + get { - get + try { - try + if (GameManager.Instance != null && GameManager.Instance.LogicComponents != null && (_logicOptions == null || !GameManager.Instance.LogicComponents.Contains(_logicOptions))) { - if (GameManager.Instance != null && GameManager.Instance.LogicComponents != null && (_logicOptions == null || !GameManager.Instance.LogicComponents.Contains(_logicOptions))) - { - foreach (GameLogicComponent glc in GameManager.Instance.LogicComponents) - if (glc.TryCast(out LogicOptions lo)) - _logicOptions = lo; - } + foreach (GameLogicComponent glc in GameManager.Instance.LogicComponents) + if (glc.TryCast(out LogicOptions lo)) + _logicOptions = lo; + } - return _logicOptions is { IsDirty: true }; - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "NormalGameOptionsSender.IsDirty.Get"); - return _logicOptions is { IsDirty: true }; - } + return _logicOptions is { IsDirty: true }; + } + catch (Exception ex) + { + Logger.Error(ex.ToString(), "NormalGameOptionsSender.IsDirty.Get"); + return _logicOptions is { IsDirty: true }; } - set => _logicOptions?.ClearDirtyFlag(); } + set => _logicOptions?.ClearDirtyFlag(); + } - protected override IGameOptions BuildGameOptions() - { - return BasedGameOptions; - } + protected override IGameOptions BuildGameOptions() + { + return BasedGameOptions; } } \ No newline at end of file diff --git a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs index f6e33fbe2..846cdc38e 100644 --- a/Modules/GameOptionsSender/PlayerGameOptionsSender.cs +++ b/Modules/GameOptionsSender/PlayerGameOptionsSender.cs @@ -15,451 +15,450 @@ using Mathf = UnityEngine.Mathf; // ReSharper disable ForCanBeConvertedToForeach -namespace EHR.Modules +namespace EHR.Modules; + +public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsSender { - public sealed class PlayerGameOptionsSender(PlayerControl player) : GameOptionsSender + private PlayerControl player = player; + + private static IGameOptions BasedGameOptions => + Main.RealOptionsData.Restore(new NormalGameOptionsV08(new UnityLogger().Cast()).Cast()); + + protected override bool IsDirty { get; set; } + + public static void SetDirty(byte playerId) { - private PlayerControl player = player; - - private static IGameOptions BasedGameOptions => - Main.RealOptionsData.Restore(new NormalGameOptionsV08(new UnityLogger().Cast()).Cast()); - - protected override bool IsDirty { get; set; } - - public static void SetDirty(byte playerId) + for (var index = 0; index < AllSenders.Count; index++) { - for (var index = 0; index < AllSenders.Count; index++) + GameOptionsSender allSender = AllSenders[index]; + + if (allSender is PlayerGameOptionsSender sender && sender.player.PlayerId == playerId) { - 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 - } + sender.SetDirty(); + break; // Only one sender can have the same player id } } + } - public static void SetDirtyToAll() + public static void SetDirtyToAll() + { + for (var index = 0; index < AllSenders.Count; index++) { - for (var index = 0; index < AllSenders.Count; index++) - { - GameOptionsSender allSender = AllSenders[index]; - if (allSender is PlayerGameOptionsSender sender) sender.SetDirty(); - } + 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.GetCustomRole().NeedUpdateOnLights() || 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 lights call/fix - public static void SetDirtyToAllV2() + // For players with kill buttons + public static void SetDirtyToAllV4() + { + for (var index = 0; index < AllSenders.Count; index++) { - for (var index = 0; index < AllSenders.Count; index++) - { - GameOptionsSender allSender = AllSenders[index]; - if (allSender is PlayerGameOptionsSender { IsDirty: false } sender && sender.player.IsAlive() && (sender.player.GetCustomRole().NeedUpdateOnLights() || sender.player.Is(CustomRoles.Torch) || sender.player.Is(CustomRoles.Mare) || sender.player.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(sender.player.PlayerId))) sender.SetDirty(); - } + GameOptionsSender allSender = AllSenders[index]; + if (allSender is PlayerGameOptionsSender { IsDirty: false } sender && sender.player.IsAlive() && sender.player.CanUseKillButton()) sender.SetDirty(); } + } + + private void SetDirty() + { + IsDirty = true; + } - // For Grenadier blidning/restoring - public static void SetDirtyToAllV3() + protected override void SendGameOptions() + { + if (player.AmOwner) { - for (var index = 0; index < AllSenders.Count; index++) + IGameOptions opt = BuildGameOptions(); + + if (GameManager.Instance?.LogicComponents != null) { - 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(); + foreach (GameLogicComponent com in GameManager.Instance.LogicComponents) + if (com.TryCast(out LogicOptions lo)) + lo.SetGameOptions(opt); } + + GameOptionsManager.Instance.CurrentGameOptions = opt; } + else + base.SendGameOptions(); + } - // For players with kill buttons - public static void SetDirtyToAllV4() + protected override void SendOptionsArray(Il2CppStructArray optionArray) + { + try { - for (var index = 0; index < AllSenders.Count; index++) + for (byte i = 0; i < GameManager.Instance.LogicComponents.Count; i++) { - GameOptionsSender allSender = AllSenders[index]; - if (allSender is PlayerGameOptionsSender { IsDirty: false } sender && sender.player.IsAlive() && sender.player.CanUseKillButton()) sender.SetDirty(); + Il2CppSystem.Object logicComponent = GameManager.Instance.LogicComponents[(Index)i]; + if (logicComponent.TryCast(out _)) SendOptionsArray(optionArray, i, player.GetClientId()); } } - - private void SetDirty() + catch (Exception ex) { - IsDirty = true; + Logger.Fatal(ex.ToString(), "PlayerGameOptionsSender.SendOptionsArray"); } + } - protected override void SendGameOptions() + 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); + } + + protected override IGameOptions BuildGameOptions() + { + try { - if (player.AmOwner) + 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(); + + switch (Options.CurrentGameMode) { - IGameOptions opt = BuildGameOptions(); + case CustomGameMode.AllInOne: + case CustomGameMode.FFA: + if (FFAManager.FFALowerVisionList.ContainsKey(player.PlayerId)) + { + opt.SetVision(true); + opt.SetFloat(FloatOptionNames.CrewLightMod, FFAManager.FFALowerVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, FFAManager.FFALowerVision.GetFloat()); + } + else + SetMaxVision(); - if (GameManager.Instance?.LogicComponents != null) - { - foreach (GameLogicComponent com in GameManager.Instance.LogicComponents) - if (com.TryCast(out LogicOptions lo)) - lo.SetGameOptions(opt); - } + if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.NaturalDisasters; + break; + case CustomGameMode.CaptureTheFlag: + CTFManager.ApplyGameOptions(opt); + SetMaxVision(); + break; + case CustomGameMode.NaturalDisasters: + SetMaxVision(); + NaturalDisasters.ApplyGameOptions(opt, player.PlayerId); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.RoomRush; + break; + case CustomGameMode.RoomRush when RoomRush.VentLimit[player.PlayerId] > 0: + AURoleOptions.EngineerCooldown = 0.01f; + AURoleOptions.EngineerInVentMaxTime = 0f; + goto case CustomGameMode.RoomRush; + case CustomGameMode.RoomRush: + case CustomGameMode.Speedrun: + case CustomGameMode.HotPotato: + case CustomGameMode.MoveAndStop: + SetMaxVision(); + break; + case CustomGameMode.HideAndSeek: + HnSManager.ApplyGameOptions(opt, player); + break; + } + + switch (player.GetCustomRoleTypes()) + { + case CustomRoleTypes.Impostor: + AURoleOptions.ShapeshifterCooldown = Options.DefaultShapeshiftCooldown.GetFloat(); + AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); + break; + case CustomRoleTypes.Neutral: + case CustomRoleTypes.Crewmate: + AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); + break; + } - GameOptionsManager.Instance.CurrentGameOptions = opt; + 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; } + + // 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 - base.SendGameOptions(); - } + AURoleOptions.NoisemakerImpostorAlert = CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool(); + + if (Shifter.WasShifter.Contains(player.PlayerId) && role.IsImpostor()) opt.SetVision(true); + + Main.PlayerStates[player.PlayerId].Role.ApplyGameOptions(opt, player.PlayerId); + + if (player.Is(CustomRoles.Bloodlust) && Bloodlust.HasImpVision.GetBool()) opt.SetVision(true); + + if (Main.AllPlayerControls.Any(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()); + } - protected override void SendOptionsArray(Il2CppStructArray optionArray) - { - try + if ((Grenadier.GrenadierBlinding.Count > 0 && + (role.IsImpostor() || + (role.IsNeutral() && Options.GrenadierCanAffectNeutral.GetBool()))) || + (Grenadier.MadGrenadierBlinding.Count > 0 && !role.IsImpostorTeam() && !player.Is(CustomRoles.Madmate))) { - for (byte i = 0; i < GameManager.Instance.LogicComponents.Count; i++) - { - Il2CppSystem.Object logicComponent = GameManager.Instance.LogicComponents[(Index)i]; - if (logicComponent.TryCast(out _)) SendOptionsArray(optionArray, i, player.GetClientId()); - } + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Options.GrenadierCauseVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.GrenadierCauseVision.GetFloat()); } - catch (Exception ex) + + switch (role) { - Logger.Fatal(ex.ToString(), "PlayerGameOptionsSender.SendOptionsArray"); + case CustomRoles.Alchemist when ((Alchemist)Main.PlayerStates[player.PlayerId].Role).VisionPotionActive: + opt.SetVisionV2(); + + 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; } - } + + Chef.ApplyGameOptionsForOthers(opt, player.PlayerId); + President.OnAnyoneApplyGameOptions(opt); + Negotiator.OnAnyoneApplyGameOptions(opt, player.PlayerId); + Wizard.OnAnyoneApplyGameOptions(opt, player.PlayerId); + Curser.OnAnyoneApplyGameOptions(opt, player.PlayerId); - public static void RemoveSender(PlayerControl player) - { - PlayerGameOptionsSender sender = AllSenders.OfType() - .FirstOrDefault(sender => sender.player.PlayerId == 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 (sender == null) return; + if (Minion.BlindPlayers.Contains(player.PlayerId)) SetBlind(); - sender.player = null; - AllSenders.Remove(sender); - } + if (Sentinel.IsPatrolling(player.PlayerId)) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Sentinel.LoweredVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Sentinel.LoweredVision.GetFloat()); + } - protected override IGameOptions BuildGameOptions() - { - try + if (Beacon.IsAffectedPlayer(player.PlayerId)) { - Main.RealOptionsData ??= new(GameOptionsManager.Instance.CurrentGameOptions); + opt.SetFloat(FloatOptionNames.CrewLightMod, Beacon.IncreasedVision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Beacon.IncreasedVision); + } + + Dazzler.SetDazzled(player, opt); + Deathpact.SetDeathpactVision(player, opt); - IGameOptions opt = BasedGameOptions; - AURoleOptions.SetOpt(opt); - PlayerState state = Main.PlayerStates[player.PlayerId]; - opt.BlackOut(state.IsBlackOut); + Spiritcaller.ReduceVision(opt, player); - CustomRoles role = player.GetCustomRole(); + if (Randomizer.HasSuperVision(player)) + SetMaxVision(); + else if (Randomizer.IsBlind(player)) SetBlind(); - switch (Options.CurrentGameMode) + List array = Main.PlayerStates[player.PlayerId].SubRoles; + + foreach (CustomRoles subRole in array) + { + if (subRole.IsGhostRole() && subRole != CustomRoles.EvilSpirit) { - case CustomGameMode.AllInOne: - case CustomGameMode.FFA: - if (FFAManager.FFALowerVisionList.ContainsKey(player.PlayerId)) - { - opt.SetVision(true); - opt.SetFloat(FloatOptionNames.CrewLightMod, FFAManager.FFALowerVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, FFAManager.FFALowerVision.GetFloat()); - } - else - SetMaxVision(); + AURoleOptions.GuardianAngelCooldown = GhostRolesManager.AssignedGhostRoles.First(x => x.Value.Role == subRole).Value.Instance.Cooldown; + continue; + } - if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.NaturalDisasters; + switch (subRole) + { + case CustomRoles.Watcher: + opt.SetBool(BoolOptionNames.AnonymousVotes, false); break; - case CustomGameMode.CaptureTheFlag: - CTFManager.ApplyGameOptions(opt); - SetMaxVision(); + case CustomRoles.Flashman: + Main.AllPlayerSpeed[player.PlayerId] = Options.FlashmanSpeed.GetFloat(); break; - case CustomGameMode.NaturalDisasters: - SetMaxVision(); - NaturalDisasters.ApplyGameOptions(opt, player.PlayerId); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.RoomRush; + case CustomRoles.Giant: + Main.AllPlayerSpeed[player.PlayerId] = Options.GiantSpeed.GetFloat(); break; - case CustomGameMode.RoomRush when RoomRush.VentLimit[player.PlayerId] > 0: - AURoleOptions.EngineerCooldown = 0.01f; - AURoleOptions.EngineerInVentMaxTime = 0f; - goto case CustomGameMode.RoomRush; - case CustomGameMode.RoomRush: - case CustomGameMode.Speedrun: - case CustomGameMode.HotPotato: - case CustomGameMode.MoveAndStop: - SetMaxVision(); + case CustomRoles.Mare when Options.MareHasIncreasedSpeed.GetBool(): + Main.AllPlayerSpeed[player.PlayerId] = Options.MareSpeedDuringLightsOut.GetFloat(); break; - case CustomGameMode.HideAndSeek: - HnSManager.ApplyGameOptions(opt, player); + case CustomRoles.Sleep when 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); + } - switch (player.GetCustomRoleTypes()) - { - case CustomRoleTypes.Impostor: - AURoleOptions.ShapeshifterCooldown = Options.DefaultShapeshiftCooldown.GetFloat(); - AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); break; - case CustomRoleTypes.Neutral: - case CustomRoleTypes.Crewmate: - AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); + case CustomRoles.Bewilder: + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Options.BewilderVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.BewilderVision.GetFloat()); break; - } - - switch (role) - { - case CustomRoles.PhantomEHR: - AURoleOptions.PhantomCooldown = ImpostorVanillaRoles.PhantomCooldown.GetFloat(); - AURoleOptions.PhantomDuration = ImpostorVanillaRoles.PhantomDuration.GetFloat(); + case CustomRoles.Sunglasses: + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Options.SunglassesVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.SunglassesVision.GetFloat()); break; - case CustomRoles.ShapeshifterEHR: - AURoleOptions.ShapeshifterCooldown = ImpostorVanillaRoles.ShapeshiftCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = ImpostorVanillaRoles.ShapeshiftDur.GetFloat(); + case CustomRoles.Reach: + opt.SetInt(Int32OptionNames.KillDistance, 2); break; - case CustomRoles.EngineerEHR: - AURoleOptions.EngineerCooldown = CrewmateVanillaRoles.EngineerCD.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = CrewmateVanillaRoles.EngineerDur.GetFloat(); + case CustomRoles.Madmate: + opt.SetVision(Options.MadmateHasImpostorVision.GetBool()); break; - case CustomRoles.NoisemakerEHR: - AURoleOptions.NoisemakerImpostorAlert = CrewmateVanillaRoles.NoiseMakerImpostorAlert.GetBool(); - AURoleOptions.NoisemakerAlertDuration = CrewmateVanillaRoles.NoisemakerAlertDuration.GetFloat(); + 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.ScientistEHR: - AURoleOptions.ScientistCooldown = CrewmateVanillaRoles.ScientistCD.GetFloat(); - AURoleOptions.ScientistBatteryCharge = CrewmateVanillaRoles.ScientistDur.GetFloat(); + case CustomRoles.Nimble when player.GetRoleTypes() == RoleTypes.Engineer: + AURoleOptions.EngineerCooldown = Nimble.NimbleCD.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = Nimble.NimbleInVentTime.GetFloat(); break; - case CustomRoles.TrackerEHR: - AURoleOptions.TrackerCooldown = CrewmateVanillaRoles.TrackerCooldown.GetFloat(); - AURoleOptions.TrackerDuration = CrewmateVanillaRoles.TrackerDuration.GetFloat(); - AURoleOptions.TrackerDelay = CrewmateVanillaRoles.TrackerDelay.GetFloat(); + case CustomRoles.Physicist when player.GetRoleTypes() == RoleTypes.Scientist: + AURoleOptions.ScientistCooldown = Physicist.PhysicistCD.GetFloat(); + AURoleOptions.ScientistBatteryCharge = Physicist.PhysicistViewDuration.GetFloat(); break; - } - - // 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(); - - if (Shifter.WasShifter.Contains(player.PlayerId) && role.IsImpostor()) opt.SetVision(true); - - Main.PlayerStates[player.PlayerId].Role.ApplyGameOptions(opt, player.PlayerId); - - if (player.Is(CustomRoles.Bloodlust) && Bloodlust.HasImpVision.GetBool()) opt.SetVision(true); - - if (Main.AllPlayerControls.Any(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)Main.PlayerStates[player.PlayerId].Role).VisionPotionActive: - opt.SetVisionV2(); - - if (Utils.IsActive(SystemTypes.Electrical)) - opt.SetFloat(FloatOptionNames.CrewLightMod, Alchemist.VisionOnLightsOut.GetFloat() * 5); - else - opt.SetFloat(FloatOptionNames.CrewLightMod, Alchemist.Vision.GetFloat()); - + case CustomRoles.Finder when player.GetRoleTypes() == RoleTypes.Tracker: + AURoleOptions.TrackerCooldown = Finder.FinderCD.GetFloat(); + AURoleOptions.TrackerDuration = Finder.FinderDuration.GetFloat(); + AURoleOptions.TrackerDelay = Finder.FinderDelay.GetFloat(); break; - case CustomRoles.Mayor when Mayor.MayorSeesVoteColorsWhenDoneTasks.GetBool() && player.GetTaskState().IsTaskFinished: - opt.SetBool(BoolOptionNames.AnonymousVotes, false); + case CustomRoles.Noisy when player.GetRoleTypes() == RoleTypes.Noisemaker: + AURoleOptions.NoisemakerImpostorAlert = Noisy.NoisyImpostorAlert.GetBool(); + AURoleOptions.NoisemakerAlertDuration = Noisy.NoisyAlertDuration.GetFloat(); break; } + } - Chef.ApplyGameOptionsForOthers(opt, player.PlayerId); - President.OnAnyoneApplyGameOptions(opt); - Negotiator.OnAnyoneApplyGameOptions(opt, player.PlayerId); - Wizard.OnAnyoneApplyGameOptions(opt, player.PlayerId); - Curser.OnAnyoneApplyGameOptions(opt, player.PlayerId); + if (Magician.BlindPpl.ContainsKey(player.PlayerId)) SetBlind(); - if (Sprayer.LowerVisionList.Contains(player.PlayerId)) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Sprayer.LoweredVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Sprayer.LoweredVision.GetFloat()); - } + if (player.IsCrewmate() && Main.PlayerStates.Values.Any(s => s.Role is Adventurer { IsEnable: true } av && av.ActiveWeapons.Contains(Adventurer.Weapon.Lantern))) SetMaxVision(); - if (Minion.BlindPlayers.Contains(player.PlayerId)) SetBlind(); + if (Chemist.Instances.Any(x => x.IsBlinding && player.PlayerId != x.ChemistPC.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 (Changeling.ChangedRole.TryGetValue(player.PlayerId, out bool changed) && changed && player.GetRoleTypes() != RoleTypes.Shapeshifter) + { + AURoleOptions.ShapeshifterCooldown = 300f; + AURoleOptions.ShapeshifterDuration = 1f; + } - if (Beacon.IsAffectedPlayer(player.PlayerId)) - { - opt.SetFloat(FloatOptionNames.CrewLightMod, Beacon.IncreasedVision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Beacon.IncreasedVision); - } + if (Options.UsePhantomBasis.GetBool() && role.SimpleAbilityTrigger()) AURoleOptions.PhantomDuration = 1f; - Dazzler.SetDazzled(player, opt); - Deathpact.SetDeathpactVision(player, opt); + if ((Options.UseUnshiftTrigger.GetBool() || role.AlwaysUsesUnshift()) && role.SimpleAbilityTrigger()) AURoleOptions.ShapeshifterDuration = 0f; - Spiritcaller.ReduceVision(opt, player); + // =================================================================================================================== - if (Randomizer.HasSuperVision(player)) - SetMaxVision(); - else if (Randomizer.IsBlind(player)) SetBlind(); + AURoleOptions.EngineerCooldown = Mathf.Max(0.01f, AURoleOptions.EngineerCooldown); - List array = Main.PlayerStates[player.PlayerId].SubRoles; + if (Main.AllPlayerKillCooldown.TryGetValue(player.PlayerId, out float killCooldown)) AURoleOptions.KillCooldown = Mathf.Max(0.01f, killCooldown); - foreach (CustomRoles subRole in array) - { - if (subRole.IsGhostRole() && subRole != CustomRoles.EvilSpirit) - { - AURoleOptions.GuardianAngelCooldown = GhostRolesManager.AssignedGhostRoles.First(x => x.Value.Role == subRole).Value.Instance.Cooldown; - continue; - } + if (Main.AllPlayerSpeed.TryGetValue(player.PlayerId, out float speed)) AURoleOptions.PlayerSpeedMod = Mathf.Clamp(speed, Main.MinSpeed, 3f); - switch (subRole) - { - case CustomRoles.Watcher: - opt.SetBool(BoolOptionNames.AnonymousVotes, false); - break; - case CustomRoles.Flashman: - Main.AllPlayerSpeed[player.PlayerId] = Options.FlashmanSpeed.GetFloat(); - break; - case CustomRoles.Giant: - Main.AllPlayerSpeed[player.PlayerId] = Options.GiantSpeed.GetFloat(); - break; - case CustomRoles.Mare when Options.MareHasIncreasedSpeed.GetBool(): - Main.AllPlayerSpeed[player.PlayerId] = Options.MareSpeedDuringLightsOut.GetFloat(); - break; - case CustomRoles.Sleep when 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: - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Options.BewilderVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Options.BewilderVision.GetFloat()); - break; - case CustomRoles.Sunglasses: - 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.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 player.GetRoleTypes() == RoleTypes.Engineer: - AURoleOptions.EngineerCooldown = Nimble.NimbleCD.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = Nimble.NimbleInVentTime.GetFloat(); - break; - case CustomRoles.Physicist when player.GetRoleTypes() == RoleTypes.Scientist: - AURoleOptions.ScientistCooldown = Physicist.PhysicistCD.GetFloat(); - AURoleOptions.ScientistBatteryCharge = Physicist.PhysicistViewDuration.GetFloat(); - break; - case CustomRoles.Finder when player.GetRoleTypes() == RoleTypes.Tracker: - AURoleOptions.TrackerCooldown = Finder.FinderCD.GetFloat(); - AURoleOptions.TrackerDuration = Finder.FinderDuration.GetFloat(); - AURoleOptions.TrackerDelay = Finder.FinderDelay.GetFloat(); - break; - case CustomRoles.Noisy when player.GetRoleTypes() == RoleTypes.Noisemaker: - AURoleOptions.NoisemakerImpostorAlert = Noisy.NoisyImpostorAlert.GetBool(); - AURoleOptions.NoisemakerAlertDuration = Noisy.NoisyAlertDuration.GetFloat(); - break; - } - } + state.TaskState.HasTasks = Utils.HasTasks(player.Data, false); + if (Options.GhostCanSeeOtherVotes.GetBool() && player.Data.IsDead) opt.SetBool(BoolOptionNames.AnonymousVotes, false); - if (Magician.BlindPpl.ContainsKey(player.PlayerId)) SetBlind(); + if (Options.AdditionalEmergencyCooldown.GetBool() && + Options.AdditionalEmergencyCooldownThreshold.GetInt() <= Utils.AllAlivePlayersCount) + { + opt.SetInt( + Int32OptionNames.EmergencyCooldown, + Options.AdditionalEmergencyCooldownTime.GetInt()); + } - if (player.IsCrewmate() && Main.PlayerStates.Values.Any(s => s.Role is Adventurer { IsEnable: true } av && av.ActiveWeapons.Contains(Adventurer.Weapon.Lantern))) SetMaxVision(); + if (Options.SyncButtonMode.GetBool() && Options.SyncedButtonCount.GetValue() <= Options.UsedButtonCount) opt.SetInt(Int32OptionNames.EmergencyCooldown, 3600); - if (Chemist.Instances.Any(x => x.IsBlinding && player.PlayerId != x.ChemistPC.PlayerId)) SetBlind(); + MeetingTimeManager.ApplyGameOptions(opt); - if (Changeling.ChangedRole.TryGetValue(player.PlayerId, out bool changed) && changed && player.GetRoleTypes() != RoleTypes.Shapeshifter) - { - AURoleOptions.ShapeshifterCooldown = 300f; - AURoleOptions.ShapeshifterDuration = 1f; - } + AURoleOptions.ShapeshifterCooldown = Mathf.Max(1f, AURoleOptions.ShapeshifterCooldown); + AURoleOptions.ProtectionDurationSeconds = 0f; + AURoleOptions.ImpostorsCanSeeProtect = false; - if (Options.UsePhantomBasis.GetBool() && role.SimpleAbilityTrigger()) AURoleOptions.PhantomDuration = 1f; + Logger.Info($"Updated settings for {player.GetNameWithRole()}: Crew Vision = {opt.GetFloat(FloatOptionNames.CrewLightMod)}, Impostor Vision = {opt.GetFloat(FloatOptionNames.ImpostorLightMod)}, Speed = {opt.GetFloat(FloatOptionNames.PlayerSpeedMod)}", "BuildGameOptions"); - if ((Options.UseUnshiftTrigger.GetBool() || role.AlwaysUsesUnshift()) && role.SimpleAbilityTrigger()) AURoleOptions.ShapeshifterDuration = 0f; + return opt; - // =================================================================================================================== - - AURoleOptions.EngineerCooldown = Mathf.Max(0.01f, AURoleOptions.EngineerCooldown); - - if (Main.AllPlayerKillCooldown.TryGetValue(player.PlayerId, out float killCooldown)) AURoleOptions.KillCooldown = Mathf.Max(0.01f, killCooldown); - - if (Main.AllPlayerSpeed.TryGetValue(player.PlayerId, out float speed)) AURoleOptions.PlayerSpeedMod = Mathf.Clamp(speed, Main.MinSpeed, 3f); + void SetMaxVision() + { + opt.SetVision(true); + opt.SetFloat(FloatOptionNames.CrewLightMod, 1.5f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.5f); + } - state.TaskState.HasTasks = Utils.HasTasks(player.Data, false); - if (Options.GhostCanSeeOtherVotes.GetBool() && player.Data.IsDead) opt.SetBool(BoolOptionNames.AnonymousVotes, false); - - if (Options.AdditionalEmergencyCooldown.GetBool() && - Options.AdditionalEmergencyCooldownThreshold.GetInt() <= Utils.AllAlivePlayersCount) - { - opt.SetInt( - Int32OptionNames.EmergencyCooldown, - Options.AdditionalEmergencyCooldownTime.GetInt()); - } - - 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)}, Impostor Vision = {opt.GetFloat(FloatOptionNames.ImpostorLightMod)}, Speed = {opt.GetFloat(FloatOptionNames.PlayerSpeedMod)}", "BuildGameOptions"); - - return opt; - - void SetMaxVision() - { - opt.SetVision(true); - opt.SetFloat(FloatOptionNames.CrewLightMod, 1.5f); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.5f); - } - - void SetBlind() - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, 0f); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0f); - } - } - catch (Exception e) + void SetBlind() { - Logger.Fatal($"Error for {player.GetRealName()} ({player.GetCustomRole()}): {e}", "PlayerGameOptionsSender.BuildGameOptions"); - Logger.SendInGame($"Error syncing settings for {player.GetRealName()} - Please report this bug to the developer AND SEND LOGS"); - return BasedGameOptions; + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, 0f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0f); } } - - protected override bool AmValid() + catch (Exception e) { - return base.AmValid() && player != null && !player.Data.Disconnected && Main.RealOptionsData != null; + Logger.Fatal($"Error for {player.GetRealName()} ({player.GetCustomRole()}): {e}", "PlayerGameOptionsSender.BuildGameOptions"); + Logger.SendInGame($"Error syncing settings for {player.GetRealName()} - Please report this bug to the developer AND SEND LOGS"); + return BasedGameOptions; } + } + + protected override bool AmValid() + { + return base.AmValid() && player != null && !player.Data.Disconnected && Main.RealOptionsData != null; } } \ No newline at end of file diff --git a/Modules/GameState.cs b/Modules/GameState.cs index 4f1f3df1a..b9e3049df 100644 --- a/Modules/GameState.cs +++ b/Modules/GameState.cs @@ -10,556 +10,555 @@ using EHR.Modules; using EHR.Neutral; using InnerNet; -namespace EHR +namespace EHR; + +public class PlayerState(byte playerId) { - public class PlayerState(byte playerId) + public enum 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, + PissedOff, + Dismembered, + LossOfHead, + Trialed, + Infected, + Demolished, + YinYanged, + Kamikazed, + RNG, + WrongAnswer, + Consumed, + BadLuck, + Asthma, + Assumed, + Negotiation, + Trapped, + Stung, + Scavenged, + Allergy, + + // Natural Disasters + Meteor, + Lava, + Tornado, + Lightning, + Drowned, + Sunken, + Collapsed, + + etc = -1 + } + + public readonly PlayerControl Player = Utils.GetPlayerById(playerId, false); + + private readonly byte PlayerId = playerId; + public readonly List SubRoles = []; + public readonly Dictionary TargetColorData = []; + public CountTypes countTypes = CountTypes.Crew; + public PlainShipRoom LastRoom; + public CustomRoles MainRole = CustomRoles.NotAssigned; + public (DateTime TimeStamp, byte ID) RealKiller = (DateTime.MinValue, byte.MaxValue); + public RoleBase Role = new VanillaRole(); + + public int RoleChangeTimes; + public bool IsDead { get; set; } +#pragma warning disable IDE1006 // Naming Styles + // ReSharper disable once InconsistentNaming + public DeathReason deathReason { get; set; } = DeathReason.etc; +#pragma warning restore IDE1006 // Naming Styles + public bool IsBlackOut { get; set; } + + public bool IsSuicide => deathReason == DeathReason.Suicide; + public TaskState TaskState { get; set; } = new(); + + public void SetMainRole(CustomRoles role) + { + try { - Kill, - Vote, - Suicide, - Spell, - Curse, - FollowingSuicide, - Bite, - Poison, - Bombed, - Misfire, - Torched, - Sniped, - Revenge, - Execution, - Disconnected, - Fall, - AFK, - - // EHR - Gambled, - Eaten, - Sacrifice, - Quantization, - Overtired, - Ashamed, - PissedOff, - Dismembered, - LossOfHead, - Trialed, - Infected, - Demolished, - YinYanged, - Kamikazed, - RNG, - WrongAnswer, - Consumed, - BadLuck, - Asthma, - Assumed, - Negotiation, - Trapped, - Stung, - Scavenged, - Allergy, - - // Natural Disasters - Meteor, - Lava, - Tornado, - Lightning, - Drowned, - Sunken, - Collapsed, - - etc = -1 + Utils.RemovePlayerFromPreviousRoleData(Player); + } + catch (Exception e) + { + Utils.ThrowException(e); } - public readonly PlayerControl Player = Utils.GetPlayerById(playerId, false); + countTypes = role.GetCountTypes(); - private readonly byte PlayerId = playerId; - public readonly List SubRoles = []; - public readonly Dictionary TargetColorData = []; - public CountTypes countTypes = CountTypes.Crew; - public PlainShipRoom LastRoom; - public CustomRoles MainRole = CustomRoles.NotAssigned; - public (DateTime TimeStamp, byte ID) RealKiller = (DateTime.MinValue, byte.MaxValue); - public RoleBase Role = new VanillaRole(); - - public int RoleChangeTimes; - public bool IsDead { get; set; } -#pragma warning disable IDE1006 // Naming Styles - // ReSharper disable once InconsistentNaming - public DeathReason deathReason { get; set; } = DeathReason.etc; -#pragma warning restore IDE1006 // Naming Styles - public bool IsBlackOut { get; set; } - - public bool IsSuicide => deathReason == DeathReason.Suicide; - public TaskState TaskState { get; set; } = new(); - - public void SetMainRole(CustomRoles role) + if (SubRoles.Contains(CustomRoles.Recruit)) { + countTypes = Jackal.SidekickCountMode.GetValue() switch + { + 0 => CountTypes.Jackal, + 1 => CountTypes.OutOfGame, + _ => role.GetCountTypes() + }; + } + + if (CustomTeamManager.GetCustomTeam(PlayerId) != null && !CustomTeamManager.IsSettingEnabledForPlayerTeam(PlayerId, CTAOption.WinWithOriginalTeam)) countTypes = CountTypes.CustomTeam; + + SubRoles.ForEach(SetAddonCountTypes); + + if (!Player.HasKillButton() && role == CustomRoles.Refugee) Player.RpcChangeRoleBasis(CustomRoles.Refugee); + + Role = role.GetRoleClass(); + + if (!role.RoleExist(true)) Role.Init(); + + MainRole = role; + + Role.Add(PlayerId); + + Logger.Info($"ID {PlayerId} ({Player.GetRealName()}) => {role}, CountTypes => {countTypes}", "SetMainRole"); + + if (!AmongUsClient.Instance.AmHost) return; + + if (!Main.HasJustStarted) + { + Player.ResetKillCooldown(); + Player.SyncSettings(); + Utils.NotifyRoles(SpecifySeer: Player); + Utils.NotifyRoles(SpecifyTarget: Player); + + if (PlayerId == PlayerControl.LocalPlayer.PlayerId && GameStates.IsInTask) + { + HudManager.Instance.SetHudActive(true); + RemoveDisableDevicesPatch.UpdateDisableDevices(); + } + + if (Lyncher.On) Lyncher.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 is CustomRoles.Sidekick or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee) + SubRoles.ToArray().DoIf(StartGameHostPatch.BasisChangingAddons.ContainsKey, RemoveSubRole); + + if (role == CustomRoles.Sidekick && Jackal.Instances.FindFirst(x => x.SidekickId == byte.MaxValue || x.SidekickId.GetPlayer() == null, out Jackal jackal)) + jackal.SidekickId = PlayerId; + + LateTask.New(() => Player.CheckAndSetUnshiftState(), 1f, log: false); + + if (CustomGameMode.Standard.IsActiveOrIntegrated() && GameStates.IsInTask && !AntiBlackout.SkipTasks) + Player.Notify(string.Format(Translator.GetString("RoleChangedNotify"), role.ToColoredString()), 10f); + + if (Options.UsePets.GetBool()) Player.RpcSetPetDesync(PetsPatch.GetPetId(), Player); + } + + CheckMurderPatch.TimeSinceLastKill.Remove(PlayerId); + + if (Main.HasJustStarted || PlayerControl.LocalPlayer.PlayerId != PlayerId || !CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + RoleChangeTimes++; + if (RoleChangeTimes >= 3) Achievements.Type.Transformer.Complete(); + } + + public void SetSubRole(CustomRoles role, bool AllReplace = false) + { + if (role == CustomRoles.Cleansed) AllReplace = true; + + if (AllReplace) + { + 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"); + } + + private void SetAddonCountTypes(CustomRoles role) + { + switch (role) + { + case CustomRoles.Bloodlust: + countTypes = CountTypes.Bloodlust; + SubRoles.Remove(CustomRoles.Stressed); + break; + case CustomRoles.Madmate: + TaskState.HasTasks = false; + TaskState.AllTasksCount = 0; + + countTypes = Options.MadmateCountMode.GetInt() switch + { + 0 => CountTypes.OutOfGame, + 1 => CountTypes.Impostor, + 2 => CountTypes.Crew, + _ => throw new NotImplementedException() + }; + + SubRoles.Remove(CustomRoles.Charmed); + SubRoles.Remove(CustomRoles.Recruit); + 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: + TaskState.HasTasks = false; + TaskState.AllTasksCount = 0; + + countTypes = Succubus.CharmedCountMode.GetInt() switch + { + 0 => CountTypes.OutOfGame, + 1 => CountTypes.Succubus, + 2 => countTypes, + _ => throw new NotImplementedException() + }; + + SubRoles.Remove(CustomRoles.Madmate); + SubRoles.Remove(CustomRoles.Recruit); + 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: + TaskState.HasTasks = false; + TaskState.AllTasksCount = 0; + + countTypes = Necromancer.UndeadCountMode.GetInt() switch + { + 0 => CountTypes.OutOfGame, + 1 => CountTypes.Necromancer, + 2 => countTypes, + _ => throw new NotImplementedException() + }; + + SubRoles.Remove(CustomRoles.Madmate); + SubRoles.Remove(CustomRoles.Recruit); + 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.LastImpostor: + SubRoles.Remove(CustomRoles.Mare); + break; + case CustomRoles.Recruit: + TaskState.HasTasks = false; + TaskState.AllTasksCount = 0; + + countTypes = Jackal.SidekickCountMode.GetInt() switch + { + 0 => CountTypes.Jackal, + 1 => CountTypes.OutOfGame, + 2 => countTypes, + _ => throw new NotImplementedException() + }; + + SubRoles.Remove(CustomRoles.Madmate); + SubRoles.Remove(CustomRoles.Charmed); + SubRoles.Remove(CustomRoles.Contagious); + SubRoles.Remove(CustomRoles.Rascal); + SubRoles.Remove(CustomRoles.Loyal); + SubRoles.Remove(CustomRoles.Loyal); + SubRoles.Remove(CustomRoles.Undead); + Utils.NotifyRoles(SpecifySeer: Player); + Utils.NotifyRoles(SpecifyTarget: Player); + break; + case CustomRoles.Contagious: + TaskState.HasTasks = false; + TaskState.AllTasksCount = 0; + + countTypes = Virus.ContagiousCountMode.GetInt() switch + { + 0 => CountTypes.OutOfGame, + 1 => CountTypes.Virus, + 2 => countTypes, + _ => throw new NotImplementedException() + }; + + SubRoles.Remove(CustomRoles.Madmate); + SubRoles.Remove(CustomRoles.Recruit); + 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.Flashman 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); + } + + public void SetDead() + { + IsDead = true; + + if (AmongUsClient.Instance.AmHost) + { + RPC.SendDeathReason(PlayerId, deathReason); + Utils.CheckAndSpawnAdditionalRefugee(Utils.GetPlayerInfoById(PlayerId)); + + if (Utils.DoRPC && !CustomGameMode.Standard.IsActiveOrIntegrated()) + Main.AllPlayerControls.DoIf(x => x.IsNonHostModClient(), x => Player.RpcSetRoleDesync(RoleTypes.CrewmateGhost, x.GetClientId())); + } + } + + 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(bool ExcludeSelfKill = false) + { + return Main.PlayerStates.Values.Where(state => !(ExcludeSelfKill && state.PlayerId == PlayerId) && state.GetRealKiller() == PlayerId).ToArray().Length; + } +} + +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().RemoveHtmlTags()}: InitTask", "TaskState.Init"); + if (player == null || player.Data?.Tasks == null) return; + + if (!Utils.HasTasks(player.Data, false)) + { + AllTasksCount = 0; + return; + } + + HasTasks = true; + AllTasksCount = player.Data.Tasks.Count; + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: TaskCounts = {CompletedTasksCount}/{AllTasksCount}", "TaskState.Init"); + } + + public void Update(PlayerControl player) + { + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags().RemoveHtmlTags()}: UpdateTask", "TaskState.Update"); + GameData.Instance.RecomputeTaskCounts(); + Logger.Info($"TotalTaskCounts = {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", "TaskState.Update"); + + if (AllTasksCount == -1) Init(player); + + if (!HasTasks) return; + + if (AmongUsClient.Instance.AmHost) + { + bool alive = player.IsAlive(); + + if (alive && CustomGameMode.Speedrun.IsActiveOrIntegrated()) + { + if (CompletedTasksCount + 1 >= AllTasksCount) SpeedrunManager.OnTaskFinish(player); + + SpeedrunManager.ResetTimer(player); + } + + // Ability Use Gain with this task completed + if (alive && !Main.HasJustStarted) + { + switch (player.GetCustomRole()) + { + case CustomRoles.SabotageMaster: + if (Main.PlayerStates[player.PlayerId].Role is not SabotageMaster sm) break; + + sm.UsedSkillCount -= SabotageMaster.SmAbilityUseGainWithEachTaskCompleted.GetFloat(); + sm.SendRPC(); + break; + case CustomRoles.NiceHacker: + if (!player.IsModClient() && NiceHacker.UseLimit.ContainsKey(player.PlayerId)) + NiceHacker.UseLimit[player.PlayerId] += NiceHacker.NiceHackerAbilityUseGainWithEachTaskCompleted.GetFloat(); + else if (NiceHacker.UseLimitSeconds.ContainsKey(player.PlayerId)) NiceHacker.UseLimitSeconds[player.PlayerId] += NiceHacker.NiceHackerAbilityUseGainWithEachTaskCompleted.GetInt() * NiceHacker.ModdedClientAbilityUseSecondsMultiplier.GetInt(); + + if (NiceHacker.UseLimitSeconds.ContainsKey(player.PlayerId)) NiceHacker.SendRPC(player.PlayerId, NiceHacker.UseLimitSeconds[player.PlayerId]); + + break; + } + + float add = Utils.GetSettingNameAndValueForRole(player.GetCustomRole(), "AbilityUseGainWithEachTaskCompleted"); + if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0) player.RpcIncreaseAbilityUseLimitBy(add); + } + try { - Utils.RemovePlayerFromPreviousRoleData(Player); + Main.PlayerStates[player.PlayerId].Role.OnTaskComplete(player, CompletedTasksCount, AllTasksCount); } catch (Exception e) { Utils.ThrowException(e); } - countTypes = role.GetCountTypes(); + List addons = Main.PlayerStates[player.PlayerId].SubRoles; - if (SubRoles.Contains(CustomRoles.Recruit)) + 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)) { - countTypes = Jackal.SidekickCountMode.GetValue() switch + if (ghostRole is { Role: CustomRoles.Specter, Instance: Specter specter } && CompletedTasksCount + 1 >= AllTasksCount) specter.OnFinishedTasks(player); + + if (ghostRole is { Role: CustomRoles.Haunter, Instance: Haunter haunter }) { - 0 => CountTypes.Jackal, - 1 => CountTypes.OutOfGame, - _ => role.GetCountTypes() - }; - } - - if (CustomTeamManager.GetCustomTeam(PlayerId) != null && !CustomTeamManager.IsSettingEnabledForPlayerTeam(PlayerId, CTAOption.WinWithOriginalTeam)) countTypes = CountTypes.CustomTeam; - - SubRoles.ForEach(SetAddonCountTypes); - - if (!Player.HasKillButton() && role == CustomRoles.Refugee) Player.RpcChangeRoleBasis(CustomRoles.Refugee); - - Role = role.GetRoleClass(); - - if (!role.RoleExist(true)) Role.Init(); - - MainRole = role; - - Role.Add(PlayerId); - - Logger.Info($"ID {PlayerId} ({Player.GetRealName()}) => {role}, CountTypes => {countTypes}", "SetMainRole"); - - if (!AmongUsClient.Instance.AmHost) return; - - if (!Main.HasJustStarted) - { - Player.ResetKillCooldown(); - Player.SyncSettings(); - Utils.NotifyRoles(SpecifySeer: Player); - Utils.NotifyRoles(SpecifyTarget: Player); - - if (PlayerId == PlayerControl.LocalPlayer.PlayerId && GameStates.IsInTask) - { - HudManager.Instance.SetHudActive(true); - RemoveDisableDevicesPatch.UpdateDisableDevices(); + if (CompletedTasksCount + 1 >= AllTasksCount) + haunter.OnFinishedTasks(player); + else if (CompletedTasksCount + 1 >= Haunter.TasksBeforeBeingKnown.GetInt()) haunter.OnOneTaskLeft(player); } - - if (Lyncher.On) Lyncher.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 is CustomRoles.Sidekick or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee) - SubRoles.ToArray().DoIf(StartGameHostPatch.BasisChangingAddons.ContainsKey, RemoveSubRole); - - if (role == CustomRoles.Sidekick && Jackal.Instances.FindFirst(x => x.SidekickId == byte.MaxValue || x.SidekickId.GetPlayer() == null, out Jackal jackal)) - jackal.SidekickId = PlayerId; - - LateTask.New(() => Player.CheckAndSetUnshiftState(), 1f, log: false); - - if (CustomGameMode.Standard.IsActiveOrIntegrated() && GameStates.IsInTask && !AntiBlackout.SkipTasks) - Player.Notify(string.Format(Translator.GetString("RoleChangedNotify"), role.ToColoredString()), 10f); - - if (Options.UsePets.GetBool()) Player.RpcSetPetDesync(PetsPatch.GetPetId(), Player); } - CheckMurderPatch.TimeSinceLastKill.Remove(PlayerId); + Simon.RemoveTarget(player, Simon.Instruction.Task); - if (Main.HasJustStarted || PlayerControl.LocalPlayer.PlayerId != PlayerId || !CustomGameMode.Standard.IsActiveOrIntegrated()) return; - - RoleChangeTimes++; - if (RoleChangeTimes >= 3) Achievements.Type.Transformer.Complete(); + // Update the player's task count for Task Managers + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc.Is(CustomRoles.TaskManager) && pc.PlayerId != player.PlayerId) + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player); } - public void SetSubRole(CustomRoles role, bool AllReplace = false) - { - if (role == CustomRoles.Cleansed) AllReplace = true; + if (CompletedTasksCount >= AllTasksCount) return; - if (AllReplace) - { - SubRoles.Clear(); - Utils.SendRPC(CustomRPC.RemoveSubRole, PlayerId, 2); - } + CompletedTasksCount++; - if (!SubRoles.Contains(role)) - SubRoles.Add(role); - - SetAddonCountTypes(role); - - Logger.Info($" ID {PlayerId} ({Player?.GetRealName()}) => {role}, CountTypes => {countTypes}", "SetSubRole"); - } - - private void SetAddonCountTypes(CustomRoles role) - { - switch (role) - { - case CustomRoles.Bloodlust: - countTypes = CountTypes.Bloodlust; - SubRoles.Remove(CustomRoles.Stressed); - break; - case CustomRoles.Madmate: - TaskState.HasTasks = false; - TaskState.AllTasksCount = 0; - - countTypes = Options.MadmateCountMode.GetInt() switch - { - 0 => CountTypes.OutOfGame, - 1 => CountTypes.Impostor, - 2 => CountTypes.Crew, - _ => throw new NotImplementedException() - }; - - SubRoles.Remove(CustomRoles.Charmed); - SubRoles.Remove(CustomRoles.Recruit); - 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: - TaskState.HasTasks = false; - TaskState.AllTasksCount = 0; - - countTypes = Succubus.CharmedCountMode.GetInt() switch - { - 0 => CountTypes.OutOfGame, - 1 => CountTypes.Succubus, - 2 => countTypes, - _ => throw new NotImplementedException() - }; - - SubRoles.Remove(CustomRoles.Madmate); - SubRoles.Remove(CustomRoles.Recruit); - 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: - TaskState.HasTasks = false; - TaskState.AllTasksCount = 0; - - countTypes = Necromancer.UndeadCountMode.GetInt() switch - { - 0 => CountTypes.OutOfGame, - 1 => CountTypes.Necromancer, - 2 => countTypes, - _ => throw new NotImplementedException() - }; - - SubRoles.Remove(CustomRoles.Madmate); - SubRoles.Remove(CustomRoles.Recruit); - 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.LastImpostor: - SubRoles.Remove(CustomRoles.Mare); - break; - case CustomRoles.Recruit: - TaskState.HasTasks = false; - TaskState.AllTasksCount = 0; - - countTypes = Jackal.SidekickCountMode.GetInt() switch - { - 0 => CountTypes.Jackal, - 1 => CountTypes.OutOfGame, - 2 => countTypes, - _ => throw new NotImplementedException() - }; - - SubRoles.Remove(CustomRoles.Madmate); - SubRoles.Remove(CustomRoles.Charmed); - SubRoles.Remove(CustomRoles.Contagious); - SubRoles.Remove(CustomRoles.Rascal); - SubRoles.Remove(CustomRoles.Loyal); - SubRoles.Remove(CustomRoles.Loyal); - SubRoles.Remove(CustomRoles.Undead); - Utils.NotifyRoles(SpecifySeer: Player); - Utils.NotifyRoles(SpecifyTarget: Player); - break; - case CustomRoles.Contagious: - TaskState.HasTasks = false; - TaskState.AllTasksCount = 0; - - countTypes = Virus.ContagiousCountMode.GetInt() switch - { - 0 => CountTypes.OutOfGame, - 1 => CountTypes.Virus, - 2 => countTypes, - _ => throw new NotImplementedException() - }; - - SubRoles.Remove(CustomRoles.Madmate); - SubRoles.Remove(CustomRoles.Recruit); - 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.Flashman 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); - } - - public void SetDead() - { - IsDead = true; - - if (AmongUsClient.Instance.AmHost) - { - RPC.SendDeathReason(PlayerId, deathReason); - Utils.CheckAndSpawnAdditionalRefugee(Utils.GetPlayerInfoById(PlayerId)); - - if (Utils.DoRPC && !CustomGameMode.Standard.IsActiveOrIntegrated()) - Main.AllPlayerControls.DoIf(x => x.IsNonHostModClient(), x => Player.RpcSetRoleDesync(RoleTypes.CrewmateGhost, x.GetClientId())); - } - } - - 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(bool ExcludeSelfKill = false) - { - return Main.PlayerStates.Values.Where(state => !(ExcludeSelfKill && state.PlayerId == PlayerId) && state.GetRealKiller() == PlayerId).ToArray().Length; - } + CompletedTasksCount = Math.Min(AllTasksCount, CompletedTasksCount); + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: TaskCounts = {CompletedTasksCount}/{AllTasksCount}", "TaskState.Update"); } +} - public class TaskState +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) { - 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; + if (obj is null) return false; - public void Init(PlayerControl player) - { - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: InitTask", "TaskState.Init"); - if (player == null || player.Data?.Tasks == null) return; + if (ReferenceEquals(this, obj)) return true; - if (!Utils.HasTasks(player.Data, false)) - { - AllTasksCount = 0; - return; - } - - HasTasks = true; - AllTasksCount = player.Data.Tasks.Count; - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: TaskCounts = {CompletedTasksCount}/{AllTasksCount}", "TaskState.Init"); - } - - public void Update(PlayerControl player) - { - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags().RemoveHtmlTags()}: UpdateTask", "TaskState.Update"); - GameData.Instance.RecomputeTaskCounts(); - Logger.Info($"TotalTaskCounts = {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", "TaskState.Update"); - - if (AllTasksCount == -1) Init(player); - - if (!HasTasks) return; - - if (AmongUsClient.Instance.AmHost) - { - bool alive = player.IsAlive(); - - if (alive && CustomGameMode.Speedrun.IsActiveOrIntegrated()) - { - if (CompletedTasksCount + 1 >= AllTasksCount) SpeedrunManager.OnTaskFinish(player); - - SpeedrunManager.ResetTimer(player); - } - - // Ability Use Gain with this task completed - if (alive && !Main.HasJustStarted) - { - switch (player.GetCustomRole()) - { - case CustomRoles.SabotageMaster: - if (Main.PlayerStates[player.PlayerId].Role is not SabotageMaster sm) break; - - sm.UsedSkillCount -= SabotageMaster.SmAbilityUseGainWithEachTaskCompleted.GetFloat(); - sm.SendRPC(); - break; - case CustomRoles.NiceHacker: - if (!player.IsModClient() && NiceHacker.UseLimit.ContainsKey(player.PlayerId)) - NiceHacker.UseLimit[player.PlayerId] += NiceHacker.NiceHackerAbilityUseGainWithEachTaskCompleted.GetFloat(); - else if (NiceHacker.UseLimitSeconds.ContainsKey(player.PlayerId)) NiceHacker.UseLimitSeconds[player.PlayerId] += NiceHacker.NiceHackerAbilityUseGainWithEachTaskCompleted.GetInt() * NiceHacker.ModdedClientAbilityUseSecondsMultiplier.GetInt(); - - if (NiceHacker.UseLimitSeconds.ContainsKey(player.PlayerId)) NiceHacker.SendRPC(player.PlayerId, NiceHacker.UseLimitSeconds[player.PlayerId]); - - break; - } - - float add = Utils.GetSettingNameAndValueForRole(player.GetCustomRole(), "AbilityUseGainWithEachTaskCompleted"); - if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0) player.RpcIncreaseAbilityUseLimitBy(add); - } - - try - { - Main.PlayerStates[player.PlayerId].Role.OnTaskComplete(player, CompletedTasksCount, AllTasksCount); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - - List 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.Specter, Instance: Specter specter } && CompletedTasksCount + 1 >= AllTasksCount) specter.OnFinishedTasks(player); - - if (ghostRole is { Role: CustomRoles.Haunter, Instance: Haunter haunter }) - { - if (CompletedTasksCount + 1 >= AllTasksCount) - haunter.OnFinishedTasks(player); - else if (CompletedTasksCount + 1 >= Haunter.TasksBeforeBeingKnown.GetInt()) haunter.OnOneTaskLeft(player); - } - } - - Simon.RemoveTarget(player, Simon.Instruction.Task); - - // Update the player's task count for Task Managers - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (pc.Is(CustomRoles.TaskManager) && pc.PlayerId != player.PlayerId) - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: player); - } - - if (CompletedTasksCount >= AllTasksCount) return; - - CompletedTasksCount++; - - CompletedTasksCount = Math.Min(AllTasksCount, CompletedTasksCount); - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: TaskCounts = {CompletedTasksCount}/{AllTasksCount}", "TaskState.Update"); - } + return obj.GetType() == GetType() && Equals((PlayerVersion)obj); } - public class PlayerVersion(Version ver, string tagStr, string forkId) + private bool Equals(PlayerVersion other) { - 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); - } + return forkId == other.forkId && tag == other.tag && Equals(version, other.version); } - public static class GameStates + public override int GetHashCode() { - public static bool InGame; - public static bool AlreadyDied; - public static bool IsModHost => PlayerControl.AllPlayerControls.ToArray().Any(x => x.IsHost() && x.IsModClient()); - 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.VoteStates.Voted or MeetingHud.VoteStates.NotVoted; - - public static bool IsCountDown => GameStartManager.InstanceExists && GameStartManager.Instance.startState == GameStartManager.StartingStates.Countdown; - - public static bool IsVanillaServer - { - get - { - if (IsLocalGame && !IsNotJoined) return true; - - const string domain = "among.us"; - - // From Reactor.gg - return ServerManager.Instance.CurrentRegion?.TryCast() is { } regionInfo && - regionInfo.PingServer.EndsWith(domain, StringComparison.Ordinal) && - regionInfo.Servers.All(serverInfo => serverInfo.Ip.EndsWith(domain, StringComparison.Ordinal)); - } - } - - /**********TOP ZOOM.cs***********/ - public static bool IsShip => ShipStatus.Instance != null; - public static bool IsCanMove => PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.CanMove; - public static bool IsDead => PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data != null && PlayerControl.LocalPlayer.Data.IsDead; + return HashCode.Combine(forkId, tag, version); } +} - public static class MeetingStates +public static class GameStates +{ + public static bool InGame; + public static bool AlreadyDied; + public static bool IsModHost => PlayerControl.AllPlayerControls.ToArray().Any(x => x.IsHost() && x.IsModClient()); + 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.VoteStates.Voted or MeetingHud.VoteStates.NotVoted; + + public static bool IsCountDown => GameStartManager.InstanceExists && GameStartManager.Instance.startState == GameStartManager.StartingStates.Countdown; + + public static bool IsVanillaServer { - public static DeadBody[] DeadBodies; + get + { + if (IsLocalGame && !IsNotJoined) return true; - public static int MeetingNum; - public static bool MeetingCalled; - public static bool FirstMeeting = true; - public static bool IsEmergencyMeeting => ReportTarget == null; - public static bool IsExistDeadBody => DeadBodies.Length > 0; + const string domain = "among.us"; - public static NetworkedPlayerInfo ReportTarget { get; set; } + // From Reactor.gg + return ServerManager.Instance.CurrentRegion?.TryCast() is { } regionInfo && + regionInfo.PingServer.EndsWith(domain, StringComparison.Ordinal) && + regionInfo.Servers.All(serverInfo => serverInfo.Ip.EndsWith(domain, StringComparison.Ordinal)); + } } + + /**********TOP ZOOM.cs***********/ + public static bool IsShip => ShipStatus.Instance != null; + public static bool IsCanMove => PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.CanMove; + public static bool IsDead => PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data != null && PlayerControl.LocalPlayer.Data.IsDead; +} + +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 == null; + public static bool IsExistDeadBody => DeadBodies.Length > 0; + + public static NetworkedPlayerInfo ReportTarget { get; set; } } \ No newline at end of file diff --git a/Modules/GhostRolesManager.cs b/Modules/GhostRolesManager.cs index 311527318..247951bed 100644 --- a/Modules/GhostRolesManager.cs +++ b/Modules/GhostRolesManager.cs @@ -8,161 +8,160 @@ using EHR.Neutral; using HarmonyLib; using UnityEngine; -namespace EHR.Modules +namespace EHR.Modules; + +internal static class GhostRolesManager { - internal static class GhostRolesManager + public static Dictionary AssignedGhostRoles = []; + private static List GhostRoles = []; + + public static void Initialize() { - public static Dictionary AssignedGhostRoles = []; - private static List GhostRoles = []; + AssignedGhostRoles = []; + GhostRoles = Enum.GetValues().Where(x => x != CustomRoles.EvilSpirit && x.IsGhostRole() && IRandom.Instance.Next(100) < x.GetMode()).ToList(); - public static void Initialize() + Logger.Msg($"Ghost roles: {GhostRoles.Join()}", "GhostRoles"); + Haunter.AllHauntedPlayers = []; + } + + public static void AssignGhostRole(PlayerControl pc) + { + if (GhostRoles.Count == 0) return; + + CustomRoles suitableRole = GetSuitableGhostRole(pc); + Logger.Warn($"Assigning Ghost Role: {pc.GetNameWithRole()} => {suitableRole}", "GhostRolesManager"); + + IGhostRole instance = CreateGhostRoleInstance(suitableRole); + pc.RpcSetCustomRole(suitableRole); + pc.RpcSetRole(RoleTypes.GuardianAngel); + instance.OnAssign(pc); + Main.ResetCamPlayerList.Add(pc.PlayerId); + AssignedGhostRoles[pc.PlayerId] = (suitableRole, instance); + + if (suitableRole == CustomRoles.Haunter) GhostRoles.Remove(suitableRole); + + NotifyAboutGhostRole(pc, true); + } + + public static void SpecificAssignGhostRole(byte id, CustomRoles role, bool set) + { + if (AssignedGhostRoles.Any(x => x.Key == id || x.Value.Role == role)) return; + + PlayerControl pc = Utils.GetPlayerById(id); + if (set) pc.RpcSetRole(RoleTypes.GuardianAngel); + + IGhostRole instance = CreateGhostRoleInstance(role); + instance.OnAssign(pc); + Main.ResetCamPlayerList.Add(pc.PlayerId); + AssignedGhostRoles[id] = (role, instance); + } + + public static void NotifyAboutGhostRole(PlayerControl pc, bool first = false) + { + if (!AssignedGhostRoles.TryGetValue(pc.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole)) return; + + if (!first && pc.IsModClient()) return; + + CustomRoles role = ghostRole.Role; + (string Split, string Message) info = GetMessage(Translator.GetString($"{role}InfoLong").Split("\n")[1..].Join(delimiter: "\n")); + var text = $"{Translator.GetString("GotGhostRoleNotify")}\n{info.Message}"; + var notifyText = $"{Translator.GetString("GotGhostRoleNotify")}\n{info.Split}"; + Utils.SendMessage(title: text, sendTo: pc.PlayerId, text: "\n"); + pc.Notify(notifyText, 5 + (5 * text.Count(x => x == '\n'))); + return; + + (string Split, string Message) GetMessage(string baseMessage) { - AssignedGhostRoles = []; - GhostRoles = Enum.GetValues().Where(x => x != CustomRoles.EvilSpirit && x.IsGhostRole() && IRandom.Instance.Next(100) < x.GetMode()).ToList(); + string message = baseMessage; - Logger.Msg($"Ghost roles: {GhostRoles.Join()}", "GhostRoles"); - Haunter.AllHauntedPlayers = []; - } - - public static void AssignGhostRole(PlayerControl pc) - { - if (GhostRoles.Count == 0) return; - - CustomRoles suitableRole = GetSuitableGhostRole(pc); - Logger.Warn($"Assigning Ghost Role: {pc.GetNameWithRole()} => {suitableRole}", "GhostRolesManager"); - - IGhostRole instance = CreateGhostRoleInstance(suitableRole); - pc.RpcSetCustomRole(suitableRole); - pc.RpcSetRole(RoleTypes.GuardianAngel); - instance.OnAssign(pc); - Main.ResetCamPlayerList.Add(pc.PlayerId); - AssignedGhostRoles[pc.PlayerId] = (suitableRole, instance); - - if (suitableRole == CustomRoles.Haunter) GhostRoles.Remove(suitableRole); - - NotifyAboutGhostRole(pc, true); - } - - public static void SpecificAssignGhostRole(byte id, CustomRoles role, bool set) - { - if (AssignedGhostRoles.Any(x => x.Key == id || x.Value.Role == role)) return; - - PlayerControl pc = Utils.GetPlayerById(id); - if (set) pc.RpcSetRole(RoleTypes.GuardianAngel); - - IGhostRole instance = CreateGhostRoleInstance(role); - instance.OnAssign(pc); - Main.ResetCamPlayerList.Add(pc.PlayerId); - AssignedGhostRoles[id] = (role, instance); - } - - public static void NotifyAboutGhostRole(PlayerControl pc, bool first = false) - { - if (!AssignedGhostRoles.TryGetValue(pc.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole)) return; - - if (!first && pc.IsModClient()) return; - - CustomRoles role = ghostRole.Role; - (string Split, string Message) info = GetMessage(Translator.GetString($"{role}InfoLong").Split("\n")[1..].Join(delimiter: "\n")); - var text = $"{Translator.GetString("GotGhostRoleNotify")}\n{info.Message}"; - var notifyText = $"{Translator.GetString("GotGhostRoleNotify")}\n{info.Split}"; - Utils.SendMessage(title: text, sendTo: pc.PlayerId, text: "\n"); - pc.Notify(notifyText, 5 + (5 * text.Count(x => x == '\n'))); - return; - - (string Split, string Message) GetMessage(string baseMessage) + for (var i = 50; i < message.Length; i += 50) { - string message = baseMessage; - - for (var i = 50; i < message.Length; i += 50) - { - int index = message.LastIndexOf(' ', i); - if (index != -1) message = message.Insert(index + 1, "\n"); - } - - return (ApplyFormat(message), ApplyFormat(baseMessage)); - - string ApplyFormat(string m) - { - return Utils.ColorString(Color.white, m.Replace(role.ToString(), role.ToColoredString())); - } + int index = message.LastIndexOf(' ', i); + if (index != -1) message = message.Insert(index + 1, "\n"); } - } - public static bool ShouldHaveGhostRole(PlayerControl pc) - { - try + return (ApplyFormat(message), ApplyFormat(baseMessage)); + + string ApplyFormat(string m) { - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; - - if (AssignedGhostRoles.Count >= GhostRoles.Count) return false; - - if (pc.IsAlive() || pc.GetCountTypes() is CountTypes.None or CountTypes.OutOfGame || pc.Is(CustomRoles.EvilSpirit) || pc.Is(CustomRoles.Curser)) return false; - - switch (pc.GetCustomRole()) - { - case CustomRoles.Backstabber: return false; - case CustomRoles.Workaholic when !Workaholic.WorkaholicCannotWinAtDeath.GetBool(): return false; - } - - CustomRoles suitableRole = GetSuitableGhostRole(pc); - - return suitableRole switch - { - CustomRoles.Specter when IsPartnerPickedRole() => false, - _ => suitableRole.IsGhostRole() && !AssignedGhostRoles.Any(x => x.Key == pc.PlayerId || x.Value.Role == suitableRole) - }; - - bool IsPartnerPickedRole() - { - return Main.PlayerStates[pc.PlayerId].Role switch - { - Romantic when Romantic.HasPickedPartner => true, - Totocalcio tc when tc.BetPlayer != byte.MaxValue => true, - _ => false - }; - } + return Utils.ColorString(Color.white, m.Replace(role.ToString(), role.ToColoredString())); } - catch (Exception e) - { - Utils.ThrowException(e); - return false; - } - } - - private static CustomRoles GetSuitableGhostRole(PlayerControl pc) - { - return GhostRoles.FirstOrDefault(x => AssignedGhostRoles.All(r => r.Value.Role != x) && (CreateGhostRoleInstance(x)?.Team & pc.GetTeam()) != 0); - } - - public static IGhostRole CreateGhostRoleInstance(CustomRoles ghostRole, bool check = false) - { - try - { - Type ghostRoleClass = Assembly.GetExecutingAssembly().GetTypes().First(x => typeof(IGhostRole).IsAssignableFrom(x) && !x.IsInterface && x.Name == $"{ghostRole}"); - var ghostRoleInstance = (IGhostRole)Activator.CreateInstance(ghostRoleClass); - return ghostRoleInstance; - } - catch (InvalidOperationException) - { - if (!check) Logger.Error($"Ghost role {ghostRole} not found", "CreateGhostRoleInstance"); - - return null; - } - catch (Exception e) - { - if (!check) Utils.ThrowException(e); - - return null; - } - } - - public static void RemoveGhostRole(byte id) - { - if (!AssignedGhostRoles.TryGetValue(id, out (CustomRoles Role, IGhostRole Instance) ghostRole)) return; - - Main.PlayerStates[id].RemoveSubRole(ghostRole.Role); - AssignedGhostRoles.Remove(id); } } + + public static bool ShouldHaveGhostRole(PlayerControl pc) + { + try + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; + + if (AssignedGhostRoles.Count >= GhostRoles.Count) return false; + + if (pc.IsAlive() || pc.GetCountTypes() is CountTypes.None or CountTypes.OutOfGame || pc.Is(CustomRoles.EvilSpirit) || pc.Is(CustomRoles.Curser)) return false; + + switch (pc.GetCustomRole()) + { + case CustomRoles.Backstabber: return false; + case CustomRoles.Workaholic when !Workaholic.WorkaholicCannotWinAtDeath.GetBool(): return false; + } + + CustomRoles suitableRole = GetSuitableGhostRole(pc); + + return suitableRole switch + { + CustomRoles.Specter when IsPartnerPickedRole() => false, + _ => suitableRole.IsGhostRole() && !AssignedGhostRoles.Any(x => x.Key == pc.PlayerId || x.Value.Role == suitableRole) + }; + + bool IsPartnerPickedRole() + { + return Main.PlayerStates[pc.PlayerId].Role switch + { + Romantic when Romantic.HasPickedPartner => true, + Totocalcio tc when tc.BetPlayer != byte.MaxValue => true, + _ => false + }; + } + } + catch (Exception e) + { + Utils.ThrowException(e); + return false; + } + } + + private static CustomRoles GetSuitableGhostRole(PlayerControl pc) + { + return GhostRoles.FirstOrDefault(x => AssignedGhostRoles.All(r => r.Value.Role != x) && (CreateGhostRoleInstance(x)?.Team & pc.GetTeam()) != 0); + } + + public static IGhostRole CreateGhostRoleInstance(CustomRoles ghostRole, bool check = false) + { + try + { + Type ghostRoleClass = Assembly.GetExecutingAssembly().GetTypes().First(x => typeof(IGhostRole).IsAssignableFrom(x) && !x.IsInterface && x.Name == $"{ghostRole}"); + var ghostRoleInstance = (IGhostRole)Activator.CreateInstance(ghostRoleClass); + return ghostRoleInstance; + } + catch (InvalidOperationException) + { + if (!check) Logger.Error($"Ghost role {ghostRole} not found", "CreateGhostRoleInstance"); + + return null; + } + catch (Exception e) + { + if (!check) Utils.ThrowException(e); + + return null; + } + } + + public static void RemoveGhostRole(byte id) + { + if (!AssignedGhostRoles.TryGetValue(id, out (CustomRoles Role, IGhostRole Instance) ghostRole)) return; + + Main.PlayerStates[id].RemoveSubRole(ghostRole.Role); + AssignedGhostRoles.Remove(id); + } } \ No newline at end of file diff --git a/Modules/GuessManager.cs b/Modules/GuessManager.cs index 870791ce4..c1acbd468 100644 --- a/Modules/GuessManager.cs +++ b/Modules/GuessManager.cs @@ -15,58 +15,58 @@ using UnityEngine.Events; using static EHR.Translator; -namespace EHR +namespace EHR; + +public static class GuessManager { - public static class GuessManager + private const int MaxOneScreenRole = 40; + private static int Page; + private static GameObject GuesserUI; + private static Dictionary> RoleButtons; + private static Dictionary RoleSelectButtons; + private static List PageButtons; + private static CustomRoleTypes CurrentTeamType; + + public static TextMeshPro TextTemplate; + private static readonly int Mask = Shader.PropertyToID("_Mask"); + + public static string GetFormatString() { - private const int MaxOneScreenRole = 40; - private static int Page; - private static GameObject GuesserUI; - private static Dictionary> RoleButtons; - private static Dictionary RoleSelectButtons; - private static List PageButtons; - private static CustomRoleTypes CurrentTeamType; + string text = GetString("PlayerIdList"); - public static TextMeshPro TextTemplate; - private static readonly int Mask = Shader.PropertyToID("_Mask"); - - public static string GetFormatString() + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - string text = GetString("PlayerIdList"); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - var id = pc.PlayerId.ToString(); - string name = pc.GetRealName(); - text += $"\n{id} → {name}"; - } - - return text; + var id = pc.PlayerId.ToString(); + string name = pc.GetRealName(); + text += $"\n{id} → {name}"; } - private static bool CheckCommand(ref string msg, string command, bool exact = true) - { - string[] comList = command.Split('|'); + return text; + } - foreach (string str in comList) + private static bool CheckCommand(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); + + foreach (string str in comList) + { + if (exact) { - if (exact) + if (msg == "/" + str) return true; + } + else + { + if (msg.StartsWith("/" + str)) { - if (msg == "/" + str) return true; - } - else - { - if (msg.StartsWith("/" + str)) - { - msg = msg.Replace("/" + str, string.Empty); - return true; - } + msg = msg.Replace("/" + str, string.Empty); + return true; } } - - return false; } + return false; + } + /* public static byte GetColorFromMsg(string msg) { @@ -98,1074 +98,1073 @@ namespace EHR } */ - public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) + public static bool GuesserMsg(PlayerControl pc, string msg, bool isUI = false) + { + string originMsg = msg; + + if (!AmongUsClient.Instance.AmHost) return false; + + if (!GameStates.IsMeeting || pc == null) return false; + + if (pc.GetCustomRole() is not (CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Doomsayer or CustomRoles.Judge or CustomRoles.NiceSwapper or CustomRoles.Councillor or CustomRoles.NecroGuesser) && !pc.Is(CustomRoles.Guesser) && !Options.GuesserMode.GetBool()) return false; + + int operate; // 1: ID, 2: Guess + msg = msg.ToLower().TrimStart().TrimEnd(); + + if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) + operate = 1; + else if (CheckCommand(ref msg, "shoot|guess|bet|st|bt|猜|赌", false)) + operate = 2; + else { - string originMsg = msg; + Logger.Msg("Not a guessing command", "Msg Guesser"); + return false; + } - if (!AmongUsClient.Instance.AmHost) return false; + Logger.Msg(msg, "Msg Guesser"); + Logger.Msg($"{operate}", "Operate"); - if (!GameStates.IsMeeting || pc == null) return false; - - if (pc.GetCustomRole() is not (CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Doomsayer or CustomRoles.Judge or CustomRoles.NiceSwapper or CustomRoles.Councillor or CustomRoles.NecroGuesser) && !pc.Is(CustomRoles.Guesser) && !Options.GuesserMode.GetBool()) return false; - - int operate; // 1: ID, 2: Guess - msg = msg.ToLower().TrimStart().TrimEnd(); - - if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) - operate = 1; - else if (CheckCommand(ref msg, "shoot|guess|bet|st|bt|猜|赌", false)) - operate = 2; - else + switch (operate) + { + case 1: + Utils.SendMessage(GetFormatString(), pc.PlayerId); + return true; + case 2: { - Logger.Msg("Not a guessing command", "Msg Guesser"); - return false; - } - - Logger.Msg(msg, "Msg Guesser"); - Logger.Msg($"{operate}", "Operate"); - - switch (operate) - { - case 1: - Utils.SendMessage(GetFormatString(), pc.PlayerId); - return true; - case 2: + if (!pc.IsAlive()) { - if (!pc.IsAlive()) + if (!isUI) + Utils.SendMessage(GetString("GuessDead"), pc.PlayerId); + else + pc.ShowPopUp(GetString("GuessDead")); + + return true; + } + + if (pc.Is(CustomRoles.Lyncher) && Lyncher.GuessMode.GetValue() == 2) goto SkipCheck; + + if ((!pc.Is(CustomRoles.NiceGuesser) && pc.IsCrewmate() && !Options.CrewmatesCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Judge) && !pc.Is(CustomRoles.NiceSwapper)) || + (!pc.Is(CustomRoles.EvilGuesser) && pc.IsImpostor() && !Options.ImpostorsCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Councillor)) || + (pc.IsNeutralKiller() && !Options.NeutralKillersCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser)) || + (pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && pc.GetCustomRole() is not CustomRoles.Doomsayer and not CustomRoles.NecroGuesser) || + (pc.Is(CustomRoles.Lyncher) && Lyncher.GuessMode.GetValue() == 0)) + { + if ((pc.Is(CustomRoles.Madmate) || pc.IsConverted()) && Options.BetrayalAddonsCanGuess.GetBool()) goto SkipCheck; + + ShowMessage("GuessNotAllowed"); + return true; + } + + SkipCheck: + + if (pc.GetCustomRole() is CustomRoles.Lyncher or CustomRoles.NecroGuesser || + (pc.Is(CustomRoles.NiceGuesser) && Options.GGTryHideMsg.GetBool()) || + (pc.Is(CustomRoles.EvilGuesser) && Options.EGTryHideMsg.GetBool()) || + (pc.Is(CustomRoles.Doomsayer) && Doomsayer.DoomsayerTryHideMsg.GetBool()) || + (pc.Is(CustomRoles.Guesser) && Guesser.GTryHideMsg.GetBool()) || (Options.GuesserMode.GetBool() && Options.HideGuesserCommands.GetBool())) + ChatManager.SendPreviousMessagesToAll(); + else if (pc.AmOwner && !isUI) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + + if (!MsgToPlayerAndRole(msg, out byte targetId, out CustomRoles role, out string error)) + { + ShowMessage(error); + return true; + } + + PlayerControl target = Utils.GetPlayerById(targetId); + + if (target != null) + { + Main.GuesserGuessed.TryAdd(pc.PlayerId, 0); + + var guesserSuicide = false; + + switch (pc.Is(CustomRoles.NecroGuesser)) { - if (!isUI) - Utils.SendMessage(GetString("GuessDead"), pc.PlayerId); - else - pc.ShowPopUp(GetString("GuessDead")); - - return true; - } - - if (pc.Is(CustomRoles.Lyncher) && Lyncher.GuessMode.GetValue() == 2) goto SkipCheck; - - if ((!pc.Is(CustomRoles.NiceGuesser) && pc.IsCrewmate() && !Options.CrewmatesCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Judge) && !pc.Is(CustomRoles.NiceSwapper)) || - (!pc.Is(CustomRoles.EvilGuesser) && pc.IsImpostor() && !Options.ImpostorsCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && !pc.Is(CustomRoles.Councillor)) || - (pc.IsNeutralKiller() && !Options.NeutralKillersCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser)) || - (pc.GetCustomRole().IsNonNK() && !Options.PassiveNeutralsCanGuess.GetBool() && !pc.Is(CustomRoles.Guesser) && pc.GetCustomRole() is not CustomRoles.Doomsayer and not CustomRoles.NecroGuesser) || - (pc.Is(CustomRoles.Lyncher) && Lyncher.GuessMode.GetValue() == 0)) - { - if ((pc.Is(CustomRoles.Madmate) || pc.IsConverted()) && Options.BetrayalAddonsCanGuess.GetBool()) goto SkipCheck; - - ShowMessage("GuessNotAllowed"); - return true; - } - - SkipCheck: - - if (pc.GetCustomRole() is CustomRoles.Lyncher or CustomRoles.NecroGuesser || - (pc.Is(CustomRoles.NiceGuesser) && Options.GGTryHideMsg.GetBool()) || - (pc.Is(CustomRoles.EvilGuesser) && Options.EGTryHideMsg.GetBool()) || - (pc.Is(CustomRoles.Doomsayer) && Doomsayer.DoomsayerTryHideMsg.GetBool()) || - (pc.Is(CustomRoles.Guesser) && Guesser.GTryHideMsg.GetBool()) || (Options.GuesserMode.GetBool() && Options.HideGuesserCommands.GetBool())) - ChatManager.SendPreviousMessagesToAll(); - else if (pc.AmOwner && !isUI) Utils.SendMessage(originMsg, 255, pc.GetRealName()); - - if (!MsgToPlayerAndRole(msg, out byte targetId, out CustomRoles role, out string error)) - { - ShowMessage(error); - return true; - } - - PlayerControl target = Utils.GetPlayerById(targetId); - - if (target != null) - { - Main.GuesserGuessed.TryAdd(pc.PlayerId, 0); - - var guesserSuicide = false; - - switch (pc.Is(CustomRoles.NecroGuesser)) - { - case true when (target.IsAlive() || target.Is(CustomRoles.Gravestone) || (Options.SeeEjectedRolesInMeeting.GetBool() && Main.PlayerStates[targetId].deathReason == PlayerState.DeathReason.Vote)): - ShowMessage(target.IsAlive() ? "NecroGuesser.TargetAliveError" : "NecroGuesser.TargetRevealedError"); - return true; - case false when !target.IsAlive(): - ShowMessage("GuessNull"); - return true; - } - - if (CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == pc.PlayerId)) - { - ShowMessage("GuessDisabled"); + case true when (target.IsAlive() || target.Is(CustomRoles.Gravestone) || (Options.SeeEjectedRolesInMeeting.GetBool() && Main.PlayerStates[targetId].deathReason == PlayerState.DeathReason.Vote)): + ShowMessage(target.IsAlive() ? "NecroGuesser.TargetAliveError" : "NecroGuesser.TargetRevealedError"); return true; - } - - if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, targetId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(targetId, CTAOption.GuessEachOther)) - { - ShowMessage("GuessSameCTAPlayer"); + case false when !target.IsAlive(): + ShowMessage("GuessNull"); return true; - } + } - bool hasGuessSetting = Options.AddonGuessSettings.TryGetValue(role, out OptionItem guessSetting); + if (CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == pc.PlayerId)) + { + ShowMessage("GuessDisabled"); + return true; + } - if (hasGuessSetting && guessSetting.GetValue() == 1) + if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, targetId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(targetId, CTAOption.GuessEachOther)) + { + ShowMessage("GuessSameCTAPlayer"); + return true; + } + + bool hasGuessSetting = Options.AddonGuessSettings.TryGetValue(role, out OptionItem guessSetting); + + if (hasGuessSetting && guessSetting.GetValue() == 1) + { + ShowMessage("GuessDisabledAddonOverride"); + return true; + } + + OptionItem convertedGuessSetting = null; + + if (role == CustomRoles.Egoist || role.IsConverted()) + { + convertedGuessSetting = role switch + { + CustomRoles.Charmed => Options.CharmedCanBeGuessed, + CustomRoles.Recruit => Options.RecruitCanBeGuessed, + CustomRoles.Contagious => Options.ContagiousCanBeGuessed, + CustomRoles.Undead => Options.UndeadCanBeGuessed, + CustomRoles.Egoist => Options.EgoistCanBeGuessed, + _ => null + }; + + if (convertedGuessSetting?.GetValue() == 1) { ShowMessage("GuessDisabledAddonOverride"); return true; } + } - OptionItem convertedGuessSetting = null; + bool forceAllowGuess = (hasGuessSetting && guessSetting.GetValue() == 0) || convertedGuessSetting?.GetValue() == 0 || (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor or CustomRoles.Lovers && Lovers.GuessAbility.GetValue() == 2); - if (role == CustomRoles.Egoist || role.IsConverted()) - { - convertedGuessSetting = role switch - { - CustomRoles.Charmed => Options.CharmedCanBeGuessed, - CustomRoles.Recruit => Options.RecruitCanBeGuessed, - CustomRoles.Contagious => Options.ContagiousCanBeGuessed, - CustomRoles.Undead => Options.UndeadCanBeGuessed, - CustomRoles.Egoist => Options.EgoistCanBeGuessed, - _ => null - }; + if (role == CustomRoles.Lovers && Lovers.GuessAbility.GetValue() == 0) + { + ShowMessage("GuessLovers"); + return true; + } - if (convertedGuessSetting?.GetValue() == 1) - { - ShowMessage("GuessDisabledAddonOverride"); - return true; - } - } - - bool forceAllowGuess = (hasGuessSetting && guessSetting.GetValue() == 0) || convertedGuessSetting?.GetValue() == 0 || (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor or CustomRoles.Lovers && Lovers.GuessAbility.GetValue() == 2); - - if (role == CustomRoles.Lovers && Lovers.GuessAbility.GetValue() == 0) - { - ShowMessage("GuessLovers"); + switch (pc.GetCustomRole()) + { + case CustomRoles.NiceGuesser when Main.GuesserGuessed[pc.PlayerId] >= Options.GGCanGuessTime.GetInt(): + ShowMessage("GGGuessMax"); return true; - } - - switch (pc.GetCustomRole()) - { - case CustomRoles.NiceGuesser when Main.GuesserGuessed[pc.PlayerId] >= Options.GGCanGuessTime.GetInt(): - ShowMessage("GGGuessMax"); - return true; - case CustomRoles.EvilGuesser when Main.GuesserGuessed[pc.PlayerId] >= Options.EGCanGuessTime.GetInt(): - ShowMessage("EGGuessMax"); - return true; - case CustomRoles.Shifter when !Shifter.CanGuess.GetBool(): - case CustomRoles.Phantasm when !Options.PhantomCanGuess.GetBool(): - case CustomRoles.Terrorist when !Options.TerroristCanGuess.GetBool(): - case CustomRoles.Workaholic when !Workaholic.WorkaholicCanGuess.GetBool(): - case CustomRoles.God when !God.GodCanGuess.GetBool(): - case CustomRoles.Executioner when Executioner.Target[pc.PlayerId] == target.PlayerId && Executioner.KnowTargetRole.GetBool() && !Executioner.CanGuessTarget.GetBool(): - ShowMessage("GuessDisabled"); - return true; - case CustomRoles.Monarch when role == CustomRoles.Knighted: - ShowMessage("GuessKnighted"); - return true; - case CustomRoles.Doomsayer: - if (Doomsayer.CantGuess) - { - ShowMessage("DoomsayerCantGuess"); - return true; - } - - if (( - (target.IsImpostor() && !Doomsayer.DCanGuessImpostors.GetBool()) || - (target.IsCrewmate() && !Doomsayer.DCanGuessCrewmates.GetBool()) || - ((role.IsNeutral() || target.IsNeutralKiller()) && !Doomsayer.DCanGuessNeutrals.GetBool())) - && !forceAllowGuess) - { - ShowMessage("GuessNotAllowed"); - return true; - } - - if (role.IsAdditionRole() && !Doomsayer.DCanGuessAdt.GetBool() && !forceAllowGuess) - { - ShowMessage("GuessAdtRole"); - return true; - } - - break; - } - - if (Medic.ProtectList.Contains(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) - { - ShowMessage("GuessShielded"); + case CustomRoles.EvilGuesser when Main.GuesserGuessed[pc.PlayerId] >= Options.EGCanGuessTime.GetInt(): + ShowMessage("EGGuessMax"); return true; - } - - switch (role) - { - case CustomRoles.Crewmate or CustomRoles.CrewmateEHR when CrewmateVanillaRoles.VanillaCrewmateCannotBeGuessed.GetBool(): - ShowMessage("GuessVanillaCrewmate"); - return true; - case CustomRoles.Workaholic when Workaholic.WorkaholicVisibleToEveryone.GetBool(): - ShowMessage("GuessWorkaholic"); - return true; - case CustomRoles.Doctor when Options.DoctorVisibleToEveryone.GetBool() && !target.HasEvilAddon(): - ShowMessage("GuessDoctor"); - return true; - case CustomRoles.Marshall when target.Is(CustomRoles.Marshall) && !Marshall.CanBeGuessedOnTaskCompletion.GetBool() && target.GetTaskState().IsTaskFinished: - ShowMessage("GuessMarshallTask"); - return true; - case CustomRoles.Monarch when pc.Is(CustomRoles.Knighted): - ShowMessage("GuessMonarch"); - return true; - case CustomRoles.Mayor when target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished: - ShowMessage("GuessMayor"); - return true; - case CustomRoles.Bait when Options.BaitNotification.GetBool(): - ShowMessage("GuessNotifiedBait"); - return true; - case CustomRoles.Pestilence: - ShowMessage("GuessPestilence"); - if (DoubleShot.CheckGuess(pc, isUI)) return true; - guesserSuicide = true; - break; - case CustomRoles.Phantasm: - ShowMessage("GuessPhantom"); - return true; - case CustomRoles.Snitch when target.GetTaskState().RemainingTasksCount <= Snitch.RemainingTasksToBeFound: - ShowMessage("EGGuessSnitchTaskDone"); - return true; - case CustomRoles.Merchant when Merchant.IsBribedKiller(pc, target): - ShowMessage("BribedByMerchant2"); - return true; - case CustomRoles.SuperStar: - ShowMessage("GuessSuperStar"); - return true; - case CustomRoles.Backstabber when target.Is(CustomRoles.Backstabber) && Backstabber.RevealAfterKilling.GetBool() && target.GetAbilityUseLimit() == 0f: - ShowMessage("GuessBackstabber"); - return true; - case CustomRoles.President when Main.PlayerStates[target.PlayerId].Role is President { IsRevealed: true }: - ShowMessage("GuessPresident"); - return true; - case CustomRoles.Eraser when Eraser.ErasedPlayers.Contains(target.PlayerId) && pc.Is(CustomRoles.Eraser): - case CustomRoles.NiceEraser when NiceEraser.ErasedPlayers.Contains(target.PlayerId) && pc.Is(CustomRoles.NiceEraser): - ShowMessage("GuessErased"); - return true; - case CustomRoles.Tank when !Tank.CanBeGuessed.GetBool(): - ShowMessage("GuessTank"); - return true; - case CustomRoles.DonutDelivery when DonutDelivery.IsUnguessable(pc, target): - case CustomRoles.Shifter: - case CustomRoles.Car: - case CustomRoles.Goose when !Goose.CanBeGuessed.GetBool(): - case CustomRoles.Disco: - case CustomRoles.Glow: - case CustomRoles.LastImpostor: - ShowMessage("GuessObviousAddon"); - return true; - case CustomRoles.GM: - Utils.SendMessage(GetString("GuessGM"), pc.PlayerId); - return true; - } - - if (target.Is(CustomRoles.Onbound)) - { - ShowMessage("GuessOnbound"); - - if (Onbound.GuesserSuicides.GetBool()) - { - if (DoubleShot.CheckGuess(pc, isUI)) return true; - guesserSuicide = true; - } - else return true; - } - - if (target.Is(CustomRoles.Lovers) && Lovers.GuessAbility.GetValue() == 0) - { - ShowMessage("GuessLovers"); + case CustomRoles.Shifter when !Shifter.CanGuess.GetBool(): + case CustomRoles.Phantasm when !Options.PhantomCanGuess.GetBool(): + case CustomRoles.Terrorist when !Options.TerroristCanGuess.GetBool(): + case CustomRoles.Workaholic when !Workaholic.WorkaholicCanGuess.GetBool(): + case CustomRoles.God when !God.GodCanGuess.GetBool(): + case CustomRoles.Executioner when Executioner.Target[pc.PlayerId] == target.PlayerId && Executioner.KnowTargetRole.GetBool() && !Executioner.CanGuessTarget.GetBool(): + ShowMessage("GuessDisabled"); return true; - } - - if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == target.PlayerId)) - { - if (!isUI) Utils.SendMessage(GetString("CantGuessJailed"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); - else pc.ShowPopUp($"{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))}\n{GetString("CantGuessJailed")}"); - + case CustomRoles.Monarch when role == CustomRoles.Knighted: + ShowMessage("GuessKnighted"); return true; - } - - if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == pc.PlayerId && role != CustomRoles.Jailor)) - { - if (!isUI) Utils.SendMessage(GetString("JailedCanOnlyGuessJailor"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); - else pc.ShowPopUp($"{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))}\n{GetString("JailedCanOnlyGuessJailor")}"); - - return true; - } - - if (Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { TargetRevealed: true } ms && ms.MarkedId == target.PlayerId)) - { - ShowMessage("GuessMarkseekerTarget"); - return true; - } - - // Check whether add-on guessing is allowed - if (!forceAllowGuess) - { - switch (pc.GetCustomRole()) - { - // Assassin & Nice Guesser Can't Guess Addons - case CustomRoles.EvilGuesser when role.IsAdditionRole() && !Options.EGCanGuessAdt.GetBool(): - case CustomRoles.NiceGuesser when role.IsAdditionRole() && !Options.GGCanGuessAdt.GetBool(): - ShowMessage("GuessAdtRole"); - return true; - // Guesser (add-on) Can't Guess Addons - default: - if (role.IsAdditionRole() && pc.Is(CustomRoles.Guesser) && !Guesser.GCanGuessAdt.GetBool()) - { - ShowMessage("GuessAdtRole"); - return true; - } - - break; - } - - // Guesser Mode Can/Can't Guess Addons - if (Options.GuesserMode.GetBool()) - { - if (role.IsAdditionRole() && !Options.CanGuessAddons.GetBool()) - { - if ((Options.ImpostorsCanGuess.GetBool() && pc.Is(CustomRoleTypes.Impostor) && !(pc.GetCustomRole() == CustomRoles.EvilGuesser || pc.Is(CustomRoles.Guesser))) || - (Options.CrewmatesCanGuess.GetBool() && pc.Is(CustomRoleTypes.Crewmate) && !(pc.GetCustomRole() == CustomRoles.NiceGuesser || pc.Is(CustomRoles.Guesser))) || - ((Options.NeutralKillersCanGuess.GetBool() || Options.PassiveNeutralsCanGuess.GetBool()) && pc.Is(CustomRoleTypes.Neutral) && !(pc.GetCustomRole() is CustomRoles.Ritualist or CustomRoles.Doomsayer || pc.Is(CustomRoles.Guesser)))) - { - ShowMessage("GuessAdtRole"); - return true; - } - } - } - } - - if (pc.PlayerId == target.PlayerId) - { - if (!isUI) Utils.SendMessage(GetString("LaughToWhoGuessSelf"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))); - else pc.ShowPopUp($"{Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))}\n{GetString("LaughToWhoGuessSelf")}"); - - if (DoubleShot.CheckGuess(pc, isUI)) return true; - guesserSuicide = true; - } - else if (pc.Is(CustomRoles.NiceGuesser) && target.IsCrewmate() && !Options.GGCanGuessCrew.GetBool() && !pc.Is(CustomRoles.Madmate)) - { - if (!isUI) Utils.SendMessage(GetString("GuessCrewRole"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))); - else pc.ShowPopUp($"{Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))}\n{GetString("GuessCrewRole")}"); - - return true; - } - else if (pc.Is(CustomRoles.EvilGuesser) && target.IsImpostor() && !Options.EGCanGuessImp.GetBool()) - { - if (!isUI) Utils.SendMessage(GetString("GuessImpRole"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))); - else pc.ShowPopUp($"{Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))}\n{GetString("GuessImpRole")}"); - - return true; - } - else if (!target.Is(role)) - { - if (DoubleShot.CheckGuess(pc, isUI)) return true; - - guesserSuicide = true; - Logger.Msg($"{guesserSuicide}", "guesserSuicide3"); - } - - if (guesserSuicide && Options.GuesserDoesntDieOnMisguess.GetBool()) - { - if (!isUI) Utils.SendMessage(GetString("MisguessButNoSuicide"), pc.PlayerId, Utils.ColorString(Color.yellow, GetString("MessageFromGurge44"))); - else pc.ShowPopUp($"{Utils.ColorString(Color.yellow, GetString("MessageFromGurge44"))}\n{GetString("MisguessButNoSuicide")}"); - - return true; - } - - - // ----------------------------------------------------------------------------------------------------------------------------------- - - - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} guessed {target.GetNameWithRole().RemoveHtmlTags()}", "Guesser"); - PlayerControl dp = guesserSuicide ? pc : target; - target = dp; - - Logger.Info($"Player: {target.GetRealName().RemoveHtmlTags()} was guessed by {pc.GetRealName().RemoveHtmlTags()}", "Guesser"); - - Main.GuesserGuessed[pc.PlayerId]++; - - if (Main.PlayerStates[pc.PlayerId].Role is NecroGuesser ng) - { - if (!guesserSuicide) ng.GuessedPlayers++; - ShowMessage(!guesserSuicide ? "NecroGuesser.GuessCorrect" : "NecroGuesser.GuessIncorrect"); - return true; - } - - if (pc.Is(CustomRoles.Doomsayer) && Doomsayer.AdvancedSettings.GetBool()) - { - if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt() && pc.PlayerId != dp.PlayerId) + case CustomRoles.Doomsayer: + if (Doomsayer.CantGuess) { ShowMessage("DoomsayerCantGuess"); return true; } - Doomsayer.GuessesCountPerMeeting++; - - if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt()) Doomsayer.CantGuess = true; - - if (!Doomsayer.KillCorrectlyGuessedPlayers.GetBool() && pc.PlayerId != dp.PlayerId) + if (( + (target.IsImpostor() && !Doomsayer.DCanGuessImpostors.GetBool()) || + (target.IsCrewmate() && !Doomsayer.DCanGuessCrewmates.GetBool()) || + ((role.IsNeutral() || target.IsNeutralKiller()) && !Doomsayer.DCanGuessNeutrals.GetBool())) + && !forceAllowGuess) { - ShowMessage("DoomsayerCorrectlyGuessRole"); + ShowMessage("GuessNotAllowed"); + return true; + } - if (Doomsayer.GuessedRoles.Contains(role)) - LateTask.New(() => Utils.SendMessage(GetString("DoomsayerGuessSameRoleAgainMsg"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerGuessCountTitle"))), 0.7f, "Doomsayer Guess Same Role Again Msg"); - else + if (role.IsAdditionRole() && !Doomsayer.DCanGuessAdt.GetBool() && !forceAllowGuess) + { + ShowMessage("GuessAdtRole"); + return true; + } + + break; + } + + if (Medic.ProtectList.Contains(target.PlayerId) && !Medic.GuesserIgnoreShield.GetBool()) + { + ShowMessage("GuessShielded"); + return true; + } + + switch (role) + { + case CustomRoles.Crewmate or CustomRoles.CrewmateEHR when CrewmateVanillaRoles.VanillaCrewmateCannotBeGuessed.GetBool(): + ShowMessage("GuessVanillaCrewmate"); + return true; + case CustomRoles.Workaholic when Workaholic.WorkaholicVisibleToEveryone.GetBool(): + ShowMessage("GuessWorkaholic"); + return true; + case CustomRoles.Doctor when Options.DoctorVisibleToEveryone.GetBool() && !target.HasEvilAddon(): + ShowMessage("GuessDoctor"); + return true; + case CustomRoles.Marshall when target.Is(CustomRoles.Marshall) && !Marshall.CanBeGuessedOnTaskCompletion.GetBool() && target.GetTaskState().IsTaskFinished: + ShowMessage("GuessMarshallTask"); + return true; + case CustomRoles.Monarch when pc.Is(CustomRoles.Knighted): + ShowMessage("GuessMonarch"); + return true; + case CustomRoles.Mayor when target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished: + ShowMessage("GuessMayor"); + return true; + case CustomRoles.Bait when Options.BaitNotification.GetBool(): + ShowMessage("GuessNotifiedBait"); + return true; + case CustomRoles.Pestilence: + ShowMessage("GuessPestilence"); + if (DoubleShot.CheckGuess(pc, isUI)) return true; + guesserSuicide = true; + break; + case CustomRoles.Phantasm: + ShowMessage("GuessPhantom"); + return true; + case CustomRoles.Snitch when target.GetTaskState().RemainingTasksCount <= Snitch.RemainingTasksToBeFound: + ShowMessage("EGGuessSnitchTaskDone"); + return true; + case CustomRoles.Merchant when Merchant.IsBribedKiller(pc, target): + ShowMessage("BribedByMerchant2"); + return true; + case CustomRoles.SuperStar: + ShowMessage("GuessSuperStar"); + return true; + case CustomRoles.Backstabber when target.Is(CustomRoles.Backstabber) && Backstabber.RevealAfterKilling.GetBool() && target.GetAbilityUseLimit() == 0f: + ShowMessage("GuessBackstabber"); + return true; + case CustomRoles.President when Main.PlayerStates[target.PlayerId].Role is President { IsRevealed: true }: + ShowMessage("GuessPresident"); + return true; + case CustomRoles.Eraser when Eraser.ErasedPlayers.Contains(target.PlayerId) && pc.Is(CustomRoles.Eraser): + case CustomRoles.NiceEraser when NiceEraser.ErasedPlayers.Contains(target.PlayerId) && pc.Is(CustomRoles.NiceEraser): + ShowMessage("GuessErased"); + return true; + case CustomRoles.Tank when !Tank.CanBeGuessed.GetBool(): + ShowMessage("GuessTank"); + return true; + case CustomRoles.DonutDelivery when DonutDelivery.IsUnguessable(pc, target): + case CustomRoles.Shifter: + case CustomRoles.Car: + case CustomRoles.Goose when !Goose.CanBeGuessed.GetBool(): + case CustomRoles.Disco: + case CustomRoles.Glow: + case CustomRoles.LastImpostor: + ShowMessage("GuessObviousAddon"); + return true; + case CustomRoles.GM: + Utils.SendMessage(GetString("GuessGM"), pc.PlayerId); + return true; + } + + if (target.Is(CustomRoles.Onbound)) + { + ShowMessage("GuessOnbound"); + + if (Onbound.GuesserSuicides.GetBool()) + { + if (DoubleShot.CheckGuess(pc, isUI)) return true; + guesserSuicide = true; + } + else return true; + } + + if (target.Is(CustomRoles.Lovers) && Lovers.GuessAbility.GetValue() == 0) + { + ShowMessage("GuessLovers"); + return true; + } + + if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == target.PlayerId)) + { + if (!isUI) Utils.SendMessage(GetString("CantGuessJailed"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); + else pc.ShowPopUp($"{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))}\n{GetString("CantGuessJailed")}"); + + return true; + } + + if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == pc.PlayerId && role != CustomRoles.Jailor)) + { + if (!isUI) Utils.SendMessage(GetString("JailedCanOnlyGuessJailor"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); + else pc.ShowPopUp($"{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))}\n{GetString("JailedCanOnlyGuessJailor")}"); + + return true; + } + + if (Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { TargetRevealed: true } ms && ms.MarkedId == target.PlayerId)) + { + ShowMessage("GuessMarkseekerTarget"); + return true; + } + + // Check whether add-on guessing is allowed + if (!forceAllowGuess) + { + switch (pc.GetCustomRole()) + { + // Assassin & Nice Guesser Can't Guess Addons + case CustomRoles.EvilGuesser when role.IsAdditionRole() && !Options.EGCanGuessAdt.GetBool(): + case CustomRoles.NiceGuesser when role.IsAdditionRole() && !Options.GGCanGuessAdt.GetBool(): + ShowMessage("GuessAdtRole"); + return true; + // Guesser (add-on) Can't Guess Addons + default: + if (role.IsAdditionRole() && pc.Is(CustomRoles.Guesser) && !Guesser.GCanGuessAdt.GetBool()) { - Doomsayer.GuessingToWin[pc.PlayerId]++; - Doomsayer.SendRPC(pc); - Doomsayer.GuessedRoles.Add(role); - - LateTask.New(() => Utils.SendMessage(string.Format(GetString("DoomsayerGuessCountMsg"), Doomsayer.GuessingToWin[pc.PlayerId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerGuessCountTitle"))), 0.7f, "Doomsayer Guess Msg 1"); + ShowMessage("GuessAdtRole"); + return true; } - Doomsayer.CheckCountGuess(pc); - - return true; - } - - if (Doomsayer.DoesNotSuicideWhenMisguessing.GetBool() && pc.PlayerId == dp.PlayerId) - { - ShowMessage("DoomsayerNotCorrectlyGuessRole"); - - if (Doomsayer.MisguessRolePrevGuessRoleUntilNextMeeting.GetBool()) - Doomsayer.CantGuess = true; - - return true; - } + break; } - string Name = dp.GetRealName(); - if (!Options.DisableKillAnimationOnGuess.GetBool()) CustomSoundsManager.RPCPlayCustomSoundAll("Gunfire"); - - LateTask.New(() => + // Guesser Mode Can/Can't Guess Addons + if (Options.GuesserMode.GetBool()) { - Main.PlayerStates[dp.PlayerId].deathReason = PlayerState.DeathReason.Gambled; - dp.SetRealKiller(pc); - dp.RpcGuesserMurderPlayer(); + if (role.IsAdditionRole() && !Options.CanGuessAddons.GetBool()) + { + if ((Options.ImpostorsCanGuess.GetBool() && pc.Is(CustomRoleTypes.Impostor) && !(pc.GetCustomRole() == CustomRoles.EvilGuesser || pc.Is(CustomRoles.Guesser))) || + (Options.CrewmatesCanGuess.GetBool() && pc.Is(CustomRoleTypes.Crewmate) && !(pc.GetCustomRole() == CustomRoles.NiceGuesser || pc.Is(CustomRoles.Guesser))) || + ((Options.NeutralKillersCanGuess.GetBool() || Options.PassiveNeutralsCanGuess.GetBool()) && pc.Is(CustomRoleTypes.Neutral) && !(pc.GetCustomRole() is CustomRoles.Ritualist or CustomRoles.Doomsayer || pc.Is(CustomRoles.Guesser)))) + { + ShowMessage("GuessAdtRole"); + return true; + } + } + } + } - if (dp.Is(CustomRoles.Medic)) Medic.IsDead(dp); + if (pc.PlayerId == target.PlayerId) + { + if (!isUI) Utils.SendMessage(GetString("LaughToWhoGuessSelf"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))); + else pc.ShowPopUp($"{Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))}\n{GetString("LaughToWhoGuessSelf")}"); - if (pc.Is(CustomRoles.Doomsayer) && pc.PlayerId != dp.PlayerId) + if (DoubleShot.CheckGuess(pc, isUI)) return true; + guesserSuicide = true; + } + else if (pc.Is(CustomRoles.NiceGuesser) && target.IsCrewmate() && !Options.GGCanGuessCrew.GetBool() && !pc.Is(CustomRoles.Madmate)) + { + if (!isUI) Utils.SendMessage(GetString("GuessCrewRole"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))); + else pc.ShowPopUp($"{Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))}\n{GetString("GuessCrewRole")}"); + + return true; + } + else if (pc.Is(CustomRoles.EvilGuesser) && target.IsImpostor() && !Options.EGCanGuessImp.GetBool()) + { + if (!isUI) Utils.SendMessage(GetString("GuessImpRole"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))); + else pc.ShowPopUp($"{Utils.ColorString(Color.cyan, GetString("MessageFromGurge44"))}\n{GetString("GuessImpRole")}"); + + return true; + } + else if (!target.Is(role)) + { + if (DoubleShot.CheckGuess(pc, isUI)) return true; + + guesserSuicide = true; + Logger.Msg($"{guesserSuicide}", "guesserSuicide3"); + } + + if (guesserSuicide && Options.GuesserDoesntDieOnMisguess.GetBool()) + { + if (!isUI) Utils.SendMessage(GetString("MisguessButNoSuicide"), pc.PlayerId, Utils.ColorString(Color.yellow, GetString("MessageFromGurge44"))); + else pc.ShowPopUp($"{Utils.ColorString(Color.yellow, GetString("MessageFromGurge44"))}\n{GetString("MisguessButNoSuicide")}"); + + return true; + } + + + // ----------------------------------------------------------------------------------------------------------------------------------- + + + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} guessed {target.GetNameWithRole().RemoveHtmlTags()}", "Guesser"); + PlayerControl dp = guesserSuicide ? pc : target; + target = dp; + + Logger.Info($"Player: {target.GetRealName().RemoveHtmlTags()} was guessed by {pc.GetRealName().RemoveHtmlTags()}", "Guesser"); + + Main.GuesserGuessed[pc.PlayerId]++; + + if (Main.PlayerStates[pc.PlayerId].Role is NecroGuesser ng) + { + if (!guesserSuicide) ng.GuessedPlayers++; + ShowMessage(!guesserSuicide ? "NecroGuesser.GuessCorrect" : "NecroGuesser.GuessIncorrect"); + return true; + } + + if (pc.Is(CustomRoles.Doomsayer) && Doomsayer.AdvancedSettings.GetBool()) + { + if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt() && pc.PlayerId != dp.PlayerId) + { + ShowMessage("DoomsayerCantGuess"); + return true; + } + + Doomsayer.GuessesCountPerMeeting++; + + if (Doomsayer.GuessesCountPerMeeting >= Doomsayer.MaxNumberOfGuessesPerMeeting.GetInt()) Doomsayer.CantGuess = true; + + if (!Doomsayer.KillCorrectlyGuessedPlayers.GetBool() && pc.PlayerId != dp.PlayerId) + { + ShowMessage("DoomsayerCorrectlyGuessRole"); + + if (Doomsayer.GuessedRoles.Contains(role)) + LateTask.New(() => Utils.SendMessage(GetString("DoomsayerGuessSameRoleAgainMsg"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerGuessCountTitle"))), 0.7f, "Doomsayer Guess Same Role Again Msg"); + else { Doomsayer.GuessingToWin[pc.PlayerId]++; Doomsayer.SendRPC(pc); + Doomsayer.GuessedRoles.Add(role); - if (!Doomsayer.GuessedRoles.Contains(role)) Doomsayer.GuessedRoles.Add(role); - - Doomsayer.CheckCountGuess(pc); + LateTask.New(() => Utils.SendMessage(string.Format(GetString("DoomsayerGuessCountMsg"), Doomsayer.GuessingToWin[pc.PlayerId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerGuessCountTitle"))), 0.7f, "Doomsayer Guess Msg 1"); } - GuessManagerRole.OnGuess(dp, pc); + Doomsayer.CheckCountGuess(pc); - Utils.AfterPlayerDeathTasks(dp, true); - Utils.NotifyRoles(GameStates.IsMeeting, NoCache: true); + return true; + } - LateTask.New(() => { Utils.SendMessage(string.Format(GetString("GuessKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("GuessKillTitle"))); }, 0.6f, "Guess Msg"); + if (Doomsayer.DoesNotSuicideWhenMisguessing.GetBool() && pc.PlayerId == dp.PlayerId) + { + ShowMessage("DoomsayerNotCorrectlyGuessRole"); - if (pc.Is(CustomRoles.Doomsayer) && pc.PlayerId != dp.PlayerId) LateTask.New(() => Utils.SendMessage(string.Format(GetString("DoomsayerGuessCountMsg"), Doomsayer.GuessingToWin[pc.PlayerId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerGuessCountTitle"))), 0.7f, "Doomsayer Guess Msg 2"); - if (pc.Is(CustomRoles.TicketsStealer) && pc.PlayerId != dp.PlayerId) LateTask.New(() => Utils.SendMessage(string.Format(GetString("TicketsStealerGetTicket"), (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == pc.PlayerId) * Options.TicketsPerKill.GetFloat()))), 0.7f, log: false); - if (pc.Is(CustomRoles.Pickpocket) && pc.PlayerId != dp.PlayerId) LateTask.New(() => Utils.SendMessage(string.Format(GetString("PickpocketGetVote"), (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == pc.PlayerId) * Pickpocket.VotesPerKill.GetFloat()))), 0.7f, log: false); - }, 0.2f, "Guesser Kill"); + if (Doomsayer.MisguessRolePrevGuessRoleUntilNextMeeting.GetBool()) + Doomsayer.CantGuess = true; - if (guesserSuicide && pc.IsLocalPlayer()) - Achievements.Type.BadLuckOrBadObservation.Complete(); + return true; + } } - break; - } - } + string Name = dp.GetRealName(); + if (!Options.DisableKillAnimationOnGuess.GetBool()) CustomSoundsManager.RPCPlayCustomSoundAll("Gunfire"); - return true; - - void ShowMessage(string str) - { - if (!isUI) Utils.SendMessage(GetString(str), pc.PlayerId); - else pc.ShowPopUp(GetString(str)); - } - } - - public static void RpcGuesserMurderPlayer(this PlayerControl pc /*, float delay = 0f*/) - { - // DEATH STUFF // - try - { - GameEndChecker.ShouldNotCheck = true; - pc.Data.IsDead = true; - pc.RpcExileV2(); - Main.PlayerStates[pc.PlayerId].SetDead(); - - var meetingHud = MeetingHud.Instance; - ProcessGuess(pc, meetingHud); - - foreach (PlayerVoteArea playerVoteArea in meetingHud.playerStates) - { - if (playerVoteArea.VotedFor != pc.PlayerId) continue; - - playerVoteArea.UnsetVote(); - - PlayerControl voteAreaPlayer = Utils.GetPlayerById(playerVoteArea.TargetPlayerId); - - if (!voteAreaPlayer.AmOwner) meetingHud.RpcClearVote(voteAreaPlayer.GetClientId()); - else meetingHud.ClearVote(); - } - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuessKill, SendOption.Reliable); - writer.Write(pc.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - finally - { - GameEndChecker.ShouldNotCheck = false; - } - } - - private static void ProcessGuess(PlayerControl pc, MeetingHud meetingHud) - { - HudManager hudManager = DestroyableSingleton.Instance; - SoundManager.Instance.PlaySound(pc.KillSfx, false, 0.8f); - if (!Options.DisableKillAnimationOnGuess.GetBool()) hudManager.KillOverlay.ShowKillAnimation(pc.Data, pc.Data); - - if (pc.AmOwner) - { - hudManager.ShadowQuad.gameObject.SetActive(false); - pc.cosmetics.nameText.GetComponent().material.SetInt(Mask, 0); - pc.RpcSetScanner(false); - var importantTextTask = new GameObject("_Player").AddComponent(); - importantTextTask.transform.SetParent(AmongUsClient.Instance.transform, false); - meetingHud.SetForegroundForDead(); - } - - PlayerVoteArea voteArea = MeetingHud.Instance.playerStates.First(x => x.TargetPlayerId == pc.PlayerId); - - if (voteArea.DidVote) voteArea.UnsetVote(); - - voteArea.AmDead = true; - voteArea.Overlay.gameObject.SetActive(true); - voteArea.Overlay.color = Color.white; - voteArea.XMark.gameObject.SetActive(true); - voteArea.XMark.transform.localScale = Vector3.one; - } - - public static void RpcClientGuess(PlayerControl pc) - { - try - { - var meetingHud = MeetingHud.Instance; - ProcessGuess(pc, meetingHud); - - foreach (PlayerVoteArea playerVoteArea in meetingHud.playerStates) - { - if (playerVoteArea.VotedFor != pc.PlayerId) continue; - - playerVoteArea.UnsetVote(); - - PlayerControl voteAreaPlayer = Utils.GetPlayerById(playerVoteArea.TargetPlayerId); - if (!voteAreaPlayer.AmOwner) continue; - - meetingHud.ClearVote(); - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles role, out string error) - { - if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); - - Regex r = new("\\d+"); - MatchCollection mc = r.Matches(msg); - var result = string.Empty; - for (var i = 0; i < mc.Count; i++) result += mc[i]; // The matching result is a complete number, and no splicing is required here. - - if (byte.TryParse(result, out byte num)) - id = num; - else - { - // It is not the player number, it determines whether it is the color or not. - //byte color = GetColorFromMsg(msg); - // Okay, I don’t know how to get the color of a certain player. I’ll fill it in later. - id = byte.MaxValue; - error = GetString("GuessHelp"); - role = new(); - return false; - } - - // Determine whether the selected player is reasonable - PlayerControl target = Utils.GetPlayerById(id); - - if (target == null) - { - error = GetString("GuessNull"); - role = new(); - return false; - } - - if (!ChatCommands.GetRoleByName(msg, out role)) - { - error = GetString("GuessHelp"); - return false; - } - - error = string.Empty; - return true; - } - - private static void CreateGuesserButton(MeetingHud __instance) - { - foreach (PlayerVoteArea pva in __instance.playerStates) - { - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; - - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new(-0.95f, 0.03f, -1.31f); - var renderer = targetBox.GetComponent(); - renderer.sprite = CustomButton.Get("TargetIcon"); - var button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - PlayerVoteArea pva1 = pva; - button.OnClick.AddListener((Action)(() => GuesserOnClick(pva1.TargetPlayerId, __instance))); - } - } - - private static void GuesserSelectRole(CustomRoleTypes Role, bool SetPage = true) - { - CurrentTeamType = Role; - if (SetPage) Page = 1; - - foreach (KeyValuePair> RoleButton in RoleButtons) - { - var index = 0; - - foreach (Transform RoleBtn in RoleButton.Value) - { - if (RoleBtn == null) continue; - - index++; - - if (index <= (Page - 1) * 40) + LateTask.New(() => { - RoleBtn.gameObject.SetActive(false); - continue; - } + Main.PlayerStates[dp.PlayerId].deathReason = PlayerState.DeathReason.Gambled; + dp.SetRealKiller(pc); + dp.RpcGuesserMurderPlayer(); - if (Page * 40 < index) - { - RoleBtn.gameObject.SetActive(false); - continue; - } + if (dp.Is(CustomRoles.Medic)) Medic.IsDead(dp); - RoleBtn.gameObject.SetActive(RoleButton.Key == Role); + if (pc.Is(CustomRoles.Doomsayer) && pc.PlayerId != dp.PlayerId) + { + Doomsayer.GuessingToWin[pc.PlayerId]++; + Doomsayer.SendRPC(pc); + + if (!Doomsayer.GuessedRoles.Contains(role)) Doomsayer.GuessedRoles.Add(role); + + Doomsayer.CheckCountGuess(pc); + } + + GuessManagerRole.OnGuess(dp, pc); + + Utils.AfterPlayerDeathTasks(dp, true); + Utils.NotifyRoles(GameStates.IsMeeting, NoCache: true); + + LateTask.New(() => { Utils.SendMessage(string.Format(GetString("GuessKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("GuessKillTitle"))); }, 0.6f, "Guess Msg"); + + if (pc.Is(CustomRoles.Doomsayer) && pc.PlayerId != dp.PlayerId) LateTask.New(() => Utils.SendMessage(string.Format(GetString("DoomsayerGuessCountMsg"), Doomsayer.GuessingToWin[pc.PlayerId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doomsayer), GetString("DoomsayerGuessCountTitle"))), 0.7f, "Doomsayer Guess Msg 2"); + if (pc.Is(CustomRoles.TicketsStealer) && pc.PlayerId != dp.PlayerId) LateTask.New(() => Utils.SendMessage(string.Format(GetString("TicketsStealerGetTicket"), (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == pc.PlayerId) * Options.TicketsPerKill.GetFloat()))), 0.7f, log: false); + if (pc.Is(CustomRoles.Pickpocket) && pc.PlayerId != dp.PlayerId) LateTask.New(() => Utils.SendMessage(string.Format(GetString("PickpocketGetVote"), (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == pc.PlayerId) * Pickpocket.VotesPerKill.GetFloat()))), 0.7f, log: false); + }, 0.2f, "Guesser Kill"); + + if (guesserSuicide && pc.IsLocalPlayer()) + Achievements.Type.BadLuckOrBadObservation.Complete(); } - } - foreach (KeyValuePair RoleButton in RoleSelectButtons) - { - if (RoleButton.Value == null) continue; - - RoleButton.Value.color = new(0, 0, 0, RoleButton.Key == Role ? 1 : 0.25f); + break; } } - private static void GuesserOnClick(byte playerId, MeetingHud __instance) + return true; + + void ShowMessage(string str) { - PlayerControl pc = Utils.GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || GuesserUI != null || !GameStates.IsVoting) return; + if (!isUI) Utils.SendMessage(GetString(str), pc.PlayerId); + else pc.ShowPopUp(GetString(str)); + } + } - try + public static void RpcGuesserMurderPlayer(this PlayerControl pc /*, float delay = 0f*/) + { + // DEATH STUFF // + try + { + GameEndChecker.ShouldNotCheck = true; + pc.Data.IsDead = true; + pc.RpcExileV2(); + Main.PlayerStates[pc.PlayerId].SetDead(); + + var meetingHud = MeetingHud.Instance; + ProcessGuess(pc, meetingHud); + + foreach (PlayerVoteArea playerVoteArea in meetingHud.playerStates) { - Page = 1; - RoleButtons = []; - RoleSelectButtons = []; - PageButtons = []; - __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(false)); + if (playerVoteArea.VotedFor != pc.PlayerId) continue; - Transform container = Object.Instantiate(GameObject.Find("PhoneUI").transform, __instance.transform); - container.transform.localPosition = new(0, 0, -200f); - GuesserUI = container.gameObject; + playerVoteArea.UnsetVote(); - List i = [0, 0, 0, 0]; + PlayerControl voteAreaPlayer = Utils.GetPlayerById(playerVoteArea.TargetPlayerId); + + if (!voteAreaPlayer.AmOwner) meetingHud.RpcClearVote(voteAreaPlayer.GetClientId()); + else meetingHud.ClearVote(); + } + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuessKill, SendOption.Reliable); + writer.Write(pc.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + finally + { + GameEndChecker.ShouldNotCheck = false; + } + } + + private static void ProcessGuess(PlayerControl pc, MeetingHud meetingHud) + { + HudManager hudManager = DestroyableSingleton.Instance; + SoundManager.Instance.PlaySound(pc.KillSfx, false, 0.8f); + if (!Options.DisableKillAnimationOnGuess.GetBool()) hudManager.KillOverlay.ShowKillAnimation(pc.Data, pc.Data); + + if (pc.AmOwner) + { + hudManager.ShadowQuad.gameObject.SetActive(false); + pc.cosmetics.nameText.GetComponent().material.SetInt(Mask, 0); + pc.RpcSetScanner(false); + var importantTextTask = new GameObject("_Player").AddComponent(); + importantTextTask.transform.SetParent(AmongUsClient.Instance.transform, false); + meetingHud.SetForegroundForDead(); + } + + PlayerVoteArea voteArea = MeetingHud.Instance.playerStates.First(x => x.TargetPlayerId == pc.PlayerId); + + if (voteArea.DidVote) voteArea.UnsetVote(); + + voteArea.AmDead = true; + voteArea.Overlay.gameObject.SetActive(true); + voteArea.Overlay.color = Color.white; + voteArea.XMark.gameObject.SetActive(true); + voteArea.XMark.transform.localScale = Vector3.one; + } + + public static void RpcClientGuess(PlayerControl pc) + { + try + { + var meetingHud = MeetingHud.Instance; + ProcessGuess(pc, meetingHud); + + foreach (PlayerVoteArea playerVoteArea in meetingHud.playerStates) + { + if (playerVoteArea.VotedFor != pc.PlayerId) continue; + + playerVoteArea.UnsetVote(); + + PlayerControl voteAreaPlayer = Utils.GetPlayerById(playerVoteArea.TargetPlayerId); + if (!voteAreaPlayer.AmOwner) continue; + + meetingHud.ClearVote(); + } + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public static bool MsgToPlayerAndRole(string msg, out byte id, out CustomRoles role, out string error) + { + if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); + + Regex r = new("\\d+"); + MatchCollection mc = r.Matches(msg); + var result = string.Empty; + for (var i = 0; i < mc.Count; i++) result += mc[i]; // The matching result is a complete number, and no splicing is required here. + + if (byte.TryParse(result, out byte num)) + id = num; + else + { + // It is not the player number, it determines whether it is the color or not. + //byte color = GetColorFromMsg(msg); + // Okay, I don’t know how to get the color of a certain player. I’ll fill it in later. + id = byte.MaxValue; + error = GetString("GuessHelp"); + role = new(); + return false; + } + + // Determine whether the selected player is reasonable + PlayerControl target = Utils.GetPlayerById(id); + + if (target == null) + { + error = GetString("GuessNull"); + role = new(); + return false; + } + + if (!ChatCommands.GetRoleByName(msg, out role)) + { + error = GetString("GuessHelp"); + return false; + } + + error = string.Empty; + return true; + } + + private static void CreateGuesserButton(MeetingHud __instance) + { + foreach (PlayerVoteArea pva in __instance.playerStates) + { + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "ShootButton"; + targetBox.transform.localPosition = new(-0.95f, 0.03f, -1.31f); + var renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("TargetIcon"); + var button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + PlayerVoteArea pva1 = pva; + button.OnClick.AddListener((Action)(() => GuesserOnClick(pva1.TargetPlayerId, __instance))); + } + } + + private static void GuesserSelectRole(CustomRoleTypes Role, bool SetPage = true) + { + CurrentTeamType = Role; + if (SetPage) Page = 1; + + foreach (KeyValuePair> RoleButton in RoleButtons) + { + var index = 0; + + foreach (Transform RoleBtn in RoleButton.Value) + { + if (RoleBtn == null) continue; + + index++; + + if (index <= (Page - 1) * 40) + { + RoleBtn.gameObject.SetActive(false); + continue; + } + + if (Page * 40 < index) + { + RoleBtn.gameObject.SetActive(false); + continue; + } + + RoleBtn.gameObject.SetActive(RoleButton.Key == Role); + } + } + + foreach (KeyValuePair RoleButton in RoleSelectButtons) + { + if (RoleButton.Value == null) continue; + + RoleButton.Value.color = new(0, 0, 0, RoleButton.Key == Role ? 1 : 0.25f); + } + } + + private static void GuesserOnClick(byte playerId, MeetingHud __instance) + { + PlayerControl pc = Utils.GetPlayerById(playerId); + if (pc == null || !pc.IsAlive() || GuesserUI != null || !GameStates.IsVoting) return; + + try + { + Page = 1; + RoleButtons = []; + RoleSelectButtons = []; + PageButtons = []; + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(false)); + + Transform container = Object.Instantiate(GameObject.Find("PhoneUI").transform, __instance.transform); + container.transform.localPosition = new(0, 0, -200f); + GuesserUI = container.gameObject; + + List i = [0, 0, 0, 0]; + Transform buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); + Transform maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); + Transform smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); + TextTemplate.enabled = true; + if (TextTemplate.transform.FindChild("RoleTextMeeting") != null) Object.Destroy(TextTemplate.transform.FindChild("RoleTextMeeting").gameObject); + + Transform exitButtonParent = new GameObject().transform; + exitButtonParent.SetParent(container); + Transform exitButton = Object.Instantiate(buttonTemplate, exitButtonParent); + exitButton.FindChild("ControllerHighlight").gameObject.SetActive(false); + Transform exitButtonMask = Object.Instantiate(maskTemplate, exitButtonParent); + exitButtonMask.transform.localScale = new(2.88f, 0.8f, 1f); + exitButtonMask.transform.localPosition = new(0f, 0f, 1f); + exitButton.gameObject.GetComponent().sprite = smallButtonTemplate.GetComponent().sprite; + exitButtonParent.transform.localPosition = new(3.88f, 2.12f, -200f); + exitButtonParent.transform.localScale = new(0.22f, 0.9f, 1f); + exitButtonParent.transform.SetAsFirstSibling(); + exitButton.GetComponent().OnClick.RemoveAllListeners(); + + exitButton.GetComponent().OnClick.AddListener((Action)(() => + { + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); + Object.Destroy(container.gameObject); + })); + + exitButton.GetComponent(); + + List buttons = []; + Transform selectedButton = null; + + var tabCount = 0; + + for (var index = 0; index < 4; index++) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.EvilGuesser)) + { + if (!Options.EGCanGuessImp.GetBool() && index == 1) continue; + if (!Options.EGCanGuessAdt.GetBool() && index == 3) continue; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.NiceGuesser)) + { + if (!Options.GGCanGuessCrew.GetBool() && index == 0) continue; + if (!Options.GGCanGuessAdt.GetBool() && index == 3) continue; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Doomsayer)) + { + if (!Doomsayer.DCanGuessCrewmates.GetBool() && index == 0) continue; + if (!Doomsayer.DCanGuessImpostors.GetBool() && index == 1) continue; + if (!Doomsayer.DCanGuessNeutrals.GetBool() && index == 2) continue; + if (!Doomsayer.DCanGuessAdt.GetBool() && index == 3) continue; + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Guesser)) + { + if (!Guesser.GCanGuessAdt.GetBool() && index == 3) continue; + } + else if (Options.GuesserMode.GetBool() && !PlayerControl.LocalPlayer.Is(CustomRoles.Guesser) && (!PlayerControl.LocalPlayer.Is(CustomRoles.Lyncher) || Lyncher.GuessMode.GetValue() != 2)) + { + if (!Options.CrewCanGuessCrew.GetBool() && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Crewmate) && index == 0) continue; + if (!Options.ImpCanGuessImp.GetBool() && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && index == 1) continue; + if (!Options.CanGuessAddons.GetBool() && index == 3) continue; + } + + Transform TeambuttonParent = new GameObject().transform; + TeambuttonParent.SetParent(container); + Transform Teambutton = Object.Instantiate(buttonTemplate, TeambuttonParent); + Teambutton.FindChild("ControllerHighlight").gameObject.SetActive(false); + Object.Instantiate(maskTemplate, TeambuttonParent); + TextMeshPro Teamlabel = Object.Instantiate(TextTemplate, Teambutton); + Teambutton.GetComponent().sprite = CustomButton.Get("GuessPlateWithKPD"); + RoleSelectButtons.Add((CustomRoleTypes)index, Teambutton.GetComponent()); + TeambuttonParent.localPosition = new(-2.75f + (tabCount++ * 1.73f), 2.225f, -200); + TeambuttonParent.localScale = new(0.53f, 0.53f, 1f); + + Teamlabel.color = (CustomRoleTypes)index switch + { + CustomRoleTypes.Crewmate => new(140, 255, 255, byte.MaxValue), + CustomRoleTypes.Impostor => new(255, 25, 25, byte.MaxValue), + CustomRoleTypes.Neutral => new(255, 171, 27, byte.MaxValue), + CustomRoleTypes.Addon => new Color32(255, 154, 206, byte.MaxValue), + _ => throw new NotImplementedException() + }; + + Logger.Info(Teamlabel.color.ToString(), ((CustomRoleTypes)index).ToString()); + Teamlabel.text = GetString("Type" + (CustomRoleTypes)index); + Teamlabel.alignment = TextAlignmentOptions.Center; + Teamlabel.transform.localPosition = new(0, 0, Teamlabel.transform.localPosition.z); + Teamlabel.transform.localScale *= 1.6f; + Teamlabel.autoSizeTextContainer = true; + + if (PlayerControl.LocalPlayer.IsAlive()) CreateTeamButton(Teambutton, (CustomRoleTypes)index); + + continue; + + static void CreateTeamButton(Component Teambutton, CustomRoleTypes type) + { + Teambutton.GetComponent().OnClick.AddListener((UnityAction)(() => + { + GuesserSelectRole(type); + ReloadPage(); + })); + } + } + + static void ReloadPage() + { + PageButtons[0].color = new(1, 1, 1, 1f); + PageButtons[1].color = new(1, 1, 1, 1f); + + if ((RoleButtons[CurrentTeamType].Count / MaxOneScreenRole) + (RoleButtons[CurrentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page) + { + Page -= 1; + PageButtons[1].color = new(1, 1, 1, 0.1f); + } + else if ((RoleButtons[CurrentTeamType].Count / MaxOneScreenRole) + (RoleButtons[CurrentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page + 1) PageButtons[1].color = new(1, 1, 1, 0.1f); + + if (Page <= 1) + { + Page = 1; + PageButtons[0].color = new(1, 1, 1, 0.1f); + } + + GuesserSelectRole(CurrentTeamType, false); + } + + static void CreatePage(bool IsNext, MeetingHud __instance, Transform container) + { Transform buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); Transform maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); - Transform smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); - TextTemplate.enabled = true; - if (TextTemplate.transform.FindChild("RoleTextMeeting") != null) Object.Destroy(TextTemplate.transform.FindChild("RoleTextMeeting").gameObject); + __instance.playerStates[0].Buttons.transform.Find("CancelButton"); + Transform PagebuttonParent = new GameObject().transform; + PagebuttonParent.SetParent(container); + Transform Pagebutton = Object.Instantiate(buttonTemplate, PagebuttonParent); + Pagebutton.FindChild("ControllerHighlight").gameObject.SetActive(false); + Object.Instantiate(maskTemplate, PagebuttonParent); + TextMeshPro Pagelabel = Object.Instantiate(TextTemplate, Pagebutton); + Pagebutton.GetComponent().sprite = CustomButton.Get("GuessPlateWithKPD"); + PagebuttonParent.localPosition = IsNext ? new(3.535f, -2.2f, -200) : new(-3.475f, -2.2f, -200); + PagebuttonParent.localScale = new(0.55f, 0.55f, 1f); + Pagelabel.color = Color.white; + Pagelabel.text = GetString(IsNext ? "NextPage" : "PreviousPage"); + Pagelabel.alignment = TextAlignmentOptions.Center; + Pagelabel.transform.localPosition = new(0, 0, Pagelabel.transform.localPosition.z); + Pagelabel.transform.localScale *= 1.6f; + Pagelabel.autoSizeTextContainer = true; + if (!IsNext && Page <= 1) Pagebutton.GetComponent().color = new(1, 1, 1, 0.1f); - Transform exitButtonParent = new GameObject().transform; - exitButtonParent.SetParent(container); - Transform exitButton = Object.Instantiate(buttonTemplate, exitButtonParent); - exitButton.FindChild("ControllerHighlight").gameObject.SetActive(false); - Transform exitButtonMask = Object.Instantiate(maskTemplate, exitButtonParent); - exitButtonMask.transform.localScale = new(2.88f, 0.8f, 1f); - exitButtonMask.transform.localPosition = new(0f, 0f, 1f); - exitButton.gameObject.GetComponent().sprite = smallButtonTemplate.GetComponent().sprite; - exitButtonParent.transform.localPosition = new(3.88f, 2.12f, -200f); - exitButtonParent.transform.localScale = new(0.22f, 0.9f, 1f); - exitButtonParent.transform.SetAsFirstSibling(); - exitButton.GetComponent().OnClick.RemoveAllListeners(); + Pagebutton.GetComponent().OnClick.AddListener((Action)ClickEvent); - exitButton.GetComponent().OnClick.AddListener((Action)(() => + PageButtons.Add(Pagebutton.GetComponent()); + return; + + void ClickEvent() { - __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); - Object.Destroy(container.gameObject); - })); + if (IsNext) + Page += 1; + else + Page -= 1; - exitButton.GetComponent(); + if (Page < 1) Page = 1; - List buttons = []; - Transform selectedButton = null; + ReloadPage(); + } + } - var tabCount = 0; + if (PlayerControl.LocalPlayer.IsAlive()) + { + CreatePage(false, __instance, container); + CreatePage(true, __instance, container); + } - for (var index = 0; index < 4; index++) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.EvilGuesser)) - { - if (!Options.EGCanGuessImp.GetBool() && index == 1) continue; - if (!Options.EGCanGuessAdt.GetBool() && index == 3) continue; - } - else if (PlayerControl.LocalPlayer.Is(CustomRoles.NiceGuesser)) - { - if (!Options.GGCanGuessCrew.GetBool() && index == 0) continue; - if (!Options.GGCanGuessAdt.GetBool() && index == 3) continue; - } - else if (PlayerControl.LocalPlayer.Is(CustomRoles.Doomsayer)) - { - if (!Doomsayer.DCanGuessCrewmates.GetBool() && index == 0) continue; - if (!Doomsayer.DCanGuessImpostors.GetBool() && index == 1) continue; - if (!Doomsayer.DCanGuessNeutrals.GetBool() && index == 2) continue; - if (!Doomsayer.DCanGuessAdt.GetBool() && index == 3) continue; - } - else if (PlayerControl.LocalPlayer.Is(CustomRoles.Guesser)) - { - if (!Guesser.GCanGuessAdt.GetBool() && index == 3) continue; - } - else if (Options.GuesserMode.GetBool() && !PlayerControl.LocalPlayer.Is(CustomRoles.Guesser) && (!PlayerControl.LocalPlayer.Is(CustomRoles.Lyncher) || Lyncher.GuessMode.GetValue() != 2)) - { - if (!Options.CrewCanGuessCrew.GetBool() && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Crewmate) && index == 0) continue; - if (!Options.ImpCanGuessImp.GetBool() && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && index == 1) continue; - if (!Options.CanGuessAddons.GetBool() && index == 3) continue; - } - - Transform TeambuttonParent = new GameObject().transform; - TeambuttonParent.SetParent(container); - Transform Teambutton = Object.Instantiate(buttonTemplate, TeambuttonParent); - Teambutton.FindChild("ControllerHighlight").gameObject.SetActive(false); - Object.Instantiate(maskTemplate, TeambuttonParent); - TextMeshPro Teamlabel = Object.Instantiate(TextTemplate, Teambutton); - Teambutton.GetComponent().sprite = CustomButton.Get("GuessPlateWithKPD"); - RoleSelectButtons.Add((CustomRoleTypes)index, Teambutton.GetComponent()); - TeambuttonParent.localPosition = new(-2.75f + (tabCount++ * 1.73f), 2.225f, -200); - TeambuttonParent.localScale = new(0.53f, 0.53f, 1f); - - Teamlabel.color = (CustomRoleTypes)index switch - { - CustomRoleTypes.Crewmate => new(140, 255, 255, byte.MaxValue), - CustomRoleTypes.Impostor => new(255, 25, 25, byte.MaxValue), - CustomRoleTypes.Neutral => new(255, 171, 27, byte.MaxValue), - CustomRoleTypes.Addon => new Color32(255, 154, 206, byte.MaxValue), - _ => throw new NotImplementedException() - }; - - Logger.Info(Teamlabel.color.ToString(), ((CustomRoleTypes)index).ToString()); - Teamlabel.text = GetString("Type" + (CustomRoleTypes)index); - Teamlabel.alignment = TextAlignmentOptions.Center; - Teamlabel.transform.localPosition = new(0, 0, Teamlabel.transform.localPosition.z); - Teamlabel.transform.localScale *= 1.6f; - Teamlabel.autoSizeTextContainer = true; - - if (PlayerControl.LocalPlayer.IsAlive()) CreateTeamButton(Teambutton, (CustomRoleTypes)index); + CustomRoles[] sortedRoles = Enum.GetValues().OrderBy(x => GetString($"{x}")).ToArray(); + foreach (CustomRoles role in sortedRoles) + { + if (role is CustomRoles.GM + or CustomRoles.SpeedBooster + or CustomRoles.Engineer + or CustomRoles.Crewmate + or CustomRoles.Oblivious + or CustomRoles.Scientist + or CustomRoles.Impostor + or CustomRoles.Shapeshifter + or CustomRoles.Flashman + or CustomRoles.Disco + or CustomRoles.Giant + or CustomRoles.NotAssigned + or CustomRoles.KB_Normal + or CustomRoles.Paranoia + or CustomRoles.SuperStar + or CustomRoles.Konan + or CustomRoles.GuardianAngelEHR + ) continue; - static void CreateTeamButton(Component Teambutton, CustomRoleTypes type) - { - Teambutton.GetComponent().OnClick.AddListener((UnityAction)(() => - { - GuesserSelectRole(type); - ReloadPage(); - })); - } - } + if (!role.IsEnable() && !role.RoleExist(true) && !role.IsConverted()) continue; - static void ReloadPage() - { - PageButtons[0].color = new(1, 1, 1, 1f); - PageButtons[1].color = new(1, 1, 1, 1f); + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || HnSManager.AllHnSRoles.Contains(role)) continue; - if ((RoleButtons[CurrentTeamType].Count / MaxOneScreenRole) + (RoleButtons[CurrentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page) - { - Page -= 1; - PageButtons[1].color = new(1, 1, 1, 0.1f); - } - else if ((RoleButtons[CurrentTeamType].Count / MaxOneScreenRole) + (RoleButtons[CurrentTeamType].Count % MaxOneScreenRole != 0 ? 1 : 0) < Page + 1) PageButtons[1].color = new(1, 1, 1, 0.1f); + CreateRole(role); + } - if (Page <= 1) - { - Page = 1; - PageButtons[0].color = new(1, 1, 1, 0.1f); - } + void CreateRole(CustomRoles role) + { + if (40 <= i[(int)role.GetCustomRoleTypes()]) i[(int)role.GetCustomRoleTypes()] = 0; - GuesserSelectRole(CurrentTeamType, false); - } + Transform buttonParent = new GameObject().transform; + buttonParent.SetParent(container); + Transform button = Object.Instantiate(buttonTemplate, buttonParent); + button.FindChild("ControllerHighlight").gameObject.SetActive(false); + Object.Instantiate(maskTemplate, buttonParent); + TextMeshPro label = Object.Instantiate(TextTemplate, button); + button.GetComponent().sprite = CustomButton.Get("GuessPlate"); + if (!RoleButtons.ContainsKey(role.GetCustomRoleTypes())) RoleButtons.Add(role.GetCustomRoleTypes(), []); - static void CreatePage(bool IsNext, MeetingHud __instance, Transform container) - { - Transform buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase"); - Transform maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea"); - __instance.playerStates[0].Buttons.transform.Find("CancelButton"); - Transform PagebuttonParent = new GameObject().transform; - PagebuttonParent.SetParent(container); - Transform Pagebutton = Object.Instantiate(buttonTemplate, PagebuttonParent); - Pagebutton.FindChild("ControllerHighlight").gameObject.SetActive(false); - Object.Instantiate(maskTemplate, PagebuttonParent); - TextMeshPro Pagelabel = Object.Instantiate(TextTemplate, Pagebutton); - Pagebutton.GetComponent().sprite = CustomButton.Get("GuessPlateWithKPD"); - PagebuttonParent.localPosition = IsNext ? new(3.535f, -2.2f, -200) : new(-3.475f, -2.2f, -200); - PagebuttonParent.localScale = new(0.55f, 0.55f, 1f); - Pagelabel.color = Color.white; - Pagelabel.text = GetString(IsNext ? "NextPage" : "PreviousPage"); - Pagelabel.alignment = TextAlignmentOptions.Center; - Pagelabel.transform.localPosition = new(0, 0, Pagelabel.transform.localPosition.z); - Pagelabel.transform.localScale *= 1.6f; - Pagelabel.autoSizeTextContainer = true; - if (!IsNext && Page <= 1) Pagebutton.GetComponent().color = new(1, 1, 1, 0.1f); + RoleButtons[role.GetCustomRoleTypes()].Add(button); + buttons.Add(button); + int row = i[(int)role.GetCustomRoleTypes()] / 5; + int col = i[(int)role.GetCustomRoleTypes()] % 5; + buttonParent.localPosition = new(-3.47f + (1.75f * col), 1.5f - (0.45f * row), -200f); + buttonParent.localScale = new(0.55f, 0.55f, 1f); + label.text = GetString(role.ToString()); + label.color = Utils.GetRoleColor(role); + label.alignment = TextAlignmentOptions.Center; + label.transform.localPosition = new(0, 0, label.transform.localPosition.z); + label.transform.localScale *= 1.6f; + label.autoSizeTextContainer = true; + _ = i[(int)role.GetCustomRoleTypes()]; - Pagebutton.GetComponent().OnClick.AddListener((Action)ClickEvent); - - PageButtons.Add(Pagebutton.GetComponent()); - return; - - void ClickEvent() - { - if (IsNext) - Page += 1; - else - Page -= 1; - - if (Page < 1) Page = 1; - - ReloadPage(); - } - } + button.GetComponent().OnClick.RemoveAllListeners(); if (PlayerControl.LocalPlayer.IsAlive()) { - CreatePage(false, __instance, container); - CreatePage(true, __instance, container); - } - - CustomRoles[] sortedRoles = Enum.GetValues().OrderBy(x => GetString($"{x}")).ToArray(); - - foreach (CustomRoles role in sortedRoles) - { - if (role is CustomRoles.GM - or CustomRoles.SpeedBooster - or CustomRoles.Engineer - or CustomRoles.Crewmate - or CustomRoles.Oblivious - or CustomRoles.Scientist - or CustomRoles.Impostor - or CustomRoles.Shapeshifter - or CustomRoles.Flashman - or CustomRoles.Disco - or CustomRoles.Giant - or CustomRoles.NotAssigned - or CustomRoles.KB_Normal - or CustomRoles.Paranoia - or CustomRoles.SuperStar - or CustomRoles.Konan - or CustomRoles.GuardianAngelEHR - ) - continue; - - if (!role.IsEnable() && !role.RoleExist(true) && !role.IsConverted()) continue; - - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || HnSManager.AllHnSRoles.Contains(role)) continue; - - CreateRole(role); - } - - void CreateRole(CustomRoles role) - { - if (40 <= i[(int)role.GetCustomRoleTypes()]) i[(int)role.GetCustomRoleTypes()] = 0; - - Transform buttonParent = new GameObject().transform; - buttonParent.SetParent(container); - Transform button = Object.Instantiate(buttonTemplate, buttonParent); - button.FindChild("ControllerHighlight").gameObject.SetActive(false); - Object.Instantiate(maskTemplate, buttonParent); - TextMeshPro label = Object.Instantiate(TextTemplate, button); - button.GetComponent().sprite = CustomButton.Get("GuessPlate"); - if (!RoleButtons.ContainsKey(role.GetCustomRoleTypes())) RoleButtons.Add(role.GetCustomRoleTypes(), []); - - RoleButtons[role.GetCustomRoleTypes()].Add(button); - buttons.Add(button); - int row = i[(int)role.GetCustomRoleTypes()] / 5; - int col = i[(int)role.GetCustomRoleTypes()] % 5; - buttonParent.localPosition = new(-3.47f + (1.75f * col), 1.5f - (0.45f * row), -200f); - buttonParent.localScale = new(0.55f, 0.55f, 1f); - label.text = GetString(role.ToString()); - label.color = Utils.GetRoleColor(role); - label.alignment = TextAlignmentOptions.Center; - label.transform.localPosition = new(0, 0, label.transform.localPosition.z); - label.transform.localScale *= 1.6f; - label.autoSizeTextContainer = true; - _ = i[(int)role.GetCustomRoleTypes()]; - - button.GetComponent().OnClick.RemoveAllListeners(); - - if (PlayerControl.LocalPlayer.IsAlive()) + button.GetComponent().OnClick.AddListener((Action)(() => { - button.GetComponent().OnClick.AddListener((Action)(() => + if (selectedButton != button) { - if (selectedButton != button) - { - selectedButton = button; - buttons.ForEach(x => x.GetComponent().color = x == selectedButton ? Utils.GetRoleColor(PlayerControl.LocalPlayer.GetCustomRole()) : Color.white); - } + selectedButton = button; + buttons.ForEach(x => x.GetComponent().color = x == selectedButton ? Utils.GetRoleColor(PlayerControl.LocalPlayer.GetCustomRole()) : Color.white); + } + else + { + if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || !PlayerControl.LocalPlayer.IsAlive()) return; + + Logger.Msg($"Click: {pc.GetNameWithRole().RemoveHtmlTags()} => {role}", "Guesser UI"); + + if (AmongUsClient.Instance.AmHost) + GuesserMsg(PlayerControl.LocalPlayer, $"/bt {playerId} {GetString(role.ToString())}", true); else - { - if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || !PlayerControl.LocalPlayer.IsAlive()) return; + SendRPC(playerId, role); - Logger.Msg($"Click: {pc.GetNameWithRole().RemoveHtmlTags()} => {role}", "Guesser UI"); - - if (AmongUsClient.Instance.AmHost) - GuesserMsg(PlayerControl.LocalPlayer, $"/bt {playerId} {GetString(role.ToString())}", true); - else - SendRPC(playerId, role); - - // Reset the GUI - __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); - Object.Destroy(container.gameObject); - TextTemplate.enabled = false; - } - })); - } - - i[(int)role.GetCustomRoleTypes()]++; + // Reset the GUI + __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); + Object.Destroy(container.gameObject); + TextTemplate.enabled = false; + } + })); } - container.transform.localScale *= 0.75f; - GuesserSelectRole(CustomRoleTypes.Crewmate); - ReloadPage(); - } - catch (Exception ex) - { - Logger.Exception(ex, "Guesser UI"); - return; + i[(int)role.GetCustomRoleTypes()]++; } - PlayerControl.LocalPlayer.RPCPlayCustomSound("Gunload"); + container.transform.localScale *= 0.75f; + GuesserSelectRole(CustomRoleTypes.Crewmate); + ReloadPage(); } - - // Modded non-host client guess role/add-on - private static void SendRPC(int playerId, CustomRoles role) + catch (Exception ex) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (int)CustomRPC.Guess, SendOption.Reliable); - writer.Write(playerId); - writer.Write((int)role); - AmongUsClient.Instance.FinishRpcImmediately(writer); + Logger.Exception(ex, "Guesser UI"); + return; } - public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + PlayerControl.LocalPlayer.RPCPlayCustomSound("Gunload"); + } + + // Modded non-host client guess role/add-on + private static void SendRPC(int playerId, CustomRoles role) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (int)CustomRPC.Guess, SendOption.Reliable); + writer.Write(playerId); + writer.Write((int)role); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + Logger.Msg($"{reader}", "GuessManager - MessageReader reader"); + Logger.Msg($"{pc}", "GuessManager - PlayerControl pc"); + + int PlayerId = reader.ReadInt32(); + Logger.Msg($"{PlayerId}", "GuessManager - Player Id"); + + var role = (CustomRoles)reader.ReadInt32(); + Logger.Msg($"{role}", "GuessManager - Role Int32"); + Logger.Msg($"{GetString(role.ToString())}", "GuessManager - Role String"); + + GuesserMsg(pc, $"/bt {PlayerId} {GetString(role.ToString())}", true); + } + + /* + public static void TryHideMsg() { - Logger.Msg($"{reader}", "GuessManager - MessageReader reader"); - Logger.Msg($"{pc}", "GuessManager - PlayerControl pc"); - - int PlayerId = reader.ReadInt32(); - Logger.Msg($"{PlayerId}", "GuessManager - Player Id"); - - var role = (CustomRoles)reader.ReadInt32(); - Logger.Msg($"{role}", "GuessManager - Role Int32"); - Logger.Msg($"{GetString(role.ToString())}", "GuessManager - Role String"); - - GuesserMsg(pc, $"/bt {PlayerId} {GetString(role.ToString())}", true); - } - - /* - public static void TryHideMsg() + ChatUpdatePatch.DoBlockChat = true; + List roles = Enum.GetValues(typeof(CustomRoles)).Cast().Where(x => x is not CustomRoles.NotAssigned and not CustomRoles.KB_Normal).ToList(); + var rd = IRandom.Instance; + string msg = Utils.EmptyMessage(); + string[] command = ["bet", "bt", "guess", "gs", "shoot", "st", "赌", "猜", "审判", "tl", "判", "审"]; + var x = Main.AllAlivePlayerControls; + var totalAlive = Main.AllAlivePlayerControls.Length; + for (int i = 0; i < 20; i++) { - ChatUpdatePatch.DoBlockChat = true; - List roles = Enum.GetValues(typeof(CustomRoles)).Cast().Where(x => x is not CustomRoles.NotAssigned and not CustomRoles.KB_Normal).ToList(); - var rd = IRandom.Instance; - string msg = Utils.EmptyMessage(); - string[] command = ["bet", "bt", "guess", "gs", "shoot", "st", "赌", "猜", "审判", "tl", "判", "审"]; - var x = Main.AllAlivePlayerControls; - var totalAlive = Main.AllAlivePlayerControls.Length; - for (int i = 0; i < 20; i++) + //msg = "/"; + //if (rd.Next(1, 100) < 20) + //{ + // msg += "id"; + //} + //else + //{ + // msg += command[rd.Next(0, command.Length - 1)]; + // msg += rd.Next(1, 100) < 50 ? string.Empty : " "; + // msg += rd.Next(0, 15).ToString(); + // msg += rd.Next(1, 100) < 50 ? string.Empty : " "; + // CustomRoles role = roles[rd.Next(0, roles.Count)]; + // msg += rd.Next(1, 100) < 50 ? string.Empty : " "; + // msg += Utils.GetRoleName(role); + //} + var player = x[rd.Next(0, totalAlive)]; + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + var writer = CustomRpcSender.Create("MessagesToSend"); + writer.StartMessage(); + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + writer.EndMessage(); + writer.SendMessage(); + } + + ChatUpdatePatch.DoBlockChat = false; + } + */ + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + private static class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + PlayerControl lp = PlayerControl.LocalPlayer; + if (!lp.IsAlive()) return; + + bool canGuess = lp.Is(CustomRoles.Guesser) || lp.GetCustomRole() switch + { + CustomRoles.EvilGuesser => true, + CustomRoles.NiceGuesser => true, + CustomRoles.NecroGuesser => true, + CustomRoles.Doomsayer when !Doomsayer.CantGuess => true, + CustomRoles.Lyncher when Lyncher.GuessMode.GetValue() == 2 => true, + _ when Options.GuesserMode.GetBool() => lp.GetTeam() switch { - //msg = "/"; - //if (rd.Next(1, 100) < 20) - //{ - // msg += "id"; - //} - //else - //{ - // msg += command[rd.Next(0, command.Length - 1)]; - // msg += rd.Next(1, 100) < 50 ? string.Empty : " "; - // msg += rd.Next(0, 15).ToString(); - // msg += rd.Next(1, 100) < 50 ? string.Empty : " "; - // CustomRoles role = roles[rd.Next(0, roles.Count)]; - // msg += rd.Next(1, 100) < 50 ? string.Empty : " "; - // msg += Utils.GetRoleName(role); - //} - var player = x[rd.Next(0, totalAlive)]; - DestroyableSingleton.Instance.Chat.AddChat(player, msg); - var writer = CustomRpcSender.Create("MessagesToSend"); - writer.StartMessage(); - writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) - .Write(msg) - .EndRpc(); - writer.EndMessage(); - writer.SendMessage(); - } - - ChatUpdatePatch.DoBlockChat = false; - } - */ - - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - private static class StartMeetingPatch - { - public static void Postfix(MeetingHud __instance) - { - PlayerControl lp = PlayerControl.LocalPlayer; - if (!lp.IsAlive()) return; - - bool canGuess = lp.Is(CustomRoles.Guesser) || lp.GetCustomRole() switch - { - CustomRoles.EvilGuesser => true, - CustomRoles.NiceGuesser => true, - CustomRoles.NecroGuesser => true, - CustomRoles.Doomsayer when !Doomsayer.CantGuess => true, - CustomRoles.Lyncher when Lyncher.GuessMode.GetValue() == 2 => true, - _ when Options.GuesserMode.GetBool() => lp.GetTeam() switch - { - Team.Impostor => Options.ImpostorsCanGuess.GetBool(), - Team.Crewmate => Options.CrewmatesCanGuess.GetBool(), - Team.Neutral when lp.IsNeutralKiller() => Options.NeutralKillersCanGuess.GetBool(), - Team.Neutral => Options.PassiveNeutralsCanGuess.GetBool(), - _ => false - }, + Team.Impostor => Options.ImpostorsCanGuess.GetBool(), + Team.Crewmate => Options.CrewmatesCanGuess.GetBool(), + Team.Neutral when lp.IsNeutralKiller() => Options.NeutralKillersCanGuess.GetBool(), + Team.Neutral => Options.PassiveNeutralsCanGuess.GetBool(), _ => false - }; + }, + _ => false + }; - if (canGuess) CreateGuesserButton(__instance); - } + if (canGuess) CreateGuesserButton(__instance); } + } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))] - private static class MeetingHudOnDestroyGuesserUIClose + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))] + private static class MeetingHudOnDestroyGuesserUIClose + { + public static void Postfix() { - public static void Postfix() - { - Object.Destroy(TextTemplate.gameObject); - } + Object.Destroy(TextTemplate.gameObject); } } } \ No newline at end of file diff --git a/Modules/HashAuth.cs b/Modules/HashAuth.cs index 39ccffe9b..9ca229584 100644 --- a/Modules/HashAuth.cs +++ b/Modules/HashAuth.cs @@ -1,60 +1,59 @@ using System.Security.Cryptography; using System.Text; -namespace EHR +namespace EHR; + +public class HashAuth(string hashValue, string salt = null, HashAlgorithm algorithm = null) { - public class HashAuth(string hashValue, string salt = null, HashAlgorithm algorithm = null) + private readonly HashAlgorithm algorithm = algorithm ?? SHA256.Create(); + + public bool CheckString(string value) { - private readonly HashAlgorithm algorithm = algorithm ?? SHA256.Create(); + string hash = CalculateHash(value); + return hashValue == hash; + } - public bool CheckString(string value) - { - string hash = CalculateHash(value); - return hashValue == hash; - } + private string CalculateHash(string source) + { + return CalculateHash(source, salt, algorithm); + } - private string CalculateHash(string source) - { - return CalculateHash(source, salt, algorithm); - } + private static string CalculateHash(string source, string salt = null, HashAlgorithm algorithm = null) + { + // 0. Initialize algorithm + algorithm ??= SHA256.Create(); - private static string CalculateHash(string source, string salt = null, HashAlgorithm algorithm = null) - { - // 0. Initialize algorithm - algorithm ??= SHA256.Create(); + // 1. Apply salt + if (salt != null) source += salt; - // 1. Apply salt - if (salt != null) source += salt; + // 2. Convert source to a byte array + byte[] sourceBytes = Encoding.UTF8.GetBytes(source); - // 2. Convert source to a byte array - byte[] sourceBytes = Encoding.UTF8.GetBytes(source); + // 3. Hash sourceBytes + byte[] hashBytes = algorithm.ComputeHash(sourceBytes); - // 3. Hash sourceBytes - byte[] hashBytes = algorithm.ComputeHash(sourceBytes); + // 4. Convert hashBytes to a string + var sb = new StringBuilder(); + foreach (byte b in hashBytes) sb.Append(b.ToString("x2")); // Convert each byte to 2-digit hexadecimal notation - // 4. Convert hashBytes to a string - var sb = new StringBuilder(); - foreach (byte b in hashBytes) sb.Append(b.ToString("x2")); // Convert each byte to 2-digit hexadecimal notation + return sb.ToString(); + } - return sb.ToString(); - } + // To check the hash value, create an instance after hashing + // This is only for checking the hash value and testing the operation at the same time. Do not use after checking. + public static HashAuth CreateByUnhashedValue(string value, string salt = null) + { + // 1. Calculate hash value + var algorithm = SHA256.Create(); + string hashValue = CalculateHash(value, salt, algorithm); - // To check the hash value, create an instance after hashing - // This is only for checking the hash value and testing the operation at the same time. Do not use after checking. - public static HashAuth CreateByUnhashedValue(string value, string salt = null) - { - // 1. Calculate hash value - var algorithm = SHA256.Create(); - string hashValue = CalculateHash(value, salt, algorithm); + // 2. Log output of hash value + // With salt: Hash value calculation result: => (salt: ) + // Without salt: Hash value calculation result: => + Logger.Info($"Hash value calculation result: {value} => {hashValue} {(salt == null ? string.Empty : $"(salt: {salt})")}", "HashAuth"); + Logger.Warn("Please paste the above values into the source code.", "HashAuth"); - // 2. Log output of hash value - // With salt: Hash value calculation result: => (salt: ) - // Without salt: Hash value calculation result: => - Logger.Info($"Hash value calculation result: {value} => {hashValue} {(salt == null ? string.Empty : $"(salt: {salt})")}", "HashAuth"); - Logger.Warn("Please paste the above values into the source code.", "HashAuth"); - - // 3. Create and return a HashAuth instance - return new(hashValue, salt, algorithm); - } + // 3. Create and return a HashAuth instance + return new(hashValue, salt, algorithm); } } \ No newline at end of file diff --git a/Modules/LateTask.cs b/Modules/LateTask.cs index 36a0b77b1..5aabcab2c 100644 --- a/Modules/LateTask.cs +++ b/Modules/LateTask.cs @@ -2,73 +2,72 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -namespace EHR +namespace EHR; + +internal class LateTask { - internal class LateTask + private static readonly List Tasks = []; + private readonly Action action; + private readonly string callerData; + private readonly bool log; + private readonly string name; + private float timer; + + private LateTask(Action action, float time, string name, bool log, string callerData) { - private static readonly List Tasks = []; - private readonly Action action; - private readonly string callerData; - private readonly bool log; - private readonly string name; - private float timer; + this.action = action; + timer = time; + this.name = name; + this.log = log; + this.callerData = callerData; + Tasks.Add(this); + if (log && name is not "" and not "No Name Task") Logger.Info("\"" + name + "\" is created", "LateTask"); + } - private LateTask(Action action, float time, string name, bool log, string callerData) + private bool Run(float deltaTime) + { + timer -= deltaTime; + + if (timer <= 0) { - this.action = action; - timer = time; - this.name = name; - this.log = log; - this.callerData = callerData; - Tasks.Add(this); - if (log && name is not "" and not "No Name Task") Logger.Info("\"" + name + "\" is created", "LateTask"); + action(); + return true; } - private bool Run(float deltaTime) - { - timer -= deltaTime; + return false; + } - if (timer <= 0) + /// + /// Creates a task that will be automatically completed after the specified amount of time + /// + /// The delayed task + /// The time to wait until the task is run + /// The name of the task + /// Whether to send log of the creation and completion of the Late Task + public static void New(Action action, float time, string name = "No Name Task", bool log = true, [CallerFilePath] string path = "", [CallerMemberName] string member = "", [CallerLineNumber] int line = 0) + { + _ = new LateTask(action, time, name, log, $"created at {path.Split('\\')[^1]}, by member {member}, at line {line}"); + } + + public static void Update(float deltaTime) + { + foreach (LateTask task in Tasks.ToArray()) + { + try { - action(); - return true; - } - - return false; - } - - /// - /// Creates a task that will be automatically completed after the specified amount of time - /// - /// The delayed task - /// The time to wait until the task is run - /// The name of the task - /// Whether to send log of the creation and completion of the Late Task - public static void New(Action action, float time, string name = "No Name Task", bool log = true, [CallerFilePath] string path = "", [CallerMemberName] string member = "", [CallerLineNumber] int line = 0) - { - _ = new LateTask(action, time, name, log, $"created at {path.Split('\\')[^1]}, by member {member}, at line {line}"); - } - - public static void Update(float deltaTime) - { - foreach (LateTask task in Tasks.ToArray()) - { - try + if (task.Run(deltaTime)) { - if (task.Run(deltaTime)) - { - if (task.name is not "" and not "No Name Task" && task.log) - Logger.Info($"\"{task.name}\" is finished", "LateTask"); + if (task.name is not "" and not "No Name Task" && task.log) + Logger.Info($"\"{task.name}\" is finished", "LateTask"); - Tasks.Remove(task); - } - } - catch (Exception ex) - { - Logger.Error($"{ex.GetType()}: {ex.Message} in \"{task.name}\" ({task.callerData})\n{ex.StackTrace}", "LateTask.Error", false); Tasks.Remove(task); } } + catch (Exception ex) + { + Logger.Error($"{ex.GetType()}: {ex.Message} in \"{task.name}\" ({task.callerData})\n{ex.StackTrace}", "LateTask.Error", false); + Tasks.Remove(task); + } } } } \ No newline at end of file diff --git a/Modules/LoadingScreen.cs b/Modules/LoadingScreen.cs index 49da05dae..cb5537eab 100644 --- a/Modules/LoadingScreen.cs +++ b/Modules/LoadingScreen.cs @@ -4,111 +4,110 @@ using EHR.Patches; using Rewired.Utils; using UnityEngine; -namespace EHR.Modules +namespace EHR.Modules; + +internal static class LoadingScreen { - internal static class LoadingScreen + private const int HintCount = 40; + private const int JokeHintCount = 6; + private static SpriteRenderer LoadingAnimation; + private static readonly HashSet ToldHints = []; + private static float HintHideTimer; + public static string Hint; + + private static void UpdateLoadingAnimation() { - private const int HintCount = 40; - private const int JokeHintCount = 6; - private static SpriteRenderer LoadingAnimation; - private static readonly HashSet ToldHints = []; - private static float HintHideTimer; - public static string Hint; - - private static void UpdateLoadingAnimation() + try { - try + if (!LoadingAnimation.IsNullOrDestroyed()) Object.Destroy(LoadingAnimation); + + LoadingAnimation = Object.Instantiate(ModManager.Instance.ModStamp); + LoadingAnimation.sprite = Utils.LoadSprite("EHR.Resources.Loading.png", 300f); + LoadingAnimation.sortingOrder = 100; + + Vector3 basePos = LoadingAnimation.transform.position; + float x = basePos.x - 9.8f; + float y = basePos.y - 4.5f; + float z = basePos.z; + LoadingAnimation.transform.position = new(x, y, z); + } + catch (Exception ex) + { + Logger.Error(ex.ToString(), "LoadingScreen.UpdateLoadingAnimation"); + } + } + + private static void NewHint() + { + int index; + if (ToldHints.Count == HintCount) ToldHints.Clear(); + + do + index = IRandom.Instance.Next(HintCount); + while (!ToldHints.Add(index)); + + bool joke = IRandom.Instance.Next(20) == 0; + if (joke) index = IRandom.Instance.Next(40, 40 + JokeHintCount); + + string text = Translator.GetString($"LoadingHint.{index}"); + text = text.Insert(0, joke ? "" : ""); + text = text.Insert(text.IndexOf('\n'), "<#ffffff>"); + text += ""; + Hint = text; + } + + public static void Update() + { + try + { + if (HintHideTimer <= 15f) HintHideTimer += Time.deltaTime; + + PlayerControl lp = PlayerControl.LocalPlayer; + if (lp == null) return; + + PlayerAnimations anims = lp.MyPhysics.Animations; + + bool visible = AmongUsClient.Instance.AmHost && AmongUsClient.Instance.IsGameStarted && !GameStates.IsCanMove && (!GameStates.IsInTask || ExileController.Instance) && !GameStates.IsMeeting && !HudManager.Instance.Chat.IsOpenOrOpening && !lp.inVent && !anims.IsPlayingAnyLadderAnimation() && !VentButtonDoClickPatch.Animating && !lp.onLadder; + + switch (visible) { - if (!LoadingAnimation.IsNullOrDestroyed()) Object.Destroy(LoadingAnimation); + case false when LoadingAnimation: + Object.Destroy(LoadingAnimation); + return; + case true when !LoadingAnimation: + UpdateLoadingAnimation(); + return; + } - LoadingAnimation = Object.Instantiate(ModManager.Instance.ModStamp); - LoadingAnimation.sprite = Utils.LoadSprite("EHR.Resources.Loading.png", 300f); - LoadingAnimation.sortingOrder = 100; + if (LoadingAnimation) + { + Vector3 basePos = ModManager.Instance.ModStamp.transform.position; - Vector3 basePos = LoadingAnimation.transform.position; float x = basePos.x - 9.8f; float y = basePos.y - 4.5f; float z = basePos.z; - LoadingAnimation.transform.position = new(x, y, z); + + if (LoadingAnimation.transform.position != new Vector3(x, y, z)) LoadingAnimation.transform.position = new(x, y, z); + + LoadingAnimation.transform.Rotate(Vector3.forward, 200f * Time.deltaTime); } - catch (Exception ex) + + switch (visible) { - Logger.Error(ex.ToString(), "LoadingScreen.UpdateLoadingAnimation"); + case false when ErrorText.HasHint: + ErrorText.RemoveHint(); + return; + case true when !ErrorText.HasHint: + if (HintHideTimer > 15f) NewHint(); + + HintHideTimer = 0f; + ErrorText.Instance.AddError(ErrorCode.LoadingHint); + break; } } - - private static void NewHint() + catch (Exception ex) { - int index; - if (ToldHints.Count == HintCount) ToldHints.Clear(); - - do - index = IRandom.Instance.Next(HintCount); - while (!ToldHints.Add(index)); - - bool joke = IRandom.Instance.Next(20) == 0; - if (joke) index = IRandom.Instance.Next(40, 40 + JokeHintCount); - - string text = Translator.GetString($"LoadingHint.{index}"); - text = text.Insert(0, joke ? "" : ""); - text = text.Insert(text.IndexOf('\n'), "<#ffffff>"); - text += ""; - Hint = text; - } - - public static void Update() - { - try - { - if (HintHideTimer <= 15f) HintHideTimer += Time.deltaTime; - - PlayerControl lp = PlayerControl.LocalPlayer; - if (lp == null) return; - - PlayerAnimations anims = lp.MyPhysics.Animations; - - bool visible = AmongUsClient.Instance.AmHost && AmongUsClient.Instance.IsGameStarted && !GameStates.IsCanMove && (!GameStates.IsInTask || ExileController.Instance) && !GameStates.IsMeeting && !HudManager.Instance.Chat.IsOpenOrOpening && !lp.inVent && !anims.IsPlayingAnyLadderAnimation() && !VentButtonDoClickPatch.Animating && !lp.onLadder; - - switch (visible) - { - case false when LoadingAnimation: - Object.Destroy(LoadingAnimation); - return; - case true when !LoadingAnimation: - UpdateLoadingAnimation(); - return; - } - - if (LoadingAnimation) - { - Vector3 basePos = ModManager.Instance.ModStamp.transform.position; - - float x = basePos.x - 9.8f; - float y = basePos.y - 4.5f; - float z = basePos.z; - - if (LoadingAnimation.transform.position != new Vector3(x, y, z)) LoadingAnimation.transform.position = new(x, y, z); - - LoadingAnimation.transform.Rotate(Vector3.forward, 200f * Time.deltaTime); - } - - switch (visible) - { - case false when ErrorText.HasHint: - ErrorText.RemoveHint(); - return; - case true when !ErrorText.HasHint: - if (HintHideTimer > 15f) NewHint(); - - HintHideTimer = 0f; - ErrorText.Instance.AddError(ErrorCode.LoadingHint); - break; - } - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "LoadingScreen.Update"); - } + Logger.Error(ex.ToString(), "LoadingScreen.Update"); } } } \ No newline at end of file diff --git a/Modules/LobbyNotifierForDiscord.cs b/Modules/LobbyNotifierForDiscord.cs index 4c96e183c..b7c4ef95e 100644 --- a/Modules/LobbyNotifierForDiscord.cs +++ b/Modules/LobbyNotifierForDiscord.cs @@ -10,154 +10,153 @@ using InnerNet; using UnityEngine; using UnityEngine.Networking; -namespace EHR.Modules +namespace EHR.Modules; + +public static class LobbyNotifierForDiscord { - public static class LobbyNotifierForDiscord + private const float BufferTime = 5; + public static long LastRequestTimeStamp; + public static string LastRoomCode = string.Empty; + private static string Token = string.Empty; + + private static string WebhookUrl { - private const float BufferTime = 5; - public static long LastRequestTimeStamp; - public static string LastRoomCode = string.Empty; - private static string Token = string.Empty; - - private static string WebhookUrl + get { - get - { - const string path = "EHR.Resources.Config.URL.txt"; - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)!; - stream.Position = 0; - using StreamReader reader = new(stream, Encoding.UTF8); - return reader.ReadToEnd(); - } + const string path = "EHR.Resources.Config.URL.txt"; + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path)!; + stream.Position = 0; + using StreamReader reader = new(stream, Encoding.UTF8); + return reader.ReadToEnd(); + } + } + + private static void NotifyLobbyCreated() + { + var gameId = AmongUsClient.Instance.GameId; + if (gameId == 32) return; + + var roomCode = GameCode.IntToGameName(gameId); + if (roomCode == LastRoomCode) return; + LastRoomCode = roomCode; + + var serverName = Utils.GetRegionName(); + var language = Translator.GetUserTrueLang().ToString(); + Main.Instance.StartCoroutine(SendLobbyCreatedRequest(roomCode, serverName, language, $"EHR v{Main.PluginDisplayVersion}")); + } + + private static IEnumerator SendLobbyCreatedRequest(string roomCode, string serverName, string language, string version) + { + var timeSinceLastRequest = Utils.TimeStamp - LastRequestTimeStamp; + + if (timeSinceLastRequest < BufferTime) + { + yield return new WaitForSeconds(BufferTime - timeSinceLastRequest); } - private static void NotifyLobbyCreated() + LastRequestTimeStamp = Utils.TimeStamp; + + var jsonData = $"{{\"roomCode\":\"{roomCode}\",\"serverName\":\"{serverName}\",\"language\":\"{language}\",\"version\":\"{version}\"}}"; + byte[] jsonToSend = new UTF8Encoding().GetBytes(jsonData); + + UnityWebRequest request = new UnityWebRequest(WebhookUrl, "POST") { - var gameId = AmongUsClient.Instance.GameId; - if (gameId == 32) return; + uploadHandler = new UploadHandlerRaw(jsonToSend), + downloadHandler = new DownloadHandlerBuffer() + }; - var roomCode = GameCode.IntToGameName(gameId); - if (roomCode == LastRoomCode) return; - LastRoomCode = roomCode; + request.SetRequestHeader("Content-Type", "application/json"); + yield return request.SendWebRequest(); - var serverName = Utils.GetRegionName(); - var language = Translator.GetUserTrueLang().ToString(); - Main.Instance.StartCoroutine(SendLobbyCreatedRequest(roomCode, serverName, language, $"EHR v{Main.PluginDisplayVersion}")); + bool success = request.result == UnityWebRequest.Result.Success; + Logger.Msg(success ? "Lobby created notification sent successfully." : $"Failed to send lobby created notification: {request.error}", "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); + + if (success) + { + try + { + string responseText = request.downloadHandler.text; + Logger.Msg("Response from server: " + responseText, "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); + + using JsonDocument doc = JsonDocument.Parse(responseText); + Token = doc.RootElement.GetProperty("token").GetString(); + + Logger.Msg($"Token for room {roomCode}: {Token}", "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); + } + catch (Exception ex) + { + Logger.Msg($"Failed to parse token from response: {ex.Message}", "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); + } + + Utils.SendMessage("\n", PlayerControl.LocalPlayer.PlayerId, Translator.GetString("Message.LobbyCodeSent")); } + } - private static IEnumerator SendLobbyCreatedRequest(string roomCode, string serverName, string language, string version) + public static void NotifyLobbyStatusChanged(LobbyStatus status) + { + if (!Options.PostLobbyCodeToEHRDiscordServer.GetBool() || !AmongUsClient.Instance.AmHost) return; + + if (GameCode.IntToGameName(AmongUsClient.Instance.GameId) != LastRoomCode) { - var timeSinceLastRequest = Utils.TimeStamp - LastRequestTimeStamp; - - if (timeSinceLastRequest < BufferTime) - { - yield return new WaitForSeconds(BufferTime - timeSinceLastRequest); - } - - LastRequestTimeStamp = Utils.TimeStamp; - - var jsonData = $"{{\"roomCode\":\"{roomCode}\",\"serverName\":\"{serverName}\",\"language\":\"{language}\",\"version\":\"{version}\"}}"; - byte[] jsonToSend = new UTF8Encoding().GetBytes(jsonData); - - UnityWebRequest request = new UnityWebRequest(WebhookUrl, "POST") - { - uploadHandler = new UploadHandlerRaw(jsonToSend), - downloadHandler = new DownloadHandlerBuffer() - }; - - request.SetRequestHeader("Content-Type", "application/json"); - yield return request.SendWebRequest(); - - bool success = request.result == UnityWebRequest.Result.Success; - Logger.Msg(success ? "Lobby created notification sent successfully." : $"Failed to send lobby created notification: {request.error}", "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); - - if (success) - { - try - { - string responseText = request.downloadHandler.text; - Logger.Msg("Response from server: " + responseText, "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); - - using JsonDocument doc = JsonDocument.Parse(responseText); - Token = doc.RootElement.GetProperty("token").GetString(); - - Logger.Msg($"Token for room {roomCode}: {Token}", "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); - } - catch (Exception ex) - { - Logger.Msg($"Failed to parse token from response: {ex.Message}", "LobbyNotifierForDiscord.SendLobbyCreatedRequest"); - } - - Utils.SendMessage("\n", PlayerControl.LocalPlayer.PlayerId, Translator.GetString("Message.LobbyCodeSent")); - } - } - - public static void NotifyLobbyStatusChanged(LobbyStatus status) - { - if (!Options.PostLobbyCodeToEHRDiscordServer.GetBool() || !AmongUsClient.Instance.AmHost) return; - - if (GameCode.IntToGameName(AmongUsClient.Instance.GameId) != LastRoomCode) - { - status = LobbyStatus.Closed; - StartMessageEdit(); - NotifyLobbyCreated(); - return; - } - + status = LobbyStatus.Closed; StartMessageEdit(); + NotifyLobbyCreated(); return; - - void StartMessageEdit() - { - Main.Instance.StartCoroutine(SendLobbyStatusChangedRequest(LastRoomCode, status.ToString().Replace('_', ' '))); - } } - private static IEnumerator SendLobbyStatusChangedRequest(string roomCode, string newStatus) + StartMessageEdit(); + return; + + void StartMessageEdit() { - if (string.IsNullOrEmpty(Token)) yield break; - - var timeSinceLastRequest = Utils.TimeStamp - LastRequestTimeStamp; - - if (timeSinceLastRequest < BufferTime) - { - yield return new WaitForSeconds(BufferTime - timeSinceLastRequest); - } - - LastRequestTimeStamp = Utils.TimeStamp; - - var jsonData = $"{{\"roomCode\":\"{roomCode}\",\"token\":\"{Token}\",\"newStatus\":\"{newStatus}\"}}"; - byte[] jsonToSend = new UTF8Encoding().GetBytes(jsonData); - - UnityWebRequest request = new UnityWebRequest(WebhookUrl.Replace("lobby_created", "update_status"), "POST") - { - uploadHandler = new UploadHandlerRaw(jsonToSend), - downloadHandler = new DownloadHandlerBuffer() - }; - - request.SetRequestHeader("Content-Type", "application/json"); - yield return request.SendWebRequest(); - - bool success = request.result == UnityWebRequest.Result.Success; - Logger.Msg(success ? "Lobby status changed notification sent successfully." : $"Failed to send lobby status changed notification: {request.error}", "LobbyNotifierForDiscord.SendLobbyStatusChangedRequest"); + Main.Instance.StartCoroutine(SendLobbyStatusChangedRequest(LastRoomCode, status.ToString().Replace('_', ' '))); } } - [SuppressMessage("ReSharper", "InconsistentNaming")] - public enum LobbyStatus + private static IEnumerator SendLobbyStatusChangedRequest(string roomCode, string newStatus) { - In_Lobby, - In_Game, - Ended, - Closed - } + if (string.IsNullOrEmpty(Token)) yield break; - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.ExitGame))] - static class ExitGamePatch - { - public static void Postfix() + var timeSinceLastRequest = Utils.TimeStamp - LastRequestTimeStamp; + + if (timeSinceLastRequest < BufferTime) { - LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.Closed); + yield return new WaitForSeconds(BufferTime - timeSinceLastRequest); } + + LastRequestTimeStamp = Utils.TimeStamp; + + var jsonData = $"{{\"roomCode\":\"{roomCode}\",\"token\":\"{Token}\",\"newStatus\":\"{newStatus}\"}}"; + byte[] jsonToSend = new UTF8Encoding().GetBytes(jsonData); + + UnityWebRequest request = new UnityWebRequest(WebhookUrl.Replace("lobby_created", "update_status"), "POST") + { + uploadHandler = new UploadHandlerRaw(jsonToSend), + downloadHandler = new DownloadHandlerBuffer() + }; + + request.SetRequestHeader("Content-Type", "application/json"); + yield return request.SendWebRequest(); + + bool success = request.result == UnityWebRequest.Result.Success; + Logger.Msg(success ? "Lobby status changed notification sent successfully." : $"Failed to send lobby status changed notification: {request.error}", "LobbyNotifierForDiscord.SendLobbyStatusChangedRequest"); + } +} + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public enum LobbyStatus +{ + In_Lobby, + In_Game, + Ended, + Closed +} + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.ExitGame))] +static class ExitGamePatch +{ + public static void Postfix() + { + LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.Closed); } } \ No newline at end of file diff --git a/Modules/LocateArrow.cs b/Modules/LocateArrow.cs index 1ddd521fb..8c38b1834 100644 --- a/Modules/LocateArrow.cs +++ b/Modules/LocateArrow.cs @@ -4,173 +4,172 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR +namespace EHR; + +internal static class LocateArrow { - internal static class LocateArrow + private static readonly Dictionary LocateArrows = []; + + private static readonly string[] Arrows = + [ + "↑", + "↗", + "→", + "↘", + "↓", + "↙", + "←", + "↖", + "・" + ]; + + public static void Init() { - private static readonly Dictionary LocateArrows = []; + LocateArrows.Clear(); + } - private static readonly string[] Arrows = - [ - "↑", - "↗", - "→", - "↘", - "↓", - "↙", - "←", - "↖", - "・" - ]; - - public static void Init() + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - LocateArrows.Clear(); + case 1: + Add(reader.ReadByte(), reader.ReadVector3()); + break; + case 2: + Remove(reader.ReadByte(), reader.ReadVector3()); + break; + case 3: + RemoveAllTarget(reader.ReadByte()); + break; } + } - public static void ReceiveRPC(MessageReader reader) + /// + /// Register a new target arrow object + /// + /// + /// + public static void Add(byte seer, Vector3 locate) + { + ArrowInfo arrowInfo = new(seer, locate); + + if (!LocateArrows.Any(a => a.Key.Equals(arrowInfo))) { - switch (reader.ReadPackedInt32()) + LocateArrows[arrowInfo] = "・"; + Utils.SendRPC(CustomRPC.Arrow, false, 1, seer, locate); + Logger.Info($"New locate arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {locate}", "LocateArrow"); + } + } + + /// + /// Delete target + /// + /// + /// + public static void Remove(byte seer, Vector3 locate) + { + ArrowInfo arrowInfo = new(seer, locate); + List removeList = new(LocateArrows.Keys.Where(k => k.Equals(arrowInfo))); + foreach (ArrowInfo a in removeList.ToArray()) LocateArrows.Remove(a); + + Utils.SendRPC(CustomRPC.Arrow, false, 2, seer, locate); + Logger.Info($"Removed locate arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {locate}", "LocateArrow"); + } + + /// + /// Delete all targets for the specified seer + /// + /// + public static void RemoveAllTarget(byte seer) + { + List removeList = new(LocateArrows.Keys.Where(k => k.From == seer)); + foreach (ArrowInfo arrowInfo in removeList.ToArray()) LocateArrows.Remove(arrowInfo); + + Utils.SendRPC(CustomRPC.Arrow, false, 3, seer); + Logger.Info($"Removed all locate arrows for: {seer} ({seer.GetPlayer()?.GetRealName()})", "LocateArrow"); + } + + /// + /// Get all visible target arrows + /// + /// + /// + public static string GetArrows(PlayerControl seer) + { + return LocateArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + LocateArrows[arrowInfo]); + } + + /// + /// Check target arrow every FixedUpdate + /// Issue NotifyRoles when there are updates + /// + /// + public static void OnFixedUpdate(PlayerControl seer) + { + if (!GameStates.IsInTask) return; + + bool seerIsDead = !seer.IsAlive(); + + List arrowList = new(LocateArrows.Keys.Where(a => a.From == seer.PlayerId)); + if (arrowList.Count == 0) return; + + var update = false; + + foreach (ArrowInfo arrowInfo in arrowList.ToArray()) + { + Vector3 loc = arrowInfo.To; + + if (seerIsDead) { - case 1: - Add(reader.ReadByte(), reader.ReadVector3()); - break; - case 2: - Remove(reader.ReadByte(), reader.ReadVector3()); - break; - case 3: - RemoveAllTarget(reader.ReadByte()); - break; + LocateArrows.Remove(arrowInfo); + update = true; + continue; + } + + // Take the direction vector of the target + Vector3 dir = loc - seer.transform.position; + int index; + + if (dir.magnitude < 2) + { + // Display a dot when close + index = 8; + } + else + { + // Convert to index with -22.5 to 22.5 degrees as 0 + // Bottom is 0 degrees, left side is +180, right side is -180 + // Adding 180 degrees clockwise with top being 0 degrees + // Add 45/2 to make index in 45 degree units + double angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5; + index = (int)(angle / 45) % 8; + } + + string arrow = Arrows[index]; + + if (LocateArrows[arrowInfo] != arrow) + { + LocateArrows[arrowInfo] = arrow; + update = true; } } - /// - /// Register a new target arrow object - /// - /// - /// - public static void Add(byte seer, Vector3 locate) - { - ArrowInfo arrowInfo = new(seer, locate); + if (update) Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false, SpecifyTarget: seer); + } - if (!LocateArrows.Any(a => a.Key.Equals(arrowInfo))) - { - LocateArrows[arrowInfo] = "・"; - Utils.SendRPC(CustomRPC.Arrow, false, 1, seer, locate); - Logger.Info($"New locate arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {locate}", "LocateArrow"); - } + private class ArrowInfo(byte from, Vector3 to) + { + public readonly byte From = from; + public readonly Vector3 To = to; + + public bool Equals(ArrowInfo obj) + { + return From == obj.From && To == obj.To; } - /// - /// Delete target - /// - /// - /// - public static void Remove(byte seer, Vector3 locate) + public override string ToString() { - ArrowInfo arrowInfo = new(seer, locate); - List removeList = new(LocateArrows.Keys.Where(k => k.Equals(arrowInfo))); - foreach (ArrowInfo a in removeList.ToArray()) LocateArrows.Remove(a); - - Utils.SendRPC(CustomRPC.Arrow, false, 2, seer, locate); - Logger.Info($"Removed locate arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {locate}", "LocateArrow"); - } - - /// - /// Delete all targets for the specified seer - /// - /// - public static void RemoveAllTarget(byte seer) - { - List removeList = new(LocateArrows.Keys.Where(k => k.From == seer)); - foreach (ArrowInfo arrowInfo in removeList.ToArray()) LocateArrows.Remove(arrowInfo); - - Utils.SendRPC(CustomRPC.Arrow, false, 3, seer); - Logger.Info($"Removed all locate arrows for: {seer} ({seer.GetPlayer()?.GetRealName()})", "LocateArrow"); - } - - /// - /// Get all visible target arrows - /// - /// - /// - public static string GetArrows(PlayerControl seer) - { - return LocateArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + LocateArrows[arrowInfo]); - } - - /// - /// Check target arrow every FixedUpdate - /// Issue NotifyRoles when there are updates - /// - /// - public static void OnFixedUpdate(PlayerControl seer) - { - if (!GameStates.IsInTask) return; - - bool seerIsDead = !seer.IsAlive(); - - List arrowList = new(LocateArrows.Keys.Where(a => a.From == seer.PlayerId)); - if (arrowList.Count == 0) return; - - var update = false; - - foreach (ArrowInfo arrowInfo in arrowList.ToArray()) - { - Vector3 loc = arrowInfo.To; - - if (seerIsDead) - { - LocateArrows.Remove(arrowInfo); - update = true; - continue; - } - - // Take the direction vector of the target - Vector3 dir = loc - seer.transform.position; - int index; - - if (dir.magnitude < 2) - { - // Display a dot when close - index = 8; - } - else - { - // Convert to index with -22.5 to 22.5 degrees as 0 - // Bottom is 0 degrees, left side is +180, right side is -180 - // Adding 180 degrees clockwise with top being 0 degrees - // Add 45/2 to make index in 45 degree units - double angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5; - index = (int)(angle / 45) % 8; - } - - string arrow = Arrows[index]; - - if (LocateArrows[arrowInfo] != arrow) - { - LocateArrows[arrowInfo] = arrow; - update = true; - } - } - - if (update) Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false, SpecifyTarget: seer); - } - - private class ArrowInfo(byte from, Vector3 to) - { - public readonly byte From = from; - public readonly Vector3 To = to; - - public bool Equals(ArrowInfo obj) - { - return From == obj.From && To == obj.To; - } - - public override string ToString() - { - return $"(From:{From} To:{To})"; - } + return $"(From:{From} To:{To})"; } } } \ No newline at end of file diff --git a/Modules/LogHandler/ILogHandler.cs b/Modules/LogHandler/ILogHandler.cs index b1b64953c..e262deff2 100644 --- a/Modules/LogHandler/ILogHandler.cs +++ b/Modules/LogHandler/ILogHandler.cs @@ -1,14 +1,13 @@ using System; -namespace EHR.Modules +namespace EHR.Modules; + +public interface ILogHandler { - public interface ILogHandler - { - public void Info(string text); - public void Warn(string text); - public void Error(string text); - public void Fatal(string text); - public void Msg(string text); - public void Exception(Exception ex); - } + public void Info(string text); + public void Warn(string text); + public void Error(string text); + public void Fatal(string text); + public void Msg(string text); + public void Exception(Exception ex); } \ No newline at end of file diff --git a/Modules/LogHandler/LogHandler.cs b/Modules/LogHandler/LogHandler.cs index a8e7c2728..270fb83d9 100644 --- a/Modules/LogHandler/LogHandler.cs +++ b/Modules/LogHandler/LogHandler.cs @@ -1,39 +1,38 @@ using System; -namespace EHR.Modules +namespace EHR.Modules; + +internal class LogHandler(string tag) : ILogHandler { - internal class LogHandler(string tag) : ILogHandler + public string Tag { get; } = tag; + + public void Info(string text) { - public string Tag { get; } = tag; + Logger.Info(text, Tag); + } - public void Info(string text) - { - Logger.Info(text, Tag); - } + public void Warn(string text) + { + Logger.Warn(text, Tag); + } - public void Warn(string text) - { - Logger.Warn(text, Tag); - } + public void Error(string text) + { + Logger.Error(text, Tag); + } - public void Error(string text) - { - Logger.Error(text, Tag); - } + public void Fatal(string text) + { + Logger.Fatal(text, Tag); + } - public void Fatal(string text) - { - Logger.Fatal(text, Tag); - } + public void Msg(string text) + { + Logger.Msg(text, Tag); + } - public void Msg(string text) - { - Logger.Msg(text, Tag); - } - - public void Exception(Exception ex) - { - Logger.Exception(ex, Tag); - } + public void Exception(Exception ex) + { + Logger.Exception(ex, Tag); } } \ No newline at end of file diff --git a/Modules/MeetingTimeManager.cs b/Modules/MeetingTimeManager.cs index 6ae636f71..97dc56029 100644 --- a/Modules/MeetingTimeManager.cs +++ b/Modules/MeetingTimeManager.cs @@ -4,97 +4,96 @@ using EHR.Crewmate; using EHR.Impostor; using HarmonyLib; -namespace EHR.Modules +namespace EHR.Modules; + +public static class MeetingTimeManager { - public static class MeetingTimeManager + private static int DiscussionTime; + private static int VotingTime; + private static int DefaultDiscussionTime; + private static int DefaultVotingTime; + + public static int VotingTimeLeft; + + public static void Init() { - private static int DiscussionTime; - private static int VotingTime; - private static int DefaultDiscussionTime; - private static int DefaultVotingTime; - - public static int VotingTimeLeft; - - public static void Init() - { - DefaultDiscussionTime = Main.RealOptionsData.GetInt(Int32OptionNames.DiscussionTime); - DefaultVotingTime = Main.RealOptionsData.GetInt(Int32OptionNames.VotingTime); - Logger.Info($"DefaultDiscussionTime: {DefaultDiscussionTime}s, DefaultVotingTime: {DefaultVotingTime}s", "MeetingTimeManager.Init"); - ResetMeetingTime(); - } - - public static void ApplyGameOptions(IGameOptions opt) - { - opt.SetInt(Int32OptionNames.DiscussionTime, DiscussionTime); - opt.SetInt(Int32OptionNames.VotingTime, VotingTime); - } - - private static void ResetMeetingTime() - { - DiscussionTime = DefaultDiscussionTime; - VotingTime = DefaultVotingTime; - } - - public static void OnReportDeadBody() - { - if (Options.AllAliveMeeting.GetBool() && Utils.IsAllAlive) - { - DiscussionTime = 0; - VotingTime = Options.AllAliveMeetingTime.GetInt(); - Logger.Info($"Discussion Time: {DiscussionTime}s, Voting Time: {VotingTime}s", "MeetingTimeManager.OnReportDeadBody"); - return; - } - - ResetMeetingTime(); - var BonusMeetingTime = 0; - var MeetingTimeMinTimeThief = 0; - var MeetingTimeMinTimeManager = 0; - var MeetingTimeMax = 300; - - if (TimeThief.PlayerIdList.Count > 0) - { - MeetingTimeMinTimeThief = TimeThief.LowerLimitVotingTime.GetInt(); - BonusMeetingTime += TimeThief.TotalDecreasedMeetingTime(); - } - - if (TimeManager.PlayerIdList.Count > 0) - { - MeetingTimeMinTimeManager = TimeManager.MadMinMeetingTimeLimit.GetInt(); - MeetingTimeMax = TimeManager.MeetingTimeLimit.GetInt(); - BonusMeetingTime += TimeManager.TotalIncreasedMeetingTime(); - } - - int TotalMeetingTime = DiscussionTime + VotingTime; - - if (TimeManager.PlayerIdList.Count > 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeManager, MeetingTimeMax) - TotalMeetingTime; - - if (TimeThief.PlayerIdList.Count > 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeThief, MeetingTimeMax) - TotalMeetingTime; - - if (TimeManager.PlayerIdList.Count == 0 && TimeThief.PlayerIdList.Count == 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeThief, MeetingTimeMax) - TotalMeetingTime; - - if (BonusMeetingTime >= 0) - VotingTime += BonusMeetingTime; - else - { - DiscussionTime += BonusMeetingTime; - - if (DiscussionTime < 0) - { - VotingTime += DiscussionTime; - DiscussionTime = 0; - } - } - - Logger.Info($"Discussion Time: {DiscussionTime}s, Voting Time: {VotingTime}s", "MeetingTimeManager.OnReportDeadBody"); - } + DefaultDiscussionTime = Main.RealOptionsData.GetInt(Int32OptionNames.DiscussionTime); + DefaultVotingTime = Main.RealOptionsData.GetInt(Int32OptionNames.VotingTime); + Logger.Info($"DefaultDiscussionTime: {DefaultDiscussionTime}s, DefaultVotingTime: {DefaultVotingTime}s", "MeetingTimeManager.Init"); + ResetMeetingTime(); } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.UpdateTimerText))] - internal static class MeetingHudUpdateTimerTextPatch + public static void ApplyGameOptions(IGameOptions opt) { - public static void Postfix([HarmonyArgument(1)] int value) + opt.SetInt(Int32OptionNames.DiscussionTime, DiscussionTime); + opt.SetInt(Int32OptionNames.VotingTime, VotingTime); + } + + private static void ResetMeetingTime() + { + DiscussionTime = DefaultDiscussionTime; + VotingTime = DefaultVotingTime; + } + + public static void OnReportDeadBody() + { + if (Options.AllAliveMeeting.GetBool() && Utils.IsAllAlive) { - MeetingTimeManager.VotingTimeLeft = value; + DiscussionTime = 0; + VotingTime = Options.AllAliveMeetingTime.GetInt(); + Logger.Info($"Discussion Time: {DiscussionTime}s, Voting Time: {VotingTime}s", "MeetingTimeManager.OnReportDeadBody"); + return; } + + ResetMeetingTime(); + var BonusMeetingTime = 0; + var MeetingTimeMinTimeThief = 0; + var MeetingTimeMinTimeManager = 0; + var MeetingTimeMax = 300; + + if (TimeThief.PlayerIdList.Count > 0) + { + MeetingTimeMinTimeThief = TimeThief.LowerLimitVotingTime.GetInt(); + BonusMeetingTime += TimeThief.TotalDecreasedMeetingTime(); + } + + if (TimeManager.PlayerIdList.Count > 0) + { + MeetingTimeMinTimeManager = TimeManager.MadMinMeetingTimeLimit.GetInt(); + MeetingTimeMax = TimeManager.MeetingTimeLimit.GetInt(); + BonusMeetingTime += TimeManager.TotalIncreasedMeetingTime(); + } + + int TotalMeetingTime = DiscussionTime + VotingTime; + + if (TimeManager.PlayerIdList.Count > 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeManager, MeetingTimeMax) - TotalMeetingTime; + + if (TimeThief.PlayerIdList.Count > 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeThief, MeetingTimeMax) - TotalMeetingTime; + + if (TimeManager.PlayerIdList.Count == 0 && TimeThief.PlayerIdList.Count == 0) BonusMeetingTime = Math.Clamp(TotalMeetingTime + BonusMeetingTime, MeetingTimeMinTimeThief, MeetingTimeMax) - TotalMeetingTime; + + if (BonusMeetingTime >= 0) + VotingTime += BonusMeetingTime; + else + { + DiscussionTime += BonusMeetingTime; + + if (DiscussionTime < 0) + { + VotingTime += DiscussionTime; + DiscussionTime = 0; + } + } + + Logger.Info($"Discussion Time: {DiscussionTime}s, Voting Time: {VotingTime}s", "MeetingTimeManager.OnReportDeadBody"); + } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.UpdateTimerText))] +internal static class MeetingHudUpdateTimerTextPatch +{ + public static void Postfix([HarmonyArgument(1)] int value) + { + MeetingTimeManager.VotingTimeLeft = value; } } \ No newline at end of file diff --git a/Modules/MessageManager/ICustomWriter.cs b/Modules/MessageManager/ICustomWriter.cs index d7d512f0e..9ac7feb05 100644 --- a/Modules/MessageManager/ICustomWriter.cs +++ b/Modules/MessageManager/ICustomWriter.cs @@ -1,24 +1,23 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; -namespace EHR +namespace EHR; + +public interface ICustomWriter { - public interface ICustomWriter - { - public void Write(float val); - public void Write(string val); - public void Write(ulong val); - public void Write(int val); - public void Write(uint val); - public void Write(ushort val); - public void Write(byte val); - public void Write(sbyte val); - public void Write(bool val); - public void Write(Il2CppStructArray bytes); - public void Write(Il2CppStructArray bytes, int offset, int length); - public void WriteBytesAndSize(Il2CppStructArray bytes); - public void WritePacked(int val); - public void WritePacked(uint val); - public void WriteNetObject(InnerNetObject obj); - } + public void Write(float val); + public void Write(string val); + public void Write(ulong val); + public void Write(int val); + public void Write(uint val); + public void Write(ushort val); + public void Write(byte val); + public void Write(sbyte val); + public void Write(bool val); + public void Write(Il2CppStructArray bytes); + public void Write(Il2CppStructArray bytes, int offset, int length); + public void WriteBytesAndSize(Il2CppStructArray bytes); + public void WritePacked(int val); + public void WritePacked(uint val); + public void WriteNetObject(InnerNetObject obj); } \ No newline at end of file diff --git a/Modules/MessageManager/WriterProxy.cs b/Modules/MessageManager/WriterProxy.cs index 018bc571c..6180f6031 100644 --- a/Modules/MessageManager/WriterProxy.cs +++ b/Modules/MessageManager/WriterProxy.cs @@ -2,85 +2,84 @@ using Hazel; using Il2CppInterop.Runtime.InteropTypes.Arrays; using InnerNet; -namespace EHR +namespace EHR; + +public class WriterProxy(MessageWriter writer) : ICustomWriter { - public class WriterProxy(MessageWriter writer) : ICustomWriter + public MessageWriter Writer { get; } = writer; + + public void Write(float val) { - public MessageWriter Writer { get; } = writer; + Writer.Write(val); + } - public void Write(float val) - { - Writer.Write(val); - } + public void Write(string val) + { + Writer.Write(val); + } - public void Write(string val) - { - Writer.Write(val); - } + public void Write(ulong val) + { + Writer.Write(val); + } - public void Write(ulong val) - { - Writer.Write(val); - } + public void Write(int val) + { + Writer.Write(val); + } - public void Write(int val) - { - Writer.Write(val); - } + public void Write(uint val) + { + Writer.Write(val); + } - public void Write(uint val) - { - Writer.Write(val); - } + public void Write(ushort val) + { + Writer.Write(val); + } - public void Write(ushort val) - { - Writer.Write(val); - } + public void Write(byte val) + { + Writer.Write(val); + } - public void Write(byte val) - { - Writer.Write(val); - } + public void Write(sbyte val) + { + Writer.Write(val); + } - public void Write(sbyte val) - { - Writer.Write(val); - } + public void Write(bool val) + { + Writer.Write(val); + } - public void Write(bool val) - { - Writer.Write(val); - } + public void Write(Il2CppStructArray bytes) + { + Writer.Write(bytes); + } - public void Write(Il2CppStructArray bytes) - { - Writer.Write(bytes); - } + public void Write(Il2CppStructArray bytes, int offset, int length) + { + Writer.Write(bytes, offset, length); + } - public void Write(Il2CppStructArray bytes, int offset, int length) - { - Writer.Write(bytes, offset, length); - } + public void WriteBytesAndSize(Il2CppStructArray bytes) + { + Writer.WriteBytesAndSize(bytes); + } - public void WriteBytesAndSize(Il2CppStructArray bytes) - { - Writer.WriteBytesAndSize(bytes); - } + public void WritePacked(int val) + { + Writer.WritePacked(val); + } - public void WritePacked(int val) - { - Writer.WritePacked(val); - } + public void WritePacked(uint val) + { + Writer.WritePacked(val); + } - public void WritePacked(uint val) - { - Writer.WritePacked(val); - } - - public void WriteNetObject(InnerNetObject obj) - { - Writer.WriteNetObject(obj); - } + public void WriteNetObject(InnerNetObject obj) + { + Writer.WriteNetObject(obj); } } \ No newline at end of file diff --git a/Modules/ModUpdater.cs b/Modules/ModUpdater.cs index 9bf5f3b82..7f88d27a6 100644 --- a/Modules/ModUpdater.cs +++ b/Modules/ModUpdater.cs @@ -11,294 +11,293 @@ using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch] +public static class ModUpdater { - [HarmonyPatch] - public static class ModUpdater + private const string URLGithub = "https://api.github.com/repos/Gurge44/EndlessHostRoles"; + public const bool ForceUpdate = false; + public static bool HasUpdate; + private static bool HasOutdate; + public static bool IsBroken; + private static bool IsChecked; + private static Version LatestVersion; + private static string LatestTitle; + public static string DownloadUrl; + private static GenericPopup InfoPopup; + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPrefix] + [HarmonyPriority(2)] + public static void Start_Prefix() { - private const string URLGithub = "https://api.github.com/repos/Gurge44/EndlessHostRoles"; - public const bool ForceUpdate = false; - public static bool HasUpdate; - private static bool HasOutdate; - public static bool IsBroken; - private static bool IsChecked; - private static Version LatestVersion; - private static string LatestTitle; - public static string DownloadUrl; - private static GenericPopup InfoPopup; + NewVersionCheck(); + DeleteOldFiles(); + InfoPopup = Object.Instantiate(TwitchManager.Instance.TwitchPopup); + InfoPopup.name = "InfoPopup"; + InfoPopup.TextAreaTMP.GetComponent().sizeDelta = new(2.5f, 2f); - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - [HarmonyPrefix] - [HarmonyPriority(2)] - public static void Start_Prefix() + if (!IsChecked) { - NewVersionCheck(); - DeleteOldFiles(); - InfoPopup = Object.Instantiate(TwitchManager.Instance.TwitchPopup); - InfoPopup.name = "InfoPopup"; - InfoPopup.TextAreaTMP.GetComponent().sizeDelta = new(2.5f, 2f); + bool done = CheckReleaseFromGithub(Main.BetaBuildUrl.Value != "").GetAwaiter().GetResult(); + Logger.Warn("done: " + done, "CheckRelease"); + Logger.Info("hasupdate: " + HasUpdate, "CheckRelease"); + Logger.Info("forceupdate: " + ForceUpdate, "CheckRelease"); + Logger.Info("downloadUrl: " + DownloadUrl, "CheckRelease"); + Logger.Info("latestVersionl: " + LatestVersion, "CheckRelease"); + } + } - if (!IsChecked) - { - bool done = CheckReleaseFromGithub(Main.BetaBuildUrl.Value != "").GetAwaiter().GetResult(); - Logger.Warn("done: " + done, "CheckRelease"); - Logger.Info("hasupdate: " + HasUpdate, "CheckRelease"); - Logger.Info("forceupdate: " + ForceUpdate, "CheckRelease"); - Logger.Info("downloadUrl: " + DownloadUrl, "CheckRelease"); - Logger.Info("latestVersionl: " + LatestVersion, "CheckRelease"); - } + public static string Get(string url) + { + string result; + HttpClient req = new(); + HttpResponseMessage res = req.GetAsync(url).Result; + Stream stream = res.Content.ReadAsStreamAsync().Result; + + try + { + using StreamReader reader = new(stream); + result = reader.ReadToEnd(); + } + finally + { + stream.Close(); } - public static string Get(string url) + return result; + } + + public static async Task CheckReleaseFromGithub(bool beta = false) + { + Logger.Warn("Checking GitHub Release", "CheckRelease"); + const string url = URLGithub + "/releases/latest"; + + try { string result; - HttpClient req = new(); - HttpResponseMessage res = req.GetAsync(url).Result; - Stream stream = res.Content.ReadAsStreamAsync().Result; - try + using (HttpClient client = new()) { - using StreamReader reader = new(stream); - result = reader.ReadToEnd(); - } - finally - { - stream.Close(); - } + client.DefaultRequestHeaders.Add("User-Agent", "EHR Updater"); + using HttpResponseMessage response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseContentRead); - return result; - } - - public static async Task CheckReleaseFromGithub(bool beta = false) - { - Logger.Warn("Checking GitHub Release", "CheckRelease"); - const string url = URLGithub + "/releases/latest"; - - try - { - string result; - - using (HttpClient client = new()) + if (!response.IsSuccessStatusCode) { - client.DefaultRequestHeaders.Add("User-Agent", "EHR Updater"); - using HttpResponseMessage response = await client.GetAsync(new Uri(url), HttpCompletionOption.ResponseContentRead); - - if (!response.IsSuccessStatusCode) - { - Logger.Error($"Response Status Code: {response.StatusCode}", "CheckRelease"); - return false; - } - - result = await response.Content.ReadAsStringAsync(); - } - - JObject data = JObject.Parse(result); - - if (beta) - { - LatestTitle = data["name"].ToString(); - DownloadUrl = data["url"].ToString(); - HasUpdate = LatestTitle != ThisAssembly.Git.Commit; - } - else - { - LatestVersion = new(data["tag_name"]?.ToString().TrimStart('v') ?? string.Empty); - LatestTitle = $"Ver. {LatestVersion}"; - var assets = data["assets"].Cast(); - - for (var i = 0; i < assets.Count; i++) - { - if (assets[i]["name"].ToString() == $"EHR.v{LatestVersion}.zip") - { - DownloadUrl = assets[i]["browser_download_url"].ToString(); - break; - } - } - - HasUpdate = LatestVersion.CompareTo(Main.Version) > 0; - HasOutdate = LatestVersion.CompareTo(Main.Version) < 0; - } - - Logger.Info("hasupdate: " + HasUpdate, "GitHub"); - Logger.Info("hasoutdate: " + HasOutdate, "GitHub"); - Logger.Info("forceupdate: " + ForceUpdate, "GitHub"); - Logger.Info("downloadUrl: " + DownloadUrl, "GitHub"); - Logger.Info("latestVersionl: " + LatestVersion, "GitHub"); - Logger.Info("latestTitle: " + LatestTitle, "GitHub"); - - if (string.IsNullOrEmpty(DownloadUrl)) - { - Logger.Error("No Download URL", "CheckRelease"); + Logger.Error($"Response Status Code: {response.StatusCode}", "CheckRelease"); return false; } - IsChecked = true; - IsBroken = false; - } - catch (Exception ex) - { - IsBroken = true; - Logger.Error($"Error while checking release from GitHub:\n{ex}", "CheckRelease", false); - return false; + result = await response.Content.ReadAsStringAsync(); } - return true; - } + JObject data = JObject.Parse(result); - public static void StartUpdate(string url, bool github) - { - ShowPopup(GetString("updatePleaseWait"), StringNames.Cancel, true, false); - _ = !github ? DownloadDLL(url) : DownloadDLLGithub(url); - } - - public static bool NewVersionCheck() - { - try + if (beta) { - if (Directory.Exists("TOH_DATA") && File.Exists("./EHR_DATA/BanWords.txt")) + LatestTitle = data["name"].ToString(); + DownloadUrl = data["url"].ToString(); + HasUpdate = LatestTitle != ThisAssembly.Git.Commit; + } + else + { + LatestVersion = new(data["tag_name"]?.ToString().TrimStart('v') ?? string.Empty); + LatestTitle = $"Ver. {LatestVersion}"; + var assets = data["assets"].Cast(); + + for (var i = 0; i < assets.Count; i++) { - DirectoryInfo di = new("TOH_DATA"); - di.Delete(true); - Logger.Warn("Directory deleted:TOH_DATA", "NewVersionCheck"); - } - } - catch (Exception ex) - { - Logger.Exception(ex, "NewVersionCheck"); - return false; - } - - return true; - } - - public static void DeleteOldFiles() - { - string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - const string searchPattern = "EHR.dll*"; - - if (path != null) - { - string[] files = Directory.GetFiles(path, searchPattern); - - try - { - foreach (string filePath in files) + if (assets[i]["name"].ToString() == $"EHR.v{LatestVersion}.zip") { - if (Path.GetFileName(filePath).EndsWith(".bak") || Path.GetFileName(filePath).EndsWith(".temp")) - { - Logger.Info($"{filePath} will be deleted", "DeleteOldFiles"); - File.Delete(filePath); - } + DownloadUrl = assets[i]["browser_download_url"].ToString(); + break; } } - catch (Exception e) - { - Logger.Error($"Failed to clear update residue\n{e}", "DeleteOldFiles"); - } + + HasUpdate = LatestVersion.CompareTo(Main.Version) > 0; + HasOutdate = LatestVersion.CompareTo(Main.Version) < 0; } - } - public static async Task DownloadDLL(string url) - { - try + Logger.Info("hasupdate: " + HasUpdate, "GitHub"); + Logger.Info("hasoutdate: " + HasOutdate, "GitHub"); + Logger.Info("forceupdate: " + ForceUpdate, "GitHub"); + Logger.Info("downloadUrl: " + DownloadUrl, "GitHub"); + Logger.Info("latestVersionl: " + LatestVersion, "GitHub"); + Logger.Info("latestTitle: " + LatestTitle, "GitHub"); + + if (string.IsNullOrEmpty(DownloadUrl)) { - const string savePath = "BepInEx/plugins/EHR.dll.temp"; - - // Delete the temporary file if it exists - if (File.Exists(savePath)) File.Delete(savePath); - - HttpResponseMessage response; - - using (HttpClient client = new()) response = await client.GetAsync(url); - - if (response is not { IsSuccessStatusCode: true }) throw new($"File retrieval failed with status code: {response.StatusCode}"); - - await using (Stream stream = await response.Content.ReadAsStreamAsync()) - await using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) - { - var buffer = new byte[1024]; - int length; - - while ((length = await stream.ReadAsync(buffer)) != 0) await fileStream.WriteAsync(buffer.AsMemory(0, length)); - } - - string fileName = Assembly.GetExecutingAssembly().Location; - File.Move(fileName, fileName + ".bak"); - File.Move(savePath, fileName); - ShowPopup(GetString("updateRestart"), StringNames.Close, true); - } - catch (Exception ex) - { - Logger.Error($"Update failed\n{ex}", "DownloadDLL", false); - ShowPopup(GetString("updateManually"), StringNames.Close, true); + Logger.Error("No Download URL", "CheckRelease"); return false; } - return true; + IsChecked = true; + IsBroken = false; + } + catch (Exception ex) + { + IsBroken = true; + Logger.Error($"Error while checking release from GitHub:\n{ex}", "CheckRelease", false); + return false; } - public static async Task DownloadDLLGithub(string url) + return true; + } + + public static void StartUpdate(string url, bool github) + { + ShowPopup(GetString("updatePleaseWait"), StringNames.Cancel, true, false); + _ = !github ? DownloadDLL(url) : DownloadDLLGithub(url); + } + + public static bool NewVersionCheck() + { + try { + if (Directory.Exists("TOH_DATA") && File.Exists("./EHR_DATA/BanWords.txt")) + { + DirectoryInfo di = new("TOH_DATA"); + di.Delete(true); + Logger.Warn("Directory deleted:TOH_DATA", "NewVersionCheck"); + } + } + catch (Exception ex) + { + Logger.Exception(ex, "NewVersionCheck"); + return false; + } + + return true; + } + + public static void DeleteOldFiles() + { + string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + const string searchPattern = "EHR.dll*"; + + if (path != null) + { + string[] files = Directory.GetFiles(path, searchPattern); + try { - const string savePath = "BepInEx/plugins/EHR.dll.temp"; - - // Delete the temporary file if it exists - if (File.Exists(savePath)) File.Delete(savePath); - - HttpResponseMessage response; - - using (HttpClient client = new()) response = await client.GetAsync(url); - - if (response is not { IsSuccessStatusCode: true }) throw new($"File retrieval failed with status code: {response.StatusCode}"); - - await using (Stream stream = await response.Content.ReadAsStreamAsync()) - using (var archive = new ZipArchive(stream, ZipArchiveMode.Read)) + foreach (string filePath in files) { - // Specify the relative path within the ZIP archive where "EHR.dll" is located - const string entryPath = "BepInEx/plugins/EHR.dll"; - ZipArchiveEntry entry = archive.GetEntry(entryPath) ?? throw new($"'{entryPath}' not found in the ZIP archive"); - - // Extract "EHR.dll" to the temporary file - await using Stream entryStream = entry.Open(); - await using var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); - await entryStream.CopyToAsync(fileStream); + if (Path.GetFileName(filePath).EndsWith(".bak") || Path.GetFileName(filePath).EndsWith(".temp")) + { + Logger.Info($"{filePath} will be deleted", "DeleteOldFiles"); + File.Delete(filePath); + } } - - string fileName = Assembly.GetExecutingAssembly().Location; - File.Move(fileName, fileName + ".bak"); - File.Move(savePath, fileName); - ShowPopup(GetString("updateRestart"), StringNames.Close, true); } - catch (Exception ex) + catch (Exception e) { - Logger.Error($"Update failed\n{ex}", "DownloadDLL", false); - ShowPopup(GetString("updateManually"), StringNames.Close, true); - return false; - } - - return true; - } - - public static void ShowPopup(string message, StringNames buttonText, bool showButton = false, bool buttonIsExit = true) - { - if (InfoPopup == null) return; - - InfoPopup.Show(message); - Transform button = InfoPopup.transform.FindChild("ExitGame"); - - if (button != null) - { - button.gameObject.SetActive(showButton); - button.GetChild(0).GetComponent().TargetText = buttonText; - button.GetChild(0).GetComponent().ResetText(); - button.GetComponent().OnClick = new(); - - if (buttonIsExit) - button.GetComponent().OnClick.AddListener((Action)Application.Quit); - else - button.GetComponent().OnClick.AddListener((Action)(() => InfoPopup.Close())); + Logger.Error($"Failed to clear update residue\n{e}", "DeleteOldFiles"); } } } + + public static async Task DownloadDLL(string url) + { + try + { + const string savePath = "BepInEx/plugins/EHR.dll.temp"; + + // Delete the temporary file if it exists + if (File.Exists(savePath)) File.Delete(savePath); + + HttpResponseMessage response; + + using (HttpClient client = new()) response = await client.GetAsync(url); + + if (response is not { IsSuccessStatusCode: true }) throw new($"File retrieval failed with status code: {response.StatusCode}"); + + await using (Stream stream = await response.Content.ReadAsStreamAsync()) + await using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true)) + { + var buffer = new byte[1024]; + int length; + + while ((length = await stream.ReadAsync(buffer)) != 0) await fileStream.WriteAsync(buffer.AsMemory(0, length)); + } + + string fileName = Assembly.GetExecutingAssembly().Location; + File.Move(fileName, fileName + ".bak"); + File.Move(savePath, fileName); + ShowPopup(GetString("updateRestart"), StringNames.Close, true); + } + catch (Exception ex) + { + Logger.Error($"Update failed\n{ex}", "DownloadDLL", false); + ShowPopup(GetString("updateManually"), StringNames.Close, true); + return false; + } + + return true; + } + + public static async Task DownloadDLLGithub(string url) + { + try + { + const string savePath = "BepInEx/plugins/EHR.dll.temp"; + + // Delete the temporary file if it exists + if (File.Exists(savePath)) File.Delete(savePath); + + HttpResponseMessage response; + + using (HttpClient client = new()) response = await client.GetAsync(url); + + if (response is not { IsSuccessStatusCode: true }) throw new($"File retrieval failed with status code: {response.StatusCode}"); + + await using (Stream stream = await response.Content.ReadAsStreamAsync()) + using (var archive = new ZipArchive(stream, ZipArchiveMode.Read)) + { + // Specify the relative path within the ZIP archive where "EHR.dll" is located + const string entryPath = "BepInEx/plugins/EHR.dll"; + ZipArchiveEntry entry = archive.GetEntry(entryPath) ?? throw new($"'{entryPath}' not found in the ZIP archive"); + + // Extract "EHR.dll" to the temporary file + await using Stream entryStream = entry.Open(); + await using var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, 4096, true); + await entryStream.CopyToAsync(fileStream); + } + + string fileName = Assembly.GetExecutingAssembly().Location; + File.Move(fileName, fileName + ".bak"); + File.Move(savePath, fileName); + ShowPopup(GetString("updateRestart"), StringNames.Close, true); + } + catch (Exception ex) + { + Logger.Error($"Update failed\n{ex}", "DownloadDLL", false); + ShowPopup(GetString("updateManually"), StringNames.Close, true); + return false; + } + + return true; + } + + public static void ShowPopup(string message, StringNames buttonText, bool showButton = false, bool buttonIsExit = true) + { + if (InfoPopup == null) return; + + InfoPopup.Show(message); + Transform button = InfoPopup.transform.FindChild("ExitGame"); + + if (button != null) + { + button.gameObject.SetActive(showButton); + button.GetChild(0).GetComponent().TargetText = buttonText; + button.GetChild(0).GetComponent().ResetText(); + button.GetComponent().OnClick = new(); + + if (buttonIsExit) + button.GetComponent().OnClick.AddListener((Action)Application.Quit); + else + button.GetComponent().OnClick.AddListener((Action)(() => InfoPopup.Close())); + } + } } \ No newline at end of file diff --git a/Modules/NameColorManager.cs b/Modules/NameColorManager.cs index 0eb45c355..7842a63d0 100644 --- a/Modules/NameColorManager.cs +++ b/Modules/NameColorManager.cs @@ -7,325 +7,324 @@ using EHR.Modules; using EHR.Neutral; using Hazel; -namespace EHR +namespace EHR; + +public static class NameColorManager { - public static class NameColorManager + public static string ApplyNameColorData(this string name, PlayerControl seer, PlayerControl target, bool isMeeting) { - public static string ApplyNameColorData(this string name, PlayerControl seer, PlayerControl target, bool isMeeting) + if (!AmongUsClient.Instance.IsGameStarted) return name; + + if (!TryGetData(seer, target, out string colorCode)) + if (KnowTargetRoleColor(seer, target, isMeeting, out string color)) + colorCode = color == "" ? target.GetRoleColorCode() : color; + + string openTag = "", closeTag = ""; + + if (colorCode != "") { - if (!AmongUsClient.Instance.IsGameStarted) return name; + if (!colorCode.StartsWith('#')) colorCode = "#" + colorCode; - if (!TryGetData(seer, target, out string colorCode)) - if (KnowTargetRoleColor(seer, target, isMeeting, out string color)) - colorCode = color == "" ? target.GetRoleColorCode() : color; - - string openTag = "", closeTag = ""; - - if (colorCode != "") - { - if (!colorCode.StartsWith('#')) colorCode = "#" + colorCode; - - openTag = $"<{colorCode}>"; - closeTag = ""; - } - - return openTag + name + closeTag; + openTag = $"<{colorCode}>"; + closeTag = ""; } - private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target, bool isMeeting, out string color) + return openTag + name + closeTag; + } + + private static bool KnowTargetRoleColor(PlayerControl seer, PlayerControl target, bool isMeeting, out string color) + { + color = ""; + + switch (Options.CurrentGameMode) { - color = ""; + case CustomGameMode.FFA when FFAManager.FFATeamMode.GetBool(): + color = FFAManager.TeamColors[FFAManager.PlayerTeams[target.PlayerId]]; + return true; + case CustomGameMode.RoomRush: + case CustomGameMode.NaturalDisasters: + case CustomGameMode.MoveAndStop: + color = "#ffffff"; + return true; + case CustomGameMode.AllInOne: + case CustomGameMode.HotPotato: + (byte HolderID, byte LastHolderID) = HotPotato.GetState(); - switch (Options.CurrentGameMode) - { - case CustomGameMode.FFA when FFAManager.FFATeamMode.GetBool(): - color = FFAManager.TeamColors[FFAManager.PlayerTeams[target.PlayerId]]; - return true; - case CustomGameMode.RoomRush: - case CustomGameMode.NaturalDisasters: - case CustomGameMode.MoveAndStop: - color = "#ffffff"; - return true; - case CustomGameMode.AllInOne: - case CustomGameMode.HotPotato: - (byte HolderID, byte LastHolderID) = HotPotato.GetState(); - - if (target.PlayerId == HolderID) - color = "#000000"; - else if (target.PlayerId == LastHolderID) - color = "#00ffff"; - else if (Options.CurrentGameMode == CustomGameMode.AllInOne && SpeedrunManager.CanKill.Contains(target.PlayerId)) - color = Main.ImpostorColor; - else - color = "#ffffff"; - - return true; - case CustomGameMode.HideAndSeek: - return HnSManager.KnowTargetRoleColor(seer, target, ref color); - case CustomGameMode.Speedrun when SpeedrunManager.CanKill.Contains(target.PlayerId): + if (target.PlayerId == HolderID) + color = "#000000"; + else if (target.PlayerId == LastHolderID) + color = "#00ffff"; + else if (Options.CurrentGameMode == CustomGameMode.AllInOne && SpeedrunManager.CanKill.Contains(target.PlayerId)) color = Main.ImpostorColor; - return true; - case CustomGameMode.CaptureTheFlag: - return CTFManager.KnowTargetRoleColor(target, ref color); - } + else + color = "#ffffff"; - RoleBase seerRoleClass = Main.PlayerStates[seer.PlayerId].Role; - RoleBase targetRoleClass = Main.PlayerStates[target.PlayerId].Role; + return true; + case CustomGameMode.HideAndSeek: + return HnSManager.KnowTargetRoleColor(seer, target, ref color); + case CustomGameMode.Speedrun when SpeedrunManager.CanKill.Contains(target.PlayerId): + color = Main.ImpostorColor; + return true; + case CustomGameMode.CaptureTheFlag: + return CTFManager.KnowTargetRoleColor(target, ref color); + } - // Global (low priority) - if (Stained.VioletNameList.Contains(target.PlayerId)) color = "#ff00ff"; + RoleBase seerRoleClass = Main.PlayerStates[seer.PlayerId].Role; + RoleBase targetRoleClass = Main.PlayerStates[target.PlayerId].Role; - // Impostors and Madmates - if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor)) color = target.Is(CustomRoles.Egoist) && Options.ImpEgoistVisibalToAllies.GetBool() && seer != target ? Main.RoleColors[CustomRoles.Egoist] : Main.ImpostorColor; - if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.DoubleAgent)) color = Main.ImpostorColor; - if (seer.IsMadmate() && target.Is(CustomRoleTypes.Impostor) && Options.MadmateKnowWhosImp.GetBool()) color = Main.ImpostorColor; - if (seer.Is(CustomRoleTypes.Impostor) && target.IsMadmate() && Options.ImpKnowWhosMadmate.GetBool()) color = Main.RoleColors[CustomRoles.Madmate]; - if (seer.IsMadmate() && target.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) color = Main.RoleColors[CustomRoles.Madmate]; - if (Blackmailer.On && seerRoleClass is Blackmailer { IsEnable: true } bm && bm.BlackmailedPlayerIds.Contains(target.PlayerId)) color = Main.RoleColors[CustomRoles.BloodKnight]; + // Global (low priority) + if (Stained.VioletNameList.Contains(target.PlayerId)) color = "#ff00ff"; - if (Commander.On && seer.Is(Team.Impostor)) + // Impostors and Madmates + if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor)) color = target.Is(CustomRoles.Egoist) && Options.ImpEgoistVisibalToAllies.GetBool() && seer != target ? Main.RoleColors[CustomRoles.Egoist] : Main.ImpostorColor; + if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.DoubleAgent)) color = Main.ImpostorColor; + if (seer.IsMadmate() && target.Is(CustomRoleTypes.Impostor) && Options.MadmateKnowWhosImp.GetBool()) color = Main.ImpostorColor; + if (seer.Is(CustomRoleTypes.Impostor) && target.IsMadmate() && Options.ImpKnowWhosMadmate.GetBool()) color = Main.RoleColors[CustomRoles.Madmate]; + if (seer.IsMadmate() && target.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) color = Main.RoleColors[CustomRoles.Madmate]; + if (Blackmailer.On && seerRoleClass is Blackmailer { IsEnable: true } bm && bm.BlackmailedPlayerIds.Contains(target.PlayerId)) color = Main.RoleColors[CustomRoles.BloodKnight]; + + if (Commander.On && seer.Is(Team.Impostor)) + { + if (Commander.PlayerList.Any(x => x.MarkedPlayer == target.PlayerId)) color = Main.RoleColors[CustomRoles.Sprayer]; + if (Commander.PlayerList.Any(x => x.DontKillMarks.Contains(target.PlayerId))) color = "#0daeff"; + } + + // Custom Teams + if (CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.KnowRoles)) color = Main.RoleColors[target.GetCustomRole()]; + + // Add-ons + if (target.Is(CustomRoles.Glow) && Utils.IsActive(SystemTypes.Electrical)) color = Main.RoleColors[CustomRoles.Glow]; + if (target.Is(CustomRoles.Mare) && Utils.IsActive(SystemTypes.Electrical) && !isMeeting) color = Main.RoleColors[CustomRoles.Mare]; + if (seer.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Contagious) && Virus.TargetKnowOtherTarget.GetBool()) color = Main.RoleColors[CustomRoles.Virus]; + if (seer.Is(CustomRoles.Charmed) && target.Is(CustomRoles.Charmed) && Succubus.TargetKnowOtherTarget.GetBool()) color = Main.RoleColors[CustomRoles.Charmed]; + if (seer.Is(CustomRoles.Undead) && target.Is(CustomRoles.Undead)) color = Main.RoleColors[CustomRoles.Undead]; + + // Ghost roles + if (GhostRolesManager.AssignedGhostRoles.TryGetValue(target.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole)) + { + if (seer.GetTeam() == ghostRole.Instance.Team) { - if (Commander.PlayerList.Any(x => x.MarkedPlayer == target.PlayerId)) color = Main.RoleColors[CustomRoles.Sprayer]; - if (Commander.PlayerList.Any(x => x.DontKillMarks.Contains(target.PlayerId))) color = "#0daeff"; - } - - // Custom Teams - if (CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.KnowRoles)) color = Main.RoleColors[target.GetCustomRole()]; - - // Add-ons - if (target.Is(CustomRoles.Glow) && Utils.IsActive(SystemTypes.Electrical)) color = Main.RoleColors[CustomRoles.Glow]; - if (target.Is(CustomRoles.Mare) && Utils.IsActive(SystemTypes.Electrical) && !isMeeting) color = Main.RoleColors[CustomRoles.Mare]; - if (seer.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Contagious) && Virus.TargetKnowOtherTarget.GetBool()) color = Main.RoleColors[CustomRoles.Virus]; - if (seer.Is(CustomRoles.Charmed) && target.Is(CustomRoles.Charmed) && Succubus.TargetKnowOtherTarget.GetBool()) color = Main.RoleColors[CustomRoles.Charmed]; - if (seer.Is(CustomRoles.Undead) && target.Is(CustomRoles.Undead)) color = Main.RoleColors[CustomRoles.Undead]; - - // Ghost roles - if (GhostRolesManager.AssignedGhostRoles.TryGetValue(target.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole)) - { - if (seer.GetTeam() == ghostRole.Instance.Team) - { - color = ghostRole.Instance.Team switch - { - Team.Impostor => Main.ImpostorColor, - Team.Crewmate => Main.CrewmateColor, - Team.Neutral => Main.NeutralColor, - _ => color - }; - } - } - - if (isMeeting && Haunter.AllHauntedPlayers.Contains(target.PlayerId)) color = Main.ImpostorColor; - - CustomRoles seerRole = seer.GetCustomRole(); - CustomRoles targetRole = target.GetCustomRole(); - - // If 2 players have the same role and that role is a NK role, they can see each other's name color - if (seerRole.IsNK() && seerRole == targetRole) color = Main.RoleColors[seerRole]; - - // Specific seer-target role combinations (excluding NK roles) - color = (seerRole, targetRole) switch - { - (CustomRoles.Necromancer, CustomRoles.Deathknight) => Main.RoleColors[CustomRoles.Deathknight], - (CustomRoles.Deathknight, CustomRoles.Necromancer) => Main.RoleColors[CustomRoles.Necromancer], - (CustomRoles.Deathknight, CustomRoles.Deathknight) => Main.RoleColors[CustomRoles.Deathknight], - _ => color - }; - - // Check if the seer can see the target's role color - color = seerRole switch - { - CustomRoles.Executioner when Executioner.Target.TryGetValue(seer.PlayerId, out byte exeTarget) && exeTarget == target.PlayerId => "000000", - CustomRoles.Gangster when target.Is(CustomRoles.Madmate) => Main.RoleColors[CustomRoles.Madmate], - CustomRoles.Crewpostor when target.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool() => Main.ImpostorColor, - CustomRoles.Succubus when target.Is(CustomRoles.Charmed) => Main.RoleColors[CustomRoles.Charmed], - CustomRoles.Necromancer or CustomRoles.Deathknight when target.Is(CustomRoles.Undead) => Main.RoleColors[CustomRoles.Undead], - CustomRoles.Necromancer or CustomRoles.Deathknight when Necromancer.PartiallyRecruitedIds.Contains(target.PlayerId) => Main.RoleColors[CustomRoles.Deathknight], - CustomRoles.Virus when target.Is(CustomRoles.Contagious) => Main.RoleColors[CustomRoles.Contagious], - CustomRoles.Monarch when target.Is(CustomRoles.Knighted) => Main.RoleColors[CustomRoles.Knighted], - CustomRoles.Spiritcaller when target.Is(CustomRoles.EvilSpirit) => Main.RoleColors[CustomRoles.EvilSpirit], - CustomRoles.Jackal when target.Is(CustomRoles.Recruit) => Main.RoleColors[CustomRoles.Jackal], - CustomRoles.Refugee when target.Is(CustomRoleTypes.Impostor) => Main.RoleColors[CustomRoles.ImpostorEHR], - CustomRoles.HeadHunter when ((HeadHunter)seerRoleClass).Targets.Contains(target.PlayerId) => "000000", - CustomRoles.BountyHunter when (seerRoleClass as BountyHunter)?.GetTarget(seer) == target.PlayerId => "000000", - CustomRoles.Pyromaniac when ((Pyromaniac)seerRoleClass).DousedList.Contains(target.PlayerId) => "#BA4A00", - CustomRoles.Glitch when target.IsRoleBlocked() => Main.RoleColors[seerRole], - CustomRoles.Aid when Aid.ShieldedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Aid], - CustomRoles.Spy when Spy.SpyRedNameList.ContainsKey(target.PlayerId) => "#BA4A00", - CustomRoles.Mastermind when Mastermind.ManipulateDelays.ContainsKey(target.PlayerId) => "#00ffa5", - CustomRoles.Mastermind when Mastermind.ManipulatedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Arsonist], - CustomRoles.Hitman when (seerRoleClass as Hitman)?.TargetId == target.PlayerId => "000000", - CustomRoles.Postman when (seerRoleClass as Postman)?.Target == target.PlayerId => Main.RoleColors[CustomRoles.Postman], - CustomRoles.Mycologist when ((Mycologist)seerRoleClass).InfectedPlayers.Contains(target.PlayerId) => Main.RoleColors[CustomRoles.Mycologist], - CustomRoles.Bubble when Bubble.EncasedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Bubble], - CustomRoles.Hookshot when (seerRoleClass as Hookshot)?.MarkedPlayerId == target.PlayerId => Main.RoleColors[CustomRoles.Hookshot], - CustomRoles.SoulHunter when SoulHunter.IsSoulHunterTarget(target.PlayerId) => Main.RoleColors[CustomRoles.SoulHunter], - CustomRoles.Kamikaze when ((Kamikaze)seerRoleClass).MarkedPlayers.Contains(target.PlayerId) => Main.RoleColors[CustomRoles.Electric], - CustomRoles.QuizMaster when ((QuizMaster)seerRoleClass).Target == target.PlayerId => "000000", - CustomRoles.Augmenter when ((Augmenter)seerRoleClass).Target == target.PlayerId => "000000", - CustomRoles.Socialite when ((Socialite)seerRoleClass).GuestList.Contains(target.PlayerId) => "000000", - CustomRoles.Socialite when ((Socialite)seerRoleClass).MarkedPlayerId == target.PlayerId => Main.RoleColors[seerRole], - CustomRoles.Beehive when ((Beehive)seerRoleClass).StungPlayers.ContainsKey(target.PlayerId) => "000000", - CustomRoles.Dad when ((Dad)seerRoleClass).DrunkPlayers.Contains(target.PlayerId) => "000000", - CustomRoles.Wasp when seerRoleClass is Wasp wasp && (wasp.DelayedKills.ContainsKey(target.PlayerId) || wasp.MeetingKills.Contains(target.PlayerId)) => "000000", - CustomRoles.God when God.KnowInfo.GetValue() == 1 => target.GetTeam() switch + color = ghostRole.Instance.Team switch { Team.Impostor => Main.ImpostorColor, Team.Crewmate => Main.CrewmateColor, Team.Neutral => Main.NeutralColor, _ => color - }, - CustomRoles.Curser when ((Curser)seerRoleClass).KnownFactionPlayers.Contains(target.PlayerId) => target.GetTeam() switch - { - Team.Impostor => Main.ImpostorColor, - Team.Crewmate => Main.CrewmateColor, - Team.Neutral => Main.NeutralColor, - _ => color - }, - _ => color - }; - - // Check if the role color can be seen based on the target's role - color = targetRole switch - { - CustomRoles.Jackal when seer.Is(CustomRoles.Recruit) => Main.RoleColors[CustomRoles.Jackal], - CustomRoles.Virus when seer.Is(CustomRoles.Contagious) => Main.RoleColors[CustomRoles.Virus], - CustomRoles.Refugee when seer.Is(CustomRoleTypes.Impostor) => Main.RoleColors[CustomRoles.Refugee], - CustomRoles.Speedrunner when !seer.Is(Team.Crewmate) && target.GetTaskState().CompletedTasksCount >= Speedrunner.SpeedrunnerNotifyAtXTasksLeft.GetInt() && Speedrunner.SpeedrunnerNotifyKillers.GetBool() => Main.RoleColors[CustomRoles.Speedrunner], - CustomRoles.SoulHunter when SoulHunter.IsSoulHunterTarget(seer.PlayerId) => Main.RoleColors[CustomRoles.SoulHunter], - CustomRoles.Necromancer or CustomRoles.Deathknight when seer.Is(CustomRoles.Undead) => Main.RoleColors[targetRole], - CustomRoles.Succubus when seer.Is(CustomRoles.Charmed) => Main.RoleColors[CustomRoles.Succubus], - CustomRoles.Crewpostor when seer.Is(CustomRoleTypes.Impostor) && Options.AlliesKnowCrewpostor.GetBool() => Main.RoleColors[CustomRoles.Madmate], - CustomRoles.President when ((President)targetRoleClass).IsRevealed => Main.RoleColors[CustomRoles.President], - _ => color - }; - - // Visionary and Necroview - if (((seer.Is(CustomRoles.Necroview) && target.Data.IsDead && !target.IsAlive()) || - (seerRoleClass is Visionary { IsEnable: true } vn && vn.RevealedPlayerIds.Contains(target.PlayerId) && target.IsAlive() && !target.Data.IsDead)) - && seer.IsAlive()) - { - color = target.GetCustomRoleTypes() switch - { - CustomRoleTypes.Impostor => Main.ImpostorColor, - CustomRoleTypes.Crewmate => Main.CrewmateColor, - CustomRoleTypes.Neutral => Main.NeutralColor, - _ => color }; - - if (target.GetCustomRole() is CustomRoles.Parasite or CustomRoles.Crewpostor or CustomRoles.Convict or CustomRoles.Refugee) color = Main.ImpostorColor; - - if (target.Is(CustomRoles.Madmate)) color = Main.ImpostorColor; - - if (target.Is(CustomRoles.Rascal)) color = Main.ImpostorColor; - - if (target.Is(CustomRoles.Charmed)) color = Main.NeutralColor; - - if (target.Is(CustomRoles.Contagious)) color = Main.NeutralColor; - - if (target.Is(CustomRoles.Egoist)) color = Main.NeutralColor; - - if (target.Is(CustomRoles.Recruit)) color = Main.NeutralColor; } - - // Global (important) - if (Bubble.EncasedPlayers.TryGetValue(target.PlayerId, out long ts) && (ts + Bubble.NotifyDelay.GetInt() < Utils.TimeStamp || seer.Is(CustomRoles.Bubble))) color = Main.RoleColors[CustomRoles.Bubble]; - - // If the color was determined, return true, else, check if the seer can see the target's role color without knowing the color - if (color != "") return true; - - return seer == target - || (Main.GodMode.Value && seer.AmOwner) - || Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop - || (Main.PlayerStates[seer.Data.PlayerId].IsDead && seer.Data.IsDead && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool() && (!CustomRoles.Altruist.RoleExist() || !Main.DiedThisRound.Contains(seer.PlayerId))) - || (seer.Is(CustomRoles.Mimic) && Main.PlayerStates[target.Data.PlayerId].IsDead && target.Data.IsDead && !target.IsAlive() && Options.MimicCanSeeDeadRoles.GetBool()) - || target.Is(CustomRoles.GM) - || seer.Is(CustomRoles.GM) - || (seer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) - || (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor)) - || (seer.Is(CustomRoles.Traitor) && target.Is(Team.Impostor)) - || (seer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick)) - || (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick)) - || (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal)) - || (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoleTypes.Impostor) && Options.MadmateKnowWhosImp.GetBool()) - || (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Madmate) && Options.ImpKnowWhosMadmate.GetBool()) - || (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosMadmate.GetBool()) - || (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) - || (target.Is(CustomRoles.Workaholic) && Workaholic.WorkaholicVisibleToEveryone.GetBool()) - || (target.Is(CustomRoles.Doctor) && !target.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool()) - || (target.Is(CustomRoles.Gravestone) && Main.PlayerStates[target.Data.PlayerId].IsDead) - || (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) - || (seer.Is(CustomRoleTypes.Crewmate) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) - || Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)); } - private static bool TryGetData(PlayerControl seer, PlayerControl target, out string colorCode) - { - colorCode = ""; - PlayerState state = Main.PlayerStates[seer.PlayerId]; - if (!state.TargetColorData.TryGetValue(target.PlayerId, out string value)) return false; + if (isMeeting && Haunter.AllHauntedPlayers.Contains(target.PlayerId)) color = Main.ImpostorColor; - colorCode = value; - return true; - } + CustomRoles seerRole = seer.GetCustomRole(); + CustomRoles targetRole = target.GetCustomRole(); - public static void Add(byte seerId, byte targetId, string colorCode = "") + // If 2 players have the same role and that role is a NK role, they can see each other's name color + if (seerRole.IsNK() && seerRole == targetRole) color = Main.RoleColors[seerRole]; + + // Specific seer-target role combinations (excluding NK roles) + color = (seerRole, targetRole) switch { - if (colorCode == "") + (CustomRoles.Necromancer, CustomRoles.Deathknight) => Main.RoleColors[CustomRoles.Deathknight], + (CustomRoles.Deathknight, CustomRoles.Necromancer) => Main.RoleColors[CustomRoles.Necromancer], + (CustomRoles.Deathknight, CustomRoles.Deathknight) => Main.RoleColors[CustomRoles.Deathknight], + _ => color + }; + + // Check if the seer can see the target's role color + color = seerRole switch + { + CustomRoles.Executioner when Executioner.Target.TryGetValue(seer.PlayerId, out byte exeTarget) && exeTarget == target.PlayerId => "000000", + CustomRoles.Gangster when target.Is(CustomRoles.Madmate) => Main.RoleColors[CustomRoles.Madmate], + CustomRoles.Crewpostor when target.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool() => Main.ImpostorColor, + CustomRoles.Succubus when target.Is(CustomRoles.Charmed) => Main.RoleColors[CustomRoles.Charmed], + CustomRoles.Necromancer or CustomRoles.Deathknight when target.Is(CustomRoles.Undead) => Main.RoleColors[CustomRoles.Undead], + CustomRoles.Necromancer or CustomRoles.Deathknight when Necromancer.PartiallyRecruitedIds.Contains(target.PlayerId) => Main.RoleColors[CustomRoles.Deathknight], + CustomRoles.Virus when target.Is(CustomRoles.Contagious) => Main.RoleColors[CustomRoles.Contagious], + CustomRoles.Monarch when target.Is(CustomRoles.Knighted) => Main.RoleColors[CustomRoles.Knighted], + CustomRoles.Spiritcaller when target.Is(CustomRoles.EvilSpirit) => Main.RoleColors[CustomRoles.EvilSpirit], + CustomRoles.Jackal when target.Is(CustomRoles.Recruit) => Main.RoleColors[CustomRoles.Jackal], + CustomRoles.Refugee when target.Is(CustomRoleTypes.Impostor) => Main.RoleColors[CustomRoles.ImpostorEHR], + CustomRoles.HeadHunter when ((HeadHunter)seerRoleClass).Targets.Contains(target.PlayerId) => "000000", + CustomRoles.BountyHunter when (seerRoleClass as BountyHunter)?.GetTarget(seer) == target.PlayerId => "000000", + CustomRoles.Pyromaniac when ((Pyromaniac)seerRoleClass).DousedList.Contains(target.PlayerId) => "#BA4A00", + CustomRoles.Glitch when target.IsRoleBlocked() => Main.RoleColors[seerRole], + CustomRoles.Aid when Aid.ShieldedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Aid], + CustomRoles.Spy when Spy.SpyRedNameList.ContainsKey(target.PlayerId) => "#BA4A00", + CustomRoles.Mastermind when Mastermind.ManipulateDelays.ContainsKey(target.PlayerId) => "#00ffa5", + CustomRoles.Mastermind when Mastermind.ManipulatedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Arsonist], + CustomRoles.Hitman when (seerRoleClass as Hitman)?.TargetId == target.PlayerId => "000000", + CustomRoles.Postman when (seerRoleClass as Postman)?.Target == target.PlayerId => Main.RoleColors[CustomRoles.Postman], + CustomRoles.Mycologist when ((Mycologist)seerRoleClass).InfectedPlayers.Contains(target.PlayerId) => Main.RoleColors[CustomRoles.Mycologist], + CustomRoles.Bubble when Bubble.EncasedPlayers.ContainsKey(target.PlayerId) => Main.RoleColors[CustomRoles.Bubble], + CustomRoles.Hookshot when (seerRoleClass as Hookshot)?.MarkedPlayerId == target.PlayerId => Main.RoleColors[CustomRoles.Hookshot], + CustomRoles.SoulHunter when SoulHunter.IsSoulHunterTarget(target.PlayerId) => Main.RoleColors[CustomRoles.SoulHunter], + CustomRoles.Kamikaze when ((Kamikaze)seerRoleClass).MarkedPlayers.Contains(target.PlayerId) => Main.RoleColors[CustomRoles.Electric], + CustomRoles.QuizMaster when ((QuizMaster)seerRoleClass).Target == target.PlayerId => "000000", + CustomRoles.Augmenter when ((Augmenter)seerRoleClass).Target == target.PlayerId => "000000", + CustomRoles.Socialite when ((Socialite)seerRoleClass).GuestList.Contains(target.PlayerId) => "000000", + CustomRoles.Socialite when ((Socialite)seerRoleClass).MarkedPlayerId == target.PlayerId => Main.RoleColors[seerRole], + CustomRoles.Beehive when ((Beehive)seerRoleClass).StungPlayers.ContainsKey(target.PlayerId) => "000000", + CustomRoles.Dad when ((Dad)seerRoleClass).DrunkPlayers.Contains(target.PlayerId) => "000000", + CustomRoles.Wasp when seerRoleClass is Wasp wasp && (wasp.DelayedKills.ContainsKey(target.PlayerId) || wasp.MeetingKills.Contains(target.PlayerId)) => "000000", + CustomRoles.God when God.KnowInfo.GetValue() == 1 => target.GetTeam() switch { - PlayerControl target = Utils.GetPlayerById(targetId); - if (target == null) return; + Team.Impostor => Main.ImpostorColor, + Team.Crewmate => Main.CrewmateColor, + Team.Neutral => Main.NeutralColor, + _ => color + }, + CustomRoles.Curser when ((Curser)seerRoleClass).KnownFactionPlayers.Contains(target.PlayerId) => target.GetTeam() switch + { + Team.Impostor => Main.ImpostorColor, + Team.Crewmate => Main.CrewmateColor, + Team.Neutral => Main.NeutralColor, + _ => color + }, + _ => color + }; - colorCode = target.GetRoleColorCode(); - } - - PlayerState state = Main.PlayerStates[seerId]; - if (state.TargetColorData.TryGetValue(targetId, out string value) && colorCode == value) return; - - state.TargetColorData.Add(targetId, colorCode); - - SendRPC(seerId, targetId, colorCode); - } - - private static void Remove(byte seerId, byte targetId) + // Check if the role color can be seen based on the target's role + color = targetRole switch { - PlayerState state = Main.PlayerStates[seerId]; - if (!state.TargetColorData.ContainsKey(targetId)) return; + CustomRoles.Jackal when seer.Is(CustomRoles.Recruit) => Main.RoleColors[CustomRoles.Jackal], + CustomRoles.Virus when seer.Is(CustomRoles.Contagious) => Main.RoleColors[CustomRoles.Virus], + CustomRoles.Refugee when seer.Is(CustomRoleTypes.Impostor) => Main.RoleColors[CustomRoles.Refugee], + CustomRoles.Speedrunner when !seer.Is(Team.Crewmate) && target.GetTaskState().CompletedTasksCount >= Speedrunner.SpeedrunnerNotifyAtXTasksLeft.GetInt() && Speedrunner.SpeedrunnerNotifyKillers.GetBool() => Main.RoleColors[CustomRoles.Speedrunner], + CustomRoles.SoulHunter when SoulHunter.IsSoulHunterTarget(seer.PlayerId) => Main.RoleColors[CustomRoles.SoulHunter], + CustomRoles.Necromancer or CustomRoles.Deathknight when seer.Is(CustomRoles.Undead) => Main.RoleColors[targetRole], + CustomRoles.Succubus when seer.Is(CustomRoles.Charmed) => Main.RoleColors[CustomRoles.Succubus], + CustomRoles.Crewpostor when seer.Is(CustomRoleTypes.Impostor) && Options.AlliesKnowCrewpostor.GetBool() => Main.RoleColors[CustomRoles.Madmate], + CustomRoles.President when ((President)targetRoleClass).IsRevealed => Main.RoleColors[CustomRoles.President], + _ => color + }; - state.TargetColorData.Remove(targetId); - - SendRPC(seerId, targetId); - } - - private static void RemoveAll(byte seerId) + // Visionary and Necroview + if (((seer.Is(CustomRoles.Necroview) && target.Data.IsDead && !target.IsAlive()) || + (seerRoleClass is Visionary { IsEnable: true } vn && vn.RevealedPlayerIds.Contains(target.PlayerId) && target.IsAlive() && !target.Data.IsDead)) + && seer.IsAlive()) { - Main.PlayerStates[seerId].TargetColorData.Clear(); + color = target.GetCustomRoleTypes() switch + { + CustomRoleTypes.Impostor => Main.ImpostorColor, + CustomRoleTypes.Crewmate => Main.CrewmateColor, + CustomRoleTypes.Neutral => Main.NeutralColor, + _ => color + }; - SendRPC(seerId); + if (target.GetCustomRole() is CustomRoles.Parasite or CustomRoles.Crewpostor or CustomRoles.Convict or CustomRoles.Refugee) color = Main.ImpostorColor; + + if (target.Is(CustomRoles.Madmate)) color = Main.ImpostorColor; + + if (target.Is(CustomRoles.Rascal)) color = Main.ImpostorColor; + + if (target.Is(CustomRoles.Charmed)) color = Main.NeutralColor; + + if (target.Is(CustomRoles.Contagious)) color = Main.NeutralColor; + + if (target.Is(CustomRoles.Egoist)) color = Main.NeutralColor; + + if (target.Is(CustomRoles.Recruit)) color = Main.NeutralColor; } - private static void SendRPC(byte seerId, byte targetId = byte.MaxValue, string colorCode = "") + // Global (important) + if (Bubble.EncasedPlayers.TryGetValue(target.PlayerId, out long ts) && (ts + Bubble.NotifyDelay.GetInt() < Utils.TimeStamp || seer.Is(CustomRoles.Bubble))) color = Main.RoleColors[CustomRoles.Bubble]; + + // If the color was determined, return true, else, check if the seer can see the target's role color without knowing the color + if (color != "") return true; + + return seer == target + || (Main.GodMode.Value && seer.AmOwner) + || Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop + || (Main.PlayerStates[seer.Data.PlayerId].IsDead && seer.Data.IsDead && !seer.IsAlive() && Options.GhostCanSeeOtherRoles.GetBool() && (!CustomRoles.Altruist.RoleExist() || !Main.DiedThisRound.Contains(seer.PlayerId))) + || (seer.Is(CustomRoles.Mimic) && Main.PlayerStates[target.Data.PlayerId].IsDead && target.Data.IsDead && !target.IsAlive() && Options.MimicCanSeeDeadRoles.GetBool()) + || target.Is(CustomRoles.GM) + || seer.Is(CustomRoles.GM) + || (seer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) + || (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor)) + || (seer.Is(CustomRoles.Traitor) && target.Is(Team.Impostor)) + || (seer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick)) + || (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick)) + || (seer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal)) + || (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoleTypes.Impostor) && Options.MadmateKnowWhosImp.GetBool()) + || (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Madmate) && Options.ImpKnowWhosMadmate.GetBool()) + || (seer.Is(CustomRoles.Madmate) && target.Is(CustomRoles.Madmate) && Options.MadmateKnowWhosMadmate.GetBool()) + || (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) + || (target.Is(CustomRoles.Workaholic) && Workaholic.WorkaholicVisibleToEveryone.GetBool()) + || (target.Is(CustomRoles.Doctor) && !target.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool()) + || (target.Is(CustomRoles.Gravestone) && Main.PlayerStates[target.Data.PlayerId].IsDead) + || (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) + || (seer.Is(CustomRoleTypes.Crewmate) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) + || Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)); + } + + private static bool TryGetData(PlayerControl seer, PlayerControl target, out string colorCode) + { + colorCode = ""; + PlayerState state = Main.PlayerStates[seer.PlayerId]; + if (!state.TargetColorData.TryGetValue(target.PlayerId, out string value)) return false; + + colorCode = value; + return true; + } + + public static void Add(byte seerId, byte targetId, string colorCode = "") + { + if (colorCode == "") { - if (!AmongUsClient.Instance.AmHost) return; + PlayerControl target = Utils.GetPlayerById(targetId); + if (target == null) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetNameColorData, SendOption.Reliable); - writer.Write(seerId); - writer.Write(targetId); - writer.Write(colorCode); - AmongUsClient.Instance.FinishRpcImmediately(writer); + colorCode = target.GetRoleColorCode(); } - public static void ReceiveRPC(MessageReader reader) - { - byte seerId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - string colorCode = reader.ReadString(); + PlayerState state = Main.PlayerStates[seerId]; + if (state.TargetColorData.TryGetValue(targetId, out string value) && colorCode == value) return; - if (targetId == byte.MaxValue) - RemoveAll(seerId); - else if (colorCode == "") - Remove(seerId, targetId); - else - Add(seerId, targetId, colorCode); - } + state.TargetColorData.Add(targetId, colorCode); + + SendRPC(seerId, targetId, colorCode); + } + + private static void Remove(byte seerId, byte targetId) + { + PlayerState state = Main.PlayerStates[seerId]; + if (!state.TargetColorData.ContainsKey(targetId)) return; + + state.TargetColorData.Remove(targetId); + + SendRPC(seerId, targetId); + } + + private static void RemoveAll(byte seerId) + { + Main.PlayerStates[seerId].TargetColorData.Clear(); + + SendRPC(seerId); + } + + private static void SendRPC(byte seerId, byte targetId = byte.MaxValue, string colorCode = "") + { + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetNameColorData, SendOption.Reliable); + writer.Write(seerId); + writer.Write(targetId); + writer.Write(colorCode); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte seerId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + string colorCode = reader.ReadString(); + + if (targetId == byte.MaxValue) + RemoveAll(seerId); + else if (colorCode == "") + Remove(seerId, targetId); + else + Add(seerId, targetId, colorCode); } } \ No newline at end of file diff --git a/Modules/NameNotifyManager.cs b/Modules/NameNotifyManager.cs index 6e19eba0b..ea42fc6c2 100644 --- a/Modules/NameNotifyManager.cs +++ b/Modules/NameNotifyManager.cs @@ -4,100 +4,99 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR +namespace EHR; + +public static class NameNotifyManager { - public static class NameNotifyManager + public static Dictionary> Notifies = []; + + public static void Reset() { - public static Dictionary> Notifies = []; + Notifies = []; + } - public static void Reset() + public static void Notify(this PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true) + { + if (!AmongUsClient.Instance.AmHost || pc == null) return; + + if (!GameStates.IsInTask) return; + + if (!text.Contains("")) text = Utils.ColorString(Color.white, text); + + if (!text.Contains("{text}"; + + long expireTS = Utils.TimeStamp + (long)time; + + if (overrideAll || !Notifies.TryGetValue(pc.PlayerId, out Dictionary notifies)) + Notifies[pc.PlayerId] = new() { { text, expireTS } }; + else + notifies[text] = expireTS; + + SendRPC(pc.PlayerId, text, expireTS, overrideAll); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (log) Logger.Info($"New name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify"); + } + + public static void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) { - Notifies = []; + Reset(); + return; } - public static void Notify(this PlayerControl pc, string text, float time = 6f, bool overrideAll = false, bool log = true) + var removed = false; + + if (Notifies.TryGetValue(player.PlayerId, out Dictionary notifies)) { - if (!AmongUsClient.Instance.AmHost || pc == null) return; - - if (!GameStates.IsInTask) return; - - if (!text.Contains("")) text = Utils.ColorString(Color.white, text); - - if (!text.Contains("{text}"; - - long expireTS = Utils.TimeStamp + (long)time; - - if (overrideAll || !Notifies.TryGetValue(pc.PlayerId, out Dictionary notifies)) - Notifies[pc.PlayerId] = new() { { text, expireTS } }; - else - notifies[text] = expireTS; - - SendRPC(pc.PlayerId, text, expireTS, overrideAll); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - if (log) Logger.Info($"New name notify for {pc.GetNameWithRole().RemoveHtmlTags()}: {text} ({time}s)", "Name Notify"); - } - - public static void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask) + foreach (KeyValuePair notify in notifies.ToArray()) { - Reset(); - return; - } - - var removed = false; - - if (Notifies.TryGetValue(player.PlayerId, out Dictionary notifies)) - { - foreach (KeyValuePair notify in notifies.ToArray()) + if (notify.Value <= Utils.TimeStamp) { - if (notify.Value <= Utils.TimeStamp) - { - notifies.Remove(notify.Key); - removed = true; - } + notifies.Remove(notify.Key); + removed = true; } } - - if (removed) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); } - public static bool GetNameNotify(PlayerControl player, out string name) - { - name = string.Empty; - if (!Notifies.TryGetValue(player.PlayerId, out Dictionary notifies)) return false; + if (removed) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + } - name = string.Join('\n', notifies.OrderBy(x => x.Value).Select(x => x.Key)); - return true; - } + public static bool GetNameNotify(PlayerControl player, out string name) + { + name = string.Empty; + if (!Notifies.TryGetValue(player.PlayerId, out Dictionary notifies)) return false; - private static void SendRPC(byte playerId, string text, long expireTS, bool overrideAll) // Only sent when adding a new notification - { - if (!AmongUsClient.Instance.AmHost) return; + name = string.Join('\n', notifies.OrderBy(x => x.Value).Select(x => x.Key)); + return true; + } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable); - writer.Write(playerId); - writer.Write(text); - writer.Write(expireTS.ToString()); - writer.Write(overrideAll); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + private static void SendRPC(byte playerId, string text, long expireTS, bool overrideAll) // Only sent when adding a new notification + { + if (!AmongUsClient.Instance.AmHost) return; - public static void ReceiveRPC(MessageReader reader) - { - if (AmongUsClient.Instance.AmHost) return; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncNameNotify, SendOption.Reliable); + writer.Write(playerId); + writer.Write(text); + writer.Write(expireTS.ToString()); + writer.Write(overrideAll); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - byte playerId = reader.ReadByte(); - string text = reader.ReadString(); - long expireTS = long.Parse(reader.ReadString()); - bool overrideAll = reader.ReadBoolean(); + public static void ReceiveRPC(MessageReader reader) + { + if (AmongUsClient.Instance.AmHost) return; - if (overrideAll || !Notifies.TryGetValue(playerId, out Dictionary notifies)) - Notifies[playerId] = new() { { text, expireTS } }; - else - notifies[text] = expireTS; + byte playerId = reader.ReadByte(); + string text = reader.ReadString(); + long expireTS = long.Parse(reader.ReadString()); + bool overrideAll = reader.ReadBoolean(); - Logger.Info($"New name notify for {Main.AllPlayerNames[playerId]}: {text} ({expireTS - Utils.TimeStamp}s)", "Name Notify"); - } + if (overrideAll || !Notifies.TryGetValue(playerId, out Dictionary notifies)) + Notifies[playerId] = new() { { text, expireTS } }; + else + notifies[text] = expireTS; + + Logger.Info($"New name notify for {Main.AllPlayerNames[playerId]}: {text} ({expireTS - Utils.TimeStamp}s)", "Name Notify"); } } \ No newline at end of file diff --git a/Modules/OptionBackup/OptionBackupData.cs b/Modules/OptionBackup/OptionBackupData.cs index 089d4711f..6a018b84a 100644 --- a/Modules/OptionBackup/OptionBackupData.cs +++ b/Modules/OptionBackup/OptionBackupData.cs @@ -3,80 +3,79 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; -namespace EHR +namespace EHR; + +public class OptionBackupData { - public class OptionBackupData + public List AllValues; + + public OptionBackupData(IGameOptions option) { - public List AllValues; + AllValues = new(32); - public OptionBackupData(IGameOptions option) - { - AllValues = new(32); + foreach (ByteOptionNames name in Enum.GetValues()) + if (option.TryGetByte(name, out byte value)) + AllValues.Add(new ByteOptionBackupValue(name, value)); - foreach (ByteOptionNames name in Enum.GetValues()) - if (option.TryGetByte(name, out byte value)) - AllValues.Add(new ByteOptionBackupValue(name, value)); + foreach (BoolOptionNames name in Enum.GetValues()) + if (option.TryGetBool(name, out bool value) && name != BoolOptionNames.GhostsDoTasks) + AllValues.Add(new BoolOptionBackupValue(name, value)); - foreach (BoolOptionNames name in Enum.GetValues()) - if (option.TryGetBool(name, out bool value) && name != BoolOptionNames.GhostsDoTasks) - AllValues.Add(new BoolOptionBackupValue(name, value)); + foreach (FloatOptionNames name in Enum.GetValues()) + if (option.TryGetFloat(name, out float value)) + AllValues.Add(new FloatOptionBackupValue(name, value)); - foreach (FloatOptionNames name in Enum.GetValues()) - if (option.TryGetFloat(name, out float value)) - AllValues.Add(new FloatOptionBackupValue(name, value)); + foreach (Int32OptionNames name in Enum.GetValues()) + if (option.TryGetInt(name, out int value)) + AllValues.Add(new IntOptionBackupValue(name, value)); - foreach (Int32OptionNames name in Enum.GetValues()) - if (option.TryGetInt(name, out int value)) - AllValues.Add(new IntOptionBackupValue(name, value)); + // [Vanilla bug] Get the number of people in the room separately, since GetInt cannot get the number of people in the room + AllValues.Add(new IntOptionBackupValue(Int32OptionNames.MaxPlayers, option.MaxPlayers)); + // Since TryGetUInt is not implemented, get it separately + AllValues.Add(new UIntOptionBackupValue(UInt32OptionNames.Keywords, (uint)option.Keywords)); - // [Vanilla bug] Get the number of people in the room separately, since GetInt cannot get the number of people in the room - AllValues.Add(new IntOptionBackupValue(Int32OptionNames.MaxPlayers, option.MaxPlayers)); - // Since TryGetUInt is not implemented, get it separately - AllValues.Add(new UIntOptionBackupValue(UInt32OptionNames.Keywords, (uint)option.Keywords)); + RoleTypes[] array = [RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter, RoleTypes.Noisemaker, RoleTypes.Phantom, RoleTypes.Tracker]; + foreach (RoleTypes role in array) AllValues.Add(new RoleRateBackupValue(role, option.RoleOptions.GetNumPerGame(role), option.RoleOptions.GetChancePerGame(role))); + } - RoleTypes[] array = [RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter, RoleTypes.Noisemaker, RoleTypes.Phantom, RoleTypes.Tracker]; - foreach (RoleTypes role in array) AllValues.Add(new RoleRateBackupValue(role, option.RoleOptions.GetNumPerGame(role), option.RoleOptions.GetChancePerGame(role))); - } + public IGameOptions Restore(IGameOptions option) + { + AllValues.ForEach(o => o.Restore(option)); + return option; + } - public IGameOptions Restore(IGameOptions option) - { - AllValues.ForEach(o => o.Restore(option)); - return option; - } + public byte GetByte(ByteOptionNames name) + { + return Get(name); + } - public byte GetByte(ByteOptionNames name) - { - return Get(name); - } + public bool GetBool(BoolOptionNames name) + { + return Get(name); + } - public bool GetBool(BoolOptionNames name) - { - return Get(name); - } + public float GetFloat(FloatOptionNames name) + { + return Get(name); + } - public float GetFloat(FloatOptionNames name) - { - return Get(name); - } + public int GetInt(Int32OptionNames name) + { + return Get(name); + } - public int GetInt(Int32OptionNames name) - { - return Get(name); - } + public uint GetUInt(UInt32OptionNames name) + { + return Get(name); + } - public uint GetUInt(UInt32OptionNames name) - { - return Get(name); - } + public TValue Get(TKey name) + where TKey : Enum + { + OptionBackupValueBase value = AllValues + .OfType>() + .FirstOrDefault(val => val.OptionName.Equals(name)); - public TValue Get(TKey name) - where TKey : Enum - { - OptionBackupValueBase value = AllValues - .OfType>() - .FirstOrDefault(val => val.OptionName.Equals(name)); - - return value == null ? default : value.Value; - } + return value == null ? default : value.Value; } } \ No newline at end of file diff --git a/Modules/OptionBackup/OptionBackupValue.cs b/Modules/OptionBackup/OptionBackupValue.cs index c9d221583..8046cf5ba 100644 --- a/Modules/OptionBackup/OptionBackupValue.cs +++ b/Modules/OptionBackup/OptionBackupValue.cs @@ -1,69 +1,68 @@ using System; using AmongUs.GameOptions; -namespace EHR +namespace EHR; + +public abstract class OptionBackupValue { - public abstract class OptionBackupValue + public abstract void Restore(IGameOptions option); +} + +public abstract class OptionBackupValueBase(NameT name, ValueT value) : OptionBackupValue + where NameT : Enum +{ + public readonly NameT OptionName = name; + public readonly ValueT Value = value; +} + +public class ByteOptionBackupValue(ByteOptionNames name, byte value) : OptionBackupValueBase(name, value) +{ + public override void Restore(IGameOptions option) { - public abstract void Restore(IGameOptions option); + option.SetByte(OptionName, Value); } +} - public abstract class OptionBackupValueBase(NameT name, ValueT value) : OptionBackupValue - where NameT : Enum +public class BoolOptionBackupValue(BoolOptionNames name, bool value) : OptionBackupValueBase(name, value) +{ + public override void Restore(IGameOptions option) { - public readonly NameT OptionName = name; - public readonly ValueT Value = value; + if (OptionName != BoolOptionNames.GhostsDoTasks) option.SetBool(OptionName, Value); } +} - public class ByteOptionBackupValue(ByteOptionNames name, byte value) : OptionBackupValueBase(name, value) +public class FloatOptionBackupValue(FloatOptionNames name, float value) : OptionBackupValueBase(name, value) +{ + public override void Restore(IGameOptions option) { - public override void Restore(IGameOptions option) - { - option.SetByte(OptionName, Value); - } + option.SetFloat(OptionName, Value); } +} - public class BoolOptionBackupValue(BoolOptionNames name, bool value) : OptionBackupValueBase(name, value) +public class IntOptionBackupValue(Int32OptionNames name, int value) : OptionBackupValueBase(name, value) +{ + public override void Restore(IGameOptions option) { - public override void Restore(IGameOptions option) - { - if (OptionName != BoolOptionNames.GhostsDoTasks) option.SetBool(OptionName, Value); - } + option.SetInt(OptionName, Value); } +} - public class FloatOptionBackupValue(FloatOptionNames name, float value) : OptionBackupValueBase(name, value) +public class UIntOptionBackupValue(UInt32OptionNames name, uint value) : OptionBackupValueBase(name, value) +{ + public override void Restore(IGameOptions option) { - public override void Restore(IGameOptions option) - { - option.SetFloat(OptionName, Value); - } + option.SetUInt(OptionName, Value); } +} - public class IntOptionBackupValue(Int32OptionNames name, int value) : OptionBackupValueBase(name, value) +public class RoleRateBackupValue(RoleTypes type, int maxCount, int chance) : OptionBackupValue +{ + public int chance = chance; + public int maxCount = maxCount; + public RoleTypes roleType = type; + + public override void Restore(IGameOptions option) { - public override void Restore(IGameOptions option) - { - option.SetInt(OptionName, Value); - } - } - - public class UIntOptionBackupValue(UInt32OptionNames name, uint value) : OptionBackupValueBase(name, value) - { - public override void Restore(IGameOptions option) - { - option.SetUInt(OptionName, Value); - } - } - - public class RoleRateBackupValue(RoleTypes type, int maxCount, int chance) : OptionBackupValue - { - public int chance = chance; - public int maxCount = maxCount; - public RoleTypes roleType = type; - - public override void Restore(IGameOptions option) - { - option.RoleOptions.SetRoleRate(roleType, maxCount, chance); - } + option.RoleOptions.SetRoleRate(roleType, maxCount, chance); } } \ No newline at end of file diff --git a/Modules/OptionHolder.cs b/Modules/OptionHolder.cs index bfddf57f0..1c4c2fc33 100644 --- a/Modules/OptionHolder.cs +++ b/Modules/OptionHolder.cs @@ -12,2637 +12,2636 @@ using UnityEngine; // ReSharper disable AccessToModifiedClosure // ReSharper disable InconsistentNaming -namespace EHR +namespace EHR; + +[Flags] +public enum CustomGameMode { - [Flags] - public enum CustomGameMode + Standard = 0x01, + SoloKombat = 0x02, + FFA = 0x03, + MoveAndStop = 0x04, + HotPotato = 0x05, + HideAndSeek = 0x06, + Speedrun = 0x07, + CaptureTheFlag = 0x08, + NaturalDisasters = 0x09, + RoomRush = 0x0A, + AllInOne = 0x0B, + All = int.MaxValue +} + +[HarmonyPatch] +public static class Options +{ + public enum GameStateInfo { - Standard = 0x01, - SoloKombat = 0x02, - FFA = 0x03, - MoveAndStop = 0x04, - HotPotato = 0x05, - HideAndSeek = 0x06, - Speedrun = 0x07, - CaptureTheFlag = 0x08, - NaturalDisasters = 0x09, - RoomRush = 0x0A, - AllInOne = 0x0B, - All = int.MaxValue + ImpCount, + MadmateCount, + ConvertedCount, + NNKCount, + NKCount, + CrewCount, + RomanticState, + LoversState, + Tasks } - [HarmonyPatch] - public static class Options + public static Dictionary GroupedOptions = []; + public static Dictionary> GroupedAddons = []; + + public static OptionItem GameMode; + + private static readonly string[] GameModes = + [ + "Standard", + "SoloKombat", + "FFA", + "MoveAndStop", + "HotPotato", + "HideAndSeek", + "Speedrun", + "CaptureTheFlag", + "NaturalDisasters", + "RoomRush", + "AllInOne" + ]; + + private static Dictionary roleCounts; + private static Dictionary roleSpawnChances; + public static Dictionary CustomRoleCounts; + public static Dictionary CustomRoleSpawnChances; + public static Dictionary CustomAdtRoleSpawnRate; + + public static readonly Dictionary FactionMinMaxSettings = []; + public static readonly Dictionary RoleSubCategoryLimits = []; + + public static readonly string[] Rates = + [ + "Rate0", + "Rate5", + "Rate10", + "Rate15", + "Rate20", + "Rate25", + "Rate30", + "Rate35", + "Rate40", + "Rate45", + "Rate50", + "Rate55", + "Rate60", + "Rate65", + "Rate70", + "Rate75", + "Rate80", + "Rate85", + "Rate90", + "Rate95", + "Rate100" + ]; + + public static readonly string[] RatesZeroOne = + [ + "RoleOff", /*"Rate10", "Rate20", "Rate30", "Rate40", "Rate50", + "Rate60", "Rate70", "Rate80", "Rate90", */ + "RoleRate" + ]; + + private static readonly string[] CheatResponsesName = + [ + "Ban", + "Kick", + "NoticeMe", + "NoticeEveryone", + "OnlyCancel" + ]; + + private static readonly string[] ConfirmEjectionsMode = + [ + "ConfirmEjections.None", + "ConfirmEjections.Team", + "ConfirmEjections.Role" + ]; + + private static readonly string[] CamouflageMode = + [ + "CamouflageMode.Default", + "CamouflageMode.Host", + "CamouflageMode.Karpe", + "CamouflageMode.Gurge44", + "CamouflageMode.TommyXL" + ]; + + public static readonly string[] PetToAssign = + [ + "pet_Bedcrab", + "pet_BredPet", + "pet_YuleGoatPet", + "pet_Bush", + "pet_Charles", + "pet_ChewiePet", + "pet_clank", + "pet_coaltonpet", + "pet_Cube", + "pet_Doggy", + "pet_Ellie", + "pet_frankendog", + "pet_D2GhostPet", + "pet_test", + "pet_GuiltySpark", + "pet_Stickmin", + "pet_HamPet", + "pet_Hamster", + "pet_Alien", + "pet_poro", + "pet_HamPet", + "pet_Lava", + "pet_Crewmate", + "pet_D2PoukaPet", + "pet_Pusheen", + "pet_Robot", + "pet_Snow", + "pet_Squig", + "pet_nuggetPet", + "pet_Charles_Red", + "pet_UFO", + "pet_D2WormPet", + "pet_RANDOM_FOR_EVERYONE" + ]; + + public static float DefaultKillCooldown = Main.NormalOptions?.KillCooldown ?? 25; + + public static Dictionary GameStateSettings = []; + public static OptionItem MinPlayersForGameStateCommand; + + public static OptionItem DisableMeeting; + public static OptionItem DisableCloseDoor; + public static OptionItem DisableSabotage; + + public static OptionItem DisableWhisperCommand; + + public static OptionItem DisableReactorOnSkeldAndMira; + public static OptionItem DisableReactorOnPolus; + public static OptionItem DisableReactorOnAirship; + public static OptionItem DisableO2; + public static OptionItem DisableComms; + public static OptionItem DisableLights; + public static OptionItem DisableMushroomMixup; + + public static OptionItem DisableTaskWin; + + public static OptionItem KillFlashDuration; + public static OptionItem EnableKillerLeftCommand; + + public static OptionItem SeeEjectedRolesInMeeting; + public static OptionItem EveryoneSeesDeathReasons; + + public static OptionItem DisableShieldAnimations; + public static OptionItem DisableShapeshiftAnimations; + public static OptionItem DisableAllShapeshiftAnimations; + public static OptionItem DisableKillAnimationOnGuess; + public static OptionItem DisableVanillaRoles; + public static OptionItem SabotageCooldownControl; + public static OptionItem SabotageCooldown; + public static OptionItem CEMode; + public static OptionItem ShowImpRemainOnEject; + public static OptionItem ShowNKRemainOnEject; + public static OptionItem ShowTeamNextToRoleNameOnEject; + public static OptionItem CheatResponses; + public static OptionItem EnableMovementChecking; + public static OptionItem LowLoadMode; + public static OptionItem DeepLowLoad; + public static OptionItem DisableVoteBan; + + + // Detailed Ejections // + public static OptionItem ConfirmEgoistOnEject; + public static OptionItem ConfirmLoversOnEject; + + public static OptionItem UniqueNeutralRevealScreen; + + + public static OptionItem NeutralRoleWinTogether; + public static OptionItem NeutralWinTogether; + public static OptionItem NeutralsKnowEachOther; + + public static OptionItem DefaultShapeshiftCooldown; + public static OptionItem DeadImpCantSabotage; + public static OptionItem ImpKnowAlliesRole; + public static OptionItem ImpKnowWhosMadmate; + public static OptionItem MadmateKnowWhosImp; + public static OptionItem MadmateKnowWhosMadmate; + + public static OptionItem MadmateHasImpostorVision; + + public static OptionItem ImpCanKillMadmate; + public static OptionItem MadmateCanKillImp; + public static OptionItem JackalCanKillSidekick; + public static OptionItem SidekickCanKillJackal; + public static OptionItem SidekickCanKillSidekick; + + public static OptionItem EGCanGuessImp; + public static OptionItem EGCanGuessAdt; + public static OptionItem EGCanGuessTime; + public static OptionItem EGTryHideMsg; + public static OptionItem ScavengerKillCooldown; + public static OptionItem ScavengerKillDuration; + public static OptionItem GGCanGuessCrew; + public static OptionItem GGCanGuessAdt; + public static OptionItem GGCanGuessTime; + public static OptionItem GGTryHideMsg; + public static OptionItem LuckeyProbability; + public static OptionItem LuckyProbability; + public static OptionItem VindicatorAdditionalVote; + public static OptionItem VindicatorHideVote; + public static OptionItem DoctorTaskCompletedBatteryCharge; + public static OptionItem TrapperBlockMoveTime; + public static OptionItem TransporterTeleportMax; + public static OptionItem CanTerroristSuicideWin; + public static OptionItem InnocentCanWinByImp; + public static OptionItem BaitNotification; + public static OptionItem DoctorVisibleToEveryone; + public static OptionItem ArsonistDouseTime; + public static OptionItem ArsonistCooldown; + public static OptionItem ArsonistKeepsGameGoing; + public static OptionItem ArsonistCanIgniteAnytime; + public static OptionItem ArsonistMinPlayersToIgnite; + public static OptionItem ArsonistMaxPlayersToIgnite; + public static OptionItem LegacyMafia; + public static OptionItem MarioVentNumWin; + public static OptionItem MarioVentCD; + public static OptionItem VeteranSkillCooldown; + public static OptionItem VeteranSkillDuration; + public static OptionItem TimeMasterSkillCooldown; + public static OptionItem TimeMasterSkillDuration; + public static OptionItem TimeMasterMaxUses; + public static OptionItem VeteranSkillMaxOfUseage; + public static OptionItem BodyguardProtectRadius; + public static OptionItem BodyguardKillsKiller; + public static OptionItem WitnessCD; + public static OptionItem WitnessTime; + public static OptionItem WitnessUsePet; + public static OptionItem DQNumOfKillsNeeded; + public static OptionItem ParanoiaNumOfUseButton; + public static OptionItem ParanoiaVentCooldown; + public static OptionItem ImpKnowCyberStarDead; + public static OptionItem NeutralKnowCyberStarDead; + public static OptionItem DemolitionistVentTime; + public static OptionItem DemolitionistKillerDiesOnMeetingCall; + public static OptionItem ExpressSpeed; + public static OptionItem ExpressSpeedDur; + public static OptionItem EveryOneKnowSuperStar; + public static OptionItem MafiaCanKillNum; + public static OptionItem ReportBaitAtAllCost; + + public static OptionItem GuesserDoesntDieOnMisguess; + + public static OptionItem RefugeeKillCD; + + public static OptionItem SkeldChance; + public static OptionItem MiraChance; + public static OptionItem PolusChance; + public static OptionItem DleksChance; + public static OptionItem AirshipChance; + public static OptionItem FungleChance; + + public static OptionItem UnderdogKillCooldown; + public static OptionItem UnderdogMaximumPlayersNeededToKill; + public static OptionItem UnderdogKillCooldownWithMorePlayersAlive; + + public static OptionItem GodfatherCancelVote; + + public static OptionItem GuardSpellTimes; + public static OptionItem CapitalismSkillCooldown; + public static OptionItem CapitalismKillCooldown; + public static OptionItem GrenadierSkillCooldown; + public static OptionItem GrenadierSkillDuration; + public static OptionItem GrenadierCauseVision; + public static OptionItem GrenadierCanAffectNeutral; + public static OptionItem GrenadierSkillMaxOfUseage; + public static OptionItem LighterVisionNormal; + public static OptionItem LighterVisionOnLightsOut; + public static OptionItem LighterSkillCooldown; + public static OptionItem LighterSkillDuration; + public static OptionItem LighterSkillMaxOfUseage; + public static OptionItem SecurityGuardSkillCooldown; + public static OptionItem SecurityGuardSkillDuration; + public static OptionItem SecurityGuardSkillMaxOfUseage; + public static OptionItem EscapeeSSCD; + public static OptionItem MinerSSCD; + public static OptionItem RevolutionistDrawTime; + public static OptionItem RevolutionistCooldown; + public static OptionItem RevolutionistDrawCount; + public static OptionItem RevolutionistKillProbability; + public static OptionItem RevolutionistVentCountDown; + public static OptionItem ShapeImperiusCurseShapeshiftDuration; + public static OptionItem ImperiusCurseShapeshiftCooldown; + public static OptionItem CrewpostorCanKillAllies; + public static OptionItem CrewpostorKnowsAllies; + public static OptionItem AlliesKnowCrewpostor; + public static OptionItem CrewpostorKillAfterTask; + public static OptionItem CrewpostorLungeKill; + public static OptionItem ObliviousBaitImmune; + public static OptionItem UnluckyTaskSuicideChance; + public static OptionItem UnluckyKillSuicideChance; + public static OptionItem UnluckyVentSuicideChance; + public static OptionItem UnluckyReportSuicideChance; + public static OptionItem UnluckySabotageSuicideChance; + public static OptionItem AsthmaticMinRedTime; + public static OptionItem AsthmaticMaxRedTime; + public static OptionItem AsthmaticMinGreenTime; + public static OptionItem AsthmaticMaxGreenTime; + public static OptionItem DiscoChangeInterval; + + public static OptionItem TruantWaitingTime; + + // RASCAL // + public static OptionItem RascalAppearAsMadmate; + + // Mare Add-on + public static OptionItem MareKillCD; + public static OptionItem MareKillCDNormally; + public static OptionItem MareHasIncreasedSpeed; + public static OptionItem MareSpeedDuringLightsOut; + + public static OptionItem AutoPlayAgain; + public static OptionItem AutoPlayAgainCountdown; + public static OptionItem AutoStartTimer; + + public static OptionItem InhibitorCD; + public static OptionItem InhibitorCDAfterMeetings; + public static OptionItem SaboteurCD; + public static OptionItem SaboteurCDAfterMeetings; + public static OptionItem PhantomCanVent; + + public static OptionItem PhantomSnatchesWin; + + public static OptionItem DiseasedCDOpt; + public static OptionItem DiseasedCDReset; + + public static OptionItem AntidoteCDOpt; + public static OptionItem AntidoteCDReset; + + public static OptionItem BaitDelayMin; + public static OptionItem BaitDelayMax; + public static OptionItem BaitDelayNotify; + public static OptionItem TorchVision; + public static OptionItem TorchAffectedByLights; + public static OptionItem TasklessCrewCanBeLazy; + public static OptionItem TaskBasedCrewCanBeLazy; + public static OptionItem DovesOfNeaceCooldown; + public static OptionItem DovesOfNeaceMaxOfUseage; + public static OptionItem killAttacker; + public static OptionItem MimicCanSeeDeadRoles; + public static OptionItem ResetDoorsEveryTurns; + public static OptionItem DoorsResetMode; + public static OptionItem ChangeDecontaminationTime; + public static OptionItem DecontaminationTimeOnMiraHQ; + public static OptionItem DecontaminationTimeOnPolus; + + public static OptionItem MafiaShapeshiftCD; + public static OptionItem MafiaShapeshiftDur; + + public static OptionItem DisableTaskWinIfAllCrewsAreDead; + public static OptionItem DisableTaskWinIfAllCrewsAreConverted; + + // Task Management + public static OptionItem DisableShortTasks; + public static OptionItem DisableCleanVent; + public static OptionItem DisableCalibrateDistributor; + public static OptionItem DisableChartCourse; + public static OptionItem DisableStabilizeSteering; + public static OptionItem DisableCleanO2Filter; + public static OptionItem DisableUnlockManifolds; + public static OptionItem DisablePrimeShields; + public static OptionItem DisableMeasureWeather; + public static OptionItem DisableBuyBeverage; + public static OptionItem DisableAssembleArtifact; + public static OptionItem DisableSortSamples; + public static OptionItem DisableProcessData; + public static OptionItem DisableRunDiagnostics; + public static OptionItem DisableRepairDrill; + public static OptionItem DisableAlignTelescope; + public static OptionItem DisableRecordTemperature; + public static OptionItem DisableFillCanisters; + public static OptionItem DisableMonitorTree; + public static OptionItem DisableStoreArtifacts; + public static OptionItem DisablePutAwayPistols; + public static OptionItem DisablePutAwayRifles; + public static OptionItem DisableMakeBurger; + public static OptionItem DisableCleanToilet; + public static OptionItem DisableDecontaminate; + public static OptionItem DisableSortRecords; + public static OptionItem DisableFixShower; + public static OptionItem DisablePickUpTowels; + public static OptionItem DisablePolishRuby; + public static OptionItem DisableDressMannequin; + public static OptionItem DisableCommonTasks; + public static OptionItem DisableSwipeCard; + public static OptionItem DisableFixWiring; + public static OptionItem DisableEnterIdCode; + public static OptionItem DisableInsertKeys; + public static OptionItem DisableScanBoardingPass; + public static OptionItem DisableLongTasks; + public static OptionItem DisableSubmitScan; + public static OptionItem DisableUnlockSafe; + public static OptionItem DisableStartReactor; + public static OptionItem DisableResetBreaker; + public static OptionItem DisableAlignEngineOutput; + public static OptionItem DisableInspectSample; + public static OptionItem DisableEmptyChute; + public static OptionItem DisableClearAsteroids; + public static OptionItem DisableWaterPlants; + public static OptionItem DisableOpenWaterways; + public static OptionItem DisableReplaceWaterJug; + public static OptionItem DisableRebootWifi; + public static OptionItem DisableDevelopPhotos; + public static OptionItem DisableRewindTapes; + public static OptionItem DisableStartFans; + public static OptionItem DisableOtherTasks; + public static OptionItem DisableUploadData; + public static OptionItem DisableEmptyGarbage; + public static OptionItem DisableFuelEngines; + public static OptionItem DisableDivertPower; + public static OptionItem DisableActivateWeatherNodes; + public static OptionItem DisableRoastMarshmallow; + public static OptionItem DisableCollectSamples; + public static OptionItem DisableReplaceParts; + public static OptionItem DisableCollectVegetables; + public static OptionItem DisableMineOres; + public static OptionItem DisableExtractFuel; + public static OptionItem DisableCatchFish; + public static OptionItem DisablePolishGem; + public static OptionItem DisableHelpCritter; + public static OptionItem DisableHoistSupplies; + public static OptionItem DisableFixAntenna; + public static OptionItem DisableBuildSandcastle; + public static OptionItem DisableCrankGenerator; + public static OptionItem DisableMonitorMushroom; + public static OptionItem DisablePlayVideoGame; + public static OptionItem DisableFindSignal; + public static OptionItem DisableThrowFisbee; + public static OptionItem DisableLiftWeights; + public static OptionItem DisableCollectShells; + + public static OptionItem BusyLongTasks; + public static OptionItem BusyShortTasks; + + // Disable Devices + public static OptionItem DisableDevices; + private static OptionItem DisableSkeldDevices; + public static OptionItem DisableSkeldAdmin; + public static OptionItem DisableSkeldCamera; + private static OptionItem DisableMiraHQDevices; + public static OptionItem DisableMiraHQAdmin; + public static OptionItem DisableMiraHQDoorLog; + private static OptionItem DisablePolusDevices; + public static OptionItem DisablePolusAdmin; + public static OptionItem DisablePolusCamera; + public static OptionItem DisablePolusVital; + private static OptionItem DisableAirshipDevices; + public static OptionItem DisableAirshipCockpitAdmin; + public static OptionItem DisableAirshipRecordsAdmin; + public static OptionItem DisableAirshipCamera; + public static OptionItem DisableAirshipVital; + private static OptionItem DisableFungleDevices; + public static OptionItem DisableFungleCamera; + public static OptionItem DisableFungleVital; + private static OptionItem DisableDevicesIgnoreConditions; + public static OptionItem DisableDevicesIgnoreImpostors; + public static OptionItem DisableDevicesIgnoreNeutrals; + public static OptionItem DisableDevicesIgnoreCrewmates; + public static OptionItem DisableDevicesIgnoreAfterAnyoneDied; + + // Maps + public static OptionItem RandomMapsMode; + public static OptionItem RandomSpawn; + public static OptionItem AirshipAdditionalSpawn; + public static OptionItem AirshipVariableElectrical; + public static OptionItem DisableAirshipMovingPlatform; + public static OptionItem DisableSporeTriggerOnFungle; + private static OptionItem DisableZiplineOnFungle; + public static OptionItem DisableZiplineFromTop; + public static OptionItem DisableZiplineFromUnder; + public static OptionItem DisableZiplineForImps; + public static OptionItem DisableZiplineForNeutrals; + public static OptionItem DisableZiplineForCrew; + public static OptionItem ZiplineTravelTimeFromBottom; + public static OptionItem ZiplineTravelTimeFromTop; + + // Sabotage + public static OptionItem CommsCamouflage; + public static OptionItem CommsCamouflageDisableOnFungle; + public static OptionItem CommsCamouflageLimit; + public static OptionItem CommsCamouflageLimitSetChance; + public static OptionItem CommsCamouflageLimitChance; + public static OptionItem CommsCamouflageLimitSetFrequency; + public static OptionItem CommsCamouflageLimitFrequency; + public static OptionItem CommsCamouflageLimitSetMaxTimes; + public static OptionItem CommsCamouflageLimitMaxTimesPerGame; + public static OptionItem CommsCamouflageLimitMaxTimesPerRound; + public static OptionItem DisableReportWhenCC; + public static OptionItem SabotageTimeControl; + public static OptionItem SkeldReactorTimeLimit; + public static OptionItem SkeldO2TimeLimit; + public static OptionItem MiraReactorTimeLimit; + public static OptionItem MiraO2TimeLimit; + public static OptionItem PolusReactorTimeLimit; + public static OptionItem AirshipReactorTimeLimit; + public static OptionItem FungleReactorTimeLimit; + public static OptionItem FungleMushroomMixupDuration; + private static OptionItem LightsOutSpecialSettings; + public static OptionItem BlockDisturbancesToSwitches; + public static OptionItem DisableAirshipViewingDeckLightsPanel; + public static OptionItem DisableAirshipGapRoomLightsPanel; + public static OptionItem DisableAirshipCargoLightsPanel; + public static OptionItem NKWinsBySabotageIfNoImpAlive; + + // Guesser Mode + public static OptionItem GuesserMode; + public static OptionItem CrewmatesCanGuess; + public static OptionItem ImpostorsCanGuess; + public static OptionItem NeutralKillersCanGuess; + public static OptionItem PassiveNeutralsCanGuess; + public static OptionItem BetrayalAddonsCanGuess; + public static OptionItem HideGuesserCommands; + public static OptionItem CanGuessAddons; + public static OptionItem ImpCanGuessImp; + public static OptionItem CrewCanGuessCrew; + + public static OptionItem EveryoneCanVent; + public static OptionItem OverrideScientistBasedRoles; + public static OptionItem WhackAMole; + + public static OptionItem SpawnAdditionalRefugeeOnImpsDead; + public static OptionItem SpawnAdditionalRefugeeWhenNKAlive; + public static OptionItem SpawnAdditionalRefugeeMinAlivePlayers; + + public static OptionItem AprilFoolsMode; + + + // Voting Modes + public static OptionItem VoteMode; + private static OptionItem WhenSkipVote; + public static OptionItem WhenSkipVoteIgnoreFirstMeeting; + public static OptionItem WhenSkipVoteIgnoreNoDeadBody; + public static OptionItem WhenSkipVoteIgnoreEmergency; + private static OptionItem WhenNonVote; + public static OptionItem WhenTie; + + private static readonly string[] VoteModes = + [ + "Default", + "Suicide", + "SelfVote", + "Skip" + ]; + + private static readonly string[] TieModes = + [ + "TieMode.Default", + "TieMode.All", + "TieMode.Random" + ]; + + public static readonly string[] MadmateSpawnModeStrings = + [ + "MadmateSpawnMode.Assign", + "MadmateSpawnMode.FirstKill", + "MadmateSpawnMode.SelfVote" + ]; + + public static readonly string[] MadmateCountModeStrings = + [ + "MadmateCountMode.None", + "MadmateCountMode.Imp", + "MadmateCountMode.Crew" + ]; + + public static readonly string[] SidekickCountMode = + [ + "SidekickCountMode.Jackal", + "SidekickCountMode.None", + "SidekickCountMode.Original" + ]; + + public static OptionItem SyncButtonMode; + public static OptionItem SyncedButtonCount; + public static int UsedButtonCount; + + public static OptionItem AllAliveMeeting; + public static OptionItem AllAliveMeetingTime; + + public static OptionItem AdditionalEmergencyCooldown; + public static OptionItem AdditionalEmergencyCooldownThreshold; + public static OptionItem AdditionalEmergencyCooldownTime; + + public static OptionItem LadderDeath; + public static OptionItem LadderDeathChance; + + public static OptionItem FixFirstKillCooldown; + public static OptionItem StartingKillCooldown; + public static OptionItem ShieldPersonDiedFirst; + public static OptionItem GhostCanSeeOtherRoles; + public static OptionItem GhostCanSeeOtherVotes; + + public static OptionItem GhostCanSeeDeathReason; + + public static OptionItem KPDCamouflageMode; + + // Guess Restrictions // + public static OptionItem TerroristCanGuess; + public static OptionItem PhantomCanGuess; + + public static OptionItem PostLobbyCodeToEHRDiscordServer; + public static OptionItem ShowAntiBlackoutWarning; + public static OptionItem AllowConsole; + public static OptionItem NoGameEnd; + public static OptionItem DontUpdateDeadPlayers; + public static OptionItem AutoDisplayLastRoles; + public static OptionItem AutoDisplayLastAddOns; + public static OptionItem AutoDisplayKillLog; + public static OptionItem AutoDisplayLastResult; + private static OptionItem SuffixMode; + public static OptionItem HideGameSettings; + public static OptionItem FormatNameMode; + public static OptionItem DisableEmojiName; + public static OptionItem ChangeNameToRoleInfo; + public static OptionItem ShowLongInfo; + public static OptionItem SendRoleDescriptionFirstMeeting; + public static OptionItem RoleAssigningAlgorithm; + public static OptionItem EndWhenPlayerBug; + public static OptionItem RemovePetsAtDeadPlayers; + public static OptionItem KickNotJoinedPlayersRegularly; + + public static OptionItem UsePets; + public static OptionItem PetToAssignToEveryone; + public static OptionItem UseUnshiftTrigger; + public static OptionItem UseUnshiftTriggerForNKs; + public static OptionItem UsePhantomBasis; + public static OptionItem UsePhantomBasisForNKs; + public static OptionItem UseVoteCancelling; + public static OptionItem AutoKickStart; + public static OptionItem AutoKickStartAsBan; + public static OptionItem AutoKickStartTimes; + public static OptionItem AutoKickStopWords; + public static OptionItem AutoKickStopWordsAsBan; + public static OptionItem AutoKickStopWordsTimes; + public static OptionItem KickAndroidPlayer; + public static OptionItem ApplyDenyNameList; + public static OptionItem KickPlayerFriendCodeNotExist; + public static OptionItem KickLowLevelPlayer; + public static OptionItem ApplyBanList; + public static OptionItem ApplyModeratorList; + public static OptionItem ApplyVIPList; + public static OptionItem AutoWarnStopWords; + + public static OptionItem DIYGameSettings; + public static OptionItem PlayerCanSetColor; + public static OptionItem PlayerCanSetName; + public static OptionItem PlayerCanTPInAndOut; + + // Add-Ons + public static OptionItem NameDisplayAddons; + public static OptionItem AddBracketsToAddons; + public static OptionItem NoLimitAddonsNumMax; + + public static OptionItem CharmedCanBeGuessed; + public static OptionItem RecruitCanBeGuessed; + public static OptionItem ContagiousCanBeGuessed; + public static OptionItem UndeadCanBeGuessed; + public static OptionItem EgoistCanBeGuessed; + + public static OptionItem BewilderVision; + public static OptionItem SunglassesVision; + public static OptionItem MadmateSpawnMode; + public static OptionItem MadmateCountMode; + public static OptionItem SheriffCanBeMadmate; + public static OptionItem MayorCanBeMadmate; + public static OptionItem NGuesserCanBeMadmate; + public static OptionItem SnitchCanBeMadmate; + public static OptionItem JudgeCanBeMadmate; + public static OptionItem MarshallCanBeMadmate; + public static OptionItem FarseerCanBeMadmate; + public static OptionItem PresidentCanBeMadmate; + + public static OptionItem MadSnitchTasks; + public static OptionItem FlashmanSpeed; + public static OptionItem GiantSpeed; + public static OptionItem ImpEgoistVisibalToAllies; + public static OptionItem TicketsPerKill; + public static OptionItem DualVotes; + public static OptionItem ImpCanBeLoyal; + public static OptionItem CrewCanBeLoyal; + public static OptionItem MinWaitAutoStart; + public static OptionItem MaxWaitAutoStart; + public static OptionItem PlayerAutoStart; + + public static OptionItem DumpLogAfterGameEnd; + + private static readonly string[] SuffixModes = + [ + "SuffixMode.None", + "SuffixMode.Version", + "SuffixMode.Streaming", + "SuffixMode.Recording", + "SuffixMode.RoomHost", + "SuffixMode.OriginalName", + "SuffixMode.DoNotKillMe", + "SuffixMode.NoAndroidPlz", + "SuffixMode.AutoHost" + ]; + + private static readonly string[] RoleAssigningAlgorithms = + [ + "RoleAssigningAlgorithm.Default", + "RoleAssigningAlgorithm.NetRandom", + "RoleAssigningAlgorithm.HashRandom", + "RoleAssigningAlgorithm.Xorshift", + "RoleAssigningAlgorithm.MersenneTwister" + ]; + + private static readonly string[] FormatNameModes = + [ + "FormatNameModes.None", + "FormatNameModes.Color", + "FormatNameModes.Snacks" + ]; + + public static bool IsLoaded; + + public static int LoadingPercentage; + public static string MainLoadingText = string.Empty; + public static string RoleLoadingText = string.Empty; + + public static readonly Dictionary AddonCanBeSettings = []; + public static readonly Dictionary AddonGuessSettings = []; + + public static readonly HashSet SingleRoles = []; + + private static readonly string[] AddonGuessOptions = + [ + "RoleOn", + "RoleOff", + "Untouched" + ]; + + static Options() { - public enum GameStateInfo - { - ImpCount, - MadmateCount, - ConvertedCount, - NNKCount, - NKCount, - CrewCount, - RomanticState, - LoversState, - Tasks - } - - public static Dictionary GroupedOptions = []; - public static Dictionary> GroupedAddons = []; - - public static OptionItem GameMode; - - private static readonly string[] GameModes = - [ - "Standard", - "SoloKombat", - "FFA", - "MoveAndStop", - "HotPotato", - "HideAndSeek", - "Speedrun", - "CaptureTheFlag", - "NaturalDisasters", - "RoomRush", - "AllInOne" - ]; - - private static Dictionary roleCounts; - private static Dictionary roleSpawnChances; - public static Dictionary CustomRoleCounts; - public static Dictionary CustomRoleSpawnChances; - public static Dictionary CustomAdtRoleSpawnRate; - - public static readonly Dictionary FactionMinMaxSettings = []; - public static readonly Dictionary RoleSubCategoryLimits = []; - - public static readonly string[] Rates = - [ - "Rate0", - "Rate5", - "Rate10", - "Rate15", - "Rate20", - "Rate25", - "Rate30", - "Rate35", - "Rate40", - "Rate45", - "Rate50", - "Rate55", - "Rate60", - "Rate65", - "Rate70", - "Rate75", - "Rate80", - "Rate85", - "Rate90", - "Rate95", - "Rate100" - ]; - - public static readonly string[] RatesZeroOne = - [ - "RoleOff", /*"Rate10", "Rate20", "Rate30", "Rate40", "Rate50", - "Rate60", "Rate70", "Rate80", "Rate90", */ - "RoleRate" - ]; - - private static readonly string[] CheatResponsesName = - [ - "Ban", - "Kick", - "NoticeMe", - "NoticeEveryone", - "OnlyCancel" - ]; - - private static readonly string[] ConfirmEjectionsMode = - [ - "ConfirmEjections.None", - "ConfirmEjections.Team", - "ConfirmEjections.Role" - ]; - - private static readonly string[] CamouflageMode = - [ - "CamouflageMode.Default", - "CamouflageMode.Host", - "CamouflageMode.Karpe", - "CamouflageMode.Gurge44", - "CamouflageMode.TommyXL" - ]; - - public static readonly string[] PetToAssign = - [ - "pet_Bedcrab", - "pet_BredPet", - "pet_YuleGoatPet", - "pet_Bush", - "pet_Charles", - "pet_ChewiePet", - "pet_clank", - "pet_coaltonpet", - "pet_Cube", - "pet_Doggy", - "pet_Ellie", - "pet_frankendog", - "pet_D2GhostPet", - "pet_test", - "pet_GuiltySpark", - "pet_Stickmin", - "pet_HamPet", - "pet_Hamster", - "pet_Alien", - "pet_poro", - "pet_HamPet", - "pet_Lava", - "pet_Crewmate", - "pet_D2PoukaPet", - "pet_Pusheen", - "pet_Robot", - "pet_Snow", - "pet_Squig", - "pet_nuggetPet", - "pet_Charles_Red", - "pet_UFO", - "pet_D2WormPet", - "pet_RANDOM_FOR_EVERYONE" - ]; - - public static float DefaultKillCooldown = Main.NormalOptions?.KillCooldown ?? 25; - - public static Dictionary GameStateSettings = []; - public static OptionItem MinPlayersForGameStateCommand; - - public static OptionItem DisableMeeting; - public static OptionItem DisableCloseDoor; - public static OptionItem DisableSabotage; - - public static OptionItem DisableWhisperCommand; - - public static OptionItem DisableReactorOnSkeldAndMira; - public static OptionItem DisableReactorOnPolus; - public static OptionItem DisableReactorOnAirship; - public static OptionItem DisableO2; - public static OptionItem DisableComms; - public static OptionItem DisableLights; - public static OptionItem DisableMushroomMixup; - - public static OptionItem DisableTaskWin; - - public static OptionItem KillFlashDuration; - public static OptionItem EnableKillerLeftCommand; - - public static OptionItem SeeEjectedRolesInMeeting; - public static OptionItem EveryoneSeesDeathReasons; - - public static OptionItem DisableShieldAnimations; - public static OptionItem DisableShapeshiftAnimations; - public static OptionItem DisableAllShapeshiftAnimations; - public static OptionItem DisableKillAnimationOnGuess; - public static OptionItem DisableVanillaRoles; - public static OptionItem SabotageCooldownControl; - public static OptionItem SabotageCooldown; - public static OptionItem CEMode; - public static OptionItem ShowImpRemainOnEject; - public static OptionItem ShowNKRemainOnEject; - public static OptionItem ShowTeamNextToRoleNameOnEject; - public static OptionItem CheatResponses; - public static OptionItem EnableMovementChecking; - public static OptionItem LowLoadMode; - public static OptionItem DeepLowLoad; - public static OptionItem DisableVoteBan; - - - // Detailed Ejections // - public static OptionItem ConfirmEgoistOnEject; - public static OptionItem ConfirmLoversOnEject; - - public static OptionItem UniqueNeutralRevealScreen; - - - public static OptionItem NeutralRoleWinTogether; - public static OptionItem NeutralWinTogether; - public static OptionItem NeutralsKnowEachOther; - - public static OptionItem DefaultShapeshiftCooldown; - public static OptionItem DeadImpCantSabotage; - public static OptionItem ImpKnowAlliesRole; - public static OptionItem ImpKnowWhosMadmate; - public static OptionItem MadmateKnowWhosImp; - public static OptionItem MadmateKnowWhosMadmate; - - public static OptionItem MadmateHasImpostorVision; - - public static OptionItem ImpCanKillMadmate; - public static OptionItem MadmateCanKillImp; - public static OptionItem JackalCanKillSidekick; - public static OptionItem SidekickCanKillJackal; - public static OptionItem SidekickCanKillSidekick; - - public static OptionItem EGCanGuessImp; - public static OptionItem EGCanGuessAdt; - public static OptionItem EGCanGuessTime; - public static OptionItem EGTryHideMsg; - public static OptionItem ScavengerKillCooldown; - public static OptionItem ScavengerKillDuration; - public static OptionItem GGCanGuessCrew; - public static OptionItem GGCanGuessAdt; - public static OptionItem GGCanGuessTime; - public static OptionItem GGTryHideMsg; - public static OptionItem LuckeyProbability; - public static OptionItem LuckyProbability; - public static OptionItem VindicatorAdditionalVote; - public static OptionItem VindicatorHideVote; - public static OptionItem DoctorTaskCompletedBatteryCharge; - public static OptionItem TrapperBlockMoveTime; - public static OptionItem TransporterTeleportMax; - public static OptionItem CanTerroristSuicideWin; - public static OptionItem InnocentCanWinByImp; - public static OptionItem BaitNotification; - public static OptionItem DoctorVisibleToEveryone; - public static OptionItem ArsonistDouseTime; - public static OptionItem ArsonistCooldown; - public static OptionItem ArsonistKeepsGameGoing; - public static OptionItem ArsonistCanIgniteAnytime; - public static OptionItem ArsonistMinPlayersToIgnite; - public static OptionItem ArsonistMaxPlayersToIgnite; - public static OptionItem LegacyMafia; - public static OptionItem MarioVentNumWin; - public static OptionItem MarioVentCD; - public static OptionItem VeteranSkillCooldown; - public static OptionItem VeteranSkillDuration; - public static OptionItem TimeMasterSkillCooldown; - public static OptionItem TimeMasterSkillDuration; - public static OptionItem TimeMasterMaxUses; - public static OptionItem VeteranSkillMaxOfUseage; - public static OptionItem BodyguardProtectRadius; - public static OptionItem BodyguardKillsKiller; - public static OptionItem WitnessCD; - public static OptionItem WitnessTime; - public static OptionItem WitnessUsePet; - public static OptionItem DQNumOfKillsNeeded; - public static OptionItem ParanoiaNumOfUseButton; - public static OptionItem ParanoiaVentCooldown; - public static OptionItem ImpKnowCyberStarDead; - public static OptionItem NeutralKnowCyberStarDead; - public static OptionItem DemolitionistVentTime; - public static OptionItem DemolitionistKillerDiesOnMeetingCall; - public static OptionItem ExpressSpeed; - public static OptionItem ExpressSpeedDur; - public static OptionItem EveryOneKnowSuperStar; - public static OptionItem MafiaCanKillNum; - public static OptionItem ReportBaitAtAllCost; - - public static OptionItem GuesserDoesntDieOnMisguess; - - public static OptionItem RefugeeKillCD; - - public static OptionItem SkeldChance; - public static OptionItem MiraChance; - public static OptionItem PolusChance; - public static OptionItem DleksChance; - public static OptionItem AirshipChance; - public static OptionItem FungleChance; - - public static OptionItem UnderdogKillCooldown; - public static OptionItem UnderdogMaximumPlayersNeededToKill; - public static OptionItem UnderdogKillCooldownWithMorePlayersAlive; - - public static OptionItem GodfatherCancelVote; - - public static OptionItem GuardSpellTimes; - public static OptionItem CapitalismSkillCooldown; - public static OptionItem CapitalismKillCooldown; - public static OptionItem GrenadierSkillCooldown; - public static OptionItem GrenadierSkillDuration; - public static OptionItem GrenadierCauseVision; - public static OptionItem GrenadierCanAffectNeutral; - public static OptionItem GrenadierSkillMaxOfUseage; - public static OptionItem LighterVisionNormal; - public static OptionItem LighterVisionOnLightsOut; - public static OptionItem LighterSkillCooldown; - public static OptionItem LighterSkillDuration; - public static OptionItem LighterSkillMaxOfUseage; - public static OptionItem SecurityGuardSkillCooldown; - public static OptionItem SecurityGuardSkillDuration; - public static OptionItem SecurityGuardSkillMaxOfUseage; - public static OptionItem EscapeeSSCD; - public static OptionItem MinerSSCD; - public static OptionItem RevolutionistDrawTime; - public static OptionItem RevolutionistCooldown; - public static OptionItem RevolutionistDrawCount; - public static OptionItem RevolutionistKillProbability; - public static OptionItem RevolutionistVentCountDown; - public static OptionItem ShapeImperiusCurseShapeshiftDuration; - public static OptionItem ImperiusCurseShapeshiftCooldown; - public static OptionItem CrewpostorCanKillAllies; - public static OptionItem CrewpostorKnowsAllies; - public static OptionItem AlliesKnowCrewpostor; - public static OptionItem CrewpostorKillAfterTask; - public static OptionItem CrewpostorLungeKill; - public static OptionItem ObliviousBaitImmune; - public static OptionItem UnluckyTaskSuicideChance; - public static OptionItem UnluckyKillSuicideChance; - public static OptionItem UnluckyVentSuicideChance; - public static OptionItem UnluckyReportSuicideChance; - public static OptionItem UnluckySabotageSuicideChance; - public static OptionItem AsthmaticMinRedTime; - public static OptionItem AsthmaticMaxRedTime; - public static OptionItem AsthmaticMinGreenTime; - public static OptionItem AsthmaticMaxGreenTime; - public static OptionItem DiscoChangeInterval; - - public static OptionItem TruantWaitingTime; - - // RASCAL // - public static OptionItem RascalAppearAsMadmate; - - // Mare Add-on - public static OptionItem MareKillCD; - public static OptionItem MareKillCDNormally; - public static OptionItem MareHasIncreasedSpeed; - public static OptionItem MareSpeedDuringLightsOut; - - public static OptionItem AutoPlayAgain; - public static OptionItem AutoPlayAgainCountdown; - public static OptionItem AutoStartTimer; - - public static OptionItem InhibitorCD; - public static OptionItem InhibitorCDAfterMeetings; - public static OptionItem SaboteurCD; - public static OptionItem SaboteurCDAfterMeetings; - public static OptionItem PhantomCanVent; - - public static OptionItem PhantomSnatchesWin; - - public static OptionItem DiseasedCDOpt; - public static OptionItem DiseasedCDReset; - - public static OptionItem AntidoteCDOpt; - public static OptionItem AntidoteCDReset; - - public static OptionItem BaitDelayMin; - public static OptionItem BaitDelayMax; - public static OptionItem BaitDelayNotify; - public static OptionItem TorchVision; - public static OptionItem TorchAffectedByLights; - public static OptionItem TasklessCrewCanBeLazy; - public static OptionItem TaskBasedCrewCanBeLazy; - public static OptionItem DovesOfNeaceCooldown; - public static OptionItem DovesOfNeaceMaxOfUseage; - public static OptionItem killAttacker; - public static OptionItem MimicCanSeeDeadRoles; - public static OptionItem ResetDoorsEveryTurns; - public static OptionItem DoorsResetMode; - public static OptionItem ChangeDecontaminationTime; - public static OptionItem DecontaminationTimeOnMiraHQ; - public static OptionItem DecontaminationTimeOnPolus; - - public static OptionItem MafiaShapeshiftCD; - public static OptionItem MafiaShapeshiftDur; - - public static OptionItem DisableTaskWinIfAllCrewsAreDead; - public static OptionItem DisableTaskWinIfAllCrewsAreConverted; - - // Task Management - public static OptionItem DisableShortTasks; - public static OptionItem DisableCleanVent; - public static OptionItem DisableCalibrateDistributor; - public static OptionItem DisableChartCourse; - public static OptionItem DisableStabilizeSteering; - public static OptionItem DisableCleanO2Filter; - public static OptionItem DisableUnlockManifolds; - public static OptionItem DisablePrimeShields; - public static OptionItem DisableMeasureWeather; - public static OptionItem DisableBuyBeverage; - public static OptionItem DisableAssembleArtifact; - public static OptionItem DisableSortSamples; - public static OptionItem DisableProcessData; - public static OptionItem DisableRunDiagnostics; - public static OptionItem DisableRepairDrill; - public static OptionItem DisableAlignTelescope; - public static OptionItem DisableRecordTemperature; - public static OptionItem DisableFillCanisters; - public static OptionItem DisableMonitorTree; - public static OptionItem DisableStoreArtifacts; - public static OptionItem DisablePutAwayPistols; - public static OptionItem DisablePutAwayRifles; - public static OptionItem DisableMakeBurger; - public static OptionItem DisableCleanToilet; - public static OptionItem DisableDecontaminate; - public static OptionItem DisableSortRecords; - public static OptionItem DisableFixShower; - public static OptionItem DisablePickUpTowels; - public static OptionItem DisablePolishRuby; - public static OptionItem DisableDressMannequin; - public static OptionItem DisableCommonTasks; - public static OptionItem DisableSwipeCard; - public static OptionItem DisableFixWiring; - public static OptionItem DisableEnterIdCode; - public static OptionItem DisableInsertKeys; - public static OptionItem DisableScanBoardingPass; - public static OptionItem DisableLongTasks; - public static OptionItem DisableSubmitScan; - public static OptionItem DisableUnlockSafe; - public static OptionItem DisableStartReactor; - public static OptionItem DisableResetBreaker; - public static OptionItem DisableAlignEngineOutput; - public static OptionItem DisableInspectSample; - public static OptionItem DisableEmptyChute; - public static OptionItem DisableClearAsteroids; - public static OptionItem DisableWaterPlants; - public static OptionItem DisableOpenWaterways; - public static OptionItem DisableReplaceWaterJug; - public static OptionItem DisableRebootWifi; - public static OptionItem DisableDevelopPhotos; - public static OptionItem DisableRewindTapes; - public static OptionItem DisableStartFans; - public static OptionItem DisableOtherTasks; - public static OptionItem DisableUploadData; - public static OptionItem DisableEmptyGarbage; - public static OptionItem DisableFuelEngines; - public static OptionItem DisableDivertPower; - public static OptionItem DisableActivateWeatherNodes; - public static OptionItem DisableRoastMarshmallow; - public static OptionItem DisableCollectSamples; - public static OptionItem DisableReplaceParts; - public static OptionItem DisableCollectVegetables; - public static OptionItem DisableMineOres; - public static OptionItem DisableExtractFuel; - public static OptionItem DisableCatchFish; - public static OptionItem DisablePolishGem; - public static OptionItem DisableHelpCritter; - public static OptionItem DisableHoistSupplies; - public static OptionItem DisableFixAntenna; - public static OptionItem DisableBuildSandcastle; - public static OptionItem DisableCrankGenerator; - public static OptionItem DisableMonitorMushroom; - public static OptionItem DisablePlayVideoGame; - public static OptionItem DisableFindSignal; - public static OptionItem DisableThrowFisbee; - public static OptionItem DisableLiftWeights; - public static OptionItem DisableCollectShells; - - public static OptionItem BusyLongTasks; - public static OptionItem BusyShortTasks; - - // Disable Devices - public static OptionItem DisableDevices; - private static OptionItem DisableSkeldDevices; - public static OptionItem DisableSkeldAdmin; - public static OptionItem DisableSkeldCamera; - private static OptionItem DisableMiraHQDevices; - public static OptionItem DisableMiraHQAdmin; - public static OptionItem DisableMiraHQDoorLog; - private static OptionItem DisablePolusDevices; - public static OptionItem DisablePolusAdmin; - public static OptionItem DisablePolusCamera; - public static OptionItem DisablePolusVital; - private static OptionItem DisableAirshipDevices; - public static OptionItem DisableAirshipCockpitAdmin; - public static OptionItem DisableAirshipRecordsAdmin; - public static OptionItem DisableAirshipCamera; - public static OptionItem DisableAirshipVital; - private static OptionItem DisableFungleDevices; - public static OptionItem DisableFungleCamera; - public static OptionItem DisableFungleVital; - private static OptionItem DisableDevicesIgnoreConditions; - public static OptionItem DisableDevicesIgnoreImpostors; - public static OptionItem DisableDevicesIgnoreNeutrals; - public static OptionItem DisableDevicesIgnoreCrewmates; - public static OptionItem DisableDevicesIgnoreAfterAnyoneDied; - - // Maps - public static OptionItem RandomMapsMode; - public static OptionItem RandomSpawn; - public static OptionItem AirshipAdditionalSpawn; - public static OptionItem AirshipVariableElectrical; - public static OptionItem DisableAirshipMovingPlatform; - public static OptionItem DisableSporeTriggerOnFungle; - private static OptionItem DisableZiplineOnFungle; - public static OptionItem DisableZiplineFromTop; - public static OptionItem DisableZiplineFromUnder; - public static OptionItem DisableZiplineForImps; - public static OptionItem DisableZiplineForNeutrals; - public static OptionItem DisableZiplineForCrew; - public static OptionItem ZiplineTravelTimeFromBottom; - public static OptionItem ZiplineTravelTimeFromTop; - - // Sabotage - public static OptionItem CommsCamouflage; - public static OptionItem CommsCamouflageDisableOnFungle; - public static OptionItem CommsCamouflageLimit; - public static OptionItem CommsCamouflageLimitSetChance; - public static OptionItem CommsCamouflageLimitChance; - public static OptionItem CommsCamouflageLimitSetFrequency; - public static OptionItem CommsCamouflageLimitFrequency; - public static OptionItem CommsCamouflageLimitSetMaxTimes; - public static OptionItem CommsCamouflageLimitMaxTimesPerGame; - public static OptionItem CommsCamouflageLimitMaxTimesPerRound; - public static OptionItem DisableReportWhenCC; - public static OptionItem SabotageTimeControl; - public static OptionItem SkeldReactorTimeLimit; - public static OptionItem SkeldO2TimeLimit; - public static OptionItem MiraReactorTimeLimit; - public static OptionItem MiraO2TimeLimit; - public static OptionItem PolusReactorTimeLimit; - public static OptionItem AirshipReactorTimeLimit; - public static OptionItem FungleReactorTimeLimit; - public static OptionItem FungleMushroomMixupDuration; - private static OptionItem LightsOutSpecialSettings; - public static OptionItem BlockDisturbancesToSwitches; - public static OptionItem DisableAirshipViewingDeckLightsPanel; - public static OptionItem DisableAirshipGapRoomLightsPanel; - public static OptionItem DisableAirshipCargoLightsPanel; - public static OptionItem NKWinsBySabotageIfNoImpAlive; - - // Guesser Mode - public static OptionItem GuesserMode; - public static OptionItem CrewmatesCanGuess; - public static OptionItem ImpostorsCanGuess; - public static OptionItem NeutralKillersCanGuess; - public static OptionItem PassiveNeutralsCanGuess; - public static OptionItem BetrayalAddonsCanGuess; - public static OptionItem HideGuesserCommands; - public static OptionItem CanGuessAddons; - public static OptionItem ImpCanGuessImp; - public static OptionItem CrewCanGuessCrew; - - public static OptionItem EveryoneCanVent; - public static OptionItem OverrideScientistBasedRoles; - public static OptionItem WhackAMole; - - public static OptionItem SpawnAdditionalRefugeeOnImpsDead; - public static OptionItem SpawnAdditionalRefugeeWhenNKAlive; - public static OptionItem SpawnAdditionalRefugeeMinAlivePlayers; - - public static OptionItem AprilFoolsMode; - - - // Voting Modes - public static OptionItem VoteMode; - private static OptionItem WhenSkipVote; - public static OptionItem WhenSkipVoteIgnoreFirstMeeting; - public static OptionItem WhenSkipVoteIgnoreNoDeadBody; - public static OptionItem WhenSkipVoteIgnoreEmergency; - private static OptionItem WhenNonVote; - public static OptionItem WhenTie; - - private static readonly string[] VoteModes = - [ - "Default", - "Suicide", - "SelfVote", - "Skip" - ]; - - private static readonly string[] TieModes = - [ - "TieMode.Default", - "TieMode.All", - "TieMode.Random" - ]; - - public static readonly string[] MadmateSpawnModeStrings = - [ - "MadmateSpawnMode.Assign", - "MadmateSpawnMode.FirstKill", - "MadmateSpawnMode.SelfVote" - ]; - - public static readonly string[] MadmateCountModeStrings = - [ - "MadmateCountMode.None", - "MadmateCountMode.Imp", - "MadmateCountMode.Crew" - ]; - - public static readonly string[] SidekickCountMode = - [ - "SidekickCountMode.Jackal", - "SidekickCountMode.None", - "SidekickCountMode.Original" - ]; - - public static OptionItem SyncButtonMode; - public static OptionItem SyncedButtonCount; - public static int UsedButtonCount; - - public static OptionItem AllAliveMeeting; - public static OptionItem AllAliveMeetingTime; - - public static OptionItem AdditionalEmergencyCooldown; - public static OptionItem AdditionalEmergencyCooldownThreshold; - public static OptionItem AdditionalEmergencyCooldownTime; - - public static OptionItem LadderDeath; - public static OptionItem LadderDeathChance; - - public static OptionItem FixFirstKillCooldown; - public static OptionItem StartingKillCooldown; - public static OptionItem ShieldPersonDiedFirst; - public static OptionItem GhostCanSeeOtherRoles; - public static OptionItem GhostCanSeeOtherVotes; - - public static OptionItem GhostCanSeeDeathReason; - - public static OptionItem KPDCamouflageMode; - - // Guess Restrictions // - public static OptionItem TerroristCanGuess; - public static OptionItem PhantomCanGuess; - - public static OptionItem PostLobbyCodeToEHRDiscordServer; - public static OptionItem ShowAntiBlackoutWarning; - public static OptionItem AllowConsole; - public static OptionItem NoGameEnd; - public static OptionItem DontUpdateDeadPlayers; - public static OptionItem AutoDisplayLastRoles; - public static OptionItem AutoDisplayLastAddOns; - public static OptionItem AutoDisplayKillLog; - public static OptionItem AutoDisplayLastResult; - private static OptionItem SuffixMode; - public static OptionItem HideGameSettings; - public static OptionItem FormatNameMode; - public static OptionItem DisableEmojiName; - public static OptionItem ChangeNameToRoleInfo; - public static OptionItem ShowLongInfo; - public static OptionItem SendRoleDescriptionFirstMeeting; - public static OptionItem RoleAssigningAlgorithm; - public static OptionItem EndWhenPlayerBug; - public static OptionItem RemovePetsAtDeadPlayers; - public static OptionItem KickNotJoinedPlayersRegularly; - - public static OptionItem UsePets; - public static OptionItem PetToAssignToEveryone; - public static OptionItem UseUnshiftTrigger; - public static OptionItem UseUnshiftTriggerForNKs; - public static OptionItem UsePhantomBasis; - public static OptionItem UsePhantomBasisForNKs; - public static OptionItem UseVoteCancelling; - public static OptionItem AutoKickStart; - public static OptionItem AutoKickStartAsBan; - public static OptionItem AutoKickStartTimes; - public static OptionItem AutoKickStopWords; - public static OptionItem AutoKickStopWordsAsBan; - public static OptionItem AutoKickStopWordsTimes; - public static OptionItem KickAndroidPlayer; - public static OptionItem ApplyDenyNameList; - public static OptionItem KickPlayerFriendCodeNotExist; - public static OptionItem KickLowLevelPlayer; - public static OptionItem ApplyBanList; - public static OptionItem ApplyModeratorList; - public static OptionItem ApplyVIPList; - public static OptionItem AutoWarnStopWords; - - public static OptionItem DIYGameSettings; - public static OptionItem PlayerCanSetColor; - public static OptionItem PlayerCanSetName; - public static OptionItem PlayerCanTPInAndOut; - - // Add-Ons - public static OptionItem NameDisplayAddons; - public static OptionItem AddBracketsToAddons; - public static OptionItem NoLimitAddonsNumMax; - - public static OptionItem CharmedCanBeGuessed; - public static OptionItem RecruitCanBeGuessed; - public static OptionItem ContagiousCanBeGuessed; - public static OptionItem UndeadCanBeGuessed; - public static OptionItem EgoistCanBeGuessed; - - public static OptionItem BewilderVision; - public static OptionItem SunglassesVision; - public static OptionItem MadmateSpawnMode; - public static OptionItem MadmateCountMode; - public static OptionItem SheriffCanBeMadmate; - public static OptionItem MayorCanBeMadmate; - public static OptionItem NGuesserCanBeMadmate; - public static OptionItem SnitchCanBeMadmate; - public static OptionItem JudgeCanBeMadmate; - public static OptionItem MarshallCanBeMadmate; - public static OptionItem FarseerCanBeMadmate; - public static OptionItem PresidentCanBeMadmate; - - public static OptionItem MadSnitchTasks; - public static OptionItem FlashmanSpeed; - public static OptionItem GiantSpeed; - public static OptionItem ImpEgoistVisibalToAllies; - public static OptionItem TicketsPerKill; - public static OptionItem DualVotes; - public static OptionItem ImpCanBeLoyal; - public static OptionItem CrewCanBeLoyal; - public static OptionItem MinWaitAutoStart; - public static OptionItem MaxWaitAutoStart; - public static OptionItem PlayerAutoStart; - - public static OptionItem DumpLogAfterGameEnd; - - private static readonly string[] SuffixModes = - [ - "SuffixMode.None", - "SuffixMode.Version", - "SuffixMode.Streaming", - "SuffixMode.Recording", - "SuffixMode.RoomHost", - "SuffixMode.OriginalName", - "SuffixMode.DoNotKillMe", - "SuffixMode.NoAndroidPlz", - "SuffixMode.AutoHost" - ]; - - private static readonly string[] RoleAssigningAlgorithms = - [ - "RoleAssigningAlgorithm.Default", - "RoleAssigningAlgorithm.NetRandom", - "RoleAssigningAlgorithm.HashRandom", - "RoleAssigningAlgorithm.Xorshift", - "RoleAssigningAlgorithm.MersenneTwister" - ]; - - private static readonly string[] FormatNameModes = - [ - "FormatNameModes.None", - "FormatNameModes.Color", - "FormatNameModes.Snacks" - ]; - - public static bool IsLoaded; - - public static int LoadingPercentage; - public static string MainLoadingText = string.Empty; - public static string RoleLoadingText = string.Empty; - - public static readonly Dictionary AddonCanBeSettings = []; - public static readonly Dictionary AddonGuessSettings = []; - - public static readonly HashSet SingleRoles = []; - - private static readonly string[] AddonGuessOptions = - [ - "RoleOn", - "RoleOff", - "Untouched" - ]; - - static Options() - { - ResetRoleCounts(); - CustomRolesHelper.CanCheck = true; - } - - public static CustomGameMode CurrentGameMode => GameMode.GetInt() switch - { - 1 => CustomGameMode.SoloKombat, - 2 => CustomGameMode.FFA, - 3 => CustomGameMode.MoveAndStop, - 4 => CustomGameMode.HotPotato, - 5 => CustomGameMode.HideAndSeek, - 6 => CustomGameMode.Speedrun, - 7 => CustomGameMode.CaptureTheFlag, - 8 => CustomGameMode.NaturalDisasters, - 9 => CustomGameMode.RoomRush, - 10 => CustomGameMode.AllInOne, - _ => CustomGameMode.Standard - }; - - public static bool IsActiveOrIntegrated(this CustomGameMode customGameMode) => CurrentGameMode == customGameMode || (CurrentGameMode == CustomGameMode.AllInOne && AllInOneGameMode.GameModeIntegrationSettings.TryGetValue(customGameMode, out var option) && option.GetBool()); - - [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.Initialize))] - [HarmonyPostfix] - public static void OptionsLoadStart() - { - Logger.Info("Options.Load Start", "Options"); - AddSteamID.AddSteamAppIdFile(); - Utils.LoadComboInfo(); - Main.LoadRoleClasses(); - ChatCommands.LoadCommands(); - - Main.Instance.StartCoroutine(Load()); - } - - private static void PostLoadTasks() - { - Logger.Info("Options.Load End", "Options"); - GroupOptions(); - GroupAddons(); - Achievements.LoadAllData(); + ResetRoleCounts(); + CustomRolesHelper.CanCheck = true; + } + + public static CustomGameMode CurrentGameMode => GameMode.GetInt() switch + { + 1 => CustomGameMode.SoloKombat, + 2 => CustomGameMode.FFA, + 3 => CustomGameMode.MoveAndStop, + 4 => CustomGameMode.HotPotato, + 5 => CustomGameMode.HideAndSeek, + 6 => CustomGameMode.Speedrun, + 7 => CustomGameMode.CaptureTheFlag, + 8 => CustomGameMode.NaturalDisasters, + 9 => CustomGameMode.RoomRush, + 10 => CustomGameMode.AllInOne, + _ => CustomGameMode.Standard + }; + + public static bool IsActiveOrIntegrated(this CustomGameMode customGameMode) => CurrentGameMode == customGameMode || (CurrentGameMode == CustomGameMode.AllInOne && AllInOneGameMode.GameModeIntegrationSettings.TryGetValue(customGameMode, out var option) && option.GetBool()); + + [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.Initialize))] + [HarmonyPostfix] + public static void OptionsLoadStart() + { + Logger.Info("Options.Load Start", "Options"); + AddSteamID.AddSteamAppIdFile(); + Utils.LoadComboInfo(); + Main.LoadRoleClasses(); + ChatCommands.LoadCommands(); + + Main.Instance.StartCoroutine(Load()); + } + + private static void PostLoadTasks() + { + Logger.Info("Options.Load End", "Options"); + GroupOptions(); + GroupAddons(); + Achievements.LoadAllData(); #if DEBUG - // Used for generating the table of roles for the README - try + // Used for generating the table of roles for the README + try + { + var sb = new StringBuilder(); + + var grouped = Enum.GetValues().GroupBy(x => { - var sb = new StringBuilder(); + if (x is CustomRoles.GM or CustomRoles.Philantropist or CustomRoles.Konan or CustomRoles.NotAssigned or CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor or CustomRoles.Convict || x.IsForOtherGameMode() || x.IsVanilla() || x.ToString().Contains("EHR") || HnSManager.AllHnSRoles.Contains(x)) return 4; + if (x.IsAdditionRole()) return 3; + if (x.IsImpostor() || x.IsMadmate()) return 0; + if (x.IsNeutral()) return 1; + if (x.IsCrewmate()) return 2; + return 4; + }).ToDictionary(x => x.Key, x => x.ToArray()); - var grouped = Enum.GetValues().GroupBy(x => - { - if (x is CustomRoles.GM or CustomRoles.Philantropist or CustomRoles.Konan or CustomRoles.NotAssigned or CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor or CustomRoles.Convict || x.IsForOtherGameMode() || x.IsVanilla() || x.ToString().Contains("EHR") || HnSManager.AllHnSRoles.Contains(x)) return 4; - if (x.IsAdditionRole()) return 3; - if (x.IsImpostor() || x.IsMadmate()) return 0; - if (x.IsNeutral()) return 1; - if (x.IsCrewmate()) return 2; - return 4; - }).ToDictionary(x => x.Key, x => x.ToArray()); + var max = grouped.Max(x => x.Value.Length); - var max = grouped.Max(x => x.Value.Length); - - for (int i = 0; i < max; i++) - { - var cr = grouped[2].ElementAtOrDefault(i); - var crew = Translator.GetString(cr.ToString()); - var ir = grouped[0].ElementAtOrDefault(i); - var imp = Translator.GetString(ir.ToString()); - if (ir == default) imp = string.Empty; - var nr = grouped[1].ElementAtOrDefault(i); - var neu = Translator.GetString(nr.ToString()); - if (nr == default) neu = string.Empty; - var a = grouped[3].ElementAtOrDefault(i); - var add = Translator.GetString(a.ToString()); - if (a == default) add = string.Empty; - sb.AppendLine($"| {crew,17} | {imp,17} | {neu,17} | {add,17} |"); - } - - const string path = "./roles.txt"; - if (!File.Exists(path)) File.Create(path).Close(); - File.WriteAllText(path, sb.ToString()); - } - catch (Exception e) + for (int i = 0; i < max; i++) { - Utils.ThrowException(e); + var cr = grouped[2].ElementAtOrDefault(i); + var crew = Translator.GetString(cr.ToString()); + var ir = grouped[0].ElementAtOrDefault(i); + var imp = Translator.GetString(ir.ToString()); + if (ir == default) imp = string.Empty; + var nr = grouped[1].ElementAtOrDefault(i); + var neu = Translator.GetString(nr.ToString()); + if (nr == default) neu = string.Empty; + var a = grouped[3].ElementAtOrDefault(i); + var add = Translator.GetString(a.ToString()); + if (a == default) add = string.Empty; + sb.AppendLine($"| {crew,17} | {imp,17} | {neu,17} | {add,17} |"); } + + const string path = "./roles.txt"; + if (!File.Exists(path)) File.Create(path).Close(); + File.WriteAllText(path, sb.ToString()); + } + catch (Exception e) + { + Utils.ThrowException(e); + } #endif - } + } - private static void GroupOptions() + private static void GroupOptions() + { + GroupedOptions = OptionItem.AllOptions + .GroupBy(x => x.Tab) + .OrderBy(x => (int)x.Key) + .ToDictionary(x => x.Key, x => x.ToArray()); + + HnSManager.AllHnSRoles = HnSManager.GetAllHnsRoles(HnSManager.GetAllHnsRoleTypes()); + } + + private static void GroupAddons() + { + GroupedAddons = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(x => x.GetInterfaces().ToList().Contains(typeof(IAddon))) + .Select(x => (IAddon)Activator.CreateInstance(x)) + .Where(x => x != null) + .GroupBy(x => x.Type) + .ToDictionary(x => x.Key, x => x.Select(y => Enum.Parse(y.GetType().Name, true)).ToList()); + } + + public static VoteMode GetWhenSkipVote() + { + return (VoteMode)WhenSkipVote.GetValue(); + } + + public static VoteMode GetWhenNonVote() + { + return (VoteMode)WhenNonVote.GetValue(); + } + + public static SuffixModes GetSuffixMode() + { + return (SuffixModes)SuffixMode.GetValue(); + } + + private static void ResetRoleCounts() + { + roleCounts = []; + roleSpawnChances = []; + + foreach (CustomRoles role in Enum.GetValues()) { - GroupedOptions = OptionItem.AllOptions - .GroupBy(x => x.Tab) - .OrderBy(x => (int)x.Key) - .ToDictionary(x => x.Key, x => x.ToArray()); - - HnSManager.AllHnSRoles = HnSManager.GetAllHnsRoles(HnSManager.GetAllHnsRoleTypes()); + roleCounts.Add(role, 0); + roleSpawnChances.Add(role, 0); } + } - private static void GroupAddons() + public static void SetRoleCount(CustomRoles role, int count) + { + roleCounts[role] = count; + + if (CustomRoleCounts.TryGetValue(role, out OptionItem option)) option.SetValue(count - 1); + } + + public static int GetRoleSpawnMode(CustomRoles role) + { + return CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem sc) ? sc.GetChance() : 0; + } + + public static int GetRoleCount(CustomRoles role) + { + int mode = GetRoleSpawnMode(role); + return mode is 0 ? 0 : CustomRoleCounts.TryGetValue(role, out OptionItem option) ? option.GetInt() : roleCounts[role]; + } + + public static float GetRoleChance(CustomRoles role) + { + return CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem option) ? option.GetValue() /* / 10f */ : roleSpawnChances[role]; + } + + private static IEnumerator Load() + { + LoadingPercentage = 0; + MainLoadingText = "Building system settings"; + + if (IsLoaded) yield break; + + OptionSaver.Initialize(); + + yield return null; + + int defaultPresetNumber = OptionSaver.GetDefaultPresetNumber(); + + _ = new PresetOptionItem(defaultPresetNumber, TabGroup.SystemSettings) + .SetColor(new Color32(255, 235, 4, byte.MaxValue)) + .SetHidden(true); + + GameMode = new StringOptionItem(1, "GameMode", GameModes, 0, TabGroup.GameSettings) + .SetHidden(true); + + #region Settings + + CustomRoleCounts = []; + CustomRoleSpawnChances = []; + CustomAdtRoleSpawnRate = []; + + MainLoadingText = "Building general settings"; + + + ImpKnowAlliesRole = new BooleanOptionItem(150, "ImpKnowAlliesRole", true, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); + + ImpKnowWhosMadmate = new BooleanOptionItem(151, "ImpKnowWhosMadmate", false, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard); + + ImpCanKillMadmate = new BooleanOptionItem(152, "ImpCanKillMadmate", true, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard); + + MadmateKnowWhosMadmate = new BooleanOptionItem(153, "MadmateKnowWhosMadmate", false, TabGroup.ImpostorRoles) + .SetHeader(true) + .SetGameMode(CustomGameMode.Standard); + + MadmateKnowWhosImp = new BooleanOptionItem(154, "MadmateKnowWhosImp", true, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard); + + MadmateCanKillImp = new BooleanOptionItem(155, "MadmateCanKillImp", true, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard); + + MadmateHasImpostorVision = new BooleanOptionItem(156, "MadmateHasImpostorVision", true, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard); + + RefugeeKillCD = new FloatOptionItem(157, "RefugeeKillCD", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Seconds); + + DefaultShapeshiftCooldown = new FloatOptionItem(200, "DefaultShapeshiftCooldown", new(5f, 180f, 5f), 15f, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetValueFormat(OptionFormat.Seconds); + + DeadImpCantSabotage = new BooleanOptionItem(201, "DeadImpCantSabotage", false, TabGroup.ImpostorRoles) + .SetGameMode(CustomGameMode.Standard); + + NeutralRoleWinTogether = new BooleanOptionItem(208, "NeutralRoleWinTogether", false, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); + + NeutralWinTogether = new BooleanOptionItem(209, "NeutralWinTogether", false, TabGroup.NeutralRoles) + .SetParent(NeutralRoleWinTogether) + .SetGameMode(CustomGameMode.Standard); + + NeutralsKnowEachOther = new BooleanOptionItem(212, "NeutralsKnowEachOther", false, TabGroup.NeutralRoles) + .SetGameMode(CustomGameMode.Standard); + + NameDisplayAddons = new BooleanOptionItem(210, "NameDisplayAddons", true, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); + + NoLimitAddonsNumMax = new IntegerOptionItem(211, "NoLimitAddonsNumMax", new(1, 90, 1), 1, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard); + + CharmedCanBeGuessed = new StringOptionItem(213, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) + .SetHeader(true) + .SetGameMode(CustomGameMode.Standard) + .AddReplacement(("{role}", CustomRoles.Charmed.ToColoredString())); + + RecruitCanBeGuessed = new StringOptionItem(214, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .AddReplacement(("{role}", CustomRoles.Recruit.ToColoredString())); + + ContagiousCanBeGuessed = new StringOptionItem(215, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .AddReplacement(("{role}", CustomRoles.Contagious.ToColoredString())); + + UndeadCanBeGuessed = new StringOptionItem(216, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .AddReplacement(("{role}", CustomRoles.Undead.ToColoredString())); + + EgoistCanBeGuessed = new StringOptionItem(217, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .AddReplacement(("{role}", CustomRoles.Egoist.ToColoredString())); + + + RoleLoadingText = "Add-ons\n."; + + yield return null; + + + AddBracketsToAddons = new BooleanOptionItem(13500, "BracketAddons", false, TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true); + + #endregion + + #region RoleListMaker + + new TextOptionItem(100030, "MenuTitle.RoleListMaker", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(0, 165, 255, byte.MaxValue)); + + int id = 19820; + + foreach (Team team in Enum.GetValues()[1..3]) { - GroupedAddons = Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(x => x.GetInterfaces().ToList().Contains(typeof(IAddon))) - .Select(x => (IAddon)Activator.CreateInstance(x)) - .Where(x => x != null) - .GroupBy(x => x.Type) - .ToDictionary(x => x.Key, x => x.Select(y => Enum.Parse(y.GetType().Name, true)).ToList()); - } - - public static VoteMode GetWhenSkipVote() - { - return (VoteMode)WhenSkipVote.GetValue(); - } - - public static VoteMode GetWhenNonVote() - { - return (VoteMode)WhenNonVote.GetValue(); - } - - public static SuffixModes GetSuffixMode() - { - return (SuffixModes)SuffixMode.GetValue(); - } - - private static void ResetRoleCounts() - { - roleCounts = []; - roleSpawnChances = []; - - foreach (CustomRoles role in Enum.GetValues()) + (int Min, int Max) defaultNum = team switch { - roleCounts.Add(role, 0); - roleSpawnChances.Add(role, 0); + Team.Impostor => (0, 3), + Team.Neutral => (0, 5), + _ => (0, 15) + }; + + var minSetting = new IntegerOptionItem(id++, $"FactionLimits.{team}.Min", new(0, 15, 1), defaultNum.Min, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(team.GetTeamColor()); + + var maxSetting = new IntegerOptionItem(id++, $"FactionLimits.{team}.Max", new(0, 15, 1), defaultNum.Max, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(team.GetTeamColor()); + + FactionMinMaxSettings[team] = (minSetting, maxSetting); + } + + HashSet doneTabs = []; + + foreach (RoleOptionType roleOptionType in Enum.GetValues()) + { + TabGroup tab = roleOptionType.GetTabFromOptionType(); + Color roleOptionTypeColor = roleOptionType.GetRoleOptionTypeColor(); + var options = new OptionItem[3]; + + options[0] = new BooleanOptionItem(id++, $"RoleSubCategoryLimitOptions.{roleOptionType}.EnableLimit", false, tab) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(doneTabs.Add(tab)) + .SetColor(roleOptionTypeColor); + + options[1] = new IntegerOptionItem(id++, $"RoleSubCategoryLimitOptions.{roleOptionType}.Min", new(0, 15, 1), 1, tab) + .SetParent(options[0]) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Players) + .SetColor(roleOptionTypeColor); + + options[2] = new IntegerOptionItem(id++, $"RoleSubCategoryLimitOptions.{roleOptionType}.Max", new(0, 15, 1), 1, tab) + .SetParent(options[0]) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Players) + .SetColor(roleOptionTypeColor); + + RoleSubCategoryLimits[roleOptionType] = options; + } + + #endregion + + #region Roles/AddOns_Settings + + var titleId = 100100; + + LoadingPercentage = 5; + MainLoadingText = "Building Add-on Settings"; + + Type IAddonType = typeof(IAddon); + + Dictionary addonTypes = Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface) + .OrderBy(t => Translator.GetString(t.Name)) + .Select(type => (IAddon)Activator.CreateInstance(type)) + .Where(x => x != null) + .GroupBy(x => x.Type) + .ToDictionary(x => x.Key, x => x.ToArray()); + + foreach (KeyValuePair addonType in addonTypes) + { + MainLoadingText = $"Building Add-on Settings ({addonType.Key})"; + var index = 0; + + new TextOptionItem(titleId, $"ROT.AddonType.{addonType.Key}", TabGroup.Addons) + .SetGameMode(CustomGameMode.Standard) + .SetColor(addonType.Key.GetAddonTypeColor()) + .SetHeader(true); + + titleId += 10; + + foreach (IAddon addon in addonType.Value) + { + index++; + RoleLoadingText = $"{addon.GetType().Name} ({index}/{addonType.Value.Length})"; + Log(); + + addon.SetupCustomOption(); } - } - - public static void SetRoleCount(CustomRoles role, int count) - { - roleCounts[role] = count; - - if (CustomRoleCounts.TryGetValue(role, out OptionItem option)) option.SetValue(count - 1); - } - - public static int GetRoleSpawnMode(CustomRoles role) - { - return CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem sc) ? sc.GetChance() : 0; - } - - public static int GetRoleCount(CustomRoles role) - { - int mode = GetRoleSpawnMode(role); - return mode is 0 ? 0 : CustomRoleCounts.TryGetValue(role, out OptionItem option) ? option.GetInt() : roleCounts[role]; - } - - public static float GetRoleChance(CustomRoles role) - { - return CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem option) ? option.GetValue() /* / 10f */ : roleSpawnChances[role]; - } - - private static IEnumerator Load() - { - LoadingPercentage = 0; - MainLoadingText = "Building system settings"; - - if (IsLoaded) yield break; - - OptionSaver.Initialize(); yield return null; + } - int defaultPresetNumber = OptionSaver.GetDefaultPresetNumber(); + LoadingPercentage = 15; + MainLoadingText = "Building Role Settings"; - _ = new PresetOptionItem(defaultPresetNumber, TabGroup.SystemSettings) - .SetColor(new Color32(255, 235, 4, byte.MaxValue)) - .SetHidden(true); + Type IVanillaType = typeof(IVanillaSettingHolder); - GameMode = new StringOptionItem(1, "GameMode", GameModes, 0, TabGroup.GameSettings) - .SetHidden(true); - - #region Settings - - CustomRoleCounts = []; - CustomRoleSpawnChances = []; - CustomAdtRoleSpawnRate = []; - - MainLoadingText = "Building general settings"; - - - ImpKnowAlliesRole = new BooleanOptionItem(150, "ImpKnowAlliesRole", true, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true); - - ImpKnowWhosMadmate = new BooleanOptionItem(151, "ImpKnowWhosMadmate", false, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard); - - ImpCanKillMadmate = new BooleanOptionItem(152, "ImpCanKillMadmate", true, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard); - - MadmateKnowWhosMadmate = new BooleanOptionItem(153, "MadmateKnowWhosMadmate", false, TabGroup.ImpostorRoles) - .SetHeader(true) - .SetGameMode(CustomGameMode.Standard); - - MadmateKnowWhosImp = new BooleanOptionItem(154, "MadmateKnowWhosImp", true, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard); - - MadmateCanKillImp = new BooleanOptionItem(155, "MadmateCanKillImp", true, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard); - - MadmateHasImpostorVision = new BooleanOptionItem(156, "MadmateHasImpostorVision", true, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard); - - RefugeeKillCD = new FloatOptionItem(157, "RefugeeKillCD", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard) - .SetValueFormat(OptionFormat.Seconds); - - DefaultShapeshiftCooldown = new FloatOptionItem(200, "DefaultShapeshiftCooldown", new(5f, 180f, 5f), 15f, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetValueFormat(OptionFormat.Seconds); - - DeadImpCantSabotage = new BooleanOptionItem(201, "DeadImpCantSabotage", false, TabGroup.ImpostorRoles) - .SetGameMode(CustomGameMode.Standard); - - NeutralRoleWinTogether = new BooleanOptionItem(208, "NeutralRoleWinTogether", false, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true); - - NeutralWinTogether = new BooleanOptionItem(209, "NeutralWinTogether", false, TabGroup.NeutralRoles) - .SetParent(NeutralRoleWinTogether) - .SetGameMode(CustomGameMode.Standard); - - NeutralsKnowEachOther = new BooleanOptionItem(212, "NeutralsKnowEachOther", false, TabGroup.NeutralRoles) - .SetGameMode(CustomGameMode.Standard); - - NameDisplayAddons = new BooleanOptionItem(210, "NameDisplayAddons", true, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true); - - NoLimitAddonsNumMax = new IntegerOptionItem(211, "NoLimitAddonsNumMax", new(1, 90, 1), 1, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard); - - CharmedCanBeGuessed = new StringOptionItem(213, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) - .SetHeader(true) - .SetGameMode(CustomGameMode.Standard) - .AddReplacement(("{role}", CustomRoles.Charmed.ToColoredString())); - - RecruitCanBeGuessed = new StringOptionItem(214, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .AddReplacement(("{role}", CustomRoles.Recruit.ToColoredString())); - - ContagiousCanBeGuessed = new StringOptionItem(215, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .AddReplacement(("{role}", CustomRoles.Contagious.ToColoredString())); - - UndeadCanBeGuessed = new StringOptionItem(216, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .AddReplacement(("{role}", CustomRoles.Undead.ToColoredString())); - - EgoistCanBeGuessed = new StringOptionItem(217, "ConvertedAddonCanBeGuessed", AddonGuessOptions, 2, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .AddReplacement(("{role}", CustomRoles.Egoist.ToColoredString())); - - - RoleLoadingText = "Add-ons\n."; - - yield return null; - - - AddBracketsToAddons = new BooleanOptionItem(13500, "BracketAddons", false, TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true); - - #endregion - - #region RoleListMaker - - new TextOptionItem(100030, "MenuTitle.RoleListMaker", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(0, 165, 255, byte.MaxValue)); - - int id = 19820; - - foreach (Team team in Enum.GetValues()[1..3]) + Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(t => IVanillaType.IsAssignableFrom(t) && !t.IsInterface) + .OrderBy(t => Translator.GetString(t.Name)) + .Select(type => (IVanillaSettingHolder)Activator.CreateInstance(type)) + .Do(x => { - (int Min, int Max) defaultNum = team switch - { - Team.Impostor => (0, 3), - Team.Neutral => (0, 5), - _ => (0, 15) - }; - - var minSetting = new IntegerOptionItem(id++, $"FactionLimits.{team}.Min", new(0, 15, 1), defaultNum.Min, TabGroup.GameSettings) + new TextOptionItem(titleId, "ROT.Vanilla", x.Tab) .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(team.GetTeamColor()); - - var maxSetting = new IntegerOptionItem(id++, $"FactionLimits.{team}.Max", new(0, 15, 1), defaultNum.Max, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(team.GetTeamColor()); - - FactionMinMaxSettings[team] = (minSetting, maxSetting); - } - - HashSet doneTabs = []; - - foreach (RoleOptionType roleOptionType in Enum.GetValues()) - { - TabGroup tab = roleOptionType.GetTabFromOptionType(); - Color roleOptionTypeColor = roleOptionType.GetRoleOptionTypeColor(); - var options = new OptionItem[3]; - - options[0] = new BooleanOptionItem(id++, $"RoleSubCategoryLimitOptions.{roleOptionType}.EnableLimit", false, tab) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(doneTabs.Add(tab)) - .SetColor(roleOptionTypeColor); - - options[1] = new IntegerOptionItem(id++, $"RoleSubCategoryLimitOptions.{roleOptionType}.Min", new(0, 15, 1), 1, tab) - .SetParent(options[0]) - .SetGameMode(CustomGameMode.Standard) - .SetValueFormat(OptionFormat.Players) - .SetColor(roleOptionTypeColor); - - options[2] = new IntegerOptionItem(id++, $"RoleSubCategoryLimitOptions.{roleOptionType}.Max", new(0, 15, 1), 1, tab) - .SetParent(options[0]) - .SetGameMode(CustomGameMode.Standard) - .SetValueFormat(OptionFormat.Players) - .SetColor(roleOptionTypeColor); - - RoleSubCategoryLimits[roleOptionType] = options; - } - - #endregion - - #region Roles/AddOns_Settings - - var titleId = 100100; - - LoadingPercentage = 5; - MainLoadingText = "Building Add-on Settings"; - - Type IAddonType = typeof(IAddon); - - Dictionary addonTypes = Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(t => IAddonType.IsAssignableFrom(t) && !t.IsInterface) - .OrderBy(t => Translator.GetString(t.Name)) - .Select(type => (IAddon)Activator.CreateInstance(type)) - .Where(x => x != null) - .GroupBy(x => x.Type) - .ToDictionary(x => x.Key, x => x.ToArray()); - - foreach (KeyValuePair addonType in addonTypes) - { - MainLoadingText = $"Building Add-on Settings ({addonType.Key})"; - var index = 0; - - new TextOptionItem(titleId, $"ROT.AddonType.{addonType.Key}", TabGroup.Addons) - .SetGameMode(CustomGameMode.Standard) - .SetColor(addonType.Key.GetAddonTypeColor()) + .SetColor(Color.white) .SetHeader(true); titleId += 10; - foreach (IAddon addon in addonType.Value) - { - index++; - RoleLoadingText = $"{addon.GetType().Name} ({index}/{addonType.Value.Length})"; - Log(); + RoleLoadingText = x.GetType().Name; + Log(); - addon.SetupCustomOption(); + x.SetupCustomOption(); + }); + + Type IType = typeof(IGhostRole); + + Assembly + .GetExecutingAssembly() + .GetTypes() + .Where(t => IType.IsAssignableFrom(t) && !t.IsInterface) + .OrderBy(t => Translator.GetString(t.Name)) + .Select(type => (IGhostRole)Activator.CreateInstance(type)) + .Do(x => x.SetupCustomOption()); + + Dictionary roleClassesDict = Main.AllRoleClasses + .Where(x => x.GetType().Name != "VanillaRole") + .GroupBy(x => ((CustomRoles)Enum.Parse(typeof(CustomRoles), ignoreCase: true, value: x.GetType().Name)).GetRoleOptionType()) + .OrderBy(x => (int)x.Key) + .ToDictionary(x => x.Key, x => x.ToArray()); + + foreach (KeyValuePair roleClasses in roleClassesDict) + { + MainLoadingText = $"Building Role Settings: {roleClasses.Key} Roles"; + int allRoles = roleClasses.Value.Length; + var index = 0; + + TabGroup tab = roleClasses.Key.GetTabFromOptionType(); + + new TextOptionItem(titleId, $"ROT.{roleClasses.Key}", tab) + .SetHeader(true) + .SetGameMode(CustomGameMode.Standard) + .SetColor(roleClasses.Key.GetRoleOptionTypeColor()); + + titleId += 10; + + foreach (RoleBase roleClass in roleClasses.Value) + { + index++; + RoleLoadingText = $"{index}/{allRoles} ({roleClass.GetType().Name})"; + Log(); + + try + { + roleClass.SetupCustomOption(); + } + catch (Exception e) + { + Logger.Exception(e, $"{MainLoadingText} - {RoleLoadingText}"); } yield return null; } - LoadingPercentage = 15; - MainLoadingText = "Building Role Settings"; - - Type IVanillaType = typeof(IVanillaSettingHolder); - - Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(t => IVanillaType.IsAssignableFrom(t) && !t.IsInterface) - .OrderBy(t => Translator.GetString(t.Name)) - .Select(type => (IVanillaSettingHolder)Activator.CreateInstance(type)) - .Do(x => - { - new TextOptionItem(titleId, "ROT.Vanilla", x.Tab) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.white) - .SetHeader(true); - - titleId += 10; - - RoleLoadingText = x.GetType().Name; - Log(); - - x.SetupCustomOption(); - }); - - Type IType = typeof(IGhostRole); - - Assembly - .GetExecutingAssembly() - .GetTypes() - .Where(t => IType.IsAssignableFrom(t) && !t.IsInterface) - .OrderBy(t => Translator.GetString(t.Name)) - .Select(type => (IGhostRole)Activator.CreateInstance(type)) - .Do(x => x.SetupCustomOption()); - - Dictionary roleClassesDict = Main.AllRoleClasses - .Where(x => x.GetType().Name != "VanillaRole") - .GroupBy(x => ((CustomRoles)Enum.Parse(typeof(CustomRoles), ignoreCase: true, value: x.GetType().Name)).GetRoleOptionType()) - .OrderBy(x => (int)x.Key) - .ToDictionary(x => x.Key, x => x.ToArray()); - - foreach (KeyValuePair roleClasses in roleClassesDict) + if (roleClasses.Key == RoleOptionType.Impostor_Miscellaneous) { - MainLoadingText = $"Building Role Settings: {roleClasses.Key} Roles"; - int allRoles = roleClasses.Value.Length; - var index = 0; - - TabGroup tab = roleClasses.Key.GetTabFromOptionType(); - - new TextOptionItem(titleId, $"ROT.{roleClasses.Key}", tab) + new TextOptionItem(titleId, "ROT.MadMates", TabGroup.ImpostorRoles) .SetHeader(true) .SetGameMode(CustomGameMode.Standard) - .SetColor(roleClasses.Key.GetRoleOptionTypeColor()); + .SetColor(Palette.ImpostorRed); titleId += 10; - - foreach (RoleBase roleClass in roleClasses.Value) - { - index++; - RoleLoadingText = $"{index}/{allRoles} ({roleClass.GetType().Name})"; - Log(); - - try - { - roleClass.SetupCustomOption(); - } - catch (Exception e) - { - Logger.Exception(e, $"{MainLoadingText} - {RoleLoadingText}"); - } - - yield return null; - } - - if (roleClasses.Key == RoleOptionType.Impostor_Miscellaneous) - { - new TextOptionItem(titleId, "ROT.MadMates", TabGroup.ImpostorRoles) - .SetHeader(true) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Palette.ImpostorRed); - - titleId += 10; - } - - yield return null; } - void Log() - { - Logger.Info(" " + RoleLoadingText, MainLoadingText); - } - - - LoadingPercentage = 60; - - RoleLoadingText = string.Empty; - - #endregion - - #region EHRSettings - - MainLoadingText = "Building EHR settings"; - - KickLowLevelPlayer = new IntegerOptionItem(19300, "KickLowLevelPlayer", new(0, 100, 1), 0, TabGroup.SystemSettings) - .SetValueFormat(OptionFormat.Level) - .SetHeader(true); - - KickAndroidPlayer = new BooleanOptionItem(19301, "KickAndroidPlayer", false, TabGroup.SystemSettings); - KickPlayerFriendCodeNotExist = new BooleanOptionItem(19302, "KickPlayerFriendCodeNotExist", false, TabGroup.SystemSettings, true); - ApplyDenyNameList = new BooleanOptionItem(19303, "ApplyDenyNameList", true, TabGroup.SystemSettings, true); - ApplyBanList = new BooleanOptionItem(19304, "ApplyBanList", true, TabGroup.SystemSettings, true); - ApplyModeratorList = new BooleanOptionItem(19305, "ApplyModeratorList", true, TabGroup.SystemSettings); - ApplyVIPList = new BooleanOptionItem(19306, "ApplyVIPList", true, TabGroup.SystemSettings); - - LoadingPercentage = 61; - - AutoKickStart = new BooleanOptionItem(19310, "AutoKickStart", false, TabGroup.SystemSettings); - - AutoKickStartTimes = new IntegerOptionItem(19311, "AutoKickStartTimes", new(0, 90, 1), 1, TabGroup.SystemSettings) - .SetParent(AutoKickStart) - .SetValueFormat(OptionFormat.Times); - - AutoKickStartAsBan = new BooleanOptionItem(19312, "AutoKickStartAsBan", false, TabGroup.SystemSettings) - .SetParent(AutoKickStart); - - AutoKickStopWords = new BooleanOptionItem(19313, "AutoKickStopWords", false, TabGroup.SystemSettings); - - AutoKickStopWordsTimes = new IntegerOptionItem(19314, "AutoKickStopWordsTimes", new(0, 90, 1), 3, TabGroup.SystemSettings) - .SetParent(AutoKickStopWords) - .SetValueFormat(OptionFormat.Times); - - AutoKickStopWordsAsBan = new BooleanOptionItem(19315, "AutoKickStopWordsAsBan", false, TabGroup.SystemSettings) - .SetParent(AutoKickStopWords); - - LoadingPercentage = 62; - - AutoWarnStopWords = new BooleanOptionItem(19316, "AutoWarnStopWords", false, TabGroup.SystemSettings); - MinWaitAutoStart = new FloatOptionItem(44420, "MinWaitAutoStart", new(0f, 10f, 0.5f), 1.5f, TabGroup.SystemSettings); - MaxWaitAutoStart = new FloatOptionItem(44421, "MaxWaitAutoStart", new(0f, 10f, 0.5f), 1.5f, TabGroup.SystemSettings); - PlayerAutoStart = new IntegerOptionItem(44422, "PlayerAutoStart", new(1, 15, 1), 14, TabGroup.SystemSettings); - - AutoStartTimer = new IntegerOptionItem(44423, "AutoStartTimer", new(10, 600, 1), 20, TabGroup.SystemSettings) - .SetValueFormat(OptionFormat.Seconds); - - AutoPlayAgain = new BooleanOptionItem(44424, "AutoPlayAgain", false, TabGroup.SystemSettings); - - AutoPlayAgainCountdown = new IntegerOptionItem(44425, "AutoPlayAgainCountdown", new(1, 90, 1), 10, TabGroup.SystemSettings) - .SetParent(AutoPlayAgain); - - LowLoadMode = new BooleanOptionItem(19317, "LowLoadMode", true, TabGroup.SystemSettings) - .SetHeader(true) - .SetColor(Color.green); - - DeepLowLoad = new BooleanOptionItem(19325, "DeepLowLoad", false, TabGroup.SystemSettings) - .SetColor(Color.red); - - DontUpdateDeadPlayers = new BooleanOptionItem(19326, "DontUpdateDeadPlayers", true, TabGroup.SystemSettings) - .SetColor(Color.red); - - DumpLogAfterGameEnd = new BooleanOptionItem(19327, "DumpLogAfterGameEnd", true, TabGroup.SystemSettings) - .SetColor(Color.yellow); - - EndWhenPlayerBug = new BooleanOptionItem(19318, "EndWhenPlayerBug", true, TabGroup.SystemSettings) - .SetHeader(true) - .SetColor(Color.blue); - - RemovePetsAtDeadPlayers = new BooleanOptionItem(60294, "RemovePetsAtDeadPlayers", false, TabGroup.SystemSettings) - .SetColor(Color.magenta); - - KickNotJoinedPlayersRegularly = new BooleanOptionItem(60295, "KickNotJoinedPlayersRegularly", true, TabGroup.SystemSettings) - .SetColor(Color.yellow); - - CheatResponses = new StringOptionItem(19319, "CheatResponses", CheatResponsesName, 2, TabGroup.SystemSettings) - .SetHeader(true); - - EnableMovementChecking = new BooleanOptionItem(19329, "EnableMovementChecking", false, TabGroup.SystemSettings) - .SetHeader(true); - - - DisableVoteBan = new BooleanOptionItem(19320, "DisableVoteBan", true, TabGroup.SystemSettings, true); - - - LoadingPercentage = 63; - - - AutoDisplayKillLog = new BooleanOptionItem(19321, "AutoDisplayKillLog", true, TabGroup.SystemSettings) - .SetHeader(true); - - AutoDisplayLastRoles = new BooleanOptionItem(19322, "AutoDisplayLastRoles", true, TabGroup.SystemSettings); - AutoDisplayLastAddOns = new BooleanOptionItem(19328, "AutoDisplayLastAddOns", true, TabGroup.SystemSettings); - AutoDisplayLastResult = new BooleanOptionItem(19323, "AutoDisplayLastResult", true, TabGroup.SystemSettings); - - SuffixMode = new StringOptionItem(19324, "SuffixMode", SuffixModes, 0, TabGroup.SystemSettings, true) - .SetHeader(true); - - HideGameSettings = new BooleanOptionItem(19400, "HideGameSettings", false, TabGroup.SystemSettings); - DIYGameSettings = new BooleanOptionItem(19401, "DIYGameSettings", false, TabGroup.SystemSettings); - PlayerCanSetColor = new BooleanOptionItem(19402, "PlayerCanSetColor", false, TabGroup.SystemSettings); - PlayerCanSetName = new BooleanOptionItem(19410, "PlayerCanSetName", false, TabGroup.SystemSettings); - PlayerCanTPInAndOut = new BooleanOptionItem(19411, "PlayerCanTPInAndOut", false, TabGroup.SystemSettings); - FormatNameMode = new StringOptionItem(19403, "FormatNameMode", FormatNameModes, 0, TabGroup.SystemSettings); - DisableEmojiName = new BooleanOptionItem(19404, "DisableEmojiName", true, TabGroup.SystemSettings); - ChangeNameToRoleInfo = new BooleanOptionItem(19405, "ChangeNameToRoleInfo", true, TabGroup.SystemSettings); - - ShowLongInfo = new BooleanOptionItem(19420, "ShowLongInfo", true, TabGroup.SystemSettings) - .SetParent(ChangeNameToRoleInfo); - - SendRoleDescriptionFirstMeeting = new BooleanOptionItem(19406, "SendRoleDescriptionFirstMeeting", true, TabGroup.SystemSettings); - - NoGameEnd = new BooleanOptionItem(19407, "NoGameEnd", false, TabGroup.SystemSettings) - .SetColor(Color.red); - - AllowConsole = new BooleanOptionItem(19408, "AllowConsole", false, TabGroup.SystemSettings) - .SetColor(Color.red); - - ShowAntiBlackoutWarning = new BooleanOptionItem(19421, "ShowAntiBlackoutWarning", true, TabGroup.SystemSettings); - - PostLobbyCodeToEHRDiscordServer = new BooleanOptionItem(19422, "PostLobbyCodeToEHRDiscordServer", true, TabGroup.SystemSettings); - - RoleAssigningAlgorithm = new StringOptionItem(19409, "RoleAssigningAlgorithm", RoleAssigningAlgorithms, 4, TabGroup.SystemSettings, true) - .RegisterUpdateValueEvent((_, args) => IRandom.SetInstanceById(args.CurrentValue)); - - KPDCamouflageMode = new StringOptionItem(19500, "KPDCamouflageMode", CamouflageMode, 0, TabGroup.SystemSettings) - .SetHeader(true) - .SetColor(new Color32(255, 192, 203, byte.MaxValue)); - - LoadingPercentage = 64; - - #endregion - yield return null; + } - #region Gamemodes - - MainLoadingText = "Building Settings for Other Gamemodes"; - - AllInOneGameMode.SetupCustomOption(); - - // SoloKombat - SoloKombatManager.SetupCustomOption(); - // FFA - FFAManager.SetupCustomOption(); - // Move And Stop - MoveAndStop.SetupCustomOption(); - // Hot Potato - HotPotato.SetupCustomOption(); - // Speedrun - SpeedrunManager.SetupCustomOption(); - // Hide And Seek - HnSManager.SetupCustomOption(); - // Capture The Flag - CTFManager.SetupCustomOption(); - // Natural Disasters - NaturalDisasters.SetupCustomOption(); - // Room Rush - RoomRush.SetupCustomOption(); - - yield return null; + void Log() + { + Logger.Info(" " + RoleLoadingText, MainLoadingText); + } - LoadingPercentage = 65; - MainLoadingText = "Building game settings"; + LoadingPercentage = 60; - new TextOptionItem(100023, "MenuTitle.Ejections", TabGroup.GameSettings) + RoleLoadingText = string.Empty; + + #endregion + + #region EHRSettings + + MainLoadingText = "Building EHR settings"; + + KickLowLevelPlayer = new IntegerOptionItem(19300, "KickLowLevelPlayer", new(0, 100, 1), 0, TabGroup.SystemSettings) + .SetValueFormat(OptionFormat.Level) + .SetHeader(true); + + KickAndroidPlayer = new BooleanOptionItem(19301, "KickAndroidPlayer", false, TabGroup.SystemSettings); + KickPlayerFriendCodeNotExist = new BooleanOptionItem(19302, "KickPlayerFriendCodeNotExist", false, TabGroup.SystemSettings, true); + ApplyDenyNameList = new BooleanOptionItem(19303, "ApplyDenyNameList", true, TabGroup.SystemSettings, true); + ApplyBanList = new BooleanOptionItem(19304, "ApplyBanList", true, TabGroup.SystemSettings, true); + ApplyModeratorList = new BooleanOptionItem(19305, "ApplyModeratorList", true, TabGroup.SystemSettings); + ApplyVIPList = new BooleanOptionItem(19306, "ApplyVIPList", true, TabGroup.SystemSettings); + + LoadingPercentage = 61; + + AutoKickStart = new BooleanOptionItem(19310, "AutoKickStart", false, TabGroup.SystemSettings); + + AutoKickStartTimes = new IntegerOptionItem(19311, "AutoKickStartTimes", new(0, 90, 1), 1, TabGroup.SystemSettings) + .SetParent(AutoKickStart) + .SetValueFormat(OptionFormat.Times); + + AutoKickStartAsBan = new BooleanOptionItem(19312, "AutoKickStartAsBan", false, TabGroup.SystemSettings) + .SetParent(AutoKickStart); + + AutoKickStopWords = new BooleanOptionItem(19313, "AutoKickStopWords", false, TabGroup.SystemSettings); + + AutoKickStopWordsTimes = new IntegerOptionItem(19314, "AutoKickStopWordsTimes", new(0, 90, 1), 3, TabGroup.SystemSettings) + .SetParent(AutoKickStopWords) + .SetValueFormat(OptionFormat.Times); + + AutoKickStopWordsAsBan = new BooleanOptionItem(19315, "AutoKickStopWordsAsBan", false, TabGroup.SystemSettings) + .SetParent(AutoKickStopWords); + + LoadingPercentage = 62; + + AutoWarnStopWords = new BooleanOptionItem(19316, "AutoWarnStopWords", false, TabGroup.SystemSettings); + MinWaitAutoStart = new FloatOptionItem(44420, "MinWaitAutoStart", new(0f, 10f, 0.5f), 1.5f, TabGroup.SystemSettings); + MaxWaitAutoStart = new FloatOptionItem(44421, "MaxWaitAutoStart", new(0f, 10f, 0.5f), 1.5f, TabGroup.SystemSettings); + PlayerAutoStart = new IntegerOptionItem(44422, "PlayerAutoStart", new(1, 15, 1), 14, TabGroup.SystemSettings); + + AutoStartTimer = new IntegerOptionItem(44423, "AutoStartTimer", new(10, 600, 1), 20, TabGroup.SystemSettings) + .SetValueFormat(OptionFormat.Seconds); + + AutoPlayAgain = new BooleanOptionItem(44424, "AutoPlayAgain", false, TabGroup.SystemSettings); + + AutoPlayAgainCountdown = new IntegerOptionItem(44425, "AutoPlayAgainCountdown", new(1, 90, 1), 10, TabGroup.SystemSettings) + .SetParent(AutoPlayAgain); + + LowLoadMode = new BooleanOptionItem(19317, "LowLoadMode", true, TabGroup.SystemSettings) + .SetHeader(true) + .SetColor(Color.green); + + DeepLowLoad = new BooleanOptionItem(19325, "DeepLowLoad", false, TabGroup.SystemSettings) + .SetColor(Color.red); + + DontUpdateDeadPlayers = new BooleanOptionItem(19326, "DontUpdateDeadPlayers", true, TabGroup.SystemSettings) + .SetColor(Color.red); + + DumpLogAfterGameEnd = new BooleanOptionItem(19327, "DumpLogAfterGameEnd", true, TabGroup.SystemSettings) + .SetColor(Color.yellow); + + EndWhenPlayerBug = new BooleanOptionItem(19318, "EndWhenPlayerBug", true, TabGroup.SystemSettings) + .SetHeader(true) + .SetColor(Color.blue); + + RemovePetsAtDeadPlayers = new BooleanOptionItem(60294, "RemovePetsAtDeadPlayers", false, TabGroup.SystemSettings) + .SetColor(Color.magenta); + + KickNotJoinedPlayersRegularly = new BooleanOptionItem(60295, "KickNotJoinedPlayersRegularly", true, TabGroup.SystemSettings) + .SetColor(Color.yellow); + + CheatResponses = new StringOptionItem(19319, "CheatResponses", CheatResponsesName, 2, TabGroup.SystemSettings) + .SetHeader(true); + + EnableMovementChecking = new BooleanOptionItem(19329, "EnableMovementChecking", false, TabGroup.SystemSettings) + .SetHeader(true); + + + DisableVoteBan = new BooleanOptionItem(19320, "DisableVoteBan", true, TabGroup.SystemSettings, true); + + + LoadingPercentage = 63; + + + AutoDisplayKillLog = new BooleanOptionItem(19321, "AutoDisplayKillLog", true, TabGroup.SystemSettings) + .SetHeader(true); + + AutoDisplayLastRoles = new BooleanOptionItem(19322, "AutoDisplayLastRoles", true, TabGroup.SystemSettings); + AutoDisplayLastAddOns = new BooleanOptionItem(19328, "AutoDisplayLastAddOns", true, TabGroup.SystemSettings); + AutoDisplayLastResult = new BooleanOptionItem(19323, "AutoDisplayLastResult", true, TabGroup.SystemSettings); + + SuffixMode = new StringOptionItem(19324, "SuffixMode", SuffixModes, 0, TabGroup.SystemSettings, true) + .SetHeader(true); + + HideGameSettings = new BooleanOptionItem(19400, "HideGameSettings", false, TabGroup.SystemSettings); + DIYGameSettings = new BooleanOptionItem(19401, "DIYGameSettings", false, TabGroup.SystemSettings); + PlayerCanSetColor = new BooleanOptionItem(19402, "PlayerCanSetColor", false, TabGroup.SystemSettings); + PlayerCanSetName = new BooleanOptionItem(19410, "PlayerCanSetName", false, TabGroup.SystemSettings); + PlayerCanTPInAndOut = new BooleanOptionItem(19411, "PlayerCanTPInAndOut", false, TabGroup.SystemSettings); + FormatNameMode = new StringOptionItem(19403, "FormatNameMode", FormatNameModes, 0, TabGroup.SystemSettings); + DisableEmojiName = new BooleanOptionItem(19404, "DisableEmojiName", true, TabGroup.SystemSettings); + ChangeNameToRoleInfo = new BooleanOptionItem(19405, "ChangeNameToRoleInfo", true, TabGroup.SystemSettings); + + ShowLongInfo = new BooleanOptionItem(19420, "ShowLongInfo", true, TabGroup.SystemSettings) + .SetParent(ChangeNameToRoleInfo); + + SendRoleDescriptionFirstMeeting = new BooleanOptionItem(19406, "SendRoleDescriptionFirstMeeting", true, TabGroup.SystemSettings); + + NoGameEnd = new BooleanOptionItem(19407, "NoGameEnd", false, TabGroup.SystemSettings) + .SetColor(Color.red); + + AllowConsole = new BooleanOptionItem(19408, "AllowConsole", false, TabGroup.SystemSettings) + .SetColor(Color.red); + + ShowAntiBlackoutWarning = new BooleanOptionItem(19421, "ShowAntiBlackoutWarning", true, TabGroup.SystemSettings); + + PostLobbyCodeToEHRDiscordServer = new BooleanOptionItem(19422, "PostLobbyCodeToEHRDiscordServer", true, TabGroup.SystemSettings); + + RoleAssigningAlgorithm = new StringOptionItem(19409, "RoleAssigningAlgorithm", RoleAssigningAlgorithms, 4, TabGroup.SystemSettings, true) + .RegisterUpdateValueEvent((_, args) => IRandom.SetInstanceById(args.CurrentValue)); + + KPDCamouflageMode = new StringOptionItem(19500, "KPDCamouflageMode", CamouflageMode, 0, TabGroup.SystemSettings) + .SetHeader(true) + .SetColor(new Color32(255, 192, 203, byte.MaxValue)); + + LoadingPercentage = 64; + + #endregion + + yield return null; + + #region Gamemodes + + MainLoadingText = "Building Settings for Other Gamemodes"; + + AllInOneGameMode.SetupCustomOption(); + + // SoloKombat + SoloKombatManager.SetupCustomOption(); + // FFA + FFAManager.SetupCustomOption(); + // Move And Stop + MoveAndStop.SetupCustomOption(); + // Hot Potato + HotPotato.SetupCustomOption(); + // Speedrun + SpeedrunManager.SetupCustomOption(); + // Hide And Seek + HnSManager.SetupCustomOption(); + // Capture The Flag + CTFManager.SetupCustomOption(); + // Natural Disasters + NaturalDisasters.SetupCustomOption(); + // Room Rush + RoomRush.SetupCustomOption(); + + yield return null; + + + LoadingPercentage = 65; + MainLoadingText = "Building game settings"; + + new TextOptionItem(100023, "MenuTitle.Ejections", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + + CEMode = new StringOptionItem(19800, "ConfirmEjectionsMode", ConfirmEjectionsMode, 2, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + + ShowImpRemainOnEject = new BooleanOptionItem(19810, "ShowImpRemainOnEject", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + + ShowNKRemainOnEject = new BooleanOptionItem(19811, "ShowNKRemainOnEject", true, TabGroup.GameSettings) + .SetParent(ShowImpRemainOnEject) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + + LoadingPercentage = 66; + + ShowTeamNextToRoleNameOnEject = new BooleanOptionItem(19812, "ShowTeamNextToRoleNameOnEject", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + + ConfirmEgoistOnEject = new BooleanOptionItem(19813, "ConfirmEgoistOnEject", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)) + .SetHeader(true); + + ConfirmLoversOnEject = new BooleanOptionItem(19815, "ConfirmLoversOnEject", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 238, 232, byte.MaxValue)); + + LoadingPercentage = 67; + + + // Map Settings + new TextOptionItem(100024, "MenuTitle.MapsSettings", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Random Maps Mode + RandomMapsMode = new BooleanOptionItem(19900, "RandomMapsMode", false, TabGroup.GameSettings) + .SetHeader(true) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + LoadingPercentage = 68; + + SkeldChance = new IntegerOptionItem(19910, "SkeldChance", new(0, 100, 5), 0, TabGroup.GameSettings) + .SetParent(RandomMapsMode) + .SetValueFormat(OptionFormat.Percent); + + MiraChance = new IntegerOptionItem(19911, "MiraChance", new(0, 100, 5), 0, TabGroup.GameSettings) + .SetParent(RandomMapsMode) + .SetValueFormat(OptionFormat.Percent); + + PolusChance = new IntegerOptionItem(19912, "PolusChance", new(0, 100, 5), 0, TabGroup.GameSettings) + .SetParent(RandomMapsMode) + .SetValueFormat(OptionFormat.Percent); + + DleksChance = new IntegerOptionItem(19914, "DleksChance", new(0, 100, 5), 0, TabGroup.GameSettings) + .SetParent(RandomMapsMode) + .SetValueFormat(OptionFormat.Percent); + + AirshipChance = new IntegerOptionItem(19913, "AirshipChance", new(0, 100, 5), 0, TabGroup.GameSettings) + .SetParent(RandomMapsMode) + .SetValueFormat(OptionFormat.Percent); + + FungleChance = new IntegerOptionItem(19922, "FungleChance", new(0, 100, 5), 0, TabGroup.GameSettings) + .SetParent(RandomMapsMode) + .SetValueFormat(OptionFormat.Percent); + + LoadingPercentage = 69; + + + // Random Spawn + RandomSpawn = new BooleanOptionItem(22000, "RandomSpawn", false, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + AirshipAdditionalSpawn = new BooleanOptionItem(22010, "AirshipAdditionalSpawn", false, TabGroup.GameSettings) + .SetParent(RandomSpawn); + + // Airship Variable Electrical + AirshipVariableElectrical = new BooleanOptionItem(22100, "AirshipVariableElectrical", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Disable Airship Moving Platform + DisableAirshipMovingPlatform = new BooleanOptionItem(22110, "DisableAirshipMovingPlatform", false, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Disable Spore Triggers on Fungle + DisableSporeTriggerOnFungle = new BooleanOptionItem(22130, "DisableSporeTriggerOnFungle", false, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Disable Zipline On Fungle + DisableZiplineOnFungle = new BooleanOptionItem(22305, "DisableZiplineOnFungle", false, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Disable Zipline From Top + DisableZiplineFromTop = new BooleanOptionItem(22308, "DisableZiplineFromTop", false, TabGroup.GameSettings) + .SetParent(DisableZiplineOnFungle) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Disable Zipline From Under + DisableZiplineFromUnder = new BooleanOptionItem(22310, "DisableZiplineFromUnder", false, TabGroup.GameSettings) + .SetParent(DisableZiplineOnFungle) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + DisableZiplineForCrew = new BooleanOptionItem(22316, "DisableZiplineForCrew", false, TabGroup.GameSettings) + .SetParent(DisableZiplineOnFungle) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + DisableZiplineForImps = new BooleanOptionItem(22318, "DisableZiplineForImps", false, TabGroup.GameSettings) + .SetParent(DisableZiplineOnFungle) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + DisableZiplineForNeutrals = new BooleanOptionItem(22320, "DisableZiplineForNeutrals", false, TabGroup.GameSettings) + .SetParent(DisableZiplineOnFungle) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + ZiplineTravelTimeFromBottom = new FloatOptionItem(22312, "ZiplineTravelTimeFromBottom", new(0.5f, 10f, 0.5f), 4f, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + ZiplineTravelTimeFromTop = new FloatOptionItem(22314, "ZiplineTravelTimeFromTop", new(0.5f, 10f, 0.5f), 2f, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Reset Doors After Meeting + ResetDoorsEveryTurns = new BooleanOptionItem(22120, "ResetDoorsEveryTurns", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Reset Doors Mode + DoorsResetMode = new StringOptionItem(22122, "DoorsResetMode", Enum.GetNames(), 2, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard) + .SetParent(ResetDoorsEveryTurns); + + // Change decontamination time on MiraHQ/Polus + ChangeDecontaminationTime = new BooleanOptionItem(60503, "ChangeDecontaminationTime", false, TabGroup.GameSettings) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Decontamination time on MiraHQ + DecontaminationTimeOnMiraHQ = new FloatOptionItem(60504, "DecontaminationTimeOnMiraHQ", new(0.5f, 10f, 0.25f), 3f, TabGroup.GameSettings) + .SetParent(ChangeDecontaminationTime) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + // Decontamination time on Polus + DecontaminationTimeOnPolus = new FloatOptionItem(60505, "DecontaminationTimeOnPolus", new(0.5f, 10f, 0.25f), 3f, TabGroup.GameSettings) + .SetParent(ChangeDecontaminationTime) + .SetValueFormat(OptionFormat.Multiplier) + .SetColor(new Color32(19, 188, 233, byte.MaxValue)); + + + LoadingPercentage = 70; + + yield return null; + + // Sabotage + new TextOptionItem(100025, "MenuTitle.Sabotage", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)) + .SetHeader(true); + + // CommsCamouflage + CommsCamouflage = new BooleanOptionItem(22200, "CommsCamouflage", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageDisableOnFungle = new BooleanOptionItem(22202, "CommsCamouflageDisableOnFungle", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflage) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimit = new BooleanOptionItem(22203, "CommsCamouflageLimit", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflage) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitSetChance = new BooleanOptionItem(22204, "CommsCamouflageLimitSetChance", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitChance = new IntegerOptionItem(22205, "CommsCamouflageLimitChance", new(0, 100, 5), 50, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetValueFormat(OptionFormat.Percent) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitSetFrequency = new BooleanOptionItem(22206, "CommsCamouflageLimitSetFrequency", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitFrequency = new IntegerOptionItem(22207, "CommsCamouflageLimitFrequency", new(1, 10, 1), 2, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitSetMaxTimes = new BooleanOptionItem(22208, "CommsCamouflageLimitSetMaxTimes", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitMaxTimesPerGame = new IntegerOptionItem(22209, "CommsCamouflageLimitMaxTimesPerGame", new(1, 30, 1), 3, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + CommsCamouflageLimitMaxTimesPerRound = new IntegerOptionItem(22210, "CommsCamouflageLimitMaxTimesPerRound", new(1, 10, 1), 1, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(CommsCamouflageLimit) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + DisableReportWhenCC = new BooleanOptionItem(22300, "DisableReportWhenCC", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)); + + LoadingPercentage = 71; + + + // Sabotage Cooldown Control + SabotageCooldownControl = new BooleanOptionItem(22400, "SabotageCooldownControl", false, TabGroup.GameSettings) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard); + + SabotageCooldown = new FloatOptionItem(22405, "SabotageCooldown", new(1f, 60f, 1f), 30f, TabGroup.GameSettings) + .SetParent(SabotageCooldownControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + // Sabotage Duration Control + SabotageTimeControl = new BooleanOptionItem(22410, "SabotageTimeControl", false, TabGroup.GameSettings) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard); + + // The Skeld + SkeldReactorTimeLimit = new FloatOptionItem(22418, "SkeldReactorTimeLimit", new(5f, 90f, 1f), 30f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + SkeldO2TimeLimit = new FloatOptionItem(22419, "SkeldO2TimeLimit", new(5f, 90f, 1f), 30f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + // Mira HQ + MiraReactorTimeLimit = new FloatOptionItem(22422, "MiraReactorTimeLimit", new(5f, 90f, 1f), 45f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + MiraO2TimeLimit = new FloatOptionItem(22423, "MiraO2TimeLimit", new(5f, 90f, 1f), 45f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + // Polus + PolusReactorTimeLimit = new FloatOptionItem(22424, "PolusReactorTimeLimit", new(5f, 90f, 1f), 60f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + // The Airship + AirshipReactorTimeLimit = new FloatOptionItem(22425, "AirshipReactorTimeLimit", new(5f, 90f, 1f), 90f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + // The Fungle + FungleReactorTimeLimit = new FloatOptionItem(22426, "FungleReactorTimeLimit", new(5f, 90f, 1f), 60f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + FungleMushroomMixupDuration = new FloatOptionItem(22427, "FungleMushroomMixupDuration", new(5f, 90f, 1f), 10f, TabGroup.GameSettings) + .SetParent(SabotageTimeControl) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); + + LoadingPercentage = 72; + + LightsOutSpecialSettings = new BooleanOptionItem(22500, "LightsOutSpecialSettings", false, TabGroup.GameSettings) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard); + + BlockDisturbancesToSwitches = new BooleanOptionItem(60551, "BlockDisturbancesToSwitches", false, TabGroup.GameSettings) + .SetParent(LightsOutSpecialSettings) + .SetGameMode(CustomGameMode.Standard); + + DisableAirshipViewingDeckLightsPanel = new BooleanOptionItem(22510, "DisableAirshipViewingDeckLightsPanel", false, TabGroup.GameSettings) + .SetParent(LightsOutSpecialSettings) + .SetGameMode(CustomGameMode.Standard); + + DisableAirshipGapRoomLightsPanel = new BooleanOptionItem(22511, "DisableAirshipGapRoomLightsPanel", false, TabGroup.GameSettings) + .SetParent(LightsOutSpecialSettings) + .SetGameMode(CustomGameMode.Standard); + + DisableAirshipCargoLightsPanel = new BooleanOptionItem(22512, "DisableAirshipCargoLightsPanel", false, TabGroup.GameSettings) + .SetParent(LightsOutSpecialSettings) + .SetGameMode(CustomGameMode.Standard); + + NKWinsBySabotageIfNoImpAlive = new BooleanOptionItem(22520, "NKWinsBySabotageIfNoImpAlive", false, TabGroup.GameSettings) + .SetColor(new Color32(243, 96, 96, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard); + + LoadingPercentage = 73; + + + new TextOptionItem(100026, "MenuTitle.Disable", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableShieldAnimations = new BooleanOptionItem(22601, "DisableShieldAnimations", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableKillAnimationOnGuess = new BooleanOptionItem(22602, "DisableKillAnimationOnGuess", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableShapeshiftAnimations = new BooleanOptionItem(22604, "DisableShapeshiftAnimations", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableAllShapeshiftAnimations = new BooleanOptionItem(22605, "DisableAllShapeshiftAnimations", false, TabGroup.GameSettings) + .SetParent(DisableShapeshiftAnimations) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableVanillaRoles = new BooleanOptionItem(22600, "DisableVanillaRoles", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableTaskWin = new BooleanOptionItem(22650, "DisableTaskWin", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableTaskWinIfAllCrewsAreDead = new BooleanOptionItem(22651, "DisableTaskWinIfAllCrewsAreDead", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableTaskWinIfAllCrewsAreConverted = new BooleanOptionItem(22652, "DisableTaskWinIfAllCrewsAreConverted", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + LoadingPercentage = 74; + + + DisableMeeting = new BooleanOptionItem(22700, "DisableMeeting", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableSabotage = new BooleanOptionItem(22800, "DisableSabotage", false, TabGroup.GameSettings) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableReactorOnSkeldAndMira = new BooleanOptionItem(22801, "DisableReactorOnSkeldAndMira", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableReactorOnPolus = new BooleanOptionItem(22802, "DisableReactorOnPolus", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableReactorOnAirship = new BooleanOptionItem(22803, "DisableReactorOnAirship", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableO2 = new BooleanOptionItem(22804, "DisableO2", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableComms = new BooleanOptionItem(22805, "DisableComms", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableLights = new BooleanOptionItem(22806, "DisableLights", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableMushroomMixup = new BooleanOptionItem(22807, "DisableMushroomMixup", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableCloseDoor = new BooleanOptionItem(22810, "DisableCloseDoor", false, TabGroup.GameSettings) + .SetParent(DisableSabotage) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableWhisperCommand = new BooleanOptionItem(22811, "DisableWhisperCommand", false, TabGroup.GameSettings) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + LoadingPercentage = 75; + + DisableDevices = new BooleanOptionItem(22900, "DisableDevices", false, TabGroup.GameSettings) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + DisableSkeldDevices = new BooleanOptionItem(22905, "DisableSkeldDevices", false, TabGroup.GameSettings) + .SetParent(DisableDevices); + + DisableSkeldAdmin = new BooleanOptionItem(22906, "DisableSkeldAdmin", false, TabGroup.GameSettings) + .SetParent(DisableSkeldDevices); + + DisableSkeldCamera = new BooleanOptionItem(22907, "DisableSkeldCamera", false, TabGroup.GameSettings) + .SetParent(DisableSkeldDevices); + + LoadingPercentage = 76; + + DisableMiraHQDevices = new BooleanOptionItem(22908, "DisableMiraHQDevices", false, TabGroup.GameSettings) + .SetParent(DisableDevices); + + DisableMiraHQAdmin = new BooleanOptionItem(22909, "DisableMiraHQAdmin", false, TabGroup.GameSettings) + .SetParent(DisableMiraHQDevices); + + DisableMiraHQDoorLog = new BooleanOptionItem(22910, "DisableMiraHQDoorLog", false, TabGroup.GameSettings) + .SetParent(DisableMiraHQDevices); + + DisablePolusDevices = new BooleanOptionItem(22911, "DisablePolusDevices", false, TabGroup.GameSettings) + .SetParent(DisableDevices); + + DisablePolusAdmin = new BooleanOptionItem(22912, "DisablePolusAdmin", false, TabGroup.GameSettings) + .SetParent(DisablePolusDevices); + + DisablePolusCamera = new BooleanOptionItem(22913, "DisablePolusCamera", false, TabGroup.GameSettings) + .SetParent(DisablePolusDevices); + + DisablePolusVital = new BooleanOptionItem(22914, "DisablePolusVital", false, TabGroup.GameSettings) + .SetParent(DisablePolusDevices); + + DisableAirshipDevices = new BooleanOptionItem(22915, "DisableAirshipDevices", false, TabGroup.GameSettings) + .SetParent(DisableDevices); + + DisableAirshipCockpitAdmin = new BooleanOptionItem(22916, "DisableAirshipCockpitAdmin", false, TabGroup.GameSettings) + .SetParent(DisableAirshipDevices); + + LoadingPercentage = 77; + + DisableAirshipRecordsAdmin = new BooleanOptionItem(22917, "DisableAirshipRecordsAdmin", false, TabGroup.GameSettings) + .SetParent(DisableAirshipDevices); + + DisableAirshipCamera = new BooleanOptionItem(22918, "DisableAirshipCamera", false, TabGroup.GameSettings) + .SetParent(DisableAirshipDevices); + + DisableAirshipVital = new BooleanOptionItem(22919, "DisableAirshipVital", false, TabGroup.GameSettings) + .SetParent(DisableAirshipDevices); + + DisableFungleDevices = new BooleanOptionItem(22925, "DisableFungleDevices", false, TabGroup.GameSettings) + .SetParent(DisableDevices) + .SetGameMode(CustomGameMode.Standard); + + DisableFungleCamera = new BooleanOptionItem(22926, "DisableFungleCamera", false, TabGroup.GameSettings) + .SetParent(DisableFungleDevices) + .SetGameMode(CustomGameMode.Standard); + + DisableFungleVital = new BooleanOptionItem(22927, "DisableFungleVital", false, TabGroup.GameSettings) + .SetParent(DisableFungleDevices) + .SetGameMode(CustomGameMode.Standard); + + DisableDevicesIgnoreConditions = new BooleanOptionItem(22920, "IgnoreConditions", false, TabGroup.GameSettings) + .SetParent(DisableDevices); + + DisableDevicesIgnoreImpostors = new BooleanOptionItem(22921, "IgnoreImpostors", false, TabGroup.GameSettings) + .SetParent(DisableDevicesIgnoreConditions); + + DisableDevicesIgnoreNeutrals = new BooleanOptionItem(22922, "IgnoreNeutrals", false, TabGroup.GameSettings) + .SetParent(DisableDevicesIgnoreConditions); + + DisableDevicesIgnoreCrewmates = new BooleanOptionItem(22923, "IgnoreCrewmates", false, TabGroup.GameSettings) + .SetParent(DisableDevicesIgnoreConditions); + + DisableDevicesIgnoreAfterAnyoneDied = new BooleanOptionItem(22924, "IgnoreAfterAnyoneDied", false, TabGroup.GameSettings) + .SetParent(DisableDevicesIgnoreConditions) + .SetGameMode(CustomGameMode.Standard); + + LoadingPercentage = 78; + + + UsePets = new BooleanOptionItem(23850, "UsePets", false, TabGroup.TaskSettings) + .SetHeader(true) + .SetColor(new Color32(60, 0, 255, byte.MaxValue)); + + PetToAssignToEveryone = new StringOptionItem(23854, "PetToAssign", PetToAssign, 24, TabGroup.TaskSettings) + .SetParent(UsePets) + .SetColor(new Color32(60, 0, 255, byte.MaxValue)); + + UseUnshiftTrigger = new BooleanOptionItem(23871, "UseUnshiftTrigger", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(255, 44, 44, byte.MaxValue)); + + UseUnshiftTriggerForNKs = new BooleanOptionItem(23872, "UseUnshiftTriggerForNKs", false, TabGroup.TaskSettings) + .SetParent(UseUnshiftTrigger) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(255, 44, 44, byte.MaxValue)); + + UsePhantomBasis = new BooleanOptionItem(23851, "UsePhantomBasis", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(255, 255, 44, byte.MaxValue)); + + UsePhantomBasisForNKs = new BooleanOptionItem(23864, "UsePhantomBasisForNKs", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(UsePhantomBasis) + .SetColor(new Color32(255, 255, 44, byte.MaxValue)); + + UseVoteCancelling = new BooleanOptionItem(23852, "UseVoteCancelling", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(0, 65, 196, byte.MaxValue)); + + EveryoneCanVent = new BooleanOptionItem(23853, "EveryoneCanVent", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(Color.green); + + OverrideScientistBasedRoles = new BooleanOptionItem(23855, "OverrideScientistBasedRoles", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(EveryoneCanVent); + + WhackAMole = new BooleanOptionItem(23856, "WhackAMole", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetParent(EveryoneCanVent); + + SpawnAdditionalRefugeeOnImpsDead = new BooleanOptionItem(23857, "SpawnAdditionalRefugeeOnImpsDead", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.magenta) + .SetHeader(true); + + SpawnAdditionalRefugeeWhenNKAlive = new BooleanOptionItem(23858, "SpawnAdditionalRefugeeWhenNKAlive", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.magenta) + .SetParent(SpawnAdditionalRefugeeOnImpsDead); + + SpawnAdditionalRefugeeMinAlivePlayers = new IntegerOptionItem(23859, "SpawnAdditionalRefugeeMinAlivePlayers", new(1, 14, 1), 7, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.magenta) + .SetParent(SpawnAdditionalRefugeeOnImpsDead); + + AprilFoolsMode = new BooleanOptionItem(23860, "AprilFoolsMode", Main.IsAprilFools, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(255, 153, 153, byte.MaxValue)); + + + // Disable Short Tasks + DisableShortTasks = new BooleanOptionItem(23000, "DisableShortTasks", false, TabGroup.TaskSettings) + .SetHeader(true) + .SetColor(new Color32(239, 89, 175, byte.MaxValue)); + + DisableCleanVent = new BooleanOptionItem(23001, "DisableCleanVent", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableCalibrateDistributor = new BooleanOptionItem(23002, "DisableCalibrateDistributor", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableChartCourse = new BooleanOptionItem(23003, "DisableChartCourse", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + LoadingPercentage = 79; + + DisableStabilizeSteering = new BooleanOptionItem(23004, "DisableStabilizeSteering", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableCleanO2Filter = new BooleanOptionItem(23005, "DisableCleanO2Filter", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableUnlockManifolds = new BooleanOptionItem(23006, "DisableUnlockManifolds", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisablePrimeShields = new BooleanOptionItem(23007, "DisablePrimeShields", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableMeasureWeather = new BooleanOptionItem(23008, "DisableMeasureWeather", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + LoadingPercentage = 80; + + DisableBuyBeverage = new BooleanOptionItem(23009, "DisableBuyBeverage", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableAssembleArtifact = new BooleanOptionItem(23010, "DisableAssembleArtifact", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableSortSamples = new BooleanOptionItem(23011, "DisableSortSamples", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableProcessData = new BooleanOptionItem(23012, "DisableProcessData", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableRunDiagnostics = new BooleanOptionItem(23013, "DisableRunDiagnostics", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + LoadingPercentage = 81; + + DisableRepairDrill = new BooleanOptionItem(23014, "DisableRepairDrill", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableAlignTelescope = new BooleanOptionItem(23015, "DisableAlignTelescope", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableRecordTemperature = new BooleanOptionItem(23016, "DisableRecordTemperature", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableFillCanisters = new BooleanOptionItem(23017, "DisableFillCanisters", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + LoadingPercentage = 82; + + DisableMonitorTree = new BooleanOptionItem(23018, "DisableMonitorTree", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableStoreArtifacts = new BooleanOptionItem(23019, "DisableStoreArtifacts", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisablePutAwayPistols = new BooleanOptionItem(23020, "DisablePutAwayPistols", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisablePutAwayRifles = new BooleanOptionItem(23021, "DisablePutAwayRifles", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableMakeBurger = new BooleanOptionItem(23022, "DisableMakeBurger", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + LoadingPercentage = 83; + + DisableCleanToilet = new BooleanOptionItem(23023, "DisableCleanToilet", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableDecontaminate = new BooleanOptionItem(23024, "DisableDecontaminate", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableSortRecords = new BooleanOptionItem(23025, "DisableSortRecords", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableFixShower = new BooleanOptionItem(23026, "DisableFixShower", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisablePickUpTowels = new BooleanOptionItem(23027, "DisablePickUpTowels", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisablePolishRuby = new BooleanOptionItem(23028, "DisablePolishRuby", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableDressMannequin = new BooleanOptionItem(23029, "DisableDressMannequin", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableRoastMarshmallow = new BooleanOptionItem(23030, "DisableRoastMarshmallow", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableCollectSamples = new BooleanOptionItem(23031, "DisableCollectSamples", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + DisableReplaceParts = new BooleanOptionItem(23032, "DisableReplaceParts", false, TabGroup.TaskSettings) + .SetParent(DisableShortTasks); + + LoadingPercentage = 84; + + + // Disable Common Tasks + DisableCommonTasks = new BooleanOptionItem(23100, "DisableCommonTasks", false, TabGroup.TaskSettings) + .SetColor(new Color32(239, 89, 175, byte.MaxValue)); + + DisableSwipeCard = new BooleanOptionItem(23101, "DisableSwipeCardTask", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableFixWiring = new BooleanOptionItem(23102, "DisableFixWiring", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableEnterIdCode = new BooleanOptionItem(23103, "DisableEnterIdCode", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableInsertKeys = new BooleanOptionItem(23104, "DisableInsertKeys", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableScanBoardingPass = new BooleanOptionItem(23105, "DisableScanBoardingPass", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableCollectVegetables = new BooleanOptionItem(23106, "DisableCollectVegetables", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableMineOres = new BooleanOptionItem(23107, "DisableMineOres", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableExtractFuel = new BooleanOptionItem(23108, "DisableExtractFuel", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableCatchFish = new BooleanOptionItem(23109, "DisableCatchFish", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisablePolishGem = new BooleanOptionItem(23110, "DisablePolishGem", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableHelpCritter = new BooleanOptionItem(23111, "DisableHelpCritter", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + DisableHoistSupplies = new BooleanOptionItem(23112, "DisableHoistSupplies", false, TabGroup.TaskSettings) + .SetParent(DisableCommonTasks); + + LoadingPercentage = 85; + + // Disable Long Tasks + DisableLongTasks = new BooleanOptionItem(23150, "DisableLongTasks", false, TabGroup.TaskSettings) + .SetColor(new Color32(239, 89, 175, byte.MaxValue)); + + DisableSubmitScan = new BooleanOptionItem(23151, "DisableSubmitScanTask", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableUnlockSafe = new BooleanOptionItem(23152, "DisableUnlockSafeTask", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableStartReactor = new BooleanOptionItem(23153, "DisableStartReactorTask", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableResetBreaker = new BooleanOptionItem(23154, "DisableResetBreakerTask", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + LoadingPercentage = 86; + + DisableAlignEngineOutput = new BooleanOptionItem(23155, "DisableAlignEngineOutput", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableInspectSample = new BooleanOptionItem(23156, "DisableInspectSample", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableEmptyChute = new BooleanOptionItem(23157, "DisableEmptyChute", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableClearAsteroids = new BooleanOptionItem(23158, "DisableClearAsteroids", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableWaterPlants = new BooleanOptionItem(23159, "DisableWaterPlants", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableOpenWaterways = new BooleanOptionItem(23160, "DisableOpenWaterways", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + LoadingPercentage = 87; + + DisableReplaceWaterJug = new BooleanOptionItem(23161, "DisableReplaceWaterJug", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableRebootWifi = new BooleanOptionItem(23162, "DisableRebootWifi", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableDevelopPhotos = new BooleanOptionItem(23163, "DisableDevelopPhotos", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableRewindTapes = new BooleanOptionItem(23164, "DisableRewindTapes", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableStartFans = new BooleanOptionItem(23165, "DisableStartFans", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableFixAntenna = new BooleanOptionItem(23166, "DisableFixAntenna", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableBuildSandcastle = new BooleanOptionItem(23167, "DisableBuildSandcastle", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + LoadingPercentage = 88; + + DisableCrankGenerator = new BooleanOptionItem(23168, "DisableCrankGenerator", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableMonitorMushroom = new BooleanOptionItem(23169, "DisableMonitorMushroom", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisablePlayVideoGame = new BooleanOptionItem(23170, "DisablePlayVideoGame", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableFindSignal = new BooleanOptionItem(23171, "DisableFindSignal", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableThrowFisbee = new BooleanOptionItem(23172, "DisableThrowFisbee", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableLiftWeights = new BooleanOptionItem(23173, "DisableLiftWeights", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + DisableCollectShells = new BooleanOptionItem(23174, "DisableCollectShells", false, TabGroup.TaskSettings) + .SetParent(DisableLongTasks); + + LoadingPercentage = 89; + + + // Disable Divert Power, Weather Nodes etc. situational Tasks + DisableOtherTasks = new BooleanOptionItem(23200, "DisableOtherTasks", false, TabGroup.TaskSettings) + .SetColor(new Color32(239, 89, 175, byte.MaxValue)); + + DisableUploadData = new BooleanOptionItem(23205, "DisableUploadDataTask", false, TabGroup.TaskSettings) + .SetParent(DisableOtherTasks); + + DisableEmptyGarbage = new BooleanOptionItem(23206, "DisableEmptyGarbage", false, TabGroup.TaskSettings) + .SetParent(DisableOtherTasks); + + DisableFuelEngines = new BooleanOptionItem(23207, "DisableFuelEngines", false, TabGroup.TaskSettings) + .SetParent(DisableOtherTasks); + + DisableDivertPower = new BooleanOptionItem(23208, "DisableDivertPower", false, TabGroup.TaskSettings) + .SetParent(DisableOtherTasks); + + DisableActivateWeatherNodes = new BooleanOptionItem(23209, "DisableActivateWeatherNodes", false, TabGroup.TaskSettings) + .SetParent(DisableOtherTasks); + + LoadingPercentage = 90; + MainLoadingText = "Building Guesser Mode settings"; + + yield return null; + + new TextOptionItem(100022, "MenuTitle.Guessers", TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.yellow) + .SetHeader(true); + + GuesserMode = new BooleanOptionItem(19700, "GuesserMode", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(Color.yellow) + .SetHeader(true); + + CrewmatesCanGuess = new BooleanOptionItem(19710, "CrewmatesCanGuess", false, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + ImpostorsCanGuess = new BooleanOptionItem(19711, "ImpostorsCanGuess", false, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + LoadingPercentage = 91; + + NeutralKillersCanGuess = new BooleanOptionItem(19712, "NeutralKillersCanGuess", false, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + PassiveNeutralsCanGuess = new BooleanOptionItem(19713, "PassiveNeutralsCanGuess", false, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + BetrayalAddonsCanGuess = new BooleanOptionItem(19719, "BetrayalAddonsCanGuess", false, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + CanGuessAddons = new BooleanOptionItem(19714, "CanGuessAddons", true, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + CrewCanGuessCrew = new BooleanOptionItem(19715, "CrewCanGuessCrew", true, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + ImpCanGuessImp = new BooleanOptionItem(19716, "ImpCanGuessImp", true, TabGroup.TaskSettings) + .SetParent(GuesserMode); + + HideGuesserCommands = new BooleanOptionItem(19717, "GuesserTryHideMsg", true, TabGroup.TaskSettings) + .SetParent(GuesserMode) + .SetColor(Color.green); + + GuesserDoesntDieOnMisguess = new BooleanOptionItem(19718, "GuesserDoesntDieOnMisguess", false, TabGroup.TaskSettings) + .SetGameMode(CustomGameMode.Standard); + + LoadingPercentage = 92; + MainLoadingText = "Building game settings"; + + new TextOptionItem(100037, "MenuTitle.Meeting", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); + + SyncButtonMode = new BooleanOptionItem(23300, "SyncButtonMode", false, TabGroup.GameSettings) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)) + .SetHeader(true) + .SetGameMode(CustomGameMode.Standard); + + SyncedButtonCount = new IntegerOptionItem(23310, "SyncedButtonCount", new(0, 100, 1), 10, TabGroup.GameSettings) + .SetParent(SyncButtonMode) + .SetValueFormat(OptionFormat.Times) + .SetGameMode(CustomGameMode.Standard); + + LoadingPercentage = 93; + + AllAliveMeeting = new BooleanOptionItem(23400, "AllAliveMeeting", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); + + AllAliveMeetingTime = new FloatOptionItem(23410, "AllAliveMeetingTime", new(1f, 300f, 1f), 10f, TabGroup.GameSettings) + .SetParent(AllAliveMeeting) + .SetValueFormat(OptionFormat.Seconds); + + EnableKillerLeftCommand = new BooleanOptionItem(44428, "EnableKillerLeftCommand", true, TabGroup.GameSettings) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); + + var i = 0; + + foreach (GameStateInfo s in Enum.GetValues()) + { + GameStateSettings[s] = new BooleanOptionItem(44429 + i, $"GameStateCommand.Show{s}", true, TabGroup.GameSettings) .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)); - - CEMode = new StringOptionItem(19800, "ConfirmEjectionsMode", ConfirmEjectionsMode, 2, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)); - - ShowImpRemainOnEject = new BooleanOptionItem(19810, "ShowImpRemainOnEject", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)); - - ShowNKRemainOnEject = new BooleanOptionItem(19811, "ShowNKRemainOnEject", true, TabGroup.GameSettings) - .SetParent(ShowImpRemainOnEject) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)); - - LoadingPercentage = 66; - - ShowTeamNextToRoleNameOnEject = new BooleanOptionItem(19812, "ShowTeamNextToRoleNameOnEject", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)); - - ConfirmEgoistOnEject = new BooleanOptionItem(19813, "ConfirmEgoistOnEject", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)) - .SetHeader(true); - - ConfirmLoversOnEject = new BooleanOptionItem(19815, "ConfirmLoversOnEject", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 238, 232, byte.MaxValue)); - - LoadingPercentage = 67; - - - // Map Settings - new TextOptionItem(100024, "MenuTitle.MapsSettings", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Random Maps Mode - RandomMapsMode = new BooleanOptionItem(19900, "RandomMapsMode", false, TabGroup.GameSettings) - .SetHeader(true) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - LoadingPercentage = 68; - - SkeldChance = new IntegerOptionItem(19910, "SkeldChance", new(0, 100, 5), 0, TabGroup.GameSettings) - .SetParent(RandomMapsMode) - .SetValueFormat(OptionFormat.Percent); - - MiraChance = new IntegerOptionItem(19911, "MiraChance", new(0, 100, 5), 0, TabGroup.GameSettings) - .SetParent(RandomMapsMode) - .SetValueFormat(OptionFormat.Percent); - - PolusChance = new IntegerOptionItem(19912, "PolusChance", new(0, 100, 5), 0, TabGroup.GameSettings) - .SetParent(RandomMapsMode) - .SetValueFormat(OptionFormat.Percent); - - DleksChance = new IntegerOptionItem(19914, "DleksChance", new(0, 100, 5), 0, TabGroup.GameSettings) - .SetParent(RandomMapsMode) - .SetValueFormat(OptionFormat.Percent); - - AirshipChance = new IntegerOptionItem(19913, "AirshipChance", new(0, 100, 5), 0, TabGroup.GameSettings) - .SetParent(RandomMapsMode) - .SetValueFormat(OptionFormat.Percent); - - FungleChance = new IntegerOptionItem(19922, "FungleChance", new(0, 100, 5), 0, TabGroup.GameSettings) - .SetParent(RandomMapsMode) - .SetValueFormat(OptionFormat.Percent); - - LoadingPercentage = 69; - - - // Random Spawn - RandomSpawn = new BooleanOptionItem(22000, "RandomSpawn", false, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - AirshipAdditionalSpawn = new BooleanOptionItem(22010, "AirshipAdditionalSpawn", false, TabGroup.GameSettings) - .SetParent(RandomSpawn); - - // Airship Variable Electrical - AirshipVariableElectrical = new BooleanOptionItem(22100, "AirshipVariableElectrical", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Disable Airship Moving Platform - DisableAirshipMovingPlatform = new BooleanOptionItem(22110, "DisableAirshipMovingPlatform", false, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Disable Spore Triggers on Fungle - DisableSporeTriggerOnFungle = new BooleanOptionItem(22130, "DisableSporeTriggerOnFungle", false, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Disable Zipline On Fungle - DisableZiplineOnFungle = new BooleanOptionItem(22305, "DisableZiplineOnFungle", false, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Disable Zipline From Top - DisableZiplineFromTop = new BooleanOptionItem(22308, "DisableZiplineFromTop", false, TabGroup.GameSettings) - .SetParent(DisableZiplineOnFungle) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Disable Zipline From Under - DisableZiplineFromUnder = new BooleanOptionItem(22310, "DisableZiplineFromUnder", false, TabGroup.GameSettings) - .SetParent(DisableZiplineOnFungle) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - DisableZiplineForCrew = new BooleanOptionItem(22316, "DisableZiplineForCrew", false, TabGroup.GameSettings) - .SetParent(DisableZiplineOnFungle) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - DisableZiplineForImps = new BooleanOptionItem(22318, "DisableZiplineForImps", false, TabGroup.GameSettings) - .SetParent(DisableZiplineOnFungle) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - DisableZiplineForNeutrals = new BooleanOptionItem(22320, "DisableZiplineForNeutrals", false, TabGroup.GameSettings) - .SetParent(DisableZiplineOnFungle) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - ZiplineTravelTimeFromBottom = new FloatOptionItem(22312, "ZiplineTravelTimeFromBottom", new(0.5f, 10f, 0.5f), 4f, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - ZiplineTravelTimeFromTop = new FloatOptionItem(22314, "ZiplineTravelTimeFromTop", new(0.5f, 10f, 0.5f), 2f, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Reset Doors After Meeting - ResetDoorsEveryTurns = new BooleanOptionItem(22120, "ResetDoorsEveryTurns", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Reset Doors Mode - DoorsResetMode = new StringOptionItem(22122, "DoorsResetMode", Enum.GetNames(), 2, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)) - .SetGameMode(CustomGameMode.Standard) - .SetParent(ResetDoorsEveryTurns); - - // Change decontamination time on MiraHQ/Polus - ChangeDecontaminationTime = new BooleanOptionItem(60503, "ChangeDecontaminationTime", false, TabGroup.GameSettings) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Decontamination time on MiraHQ - DecontaminationTimeOnMiraHQ = new FloatOptionItem(60504, "DecontaminationTimeOnMiraHQ", new(0.5f, 10f, 0.25f), 3f, TabGroup.GameSettings) - .SetParent(ChangeDecontaminationTime) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - // Decontamination time on Polus - DecontaminationTimeOnPolus = new FloatOptionItem(60505, "DecontaminationTimeOnPolus", new(0.5f, 10f, 0.25f), 3f, TabGroup.GameSettings) - .SetParent(ChangeDecontaminationTime) - .SetValueFormat(OptionFormat.Multiplier) - .SetColor(new Color32(19, 188, 233, byte.MaxValue)); - - - LoadingPercentage = 70; - - yield return null; - - // Sabotage - new TextOptionItem(100025, "MenuTitle.Sabotage", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)) - .SetHeader(true); - - // CommsCamouflage - CommsCamouflage = new BooleanOptionItem(22200, "CommsCamouflage", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageDisableOnFungle = new BooleanOptionItem(22202, "CommsCamouflageDisableOnFungle", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflage) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimit = new BooleanOptionItem(22203, "CommsCamouflageLimit", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflage) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitSetChance = new BooleanOptionItem(22204, "CommsCamouflageLimitSetChance", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitChance = new IntegerOptionItem(22205, "CommsCamouflageLimitChance", new(0, 100, 5), 50, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetValueFormat(OptionFormat.Percent) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitSetFrequency = new BooleanOptionItem(22206, "CommsCamouflageLimitSetFrequency", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitFrequency = new IntegerOptionItem(22207, "CommsCamouflageLimitFrequency", new(1, 10, 1), 2, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitSetMaxTimes = new BooleanOptionItem(22208, "CommsCamouflageLimitSetMaxTimes", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitMaxTimesPerGame = new IntegerOptionItem(22209, "CommsCamouflageLimitMaxTimesPerGame", new(1, 30, 1), 3, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - CommsCamouflageLimitMaxTimesPerRound = new IntegerOptionItem(22210, "CommsCamouflageLimitMaxTimesPerRound", new(1, 10, 1), 1, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(CommsCamouflageLimit) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - DisableReportWhenCC = new BooleanOptionItem(22300, "DisableReportWhenCC", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)); - - LoadingPercentage = 71; - - - // Sabotage Cooldown Control - SabotageCooldownControl = new BooleanOptionItem(22400, "SabotageCooldownControl", false, TabGroup.GameSettings) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)) - .SetGameMode(CustomGameMode.Standard); - - SabotageCooldown = new FloatOptionItem(22405, "SabotageCooldown", new(1f, 60f, 1f), 30f, TabGroup.GameSettings) - .SetParent(SabotageCooldownControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - // Sabotage Duration Control - SabotageTimeControl = new BooleanOptionItem(22410, "SabotageTimeControl", false, TabGroup.GameSettings) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)) - .SetGameMode(CustomGameMode.Standard); - - // The Skeld - SkeldReactorTimeLimit = new FloatOptionItem(22418, "SkeldReactorTimeLimit", new(5f, 90f, 1f), 30f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - SkeldO2TimeLimit = new FloatOptionItem(22419, "SkeldO2TimeLimit", new(5f, 90f, 1f), 30f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - // Mira HQ - MiraReactorTimeLimit = new FloatOptionItem(22422, "MiraReactorTimeLimit", new(5f, 90f, 1f), 45f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - MiraO2TimeLimit = new FloatOptionItem(22423, "MiraO2TimeLimit", new(5f, 90f, 1f), 45f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - // Polus - PolusReactorTimeLimit = new FloatOptionItem(22424, "PolusReactorTimeLimit", new(5f, 90f, 1f), 60f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - // The Airship - AirshipReactorTimeLimit = new FloatOptionItem(22425, "AirshipReactorTimeLimit", new(5f, 90f, 1f), 90f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - // The Fungle - FungleReactorTimeLimit = new FloatOptionItem(22426, "FungleReactorTimeLimit", new(5f, 90f, 1f), 60f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - FungleMushroomMixupDuration = new FloatOptionItem(22427, "FungleMushroomMixupDuration", new(5f, 90f, 1f), 10f, TabGroup.GameSettings) - .SetParent(SabotageTimeControl) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - LoadingPercentage = 72; - - LightsOutSpecialSettings = new BooleanOptionItem(22500, "LightsOutSpecialSettings", false, TabGroup.GameSettings) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)) - .SetGameMode(CustomGameMode.Standard); - - BlockDisturbancesToSwitches = new BooleanOptionItem(60551, "BlockDisturbancesToSwitches", false, TabGroup.GameSettings) - .SetParent(LightsOutSpecialSettings) - .SetGameMode(CustomGameMode.Standard); - - DisableAirshipViewingDeckLightsPanel = new BooleanOptionItem(22510, "DisableAirshipViewingDeckLightsPanel", false, TabGroup.GameSettings) - .SetParent(LightsOutSpecialSettings) - .SetGameMode(CustomGameMode.Standard); - - DisableAirshipGapRoomLightsPanel = new BooleanOptionItem(22511, "DisableAirshipGapRoomLightsPanel", false, TabGroup.GameSettings) - .SetParent(LightsOutSpecialSettings) - .SetGameMode(CustomGameMode.Standard); - - DisableAirshipCargoLightsPanel = new BooleanOptionItem(22512, "DisableAirshipCargoLightsPanel", false, TabGroup.GameSettings) - .SetParent(LightsOutSpecialSettings) - .SetGameMode(CustomGameMode.Standard); - - NKWinsBySabotageIfNoImpAlive = new BooleanOptionItem(22520, "NKWinsBySabotageIfNoImpAlive", false, TabGroup.GameSettings) - .SetColor(new Color32(243, 96, 96, byte.MaxValue)) - .SetGameMode(CustomGameMode.Standard); - - LoadingPercentage = 73; - - - new TextOptionItem(100026, "MenuTitle.Disable", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableShieldAnimations = new BooleanOptionItem(22601, "DisableShieldAnimations", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableKillAnimationOnGuess = new BooleanOptionItem(22602, "DisableKillAnimationOnGuess", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableShapeshiftAnimations = new BooleanOptionItem(22604, "DisableShapeshiftAnimations", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableAllShapeshiftAnimations = new BooleanOptionItem(22605, "DisableAllShapeshiftAnimations", false, TabGroup.GameSettings) - .SetParent(DisableShapeshiftAnimations) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableVanillaRoles = new BooleanOptionItem(22600, "DisableVanillaRoles", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableTaskWin = new BooleanOptionItem(22650, "DisableTaskWin", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableTaskWinIfAllCrewsAreDead = new BooleanOptionItem(22651, "DisableTaskWinIfAllCrewsAreDead", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableTaskWinIfAllCrewsAreConverted = new BooleanOptionItem(22652, "DisableTaskWinIfAllCrewsAreConverted", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - LoadingPercentage = 74; - - - DisableMeeting = new BooleanOptionItem(22700, "DisableMeeting", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableSabotage = new BooleanOptionItem(22800, "DisableSabotage", false, TabGroup.GameSettings) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableReactorOnSkeldAndMira = new BooleanOptionItem(22801, "DisableReactorOnSkeldAndMira", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableReactorOnPolus = new BooleanOptionItem(22802, "DisableReactorOnPolus", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableReactorOnAirship = new BooleanOptionItem(22803, "DisableReactorOnAirship", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableO2 = new BooleanOptionItem(22804, "DisableO2", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableComms = new BooleanOptionItem(22805, "DisableComms", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableLights = new BooleanOptionItem(22806, "DisableLights", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableMushroomMixup = new BooleanOptionItem(22807, "DisableMushroomMixup", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableCloseDoor = new BooleanOptionItem(22810, "DisableCloseDoor", false, TabGroup.GameSettings) - .SetParent(DisableSabotage) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableWhisperCommand = new BooleanOptionItem(22811, "DisableWhisperCommand", false, TabGroup.GameSettings) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - LoadingPercentage = 75; - - DisableDevices = new BooleanOptionItem(22900, "DisableDevices", false, TabGroup.GameSettings) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - DisableSkeldDevices = new BooleanOptionItem(22905, "DisableSkeldDevices", false, TabGroup.GameSettings) - .SetParent(DisableDevices); - - DisableSkeldAdmin = new BooleanOptionItem(22906, "DisableSkeldAdmin", false, TabGroup.GameSettings) - .SetParent(DisableSkeldDevices); - - DisableSkeldCamera = new BooleanOptionItem(22907, "DisableSkeldCamera", false, TabGroup.GameSettings) - .SetParent(DisableSkeldDevices); - - LoadingPercentage = 76; - - DisableMiraHQDevices = new BooleanOptionItem(22908, "DisableMiraHQDevices", false, TabGroup.GameSettings) - .SetParent(DisableDevices); - - DisableMiraHQAdmin = new BooleanOptionItem(22909, "DisableMiraHQAdmin", false, TabGroup.GameSettings) - .SetParent(DisableMiraHQDevices); - - DisableMiraHQDoorLog = new BooleanOptionItem(22910, "DisableMiraHQDoorLog", false, TabGroup.GameSettings) - .SetParent(DisableMiraHQDevices); - - DisablePolusDevices = new BooleanOptionItem(22911, "DisablePolusDevices", false, TabGroup.GameSettings) - .SetParent(DisableDevices); - - DisablePolusAdmin = new BooleanOptionItem(22912, "DisablePolusAdmin", false, TabGroup.GameSettings) - .SetParent(DisablePolusDevices); - - DisablePolusCamera = new BooleanOptionItem(22913, "DisablePolusCamera", false, TabGroup.GameSettings) - .SetParent(DisablePolusDevices); - - DisablePolusVital = new BooleanOptionItem(22914, "DisablePolusVital", false, TabGroup.GameSettings) - .SetParent(DisablePolusDevices); - - DisableAirshipDevices = new BooleanOptionItem(22915, "DisableAirshipDevices", false, TabGroup.GameSettings) - .SetParent(DisableDevices); - - DisableAirshipCockpitAdmin = new BooleanOptionItem(22916, "DisableAirshipCockpitAdmin", false, TabGroup.GameSettings) - .SetParent(DisableAirshipDevices); - - LoadingPercentage = 77; - - DisableAirshipRecordsAdmin = new BooleanOptionItem(22917, "DisableAirshipRecordsAdmin", false, TabGroup.GameSettings) - .SetParent(DisableAirshipDevices); - - DisableAirshipCamera = new BooleanOptionItem(22918, "DisableAirshipCamera", false, TabGroup.GameSettings) - .SetParent(DisableAirshipDevices); - - DisableAirshipVital = new BooleanOptionItem(22919, "DisableAirshipVital", false, TabGroup.GameSettings) - .SetParent(DisableAirshipDevices); - - DisableFungleDevices = new BooleanOptionItem(22925, "DisableFungleDevices", false, TabGroup.GameSettings) - .SetParent(DisableDevices) - .SetGameMode(CustomGameMode.Standard); - - DisableFungleCamera = new BooleanOptionItem(22926, "DisableFungleCamera", false, TabGroup.GameSettings) - .SetParent(DisableFungleDevices) - .SetGameMode(CustomGameMode.Standard); - - DisableFungleVital = new BooleanOptionItem(22927, "DisableFungleVital", false, TabGroup.GameSettings) - .SetParent(DisableFungleDevices) - .SetGameMode(CustomGameMode.Standard); - - DisableDevicesIgnoreConditions = new BooleanOptionItem(22920, "IgnoreConditions", false, TabGroup.GameSettings) - .SetParent(DisableDevices); - - DisableDevicesIgnoreImpostors = new BooleanOptionItem(22921, "IgnoreImpostors", false, TabGroup.GameSettings) - .SetParent(DisableDevicesIgnoreConditions); - - DisableDevicesIgnoreNeutrals = new BooleanOptionItem(22922, "IgnoreNeutrals", false, TabGroup.GameSettings) - .SetParent(DisableDevicesIgnoreConditions); - - DisableDevicesIgnoreCrewmates = new BooleanOptionItem(22923, "IgnoreCrewmates", false, TabGroup.GameSettings) - .SetParent(DisableDevicesIgnoreConditions); - - DisableDevicesIgnoreAfterAnyoneDied = new BooleanOptionItem(22924, "IgnoreAfterAnyoneDied", false, TabGroup.GameSettings) - .SetParent(DisableDevicesIgnoreConditions) - .SetGameMode(CustomGameMode.Standard); - - LoadingPercentage = 78; - - - UsePets = new BooleanOptionItem(23850, "UsePets", false, TabGroup.TaskSettings) - .SetHeader(true) - .SetColor(new Color32(60, 0, 255, byte.MaxValue)); - - PetToAssignToEveryone = new StringOptionItem(23854, "PetToAssign", PetToAssign, 24, TabGroup.TaskSettings) - .SetParent(UsePets) - .SetColor(new Color32(60, 0, 255, byte.MaxValue)); - - UseUnshiftTrigger = new BooleanOptionItem(23871, "UseUnshiftTrigger", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(255, 44, 44, byte.MaxValue)); - - UseUnshiftTriggerForNKs = new BooleanOptionItem(23872, "UseUnshiftTriggerForNKs", false, TabGroup.TaskSettings) - .SetParent(UseUnshiftTrigger) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(255, 44, 44, byte.MaxValue)); - - UsePhantomBasis = new BooleanOptionItem(23851, "UsePhantomBasis", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(255, 255, 44, byte.MaxValue)); - - UsePhantomBasisForNKs = new BooleanOptionItem(23864, "UsePhantomBasisForNKs", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(UsePhantomBasis) - .SetColor(new Color32(255, 255, 44, byte.MaxValue)); - - UseVoteCancelling = new BooleanOptionItem(23852, "UseVoteCancelling", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(0, 65, 196, byte.MaxValue)); - - EveryoneCanVent = new BooleanOptionItem(23853, "EveryoneCanVent", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(Color.green); - - OverrideScientistBasedRoles = new BooleanOptionItem(23855, "OverrideScientistBasedRoles", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(EveryoneCanVent); - - WhackAMole = new BooleanOptionItem(23856, "WhackAMole", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(EveryoneCanVent); - - SpawnAdditionalRefugeeOnImpsDead = new BooleanOptionItem(23857, "SpawnAdditionalRefugeeOnImpsDead", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.magenta) - .SetHeader(true); - - SpawnAdditionalRefugeeWhenNKAlive = new BooleanOptionItem(23858, "SpawnAdditionalRefugeeWhenNKAlive", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.magenta) - .SetParent(SpawnAdditionalRefugeeOnImpsDead); - - SpawnAdditionalRefugeeMinAlivePlayers = new IntegerOptionItem(23859, "SpawnAdditionalRefugeeMinAlivePlayers", new(1, 14, 1), 7, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.magenta) - .SetParent(SpawnAdditionalRefugeeOnImpsDead); - - AprilFoolsMode = new BooleanOptionItem(23860, "AprilFoolsMode", Main.IsAprilFools, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(255, 153, 153, byte.MaxValue)); - - - // Disable Short Tasks - DisableShortTasks = new BooleanOptionItem(23000, "DisableShortTasks", false, TabGroup.TaskSettings) - .SetHeader(true) - .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - - DisableCleanVent = new BooleanOptionItem(23001, "DisableCleanVent", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableCalibrateDistributor = new BooleanOptionItem(23002, "DisableCalibrateDistributor", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableChartCourse = new BooleanOptionItem(23003, "DisableChartCourse", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - LoadingPercentage = 79; - - DisableStabilizeSteering = new BooleanOptionItem(23004, "DisableStabilizeSteering", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableCleanO2Filter = new BooleanOptionItem(23005, "DisableCleanO2Filter", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableUnlockManifolds = new BooleanOptionItem(23006, "DisableUnlockManifolds", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisablePrimeShields = new BooleanOptionItem(23007, "DisablePrimeShields", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableMeasureWeather = new BooleanOptionItem(23008, "DisableMeasureWeather", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - LoadingPercentage = 80; - - DisableBuyBeverage = new BooleanOptionItem(23009, "DisableBuyBeverage", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableAssembleArtifact = new BooleanOptionItem(23010, "DisableAssembleArtifact", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableSortSamples = new BooleanOptionItem(23011, "DisableSortSamples", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableProcessData = new BooleanOptionItem(23012, "DisableProcessData", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableRunDiagnostics = new BooleanOptionItem(23013, "DisableRunDiagnostics", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - LoadingPercentage = 81; - - DisableRepairDrill = new BooleanOptionItem(23014, "DisableRepairDrill", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableAlignTelescope = new BooleanOptionItem(23015, "DisableAlignTelescope", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableRecordTemperature = new BooleanOptionItem(23016, "DisableRecordTemperature", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableFillCanisters = new BooleanOptionItem(23017, "DisableFillCanisters", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - LoadingPercentage = 82; - - DisableMonitorTree = new BooleanOptionItem(23018, "DisableMonitorTree", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableStoreArtifacts = new BooleanOptionItem(23019, "DisableStoreArtifacts", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisablePutAwayPistols = new BooleanOptionItem(23020, "DisablePutAwayPistols", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisablePutAwayRifles = new BooleanOptionItem(23021, "DisablePutAwayRifles", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableMakeBurger = new BooleanOptionItem(23022, "DisableMakeBurger", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - LoadingPercentage = 83; - - DisableCleanToilet = new BooleanOptionItem(23023, "DisableCleanToilet", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableDecontaminate = new BooleanOptionItem(23024, "DisableDecontaminate", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableSortRecords = new BooleanOptionItem(23025, "DisableSortRecords", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableFixShower = new BooleanOptionItem(23026, "DisableFixShower", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisablePickUpTowels = new BooleanOptionItem(23027, "DisablePickUpTowels", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisablePolishRuby = new BooleanOptionItem(23028, "DisablePolishRuby", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableDressMannequin = new BooleanOptionItem(23029, "DisableDressMannequin", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableRoastMarshmallow = new BooleanOptionItem(23030, "DisableRoastMarshmallow", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableCollectSamples = new BooleanOptionItem(23031, "DisableCollectSamples", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - DisableReplaceParts = new BooleanOptionItem(23032, "DisableReplaceParts", false, TabGroup.TaskSettings) - .SetParent(DisableShortTasks); - - LoadingPercentage = 84; - - - // Disable Common Tasks - DisableCommonTasks = new BooleanOptionItem(23100, "DisableCommonTasks", false, TabGroup.TaskSettings) - .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - - DisableSwipeCard = new BooleanOptionItem(23101, "DisableSwipeCardTask", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableFixWiring = new BooleanOptionItem(23102, "DisableFixWiring", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableEnterIdCode = new BooleanOptionItem(23103, "DisableEnterIdCode", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableInsertKeys = new BooleanOptionItem(23104, "DisableInsertKeys", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableScanBoardingPass = new BooleanOptionItem(23105, "DisableScanBoardingPass", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableCollectVegetables = new BooleanOptionItem(23106, "DisableCollectVegetables", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableMineOres = new BooleanOptionItem(23107, "DisableMineOres", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableExtractFuel = new BooleanOptionItem(23108, "DisableExtractFuel", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableCatchFish = new BooleanOptionItem(23109, "DisableCatchFish", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisablePolishGem = new BooleanOptionItem(23110, "DisablePolishGem", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableHelpCritter = new BooleanOptionItem(23111, "DisableHelpCritter", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - DisableHoistSupplies = new BooleanOptionItem(23112, "DisableHoistSupplies", false, TabGroup.TaskSettings) - .SetParent(DisableCommonTasks); - - LoadingPercentage = 85; - - // Disable Long Tasks - DisableLongTasks = new BooleanOptionItem(23150, "DisableLongTasks", false, TabGroup.TaskSettings) - .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - - DisableSubmitScan = new BooleanOptionItem(23151, "DisableSubmitScanTask", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableUnlockSafe = new BooleanOptionItem(23152, "DisableUnlockSafeTask", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableStartReactor = new BooleanOptionItem(23153, "DisableStartReactorTask", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableResetBreaker = new BooleanOptionItem(23154, "DisableResetBreakerTask", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - LoadingPercentage = 86; - - DisableAlignEngineOutput = new BooleanOptionItem(23155, "DisableAlignEngineOutput", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableInspectSample = new BooleanOptionItem(23156, "DisableInspectSample", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableEmptyChute = new BooleanOptionItem(23157, "DisableEmptyChute", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableClearAsteroids = new BooleanOptionItem(23158, "DisableClearAsteroids", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableWaterPlants = new BooleanOptionItem(23159, "DisableWaterPlants", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableOpenWaterways = new BooleanOptionItem(23160, "DisableOpenWaterways", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - LoadingPercentage = 87; - - DisableReplaceWaterJug = new BooleanOptionItem(23161, "DisableReplaceWaterJug", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableRebootWifi = new BooleanOptionItem(23162, "DisableRebootWifi", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableDevelopPhotos = new BooleanOptionItem(23163, "DisableDevelopPhotos", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableRewindTapes = new BooleanOptionItem(23164, "DisableRewindTapes", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableStartFans = new BooleanOptionItem(23165, "DisableStartFans", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableFixAntenna = new BooleanOptionItem(23166, "DisableFixAntenna", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableBuildSandcastle = new BooleanOptionItem(23167, "DisableBuildSandcastle", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - LoadingPercentage = 88; - - DisableCrankGenerator = new BooleanOptionItem(23168, "DisableCrankGenerator", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableMonitorMushroom = new BooleanOptionItem(23169, "DisableMonitorMushroom", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisablePlayVideoGame = new BooleanOptionItem(23170, "DisablePlayVideoGame", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableFindSignal = new BooleanOptionItem(23171, "DisableFindSignal", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableThrowFisbee = new BooleanOptionItem(23172, "DisableThrowFisbee", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableLiftWeights = new BooleanOptionItem(23173, "DisableLiftWeights", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - DisableCollectShells = new BooleanOptionItem(23174, "DisableCollectShells", false, TabGroup.TaskSettings) - .SetParent(DisableLongTasks); - - LoadingPercentage = 89; - - - // Disable Divert Power, Weather Nodes etc. situational Tasks - DisableOtherTasks = new BooleanOptionItem(23200, "DisableOtherTasks", false, TabGroup.TaskSettings) - .SetColor(new Color32(239, 89, 175, byte.MaxValue)); - - DisableUploadData = new BooleanOptionItem(23205, "DisableUploadDataTask", false, TabGroup.TaskSettings) - .SetParent(DisableOtherTasks); - - DisableEmptyGarbage = new BooleanOptionItem(23206, "DisableEmptyGarbage", false, TabGroup.TaskSettings) - .SetParent(DisableOtherTasks); - - DisableFuelEngines = new BooleanOptionItem(23207, "DisableFuelEngines", false, TabGroup.TaskSettings) - .SetParent(DisableOtherTasks); - - DisableDivertPower = new BooleanOptionItem(23208, "DisableDivertPower", false, TabGroup.TaskSettings) - .SetParent(DisableOtherTasks); - - DisableActivateWeatherNodes = new BooleanOptionItem(23209, "DisableActivateWeatherNodes", false, TabGroup.TaskSettings) - .SetParent(DisableOtherTasks); - - LoadingPercentage = 90; - MainLoadingText = "Building Guesser Mode settings"; - - yield return null; - - new TextOptionItem(100022, "MenuTitle.Guessers", TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.yellow) - .SetHeader(true); - - GuesserMode = new BooleanOptionItem(19700, "GuesserMode", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(Color.yellow) - .SetHeader(true); - - CrewmatesCanGuess = new BooleanOptionItem(19710, "CrewmatesCanGuess", false, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - ImpostorsCanGuess = new BooleanOptionItem(19711, "ImpostorsCanGuess", false, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - LoadingPercentage = 91; - - NeutralKillersCanGuess = new BooleanOptionItem(19712, "NeutralKillersCanGuess", false, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - PassiveNeutralsCanGuess = new BooleanOptionItem(19713, "PassiveNeutralsCanGuess", false, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - BetrayalAddonsCanGuess = new BooleanOptionItem(19719, "BetrayalAddonsCanGuess", false, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - CanGuessAddons = new BooleanOptionItem(19714, "CanGuessAddons", true, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - CrewCanGuessCrew = new BooleanOptionItem(19715, "CrewCanGuessCrew", true, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - ImpCanGuessImp = new BooleanOptionItem(19716, "ImpCanGuessImp", true, TabGroup.TaskSettings) - .SetParent(GuesserMode); - - HideGuesserCommands = new BooleanOptionItem(19717, "GuesserTryHideMsg", true, TabGroup.TaskSettings) - .SetParent(GuesserMode) - .SetColor(Color.green); - - GuesserDoesntDieOnMisguess = new BooleanOptionItem(19718, "GuesserDoesntDieOnMisguess", false, TabGroup.TaskSettings) - .SetGameMode(CustomGameMode.Standard); - - LoadingPercentage = 92; - MainLoadingText = "Building game settings"; - - new TextOptionItem(100037, "MenuTitle.Meeting", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - SyncButtonMode = new BooleanOptionItem(23300, "SyncButtonMode", false, TabGroup.GameSettings) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)) - .SetHeader(true) - .SetGameMode(CustomGameMode.Standard); - - SyncedButtonCount = new IntegerOptionItem(23310, "SyncedButtonCount", new(0, 100, 1), 10, TabGroup.GameSettings) - .SetParent(SyncButtonMode) - .SetValueFormat(OptionFormat.Times) - .SetGameMode(CustomGameMode.Standard); - - LoadingPercentage = 93; - - AllAliveMeeting = new BooleanOptionItem(23400, "AllAliveMeeting", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - AllAliveMeetingTime = new FloatOptionItem(23410, "AllAliveMeetingTime", new(1f, 300f, 1f), 10f, TabGroup.GameSettings) - .SetParent(AllAliveMeeting) - .SetValueFormat(OptionFormat.Seconds); - - EnableKillerLeftCommand = new BooleanOptionItem(44428, "EnableKillerLeftCommand", true, TabGroup.GameSettings) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - var i = 0; - - foreach (GameStateInfo s in Enum.GetValues()) - { - GameStateSettings[s] = new BooleanOptionItem(44429 + i, $"GameStateCommand.Show{s}", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetParent(EnableKillerLeftCommand) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - i++; - } - - MinPlayersForGameStateCommand = new IntegerOptionItem(44438, "MinPlayersForGameStateCommand", new(1, 15, 1), 1, TabGroup.GameSettings) .SetParent(EnableKillerLeftCommand) - .SetGameMode(CustomGameMode.Standard) - .SetValueFormat(OptionFormat.Players) .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - SeeEjectedRolesInMeeting = new BooleanOptionItem(44439, "SeeEjectedRolesInMeeting", true, TabGroup.GameSettings) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - EveryoneSeesDeathReasons = new BooleanOptionItem(44440, "EveryoneSeesDeathReasons", false, TabGroup.GameSettings) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - LoadingPercentage = 94; - - - AdditionalEmergencyCooldown = new BooleanOptionItem(23500, "AdditionalEmergencyCooldown", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - - AdditionalEmergencyCooldownThreshold = new IntegerOptionItem(23510, "AdditionalEmergencyCooldownThreshold", new(1, 15, 1), 1, TabGroup.GameSettings) - .SetParent(AdditionalEmergencyCooldown) - .SetGameMode(CustomGameMode.Standard) - .SetValueFormat(OptionFormat.Players); - - AdditionalEmergencyCooldownTime = new FloatOptionItem(23511, "AdditionalEmergencyCooldownTime", new(1f, 60f, 1f), 1f, TabGroup.GameSettings) - .SetParent(AdditionalEmergencyCooldown) - .SetGameMode(CustomGameMode.Standard) - .SetValueFormat(OptionFormat.Seconds); - - LoadingPercentage = 95; - - VoteMode = new BooleanOptionItem(23600, "VoteMode", false, TabGroup.GameSettings) - .SetColor(new Color32(147, 241, 240, byte.MaxValue)) - .SetGameMode(CustomGameMode.Standard); - - WhenSkipVote = new StringOptionItem(23610, "WhenSkipVote", VoteModes[..3], 0, TabGroup.GameSettings) - .SetParent(VoteMode) - .SetGameMode(CustomGameMode.Standard); - - WhenSkipVoteIgnoreFirstMeeting = new BooleanOptionItem(23611, "WhenSkipVoteIgnoreFirstMeeting", false, TabGroup.GameSettings) - .SetParent(WhenSkipVote) - .SetGameMode(CustomGameMode.Standard); - - WhenSkipVoteIgnoreNoDeadBody = new BooleanOptionItem(23612, "WhenSkipVoteIgnoreNoDeadBody", false, TabGroup.GameSettings) - .SetParent(WhenSkipVote) - .SetGameMode(CustomGameMode.Standard); - - WhenSkipVoteIgnoreEmergency = new BooleanOptionItem(23613, "WhenSkipVoteIgnoreEmergency", false, TabGroup.GameSettings) - .SetParent(WhenSkipVote) - .SetGameMode(CustomGameMode.Standard); - - WhenNonVote = new StringOptionItem(23700, "WhenNonVote", VoteModes, 0, TabGroup.GameSettings) - .SetParent(VoteMode) - .SetGameMode(CustomGameMode.Standard); - - WhenTie = new StringOptionItem(23750, "WhenTie", TieModes, 0, TabGroup.GameSettings) - .SetParent(VoteMode) - .SetGameMode(CustomGameMode.Standard); - - LoadingPercentage = 96; - - - new TextOptionItem(100028, "MenuTitle.Other", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - - LadderDeath = new BooleanOptionItem(23800, "LadderDeath", false, TabGroup.GameSettings) - .SetHeader(true) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - - LadderDeathChance = new StringOptionItem(23810, "LadderDeathChance", Rates[1..], 0, TabGroup.GameSettings) - .SetParent(LadderDeath); - - LoadingPercentage = 97; - - - FixFirstKillCooldown = new BooleanOptionItem(23900, "FixFirstKillCooldown", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - - StartingKillCooldown = new FloatOptionItem(23950, "StartingKillCooldown", new(1, 60, 1), 18, TabGroup.GameSettings) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds); - - ShieldPersonDiedFirst = new BooleanOptionItem(24000, "ShieldPersonDiedFirst", false, TabGroup.GameSettings) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - - LoadingPercentage = 98; - - - KillFlashDuration = new FloatOptionItem(24100, "KillFlashDuration", new(0.1f, 0.45f, 0.05f), 0.3f, TabGroup.GameSettings) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(CustomGameMode.Standard); - - UniqueNeutralRevealScreen = new BooleanOptionItem(24450, "UniqueNeutralRevealScreen", false, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - - - new TextOptionItem(100029, "MenuTitle.Ghost", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - - LoadingPercentage = 99; - - - GhostCanSeeOtherRoles = new BooleanOptionItem(24300, "GhostCanSeeOtherRoles", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - - GhostCanSeeOtherVotes = new BooleanOptionItem(24400, "GhostCanSeeOtherVotes", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - - GhostCanSeeDeathReason = new BooleanOptionItem(24500, "GhostCanSeeDeathReason", true, TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - - yield return null; - - - AFKDetector.SetupCustomOption(); - - - new TextOptionItem(100027, "MenuTitle.CTA", TabGroup.GameSettings) - .SetGameMode(CustomGameMode.Standard) - .SetHeader(true) - .SetColor(new Color32(215, 227, 84, byte.MaxValue)); - - CustomTeamManager.LoadCustomTeams(); - - LoadingPercentage = 100; - - #endregion - - yield return null; - - OptionSaver.Load(); - - IsLoaded = true; - - PostLoadTasks(); + i++; } - public static void SetupRoleOptions(int id, TabGroup tab, CustomRoles role, CustomGameMode customGameMode = CustomGameMode.Standard, bool zeroOne = false) - { - var spawnOption = new StringOptionItem(id, role.ToString(), zeroOne ? RatesZeroOne : Rates, 0, tab).SetColor(Utils.GetRoleColor(role)) - .SetHeader(true) - .SetGameMode(customGameMode) as StringOptionItem; + MinPlayersForGameStateCommand = new IntegerOptionItem(44438, "MinPlayersForGameStateCommand", new(1, 15, 1), 1, TabGroup.GameSettings) + .SetParent(EnableKillerLeftCommand) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Players) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - CustomRoleSpawnChances.Add(role, spawnOption); + SeeEjectedRolesInMeeting = new BooleanOptionItem(44439, "SeeEjectedRolesInMeeting", true, TabGroup.GameSettings) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - OptionItem countOption = new IntegerOptionItem(id + 1, "Maximum", new(1, 15, 1), 1, tab) - .SetParent(spawnOption) - .SetValueFormat(OptionFormat.Players) - .SetGameMode(customGameMode); + EveryoneSeesDeathReasons = new BooleanOptionItem(44440, "EveryoneSeesDeathReasons", false, TabGroup.GameSettings) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - CustomRoleCounts.Add(role, countOption); - } + LoadingPercentage = 94; - public static void SetupAdtRoleOptions(int id, CustomRoles role, CustomGameMode customGameMode = CustomGameMode.Standard, bool canSetNum = false, TabGroup tab = TabGroup.Addons, bool canSetChance = true, bool teamSpawnOptions = false, bool allowZeroCount = false) - { - var spawnOption = new StringOptionItem(id, role.ToString(), RatesZeroOne, 0, tab).SetColor(Utils.GetRoleColor(role)) - .SetHeader(true) - .SetGameMode(customGameMode) as StringOptionItem; - OptionItem countOption = new IntegerOptionItem(id + 1, "Maximum", new(allowZeroCount ? 0 : 1, canSetNum ? 15 : 1, 1), 1, tab) - .SetParent(spawnOption) - .SetValueFormat(OptionFormat.Players) - .SetHidden(!canSetNum) - .SetGameMode(customGameMode); + AdditionalEmergencyCooldown = new BooleanOptionItem(23500, "AdditionalEmergencyCooldown", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)); - var spawnRateOption = new IntegerOptionItem(id + 2, "AdditionRolesSpawnRate", new(0, 100, 5), canSetChance ? 65 : 100, tab) - .SetParent(spawnOption) - .SetValueFormat(OptionFormat.Percent) - .SetHidden(!canSetChance) - .SetGameMode(customGameMode) as IntegerOptionItem; + AdditionalEmergencyCooldownThreshold = new IntegerOptionItem(23510, "AdditionalEmergencyCooldownThreshold", new(1, 15, 1), 1, TabGroup.GameSettings) + .SetParent(AdditionalEmergencyCooldown) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Players); - OptionItem guessSetting = new StringOptionItem(id + 3, "AddonCanBeGuessed", AddonGuessOptions, 2, tab) - .SetParent(spawnOption) - .SetGameMode(customGameMode); + AdditionalEmergencyCooldownTime = new FloatOptionItem(23511, "AdditionalEmergencyCooldownTime", new(1f, 60f, 1f), 1f, TabGroup.GameSettings) + .SetParent(AdditionalEmergencyCooldown) + .SetGameMode(CustomGameMode.Standard) + .SetValueFormat(OptionFormat.Seconds); - if (teamSpawnOptions) - { - OptionItem impOption = new BooleanOptionItem(id + 4, "ImpCanBeRole", true, tab) - .SetParent(spawnOption) - .SetGameMode(customGameMode) - .AddReplacement(("{role}", role.ToColoredString())); + LoadingPercentage = 95; - OptionItem neutralOption = new BooleanOptionItem(id + 5, "NeutralCanBeRole", true, tab) - .SetParent(spawnOption) - .SetGameMode(customGameMode) - .AddReplacement(("{role}", role.ToColoredString())); + VoteMode = new BooleanOptionItem(23600, "VoteMode", false, TabGroup.GameSettings) + .SetColor(new Color32(147, 241, 240, byte.MaxValue)) + .SetGameMode(CustomGameMode.Standard); - OptionItem crewOption = new BooleanOptionItem(id + 6, "CrewCanBeRole", true, tab) - .SetParent(spawnOption) - .SetGameMode(customGameMode) - .AddReplacement(("{role}", role.ToColoredString())); + WhenSkipVote = new StringOptionItem(23610, "WhenSkipVote", VoteModes[..3], 0, TabGroup.GameSettings) + .SetParent(VoteMode) + .SetGameMode(CustomGameMode.Standard); - AddonCanBeSettings.Add(role, (impOption, neutralOption, crewOption)); - } + WhenSkipVoteIgnoreFirstMeeting = new BooleanOptionItem(23611, "WhenSkipVoteIgnoreFirstMeeting", false, TabGroup.GameSettings) + .SetParent(WhenSkipVote) + .SetGameMode(CustomGameMode.Standard); - AddonGuessSettings.Add(role, guessSetting); + WhenSkipVoteIgnoreNoDeadBody = new BooleanOptionItem(23612, "WhenSkipVoteIgnoreNoDeadBody", false, TabGroup.GameSettings) + .SetParent(WhenSkipVote) + .SetGameMode(CustomGameMode.Standard); - CustomAdtRoleSpawnRate.Add(role, spawnRateOption); - CustomRoleSpawnChances.Add(role, spawnOption); - CustomRoleCounts.Add(role, countOption); - } + WhenSkipVoteIgnoreEmergency = new BooleanOptionItem(23613, "WhenSkipVoteIgnoreEmergency", false, TabGroup.GameSettings) + .SetParent(WhenSkipVote) + .SetGameMode(CustomGameMode.Standard); - public static void SetupSingleRoleOptions(int id, TabGroup tab, CustomRoles role, int count = 1, CustomGameMode customGameMode = CustomGameMode.Standard, bool zeroOne = false, bool hideMaxSetting = true) - { - var spawnOption = new StringOptionItem(id, role.ToString(), zeroOne ? RatesZeroOne : Rates, 0, tab).SetColor(Utils.GetRoleColor(role)) - .SetHeader(true) - .SetGameMode(customGameMode) as StringOptionItem; + WhenNonVote = new StringOptionItem(23700, "WhenNonVote", VoteModes, 0, TabGroup.GameSettings) + .SetParent(VoteMode) + .SetGameMode(CustomGameMode.Standard); - OptionItem countOption = new IntegerOptionItem(id + 1, "Maximum", new(count, count, count), count, tab) - .SetParent(spawnOption) - .SetHidden(hideMaxSetting) - .SetGameMode(customGameMode); + WhenTie = new StringOptionItem(23750, "WhenTie", TieModes, 0, TabGroup.GameSettings) + .SetParent(VoteMode) + .SetGameMode(CustomGameMode.Standard); - CustomRoleSpawnChances.Add(role, spawnOption); - CustomRoleCounts.Add(role, countOption); - SingleRoles.Add(role); - } + LoadingPercentage = 96; - public static OptionItem CreateCDSetting(int id, TabGroup tab, CustomRoles role, bool isKCD = false) - { - return new FloatOptionItem(id, isKCD ? "KillCooldown" : "AbilityCooldown", new(0f, 180f, 2.5f), 30f, tab) - .SetParent(CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - } - public static OptionItem CreatePetUseSetting(int id, CustomRoles role) - { - return new BooleanOptionItem(id, "UsePetInsteadOfKillButton", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[role]) - .SetColor(Color.magenta); - } + new TextOptionItem(100028, "MenuTitle.Other", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - public static OptionItem CreateVoteCancellingUseSetting(int id, CustomRoles role, TabGroup tab) - { - return new BooleanOptionItem(id, "UseVoteCancellingAfterVote", false, tab) - .SetParent(CustomRoleSpawnChances[role]) - .SetColor(Color.yellow); - } + LadderDeath = new BooleanOptionItem(23800, "LadderDeath", false, TabGroup.GameSettings) + .SetHeader(true) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - public class OverrideTasksData - { - public static readonly Dictionary AllData = []; - public readonly OptionItem AssignCommonTasks; - public readonly OptionItem DoOverride; - public readonly OptionItem NumLongTasks; - public readonly OptionItem NumShortTasks; + LadderDeathChance = new StringOptionItem(23810, "LadderDeathChance", Rates[1..], 0, TabGroup.GameSettings) + .SetParent(LadderDeath); - private OverrideTasksData(int idStart, TabGroup tab, CustomRoles role) - { - Dictionary replacementDic = new() { { "%role%", role.ToColoredString() } }; + LoadingPercentage = 97; - DoOverride = new BooleanOptionItem(idStart++, "doOverride", false, tab) - .SetParent(CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.None); - DoOverride.ReplacementDictionary = replacementDic; + FixFirstKillCooldown = new BooleanOptionItem(23900, "FixFirstKillCooldown", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - AssignCommonTasks = new BooleanOptionItem(idStart++, "assignCommonTasks", true, tab) - .SetParent(DoOverride) - .SetValueFormat(OptionFormat.None); + StartingKillCooldown = new FloatOptionItem(23950, "StartingKillCooldown", new(1, 60, 1), 18, TabGroup.GameSettings) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds); - AssignCommonTasks.ReplacementDictionary = replacementDic; + ShieldPersonDiedFirst = new BooleanOptionItem(24000, "ShieldPersonDiedFirst", false, TabGroup.GameSettings) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - NumLongTasks = new IntegerOptionItem(idStart++, "roleLongTasksNum", new(0, 90, 1), 3, tab) - .SetParent(DoOverride) - .SetValueFormat(OptionFormat.Pieces); + LoadingPercentage = 98; - NumLongTasks.ReplacementDictionary = replacementDic; - NumShortTasks = new IntegerOptionItem(idStart, "roleShortTasksNum", new(0, 90, 1), 3, tab) - .SetParent(DoOverride) - .SetValueFormat(OptionFormat.Pieces); + KillFlashDuration = new FloatOptionItem(24100, "KillFlashDuration", new(0.1f, 0.45f, 0.05f), 0.3f, TabGroup.GameSettings) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(CustomGameMode.Standard); - NumShortTasks.ReplacementDictionary = replacementDic; + UniqueNeutralRevealScreen = new BooleanOptionItem(24450, "UniqueNeutralRevealScreen", false, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(193, 255, 209, byte.MaxValue)); - if (!AllData.ContainsKey(role)) - AllData.Add(role, this); - else - Logger.Warn("OverrideTasksData created for duplicate CustomRoles", "OverrideTasksData"); - } - public static OverrideTasksData Create(int idStart, TabGroup tab, CustomRoles role) - { - return new(idStart, tab, role); - } - } + new TextOptionItem(100029, "MenuTitle.Ghost", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - // ReSharper disable NotAccessedField.Global + LoadingPercentage = 99; - // Ability Use Gain With Each Task Completed - public static OptionItem TimeMasterAbilityUseGainWithEachTaskCompleted; - public static OptionItem VeteranAbilityUseGainWithEachTaskCompleted; - public static OptionItem GrenadierAbilityUseGainWithEachTaskCompleted; - public static OptionItem LighterAbilityUseGainWithEachTaskCompleted; - public static OptionItem SecurityGuardAbilityUseGainWithEachTaskCompleted; - public static OptionItem DovesOfNeaceAbilityUseGainWithEachTaskCompleted; - // Ability Use Gain every 5 seconds - public static OptionItem GrenadierAbilityChargesWhenFinishedTasks; - public static OptionItem LighterAbilityChargesWhenFinishedTasks; - public static OptionItem SecurityGuardAbilityChargesWhenFinishedTasks; - public static OptionItem DovesOfNeaceAbilityChargesWhenFinishedTasks; - public static OptionItem TimeMasterAbilityChargesWhenFinishedTasks; - public static OptionItem VeteranAbilityChargesWhenFinishedTasks; + GhostCanSeeOtherRoles = new BooleanOptionItem(24300, "GhostCanSeeOtherRoles", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(217, 218, 255, byte.MaxValue)); - // ReSharper restore NotAccessedField.Global + GhostCanSeeOtherVotes = new BooleanOptionItem(24400, "GhostCanSeeOtherVotes", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(217, 218, 255, byte.MaxValue)); + + GhostCanSeeDeathReason = new BooleanOptionItem(24500, "GhostCanSeeDeathReason", true, TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetColor(new Color32(217, 218, 255, byte.MaxValue)); + + yield return null; + + + AFKDetector.SetupCustomOption(); + + + new TextOptionItem(100027, "MenuTitle.CTA", TabGroup.GameSettings) + .SetGameMode(CustomGameMode.Standard) + .SetHeader(true) + .SetColor(new Color32(215, 227, 84, byte.MaxValue)); + + CustomTeamManager.LoadCustomTeams(); + + LoadingPercentage = 100; + + #endregion + + yield return null; + + OptionSaver.Load(); + + IsLoaded = true; + + PostLoadTasks(); } + + public static void SetupRoleOptions(int id, TabGroup tab, CustomRoles role, CustomGameMode customGameMode = CustomGameMode.Standard, bool zeroOne = false) + { + var spawnOption = new StringOptionItem(id, role.ToString(), zeroOne ? RatesZeroOne : Rates, 0, tab).SetColor(Utils.GetRoleColor(role)) + .SetHeader(true) + .SetGameMode(customGameMode) as StringOptionItem; + + CustomRoleSpawnChances.Add(role, spawnOption); + + OptionItem countOption = new IntegerOptionItem(id + 1, "Maximum", new(1, 15, 1), 1, tab) + .SetParent(spawnOption) + .SetValueFormat(OptionFormat.Players) + .SetGameMode(customGameMode); + + CustomRoleCounts.Add(role, countOption); + } + + public static void SetupAdtRoleOptions(int id, CustomRoles role, CustomGameMode customGameMode = CustomGameMode.Standard, bool canSetNum = false, TabGroup tab = TabGroup.Addons, bool canSetChance = true, bool teamSpawnOptions = false, bool allowZeroCount = false) + { + var spawnOption = new StringOptionItem(id, role.ToString(), RatesZeroOne, 0, tab).SetColor(Utils.GetRoleColor(role)) + .SetHeader(true) + .SetGameMode(customGameMode) as StringOptionItem; + + OptionItem countOption = new IntegerOptionItem(id + 1, "Maximum", new(allowZeroCount ? 0 : 1, canSetNum ? 15 : 1, 1), 1, tab) + .SetParent(spawnOption) + .SetValueFormat(OptionFormat.Players) + .SetHidden(!canSetNum) + .SetGameMode(customGameMode); + + var spawnRateOption = new IntegerOptionItem(id + 2, "AdditionRolesSpawnRate", new(0, 100, 5), canSetChance ? 65 : 100, tab) + .SetParent(spawnOption) + .SetValueFormat(OptionFormat.Percent) + .SetHidden(!canSetChance) + .SetGameMode(customGameMode) as IntegerOptionItem; + + OptionItem guessSetting = new StringOptionItem(id + 3, "AddonCanBeGuessed", AddonGuessOptions, 2, tab) + .SetParent(spawnOption) + .SetGameMode(customGameMode); + + if (teamSpawnOptions) + { + OptionItem impOption = new BooleanOptionItem(id + 4, "ImpCanBeRole", true, tab) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + OptionItem neutralOption = new BooleanOptionItem(id + 5, "NeutralCanBeRole", true, tab) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + OptionItem crewOption = new BooleanOptionItem(id + 6, "CrewCanBeRole", true, tab) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .AddReplacement(("{role}", role.ToColoredString())); + + AddonCanBeSettings.Add(role, (impOption, neutralOption, crewOption)); + } + + AddonGuessSettings.Add(role, guessSetting); + + CustomAdtRoleSpawnRate.Add(role, spawnRateOption); + CustomRoleSpawnChances.Add(role, spawnOption); + CustomRoleCounts.Add(role, countOption); + } + + public static void SetupSingleRoleOptions(int id, TabGroup tab, CustomRoles role, int count = 1, CustomGameMode customGameMode = CustomGameMode.Standard, bool zeroOne = false, bool hideMaxSetting = true) + { + var spawnOption = new StringOptionItem(id, role.ToString(), zeroOne ? RatesZeroOne : Rates, 0, tab).SetColor(Utils.GetRoleColor(role)) + .SetHeader(true) + .SetGameMode(customGameMode) as StringOptionItem; + + OptionItem countOption = new IntegerOptionItem(id + 1, "Maximum", new(count, count, count), count, tab) + .SetParent(spawnOption) + .SetHidden(hideMaxSetting) + .SetGameMode(customGameMode); + + CustomRoleSpawnChances.Add(role, spawnOption); + CustomRoleCounts.Add(role, countOption); + SingleRoles.Add(role); + } + + public static OptionItem CreateCDSetting(int id, TabGroup tab, CustomRoles role, bool isKCD = false) + { + return new FloatOptionItem(id, isKCD ? "KillCooldown" : "AbilityCooldown", new(0f, 180f, 2.5f), 30f, tab) + .SetParent(CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); + } + + public static OptionItem CreatePetUseSetting(int id, CustomRoles role) + { + return new BooleanOptionItem(id, "UsePetInsteadOfKillButton", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[role]) + .SetColor(Color.magenta); + } + + public static OptionItem CreateVoteCancellingUseSetting(int id, CustomRoles role, TabGroup tab) + { + return new BooleanOptionItem(id, "UseVoteCancellingAfterVote", false, tab) + .SetParent(CustomRoleSpawnChances[role]) + .SetColor(Color.yellow); + } + + public class OverrideTasksData + { + public static readonly Dictionary AllData = []; + public readonly OptionItem AssignCommonTasks; + public readonly OptionItem DoOverride; + public readonly OptionItem NumLongTasks; + public readonly OptionItem NumShortTasks; + + private OverrideTasksData(int idStart, TabGroup tab, CustomRoles role) + { + Dictionary replacementDic = new() { { "%role%", role.ToColoredString() } }; + + DoOverride = new BooleanOptionItem(idStart++, "doOverride", false, tab) + .SetParent(CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.None); + + DoOverride.ReplacementDictionary = replacementDic; + + AssignCommonTasks = new BooleanOptionItem(idStart++, "assignCommonTasks", true, tab) + .SetParent(DoOverride) + .SetValueFormat(OptionFormat.None); + + AssignCommonTasks.ReplacementDictionary = replacementDic; + + NumLongTasks = new IntegerOptionItem(idStart++, "roleLongTasksNum", new(0, 90, 1), 3, tab) + .SetParent(DoOverride) + .SetValueFormat(OptionFormat.Pieces); + + NumLongTasks.ReplacementDictionary = replacementDic; + + NumShortTasks = new IntegerOptionItem(idStart, "roleShortTasksNum", new(0, 90, 1), 3, tab) + .SetParent(DoOverride) + .SetValueFormat(OptionFormat.Pieces); + + NumShortTasks.ReplacementDictionary = replacementDic; + + if (!AllData.ContainsKey(role)) + AllData.Add(role, this); + else + Logger.Warn("OverrideTasksData created for duplicate CustomRoles", "OverrideTasksData"); + } + + public static OverrideTasksData Create(int idStart, TabGroup tab, CustomRoles role) + { + return new(idStart, tab, role); + } + } + + // ReSharper disable NotAccessedField.Global + + // Ability Use Gain With Each Task Completed + public static OptionItem TimeMasterAbilityUseGainWithEachTaskCompleted; + public static OptionItem VeteranAbilityUseGainWithEachTaskCompleted; + public static OptionItem GrenadierAbilityUseGainWithEachTaskCompleted; + public static OptionItem LighterAbilityUseGainWithEachTaskCompleted; + public static OptionItem SecurityGuardAbilityUseGainWithEachTaskCompleted; + public static OptionItem DovesOfNeaceAbilityUseGainWithEachTaskCompleted; + + // Ability Use Gain every 5 seconds + public static OptionItem GrenadierAbilityChargesWhenFinishedTasks; + public static OptionItem LighterAbilityChargesWhenFinishedTasks; + public static OptionItem SecurityGuardAbilityChargesWhenFinishedTasks; + public static OptionItem DovesOfNeaceAbilityChargesWhenFinishedTasks; + public static OptionItem TimeMasterAbilityChargesWhenFinishedTasks; + public static OptionItem VeteranAbilityChargesWhenFinishedTasks; + + // ReSharper restore NotAccessedField.Global } \ No newline at end of file diff --git a/Modules/OptionItem/BooleanOptionItem.cs b/Modules/OptionItem/BooleanOptionItem.cs index ba7535f76..cf59ece1a 100644 --- a/Modules/OptionItem/BooleanOptionItem.cs +++ b/Modules/OptionItem/BooleanOptionItem.cs @@ -1,20 +1,19 @@ -namespace EHR +namespace EHR; + +public class BooleanOptionItem(int id, string name, bool defaultValue, TabGroup tab, bool isSingleValue = false) : OptionItem(id, name, defaultValue ? 1 : 0, tab, isSingleValue) { - public class BooleanOptionItem(int id, string name, bool defaultValue, TabGroup tab, bool isSingleValue = false) : OptionItem(id, name, defaultValue ? 1 : 0, tab, isSingleValue) + private const string TextTrue = "ColoredOn"; + private const string TextFalse = "ColoredOff"; + + // Getter + public override string GetString() { - private const string TextTrue = "ColoredOn"; - private const string TextFalse = "ColoredOff"; + return Translator.GetString(GetBool() ? TextTrue : TextFalse); + } - // Getter - public override string GetString() - { - return Translator.GetString(GetBool() ? TextTrue : TextFalse); - } - - // Setter - public override void SetValue(int value, bool doSync = true) - { - base.SetValue(value % 2 == 0 ? 0 : 1, doSync); - } + // Setter + public override void SetValue(int value, bool doSync = true) + { + base.SetValue(value % 2 == 0 ? 0 : 1, doSync); } } \ No newline at end of file diff --git a/Modules/OptionItem/FloatOptionItem.cs b/Modules/OptionItem/FloatOptionItem.cs index b189529ad..bf1bae7a5 100644 --- a/Modules/OptionItem/FloatOptionItem.cs +++ b/Modules/OptionItem/FloatOptionItem.cs @@ -1,36 +1,35 @@ using System.Globalization; -namespace EHR +namespace EHR; + +public class FloatOptionItem(int id, string name, FloatValueRule rule, float defaultValue, TabGroup tab, bool isSingleValue = false) : OptionItem(id, name, rule.GetNearestIndex(defaultValue), tab, isSingleValue) { - public class FloatOptionItem(int id, string name, FloatValueRule rule, float defaultValue, TabGroup tab, bool isSingleValue = false) : OptionItem(id, name, rule.GetNearestIndex(defaultValue), tab, isSingleValue) + public readonly FloatValueRule Rule = rule; + + // Getter + public override int GetInt() { - public readonly FloatValueRule Rule = rule; + return (int)Rule.GetValueByIndex(CurrentValue); + } - // Getter - public override int GetInt() - { - return (int)Rule.GetValueByIndex(CurrentValue); - } + public override float GetFloat() + { + return Rule.GetValueByIndex(CurrentValue); + } - public override float GetFloat() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override string GetString() + { + return ApplyFormat(((float)((int)(Rule.GetValueByIndex(CurrentValue) * 100) * 1.0) / 100).ToString(CultureInfo.CurrentCulture)); + } - public override string GetString() - { - return ApplyFormat(((float)((int)(Rule.GetValueByIndex(CurrentValue) * 100) * 1.0) / 100).ToString(CultureInfo.CurrentCulture)); - } + public override int GetValue() + { + return Rule.RepeatIndex(base.GetValue()); + } - public override int GetValue() - { - return Rule.RepeatIndex(base.GetValue()); - } - - // Setter - public override void SetValue(int value, bool doSync = true) - { - base.SetValue(Rule.RepeatIndex(value), doSync); - } + // Setter + public override void SetValue(int value, bool doSync = true) + { + base.SetValue(Rule.RepeatIndex(value), doSync); } } \ No newline at end of file diff --git a/Modules/OptionItem/IntegerOptionItem.cs b/Modules/OptionItem/IntegerOptionItem.cs index beca02559..d191263b6 100644 --- a/Modules/OptionItem/IntegerOptionItem.cs +++ b/Modules/OptionItem/IntegerOptionItem.cs @@ -1,34 +1,33 @@ -namespace EHR +namespace EHR; + +public class IntegerOptionItem(int id, string name, IntegerValueRule rule, int defaultValue, TabGroup tab, bool isSingleValue = false) : OptionItem(id, name, rule.GetNearestIndex(defaultValue), tab, isSingleValue) { - public class IntegerOptionItem(int id, string name, IntegerValueRule rule, int defaultValue, TabGroup tab, bool isSingleValue = false) : OptionItem(id, name, rule.GetNearestIndex(defaultValue), tab, isSingleValue) + public readonly IntegerValueRule Rule = rule; + + // Getter + public override int GetInt() { - public readonly IntegerValueRule Rule = rule; + return Rule.GetValueByIndex(CurrentValue); + } - // Getter - public override int GetInt() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override float GetFloat() + { + return Rule.GetValueByIndex(CurrentValue); + } - public override float GetFloat() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override string GetString() + { + return ApplyFormat(Rule.GetValueByIndex(CurrentValue).ToString()); + } - public override string GetString() - { - return ApplyFormat(Rule.GetValueByIndex(CurrentValue).ToString()); - } + public override int GetValue() + { + return Rule.RepeatIndex(base.GetValue()); + } - public override int GetValue() - { - return Rule.RepeatIndex(base.GetValue()); - } - - // Setter - public override void SetValue(int value, bool doSync = true) - { - base.SetValue(Rule.RepeatIndex(value), doSync); - } + // Setter + public override void SetValue(int value, bool doSync = true) + { + base.SetValue(Rule.RepeatIndex(value), doSync); } } \ No newline at end of file diff --git a/Modules/OptionItem/OptionItem.cs b/Modules/OptionItem/OptionItem.cs index 8953e817b..a64412129 100644 --- a/Modules/OptionItem/OptionItem.cs +++ b/Modules/OptionItem/OptionItem.cs @@ -4,364 +4,363 @@ using System.Linq; using EHR.Modules; using UnityEngine; -namespace EHR +namespace EHR; + +public abstract class OptionItem { - public abstract class OptionItem + public const int NumPresets = 10; + private const int PresetId = 0; + public readonly List Children; + + private Dictionary _replacementDictionary; + + public OptionBehaviour OptionBehaviour; + + protected OptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSingleValue) { - public const int NumPresets = 10; - private const int PresetId = 0; - public readonly List Children; + Id = id; + Name = name; + DefaultValue = defaultValue; + Tab = tab; + IsSingleValue = isSingleValue; - private Dictionary _replacementDictionary; + NameColor = Color.white; + ValueFormat = OptionFormat.None; + GameMode = CustomGameMode.All; + IsHeader = false; + IsHidden = false; + IsText = false; - public OptionBehaviour OptionBehaviour; + Children = []; - protected OptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSingleValue) + if (Id == PresetId) { - Id = id; - Name = name; - DefaultValue = defaultValue; - Tab = tab; - IsSingleValue = isSingleValue; + SingleValue = DefaultValue; + CurrentPreset = SingleValue; + } + else if (IsSingleValue) + SingleValue = DefaultValue; + else + for (var i = 0; i < NumPresets; i++) + AllValues[i] = DefaultValue; - NameColor = Color.white; - ValueFormat = OptionFormat.None; - GameMode = CustomGameMode.All; - IsHeader = false; - IsHidden = false; - IsText = false; + if (FastOpts.TryAdd(id, this)) + Options.Add(this); + else + Logger.Error($"Duplicate ID: {id} ({name})", "OptionItem"); + } - Children = []; + public int Id { get; } + public string Name { get; } + public int DefaultValue { get; } + public TabGroup Tab { get; } + public bool IsSingleValue { get; } - if (Id == PresetId) - { - SingleValue = DefaultValue; - CurrentPreset = SingleValue; - } - else if (IsSingleValue) - SingleValue = DefaultValue; + private Color NameColor { get; set; } + private OptionFormat ValueFormat { get; set; } + public CustomGameMode GameMode { get; private set; } + public bool IsHeader { get; protected set; } + private bool IsHidden { get; set; } + public bool IsText { get; protected set; } + + public Dictionary ReplacementDictionary + { + get => _replacementDictionary; + set + { + if (value == null) + _replacementDictionary?.Clear(); else - for (var i = 0; i < NumPresets; i++) - AllValues[i] = DefaultValue; - - if (FastOpts.TryAdd(id, this)) - Options.Add(this); - else - Logger.Error($"Duplicate ID: {id} ({name})", "OptionItem"); + _replacementDictionary = value; } + } - public int Id { get; } - public string Name { get; } - public int DefaultValue { get; } - public TabGroup Tab { get; } - public bool IsSingleValue { get; } + public int[] AllValues { get; private set; } = new int[NumPresets]; - private Color NameColor { get; set; } - private OptionFormat ValueFormat { get; set; } - public CustomGameMode GameMode { get; private set; } - public bool IsHeader { get; protected set; } - private bool IsHidden { get; set; } - public bool IsText { get; protected set; } + public int CurrentValue + { + get => GetValue(); + set => SetValue(value); + } - public Dictionary ReplacementDictionary + public int SingleValue { get; private set; } + + public OptionItem Parent { get; private set; } + + public event EventHandler UpdateValueEvent; + + // Setter + private OptionItem Do(Action action) + { + action(this); + return this; + } + + public OptionItem SetColor(Color value) + { + return Do(i => i.NameColor = value); + } + + public OptionItem SetValueFormat(OptionFormat value) + { + return Do(i => i.ValueFormat = value); + } + + public OptionItem SetGameMode(CustomGameMode value) + { + return Do(i => i.GameMode = value); + } + + public OptionItem SetHeader(bool value) + { + return Do(i => i.IsHeader = value); + } + + public OptionItem SetHidden(bool value) + { + return Do(i => i.IsHidden = value); + } + + public OptionItem SetText(bool value) + { + return Do(i => i.IsText = value); + } + + public OptionItem SetParent(OptionItem parent) + { + return Do(i => { - get => _replacementDictionary; - set + foreach (KeyValuePair role in EHR.Options.CustomRoleSpawnChances) { - if (value == null) - _replacementDictionary?.Clear(); - else - _replacementDictionary = value; - } - } - - public int[] AllValues { get; private set; } = new int[NumPresets]; - - public int CurrentValue - { - get => GetValue(); - set => SetValue(value); - } - - public int SingleValue { get; private set; } - - public OptionItem Parent { get; private set; } - - public event EventHandler UpdateValueEvent; - - // Setter - private OptionItem Do(Action action) - { - action(this); - return this; - } - - public OptionItem SetColor(Color value) - { - return Do(i => i.NameColor = value); - } - - public OptionItem SetValueFormat(OptionFormat value) - { - return Do(i => i.ValueFormat = value); - } - - public OptionItem SetGameMode(CustomGameMode value) - { - return Do(i => i.GameMode = value); - } - - public OptionItem SetHeader(bool value) - { - return Do(i => i.IsHeader = value); - } - - public OptionItem SetHidden(bool value) - { - return Do(i => i.IsHidden = value); - } - - public OptionItem SetText(bool value) - { - return Do(i => i.IsText = value); - } - - public OptionItem SetParent(OptionItem parent) - { - return Do(i => - { - foreach (KeyValuePair role in EHR.Options.CustomRoleSpawnChances) + if (role.Value.Name == parent.Name) { - if (role.Value.Name == parent.Name) - { - string roleName = Translator.GetString(Enum.GetName(typeof(CustomRoles), role.Key)); - ReplacementDictionary ??= []; - ReplacementDictionary.TryAdd(roleName, Utils.ColorString(Utils.GetRoleColor(role.Key), roleName)); - break; - } + string roleName = Translator.GetString(Enum.GetName(typeof(CustomRoles), role.Key)); + ReplacementDictionary ??= []; + ReplacementDictionary.TryAdd(roleName, Utils.ColorString(Utils.GetRoleColor(role.Key), roleName)); + break; } - - i.Parent = parent; - parent.SetChild(i); - }); - } - - private OptionItem SetChild(OptionItem child) - { - return Do(i => i.Children.Add(child)); - } - - public OptionItem RegisterUpdateValueEvent(EventHandler handler) - { - return Do(_ => UpdateValueEvent += handler); - } - - public OptionItem AddReplacement((string key, string value) kvp) - { - return Do(_ => - { - ReplacementDictionary ??= []; - ReplacementDictionary.Add(kvp.key, kvp.value); - }); - } - - public OptionItem RemoveReplacement(string key) - { - return Do(_ => ReplacementDictionary?.Remove(key)); - } - - // Getter - public string GetName(bool disableColor = false, bool console = false) - { - if (Name.Contains("CTA.FLAG")) return Utils.ColorString(NameColor, Translator.GetString("CTA.TeamEnabled.Prefix") + Name[8..] + Translator.GetString("CTA.TeamEnabled.Suffix")); - - return disableColor ? Translator.GetString(Name, ReplacementDictionary, console) : Utils.ColorString(NameColor, Translator.GetString(Name, ReplacementDictionary)); - } - - public bool GetBool() - { - return (Parent == null || Parent.GetBool()) && Name switch - { - "LoverDieConsequence" => GetValue() == 1, - "Bargainer.LensOfTruth.DurationSwitch" => GetValue() == 3, - "BlackHoleDespawnMode" => GetValue() == 1, - _ => CurrentValue != 0 - }; - } - - public virtual int GetInt() - { - return CurrentValue; - } - - public virtual float GetFloat() - { - return CurrentValue; - } - - public virtual string GetString() - { - return ApplyFormat(CurrentValue.ToString()); - } - - public virtual int GetValue() - { - return IsSingleValue ? SingleValue : AllValues[CurrentPreset]; - } - - public bool IsCurrentlyHidden() - { - var mode = EHR.Options.CurrentGameMode; - return CheckHidden() || (GameMode != CustomGameMode.All && GameMode != mode && !(mode == CustomGameMode.AllInOne && AllInOneGameMode.GameModeIntegrationSettings.TryGetValue(GameMode, out var option) && option.GetBool())); - } - - private bool CheckHidden() - { - int LastParent = Id; - - for (var i = 0; i < 5; i++) - { - if (AllOptions.First(x => x.Id == LastParent).Parent == null) break; - LastParent = AllOptions.First(x => x.Id == LastParent).Parent.Id; } - return IsHidden || Parent?.IsHidden == true || AllOptions.First(x => x.Id == LastParent).IsHidden; - } - - protected string ApplyFormat(string value) - { - if (ValueFormat == OptionFormat.None) return value; - return string.Format(Translator.GetString("Format." + ValueFormat), value); - } - - private void Refresh() - { - if (OptionBehaviour is StringOption opt) - { - opt.TitleText.text = GetName(); - opt.ValueText.text = GetString(); - opt.oldValue = opt.Value = CurrentValue; - } - } - - public void SetValue(int afterValue, bool doSave, bool doSync = true) - { - int beforeValue = CurrentValue; - - if (IsSingleValue) - SingleValue = afterValue; - else - AllValues[CurrentPreset] = afterValue; - - CallUpdateValueEvent(beforeValue, afterValue); - Refresh(); - if (doSync) SyncAllOptions(); - - if (doSave) OptionSaver.Save(); - } - - public virtual void SetValue(int afterValue, bool doSync = true) - { - SetValue(afterValue, true, doSync); - } - - public void SetAllValues(int[] values) - { - if (values.Length == AllValues.Length) - AllValues = values; - else - for (var i = 0; i < values.Length; i++) - AllValues[i] = values[i]; - } - - public static OptionItem operator ++(OptionItem item) - { - return item.Do(item => item.SetValue(item.CurrentValue + 1)); - } - - public static OptionItem operator --(OptionItem item) - { - return item.Do(item => item.SetValue(item.CurrentValue - 1)); - } - - protected static void SwitchPreset(int newPreset) - { - CurrentPreset = Math.Clamp(newPreset, 0, NumPresets - 1); - - foreach (OptionItem op in AllOptions) op.Refresh(); - - SyncAllOptions(); - } - - public static void SyncAllOptions(int targetId = -1) - { - if ( - Main.AllPlayerControls.Length <= 1 - || AmongUsClient.Instance.AmHost == false - || PlayerControl.LocalPlayer == null - ) - return; - - RPC.SyncCustomSettingsRPC(targetId); - } - - - // EventArgs - private void CallUpdateValueEvent(int beforeValue, int currentValue) - { - if (UpdateValueEvent == null) return; - - try - { - UpdateValueEvent(this, new(beforeValue, currentValue)); - } - catch (Exception ex) - { - Logger.Error($"[{Name}] - Exception occurred when calling UpdateValueEvent", "OptionItem.UpdateValueEvent"); - Logger.Exception(ex, "OptionItem.UpdateValueEvent"); - } - } - - public class UpdateValueEventArgs(int beforeValue, int currentValue) : EventArgs - { - public int CurrentValue { get; set; } = currentValue; - public int BeforeValue { get; set; } = beforeValue; - } - - #region static - - public static IReadOnlyList AllOptions => Options; - private static readonly List Options = new(1024); - public static IReadOnlyDictionary FastOptions => FastOpts; - private static readonly Dictionary FastOpts = new(1024); - public static int CurrentPreset { get; private set; } - - #endregion + i.Parent = parent; + parent.SetChild(i); + }); } - public enum TabGroup + private OptionItem SetChild(OptionItem child) { - SystemSettings, - GameSettings, - TaskSettings, - ImpostorRoles, - CrewmateRoles, - NeutralRoles, - Addons, - OtherRoles + return Do(i => i.Children.Add(child)); } - public enum OptionFormat + public OptionItem RegisterUpdateValueEvent(EventHandler handler) { - None, - Players, - Seconds, - Percent, - Times, - Multiplier, - Votes, - Pieces, - Health, - Level + return Do(_ => UpdateValueEvent += handler); } + + public OptionItem AddReplacement((string key, string value) kvp) + { + return Do(_ => + { + ReplacementDictionary ??= []; + ReplacementDictionary.Add(kvp.key, kvp.value); + }); + } + + public OptionItem RemoveReplacement(string key) + { + return Do(_ => ReplacementDictionary?.Remove(key)); + } + + // Getter + public string GetName(bool disableColor = false, bool console = false) + { + if (Name.Contains("CTA.FLAG")) return Utils.ColorString(NameColor, Translator.GetString("CTA.TeamEnabled.Prefix") + Name[8..] + Translator.GetString("CTA.TeamEnabled.Suffix")); + + return disableColor ? Translator.GetString(Name, ReplacementDictionary, console) : Utils.ColorString(NameColor, Translator.GetString(Name, ReplacementDictionary)); + } + + public bool GetBool() + { + return (Parent == null || Parent.GetBool()) && Name switch + { + "LoverDieConsequence" => GetValue() == 1, + "Bargainer.LensOfTruth.DurationSwitch" => GetValue() == 3, + "BlackHoleDespawnMode" => GetValue() == 1, + _ => CurrentValue != 0 + }; + } + + public virtual int GetInt() + { + return CurrentValue; + } + + public virtual float GetFloat() + { + return CurrentValue; + } + + public virtual string GetString() + { + return ApplyFormat(CurrentValue.ToString()); + } + + public virtual int GetValue() + { + return IsSingleValue ? SingleValue : AllValues[CurrentPreset]; + } + + public bool IsCurrentlyHidden() + { + var mode = EHR.Options.CurrentGameMode; + return CheckHidden() || (GameMode != CustomGameMode.All && GameMode != mode && !(mode == CustomGameMode.AllInOne && AllInOneGameMode.GameModeIntegrationSettings.TryGetValue(GameMode, out var option) && option.GetBool())); + } + + private bool CheckHidden() + { + int LastParent = Id; + + for (var i = 0; i < 5; i++) + { + if (AllOptions.First(x => x.Id == LastParent).Parent == null) break; + LastParent = AllOptions.First(x => x.Id == LastParent).Parent.Id; + } + + return IsHidden || Parent?.IsHidden == true || AllOptions.First(x => x.Id == LastParent).IsHidden; + } + + protected string ApplyFormat(string value) + { + if (ValueFormat == OptionFormat.None) return value; + return string.Format(Translator.GetString("Format." + ValueFormat), value); + } + + private void Refresh() + { + if (OptionBehaviour is StringOption opt) + { + opt.TitleText.text = GetName(); + opt.ValueText.text = GetString(); + opt.oldValue = opt.Value = CurrentValue; + } + } + + public void SetValue(int afterValue, bool doSave, bool doSync = true) + { + int beforeValue = CurrentValue; + + if (IsSingleValue) + SingleValue = afterValue; + else + AllValues[CurrentPreset] = afterValue; + + CallUpdateValueEvent(beforeValue, afterValue); + Refresh(); + if (doSync) SyncAllOptions(); + + if (doSave) OptionSaver.Save(); + } + + public virtual void SetValue(int afterValue, bool doSync = true) + { + SetValue(afterValue, true, doSync); + } + + public void SetAllValues(int[] values) + { + if (values.Length == AllValues.Length) + AllValues = values; + else + for (var i = 0; i < values.Length; i++) + AllValues[i] = values[i]; + } + + public static OptionItem operator ++(OptionItem item) + { + return item.Do(item => item.SetValue(item.CurrentValue + 1)); + } + + public static OptionItem operator --(OptionItem item) + { + return item.Do(item => item.SetValue(item.CurrentValue - 1)); + } + + protected static void SwitchPreset(int newPreset) + { + CurrentPreset = Math.Clamp(newPreset, 0, NumPresets - 1); + + foreach (OptionItem op in AllOptions) op.Refresh(); + + SyncAllOptions(); + } + + public static void SyncAllOptions(int targetId = -1) + { + if ( + Main.AllPlayerControls.Length <= 1 + || AmongUsClient.Instance.AmHost == false + || PlayerControl.LocalPlayer == null + ) + return; + + RPC.SyncCustomSettingsRPC(targetId); + } + + + // EventArgs + private void CallUpdateValueEvent(int beforeValue, int currentValue) + { + if (UpdateValueEvent == null) return; + + try + { + UpdateValueEvent(this, new(beforeValue, currentValue)); + } + catch (Exception ex) + { + Logger.Error($"[{Name}] - Exception occurred when calling UpdateValueEvent", "OptionItem.UpdateValueEvent"); + Logger.Exception(ex, "OptionItem.UpdateValueEvent"); + } + } + + public class UpdateValueEventArgs(int beforeValue, int currentValue) : EventArgs + { + public int CurrentValue { get; set; } = currentValue; + public int BeforeValue { get; set; } = beforeValue; + } + + #region static + + public static IReadOnlyList AllOptions => Options; + private static readonly List Options = new(1024); + public static IReadOnlyDictionary FastOptions => FastOpts; + private static readonly Dictionary FastOpts = new(1024); + public static int CurrentPreset { get; private set; } + + #endregion +} + +public enum TabGroup +{ + SystemSettings, + GameSettings, + TaskSettings, + ImpostorRoles, + CrewmateRoles, + NeutralRoles, + Addons, + OtherRoles +} + +public enum OptionFormat +{ + None, + Players, + Seconds, + Percent, + Times, + Multiplier, + Votes, + Pieces, + Health, + Level } \ No newline at end of file diff --git a/Modules/OptionItem/PresetOptionItem.cs b/Modules/OptionItem/PresetOptionItem.cs index c468aaf53..4d58a272c 100644 --- a/Modules/OptionItem/PresetOptionItem.cs +++ b/Modules/OptionItem/PresetOptionItem.cs @@ -1,48 +1,47 @@ -namespace EHR +namespace EHR; + +public class PresetOptionItem(int defaultValue, TabGroup tab) : OptionItem(0, "Preset", defaultValue, tab, true) { - public class PresetOptionItem(int defaultValue, TabGroup tab) : OptionItem(0, "Preset", defaultValue, tab, true) + public readonly IntegerValueRule Rule = (0, NumPresets - 1, 1); + + // Getter + public override int GetInt() { - public readonly IntegerValueRule Rule = (0, NumPresets - 1, 1); + return Rule.GetValueByIndex(CurrentValue); + } - // Getter - public override int GetInt() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override float GetFloat() + { + return Rule.GetValueByIndex(CurrentValue); + } - public override float GetFloat() + public override string GetString() + { + return CurrentValue switch { - return Rule.GetValueByIndex(CurrentValue); - } + 0 => Main.Preset1.Value == (string)Main.Preset1.DefaultValue ? Translator.GetString("Preset_1") : Main.Preset1.Value, + 1 => Main.Preset2.Value == (string)Main.Preset2.DefaultValue ? Translator.GetString("Preset_2") : Main.Preset2.Value, + 2 => Main.Preset3.Value == (string)Main.Preset3.DefaultValue ? Translator.GetString("Preset_3") : Main.Preset3.Value, + 3 => Main.Preset4.Value == (string)Main.Preset4.DefaultValue ? Translator.GetString("Preset_4") : Main.Preset4.Value, + 4 => Main.Preset5.Value == (string)Main.Preset5.DefaultValue ? Translator.GetString("Preset_5") : Main.Preset5.Value, + 5 => Main.Preset6.Value == (string)Main.Preset6.DefaultValue ? Translator.GetString("Preset_6") : Main.Preset6.Value, + 6 => Main.Preset7.Value == (string)Main.Preset7.DefaultValue ? Translator.GetString("Preset_7") : Main.Preset7.Value, + 7 => Main.Preset8.Value == (string)Main.Preset8.DefaultValue ? Translator.GetString("Preset_8") : Main.Preset8.Value, + 8 => Main.Preset9.Value == (string)Main.Preset9.DefaultValue ? Translator.GetString("Preset_9") : Main.Preset9.Value, + 9 => Main.Preset10.Value == (string)Main.Preset10.DefaultValue ? Translator.GetString("Preset_10") : Main.Preset10.Value, + _ => null + }; + } - public override string GetString() - { - return CurrentValue switch - { - 0 => Main.Preset1.Value == (string)Main.Preset1.DefaultValue ? Translator.GetString("Preset_1") : Main.Preset1.Value, - 1 => Main.Preset2.Value == (string)Main.Preset2.DefaultValue ? Translator.GetString("Preset_2") : Main.Preset2.Value, - 2 => Main.Preset3.Value == (string)Main.Preset3.DefaultValue ? Translator.GetString("Preset_3") : Main.Preset3.Value, - 3 => Main.Preset4.Value == (string)Main.Preset4.DefaultValue ? Translator.GetString("Preset_4") : Main.Preset4.Value, - 4 => Main.Preset5.Value == (string)Main.Preset5.DefaultValue ? Translator.GetString("Preset_5") : Main.Preset5.Value, - 5 => Main.Preset6.Value == (string)Main.Preset6.DefaultValue ? Translator.GetString("Preset_6") : Main.Preset6.Value, - 6 => Main.Preset7.Value == (string)Main.Preset7.DefaultValue ? Translator.GetString("Preset_7") : Main.Preset7.Value, - 7 => Main.Preset8.Value == (string)Main.Preset8.DefaultValue ? Translator.GetString("Preset_8") : Main.Preset8.Value, - 8 => Main.Preset9.Value == (string)Main.Preset9.DefaultValue ? Translator.GetString("Preset_9") : Main.Preset9.Value, - 9 => Main.Preset10.Value == (string)Main.Preset10.DefaultValue ? Translator.GetString("Preset_10") : Main.Preset10.Value, - _ => null - }; - } + public override int GetValue() + { + return Rule.RepeatIndex(base.GetValue()); + } - public override int GetValue() - { - return Rule.RepeatIndex(base.GetValue()); - } - - // Setter - public override void SetValue(int value, bool doSync = true) - { - base.SetValue(Rule.RepeatIndex(value), doSync); - SwitchPreset(Rule.RepeatIndex(value)); - } + // Setter + public override void SetValue(int value, bool doSync = true) + { + base.SetValue(Rule.RepeatIndex(value), doSync); + SwitchPreset(Rule.RepeatIndex(value)); } } \ No newline at end of file diff --git a/Modules/OptionItem/StringOptionItem.cs b/Modules/OptionItem/StringOptionItem.cs index 4086db8f3..dec6eddf6 100644 --- a/Modules/OptionItem/StringOptionItem.cs +++ b/Modules/OptionItem/StringOptionItem.cs @@ -1,58 +1,57 @@ using System.Collections.Generic; -namespace EHR +namespace EHR; + +public class StringOptionItem(int id, string name, IList selections, int defaultValue, TabGroup tab, bool isSingleValue = false, bool noTranslation = false) : OptionItem(id, name, defaultValue, tab, isSingleValue) { - public class StringOptionItem(int id, string name, IList selections, int defaultValue, TabGroup tab, bool isSingleValue = false, bool noTranslation = false) : OptionItem(id, name, defaultValue, tab, isSingleValue) + public readonly bool noTranslation = noTranslation; + public readonly IntegerValueRule Rule = (0, selections.Count - 1, 1); + public readonly IList Selections = selections; + + // Getter + public override int GetInt() { - public readonly bool noTranslation = noTranslation; - public readonly IntegerValueRule Rule = (0, selections.Count - 1, 1); - public readonly IList Selections = selections; + return Rule.GetValueByIndex(CurrentValue); + } - // Getter - public override int GetInt() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override float GetFloat() + { + return Rule.GetValueByIndex(CurrentValue); + } - public override float GetFloat() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override string GetString() + { + string str = Selections[Rule.GetValueByIndex(CurrentValue)]; + return noTranslation ? str : Translator.GetString(str); + } - public override string GetString() + public int GetChance() + { + switch (Selections.Count) { - string str = Selections[Rule.GetValueByIndex(CurrentValue)]; - return noTranslation ? str : Translator.GetString(str); - } - - public int GetChance() - { - switch (Selections.Count) - { - // For 0% or 100% - case 2: - return CurrentValue * 100; - // EHR’s career generation mode - case 3: - return CurrentValue; - // For 0% to 100% or 5% to 100% - default: - int offset = Options.Rates.Length - Selections.Count; - int index = CurrentValue + offset; - int rate = index * 5; - return rate; - } - } - - public override int GetValue() - { - return Rule.RepeatIndex(base.GetValue()); - } - - // Setter - public override void SetValue(int value, bool doSync = true) - { - base.SetValue(Rule.RepeatIndex(value), doSync); + // For 0% or 100% + case 2: + return CurrentValue * 100; + // EHR’s career generation mode + case 3: + return CurrentValue; + // For 0% to 100% or 5% to 100% + default: + int offset = Options.Rates.Length - Selections.Count; + int index = CurrentValue + offset; + int rate = index * 5; + return rate; } } + + public override int GetValue() + { + return Rule.RepeatIndex(base.GetValue()); + } + + // Setter + public override void SetValue(int value, bool doSync = true) + { + base.SetValue(Rule.RepeatIndex(value), doSync); + } } \ No newline at end of file diff --git a/Modules/OptionItem/TextOptionItem.cs b/Modules/OptionItem/TextOptionItem.cs index 55afeec94..2d4a2e44d 100644 --- a/Modules/OptionItem/TextOptionItem.cs +++ b/Modules/OptionItem/TextOptionItem.cs @@ -1,29 +1,28 @@ -namespace EHR +namespace EHR; + +public class TextOptionItem : OptionItem { - public class TextOptionItem : OptionItem + public IntegerValueRule Rule; + + public TextOptionItem(int id, string name, TabGroup tab, int defaultValue = 0, bool isSingleValue = false) : base(id, name, defaultValue, tab, isSingleValue) { - public IntegerValueRule Rule; + IsText = true; + IsHeader = true; + } - public TextOptionItem(int id, string name, TabGroup tab, int defaultValue = 0, bool isSingleValue = false) : base(id, name, defaultValue, tab, isSingleValue) - { - IsText = true; - IsHeader = true; - } + // Getter + public override int GetInt() + { + return Rule.GetValueByIndex(CurrentValue); + } - // Getter - public override int GetInt() - { - return Rule.GetValueByIndex(CurrentValue); - } + public override float GetFloat() + { + return Rule.GetValueByIndex(CurrentValue); + } - public override float GetFloat() - { - return Rule.GetValueByIndex(CurrentValue); - } - - public override string GetString() - { - return Translator.GetString(Name); - } + public override string GetString() + { + return Translator.GetString(Name); } } \ No newline at end of file diff --git a/Modules/OptionItem/ValueRule.cs b/Modules/OptionItem/ValueRule.cs index 4b75bdc43..98d07d93e 100644 --- a/Modules/OptionItem/ValueRule.cs +++ b/Modules/OptionItem/ValueRule.cs @@ -1,84 +1,83 @@ using System; -namespace EHR +namespace EHR; + +public abstract class ValueRule(T minValue, T maxValue, T step) { - public abstract class ValueRule(T minValue, T maxValue, T step) + public ValueRule((T, T, T) tuple) + : this(tuple.Item1, tuple.Item2, tuple.Item3) { } + + public T MinValue { get; protected set; } = minValue; + public T MaxValue { get; protected set; } = maxValue; + public T Step { get; protected set; } = step; + + public abstract int RepeatIndex(int value); + public abstract T GetValueByIndex(int index); + public abstract int GetNearestIndex(T num); +} + +public class IntegerValueRule : ValueRule +{ + public IntegerValueRule(int minValue, int maxValue, int step) + : base(minValue, maxValue, step) { } + + public IntegerValueRule((int, int, int) tuple) + : base(tuple) { } + + public static implicit operator IntegerValueRule((int, int, int) tuple) { - public ValueRule((T, T, T) tuple) - : this(tuple.Item1, tuple.Item2, tuple.Item3) { } - - public T MinValue { get; protected set; } = minValue; - public T MaxValue { get; protected set; } = maxValue; - public T Step { get; protected set; } = step; - - public abstract int RepeatIndex(int value); - public abstract T GetValueByIndex(int index); - public abstract int GetNearestIndex(T num); + return new(tuple); } - public class IntegerValueRule : ValueRule + public override int RepeatIndex(int value) { - public IntegerValueRule(int minValue, int maxValue, int step) - : base(minValue, maxValue, step) { } + int MaxIndex = (MaxValue - MinValue) / Step; + value %= MaxIndex + 1; + if (value < 0) value = MaxIndex; - public IntegerValueRule((int, int, int) tuple) - : base(tuple) { } - - public static implicit operator IntegerValueRule((int, int, int) tuple) - { - return new(tuple); - } - - public override int RepeatIndex(int value) - { - int MaxIndex = (MaxValue - MinValue) / Step; - value %= MaxIndex + 1; - if (value < 0) value = MaxIndex; - - return value; - } - - public override int GetValueByIndex(int index) - { - return (RepeatIndex(index) * Step) + MinValue; - } - - public override int GetNearestIndex(int num) - { - return (int)Math.Round((num - MinValue) / (float)Step); - } + return value; } - public class FloatValueRule : ValueRule + public override int GetValueByIndex(int index) { - public FloatValueRule(float minValue, float maxValue, float step) - : base(minValue, maxValue, step) { } + return (RepeatIndex(index) * Step) + MinValue; + } - public FloatValueRule((float, float, float) tuple) - : base(tuple) { } + public override int GetNearestIndex(int num) + { + return (int)Math.Round((num - MinValue) / (float)Step); + } +} - public static implicit operator FloatValueRule((float, float, float) tuple) - { - return new(tuple); - } +public class FloatValueRule : ValueRule +{ + public FloatValueRule(float minValue, float maxValue, float step) + : base(minValue, maxValue, step) { } - public override int RepeatIndex(int value) - { - var MaxIndex = (int)((MaxValue - MinValue) / Step); - value %= MaxIndex + 1; - if (value < 0) value = MaxIndex; + public FloatValueRule((float, float, float) tuple) + : base(tuple) { } - return value; - } + public static implicit operator FloatValueRule((float, float, float) tuple) + { + return new(tuple); + } - public override float GetValueByIndex(int index) - { - return (RepeatIndex(index) * Step) + MinValue; - } + public override int RepeatIndex(int value) + { + var MaxIndex = (int)((MaxValue - MinValue) / Step); + value %= MaxIndex + 1; + if (value < 0) value = MaxIndex; - public override int GetNearestIndex(float num) - { - return (int)Math.Round((num - MinValue) / Step); - } + return value; + } + + public override float GetValueByIndex(int index) + { + return (RepeatIndex(index) * Step) + MinValue; + } + + public override int GetNearestIndex(float num) + { + return (int)Math.Round((num - MinValue) / Step); } } \ No newline at end of file diff --git a/Modules/OptionSaver.cs b/Modules/OptionSaver.cs index 24227509f..b5466d145 100644 --- a/Modules/OptionSaver.cs +++ b/Modules/OptionSaver.cs @@ -2,116 +2,115 @@ using System.IO; using System.Text.Json; -namespace EHR.Modules +namespace EHR.Modules; + +// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionSaver.cs +public static class OptionSaver { - // https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionSaver.cs - public static class OptionSaver + private static readonly DirectoryInfo SaveDataDirectoryInfo = new("./EHR_DATA/SaveData/"); + private static readonly FileInfo OptionSaverFileInfo = new($"{SaveDataDirectoryInfo.FullName}/Options.json"); + private static readonly LogHandler Logger = EHR.Logger.Handler(nameof(OptionSaver)); + private static readonly FileInfo DefaultPresetFileInfo = new($"{SaveDataDirectoryInfo.FullName}/DefaultPreset.txt"); + private static int DefaultPresetNumber; + + public static readonly int Version; + + public static int GetDefaultPresetNumber() { - private static readonly DirectoryInfo SaveDataDirectoryInfo = new("./EHR_DATA/SaveData/"); - private static readonly FileInfo OptionSaverFileInfo = new($"{SaveDataDirectoryInfo.FullName}/Options.json"); - private static readonly LogHandler Logger = EHR.Logger.Handler(nameof(OptionSaver)); - private static readonly FileInfo DefaultPresetFileInfo = new($"{SaveDataDirectoryInfo.FullName}/DefaultPreset.txt"); - private static int DefaultPresetNumber; - - public static readonly int Version; - - public static int GetDefaultPresetNumber() + if (DefaultPresetFileInfo.Exists) { - if (DefaultPresetFileInfo.Exists) + string presetNmber = File.ReadAllText(DefaultPresetFileInfo.FullName); + if (int.TryParse(presetNmber, out int number) && number is >= 0 and <= OptionItem.NumPresets - 1) return number; + } + + return 0; + } + + public static void Initialize() + { + if (!SaveDataDirectoryInfo.Exists) + { + SaveDataDirectoryInfo.Create(); + SaveDataDirectoryInfo.Attributes |= FileAttributes.Hidden; + } + + if (!OptionSaverFileInfo.Exists) OptionSaverFileInfo.Create().Dispose(); + + if (!DefaultPresetFileInfo.Exists) DefaultPresetFileInfo.Create().Dispose(); + } + + private static SerializableOptionsData GenerateOptionsData() + { + Dictionary singleOptions = []; + Dictionary presetOptions = []; + + foreach (OptionItem option in OptionItem.AllOptions) + { + if (option.IsSingleValue) { - string presetNmber = File.ReadAllText(DefaultPresetFileInfo.FullName); - if (int.TryParse(presetNmber, out int number) && number is >= 0 and <= OptionItem.NumPresets - 1) return number; + if (!singleOptions.TryAdd(option.Id, option.SingleValue)) EHR.Logger.Warn($"Duplicate SingleOption ID: {option.Id}", "Options Load"); } - - return 0; + else if (!presetOptions.TryAdd(option.Id, option.AllValues)) EHR.Logger.Warn($"Duplicate preset option ID: {option.Id}", "Options Load"); } - public static void Initialize() + DefaultPresetNumber = singleOptions[0]; + + return new() { - if (!SaveDataDirectoryInfo.Exists) - { - SaveDataDirectoryInfo.Create(); - SaveDataDirectoryInfo.Attributes |= FileAttributes.Hidden; - } + Version = Version, + SingleOptions = singleOptions, + PresetOptions = presetOptions + }; + } - if (!OptionSaverFileInfo.Exists) OptionSaverFileInfo.Create().Dispose(); - - if (!DefaultPresetFileInfo.Exists) DefaultPresetFileInfo.Create().Dispose(); - } - - private static SerializableOptionsData GenerateOptionsData() + private static void LoadOptionsData(SerializableOptionsData serializableOptionsData) + { + if (serializableOptionsData.Version != Version) { - Dictionary singleOptions = []; - Dictionary presetOptions = []; - - foreach (OptionItem option in OptionItem.AllOptions) - { - if (option.IsSingleValue) - { - if (!singleOptions.TryAdd(option.Id, option.SingleValue)) EHR.Logger.Warn($"Duplicate SingleOption ID: {option.Id}", "Options Load"); - } - else if (!presetOptions.TryAdd(option.Id, option.AllValues)) EHR.Logger.Warn($"Duplicate preset option ID: {option.Id}", "Options Load"); - } - - DefaultPresetNumber = singleOptions[0]; - - return new() - { - Version = Version, - SingleOptions = singleOptions, - PresetOptions = presetOptions - }; + Logger.Info($"Loaded option version {serializableOptionsData.Version} does not match current version {Version}, overwriting with default value"); + Save(); + return; } - private static void LoadOptionsData(SerializableOptionsData serializableOptionsData) + Dictionary singleOptions = serializableOptionsData.SingleOptions; + Dictionary presetOptions = serializableOptionsData.PresetOptions; + + foreach ((int id, int value) in singleOptions) + if (OptionItem.FastOptions.TryGetValue(id, out OptionItem optionItem)) + optionItem.SetValue(value, doSave: false); + + foreach ((int id, int[] values) in presetOptions) + if (OptionItem.FastOptions.TryGetValue(id, out OptionItem optionItem)) + optionItem.SetAllValues(values); + } + + public static void Save() + { + if (AmongUsClient.Instance != null && !AmongUsClient.Instance.AmHost) return; + + string jsonString = JsonSerializer.Serialize(GenerateOptionsData(), new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(OptionSaverFileInfo.FullName, jsonString); + File.WriteAllText(DefaultPresetFileInfo.FullName, DefaultPresetNumber.ToString()); + } + + public static void Load() + { + string jsonString = File.ReadAllText(OptionSaverFileInfo.FullName); + + if (jsonString.Length <= 0) { - if (serializableOptionsData.Version != Version) - { - Logger.Info($"Loaded option version {serializableOptionsData.Version} does not match current version {Version}, overwriting with default value"); - Save(); - return; - } - - Dictionary singleOptions = serializableOptionsData.SingleOptions; - Dictionary presetOptions = serializableOptionsData.PresetOptions; - - foreach ((int id, int value) in singleOptions) - if (OptionItem.FastOptions.TryGetValue(id, out OptionItem optionItem)) - optionItem.SetValue(value, doSave: false); - - foreach ((int id, int[] values) in presetOptions) - if (OptionItem.FastOptions.TryGetValue(id, out OptionItem optionItem)) - optionItem.SetAllValues(values); + Logger.Info("Save default value as option data is empty"); + Save(); + return; } - public static void Save() - { - if (AmongUsClient.Instance != null && !AmongUsClient.Instance.AmHost) return; + LoadOptionsData(JsonSerializer.Deserialize(jsonString)); + } - string jsonString = JsonSerializer.Serialize(GenerateOptionsData(), new JsonSerializerOptions { WriteIndented = true }); - File.WriteAllText(OptionSaverFileInfo.FullName, jsonString); - File.WriteAllText(DefaultPresetFileInfo.FullName, DefaultPresetNumber.ToString()); - } - - public static void Load() - { - string jsonString = File.ReadAllText(OptionSaverFileInfo.FullName); - - if (jsonString.Length <= 0) - { - Logger.Info("Save default value as option data is empty"); - Save(); - return; - } - - LoadOptionsData(JsonSerializer.Deserialize(jsonString)); - } - - public class SerializableOptionsData - { - public int Version { get; init; } - public Dictionary SingleOptions { get; init; } - public Dictionary PresetOptions { get; init; } - } + public class SerializableOptionsData + { + public int Version { get; init; } + public Dictionary SingleOptions { get; init; } + public Dictionary PresetOptions { get; init; } } } \ No newline at end of file diff --git a/Modules/OptionShower.cs b/Modules/OptionShower.cs index 9741c6893..04d28c891 100644 --- a/Modules/OptionShower.cs +++ b/Modules/OptionShower.cs @@ -4,128 +4,127 @@ using HarmonyLib; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +public static class OptionShower { - public static class OptionShower + public static int CurrentPage; + public static List Pages = []; + + private static long LastUpdate; + + static OptionShower() { } + + public static string GetTextNoFresh() { - public static int CurrentPage; - public static List Pages = []; - - private static long LastUpdate; - - static OptionShower() { } - - public static string GetTextNoFresh() + if (Pages.Count < 3 || (CurrentPage == 0 && LastUpdate != Utils.TimeStamp)) { - if (Pages.Count < 3 || (CurrentPage == 0 && LastUpdate != Utils.TimeStamp)) - { - GetText(); - LastUpdate = Utils.TimeStamp; - } - - return $"{Pages[CurrentPage]}{GetString("PressTabToNextPage")}({CurrentPage + 1}/{Pages.Count})"; + GetText(); + LastUpdate = Utils.TimeStamp; } - public static string GetText() + return $"{Pages[CurrentPage]}{GetString("PressTabToNextPage")}({CurrentPage + 1}/{Pages.Count})"; + } + + public static string GetText() + { + StringBuilder sb = new(); + + Pages = + [ + GameOptionsManager.Instance.CurrentGameOptions.ToHudString(GameData.Instance ? GameData.Instance.PlayerCount : 10).Split('\n').SkipLast(8).Join(delimiter: "\n") + "\n\n" + ]; + + sb.Append($"{Options.GameMode.GetName()}: {Options.GameMode.GetString()}\n\n"); + + if (Options.HideGameSettings.GetBool() && !AmongUsClient.Instance.AmHost) + sb.Append($"{GetString("Message.HideGameSettings")}"); + else { - StringBuilder sb = new(); - - Pages = - [ - GameOptionsManager.Instance.CurrentGameOptions.ToHudString(GameData.Instance ? GameData.Instance.PlayerCount : 10).Split('\n').SkipLast(8).Join(delimiter: "\n") + "\n\n" - ]; - - sb.Append($"{Options.GameMode.GetName()}: {Options.GameMode.GetString()}\n\n"); - - if (Options.HideGameSettings.GetBool() && !AmongUsClient.Instance.AmHost) - sb.Append($"{GetString("Message.HideGameSettings")}"); - else + if (CustomGameMode.Standard.IsActiveOrIntegrated()) { - if (CustomGameMode.Standard.IsActiveOrIntegrated()) + sb.Append($"{Utils.GetRoleName(CustomRoles.GM)}: {(Main.GM.Value ? GetString("RoleRate") : GetString("RoleOff"))}\n\n"); + sb.Append(GetString("ActiveRolesList")).Append('\n'); + var count = 4; + + foreach (KeyValuePair kvp in Options.CustomRoleSpawnChances) { - sb.Append($"{Utils.GetRoleName(CustomRoles.GM)}: {(Main.GM.Value ? GetString("RoleRate") : GetString("RoleOff"))}\n\n"); - sb.Append(GetString("ActiveRolesList")).Append('\n'); - var count = 4; - - foreach (KeyValuePair kvp in Options.CustomRoleSpawnChances) + if (kvp.Value.GameMode is CustomGameMode.Standard or CustomGameMode.All && kvp.Value.GetBool()) { - if (kvp.Value.GameMode is CustomGameMode.Standard or CustomGameMode.All && kvp.Value.GetBool()) - { - sb.Append($"{Utils.ColorString(Utils.GetRoleColor(kvp.Key), Utils.GetRoleName(kvp.Key))}: {kvp.Value.GetString()} ×{kvp.Key.GetCount()}\n"); - count++; + sb.Append($"{Utils.ColorString(Utils.GetRoleColor(kvp.Key), Utils.GetRoleName(kvp.Key))}: {kvp.Value.GetString()} ×{kvp.Key.GetCount()}\n"); + count++; - if (count > 44) - { - count = 0; - Pages.Add(sb + "\n\n"); - sb.Clear().Append(GetString("ActiveRolesList")).Append('\n'); - } + if (count > 44) + { + count = 0; + Pages.Add(sb + "\n\n"); + sb.Clear().Append(GetString("ActiveRolesList")).Append('\n'); } } - - Pages.Add(sb + "\n\n"); - sb.Clear(); } - Pages.Add(""); - sb.Append($"{Utils.GetRoleName(CustomRoles.GM)}: {(Main.GM.Value ? GetString("RoleRate") : GetString("RoleOff"))}\n\n"); - - if (!CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) - { - foreach (KeyValuePair kvp in Options.CustomRoleSpawnChances) - { - if (!kvp.Key.IsEnable() || kvp.Value.IsCurrentlyHidden()) continue; - - sb.Append('\n'); - sb.Append($"{Utils.ColorString(Utils.GetRoleColor(kvp.Key), Utils.GetRoleName(kvp.Key))}: {kvp.Value.GetString()} ×{kvp.Key.GetCount()}\n"); - ShowChildren(kvp.Value, ref sb, Utils.GetRoleColor(kvp.Key).ShadeColor(-0.5f), 1); - } - } - - foreach (OptionItem opt in OptionItem.AllOptions) - { - if (opt.Id is >= 90000 and (< 600000 or > 700000) && !opt.IsCurrentlyHidden() && opt.Parent == null && !opt.IsText) - { - if (opt.IsHeader) sb.Append('\n'); - - sb.Append($"{opt.GetName()}: {opt.GetString()}\n"); - if (opt.GetBool()) ShowChildren(opt, ref sb, Color.white, 1); - } - } + Pages.Add(sb + "\n\n"); + sb.Clear(); } - string[] tmp = sb.ToString().Split("\n\n"); + Pages.Add(""); + sb.Append($"{Utils.GetRoleName(CustomRoles.GM)}: {(Main.GM.Value ? GetString("RoleRate") : GetString("RoleOff"))}\n\n"); - foreach (string str in tmp) + if (!CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) { - if (Pages[^1].Count(c => c == '\n') + 1 + str.Count(c => c == '\n') + 1 > 44) - Pages.Add(str + "\n\n"); - else - Pages[^1] += str + "\n\n"; + foreach (KeyValuePair kvp in Options.CustomRoleSpawnChances) + { + if (!kvp.Key.IsEnable() || kvp.Value.IsCurrentlyHidden()) continue; + + sb.Append('\n'); + sb.Append($"{Utils.ColorString(Utils.GetRoleColor(kvp.Key), Utils.GetRoleName(kvp.Key))}: {kvp.Value.GetString()} ×{kvp.Key.GetCount()}\n"); + ShowChildren(kvp.Value, ref sb, Utils.GetRoleColor(kvp.Key).ShadeColor(-0.5f), 1); + } } - if (CurrentPage >= Pages.Count) CurrentPage = Pages.Count - 1; + foreach (OptionItem opt in OptionItem.AllOptions) + { + if (opt.Id is >= 90000 and (< 600000 or > 700000) && !opt.IsCurrentlyHidden() && opt.Parent == null && !opt.IsText) + { + if (opt.IsHeader) sb.Append('\n'); - return $"{Pages[CurrentPage]}{GetString("PressTabToNextPage")}({CurrentPage + 1}/{Pages.Count})"; + sb.Append($"{opt.GetName()}: {opt.GetString()}\n"); + if (opt.GetBool()) ShowChildren(opt, ref sb, Color.white, 1); + } + } } - public static void Next() + string[] tmp = sb.ToString().Split("\n\n"); + + foreach (string str in tmp) { - CurrentPage++; - if (CurrentPage >= Pages.Count) CurrentPage = 0; + if (Pages[^1].Count(c => c == '\n') + 1 + str.Count(c => c == '\n') + 1 > 44) + Pages.Add(str + "\n\n"); + else + Pages[^1] += str + "\n\n"; } - private static void ShowChildren(OptionItem option, ref StringBuilder sb, Color color, int deep = 0) - { - foreach (var opt in option.Children.Select((v, i) => new { Value = v, Index = i + 1 })) - { - if (opt.Value.Name == "Maximum") continue; + if (CurrentPage >= Pages.Count) CurrentPage = Pages.Count - 1; - sb.Append(string.Concat(Enumerable.Repeat(Utils.ColorString(color, "┃"), deep - 1))); - sb.Append(Utils.ColorString(color, opt.Index == option.Children.Count ? "┗ " : "┣ ")); - sb.Append($"{opt.Value.GetName()}: {opt.Value.GetString()}\n"); - if (opt.Value.GetBool()) ShowChildren(opt.Value, ref sb, color, deep + 1); - } + return $"{Pages[CurrentPage]}{GetString("PressTabToNextPage")}({CurrentPage + 1}/{Pages.Count})"; + } + + public static void Next() + { + CurrentPage++; + if (CurrentPage >= Pages.Count) CurrentPage = 0; + } + + private static void ShowChildren(OptionItem option, ref StringBuilder sb, Color color, int deep = 0) + { + foreach (var opt in option.Children.Select((v, i) => new { Value = v, Index = i + 1 })) + { + if (opt.Value.Name == "Maximum") continue; + + sb.Append(string.Concat(Enumerable.Repeat(Utils.ColorString(color, "┃"), deep - 1))); + sb.Append(Utils.ColorString(color, opt.Index == option.Children.Count ? "┗ " : "┣ ")); + sb.Append($"{opt.Value.GetName()}: {opt.Value.GetString()}\n"); + if (opt.Value.GetBool()) ShowChildren(opt.Value, ref sb, color, deep + 1); } } } \ No newline at end of file diff --git a/Modules/RPC.cs b/Modules/RPC.cs index cc55422a5..7621160ac 100644 --- a/Modules/RPC.cs +++ b/Modules/RPC.cs @@ -16,1476 +16,1475 @@ using Hazel; using InnerNet; using static EHR.Translator; -namespace EHR.Modules +namespace EHR.Modules; + +public enum CustomRPC { - public enum CustomRPC + VersionCheck = 102, + RequestRetryVersionCheck, + SyncCustomSettings, + SetDeathReason, + EndGame, + PlaySound, + SetCustomRole, + SetNameColorData, + SetRealKiller, + ShowChat, + SyncLobbyTimer, + AntiBlackout, + PlayCustomSound, + SetKillTimer, + SyncAllPlayerNames, + SyncAllClientRealNames, + SyncNameNotify, + ShowPopUp, + KillFlash, + SyncAbilityUseLimit, + RemoveAbilityUseLimit, + RemoveSubRole, + Arrow, + FixModdedClientCNO, + SyncAbilityCD, + SyncGeneralOptions, + SyncRoleData, + RequestSendMessage, + NotificationPopper, + RequestCommandProcessing, + + // Roles + DoSpell, + SniperSync, + SetLoversPlayers, + SetExecutionerTarget, + RemoveExecutionerTarget, + SetLawyerTarget, + RemoveLawyerTarget, + SendFireWorksState, + SetCurrentDousingTarget, + SetEvilTrackerTarget, + SetBountyTarget, + SetKillOrSpell, + SetDousedPlayer, + SetPlaguedPlayer, + SetDrawPlayer, + SyncHeadHunter, + SyncRabbit, + SyncSoulHunter, + + // BAU should always be 150 + BAU = 150, + + SyncMycologist, + SyncBubble, + AddTornado, + SyncHookshot, + SyncStressedTimer, + SetLibrarianMode, + SyncYinYanger, + DruidAddTrigger, + SyncBenefactorMarkedTask, + SyncMafiosoData, + SyncMafiosoPistolCD, + SyncDamoclesTimer, + SyncChronomancer, + PenguinSync, + SyncPlagueDoctor, + SetAlchemistPotion, + SetRicochetTarget, + SetTetherTarget, + SetHitmanTarget, + SetWeaponMasterMode, + SyncGlitchTimers, + SyncSpy, + SetSabotageMasterLimit, + SetNiceHackerLimit, + SetCurrentDrawTarget, + SetCpTasksDone, + SetGamerHealth, + SetPelicanEtenNum, + SwordsManKill, + SetGhostPlayer, + SetDarkHiderKillCount, + SetEvilDiviner, + SetGreedierOe, + SetCollectorVotes, + SetQuickShooterShotLimit, + GuessKill, + SetMarkedPlayer, + SetMedicalerProtectList, + SyncPsychicRedList, + SetCleanserCleanLimit, + SetJailorTarget, + SetDoppelgangerStealLimit, + SetJailorExeLimit, + SetWwTimer, + SetNiceSwapperVotes, + Judge, + Guess, + MeetingKill, + MafiaRevenge, + SetSwooperTimer, + SetBanditStealLimit, + SetBkTimer, + SyncTotocalcioTargetAndTimes, + SyncRomanticTarget, + SyncVengefulRomanticTarget, + SetRevealedPlayer, + SetCurrentRevealTarget, + SetDoomsayerProgress, + SetTrackerTarget, + RpcPassBomb, + SetAlchemistTimer, + SyncPostman, + SyncChangeling, + SyncTiger, + SyncSentry, + SyncBargainer, + SyncOverheat, + SyncIntrovert, + SyncAllergic, + + // Game Modes + RoomRushDataSync, + FFAKill +} + +public enum Sounds +{ + KillSound, + TaskComplete, + TaskUpdateSound, + ImpTransform +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] +internal static class RPCHandlerPatch +{ + public static readonly Dictionary ReportDeadBodyRPCs = []; + + private static bool TrustedRpc(byte id) { - VersionCheck = 102, - RequestRetryVersionCheck, - SyncCustomSettings, - SetDeathReason, - EndGame, - PlaySound, - SetCustomRole, - SetNameColorData, - SetRealKiller, - ShowChat, - SyncLobbyTimer, - AntiBlackout, - PlayCustomSound, - SetKillTimer, - SyncAllPlayerNames, - SyncAllClientRealNames, - SyncNameNotify, - ShowPopUp, - KillFlash, - SyncAbilityUseLimit, - RemoveAbilityUseLimit, - RemoveSubRole, - Arrow, - FixModdedClientCNO, - SyncAbilityCD, - SyncGeneralOptions, - SyncRoleData, - RequestSendMessage, - NotificationPopper, - RequestCommandProcessing, - - // Roles - DoSpell, - SniperSync, - SetLoversPlayers, - SetExecutionerTarget, - RemoveExecutionerTarget, - SetLawyerTarget, - RemoveLawyerTarget, - SendFireWorksState, - SetCurrentDousingTarget, - SetEvilTrackerTarget, - SetBountyTarget, - SetKillOrSpell, - SetDousedPlayer, - SetPlaguedPlayer, - SetDrawPlayer, - SyncHeadHunter, - SyncRabbit, - SyncSoulHunter, - - // BAU should always be 150 - BAU = 150, - - SyncMycologist, - SyncBubble, - AddTornado, - SyncHookshot, - SyncStressedTimer, - SetLibrarianMode, - SyncYinYanger, - DruidAddTrigger, - SyncBenefactorMarkedTask, - SyncMafiosoData, - SyncMafiosoPistolCD, - SyncDamoclesTimer, - SyncChronomancer, - PenguinSync, - SyncPlagueDoctor, - SetAlchemistPotion, - SetRicochetTarget, - SetTetherTarget, - SetHitmanTarget, - SetWeaponMasterMode, - SyncGlitchTimers, - SyncSpy, - SetSabotageMasterLimit, - SetNiceHackerLimit, - SetCurrentDrawTarget, - SetCpTasksDone, - SetGamerHealth, - SetPelicanEtenNum, - SwordsManKill, - SetGhostPlayer, - SetDarkHiderKillCount, - SetEvilDiviner, - SetGreedierOe, - SetCollectorVotes, - SetQuickShooterShotLimit, - GuessKill, - SetMarkedPlayer, - SetMedicalerProtectList, - SyncPsychicRedList, - SetCleanserCleanLimit, - SetJailorTarget, - SetDoppelgangerStealLimit, - SetJailorExeLimit, - SetWwTimer, - SetNiceSwapperVotes, - Judge, - Guess, - MeetingKill, - MafiaRevenge, - SetSwooperTimer, - SetBanditStealLimit, - SetBkTimer, - SyncTotocalcioTargetAndTimes, - SyncRomanticTarget, - SyncVengefulRomanticTarget, - SetRevealedPlayer, - SetCurrentRevealTarget, - SetDoomsayerProgress, - SetTrackerTarget, - RpcPassBomb, - SetAlchemistTimer, - SyncPostman, - SyncChangeling, - SyncTiger, - SyncSentry, - SyncBargainer, - SyncOverheat, - SyncIntrovert, - SyncAllergic, - - // Game Modes - RoomRushDataSync, - FFAKill + return (CustomRPC)id is CustomRPC.VersionCheck or CustomRPC.RequestRetryVersionCheck or CustomRPC.AntiBlackout or CustomRPC.SyncNameNotify or CustomRPC.RequestSendMessage or CustomRPC.RequestCommandProcessing or CustomRPC.Judge or CustomRPC.SetNiceSwapperVotes or CustomRPC.MeetingKill or CustomRPC.Guess or CustomRPC.MafiaRevenge or CustomRPC.BAU or CustomRPC.FFAKill; } - public enum Sounds + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) { - KillSound, - TaskComplete, - TaskUpdateSound, - ImpTransform - } + var rpcType = (RpcCalls)callId; + MessageReader subReader = MessageReader.Get(reader); + if (EAC.ReceiveRpc(__instance, callId, reader)) return false; - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] - internal static class RPCHandlerPatch - { - public static readonly Dictionary ReportDeadBodyRPCs = []; + Logger.Info($"From ID: {__instance?.Data?.PlayerId} ({(__instance?.Data?.PlayerId == 0 ? "Host" : __instance?.Data?.PlayerName)}) : {callId} ({RPC.GetRpcName(callId)})", "ReceiveRPC"); - private static bool TrustedRpc(byte id) + if (callId == 11 && __instance != null) { - return (CustomRPC)id is CustomRPC.VersionCheck or CustomRPC.RequestRetryVersionCheck or CustomRPC.AntiBlackout or CustomRPC.SyncNameNotify or CustomRPC.RequestSendMessage or CustomRPC.RequestCommandProcessing or CustomRPC.Judge or CustomRPC.SetNiceSwapperVotes or CustomRPC.MeetingKill or CustomRPC.Guess or CustomRPC.MafiaRevenge or CustomRPC.BAU or CustomRPC.FFAKill; + if (!ReportDeadBodyRPCs.ContainsKey(__instance.PlayerId)) ReportDeadBodyRPCs.TryAdd(__instance.PlayerId, 0); + + ReportDeadBodyRPCs[__instance.PlayerId]++; + Logger.Info($"ReportDeadBody RPC count: {ReportDeadBodyRPCs[__instance.PlayerId]}, from {__instance.Data?.PlayerName}", "EAC"); } - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + switch (rpcType) { - var rpcType = (RpcCalls)callId; - MessageReader subReader = MessageReader.Get(reader); - if (EAC.ReceiveRpc(__instance, callId, reader)) return false; + case RpcCalls.SetName: + subReader.ReadUInt32(); + string name = subReader.ReadString(); + if (subReader.BytesRemaining > 0 && subReader.ReadBoolean()) return false; - Logger.Info($"From ID: {__instance?.Data?.PlayerId} ({(__instance?.Data?.PlayerId == 0 ? "Host" : __instance?.Data?.PlayerName)}) : {callId} ({RPC.GetRpcName(callId)})", "ReceiveRPC"); + Logger.Info($"RPC Set Name For Player: {__instance.GetNameWithRole().RemoveHtmlTags()} => {name}", "SetName"); + break; + case RpcCalls.SetRole: + var role = (RoleTypes)subReader.ReadUInt16(); + bool canOverriddenRole = subReader.ReadBoolean(); + Logger.Info($"RPC Set Role For Player: {__instance.GetRealName()} => {role} CanOverrideRole: {canOverriddenRole}", "SetRole"); + break; + case RpcCalls.SendChat: + string text = subReader.ReadString(); + Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}:{text}", "ReceiveChat"); + ChatCommands.OnReceiveChat(__instance, text, out bool canceled); + if (canceled) return false; - if (callId == 11 && __instance != null) - { - if (!ReportDeadBodyRPCs.ContainsKey(__instance.PlayerId)) ReportDeadBodyRPCs.TryAdd(__instance.PlayerId, 0); - - ReportDeadBodyRPCs[__instance.PlayerId]++; - Logger.Info($"ReportDeadBody RPC count: {ReportDeadBodyRPCs[__instance.PlayerId]}, from {__instance.Data?.PlayerName}", "EAC"); - } - - switch (rpcType) - { - case RpcCalls.SetName: - subReader.ReadUInt32(); - string name = subReader.ReadString(); - if (subReader.BytesRemaining > 0 && subReader.ReadBoolean()) return false; - - Logger.Info($"RPC Set Name For Player: {__instance.GetNameWithRole().RemoveHtmlTags()} => {name}", "SetName"); - break; - case RpcCalls.SetRole: - var role = (RoleTypes)subReader.ReadUInt16(); - bool canOverriddenRole = subReader.ReadBoolean(); - Logger.Info($"RPC Set Role For Player: {__instance.GetRealName()} => {role} CanOverrideRole: {canOverriddenRole}", "SetRole"); - break; - case RpcCalls.SendChat: - string text = subReader.ReadString(); - Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}:{text}", "ReceiveChat"); - ChatCommands.OnReceiveChat(__instance, text, out bool canceled); - if (canceled) return false; - - break; - case RpcCalls.StartMeeting: - PlayerControl p = Utils.GetPlayerById(subReader.ReadByte()); - Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); - break; - case RpcCalls.Pet: - Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} petted their pet", "RpcHandlerPatch"); - break; - case RpcCalls.SetScanner when Main.HasJustStarted: - Logger.Fatal($"{__instance.GetNameWithRole().RemoveHtmlTags()} triggered this bs ---- revive was attempted", "RpcHandlerPatch"); - __instance?.Revive(); - return false; - } - - if (__instance != null && !__instance.IsHost() && Enum.IsDefined(typeof(CustomRPC), (int)callId) && !TrustedRpc(callId)) - { - Logger.Warn($"{__instance.Data?.PlayerName}:{callId}({RPC.GetRpcName(callId)}) canceled because it was sent by someone other than the host.", "CustomRPC"); - if (!AmongUsClient.Instance.AmHost) return false; - - if (!EAC.ReceiveInvalidRpc(__instance, callId)) return false; - - AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); - Logger.Warn($"The RPC received from {__instance.Data?.PlayerName} is not trusted, so they were kicked.", "Kick"); - Logger.SendInGame(string.Format(GetString("Warning.InvalidRpc"), __instance.Data?.PlayerName)); + break; + case RpcCalls.StartMeeting: + PlayerControl p = Utils.GetPlayerById(subReader.ReadByte()); + Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {p?.GetNameWithRole() ?? "null"}", "StartMeeting"); + break; + case RpcCalls.Pet: + Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} petted their pet", "RpcHandlerPatch"); + break; + case RpcCalls.SetScanner when Main.HasJustStarted: + Logger.Fatal($"{__instance.GetNameWithRole().RemoveHtmlTags()} triggered this bs ---- revive was attempted", "RpcHandlerPatch"); + __instance?.Revive(); return false; - } + } - if (__instance != null && (!ReportDeadBodyRPCs.TryGetValue(__instance.PlayerId, out int times) || times <= 4)) return true; + if (__instance != null && !__instance.IsHost() && Enum.IsDefined(typeof(CustomRPC), (int)callId) && !TrustedRpc(callId)) + { + Logger.Warn($"{__instance.Data?.PlayerName}:{callId}({RPC.GetRpcName(callId)}) canceled because it was sent by someone other than the host.", "CustomRPC"); + if (!AmongUsClient.Instance.AmHost) return false; - AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), true); - Logger.Warn($"{__instance?.Data?.PlayerName} has sent 5 or more ReportDeadBody RPCs in the last 1 second, they were banned for hacking.", "EAC"); - Logger.SendInGame(string.Format(GetString("Warning.ReportDeadBodyHack"), __instance?.Data?.PlayerName)); + if (!EAC.ReceiveInvalidRpc(__instance, callId)) return false; + + AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); + Logger.Warn($"The RPC received from {__instance.Data?.PlayerName} is not trusted, so they were kicked.", "Kick"); + Logger.SendInGame(string.Format(GetString("Warning.InvalidRpc"), __instance.Data?.PlayerName)); return false; } - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + if (__instance != null && (!ReportDeadBodyRPCs.TryGetValue(__instance.PlayerId, out int times) || times <= 4)) return true; + + AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), true); + Logger.Warn($"{__instance?.Data?.PlayerName} has sent 5 or more ReportDeadBody RPCs in the last 1 second, they were banned for hacking.", "EAC"); + Logger.SendInGame(string.Format(GetString("Warning.ReportDeadBodyHack"), __instance?.Data?.PlayerName)); + return false; + } + + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + { + var rpcType = (CustomRPC)callId; + if (AmongUsClient.Instance.AmHost && !TrustedRpc(callId)) return; + + switch (rpcType) { - var rpcType = (CustomRPC)callId; - if (AmongUsClient.Instance.AmHost && !TrustedRpc(callId)) return; - - switch (rpcType) + case CustomRPC.AntiBlackout: { - case CustomRPC.AntiBlackout: + if (Options.EndWhenPlayerBug.GetBool()) { - if (Options.EndWhenPlayerBug.GetBool()) - { - Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance?.PlayerId}): {reader.ReadString()} - Error, terminate the game according to settings", "Anti-blackout"); - ChatUpdatePatch.DoBlockChat = true; - Main.OverrideWelcomeMsg = string.Format(GetString("RpcAntiBlackOutNotifyInLobby"), __instance?.Data?.PlayerName, GetString("EndWhenPlayerBug")); - LateTask.New(() => { Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutEndGame"), __instance?.Data?.PlayerName) /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); + Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance?.PlayerId}): {reader.ReadString()} - Error, terminate the game according to settings", "Anti-blackout"); + ChatUpdatePatch.DoBlockChat = true; + Main.OverrideWelcomeMsg = string.Format(GetString("RpcAntiBlackOutNotifyInLobby"), __instance?.Data?.PlayerName, GetString("EndWhenPlayerBug")); + LateTask.New(() => { Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutEndGame"), __instance?.Data?.PlayerName) /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); + LateTask.New(() => + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + RPC.ForceEndGame(CustomWinner.Error); + }, 5.5f, "Anti-Black End Game"); + + LateTask.New(() => ChatUpdatePatch.DoBlockChat = false, 6f, log: false); + } + else if (GameStates.IsOnlineGame) + { + Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance?.PlayerId}): Change Role Setting Postfix - Error, continue the game according to settings", "Anti-blackout"); + LateTask.New(() => { Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutIgnored"), __instance?.Data?.PlayerName) /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); + } + + break; + } + case CustomRPC.VersionCheck: + { + try + { + Version version = Version.Parse(reader.ReadString()); + string tag = reader.ReadString(); + string forkId = reader.ReadString(); + + try + { + if (!Main.PlayerVersion.ContainsKey(__instance.PlayerId)) RPC.RpcVersionCheck(); + } + catch { } + + Main.PlayerVersion[__instance.PlayerId] = new(version, tag, forkId); + + // Kick Unmached Player Start + if (AmongUsClient.Instance.AmHost && tag != $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" && forkId != Main.ForkId) + { LateTask.New(() => { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - RPC.ForceEndGame(CustomWinner.Error); - }, 5.5f, "Anti-Black End Game"); - - LateTask.New(() => ChatUpdatePatch.DoBlockChat = false, 6f, log: false); + if (__instance.Data?.Disconnected is not null and not true) + { + string msg = string.Format(GetString("KickBecauseDiffrentVersionOrMod"), __instance.Data?.PlayerName); + Logger.Warn(msg, "Version Kick"); + Logger.SendInGame(msg); + AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); + } + }, 5f, "Kick"); } - else if (GameStates.IsOnlineGame) - { - Logger.Fatal($"{__instance?.Data?.PlayerName}({__instance?.PlayerId}): Change Role Setting Postfix - Error, continue the game according to settings", "Anti-blackout"); - LateTask.New(() => { Logger.SendInGame(string.Format(GetString("RpcAntiBlackOutIgnored"), __instance?.Data?.PlayerName) /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); - } - - break; + // Kick Unmached Player End } - case CustomRPC.VersionCheck: + catch (Exception e) + { + Logger.Warn($"{__instance?.Data?.PlayerName}({__instance?.PlayerId}): error during version check", "RpcVersionCheck"); + Utils.ThrowException(e); + if (GameStates.InGame || AmongUsClient.Instance.IsGameStarted) break; + + LateTask.New(() => + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, __instance.GetClientId()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + }, 1f, "Retry Version Check Task"); + } + + break; + } + case CustomRPC.RequestRetryVersionCheck: + { + RPC.RpcVersionCheck(); + break; + } + case CustomRPC.SyncCustomSettings: + { + if (AmongUsClient.Instance.AmHost) break; + + List listOptions = []; + int startAmount = reader.ReadInt32(); + int lastAmount = reader.ReadInt32(); + + int countAllOptions = OptionItem.AllOptions.Count; + + // Add Options + for (int option = startAmount; option < countAllOptions && option <= lastAmount; option++) listOptions.Add(OptionItem.AllOptions[option]); + + int countOptions = listOptions.Count; + Logger.Msg($"StartAmount: {startAmount} - LastAmount: {lastAmount} ({startAmount}/{lastAmount}) :--: ListOptionsCount: {countOptions} - AllOptions: {countAllOptions} ({countOptions}/{countAllOptions})", "SyncCustomSettings"); + + // Sync Settings + foreach (OptionItem option in listOptions) { try { - Version version = Version.Parse(reader.ReadString()); - string tag = reader.ReadString(); - string forkId = reader.ReadString(); - - try - { - if (!Main.PlayerVersion.ContainsKey(__instance.PlayerId)) RPC.RpcVersionCheck(); - } - catch { } - - Main.PlayerVersion[__instance.PlayerId] = new(version, tag, forkId); - - // Kick Unmached Player Start - if (AmongUsClient.Instance.AmHost && tag != $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" && forkId != Main.ForkId) - { - LateTask.New(() => - { - if (__instance.Data?.Disconnected is not null and not true) - { - string msg = string.Format(GetString("KickBecauseDiffrentVersionOrMod"), __instance.Data?.PlayerName); - Logger.Warn(msg, "Version Kick"); - Logger.SendInGame(msg); - AmongUsClient.Instance.KickPlayer(__instance.GetClientId(), false); - } - }, 5f, "Kick"); - } - // Kick Unmached Player End + option.SetValue(reader.ReadPackedInt32()); } - catch (Exception e) + catch { } + } + + OptionShower.GetText(); + break; + } + case CustomRPC.SetDeathReason: + { + RPC.GetDeathReason(reader); + break; + } + case CustomRPC.EndGame: + { + RPC.EndGame(reader); + break; + } + case CustomRPC.PlaySound: + { + byte playerID = reader.ReadByte(); + var sound = (Sounds)reader.ReadByte(); + RPC.PlaySound(playerID, sound); + break; + } + case CustomRPC.ShowPopUp: + { + string msg = reader.ReadString(); + HudManager.Instance.ShowPopUp(msg); + break; + } + case CustomRPC.SyncAllClientRealNames: + { + Main.AllClientRealNames.Clear(); + int num2 = reader.ReadPackedInt32(); + for (var i = 0; i < num2; i++) Main.AllClientRealNames.TryAdd(reader.ReadInt32(), reader.ReadString()); + + break; + } + case CustomRPC.SetCustomRole: + { + byte CustomRoleTargetId = reader.ReadByte(); + var role = (CustomRoles)reader.ReadPackedInt32(); + bool replaceAllAddons = reader.ReadBoolean(); + RPC.SetCustomRole(CustomRoleTargetId, role, replaceAllAddons); + break; + } + case CustomRPC.SyncAbilityUseLimit: + { + PlayerControl pc = Utils.GetPlayerById(reader.ReadByte()); + pc.SetAbilityUseLimit(reader.ReadSingle(), false); + break; + } + case CustomRPC.RemoveAbilityUseLimit: + { + Main.AbilityUseLimit.Remove(reader.ReadByte()); + break; + } + case CustomRPC.RemoveSubRole: + { + byte id = reader.ReadByte(); + + if (reader.ReadPackedInt32() == 2) + Main.PlayerStates[id].SubRoles.Clear(); + else + Main.PlayerStates[id].RemoveSubRole((CustomRoles)reader.ReadPackedInt32()); + + break; + } + case CustomRPC.Arrow: + { + if (reader.ReadBoolean()) + TargetArrow.ReceiveRPC(reader); + else + LocateArrow.ReceiveRPC(reader); + + break; + } + case CustomRPC.SyncAbilityCD: + { + switch (reader.ReadPackedInt32()) + { + case 1: { - Logger.Warn($"{__instance?.Data?.PlayerName}({__instance?.PlayerId}): error during version check", "RpcVersionCheck"); - Utils.ThrowException(e); - if (GameStates.InGame || AmongUsClient.Instance.IsGameStarted) break; - - LateTask.New(() => - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, __instance.GetClientId()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - }, 1f, "Retry Version Check Task"); + byte id = reader.ReadByte(); + int cd = reader.ReadPackedInt32(); + long ts = Utils.TimeStamp; + Main.AbilityCD[id] = (ts, cd); + break; } - - break; - } - case CustomRPC.RequestRetryVersionCheck: - { - RPC.RpcVersionCheck(); - break; - } - case CustomRPC.SyncCustomSettings: - { - if (AmongUsClient.Instance.AmHost) break; - - List listOptions = []; - int startAmount = reader.ReadInt32(); - int lastAmount = reader.ReadInt32(); - - int countAllOptions = OptionItem.AllOptions.Count; - - // Add Options - for (int option = startAmount; option < countAllOptions && option <= lastAmount; option++) listOptions.Add(OptionItem.AllOptions[option]); - - int countOptions = listOptions.Count; - Logger.Msg($"StartAmount: {startAmount} - LastAmount: {lastAmount} ({startAmount}/{lastAmount}) :--: ListOptionsCount: {countOptions} - AllOptions: {countAllOptions} ({countOptions}/{countAllOptions})", "SyncCustomSettings"); - - // Sync Settings - foreach (OptionItem option in listOptions) + case 2: { - try - { - option.SetValue(reader.ReadPackedInt32()); - } - catch { } + Main.AbilityCD.Clear(); + break; } - - OptionShower.GetText(); - break; - } - case CustomRPC.SetDeathReason: - { - RPC.GetDeathReason(reader); - break; - } - case CustomRPC.EndGame: - { - RPC.EndGame(reader); - break; - } - case CustomRPC.PlaySound: - { - byte playerID = reader.ReadByte(); - var sound = (Sounds)reader.ReadByte(); - RPC.PlaySound(playerID, sound); - break; - } - case CustomRPC.ShowPopUp: - { - string msg = reader.ReadString(); - HudManager.Instance.ShowPopUp(msg); - break; - } - case CustomRPC.SyncAllClientRealNames: - { - Main.AllClientRealNames.Clear(); - int num2 = reader.ReadPackedInt32(); - for (var i = 0; i < num2; i++) Main.AllClientRealNames.TryAdd(reader.ReadInt32(), reader.ReadString()); - - break; - } - case CustomRPC.SetCustomRole: - { - byte CustomRoleTargetId = reader.ReadByte(); - var role = (CustomRoles)reader.ReadPackedInt32(); - bool replaceAllAddons = reader.ReadBoolean(); - RPC.SetCustomRole(CustomRoleTargetId, role, replaceAllAddons); - break; - } - case CustomRPC.SyncAbilityUseLimit: - { - PlayerControl pc = Utils.GetPlayerById(reader.ReadByte()); - pc.SetAbilityUseLimit(reader.ReadSingle(), false); - break; - } - case CustomRPC.RemoveAbilityUseLimit: - { - Main.AbilityUseLimit.Remove(reader.ReadByte()); - break; - } - case CustomRPC.RemoveSubRole: - { - byte id = reader.ReadByte(); - - if (reader.ReadPackedInt32() == 2) - Main.PlayerStates[id].SubRoles.Clear(); - else - Main.PlayerStates[id].RemoveSubRole((CustomRoles)reader.ReadPackedInt32()); - - break; - } - case CustomRPC.Arrow: - { - if (reader.ReadBoolean()) - TargetArrow.ReceiveRPC(reader); - else - LocateArrow.ReceiveRPC(reader); - - break; - } - case CustomRPC.SyncAbilityCD: - { - switch (reader.ReadPackedInt32()) + case 3: { - case 1: - { - byte id = reader.ReadByte(); - int cd = reader.ReadPackedInt32(); - long ts = Utils.TimeStamp; - Main.AbilityCD[id] = (ts, cd); - break; - } - case 2: - { - Main.AbilityCD.Clear(); - break; - } - case 3: - { - byte id = reader.ReadByte(); - Main.AbilityCD.Remove(id); - break; - } - } - - break; - } - case CustomRPC.SyncRoleData: - { - byte id = reader.ReadByte(); - RoleBase r = Main.PlayerStates[id].Role; - r.GetType().GetMethod("ReceiveRPC")?.Invoke(r, [reader]); - break; - } - case CustomRPC.FixModdedClientCNO: - { - var CNO = reader.ReadNetObject(); - bool active = reader.ReadBoolean(); - CNO.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(active); - break; - } - case CustomRPC.SyncGeneralOptions: - { - byte id = reader.ReadByte(); - var role = (CustomRoles)reader.ReadPackedInt32(); - bool dead = reader.ReadBoolean(); - var dr = (PlayerState.DeathReason)reader.ReadPackedInt32(); - - if (Main.PlayerStates.TryGetValue(id, out PlayerState state)) - { - state.MainRole = role; - state.IsDead = dead; - state.deathReason = dr; - } - - float kcd = reader.ReadSingle(); - float speed = reader.ReadSingle(); - Main.AllPlayerKillCooldown[id] = kcd; - Main.AllPlayerSpeed[id] = speed; - break; - } - case CustomRPC.RequestSendMessage: - { - if (!AmongUsClient.Instance.AmHost) break; - - string text = reader.ReadString(); - byte sendTo = reader.ReadByte(); - string title = reader.ReadString(); - bool noSplit = reader.ReadBoolean(); - Utils.SendMessage(text, sendTo, title, noSplit); - break; - } - case CustomRPC.NotificationPopper: - { - byte typeId = reader.ReadByte(); - int item = reader.ReadPackedInt32(); - int customRole = reader.ReadPackedInt32(); - bool playSound = reader.ReadBoolean(); - OptionItem key = OptionItem.AllOptions[item]; - - switch (typeId) - { - case 0: - NotificationPopperPatch.AddSettingsChangeMessage(item, key, playSound); - break; - case 1: - NotificationPopperPatch.AddRoleSettingsChangeMessage(item, key, (CustomRoles)customRole, playSound); - break; - } - - break; - } - case CustomRPC.RequestCommandProcessing: - { - if (!AmongUsClient.Instance.AmHost) break; - - string methodName = reader.ReadString(); - PlayerControl player = reader.ReadByte().GetPlayer(); - string text = reader.ReadString(); - - typeof(ChatCommands).GetMethod(methodName, BindingFlags.Static)?.Invoke(null, [null, player, text, text.Split(' ')]); - break; - } - case CustomRPC.SyncPostman: - { - byte id = reader.ReadByte(); - byte target = reader.ReadByte(); - bool isFinished = reader.ReadBoolean(); - if (Main.PlayerStates[id].Role is not Postman pm) break; - - pm.Target = target; - pm.IsFinished = isFinished; - break; - } - case CustomRPC.SyncChangeling: - { - if (Main.PlayerStates[reader.ReadByte()].Role is not Changeling changeling) break; - - changeling.CurrentRole = (CustomRoles)reader.ReadPackedInt32(); - break; - } - case CustomRPC.SyncTiger: - { - if (Main.PlayerStates[reader.ReadByte()].Role is not Tiger tiger) break; - - tiger.EnrageTimer = reader.ReadSingle(); - break; - } - case CustomRPC.SyncSentry: - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not Crewmate.Sentry sentry) break; - - sentry.MonitoredRoom = Utils.GetPlayerById(id).GetPlainShipRoom(); - break; - } - case CustomRPC.SyncOverheat: - { - ((Overheat)Main.PlayerStates[reader.ReadByte()].Role).Temperature = reader.ReadPackedInt32(); - break; - } - case CustomRPC.SyncIntrovert: - { - Introvert.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncAllergic: - { - Allergic.ReceiveRPC(reader); - break; - } - case CustomRPC.SetBountyTarget: - { - byte bountyId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - (Main.PlayerStates[bountyId].Role as BountyHunter)?.ReceiveRPC(bountyId, targetId); - break; - } - case CustomRPC.SyncBargainer: - { - Bargainer.ReceiveRPC(reader); - break; - } - case CustomRPC.SetKillOrSpell: - { - Witch.ReceiveRPC(reader, false); - break; - } - case CustomRPC.SetCpTasksDone: - { - Crewpostor.RecieveRPC(reader); - break; - } - case CustomRPC.SetLibrarianMode: - { - Librarian.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncSoulHunter: - { - SoulHunter.ReceiveRPC(reader); - break; - } - case CustomRPC.SetDousedPlayer: - { - byte ArsonistId = reader.ReadByte(); - byte dousedId = reader.ReadByte(); - bool doused = reader.ReadBoolean(); - Arsonist.IsDoused[(ArsonistId, dousedId)] = doused; - break; - } - case CustomRPC.SetPlaguedPlayer: - { - PlagueBearer.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncHeadHunter: - { - HeadHunter.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncDamoclesTimer: - { - Damocles.ReceiveRPC(reader); - break; - } - case CustomRPC.SetDrawPlayer: - { - byte RevolutionistId = reader.ReadByte(); - byte DrawId = reader.ReadByte(); - bool drawed = reader.ReadBoolean(); - Revolutionist.IsDraw[(RevolutionistId, DrawId)] = drawed; - break; - } - case CustomRPC.SetRevealedPlayer: - { - byte FarseerId = reader.ReadByte(); - byte RevealId = reader.ReadByte(); - bool revealed = reader.ReadBoolean(); - Farseer.IsRevealed[(FarseerId, RevealId)] = revealed; - break; - } - case CustomRPC.SetNameColorData: - { - NameColorManager.ReceiveRPC(reader); - break; - } - case CustomRPC.RpcPassBomb: - { - Agitater.ReceiveRPC(reader); - break; - } - case CustomRPC.DoSpell: - { - Witch.ReceiveRPC(reader, true); - break; - } - case CustomRPC.SetBanditStealLimit: - { - Bandit.ReceiveRPC(reader); - break; - } - case CustomRPC.SniperSync: - { - byte id = reader.ReadByte(); - (Main.PlayerStates[id].Role as Sniper)?.ReceiveRPC(reader); - } - - break; - case CustomRPC.SyncSpy: - { - Spy.ReceiveRPC(reader); - break; - } - case CustomRPC.SetAlchemistPotion: - { - Alchemist.ReceiveRPCData(reader); - break; - } - case CustomRPC.SetRicochetTarget: - { - Ricochet.ReceiveRPCSyncTarget(reader); - break; - } - case CustomRPC.SyncRabbit: - { - Rabbit.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncYinYanger: - { - YinYanger.ReceiveRPC(reader); - break; - } - case CustomRPC.SetTetherTarget: - { - Tether.ReceiveRPCSyncTarget(reader); - break; - } - case CustomRPC.SetHitmanTarget: - { - byte hitmanId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - (Main.PlayerStates[hitmanId].Role as Hitman)?.ReceiveRPC(targetId); - } - - break; - case CustomRPC.SetWeaponMasterMode: - { - WeaponMaster.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncGlitchTimers: - { - Glitch.ReceiveRPCSyncTimers(reader); - break; - } - case CustomRPC.DruidAddTrigger: - { - Druid.ReceiveRPCAddTrigger(reader); - break; - } - case CustomRPC.SetSabotageMasterLimit: - { - SabotageMaster.ReceiveRPC(reader); - break; - } - case CustomRPC.SetNiceHackerLimit: - { - NiceHacker.ReceiveRPC(reader); - break; - } - case CustomRPC.SetLoversPlayers: - { - Main.LoversPlayers.Clear(); - int count = reader.ReadInt32(); - for (var i = 0; i < count; i++) Main.LoversPlayers.Add(Utils.GetPlayerById(reader.ReadByte())); - } - - break; - case CustomRPC.SetExecutionerTarget: - { - Executioner.ReceiveRPC(reader, true); - break; - } - case CustomRPC.RemoveExecutionerTarget: - { - Executioner.ReceiveRPC(reader, false); - break; - } - case CustomRPC.SetLawyerTarget: - { - Lawyer.ReceiveRPC(reader, true); - break; - } - case CustomRPC.RemoveLawyerTarget: - { - Lawyer.ReceiveRPC(reader, false); - break; - } - case CustomRPC.SendFireWorksState: - { - byte id = reader.ReadByte(); - int count = reader.ReadInt32(); - int state = reader.ReadInt32(); - var newState = (FireWorks.FireWorksState)state; - (Main.PlayerStates[id].Role as FireWorks)?.ReceiveRPC(count, newState); - } - - break; - case CustomRPC.SyncMycologist: - { - Mycologist.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncBubble: - { - Bubble.ReceiveRPC(reader); - break; - } - case CustomRPC.SetCurrentDousingTarget: - { - byte arsonistId = reader.ReadByte(); - byte dousingTargetId = reader.ReadByte(); - if (PlayerControl.LocalPlayer.PlayerId == arsonistId) Arsonist.CurrentDousingTarget = dousingTargetId; - - break; - } - case CustomRPC.SetCurrentDrawTarget: - { - byte arsonistId1 = reader.ReadByte(); - byte doTargetId = reader.ReadByte(); - if (PlayerControl.LocalPlayer.PlayerId == arsonistId1) Revolutionist.CurrentDrawTarget = doTargetId; - - break; - } - case CustomRPC.SetEvilTrackerTarget: - { - EvilTracker.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncPlagueDoctor: - { - PlagueDoctor.ReceiveRPC(reader); - break; - } - case CustomRPC.PenguinSync: - { - byte id = reader.ReadByte(); - int operate = reader.ReadInt32(); - - if (operate == 1) - { - byte victim = reader.ReadByte(); - (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPC(victim); - } - else - { - float timer = reader.ReadSingle(); - (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPC(timer); - } - - break; - } - case CustomRPC.SetRealKiller: - { - byte targetId = reader.ReadByte(); - byte killerId = reader.ReadByte(); - RPC.SetRealKiller(targetId, killerId); - break; - } - case CustomRPC.ShowChat: - { - uint clientId = reader.ReadPackedUInt32(); - bool show = reader.ReadBoolean(); - if (AmongUsClient.Instance.ClientId == clientId) HudManager.Instance.Chat.SetVisible(show); - - break; - } - case CustomRPC.SyncLobbyTimer: - { - GameStartManagerPatch.Timer = reader.ReadPackedInt32(); - break; - } - case CustomRPC.SetGamerHealth: - { - Gamer.ReceiveRPC(reader); - break; - } - case CustomRPC.SetPelicanEtenNum: - { - Pelican.ReceiveRPC(reader); - break; - } - case CustomRPC.SetDoomsayerProgress: - { - Doomsayer.ReceiveRPC(reader); - break; - } - case CustomRPC.SwordsManKill: - { - SwordsMan.ReceiveRPC(reader); - break; - } - case CustomRPC.PlayCustomSound: - { - CustomSoundsManager.ReceiveRPC(reader); - break; - } - case CustomRPC.SetGhostPlayer: - { - BallLightning.ReceiveRPC(reader); - break; - } - case CustomRPC.SetDarkHiderKillCount: - { - DarkHide.ReceiveRPC(reader); - break; - } - case CustomRPC.SetGreedierOe: - { - byte id = reader.ReadByte(); - bool isOdd = reader.ReadBoolean(); - (Main.PlayerStates[id].Role as Greedier)?.ReceiveRPC(isOdd); - } - - break; - case CustomRPC.SetCollectorVotes: - { - Collector.ReceiveRPC(reader); - break; - } - case CustomRPC.SetQuickShooterShotLimit: - { - QuickShooter.ReceiveRPC(reader); - break; - } - case CustomRPC.GuessKill: - { - GuessManager.RpcClientGuess(Utils.GetPlayerById(reader.ReadByte())); - break; - } - case CustomRPC.SetMarkedPlayer: - { - Assassin.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncChronomancer: - { - byte id = reader.ReadByte(); - bool isRampaging = reader.ReadBoolean(); - int chargePercent = reader.ReadInt32(); - long lastUpdate = long.Parse(reader.ReadString()); - (Main.PlayerStates[id].Role as Chronomancer)?.ReceiveRPC(isRampaging, chargePercent, lastUpdate); - } - - break; - case CustomRPC.SetMedicalerProtectList: - { - Medic.ReceiveRPCForProtectList(reader); - break; - } - case CustomRPC.SyncPsychicRedList: - { - Psychic.ReceiveRPC(reader); - break; - } - case CustomRPC.SetKillTimer: - { - float time = reader.ReadSingle(); - PlayerControl.LocalPlayer.SetKillTimer(time); - break; - } - case CustomRPC.SyncAllPlayerNames: - { - Main.AllPlayerNames = []; - int num = reader.ReadInt32(); - for (var i = 0; i < num; i++) Main.AllPlayerNames.TryAdd(reader.ReadByte(), reader.ReadString()); - - break; - } - case CustomRPC.SyncMafiosoData: - { - byte id = reader.ReadByte(); - (Main.PlayerStates[id].Role as Mafioso)?.ReceiveRPC(reader); - } - - break; - case CustomRPC.SyncMafiosoPistolCD: - { - byte id = reader.ReadByte(); - (Main.PlayerStates[id].Role as Mafioso)?.ReceiveRPCSyncPistolCD(reader); - } - - break; - case CustomRPC.SyncNameNotify: - { - NameNotifyManager.ReceiveRPC(reader); - break; - } - case CustomRPC.Judge: - { - Judge.ReceiveRPC(reader, __instance); - break; - } - case CustomRPC.MeetingKill: - { - Councillor.ReceiveRPC(reader, __instance); - break; - } - case CustomRPC.Guess: - { - GuessManager.ReceiveRPC(reader, __instance); - break; - } - case CustomRPC.MafiaRevenge: - { - Mafia.ReceiveRPC(reader, __instance); - break; - } - case CustomRPC.SetSwooperTimer: - { - byte id = reader.ReadByte(); - (Main.PlayerStates[id].Role as Swooper)?.ReceiveRPC(reader); - } - - break; - case CustomRPC.SetAlchemistTimer: - { - Alchemist.ReceiveRPC(reader); - break; - } - case CustomRPC.SetBkTimer: - { - Wildling.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncTotocalcioTargetAndTimes: - { - Totocalcio.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncRomanticTarget: - { - Romantic.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncVengefulRomanticTarget: - { - VengefulRomantic.ReceiveRPC(reader); - break; - } - //case CustomRPC.SetCursedSoulCurseLimit: - // CursedSoul.ReceiveRPC(reader); - // break; - case CustomRPC.SetEvilDiviner: - { - byte id = reader.ReadByte(); - byte targetId = reader.ReadByte(); - (Main.PlayerStates[id].Role as EvilDiviner)?.ReceiveRPC(targetId); - } - - break; - case CustomRPC.SyncHookshot: - { - Hookshot.ReceiveRPC(reader); - break; - } - case CustomRPC.AddTornado: - { - Tornado.ReceiveRPCAddTornado(reader); - break; - } - case CustomRPC.SetDoppelgangerStealLimit: - { - Doppelganger.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncBenefactorMarkedTask: - { - Benefactor.ReceiveRPC(reader); - break; - } - case CustomRPC.SyncStressedTimer: - { - Stressed.ReceiveRPC(reader); - break; - } - case CustomRPC.KillFlash: - { - Utils.FlashColor(new(1f, 0f, 0f, 0.3f)); - if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, Sounds.KillSound); - - break; - } - case CustomRPC.SetCleanserCleanLimit: - { - Cleanser.ReceiveRPC(reader); - break; - } - case CustomRPC.SetJailorExeLimit: - { - Jailor.ReceiveRPC(reader, false); - break; - } - case CustomRPC.SetJailorTarget: - { - Jailor.ReceiveRPC(reader); - break; - } - case CustomRPC.SetWwTimer: - { - byte id = reader.ReadByte(); - (Main.PlayerStates[id].Role as Werewolf)?.ReceiveRPC(reader); - } - - break; - case CustomRPC.SetNiceSwapperVotes: - { - NiceSwapper.ReceiveRPC(reader, __instance); - break; - } - case CustomRPC.SetTrackerTarget: - { - Scout.ReceiveRPC(reader); - break; - } - case CustomRPC.RoomRushDataSync: - { - RoomRush.ReceiveRPC(reader); - break; - } - case CustomRPC.FFAKill: - { - if (!CustomGameMode.FFA.IsActiveOrIntegrated()) - { - EAC.WarnHost(); - EAC.Report(__instance, "FFA RPC when game mode is not FFA"); + byte id = reader.ReadByte(); + Main.AbilityCD.Remove(id); break; } - - var killer = reader.ReadNetObject(); - var target = reader.ReadNetObject(); - - if (!killer.IsAlive() || !target.IsAlive() || AntiBlackout.SkipTasks || target.inMovingPlat || target.onLadder || target.inVent || MeetingHud.Instance) break; - - FFAManager.OnPlayerAttack(killer, target); - break; } - } - } - } - internal static class RPC - { - //来源:https://github.com/music-discussion/TownOfHost-TheOtherRoles/blob/main/Modules/RPC.cs - public static void SyncCustomSettingsRPC(int targetId = -1) - { - if (targetId != -1) + break; + } + case CustomRPC.SyncRoleData: { - ClientData client = Utils.GetClientById(targetId); - if (client == null || client.Character == null || !Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) return; + byte id = reader.ReadByte(); + RoleBase r = Main.PlayerStates[id].Role; + r.GetType().GetMethod("ReceiveRPC")?.Invoke(r, [reader]); + break; + } + case CustomRPC.FixModdedClientCNO: + { + var CNO = reader.ReadNetObject(); + bool active = reader.ReadBoolean(); + CNO.transform.FindChild("Names").FindChild("NameText_TMP").gameObject.SetActive(active); + break; + } + case CustomRPC.SyncGeneralOptions: + { + byte id = reader.ReadByte(); + var role = (CustomRoles)reader.ReadPackedInt32(); + bool dead = reader.ReadBoolean(); + var dr = (PlayerState.DeathReason)reader.ReadPackedInt32(); + + if (Main.PlayerStates.TryGetValue(id, out PlayerState state)) + { + state.MainRole = role; + state.IsDead = dead; + state.deathReason = dr; + } + + float kcd = reader.ReadSingle(); + float speed = reader.ReadSingle(); + Main.AllPlayerKillCooldown[id] = kcd; + Main.AllPlayerSpeed[id] = speed; + break; + } + case CustomRPC.RequestSendMessage: + { + if (!AmongUsClient.Instance.AmHost) break; + + string text = reader.ReadString(); + byte sendTo = reader.ReadByte(); + string title = reader.ReadString(); + bool noSplit = reader.ReadBoolean(); + Utils.SendMessage(text, sendTo, title, noSplit); + break; + } + case CustomRPC.NotificationPopper: + { + byte typeId = reader.ReadByte(); + int item = reader.ReadPackedInt32(); + int customRole = reader.ReadPackedInt32(); + bool playSound = reader.ReadBoolean(); + OptionItem key = OptionItem.AllOptions[item]; + + switch (typeId) + { + case 0: + NotificationPopperPatch.AddSettingsChangeMessage(item, key, playSound); + break; + case 1: + NotificationPopperPatch.AddRoleSettingsChangeMessage(item, key, (CustomRoles)customRole, playSound); + break; + } + + break; + } + case CustomRPC.RequestCommandProcessing: + { + if (!AmongUsClient.Instance.AmHost) break; + + string methodName = reader.ReadString(); + PlayerControl player = reader.ReadByte().GetPlayer(); + string text = reader.ReadString(); + + typeof(ChatCommands).GetMethod(methodName, BindingFlags.Static)?.Invoke(null, [null, player, text, text.Split(' ')]); + break; + } + case CustomRPC.SyncPostman: + { + byte id = reader.ReadByte(); + byte target = reader.ReadByte(); + bool isFinished = reader.ReadBoolean(); + if (Main.PlayerStates[id].Role is not Postman pm) break; + + pm.Target = target; + pm.IsFinished = isFinished; + break; + } + case CustomRPC.SyncChangeling: + { + if (Main.PlayerStates[reader.ReadByte()].Role is not Changeling changeling) break; + + changeling.CurrentRole = (CustomRoles)reader.ReadPackedInt32(); + break; + } + case CustomRPC.SyncTiger: + { + if (Main.PlayerStates[reader.ReadByte()].Role is not Tiger tiger) break; + + tiger.EnrageTimer = reader.ReadSingle(); + break; + } + case CustomRPC.SyncSentry: + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not Crewmate.Sentry sentry) break; + + sentry.MonitoredRoom = Utils.GetPlayerById(id).GetPlainShipRoom(); + break; + } + case CustomRPC.SyncOverheat: + { + ((Overheat)Main.PlayerStates[reader.ReadByte()].Role).Temperature = reader.ReadPackedInt32(); + break; + } + case CustomRPC.SyncIntrovert: + { + Introvert.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncAllergic: + { + Allergic.ReceiveRPC(reader); + break; + } + case CustomRPC.SetBountyTarget: + { + byte bountyId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + (Main.PlayerStates[bountyId].Role as BountyHunter)?.ReceiveRPC(bountyId, targetId); + break; + } + case CustomRPC.SyncBargainer: + { + Bargainer.ReceiveRPC(reader); + break; + } + case CustomRPC.SetKillOrSpell: + { + Witch.ReceiveRPC(reader, false); + break; + } + case CustomRPC.SetCpTasksDone: + { + Crewpostor.RecieveRPC(reader); + break; + } + case CustomRPC.SetLibrarianMode: + { + Librarian.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncSoulHunter: + { + SoulHunter.ReceiveRPC(reader); + break; + } + case CustomRPC.SetDousedPlayer: + { + byte ArsonistId = reader.ReadByte(); + byte dousedId = reader.ReadByte(); + bool doused = reader.ReadBoolean(); + Arsonist.IsDoused[(ArsonistId, dousedId)] = doused; + break; + } + case CustomRPC.SetPlaguedPlayer: + { + PlagueBearer.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncHeadHunter: + { + HeadHunter.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncDamoclesTimer: + { + Damocles.ReceiveRPC(reader); + break; + } + case CustomRPC.SetDrawPlayer: + { + byte RevolutionistId = reader.ReadByte(); + byte DrawId = reader.ReadByte(); + bool drawed = reader.ReadBoolean(); + Revolutionist.IsDraw[(RevolutionistId, DrawId)] = drawed; + break; + } + case CustomRPC.SetRevealedPlayer: + { + byte FarseerId = reader.ReadByte(); + byte RevealId = reader.ReadByte(); + bool revealed = reader.ReadBoolean(); + Farseer.IsRevealed[(FarseerId, RevealId)] = revealed; + break; + } + case CustomRPC.SetNameColorData: + { + NameColorManager.ReceiveRPC(reader); + break; + } + case CustomRPC.RpcPassBomb: + { + Agitater.ReceiveRPC(reader); + break; + } + case CustomRPC.DoSpell: + { + Witch.ReceiveRPC(reader, true); + break; + } + case CustomRPC.SetBanditStealLimit: + { + Bandit.ReceiveRPC(reader); + break; + } + case CustomRPC.SniperSync: + { + byte id = reader.ReadByte(); + (Main.PlayerStates[id].Role as Sniper)?.ReceiveRPC(reader); } - if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) return; - - int amount = OptionItem.AllOptions.Count; - int divideBy = amount / 10; - for (var i = 0; i <= 10; i++) SyncOptionsBetween(i * divideBy, (i + 1) * divideBy, targetId); - } - - private static void SyncOptionsBetween(int startAmount, int lastAmount, int targetId = -1) - { - if (targetId != -1) + break; + case CustomRPC.SyncSpy: { - ClientData client = Utils.GetClientById(targetId); - if (client == null || client.Character == null || !Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) return; + Spy.ReceiveRPC(reader); + break; + } + case CustomRPC.SetAlchemistPotion: + { + Alchemist.ReceiveRPCData(reader); + break; + } + case CustomRPC.SetRicochetTarget: + { + Ricochet.ReceiveRPCSyncTarget(reader); + break; + } + case CustomRPC.SyncRabbit: + { + Rabbit.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncYinYanger: + { + YinYanger.ReceiveRPC(reader); + break; + } + case CustomRPC.SetTetherTarget: + { + Tether.ReceiveRPCSyncTarget(reader); + break; + } + case CustomRPC.SetHitmanTarget: + { + byte hitmanId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + (Main.PlayerStates[hitmanId].Role as Hitman)?.ReceiveRPC(targetId); } - if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) return; - - int amountAllOptions = OptionItem.AllOptions.Count; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncCustomSettings, SendOption.Reliable, targetId); - writer.Write(startAmount); - writer.Write(lastAmount); - - List listOptions = []; - - // Add Options - for (int option = startAmount; option < amountAllOptions && option <= lastAmount; option++) listOptions.Add(OptionItem.AllOptions[option]); - - int countListOptions = listOptions.Count; - Logger.Msg($"StartAmount: {startAmount} - LastAmount: {lastAmount} ({startAmount}/{lastAmount}) :--: ListOptionsCount: {countListOptions} - AllOptions: {amountAllOptions} ({countListOptions}/{amountAllOptions})", "SyncCustomSettings"); - - // Sync Settings - foreach (OptionItem option in listOptions) writer.WritePacked(option.GetValue()); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void PlaySoundRPC(byte PlayerID, Sounds sound) - { - if (AmongUsClient.Instance.AmHost) PlaySound(PlayerID, sound); - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaySound, SendOption.Reliable); - writer.Write(PlayerID); - writer.Write((byte)sound); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void SyncAllPlayerNames() - { - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncAllPlayerNames, SendOption.Reliable); - writer.Write(Main.AllPlayerNames.Count); - - foreach (KeyValuePair name in Main.AllPlayerNames) + break; + case CustomRPC.SetWeaponMasterMode: { - writer.Write(name.Key); - writer.Write(name.Value); + WeaponMaster.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncGlitchTimers: + { + Glitch.ReceiveRPCSyncTimers(reader); + break; + } + case CustomRPC.DruidAddTrigger: + { + Druid.ReceiveRPCAddTrigger(reader); + break; + } + case CustomRPC.SetSabotageMasterLimit: + { + SabotageMaster.ReceiveRPC(reader); + break; + } + case CustomRPC.SetNiceHackerLimit: + { + NiceHacker.ReceiveRPC(reader); + break; + } + case CustomRPC.SetLoversPlayers: + { + Main.LoversPlayers.Clear(); + int count = reader.ReadInt32(); + for (var i = 0; i < count; i++) Main.LoversPlayers.Add(Utils.GetPlayerById(reader.ReadByte())); } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void SendGameData(int clientId = -1) - { - foreach (NetworkedPlayerInfo innerNetObject in GameData.Instance.AllPlayers) innerNetObject.SetDirtyBit(uint.MaxValue); - - AmongUsClient.Instance.SendAllStreamedObjects(); - } - - public static void ShowPopUp(this PlayerControl pc, string msg) - { - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShowPopUp, SendOption.Reliable, pc.GetClientId()); - writer.Write(msg); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void SyncAllClientRealNames() - { - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncAllClientRealNames, SendOption.Reliable); - writer.WritePacked(Main.AllClientRealNames.Count); - - foreach (KeyValuePair name in Main.AllClientRealNames) + break; + case CustomRPC.SetExecutionerTarget: { - writer.Write(name.Key); - writer.Write(name.Value); + Executioner.ReceiveRPC(reader, true); + break; + } + case CustomRPC.RemoveExecutionerTarget: + { + Executioner.ReceiveRPC(reader, false); + break; + } + case CustomRPC.SetLawyerTarget: + { + Lawyer.ReceiveRPC(reader, true); + break; + } + case CustomRPC.RemoveLawyerTarget: + { + Lawyer.ReceiveRPC(reader, false); + break; + } + case CustomRPC.SendFireWorksState: + { + byte id = reader.ReadByte(); + int count = reader.ReadInt32(); + int state = reader.ReadInt32(); + var newState = (FireWorks.FireWorksState)state; + (Main.PlayerStates[id].Role as FireWorks)?.ReceiveRPC(count, newState); } - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ExileAsync(PlayerControl player) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable); - AmongUsClient.Instance.FinishRpcImmediately(writer); - player.Exiled(); - } - - public static void RpcVersionCheck() - { - Main.Instance.StartCoroutine(VersionCheck()); - return; - - static IEnumerator VersionCheck() + break; + case CustomRPC.SyncMycologist: { - while (PlayerControl.LocalPlayer == null) yield return null; + Mycologist.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncBubble: + { + Bubble.ReceiveRPC(reader); + break; + } + case CustomRPC.SetCurrentDousingTarget: + { + byte arsonistId = reader.ReadByte(); + byte dousingTargetId = reader.ReadByte(); + if (PlayerControl.LocalPlayer.PlayerId == arsonistId) Arsonist.CurrentDousingTarget = dousingTargetId; - if (AmongUsClient.Instance.AmHost) - Utils.SendRPC(CustomRPC.VersionCheck, Main.PluginVersion, $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})", Main.ForkId); + break; + } + case CustomRPC.SetCurrentDrawTarget: + { + byte arsonistId1 = reader.ReadByte(); + byte doTargetId = reader.ReadByte(); + if (PlayerControl.LocalPlayer.PlayerId == arsonistId1) Revolutionist.CurrentDrawTarget = doTargetId; + + break; + } + case CustomRPC.SetEvilTrackerTarget: + { + EvilTracker.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncPlagueDoctor: + { + PlagueDoctor.ReceiveRPC(reader); + break; + } + case CustomRPC.PenguinSync: + { + byte id = reader.ReadByte(); + int operate = reader.ReadInt32(); + + if (operate == 1) + { + byte victim = reader.ReadByte(); + (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPC(victim); + } else { - MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionCheck); - writer.Write(Main.PluginVersion); - writer.Write($"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})"); - writer.Write(Main.ForkId); - writer.EndMessage(); + float timer = reader.ReadSingle(); + (Main.PlayerStates[id].Role as Penguin)?.ReceiveRPC(timer); } - Main.PlayerVersion[PlayerControl.LocalPlayer.PlayerId] = new(Main.PluginVersion, $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})", Main.ForkId); + break; } - } - - public static void SendDeathReason(byte playerId, PlayerState.DeathReason deathReason) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDeathReason, SendOption.Reliable); - writer.Write(playerId); - writer.Write((int)deathReason); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void GetDeathReason(MessageReader reader) - { - byte playerId = reader.ReadByte(); - var deathReason = (PlayerState.DeathReason)reader.ReadInt32(); - Main.PlayerStates[playerId].deathReason = deathReason; - Main.PlayerStates[playerId].IsDead = true; - } - - public static void ForceEndGame(CustomWinner win) - { - if (ShipStatus.Instance == null) return; - - try + case CustomRPC.SetRealKiller: { - CustomWinnerHolder.ResetAndSetWinner(win); + byte targetId = reader.ReadByte(); + byte killerId = reader.ReadByte(); + RPC.SetRealKiller(targetId, killerId); + break; } - catch { } + case CustomRPC.ShowChat: + { + uint clientId = reader.ReadPackedUInt32(); + bool show = reader.ReadBoolean(); + if (AmongUsClient.Instance.ClientId == clientId) HudManager.Instance.Chat.SetVisible(show); + + break; + } + case CustomRPC.SyncLobbyTimer: + { + GameStartManagerPatch.Timer = reader.ReadPackedInt32(); + break; + } + case CustomRPC.SetGamerHealth: + { + Gamer.ReceiveRPC(reader); + break; + } + case CustomRPC.SetPelicanEtenNum: + { + Pelican.ReceiveRPC(reader); + break; + } + case CustomRPC.SetDoomsayerProgress: + { + Doomsayer.ReceiveRPC(reader); + break; + } + case CustomRPC.SwordsManKill: + { + SwordsMan.ReceiveRPC(reader); + break; + } + case CustomRPC.PlayCustomSound: + { + CustomSoundsManager.ReceiveRPC(reader); + break; + } + case CustomRPC.SetGhostPlayer: + { + BallLightning.ReceiveRPC(reader); + break; + } + case CustomRPC.SetDarkHiderKillCount: + { + DarkHide.ReceiveRPC(reader); + break; + } + case CustomRPC.SetGreedierOe: + { + byte id = reader.ReadByte(); + bool isOdd = reader.ReadBoolean(); + (Main.PlayerStates[id].Role as Greedier)?.ReceiveRPC(isOdd); + } + + break; + case CustomRPC.SetCollectorVotes: + { + Collector.ReceiveRPC(reader); + break; + } + case CustomRPC.SetQuickShooterShotLimit: + { + QuickShooter.ReceiveRPC(reader); + break; + } + case CustomRPC.GuessKill: + { + GuessManager.RpcClientGuess(Utils.GetPlayerById(reader.ReadByte())); + break; + } + case CustomRPC.SetMarkedPlayer: + { + Assassin.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncChronomancer: + { + byte id = reader.ReadByte(); + bool isRampaging = reader.ReadBoolean(); + int chargePercent = reader.ReadInt32(); + long lastUpdate = long.Parse(reader.ReadString()); + (Main.PlayerStates[id].Role as Chronomancer)?.ReceiveRPC(isRampaging, chargePercent, lastUpdate); + } + + break; + case CustomRPC.SetMedicalerProtectList: + { + Medic.ReceiveRPCForProtectList(reader); + break; + } + case CustomRPC.SyncPsychicRedList: + { + Psychic.ReceiveRPC(reader); + break; + } + case CustomRPC.SetKillTimer: + { + float time = reader.ReadSingle(); + PlayerControl.LocalPlayer.SetKillTimer(time); + break; + } + case CustomRPC.SyncAllPlayerNames: + { + Main.AllPlayerNames = []; + int num = reader.ReadInt32(); + for (var i = 0; i < num; i++) Main.AllPlayerNames.TryAdd(reader.ReadByte(), reader.ReadString()); + + break; + } + case CustomRPC.SyncMafiosoData: + { + byte id = reader.ReadByte(); + (Main.PlayerStates[id].Role as Mafioso)?.ReceiveRPC(reader); + } + + break; + case CustomRPC.SyncMafiosoPistolCD: + { + byte id = reader.ReadByte(); + (Main.PlayerStates[id].Role as Mafioso)?.ReceiveRPCSyncPistolCD(reader); + } + + break; + case CustomRPC.SyncNameNotify: + { + NameNotifyManager.ReceiveRPC(reader); + break; + } + case CustomRPC.Judge: + { + Judge.ReceiveRPC(reader, __instance); + break; + } + case CustomRPC.MeetingKill: + { + Councillor.ReceiveRPC(reader, __instance); + break; + } + case CustomRPC.Guess: + { + GuessManager.ReceiveRPC(reader, __instance); + break; + } + case CustomRPC.MafiaRevenge: + { + Mafia.ReceiveRPC(reader, __instance); + break; + } + case CustomRPC.SetSwooperTimer: + { + byte id = reader.ReadByte(); + (Main.PlayerStates[id].Role as Swooper)?.ReceiveRPC(reader); + } + + break; + case CustomRPC.SetAlchemistTimer: + { + Alchemist.ReceiveRPC(reader); + break; + } + case CustomRPC.SetBkTimer: + { + Wildling.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncTotocalcioTargetAndTimes: + { + Totocalcio.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncRomanticTarget: + { + Romantic.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncVengefulRomanticTarget: + { + VengefulRomantic.ReceiveRPC(reader); + break; + } + //case CustomRPC.SetCursedSoulCurseLimit: + // CursedSoul.ReceiveRPC(reader); + // break; + case CustomRPC.SetEvilDiviner: + { + byte id = reader.ReadByte(); + byte targetId = reader.ReadByte(); + (Main.PlayerStates[id].Role as EvilDiviner)?.ReceiveRPC(targetId); + } + + break; + case CustomRPC.SyncHookshot: + { + Hookshot.ReceiveRPC(reader); + break; + } + case CustomRPC.AddTornado: + { + Tornado.ReceiveRPCAddTornado(reader); + break; + } + case CustomRPC.SetDoppelgangerStealLimit: + { + Doppelganger.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncBenefactorMarkedTask: + { + Benefactor.ReceiveRPC(reader); + break; + } + case CustomRPC.SyncStressedTimer: + { + Stressed.ReceiveRPC(reader); + break; + } + case CustomRPC.KillFlash: + { + Utils.FlashColor(new(1f, 0f, 0f, 0.3f)); + if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, Sounds.KillSound); + + break; + } + case CustomRPC.SetCleanserCleanLimit: + { + Cleanser.ReceiveRPC(reader); + break; + } + case CustomRPC.SetJailorExeLimit: + { + Jailor.ReceiveRPC(reader, false); + break; + } + case CustomRPC.SetJailorTarget: + { + Jailor.ReceiveRPC(reader); + break; + } + case CustomRPC.SetWwTimer: + { + byte id = reader.ReadByte(); + (Main.PlayerStates[id].Role as Werewolf)?.ReceiveRPC(reader); + } + + break; + case CustomRPC.SetNiceSwapperVotes: + { + NiceSwapper.ReceiveRPC(reader, __instance); + break; + } + case CustomRPC.SetTrackerTarget: + { + Scout.ReceiveRPC(reader); + break; + } + case CustomRPC.RoomRushDataSync: + { + RoomRush.ReceiveRPC(reader); + break; + } + case CustomRPC.FFAKill: + { + if (!CustomGameMode.FFA.IsActiveOrIntegrated()) + { + EAC.WarnHost(); + EAC.Report(__instance, "FFA RPC when game mode is not FFA"); + break; + } + + var killer = reader.ReadNetObject(); + var target = reader.ReadNetObject(); + + if (!killer.IsAlive() || !target.IsAlive() || AntiBlackout.SkipTasks || target.inMovingPlat || target.onLadder || target.inVent || MeetingHud.Instance) break; + + FFAManager.OnPlayerAttack(killer, target); + break; + } + } + } +} + +internal static class RPC +{ + //来源:https://github.com/music-discussion/TownOfHost-TheOtherRoles/blob/main/Modules/RPC.cs + public static void SyncCustomSettingsRPC(int targetId = -1) + { + if (targetId != -1) + { + ClientData client = Utils.GetClientById(targetId); + if (client == null || client.Character == null || !Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) return; + } + + if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) return; + + int amount = OptionItem.AllOptions.Count; + int divideBy = amount / 10; + for (var i = 0; i <= 10; i++) SyncOptionsBetween(i * divideBy, (i + 1) * divideBy, targetId); + } + + private static void SyncOptionsBetween(int startAmount, int lastAmount, int targetId = -1) + { + if (targetId != -1) + { + ClientData client = Utils.GetClientById(targetId); + if (client == null || client.Character == null || !Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) return; + } + + if (!AmongUsClient.Instance.AmHost || PlayerControl.AllPlayerControls.Count <= 1 || (AmongUsClient.Instance.AmHost == false && PlayerControl.LocalPlayer == null)) return; + + int amountAllOptions = OptionItem.AllOptions.Count; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncCustomSettings, SendOption.Reliable, targetId); + writer.Write(startAmount); + writer.Write(lastAmount); + + List listOptions = []; + + // Add Options + for (int option = startAmount; option < amountAllOptions && option <= lastAmount; option++) listOptions.Add(OptionItem.AllOptions[option]); + + int countListOptions = listOptions.Count; + Logger.Msg($"StartAmount: {startAmount} - LastAmount: {lastAmount} ({startAmount}/{lastAmount}) :--: ListOptionsCount: {countListOptions} - AllOptions: {amountAllOptions} ({countListOptions}/{amountAllOptions})", "SyncCustomSettings"); + + // Sync Settings + foreach (OptionItem option in listOptions) writer.WritePacked(option.GetValue()); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void PlaySoundRPC(byte PlayerID, Sounds sound) + { + if (AmongUsClient.Instance.AmHost) PlaySound(PlayerID, sound); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaySound, SendOption.Reliable); + writer.Write(PlayerID); + writer.Write((byte)sound); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void SyncAllPlayerNames() + { + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncAllPlayerNames, SendOption.Reliable); + writer.Write(Main.AllPlayerNames.Count); + + foreach (KeyValuePair name in Main.AllPlayerNames) + { + writer.Write(name.Key); + writer.Write(name.Value); + } + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void SendGameData(int clientId = -1) + { + foreach (NetworkedPlayerInfo innerNetObject in GameData.Instance.AllPlayers) innerNetObject.SetDirtyBit(uint.MaxValue); + + AmongUsClient.Instance.SendAllStreamedObjects(); + } + + public static void ShowPopUp(this PlayerControl pc, string msg) + { + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShowPopUp, SendOption.Reliable, pc.GetClientId()); + writer.Write(msg); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void SyncAllClientRealNames() + { + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncAllClientRealNames, SendOption.Reliable); + writer.WritePacked(Main.AllClientRealNames.Count); + + foreach (KeyValuePair name in Main.AllClientRealNames) + { + writer.Write(name.Key); + writer.Write(name.Value); + } + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ExileAsync(PlayerControl player) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(player.NetId, (byte)RpcCalls.Exiled, SendOption.Reliable); + AmongUsClient.Instance.FinishRpcImmediately(writer); + player.Exiled(); + } + + public static void RpcVersionCheck() + { + Main.Instance.StartCoroutine(VersionCheck()); + return; + + static IEnumerator VersionCheck() + { + while (PlayerControl.LocalPlayer == null) yield return null; if (AmongUsClient.Instance.AmHost) - { - ShipStatus.Instance.enabled = false; - - try - { - GameManager.Instance.LogicFlow.CheckEndCriteria(); - } - catch { } - - try - { - GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); - } - catch { } - } - } - - public static void EndGame(MessageReader reader) - { - try - { - CustomWinnerHolder.ReadFrom(reader); - } - catch (Exception ex) - { - Logger.Error($"正常にEndGameを行えませんでした。\n{ex}", "EndGame", false); - } - } - - public static void PlaySound(byte playerID, Sounds sound) - { - if (PlayerControl.LocalPlayer.PlayerId == playerID) - { - switch (sound) - { - case Sounds.KillSound: - SoundManager.Instance.PlaySound(PlayerControl.LocalPlayer.KillSfx, false); - break; - case Sounds.TaskComplete: - SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskCompleteSound, false); - break; - case Sounds.TaskUpdateSound: - SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskUpdateSound, false); - break; - case Sounds.ImpTransform: - SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx, false, 0.8f); - break; - } - } - } - - public static void SetCustomRole(byte targetId, CustomRoles role, bool replaceAllAddons = false) - { - if (role < CustomRoles.NotAssigned) - Main.PlayerStates[targetId].SetMainRole(role); + Utils.SendRPC(CustomRPC.VersionCheck, Main.PluginVersion, $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})", Main.ForkId); else - Main.PlayerStates[targetId].SetSubRole(role, replaceAllAddons); + { + MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionCheck); + writer.Write(Main.PluginVersion); + writer.Write($"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})"); + writer.Write(Main.ForkId); + writer.EndMessage(); + } - HudManager.Instance.SetHudActive(true); - if (PlayerControl.LocalPlayer.PlayerId == targetId) RemoveDisableDevicesPatch.UpdateDisableDevices(); + Main.PlayerVersion[PlayerControl.LocalPlayer.PlayerId] = new(Main.PluginVersion, $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})", Main.ForkId); } + } - public static void SyncLoversPlayers() + public static void SendDeathReason(byte playerId, PlayerState.DeathReason deathReason) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDeathReason, SendOption.Reliable); + writer.Write(playerId); + writer.Write((int)deathReason); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void GetDeathReason(MessageReader reader) + { + byte playerId = reader.ReadByte(); + var deathReason = (PlayerState.DeathReason)reader.ReadInt32(); + Main.PlayerStates[playerId].deathReason = deathReason; + Main.PlayerStates[playerId].IsDead = true; + } + + public static void ForceEndGame(CustomWinner win) + { + if (ShipStatus.Instance == null) return; + + try { - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLoversPlayers, SendOption.Reliable); - writer.Write(Main.LoversPlayers.Count); - foreach (PlayerControl lp in Main.LoversPlayers) writer.Write(lp.PlayerId); - - AmongUsClient.Instance.FinishRpcImmediately(writer); + CustomWinnerHolder.ResetAndSetWinner(win); } + catch { } - public static void SendRpcLogger(uint targetNetId, byte callId, int targetClientId = -1) + if (AmongUsClient.Instance.AmHost) { - if (!DebugModeManager.AmDebugger) return; - - string rpcName = GetRpcName(callId); - var from = targetNetId.ToString(); - var target = targetClientId.ToString(); + ShipStatus.Instance.enabled = false; try { - target = targetClientId < 0 ? "All" : AmongUsClient.Instance.GetClient(targetClientId).PlayerName; - from = Main.AllPlayerControls.FirstOrDefault(c => c.NetId == targetNetId)?.Data?.PlayerName; + GameManager.Instance.LogicFlow.CheckEndCriteria(); } catch { } - Logger.Info($"FromNetID: {targetNetId} ({from}) / TargetClientID: {targetClientId} ({target}) / CallID: {callId} ({rpcName})", "SendRPC"); - } - - public static string GetRpcName(byte callId) - { - string rpcName; - - if ((rpcName = Enum.GetName(typeof(RpcCalls), callId)) != null) { } - else if ((rpcName = Enum.GetName(typeof(CustomRPC), callId)) != null) { } - else - rpcName = callId.ToString(); - - return rpcName; - } - - public static void SetCurrentDousingTarget(byte arsonistId, byte targetId) - { - if (PlayerControl.LocalPlayer.PlayerId == arsonistId) - Arsonist.CurrentDousingTarget = targetId; - else + try { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentDousingTarget, SendOption.Reliable); - writer.Write(arsonistId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + } + catch { } + } + } + + public static void EndGame(MessageReader reader) + { + try + { + CustomWinnerHolder.ReadFrom(reader); + } + catch (Exception ex) + { + Logger.Error($"正常にEndGameを行えませんでした。\n{ex}", "EndGame", false); + } + } + + public static void PlaySound(byte playerID, Sounds sound) + { + if (PlayerControl.LocalPlayer.PlayerId == playerID) + { + switch (sound) + { + case Sounds.KillSound: + SoundManager.Instance.PlaySound(PlayerControl.LocalPlayer.KillSfx, false); + break; + case Sounds.TaskComplete: + SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskCompleteSound, false); + break; + case Sounds.TaskUpdateSound: + SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.TaskUpdateSound, false); + break; + case Sounds.ImpTransform: + SoundManager.Instance.PlaySound(DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx, false, 0.8f); + break; } } + } - public static void SetCurrentDrawTarget(byte arsonistId, byte targetId) + public static void SetCustomRole(byte targetId, CustomRoles role, bool replaceAllAddons = false) + { + if (role < CustomRoles.NotAssigned) + Main.PlayerStates[targetId].SetMainRole(role); + else + Main.PlayerStates[targetId].SetSubRole(role, replaceAllAddons); + + HudManager.Instance.SetHudActive(true); + if (PlayerControl.LocalPlayer.PlayerId == targetId) RemoveDisableDevicesPatch.UpdateDisableDevices(); + } + + public static void SyncLoversPlayers() + { + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLoversPlayers, SendOption.Reliable); + writer.Write(Main.LoversPlayers.Count); + foreach (PlayerControl lp in Main.LoversPlayers) writer.Write(lp.PlayerId); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void SendRpcLogger(uint targetNetId, byte callId, int targetClientId = -1) + { + if (!DebugModeManager.AmDebugger) return; + + string rpcName = GetRpcName(callId); + var from = targetNetId.ToString(); + var target = targetClientId.ToString(); + + try { - if (PlayerControl.LocalPlayer.PlayerId == arsonistId) - Revolutionist.CurrentDrawTarget = targetId; - else - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentDrawTarget, SendOption.Reliable); - writer.Write(arsonistId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + target = targetClientId < 0 ? "All" : AmongUsClient.Instance.GetClient(targetClientId).PlayerName; + from = Main.AllPlayerControls.FirstOrDefault(c => c.NetId == targetNetId)?.Data?.PlayerName; } + catch { } - public static void SetCurrentRevealTarget(byte arsonistId, byte targetId) + Logger.Info($"FromNetID: {targetNetId} ({from}) / TargetClientID: {targetClientId} ({target}) / CallID: {callId} ({rpcName})", "SendRPC"); + } + + public static string GetRpcName(byte callId) + { + string rpcName; + + if ((rpcName = Enum.GetName(typeof(RpcCalls), callId)) != null) { } + else if ((rpcName = Enum.GetName(typeof(CustomRPC), callId)) != null) { } + else + rpcName = callId.ToString(); + + return rpcName; + } + + public static void SetCurrentDousingTarget(byte arsonistId, byte targetId) + { + if (PlayerControl.LocalPlayer.PlayerId == arsonistId) + Arsonist.CurrentDousingTarget = targetId; + else { - if (PlayerControl.LocalPlayer.PlayerId == arsonistId) - Revolutionist.CurrentDrawTarget = targetId; - else - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentRevealTarget, SendOption.Reliable); - writer.Write(arsonistId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } - - public static void ResetCurrentDousingTarget(byte arsonistId) - { - SetCurrentDousingTarget(arsonistId, 255); - } - - public static void ResetCurrentDrawTarget(byte arsonistId) - { - SetCurrentDrawTarget(arsonistId, 255); - } - - public static void ResetCurrentRevealTarget(byte arsonistId) - { - SetCurrentRevealTarget(arsonistId, 255); - } - - public static void SetRealKiller(byte targetId, byte killerId) - { - PlayerState state = Main.PlayerStates[targetId]; - state.RealKiller.TimeStamp = DateTime.Now; - state.RealKiller.ID = killerId; - - if (!AmongUsClient.Instance.AmHost) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRealKiller, SendOption.Reliable); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentDousingTarget, SendOption.Reliable); + writer.Write(arsonistId); writer.Write(targetId); - writer.Write(killerId); AmongUsClient.Instance.FinishRpcImmediately(writer); } } - [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleRpc))] - internal static class PlayerPhysicsRPCHandlerPatch + public static void SetCurrentDrawTarget(byte arsonistId, byte targetId) { - public static bool Prefix(PlayerPhysics __instance, byte callId, MessageReader reader) + if (PlayerControl.LocalPlayer.PlayerId == arsonistId) + Revolutionist.CurrentDrawTarget = targetId; + else { - bool host = __instance.IsHost(); - if (!host && EAC.PlayerPhysicsRpcCheck(__instance, callId, reader)) return false; - - PlayerControl player = __instance.myPlayer; - - if (!player) - { - Logger.Warn("Received Physics RPC without a player", "PlayerPhysics_ReceiveRPC"); - return false; - } - - return true; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentDrawTarget, SendOption.Reliable); + writer.Write(arsonistId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); } } - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.StartRpc))] - internal static class StartRpcPatch + public static void SetCurrentRevealTarget(byte arsonistId, byte targetId) { - public static void Prefix( /*InnerNet.InnerNetClient __instance,*/ [HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId) + if (PlayerControl.LocalPlayer.PlayerId == arsonistId) + Revolutionist.CurrentDrawTarget = targetId; + else { - RPC.SendRpcLogger(targetNetId, callId); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCurrentRevealTarget, SendOption.Reliable); + writer.Write(arsonistId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); } } - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.StartRpcImmediately))] - internal static class StartRpcImmediatelyPatch + public static void ResetCurrentDousingTarget(byte arsonistId) { - public static void Prefix( /*InnerNet.InnerNetClient __instance,*/ [HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId, [HarmonyArgument(3)] int targetClientId = -1) + SetCurrentDousingTarget(arsonistId, 255); + } + + public static void ResetCurrentDrawTarget(byte arsonistId) + { + SetCurrentDrawTarget(arsonistId, 255); + } + + public static void ResetCurrentRevealTarget(byte arsonistId) + { + SetCurrentRevealTarget(arsonistId, 255); + } + + public static void SetRealKiller(byte targetId, byte killerId) + { + PlayerState state = Main.PlayerStates[targetId]; + state.RealKiller.TimeStamp = DateTime.Now; + state.RealKiller.ID = killerId; + + if (!AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRealKiller, SendOption.Reliable); + writer.Write(targetId); + writer.Write(killerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } +} + +[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleRpc))] +internal static class PlayerPhysicsRPCHandlerPatch +{ + public static bool Prefix(PlayerPhysics __instance, byte callId, MessageReader reader) + { + bool host = __instance.IsHost(); + if (!host && EAC.PlayerPhysicsRpcCheck(__instance, callId, reader)) return false; + + PlayerControl player = __instance.myPlayer; + + if (!player) { - RPC.SendRpcLogger(targetNetId, callId, targetClientId); + Logger.Warn("Received Physics RPC without a player", "PlayerPhysics_ReceiveRPC"); + return false; } + + return true; + } +} + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.StartRpc))] +internal static class StartRpcPatch +{ + public static void Prefix( /*InnerNet.InnerNetClient __instance,*/ [HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId) + { + RPC.SendRpcLogger(targetNetId, callId); + } +} + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.StartRpcImmediately))] +internal static class StartRpcImmediatelyPatch +{ + public static void Prefix( /*InnerNet.InnerNetClient __instance,*/ [HarmonyArgument(0)] uint targetNetId, [HarmonyArgument(1)] byte callId, [HarmonyArgument(3)] int targetClientId = -1) + { + RPC.SendRpcLogger(targetNetId, callId, targetClientId); } } \ No newline at end of file diff --git a/Modules/Random/HashRandomWrapper.cs b/Modules/Random/HashRandomWrapper.cs index 858a9c2b0..50887d29a 100644 --- a/Modules/Random/HashRandomWrapper.cs +++ b/Modules/Random/HashRandomWrapper.cs @@ -1,26 +1,25 @@ #pragma warning disable CA1822 -namespace EHR +namespace EHR; + +public class HashRandomWrapper : IRandom { - public class HashRandomWrapper : IRandom + public int Next(int minValue, int maxValue) { - public int Next(int minValue, int maxValue) - { - return HashRandom.Next(minValue, maxValue); - } + return HashRandom.Next(minValue, maxValue); + } - public int Next(int maxValue) - { - return HashRandom.Next(maxValue); - } + public int Next(int maxValue) + { + return HashRandom.Next(maxValue); + } - public uint Next() - { - return HashRandom.Next(); - } + public uint Next() + { + return HashRandom.Next(); + } - public int FastNext(int maxValue) - { - return HashRandom.FastNext(maxValue); - } + public int FastNext(int maxValue) + { + return HashRandom.FastNext(maxValue); } } \ No newline at end of file diff --git a/Modules/Random/IRandom.cs b/Modules/Random/IRandom.cs index 59c63ddd1..1abcc8421 100644 --- a/Modules/Random/IRandom.cs +++ b/Modules/Random/IRandom.cs @@ -1,82 +1,81 @@ using System; using System.Collections.Generic; -namespace EHR +namespace EHR; + +public interface IRandom { - public interface IRandom + // A list of classes that implement IRandom + public static readonly Dictionary RandomTypes = new() { - // A list of classes that implement IRandom - public static readonly Dictionary RandomTypes = new() + { 0, typeof(NetRandomWrapper) }, // Default + { 1, typeof(NetRandomWrapper) }, + { 2, typeof(HashRandomWrapper) }, + { 3, typeof(Xorshift) }, + { 4, typeof(MersenneTwister) } + }; + + public static IRandom Instance { get; private set; } + + /// Generates a random number greater than or equal to 0 and less than maxValue. + public int Next(int maxValue); + + /// Generates a random number greater than or equal to minValue and less than maxValue. + public int Next(int minValue, int maxValue); + + // == static == + + /// + /// Generates a sequence of random numbers. + /// + /// The number of random numbers to generate. + /// The inclusive lower bound of the random numbers to generate. + /// The exclusive upper bound of the random numbers to generate. + /// A sequence of random numbers. + public static IEnumerable Sequence(int count, int minValue, int maxValue) + { + for (var i = 0; i < count; i++) yield return Instance.Next(minValue, maxValue); + } + + /// + /// Generates a sequence of unique random numbers. + /// + /// The number of random numbers to generate. + /// The inclusive lower bound of the random numbers to generate. + /// The exclusive upper bound of the random numbers to generate. + /// A sequence of unique random numbers. + public static IEnumerable SequenceUnique(int count, int minValue, int maxValue) + { + var set = new HashSet(); + + for (var i = 0; i < count; i++) { - { 0, typeof(NetRandomWrapper) }, // Default - { 1, typeof(NetRandomWrapper) }, - { 2, typeof(HashRandomWrapper) }, - { 3, typeof(Xorshift) }, - { 4, typeof(MersenneTwister) } - }; + // If all possible values are used, the loop will be infinite. Break the loop if the set is full. + if (set.Count == maxValue - minValue) break; - public static IRandom Instance { get; private set; } + int value; - /// Generates a random number greater than or equal to 0 and less than maxValue. - public int Next(int maxValue); + do + value = Instance.Next(minValue, maxValue); + while (!set.Add(value)); - /// Generates a random number greater than or equal to minValue and less than maxValue. - public int Next(int minValue, int maxValue); - - // == static == - - /// - /// Generates a sequence of random numbers. - /// - /// The number of random numbers to generate. - /// The inclusive lower bound of the random numbers to generate. - /// The exclusive upper bound of the random numbers to generate. - /// A sequence of random numbers. - public static IEnumerable Sequence(int count, int minValue, int maxValue) - { - for (var i = 0; i < count; i++) yield return Instance.Next(minValue, maxValue); - } - - /// - /// Generates a sequence of unique random numbers. - /// - /// The number of random numbers to generate. - /// The inclusive lower bound of the random numbers to generate. - /// The exclusive upper bound of the random numbers to generate. - /// A sequence of unique random numbers. - public static IEnumerable SequenceUnique(int count, int minValue, int maxValue) - { - var set = new HashSet(); - - for (var i = 0; i < count; i++) - { - // If all possible values are used, the loop will be infinite. Break the loop if the set is full. - if (set.Count == maxValue - minValue) break; - - int value; - - do - value = Instance.Next(minValue, maxValue); - while (!set.Add(value)); - - yield return value; - } - } - - public static void SetInstance(IRandom instance) - { - if (instance != null) Instance = instance; - } - - public static void SetInstanceById(int id) - { - if (RandomTypes.TryGetValue(id, out Type type)) - { - // The current instance is null or the type of the current instance does not match the specified type. - if (Instance == null || Instance.GetType() != type) Instance = Activator.CreateInstance(type) as IRandom ?? Instance; - } - else - Logger.Warn($"無効なID: {id}", "IRandom.SetInstanceById"); + yield return value; } } + + public static void SetInstance(IRandom instance) + { + if (instance != null) Instance = instance; + } + + public static void SetInstanceById(int id) + { + if (RandomTypes.TryGetValue(id, out Type type)) + { + // The current instance is null or the type of the current instance does not match the specified type. + if (Instance == null || Instance.GetType() != type) Instance = Activator.CreateInstance(type) as IRandom ?? Instance; + } + else + Logger.Warn($"無効なID: {id}", "IRandom.SetInstanceById"); + } } \ No newline at end of file diff --git a/Modules/Random/MersenneTwister.cs b/Modules/Random/MersenneTwister.cs index 6adc60ac0..d6bb96cc8 100644 --- a/Modules/Random/MersenneTwister.cs +++ b/Modules/Random/MersenneTwister.cs @@ -22,119 +22,118 @@ using System; -namespace EHR +namespace EHR; + +public class MersenneTwister : IRandom { - public class MersenneTwister : IRandom + // 参考元 + public const string REFERENCE_HOMEPAGE = "http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/mt.html"; + public const string REFERENCE_SOURCE_CODE = "https://github.com/vpmedia/template-unity/blob/master/Framework/Assets/Frameworks/URandom/MersenneTwister.cs"; + + /// + /// 数値の上限を設定 + /// これより下の値の一部は参考元のソースより拝借 + /// + private const Int32 N = 624; + + private const Int32 M = 397; + private const UInt32 MatrixA = 0x9908b0df; + private const UInt32 UpperMask = 0x80000000; + private const UInt32 LowerMask = 0x7fffffff; + private const UInt32 TemperingMaskB = 0x9d2c5680; + private const UInt32 TemperingMaskC = 0xefc60000; + private readonly UInt32[] _mag01 = [0x0, MatrixA]; + + private readonly UInt32[] _mt = new UInt32[N]; + private Int16 _mtItems; + + public MersenneTwister() : this((Int32)DateTime.UtcNow.Ticks) { } + + public MersenneTwister(Int32 seed) { - // 参考元 - public const string REFERENCE_HOMEPAGE = "http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/mt.html"; - public const string REFERENCE_SOURCE_CODE = "https://github.com/vpmedia/template-unity/blob/master/Framework/Assets/Frameworks/URandom/MersenneTwister.cs"; + Init((UInt32)seed); + } - /// - /// 数値の上限を設定 - /// これより下の値の一部は参考元のソースより拝借 - /// - private const Int32 N = 624; + public int Next(int minValue, int maxValue) + { + if (minValue < 0 || maxValue < 0) throw new ArgumentOutOfRangeException("minValue and maxValue must be bigger than 0."); - private const Int32 M = 397; - private const UInt32 MatrixA = 0x9908b0df; - private const UInt32 UpperMask = 0x80000000; - private const UInt32 LowerMask = 0x7fffffff; - private const UInt32 TemperingMaskB = 0x9d2c5680; - private const UInt32 TemperingMaskC = 0xefc60000; - private readonly UInt32[] _mag01 = [0x0, MatrixA]; + if (minValue > maxValue) throw new ArgumentException("maxValue must be bigger than minValue."); - private readonly UInt32[] _mt = new UInt32[N]; - private Int16 _mtItems; + if (minValue == maxValue) return minValue; - public MersenneTwister() : this((Int32)DateTime.UtcNow.Ticks) { } + return (int)(minValue + (Next() % (maxValue - minValue))); + } - public MersenneTwister(Int32 seed) + public int Next(int maxValue) + { + return Next(0, maxValue); + } + + private static UInt32 ShiftU(UInt32 y) + { + return y >> 11; + } + + private static UInt32 ShiftS(UInt32 y) + { + return y << 7; + } + + private static UInt32 ShiftT(UInt32 y) + { + return y << 15; + } + + private static UInt32 ShiftL(UInt32 y) + { + return y >> 18; + } + + private void Init(UInt32 seed) + { + _mt[0] = seed & 0xffffffffU; + + for (_mtItems = 1; _mtItems < N; _mtItems++) { - Init((UInt32)seed); - } - - public int Next(int minValue, int maxValue) - { - if (minValue < 0 || maxValue < 0) throw new ArgumentOutOfRangeException("minValue and maxValue must be bigger than 0."); - - if (minValue > maxValue) throw new ArgumentException("maxValue must be bigger than minValue."); - - if (minValue == maxValue) return minValue; - - return (int)(minValue + (Next() % (maxValue - minValue))); - } - - public int Next(int maxValue) - { - return Next(0, maxValue); - } - - private static UInt32 ShiftU(UInt32 y) - { - return y >> 11; - } - - private static UInt32 ShiftS(UInt32 y) - { - return y << 7; - } - - private static UInt32 ShiftT(UInt32 y) - { - return y << 15; - } - - private static UInt32 ShiftL(UInt32 y) - { - return y >> 18; - } - - private void Init(UInt32 seed) - { - _mt[0] = seed & 0xffffffffU; - - for (_mtItems = 1; _mtItems < N; _mtItems++) - { - _mt[_mtItems] = (uint)((1812433253U * (_mt[_mtItems - 1] ^ (_mt[_mtItems - 1] >> 30))) + _mtItems); - _mt[_mtItems] &= 0xffffffffU; - } - } - - public uint Next() - { - UInt32 y; - - /* _mag01[x] = x * MatrixA for x=0,1 */ - if (_mtItems >= N) /* generate N words at one time */ - { - Int16 kk = 0; - - for (; kk < N - M; ++kk) - { - y = (_mt[kk] & UpperMask) | (_mt[kk + 1] & LowerMask); - _mt[kk] = _mt[kk + M] ^ (y >> 1) ^ _mag01[y & 0x1]; - } - - for (; kk < N - 1; ++kk) - { - y = (_mt[kk] & UpperMask) | (_mt[kk + 1] & LowerMask); - _mt[kk] = _mt[kk + (M - N)] ^ (y >> 1) ^ _mag01[y & 0x1]; - } - - y = (_mt[N - 1] & UpperMask) | (_mt[0] & LowerMask); - _mt[N - 1] = _mt[M - 1] ^ (y >> 1) ^ _mag01[y & 0x1]; - - _mtItems = 0; - } - - y = _mt[_mtItems++]; - y ^= ShiftU(y); - y ^= ShiftS(y) & TemperingMaskB; - y ^= ShiftT(y) & TemperingMaskC; - y ^= ShiftL(y); - - return y; + _mt[_mtItems] = (uint)((1812433253U * (_mt[_mtItems - 1] ^ (_mt[_mtItems - 1] >> 30))) + _mtItems); + _mt[_mtItems] &= 0xffffffffU; } } + + public uint Next() + { + UInt32 y; + + /* _mag01[x] = x * MatrixA for x=0,1 */ + if (_mtItems >= N) /* generate N words at one time */ + { + Int16 kk = 0; + + for (; kk < N - M; ++kk) + { + y = (_mt[kk] & UpperMask) | (_mt[kk + 1] & LowerMask); + _mt[kk] = _mt[kk + M] ^ (y >> 1) ^ _mag01[y & 0x1]; + } + + for (; kk < N - 1; ++kk) + { + y = (_mt[kk] & UpperMask) | (_mt[kk + 1] & LowerMask); + _mt[kk] = _mt[kk + (M - N)] ^ (y >> 1) ^ _mag01[y & 0x1]; + } + + y = (_mt[N - 1] & UpperMask) | (_mt[0] & LowerMask); + _mt[N - 1] = _mt[M - 1] ^ (y >> 1) ^ _mag01[y & 0x1]; + + _mtItems = 0; + } + + y = _mt[_mtItems++]; + y ^= ShiftU(y); + y ^= ShiftS(y) & TemperingMaskB; + y ^= ShiftT(y) & TemperingMaskC; + y ^= ShiftL(y); + + return y; + } } \ No newline at end of file diff --git a/Modules/Random/NetRandomWrapper.cs b/Modules/Random/NetRandomWrapper.cs index 59259600a..845c7fbb4 100644 --- a/Modules/Random/NetRandomWrapper.cs +++ b/Modules/Random/NetRandomWrapper.cs @@ -1,28 +1,27 @@ using System; -namespace EHR +namespace EHR; + +public class NetRandomWrapper(Random instance) : IRandom { - public class NetRandomWrapper(Random instance) : IRandom + public Random wrapping = instance; + + public NetRandomWrapper() : this(new Random()) { } + + public NetRandomWrapper(int seed) : this(new Random(seed)) { } + + public int Next(int minValue, int maxValue) { - public Random wrapping = instance; + return wrapping.Next(minValue, maxValue); + } - public NetRandomWrapper() : this(new Random()) { } + public int Next(int maxValue) + { + return wrapping.Next(maxValue); + } - public NetRandomWrapper(int seed) : this(new Random(seed)) { } - - public int Next(int minValue, int maxValue) - { - return wrapping.Next(minValue, maxValue); - } - - public int Next(int maxValue) - { - return wrapping.Next(maxValue); - } - - public int Next() - { - return wrapping.Next(); - } + public int Next() + { + return wrapping.Next(); } } \ No newline at end of file diff --git a/Modules/Random/Xorshift.cs b/Modules/Random/Xorshift.cs index d9a5a4a52..0b4f4d2f3 100644 --- a/Modules/Random/Xorshift.cs +++ b/Modules/Random/Xorshift.cs @@ -1,39 +1,38 @@ using System; -namespace EHR +namespace EHR; + +public class Xorshift(uint seed) : IRandom { - public class Xorshift(uint seed) : IRandom + // 参考元 + public const string REFERENCE = "https://ja.wikipedia.org/wiki/Xorshift"; + + private uint num = seed; + + public Xorshift() : this((uint)DateTime.UtcNow.Ticks) { } + + public int Next(int minValue, int maxValue) { - // 参考元 - public const string REFERENCE = "https://ja.wikipedia.org/wiki/Xorshift"; + if (minValue < 0 || maxValue < 0) throw new ArgumentOutOfRangeException("minValue and maxValue must be bigger than 0."); - private uint num = seed; + if (minValue > maxValue) throw new ArgumentException("maxValue must be bigger than minValue."); - public Xorshift() : this((uint)DateTime.UtcNow.Ticks) { } + if (minValue == maxValue) return minValue; - public int Next(int minValue, int maxValue) - { - if (minValue < 0 || maxValue < 0) throw new ArgumentOutOfRangeException("minValue and maxValue must be bigger than 0."); + return (int)(minValue + (Next() % (maxValue - minValue))); + } - if (minValue > maxValue) throw new ArgumentException("maxValue must be bigger than minValue."); + public int Next(int maxValue) + { + return Next(0, maxValue); + } - if (minValue == maxValue) return minValue; + public uint Next() + { + num ^= num << 13; + num ^= num >> 17; + num ^= num << 5; - return (int)(minValue + (Next() % (maxValue - minValue))); - } - - public int Next(int maxValue) - { - return Next(0, maxValue); - } - - public uint Next() - { - num ^= num << 13; - num ^= num >> 17; - num ^= num << 5; - - return num; - } + return num; } } \ No newline at end of file diff --git a/Modules/RoleBlockManager.cs b/Modules/RoleBlockManager.cs index bb00e0bfc..f1420ba5d 100644 --- a/Modules/RoleBlockManager.cs +++ b/Modules/RoleBlockManager.cs @@ -1,58 +1,57 @@ using System.Collections.Generic; using UnityEngine; -namespace EHR.Modules -{ - public static class RoleBlockManager - { - public static Dictionary RoleBlockedPlayers = []; +namespace EHR.Modules; - public static void Reset() +public static class RoleBlockManager +{ + public static Dictionary RoleBlockedPlayers = []; + + public static void Reset() + { + RoleBlockedPlayers = []; + } + + public static void AddRoleBlock(PlayerControl pc, float duration) + { + long now = Utils.TimeStamp; + + if (RoleBlockedPlayers.TryGetValue(pc.PlayerId, out (long StartTimeStamp, float Duration) data) && data.Duration - (now - data.StartTimeStamp) + 1 > duration) { - RoleBlockedPlayers = []; + Logger.Info($"{pc.GetNameWithRole()} got role blocked, but the duration is less than the previous one", "RoleBlockManager"); + return; } - public static void AddRoleBlock(PlayerControl pc, float duration) + RoleBlockedPlayers[pc.PlayerId] = (now, duration); + Logger.Info($"{pc.GetNameWithRole()} is now role blocked for {duration} seconds", "RoleBlockManager"); + } + + public static void OnFixedUpdate(PlayerControl pc) + { + if (RoleBlockedPlayers.TryGetValue(pc.PlayerId, out (long StartTimeStamp, float Duration) data)) { long now = Utils.TimeStamp; - if (RoleBlockedPlayers.TryGetValue(pc.PlayerId, out (long StartTimeStamp, float Duration) data) && data.Duration - (now - data.StartTimeStamp) + 1 > duration) + if (now - data.StartTimeStamp >= data.Duration) { - Logger.Info($"{pc.GetNameWithRole()} got role blocked, but the duration is less than the previous one", "RoleBlockManager"); - return; + RoleBlockedPlayers.Remove(pc.PlayerId); + Logger.Info($"{pc.GetNameWithRole()} is no longer role blocked", "RoleBlockManager"); } - - RoleBlockedPlayers[pc.PlayerId] = (now, duration); - Logger.Info($"{pc.GetNameWithRole()} is now role blocked for {duration} seconds", "RoleBlockManager"); - } - - public static void OnFixedUpdate(PlayerControl pc) - { - if (RoleBlockedPlayers.TryGetValue(pc.PlayerId, out (long StartTimeStamp, float Duration) data)) - { - long now = Utils.TimeStamp; - - if (now - data.StartTimeStamp >= data.Duration) - { - RoleBlockedPlayers.Remove(pc.PlayerId); - Logger.Info($"{pc.GetNameWithRole()} is no longer role blocked", "RoleBlockManager"); - } - } - } - - public static string GetBlockNotify(this BlockedAction blockedAction) - { - string blockedActionString = Translator.GetString($"{blockedAction}"); - string notify = string.Format(Translator.GetString("RoleBlockNotify"), blockedActionString); - return Utils.ColorString(Color.yellow, notify); } } - public enum BlockedAction + public static string GetBlockNotify(this BlockedAction blockedAction) { - Kill, - Report, - Sabotage, - Vent + string blockedActionString = Translator.GetString($"{blockedAction}"); + string notify = string.Format(Translator.GetString("RoleBlockNotify"), blockedActionString); + return Utils.ColorString(Color.yellow, notify); } +} + +public enum BlockedAction +{ + Kill, + Report, + Sabotage, + Vent } \ No newline at end of file diff --git a/Modules/SimpleButton.cs b/Modules/SimpleButton.cs index 2fbbc33a6..b00202403 100644 --- a/Modules/SimpleButton.cs +++ b/Modules/SimpleButton.cs @@ -2,101 +2,100 @@ using TMPro; using UnityEngine; -namespace EHR +namespace EHR; + +public class SimpleButton { - public class SimpleButton + private static PassiveButton baseButton; + private readonly BoxCollider2D buttonCollider; + private float _fontSize; + private Vector2 _scale; + + /// 新しいボタンを作成する + /// 親オブジェクト + /// オブジェクト名 + /// 通常時の背景色 + /// マウスホバー時の背景色 + /// クリック時に発火するアクション + /// ボタンのラベル + /// ボタンの大きさ + /// 初期状態でアクティブにするかどうか(デフォルトtrue) + public SimpleButton( + Transform parent, + string name, + Vector3 localPosition, + Color32 normalColor, + Color32 hoverColor, + Action action, + string label, + bool isActive = true) { - private static PassiveButton baseButton; - private readonly BoxCollider2D buttonCollider; - private float _fontSize; - private Vector2 _scale; + if (baseButton == null) throw new InvalidOperationException("baseButtonが未設定"); - /// 新しいボタンを作成する - /// 親オブジェクト - /// オブジェクト名 - /// 通常時の背景色 - /// マウスホバー時の背景色 - /// クリック時に発火するアクション - /// ボタンのラベル - /// ボタンの大きさ - /// 初期状態でアクティブにするかどうか(デフォルトtrue) - public SimpleButton( - Transform parent, - string name, - Vector3 localPosition, - Color32 normalColor, - Color32 hoverColor, - Action action, - string label, - bool isActive = true) - { - if (baseButton == null) throw new InvalidOperationException("baseButtonが未設定"); + Button = Object.Instantiate(baseButton, parent); + Label = Button.transform.Find("FontPlacer/Text_TMP").GetComponent(); + NormalSprite = Button.inactiveSprites.GetComponent(); + HoverSprite = Button.activeSprites.GetComponent(); + buttonCollider = Button.GetComponent(); - Button = Object.Instantiate(baseButton, parent); - Label = Button.transform.Find("FontPlacer/Text_TMP").GetComponent(); - NormalSprite = Button.inactiveSprites.GetComponent(); - HoverSprite = Button.activeSprites.GetComponent(); - buttonCollider = Button.GetComponent(); + // ラベルをセンタリング + Transform container = Label.transform.parent; + Object.Destroy(Label.GetComponent()); + container.SetLocalX(0f); + Label.transform.SetLocalX(0f); + Label.horizontalAlignment = HorizontalAlignmentOptions.Center; - // ラベルをセンタリング - Transform container = Label.transform.parent; - Object.Destroy(Label.GetComponent()); - container.SetLocalX(0f); - Label.transform.SetLocalX(0f); - Label.horizontalAlignment = HorizontalAlignmentOptions.Center; + Button.name = name; + Button.transform.localPosition = localPosition; + NormalSprite.color = normalColor; + HoverSprite.color = hoverColor; + Button.OnClick.AddListener(action); + Label.text = label; + Button.gameObject.SetActive(isActive); + } - Button.name = name; - Button.transform.localPosition = localPosition; - NormalSprite.color = normalColor; - HoverSprite.color = hoverColor; - Button.OnClick.AddListener(action); - Label.text = label; - Button.gameObject.SetActive(isActive); - } + public PassiveButton Button { get; } + public TextMeshPro Label { get; } + public SpriteRenderer NormalSprite { get; } + public SpriteRenderer HoverSprite { get; } - public PassiveButton Button { get; } - public TextMeshPro Label { get; } - public SpriteRenderer NormalSprite { get; } - public SpriteRenderer HoverSprite { get; } + public Vector2 Scale + { + get => _scale; + set => _scale = NormalSprite.size = HoverSprite.size = buttonCollider.size = value; + } - public Vector2 Scale - { - get => _scale; - set => _scale = NormalSprite.size = HoverSprite.size = buttonCollider.size = value; - } + public float FontSize + { + get => _fontSize; + set => _fontSize = Label.fontSize = Label.fontSizeMin = Label.fontSizeMax = value; + } - public float FontSize - { - get => _fontSize; - set => _fontSize = Label.fontSize = Label.fontSizeMin = Label.fontSizeMax = value; - } + public static void SetBase(PassiveButton passiveButton) + { + if (baseButton != null || passiveButton == null) return; - public static void SetBase(PassiveButton passiveButton) - { - if (baseButton != null || passiveButton == null) return; + // 複製 + baseButton = Object.Instantiate(passiveButton); + var label = baseButton.transform.Find("FontPlacer/Text_TMP").GetComponent(); + baseButton.gameObject.SetActive(false); + // シーン切替時に破棄されないようにする + Object.DontDestroyOnLoad(baseButton); + baseButton.name = "EHR_SimpleButtonBase"; + // 不要なコンポーネントを無効化 + Object.Destroy(baseButton.GetComponent()); + label.DestroyTranslator(); + label.fontSize = label.fontSizeMax = label.fontSizeMin = 3.5f; + label.enableWordWrapping = false; + label.text = "EHR SIMPLE BUTTON BASE"; + // 当たり判定がズレてるのを直す + var buttonCollider = baseButton.GetComponent(); + buttonCollider.offset = new(0f, 0f); + baseButton.OnClick = new(); + } - // 複製 - baseButton = Object.Instantiate(passiveButton); - var label = baseButton.transform.Find("FontPlacer/Text_TMP").GetComponent(); - baseButton.gameObject.SetActive(false); - // シーン切替時に破棄されないようにする - Object.DontDestroyOnLoad(baseButton); - baseButton.name = "EHR_SimpleButtonBase"; - // 不要なコンポーネントを無効化 - Object.Destroy(baseButton.GetComponent()); - label.DestroyTranslator(); - label.fontSize = label.fontSizeMax = label.fontSizeMin = 3.5f; - label.enableWordWrapping = false; - label.text = "EHR SIMPLE BUTTON BASE"; - // 当たり判定がズレてるのを直す - var buttonCollider = baseButton.GetComponent(); - buttonCollider.offset = new(0f, 0f); - baseButton.OnClick = new(); - } - - public static bool IsNullOrDestroyed(SimpleButton button) - { - return button == null || button.Button == null; - } + public static bool IsNullOrDestroyed(SimpleButton button) + { + return button == null || button.Button == null; } } \ No newline at end of file diff --git a/Modules/SpamManager.cs b/Modules/SpamManager.cs index b144e3cfb..7cd89aaa8 100644 --- a/Modules/SpamManager.cs +++ b/Modules/SpamManager.cs @@ -7,406 +7,405 @@ using System.Reflection; using System.Text; using static EHR.Translator; -namespace EHR +namespace EHR; + +public static class SpamManager { - public static class SpamManager + private static readonly string BANEDWORDS_FILE_PATH = "./EHR_DATA/BanWords.txt"; + public static List BanWords = []; + + public static void Init() { - private static readonly string BANEDWORDS_FILE_PATH = "./EHR_DATA/BanWords.txt"; - public static List BanWords = []; + CreateIfNotExists(); + BanWords = ReturnAllNewLinesInFile(BANEDWORDS_FILE_PATH); + } - public static void Init() + public static void CreateIfNotExists() + { + if (!File.Exists(BANEDWORDS_FILE_PATH)) { - CreateIfNotExists(); - BanWords = ReturnAllNewLinesInFile(BANEDWORDS_FILE_PATH); - } - - public static void CreateIfNotExists() - { - if (!File.Exists(BANEDWORDS_FILE_PATH)) + try { - try - { - if (!Directory.Exists(@"EHR_DATA")) Directory.CreateDirectory(@"EHR_DATA"); + if (!Directory.Exists(@"EHR_DATA")) Directory.CreateDirectory(@"EHR_DATA"); - if (File.Exists(@"./BanWords.txt")) - File.Move(@"./BanWords.txt", BANEDWORDS_FILE_PATH); - else - { - string fileName; - string[] name = CultureInfo.CurrentCulture.Name.Split("-"); - - if (name.Length >= 2) - { - fileName = name[0] switch - { - "zh" => "SChinese", - "ru" => "Russian", - _ => "English" - }; - } - else - fileName = "English"; - - Logger.Warn($"创建新的 BanWords 文件:{fileName}", "SpamManager"); - File.WriteAllText(BANEDWORDS_FILE_PATH, GetResourcesTxt($"EHR.Resources.Config.BanWords.{fileName}.txt")); - } - } - catch (Exception ex) - { - Logger.Exception(ex, "SpamManager"); - } - } - } - - private static string GetResourcesTxt(string path) - { - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); - stream.Position = 0; - using StreamReader reader = new(stream, Encoding.UTF8); - return reader.ReadToEnd(); - } - - public static List ReturnAllNewLinesInFile(string filename) - { - if (!File.Exists(filename)) return []; - - using StreamReader sr = new(filename, Encoding.GetEncoding("UTF-8")); - string text; - List sendList = []; - - while ((text = sr.ReadLine()) != null) - if (text.Length > 1 && text != "") - sendList.Add(text.Replace("\\n", "\n").ToLower()); - - return sendList; - } - - public static bool CheckSpam(PlayerControl player, string text) - { - if (player.IsLocalPlayer()) return false; - - string name = player.GetRealName(); - var kick = false; - var msg = string.Empty; - - if (Options.AutoKickStart.GetBool()) - { - if (ContainsStart(text) && GameStates.IsLobby) - { - if (!Main.SayStartTimes.ContainsKey(player.GetClientId())) Main.SayStartTimes.Add(player.GetClientId(), 0); - - Main.SayStartTimes[player.GetClientId()]++; - msg = string.Format(GetString("Message.WarnWhoSayStart"), name, Main.SayStartTimes[player.GetClientId()]); - - if (Main.SayStartTimes[player.GetClientId()] > Options.AutoKickStartTimes.GetInt()) - { - msg = string.Format(GetString("Message.KickStartAfterWarn"), name, Main.SayStartTimes[player.GetClientId()]); - kick = true; - } - - if (msg != string.Empty && msg != "") Utils.SendMessage(msg); - - if (kick) AmongUsClient.Instance.KickPlayer(player.GetClientId(), Options.AutoKickStartAsBan.GetBool()); - - return true; - } - } - - bool banned = BanWords.Any(text.Contains); - - if (!banned) return false; - - if (Options.AutoWarnStopWords.GetBool()) msg = string.Format(GetString("Message.WarnWhoSayBanWord"), name); - - if (Options.AutoKickStopWords.GetBool()) - { - if (!Main.SayBanwordsTimes.ContainsKey(player.GetClientId())) Main.SayBanwordsTimes.Add(player.GetClientId(), 0); - - Main.SayBanwordsTimes[player.GetClientId()]++; - msg = string.Format(GetString("Message.WarnWhoSayBanWordTimes"), name, Main.SayBanwordsTimes[player.GetClientId()]); - - if (Main.SayBanwordsTimes[player.GetClientId()] > Options.AutoKickStopWordsTimes.GetInt()) - { - msg = string.Format(GetString("Message.KickWhoSayBanWordAfterWarn"), name, Main.SayBanwordsTimes[player.GetClientId()]); - kick = true; - } - } - - if (msg != string.Empty && msg != "") - { - if (kick || !GameStates.IsInGame) - Utils.SendMessage(msg); + if (File.Exists(@"./BanWords.txt")) + File.Move(@"./BanWords.txt", BANEDWORDS_FILE_PATH); else - foreach (PlayerControl pc in Main.AllPlayerControls.Where(x => x.IsAlive() == player.IsAlive()).ToArray()) - Utils.SendMessage(msg, pc.PlayerId); + { + string fileName; + string[] name = CultureInfo.CurrentCulture.Name.Split("-"); + + if (name.Length >= 2) + { + fileName = name[0] switch + { + "zh" => "SChinese", + "ru" => "Russian", + _ => "English" + }; + } + else + fileName = "English"; + + Logger.Warn($"创建新的 BanWords 文件:{fileName}", "SpamManager"); + File.WriteAllText(BANEDWORDS_FILE_PATH, GetResourcesTxt($"EHR.Resources.Config.BanWords.{fileName}.txt")); + } } - - if (kick) AmongUsClient.Instance.KickPlayer(player.GetClientId(), Options.AutoKickStopWordsAsBan.GetBool()); - - return true; - } - - private static bool ContainsStart(string text) - { - text = text.Trim().ToLower(); - - var stNum = 0; - - for (var i = 0; i < text.Length; i++) + catch (Exception ex) { - if (text[i..].Equals("k")) stNum++; - - if (text[i..].Equals("开")) stNum++; + Logger.Exception(ex, "SpamManager"); } - - if (stNum >= 3) return true; - - if (text == "Start") return true; - - if (text == "start") return true; - - if (text == "/Start") return true; - - if (text == "/Start/") return true; - - if (text == "Start/") return true; - - if (text == "/start") return true; - - if (text == "/start/") return true; - - if (text == "start/") return true; - - if (text == "plsstart") return true; - - if (text == "pls start") return true; - - if (text == "please start") return true; - - if (text == "pleasestart") return true; - - if (text == "Plsstart") return true; - - if (text == "Pls start") return true; - - if (text == "Please start") return true; - - if (text == "Pleasestart") return true; - - if (text == "plsStart") return true; - - if (text == "pls Start") return true; - - if (text == "please Start") return true; - - if (text == "pleaseStart") return true; - - if (text == "PlsStart") return true; - - if (text == "Pls Start") return true; - - if (text == "Please Start") return true; - - if (text == "PleaseStart") return true; - - if (text == "sTart") return true; - - if (text == "stArt") return true; - - if (text == "staRt") return true; - - if (text == "starT") return true; - - if (text == "s t a r t") return true; - - if (text == "S t a r t") return true; - - if (text == "started") return true; - - if (text == "Started") return true; - - if (text == "s t a r t e d") return true; - - if (text == "S t a r t e d") return true; - - if (text == "Го") return true; - - if (text == "гО") return true; - - if (text == "го") return true; - - if (text == "Гоу") return true; - - if (text == "гоу") return true; - - if (text == "Старт") return true; - - if (text == "старт") return true; - - if (text == "/Старт") return true; - - if (text == "/Старт/") return true; - - if (text == "Старт/") return true; - - if (text == "/старт") return true; - - if (text == "/старт/") return true; - - if (text == "старт/") return true; - - if (text == "пжстарт") return true; - - if (text == "пж старт") return true; - - if (text == "пжСтарт") return true; - - if (text == "пж Старт") return true; - - if (text == "Пжстарт") return true; - - if (text == "Пж старт") return true; - - if (text == "ПжСтарт") return true; - - if (text == "Пж Старт") return true; - - if (text == "сТарт") return true; - - if (text == "стАрт") return true; - - if (text == "стаРт") return true; - - if (text == "старТ") return true; - - if (text == "с т а р т") return true; - - if (text == "С т а р т") return true; - - if (text == "начни") return true; - - if (text == "Начни") return true; - - if (text == "начинай") return true; - - if (text == "начинай уже") return true; - - if (text == "Начинай") return true; - - if (text == "Начинай уже") return true; - - if (text == "Начинай Уже") return true; - - if (text == "н а ч и н а й") return true; - - if (text == "Н а ч и н а й") return true; - - if (text == "пж го") return true; - - if (text == "пжго") return true; - - if (text == "Пж Го") return true; - - if (text == "Пж го") return true; - - if (text == "пж Го") return true; - - if (text == "ПжГо") return true; - - if (text == "Пжго") return true; - - if (text == "пжГо") return true; - - if (text == "ГоПж") return true; - - if (text == "гоПж") return true; - - if (text == "Гопж") return true; - - if (text == "开") return true; - - if (text == "快开") return true; - - if (text == "开始") return true; - - if (text == "开啊") return true; - - if (text == "开阿") return true; - - if (text == "kai") return true; - - if (text == "kaishi") return true; - - //if (text.Length >= 3) return false; - if (text.Contains("start")) return true; - - if (text.Contains("Start")) return true; - - if (text.Contains("STart")) return true; - - if (text.Contains("s t a r t")) return true; - - if (text.Contains("begin")) return true; - - if (text.Contains('了')) return false; - - if (text.Contains('没')) return false; - - if (text.Contains('吗')) return false; - - if (text.Contains('哈')) return false; - - if (text.Contains('还')) return false; - - if (text.Contains('现')) return false; - - if (text.Contains('不')) return false; - - if (text.Contains('可')) return false; - - if (text.Contains('刚')) return false; - - if (text.Contains('的')) return false; - - if (text.Contains('打')) return false; - - if (text.Contains('门')) return false; - - if (text.Contains('关')) return false; - - if (text.Contains('怎')) return false; - - if (text.Contains('要')) return false; - - if (text.Contains('摆')) return false; - - if (text.Contains('啦')) return false; - - if (text.Contains('咯')) return false; - - if (text.Contains('嘞')) return false; - - if (text.Contains('勒')) return false; - - if (text.Contains('心')) return false; - - if (text.Contains('呢')) return false; - - if (text.Contains('门')) return false; - - if (text.Contains('总')) return false; - - if (text.Contains('哥')) return false; - - if (text.Contains('姐')) return false; - - if (text.Contains('《')) return false; - - if (text.Contains('?')) return false; - - if (text.Contains('?')) return false; - - return text.Contains('开') || text.Contains("kai"); } } + + private static string GetResourcesTxt(string path) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + stream.Position = 0; + using StreamReader reader = new(stream, Encoding.UTF8); + return reader.ReadToEnd(); + } + + public static List ReturnAllNewLinesInFile(string filename) + { + if (!File.Exists(filename)) return []; + + using StreamReader sr = new(filename, Encoding.GetEncoding("UTF-8")); + string text; + List sendList = []; + + while ((text = sr.ReadLine()) != null) + if (text.Length > 1 && text != "") + sendList.Add(text.Replace("\\n", "\n").ToLower()); + + return sendList; + } + + public static bool CheckSpam(PlayerControl player, string text) + { + if (player.IsLocalPlayer()) return false; + + string name = player.GetRealName(); + var kick = false; + var msg = string.Empty; + + if (Options.AutoKickStart.GetBool()) + { + if (ContainsStart(text) && GameStates.IsLobby) + { + if (!Main.SayStartTimes.ContainsKey(player.GetClientId())) Main.SayStartTimes.Add(player.GetClientId(), 0); + + Main.SayStartTimes[player.GetClientId()]++; + msg = string.Format(GetString("Message.WarnWhoSayStart"), name, Main.SayStartTimes[player.GetClientId()]); + + if (Main.SayStartTimes[player.GetClientId()] > Options.AutoKickStartTimes.GetInt()) + { + msg = string.Format(GetString("Message.KickStartAfterWarn"), name, Main.SayStartTimes[player.GetClientId()]); + kick = true; + } + + if (msg != string.Empty && msg != "") Utils.SendMessage(msg); + + if (kick) AmongUsClient.Instance.KickPlayer(player.GetClientId(), Options.AutoKickStartAsBan.GetBool()); + + return true; + } + } + + bool banned = BanWords.Any(text.Contains); + + if (!banned) return false; + + if (Options.AutoWarnStopWords.GetBool()) msg = string.Format(GetString("Message.WarnWhoSayBanWord"), name); + + if (Options.AutoKickStopWords.GetBool()) + { + if (!Main.SayBanwordsTimes.ContainsKey(player.GetClientId())) Main.SayBanwordsTimes.Add(player.GetClientId(), 0); + + Main.SayBanwordsTimes[player.GetClientId()]++; + msg = string.Format(GetString("Message.WarnWhoSayBanWordTimes"), name, Main.SayBanwordsTimes[player.GetClientId()]); + + if (Main.SayBanwordsTimes[player.GetClientId()] > Options.AutoKickStopWordsTimes.GetInt()) + { + msg = string.Format(GetString("Message.KickWhoSayBanWordAfterWarn"), name, Main.SayBanwordsTimes[player.GetClientId()]); + kick = true; + } + } + + if (msg != string.Empty && msg != "") + { + if (kick || !GameStates.IsInGame) + Utils.SendMessage(msg); + else + foreach (PlayerControl pc in Main.AllPlayerControls.Where(x => x.IsAlive() == player.IsAlive()).ToArray()) + Utils.SendMessage(msg, pc.PlayerId); + } + + if (kick) AmongUsClient.Instance.KickPlayer(player.GetClientId(), Options.AutoKickStopWordsAsBan.GetBool()); + + return true; + } + + private static bool ContainsStart(string text) + { + text = text.Trim().ToLower(); + + var stNum = 0; + + for (var i = 0; i < text.Length; i++) + { + if (text[i..].Equals("k")) stNum++; + + if (text[i..].Equals("开")) stNum++; + } + + if (stNum >= 3) return true; + + if (text == "Start") return true; + + if (text == "start") return true; + + if (text == "/Start") return true; + + if (text == "/Start/") return true; + + if (text == "Start/") return true; + + if (text == "/start") return true; + + if (text == "/start/") return true; + + if (text == "start/") return true; + + if (text == "plsstart") return true; + + if (text == "pls start") return true; + + if (text == "please start") return true; + + if (text == "pleasestart") return true; + + if (text == "Plsstart") return true; + + if (text == "Pls start") return true; + + if (text == "Please start") return true; + + if (text == "Pleasestart") return true; + + if (text == "plsStart") return true; + + if (text == "pls Start") return true; + + if (text == "please Start") return true; + + if (text == "pleaseStart") return true; + + if (text == "PlsStart") return true; + + if (text == "Pls Start") return true; + + if (text == "Please Start") return true; + + if (text == "PleaseStart") return true; + + if (text == "sTart") return true; + + if (text == "stArt") return true; + + if (text == "staRt") return true; + + if (text == "starT") return true; + + if (text == "s t a r t") return true; + + if (text == "S t a r t") return true; + + if (text == "started") return true; + + if (text == "Started") return true; + + if (text == "s t a r t e d") return true; + + if (text == "S t a r t e d") return true; + + if (text == "Го") return true; + + if (text == "гО") return true; + + if (text == "го") return true; + + if (text == "Гоу") return true; + + if (text == "гоу") return true; + + if (text == "Старт") return true; + + if (text == "старт") return true; + + if (text == "/Старт") return true; + + if (text == "/Старт/") return true; + + if (text == "Старт/") return true; + + if (text == "/старт") return true; + + if (text == "/старт/") return true; + + if (text == "старт/") return true; + + if (text == "пжстарт") return true; + + if (text == "пж старт") return true; + + if (text == "пжСтарт") return true; + + if (text == "пж Старт") return true; + + if (text == "Пжстарт") return true; + + if (text == "Пж старт") return true; + + if (text == "ПжСтарт") return true; + + if (text == "Пж Старт") return true; + + if (text == "сТарт") return true; + + if (text == "стАрт") return true; + + if (text == "стаРт") return true; + + if (text == "старТ") return true; + + if (text == "с т а р т") return true; + + if (text == "С т а р т") return true; + + if (text == "начни") return true; + + if (text == "Начни") return true; + + if (text == "начинай") return true; + + if (text == "начинай уже") return true; + + if (text == "Начинай") return true; + + if (text == "Начинай уже") return true; + + if (text == "Начинай Уже") return true; + + if (text == "н а ч и н а й") return true; + + if (text == "Н а ч и н а й") return true; + + if (text == "пж го") return true; + + if (text == "пжго") return true; + + if (text == "Пж Го") return true; + + if (text == "Пж го") return true; + + if (text == "пж Го") return true; + + if (text == "ПжГо") return true; + + if (text == "Пжго") return true; + + if (text == "пжГо") return true; + + if (text == "ГоПж") return true; + + if (text == "гоПж") return true; + + if (text == "Гопж") return true; + + if (text == "开") return true; + + if (text == "快开") return true; + + if (text == "开始") return true; + + if (text == "开啊") return true; + + if (text == "开阿") return true; + + if (text == "kai") return true; + + if (text == "kaishi") return true; + + //if (text.Length >= 3) return false; + if (text.Contains("start")) return true; + + if (text.Contains("Start")) return true; + + if (text.Contains("STart")) return true; + + if (text.Contains("s t a r t")) return true; + + if (text.Contains("begin")) return true; + + if (text.Contains('了')) return false; + + if (text.Contains('没')) return false; + + if (text.Contains('吗')) return false; + + if (text.Contains('哈')) return false; + + if (text.Contains('还')) return false; + + if (text.Contains('现')) return false; + + if (text.Contains('不')) return false; + + if (text.Contains('可')) return false; + + if (text.Contains('刚')) return false; + + if (text.Contains('的')) return false; + + if (text.Contains('打')) return false; + + if (text.Contains('门')) return false; + + if (text.Contains('关')) return false; + + if (text.Contains('怎')) return false; + + if (text.Contains('要')) return false; + + if (text.Contains('摆')) return false; + + if (text.Contains('啦')) return false; + + if (text.Contains('咯')) return false; + + if (text.Contains('嘞')) return false; + + if (text.Contains('勒')) return false; + + if (text.Contains('心')) return false; + + if (text.Contains('呢')) return false; + + if (text.Contains('门')) return false; + + if (text.Contains('总')) return false; + + if (text.Contains('哥')) return false; + + if (text.Contains('姐')) return false; + + if (text.Contains('《')) return false; + + if (text.Contains('?')) return false; + + if (text.Contains('?')) return false; + + return text.Contains('开') || text.Contains("kai"); + } } \ No newline at end of file diff --git a/Modules/Statistics.cs b/Modules/Statistics.cs index 9a2f1657c..f930810aa 100644 --- a/Modules/Statistics.cs +++ b/Modules/Statistics.cs @@ -4,330 +4,329 @@ using EHR.AddOns.Common; using EHR.Crewmate; using EHR.Neutral; -namespace EHR.Modules +namespace EHR.Modules; + +public static class Statistics { - public static class Statistics + private const int MinPlayers = 3; + private static bool OnlyVotingForKillersAsCrew = true; + private static bool VotedBySomeone; + public static int VentTimes; + public static bool HasUsedAnyCommand; + + public static void OnGameEnd() { - private const int MinPlayers = 3; - private static bool OnlyVotingForKillersAsCrew = true; - private static bool VotedBySomeone; - public static int VentTimes; - public static bool HasUsedAnyCommand; + if (CustomWinnerHolder.WinnerTeam is CustomWinner.None or CustomWinner.Draw or CustomWinner.Error || Main.AllPlayerControls.Length <= 3) return; - public static void OnGameEnd() + var lp = PlayerControl.LocalPlayer; + var role = lp.GetCustomRole(); + var addons = lp.GetCustomSubRoles(); + + try { - if (CustomWinnerHolder.WinnerTeam is CustomWinner.None or CustomWinner.Draw or CustomWinner.Error || Main.AllPlayerControls.Length <= 3) return; + bool won = CustomWinnerHolder.WinnerIds.Contains(lp.PlayerId) || CustomWinnerHolder.WinnerRoles.Contains(role) || (CustomWinnerHolder.WinnerTeam == CustomWinner.Bloodlust && addons.Contains(CustomRoles.Bloodlust)); - var lp = PlayerControl.LocalPlayer; - var role = lp.GetCustomRole(); - var addons = lp.GetCustomSubRoles(); - - try + switch (Options.CurrentGameMode) { - bool won = CustomWinnerHolder.WinnerIds.Contains(lp.PlayerId) || CustomWinnerHolder.WinnerRoles.Contains(role) || (CustomWinnerHolder.WinnerTeam == CustomWinner.Bloodlust && addons.Contains(CustomRoles.Bloodlust)); + case CustomGameMode.FFA when won: + Achievements.Type.SerialKiller.CompleteAfterGameEnd(); + return; + case CustomGameMode.SoloKombat when won: + Achievements.Type.PVPMaster.CompleteAfterGameEnd(); + return; + case CustomGameMode.MoveAndStop when won: + Achievements.Type.HarderThanDrivingThroughTrafficLightsRight.CompleteAfterGameEnd(); + return; + case CustomGameMode.Speedrun when won: + Achievements.Type.TwoFast4You.CompleteAfterGameEnd(); + return; + case CustomGameMode.HotPotato when won: + Achievements.Type.TooHotForMe.CompleteAfterGameEnd(); + return; + case CustomGameMode.HideAndSeek when won: + Achievements.Type.SeekAndHide.CompleteAfterGameEnd(); + return; + case CustomGameMode.NaturalDisasters when won: + Achievements.Type.Two012.CompleteAfterGameEnd(); + return; + case CustomGameMode.CaptureTheFlag: + if (won) Achievements.Type.YourFlagIsMine.CompleteAfterGameEnd(); - switch (Options.CurrentGameMode) - { - case CustomGameMode.FFA when won: - Achievements.Type.SerialKiller.CompleteAfterGameEnd(); - return; - case CustomGameMode.SoloKombat when won: - Achievements.Type.PVPMaster.CompleteAfterGameEnd(); - return; - case CustomGameMode.MoveAndStop when won: - Achievements.Type.HarderThanDrivingThroughTrafficLightsRight.CompleteAfterGameEnd(); - return; - case CustomGameMode.Speedrun when won: - Achievements.Type.TwoFast4You.CompleteAfterGameEnd(); - return; - case CustomGameMode.HotPotato when won: - Achievements.Type.TooHotForMe.CompleteAfterGameEnd(); - return; - case CustomGameMode.HideAndSeek when won: - Achievements.Type.SeekAndHide.CompleteAfterGameEnd(); - return; - case CustomGameMode.NaturalDisasters when won: - Achievements.Type.Two012.CompleteAfterGameEnd(); - return; - case CustomGameMode.CaptureTheFlag: - if (won) Achievements.Type.YourFlagIsMine.CompleteAfterGameEnd(); + if (Main.AllPlayerControls.Select(x => (pc: x, time: CTFManager.GetFlagTime(x.PlayerId))).MaxBy(x => x.time).pc.PlayerId == lp.PlayerId) + Achievements.Type.FlagMaster.CompleteAfterGameEnd(); - if (Main.AllPlayerControls.Select(x => (pc: x, time: CTFManager.GetFlagTime(x.PlayerId))).MaxBy(x => x.time).pc.PlayerId == lp.PlayerId) - Achievements.Type.FlagMaster.CompleteAfterGameEnd(); + if (Main.AllPlayerControls.Select(x => (pc: x, time: CTFManager.GetTagCount(x.PlayerId))).MaxBy(x => x.time).pc.PlayerId == lp.PlayerId) + Achievements.Type.Tag.CompleteAfterGameEnd(); - if (Main.AllPlayerControls.Select(x => (pc: x, time: CTFManager.GetTagCount(x.PlayerId))).MaxBy(x => x.time).pc.PlayerId == lp.PlayerId) - Achievements.Type.Tag.CompleteAfterGameEnd(); - - return; - case CustomGameMode.RoomRush when won: - Achievements.Type.BestReactionTime.CompleteAfterGameEnd(); - return; - case CustomGameMode.Standard: - Reset(); - break; - } - - if (won && addons.Contains(CustomRoles.Undead) && CustomWinnerHolder.WinnerIds.ToValidPlayers().Count(x => x.Is(CustomRoles.Necromancer)) >= 3) - Achievements.Type.IdeasLiveOn.CompleteAfterGameEnd(); - - if (Main.PlayerStates.Values.Count(x => x.GetRealKiller() == lp.PlayerId) >= 7) - Achievements.Type.TheKillingMachine2Point0.CompleteAfterGameEnd(); - - if (won && lp.IsCrewmate() && Main.AllAlivePlayerControls.Length == 1) - Achievements.Type.TheLastSurvivor.CompleteAfterGameEnd(); - - if (addons.Contains(CustomRoles.Spurt) && Spurt.LocalPlayerAvoidsZeroAndOneHundredPrecent) - Achievements.Type.ExpertControl.CompleteAfterGameEnd(); - - if (Utils.GameStartTimeStamp + 90 > Utils.TimeStamp) - Achievements.Type.Speedrun.CompleteAfterGameEnd(); - - if (won && CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor && lp.IsMadmate() && !Main.AllAlivePlayerControls.Any(x => x.IsImpostor())) - Achievements.Type.Carried.CompleteAfterGameEnd(); - - if (won && lp.IsNeutralKiller()) - Achievements.Type.CommonEnemyNo1.CompleteAfterGameEnd(); - - if (won && lp.IsImpostor()) - { - if (Main.PlayerStates.Values.All(x => x.GetRealKiller() != lp.PlayerId)) - Achievements.Type.InnocentImpostor.CompleteAfterGameEnd(); - - if (Main.NormalOptions.NumImpostors > 1 && Main.AllPlayerControls.Where(x => x.IsImpostor()).All(x => x.IsAlive())) - Achievements.Type.ImpostorGang.CompleteAfterGameEnd(); - } - - switch (CustomWinnerHolder.WinnerTeam) - { - case CustomWinner.None when Main.PlayerStates.Values.FindFirst(x => x.SubRoles.Contains(CustomRoles.Avanger), out var state) && state.GetRealKiller().GetPlayer().Is(CustomRoles.OverKiller): - Achievements.Type.FuriousAvenger.CompleteAfterGameEnd(); - break; - case CustomWinner.Crewmate when won && Main.AllPlayerControls.Count(x => x.IsImpostor() || x.IsNeutralKiller()) >= 2 && MeetingStates.MeetingNum < 3: - Achievements.Type.BrainStorm.CompleteAfterGameEnd(); - break; - case CustomWinner.Lovers when won && role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor: - Achievements.Type.NothingCanStopLove.CompleteAfterGameEnd(); - break; - case CustomWinner.Executioner: - case CustomWinner.Innocent: - case CustomWinner.Jester: - - if (CustomWinnerHolder.AdditionalWinnerTeams.Contains(AdditionalWinners.Executioner) || - CustomWinnerHolder.WinnerIds.ToValidPlayers().Any(x => x.GetCustomRole() is CustomRoles.Executioner or CustomRoles.Innocent or CustomRoles.Jester)) - Achievements.Type.CoordinatedAttack.CompleteAfterGameEnd(); - - break; - } - - switch (role) - { - case CustomRoles.Sheriff when won && lp.IsAlive() && Main.PlayerStates.Values.Where(x => x.IsDead && x.deathReason == PlayerState.DeathReason.Kill).All(x => x.GetRealKiller() == lp.PlayerId): - Achievements.Type.Superhero.CompleteAfterGameEnd(); - break; - case CustomRoles.Snitch when Snitch.IsExposed.TryGetValue(lp.PlayerId, out var exposed) && exposed && CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate && won: - Achievements.Type.CrewHero.Complete(); - break; - case CustomRoles.Minimalism when Main.PlayerStates.Values.Count(x => x.GetRealKiller() == lp.PlayerId) >= 8: - Achievements.Type.Bloodbath.CompleteAfterGameEnd(); - break; - case CustomRoles.Technician when Technician.LocalPlayerFixedSabotageTypes.Count >= 4: - Achievements.Type.AntiSaboteur.CompleteAfterGameEnd(); - break; - case CustomRoles.Bargainer when won && Bargainer.PurchasedItems.Count >= 3: - Achievements.Type.P2W.CompleteAfterGameEnd(); - break; - case CustomRoles.Lawyer when Lawyer.Target.TryGetValue(lp.PlayerId, out var ltg) && Main.PlayerStates.TryGetValue(ltg, out var ltgState) && ltgState.IsDead && ltgState.MainRole.IsCrewmate() && !ltgState.SubRoles.Contains(CustomRoles.Bloodlust) && won && CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor: - Achievements.Type.LiarLiar.CompleteAfterGameEnd(); - break; - } - - int correctGuesses = Main.PlayerStates.Values.Count(x => !x.Player.IsLocalPlayer() && x.GetRealKiller() == lp.PlayerId && x.deathReason == PlayerState.DeathReason.Gambled); - - if (correctGuesses >= 1) Achievements.Type.LuckOrObservation.CompleteAfterGameEnd(); - if (correctGuesses >= 2) Achievements.Type.BeginnerGuesser.CompleteAfterGameEnd(); - if (correctGuesses >= 4) Achievements.Type.GuessMaster.CompleteAfterGameEnd(); - if (correctGuesses >= 6) Achievements.Type.BestGuesserAward.CompleteAfterGameEnd(); - } - catch (Exception e) - { - Utils.ThrowException(e); + return; + case CustomGameMode.RoomRush when won: + Achievements.Type.BestReactionTime.CompleteAfterGameEnd(); + return; + case CustomGameMode.Standard: + Reset(); + break; } - return; + if (won && addons.Contains(CustomRoles.Undead) && CustomWinnerHolder.WinnerIds.ToValidPlayers().Count(x => x.Is(CustomRoles.Necromancer)) >= 3) + Achievements.Type.IdeasLiveOn.CompleteAfterGameEnd(); - void Reset() + if (Main.PlayerStates.Values.Count(x => x.GetRealKiller() == lp.PlayerId) >= 7) + Achievements.Type.TheKillingMachine2Point0.CompleteAfterGameEnd(); + + if (won && lp.IsCrewmate() && Main.AllAlivePlayerControls.Length == 1) + Achievements.Type.TheLastSurvivor.CompleteAfterGameEnd(); + + if (addons.Contains(CustomRoles.Spurt) && Spurt.LocalPlayerAvoidsZeroAndOneHundredPrecent) + Achievements.Type.ExpertControl.CompleteAfterGameEnd(); + + if (Utils.GameStartTimeStamp + 90 > Utils.TimeStamp) + Achievements.Type.Speedrun.CompleteAfterGameEnd(); + + if (won && CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor && lp.IsMadmate() && !Main.AllAlivePlayerControls.Any(x => x.IsImpostor())) + Achievements.Type.Carried.CompleteAfterGameEnd(); + + if (won && lp.IsNeutralKiller()) + Achievements.Type.CommonEnemyNo1.CompleteAfterGameEnd(); + + if (won && lp.IsImpostor()) { - if (OnlyVotingForKillersAsCrew && lp.IsCrewmate() && !MeetingStates.FirstMeeting) Achievements.Type.MasterDetective.CompleteAfterGameEnd(); - OnlyVotingForKillersAsCrew = true; + if (Main.PlayerStates.Values.All(x => x.GetRealKiller() != lp.PlayerId)) + Achievements.Type.InnocentImpostor.CompleteAfterGameEnd(); - if (!VotedBySomeone && (lp.IsImpostor() || lp.IsNeutralKiller()) && !MeetingStates.FirstMeeting) Achievements.Type.Unsuspected.CompleteAfterGameEnd(); - VotedBySomeone = false; - - if (VentTimes >= 50) Achievements.Type.Vectory.CompleteAfterGameEnd(); - VentTimes = 0; - - if (!HasUsedAnyCommand) Achievements.Type.AndForWhatDidICodeTheseCommandsForIfYouDontUseThemAtAll.CompleteAfterGameEnd(); - HasUsedAnyCommand = false; + if (Main.NormalOptions.NumImpostors > 1 && Main.AllPlayerControls.Where(x => x.IsImpostor()).All(x => x.IsAlive())) + Achievements.Type.ImpostorGang.CompleteAfterGameEnd(); } + + switch (CustomWinnerHolder.WinnerTeam) + { + case CustomWinner.None when Main.PlayerStates.Values.FindFirst(x => x.SubRoles.Contains(CustomRoles.Avanger), out var state) && state.GetRealKiller().GetPlayer().Is(CustomRoles.OverKiller): + Achievements.Type.FuriousAvenger.CompleteAfterGameEnd(); + break; + case CustomWinner.Crewmate when won && Main.AllPlayerControls.Count(x => x.IsImpostor() || x.IsNeutralKiller()) >= 2 && MeetingStates.MeetingNum < 3: + Achievements.Type.BrainStorm.CompleteAfterGameEnd(); + break; + case CustomWinner.Lovers when won && role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor: + Achievements.Type.NothingCanStopLove.CompleteAfterGameEnd(); + break; + case CustomWinner.Executioner: + case CustomWinner.Innocent: + case CustomWinner.Jester: + + if (CustomWinnerHolder.AdditionalWinnerTeams.Contains(AdditionalWinners.Executioner) || + CustomWinnerHolder.WinnerIds.ToValidPlayers().Any(x => x.GetCustomRole() is CustomRoles.Executioner or CustomRoles.Innocent or CustomRoles.Jester)) + Achievements.Type.CoordinatedAttack.CompleteAfterGameEnd(); + + break; + } + + switch (role) + { + case CustomRoles.Sheriff when won && lp.IsAlive() && Main.PlayerStates.Values.Where(x => x.IsDead && x.deathReason == PlayerState.DeathReason.Kill).All(x => x.GetRealKiller() == lp.PlayerId): + Achievements.Type.Superhero.CompleteAfterGameEnd(); + break; + case CustomRoles.Snitch when Snitch.IsExposed.TryGetValue(lp.PlayerId, out var exposed) && exposed && CustomWinnerHolder.WinnerTeam == CustomWinner.Crewmate && won: + Achievements.Type.CrewHero.Complete(); + break; + case CustomRoles.Minimalism when Main.PlayerStates.Values.Count(x => x.GetRealKiller() == lp.PlayerId) >= 8: + Achievements.Type.Bloodbath.CompleteAfterGameEnd(); + break; + case CustomRoles.Technician when Technician.LocalPlayerFixedSabotageTypes.Count >= 4: + Achievements.Type.AntiSaboteur.CompleteAfterGameEnd(); + break; + case CustomRoles.Bargainer when won && Bargainer.PurchasedItems.Count >= 3: + Achievements.Type.P2W.CompleteAfterGameEnd(); + break; + case CustomRoles.Lawyer when Lawyer.Target.TryGetValue(lp.PlayerId, out var ltg) && Main.PlayerStates.TryGetValue(ltg, out var ltgState) && ltgState.IsDead && ltgState.MainRole.IsCrewmate() && !ltgState.SubRoles.Contains(CustomRoles.Bloodlust) && won && CustomWinnerHolder.WinnerTeam == CustomWinner.Impostor: + Achievements.Type.LiarLiar.CompleteAfterGameEnd(); + break; + } + + int correctGuesses = Main.PlayerStates.Values.Count(x => !x.Player.IsLocalPlayer() && x.GetRealKiller() == lp.PlayerId && x.deathReason == PlayerState.DeathReason.Gambled); + + if (correctGuesses >= 1) Achievements.Type.LuckOrObservation.CompleteAfterGameEnd(); + if (correctGuesses >= 2) Achievements.Type.BeginnerGuesser.CompleteAfterGameEnd(); + if (correctGuesses >= 4) Achievements.Type.GuessMaster.CompleteAfterGameEnd(); + if (correctGuesses >= 6) Achievements.Type.BestGuesserAward.CompleteAfterGameEnd(); + } + catch (Exception e) + { + Utils.ThrowException(e); } - public static void OnVotingComplete(MeetingHud.VoterState[] states, NetworkedPlayerInfo exiledPlayer, bool tie, bool dictator) + return; + + void Reset() { - try + if (OnlyVotingForKillersAsCrew && lp.IsCrewmate() && !MeetingStates.FirstMeeting) Achievements.Type.MasterDetective.CompleteAfterGameEnd(); + OnlyVotingForKillersAsCrew = true; + + if (!VotedBySomeone && (lp.IsImpostor() || lp.IsNeutralKiller()) && !MeetingStates.FirstMeeting) Achievements.Type.Unsuspected.CompleteAfterGameEnd(); + VotedBySomeone = false; + + if (VentTimes >= 50) Achievements.Type.Vectory.CompleteAfterGameEnd(); + VentTimes = 0; + + if (!HasUsedAnyCommand) Achievements.Type.AndForWhatDidICodeTheseCommandsForIfYouDontUseThemAtAll.CompleteAfterGameEnd(); + HasUsedAnyCommand = false; + } + } + + public static void OnVotingComplete(MeetingHud.VoterState[] states, NetworkedPlayerInfo exiledPlayer, bool tie, bool dictator) + { + try + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || Main.AllPlayerControls.Length <= MinPlayers) return; + + PlayerControl lp = PlayerControl.LocalPlayer; + + bool amDictator = dictator && lp.Is(CustomRoles.Dictator); + + if (amDictator) Achievements.Type.KimJongUnExperience.Complete(); + + bool lpHasVS = states.FindFirst(x => x.VoterId == lp.PlayerId, out var lpVS); + + bool exiled = exiledPlayer != null && exiledPlayer.Object != null; + + if (!tie && exiled && exiledPlayer.Object.Is(CustomRoles.Jester)) { - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || Main.AllPlayerControls.Length <= MinPlayers) return; + if (states.Any(x => x.VoterId == lp.PlayerId && x.VotedForId == exiledPlayer.PlayerId)) + Achievements.Type.HowCouldIBelieveThem.Complete(); - PlayerControl lp = PlayerControl.LocalPlayer; - - bool amDictator = dictator && lp.Is(CustomRoles.Dictator); - - if (amDictator) Achievements.Type.KimJongUnExperience.Complete(); - - bool lpHasVS = states.FindFirst(x => x.VoterId == lp.PlayerId, out var lpVS); - - bool exiled = exiledPlayer != null && exiledPlayer.Object != null; - - if (!tie && exiled && exiledPlayer.Object.Is(CustomRoles.Jester)) - { - if (states.Any(x => x.VoterId == lp.PlayerId && x.VotedForId == exiledPlayer.PlayerId)) - Achievements.Type.HowCouldIBelieveThem.Complete(); - - if (amDictator && lpHasVS && lpVS.VotedForId == exiledPlayer.PlayerId) - Achievements.Type.WhyJustWhy.Complete(); - } - - if (lpHasVS) - { - PlayerControl voteTarget = lpVS.VotedForId.GetPlayer(); - - if (!voteTarget.IsCrewmate() && !voteTarget.IsConverted()) - OnlyVotingForKillersAsCrew = false; - } - - if (states.Any(x => x.VotedForId == lp.PlayerId)) - VotedBySomeone = true; - - if (exiled && exiledPlayer.PlayerId == lp.PlayerId && Main.PlayerStates.Values.Any(x => x.SubRoles.Contains(CustomRoles.Bait) && x.GetRealKiller() == lp.PlayerId)) - Achievements.Type.Gotcha.Complete(); - - if (lp.Is(CustomRoles.Lyncher) && Main.PlayerStates.Values.Count(x => x.deathReason == PlayerState.DeathReason.Gambled && x.GetRealKiller() == lp.PlayerId) >= 3) - Achievements.Type.GetLynched.Complete(); - - LateTask.New(() => - { - if (GameStates.IsEnded) return; - - var aapc = Main.AllAlivePlayerControls; - - if (aapc.Length == 2 && lp.IsAlive() && aapc.All(x => x.IsNeutralKiller() || x.IsImpostor())) - Achievements.Type.Duel.Complete(); - }, 12f, log: false); + if (amDictator && lpHasVS && lpVS.VotedForId == exiledPlayer.PlayerId) + Achievements.Type.WhyJustWhy.Complete(); } - catch (Exception e) + + if (lpHasVS) { - Utils.ThrowException(e); + PlayerControl voteTarget = lpVS.VotedForId.GetPlayer(); + + if (!voteTarget.IsCrewmate() && !voteTarget.IsConverted()) + OnlyVotingForKillersAsCrew = false; + } + + if (states.Any(x => x.VotedForId == lp.PlayerId)) + VotedBySomeone = true; + + if (exiled && exiledPlayer.PlayerId == lp.PlayerId && Main.PlayerStates.Values.Any(x => x.SubRoles.Contains(CustomRoles.Bait) && x.GetRealKiller() == lp.PlayerId)) + Achievements.Type.Gotcha.Complete(); + + if (lp.Is(CustomRoles.Lyncher) && Main.PlayerStates.Values.Count(x => x.deathReason == PlayerState.DeathReason.Gambled && x.GetRealKiller() == lp.PlayerId) >= 3) + Achievements.Type.GetLynched.Complete(); + + LateTask.New(() => + { + if (GameStates.IsEnded) return; + + var aapc = Main.AllAlivePlayerControls; + + if (aapc.Length == 2 && lp.IsAlive() && aapc.All(x => x.IsNeutralKiller() || x.IsImpostor())) + Achievements.Type.Duel.Complete(); + }, 12f, log: false); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public static void OnRoleSelectionComplete() + { + try + { + if (!CustomRoleSelector.RoleResult.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out CustomRoles role) || Main.AllPlayerControls.Length <= MinPlayers) return; + + const float delay = 15f; + + switch (role) + { + case CustomRoles.Dad: + Achievements.Type.WhatKindOfGeniusCameUpWithThisRoleIdea.CompleteAfterDelay(delay); + break; + case CustomRoles.Crewmate: + case CustomRoles.CrewmateEHR: + Achievements.Type.Bruh.CompleteAfterDelay(delay); + break; } } - - public static void OnRoleSelectionComplete() + catch (Exception e) { - try + Utils.ThrowException(e); + } + } + + public static void OnMurder(PlayerControl killer, PlayerControl target) + { + try + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || killer.PlayerId == target.PlayerId || Main.AllPlayerControls.Length <= MinPlayers) return; + + if (killer.IsLocalPlayer()) { - if (!CustomRoleSelector.RoleResult.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out CustomRoles role) || Main.AllPlayerControls.Length <= MinPlayers) return; + if (Main.AliveImpostorCount == 0 && killer.IsCrewmate() && target.IsImpostor() && !Main.AllAlivePlayerControls.Any(x => x.IsNeutralKiller())) + Achievements.Type.ImCrewISwear.Complete(); - const float delay = 15f; + if ((killer.IsImpostor() && target.IsMadmate()) || (killer.IsMadmate() && target.IsImpostor())) + Achievements.Type.BetrayalLevel100.CompleteAfterGameEnd(); - switch (role) + PlayerState killerState = Main.PlayerStates[killer.PlayerId]; + PlayerState targetState = Main.PlayerStates[target.PlayerId]; + + if ((targetState.MainRole == CustomRoles.Romantic && Romantic.PartnerId == killer.PlayerId) || + (targetState.SubRoles.Contains(CustomRoles.Lovers) && killer.Is(CustomRoles.Lovers)) || + (targetState.MainRole == CustomRoles.Lawyer && Lawyer.Target.TryGetValue(target.PlayerId, out var ltg) && ltg == killer.PlayerId) || + (targetState.Role is Totocalcio tc && tc.BetPlayer == killer.PlayerId)) + Achievements.Type.WhatHaveIDone.CompleteAfterGameEnd(); + + if (targetState.MainRole == CustomRoles.Snitch && Snitch.IsExposed.TryGetValue(target.PlayerId, out bool exposed) && exposed) + Achievements.Type.ThatWasClose.CompleteAfterGameEnd(); + + switch (killerState.MainRole) { - case CustomRoles.Dad: - Achievements.Type.WhatKindOfGeniusCameUpWithThisRoleIdea.CompleteAfterDelay(delay); + case CustomRoles.Traitor when targetState.MainRole == CustomRoles.Refugee: + Achievements.Type.TheRealTraitor.CompleteAfterGameEnd(); break; - case CustomRoles.Crewmate: - case CustomRoles.CrewmateEHR: - Achievements.Type.Bruh.CompleteAfterDelay(delay); + case CustomRoles.Bargainer when targetState.MainRole == CustomRoles.Merchant: + case CustomRoles.Merchant when targetState.MainRole == CustomRoles.Bargainer: + Achievements.Type.EconomicCompetition.CompleteAfterGameEnd(); + break; + case CustomRoles.Crusader when targetState.MainRole is CustomRoles.Witch or CustomRoles.HexMaster or CustomRoles.Vampire or CustomRoles.Warlock: + Achievements.Type.Inquisition.CompleteAfterGameEnd(); break; } + + if (killerState.MainRole == targetState.MainRole) + Achievements.Type.ThereCanOnlyBeOne.CompleteAfterGameEnd(); } - catch (Exception e) + + if (target.IsLocalPlayer()) { - Utils.ThrowException(e); + PlayerState targetState = Main.PlayerStates[target.PlayerId]; + + if (targetState.MainRole is CustomRoles.Maverick or CustomRoles.Opportunist) + Achievements.Type.YouDidTheExactOppositeOfWhatYouWereSupposedToDo.Complete(); } } - - public static void OnMurder(PlayerControl killer, PlayerControl target) + catch (Exception e) { - try + Utils.ThrowException(e); + } + } + + public static void OnShapeshift(PlayerControl shapeshifter, bool shapeshifting, bool animated) + { + try + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || Main.AllPlayerControls.Length <= MinPlayers) return; + + if (shapeshifter.IsLocalPlayer() && shapeshifting && animated) { - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || killer.PlayerId == target.PlayerId || Main.AllPlayerControls.Length <= MinPlayers) return; - - if (killer.IsLocalPlayer()) - { - if (Main.AliveImpostorCount == 0 && killer.IsCrewmate() && target.IsImpostor() && !Main.AllAlivePlayerControls.Any(x => x.IsNeutralKiller())) - Achievements.Type.ImCrewISwear.Complete(); - - if ((killer.IsImpostor() && target.IsMadmate()) || (killer.IsMadmate() && target.IsImpostor())) - Achievements.Type.BetrayalLevel100.CompleteAfterGameEnd(); - - PlayerState killerState = Main.PlayerStates[killer.PlayerId]; - PlayerState targetState = Main.PlayerStates[target.PlayerId]; - - if ((targetState.MainRole == CustomRoles.Romantic && Romantic.PartnerId == killer.PlayerId) || - (targetState.SubRoles.Contains(CustomRoles.Lovers) && killer.Is(CustomRoles.Lovers)) || - (targetState.MainRole == CustomRoles.Lawyer && Lawyer.Target.TryGetValue(target.PlayerId, out var ltg) && ltg == killer.PlayerId) || - (targetState.Role is Totocalcio tc && tc.BetPlayer == killer.PlayerId)) - Achievements.Type.WhatHaveIDone.CompleteAfterGameEnd(); - - if (targetState.MainRole == CustomRoles.Snitch && Snitch.IsExposed.TryGetValue(target.PlayerId, out bool exposed) && exposed) - Achievements.Type.ThatWasClose.CompleteAfterGameEnd(); - - switch (killerState.MainRole) - { - case CustomRoles.Traitor when targetState.MainRole == CustomRoles.Refugee: - Achievements.Type.TheRealTraitor.CompleteAfterGameEnd(); - break; - case CustomRoles.Bargainer when targetState.MainRole == CustomRoles.Merchant: - case CustomRoles.Merchant when targetState.MainRole == CustomRoles.Bargainer: - Achievements.Type.EconomicCompetition.CompleteAfterGameEnd(); - break; - case CustomRoles.Crusader when targetState.MainRole is CustomRoles.Witch or CustomRoles.HexMaster or CustomRoles.Vampire or CustomRoles.Warlock: - Achievements.Type.Inquisition.CompleteAfterGameEnd(); - break; - } - - if (killerState.MainRole == targetState.MainRole) - Achievements.Type.ThereCanOnlyBeOne.CompleteAfterGameEnd(); - } - - if (target.IsLocalPlayer()) - { - PlayerState targetState = Main.PlayerStates[target.PlayerId]; - - if (targetState.MainRole is CustomRoles.Maverick or CustomRoles.Opportunist) - Achievements.Type.YouDidTheExactOppositeOfWhatYouWereSupposedToDo.Complete(); - } - } - catch (Exception e) - { - Utils.ThrowException(e); + Achievements.Type.ItsMorbinTime.Complete(); + if (shapeshifter.Is(CustomRoles.Disco)) Achievements.Type.Prankster.Complete(); } } - - public static void OnShapeshift(PlayerControl shapeshifter, bool shapeshifting, bool animated) + catch (Exception e) { - try - { - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || Main.AllPlayerControls.Length <= MinPlayers) return; - - if (shapeshifter.IsLocalPlayer() && shapeshifting && animated) - { - Achievements.Type.ItsMorbinTime.Complete(); - if (shapeshifter.Is(CustomRoles.Disco)) Achievements.Type.Prankster.Complete(); - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } + Utils.ThrowException(e); } } } \ No newline at end of file diff --git a/Modules/TargetArrow.cs b/Modules/TargetArrow.cs index 7eba0945f..22d8f24b7 100644 --- a/Modules/TargetArrow.cs +++ b/Modules/TargetArrow.cs @@ -4,185 +4,184 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR +namespace EHR; + +internal static class TargetArrow { - internal static class TargetArrow + private static readonly Dictionary TargetArrows = []; + + private static readonly string[] Arrows = + [ + "↑", + "↗", + "→", + "↘", + "↓", + "↙", + "←", + "↖", + "・" + ]; + + public static void Init() { - private static readonly Dictionary TargetArrows = []; + TargetArrows.Clear(); + } - private static readonly string[] Arrows = - [ - "↑", - "↗", - "→", - "↘", - "↓", - "↙", - "←", - "↖", - "・" - ]; - - public static void Init() + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - TargetArrows.Clear(); + case 1: + Add(reader.ReadByte(), reader.ReadByte()); + break; + case 2: + Remove(reader.ReadByte(), reader.ReadByte()); + break; + case 3: + RemoveAllTarget(reader.ReadByte()); + break; } + } - public static void ReceiveRPC(MessageReader reader) + /// + /// Register a new target arrow object + /// + /// + /// + public static void Add(byte seer, byte target) + { + ArrowInfo arrowInfo = new(seer, target); + + if (!TargetArrows.Any(a => a.Key.Equals(arrowInfo))) { - switch (reader.ReadPackedInt32()) + TargetArrows[arrowInfo] = "・"; + Utils.SendRPC(CustomRPC.Arrow, true, 1, seer, target); + Logger.Info($"New target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow"); + } + } + + /// + /// Delete target + /// + /// + /// + public static void Remove(byte seer, byte target) + { + ArrowInfo arrowInfo = new(seer, target); + List removeList = new(TargetArrows.Keys.Where(k => k.Equals(arrowInfo))); + foreach (ArrowInfo a in removeList.ToArray()) TargetArrows.Remove(a); + + Utils.SendRPC(CustomRPC.Arrow, true, 2, seer, target); + Logger.Info($"Removed target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow"); + } + + /// + /// Delete all targets for the specified seer + /// + /// + public static void RemoveAllTarget(byte seer) + { + List removeList = new(TargetArrows.Keys.Where(k => k.From == seer)); + foreach (ArrowInfo arrowInfo in removeList.ToArray()) TargetArrows.Remove(arrowInfo); + + Utils.SendRPC(CustomRPC.Arrow, true, 3, seer); + Logger.Info($"Removed all target arrows for {seer} ({seer.GetPlayer()?.GetRealName()})", "TargetArrow"); + } + + /// + /// Get all visible target arrows for the specified seer to the specified target(s) + /// + /// + /// + /// + public static string GetArrows(PlayerControl seer, params byte[] targets) + { + return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId && targets.Contains(ai.To)).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]); + } + + /// + /// Get all visible target arrows for the specified seer + /// + /// + /// + public static string GetAllArrows(PlayerControl seer) + { + return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]); + } + + /// + /// Check target arrow every FixedUpdate + /// Issue NotifyRoles when there are updates + /// + /// + public static void OnFixedUpdate(PlayerControl seer) + { + if (!GameStates.IsInTask) return; + + bool seerIsDead = !seer.IsAlive(); + + List arrowList = new(TargetArrows.Keys.Where(a => a.From == seer.PlayerId)); + if (arrowList.Count == 0) return; + + var update = false; + + foreach (ArrowInfo arrowInfo in arrowList.ToArray()) + { + byte targetId = arrowInfo.To; + PlayerControl target = Utils.GetPlayerById(targetId); + + if (seerIsDead || (!target.IsAlive() && !seer.Is(CustomRoles.Spiritualist))) { - case 1: - Add(reader.ReadByte(), reader.ReadByte()); - break; - case 2: - Remove(reader.ReadByte(), reader.ReadByte()); - break; - case 3: - RemoveAllTarget(reader.ReadByte()); - break; + TargetArrows.Remove(arrowInfo); + update = true; + continue; + } + + // Take the direction vector of the target + Vector3 dir = target.transform.position - seer.transform.position; + int index; + + if (dir.magnitude < 2) + { + // Display a dot when close + index = 8; + } + else + { + // Convert to index with -22.5 to 22.5 degrees as 0 + // Bottom is 0 degrees, left side is +180, right side is -180 + // Adding 180 degrees clockwise with top being 0 degrees + // Add 45/2 to make index in 45 degree units + double angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5; + index = (int)(angle / 45) % 8; + } + + string arrow = Arrows[index]; + + if (TargetArrows[arrowInfo] != arrow) + { + TargetArrows[arrowInfo] = arrow; + update = true; } } - /// - /// Register a new target arrow object - /// - /// - /// - public static void Add(byte seer, byte target) - { - ArrowInfo arrowInfo = new(seer, target); + if (update) Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false, SpecifyTarget: seer); + } - if (!TargetArrows.Any(a => a.Key.Equals(arrowInfo))) - { - TargetArrows[arrowInfo] = "・"; - Utils.SendRPC(CustomRPC.Arrow, true, 1, seer, target); - Logger.Info($"New target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow"); - } + private class ArrowInfo(byte from, byte to) + { + public readonly byte From = from; + public readonly byte To = to; + + public bool Equals(ArrowInfo obj) + { + return From == obj.From && To == obj.To; } - /// - /// Delete target - /// - /// - /// - public static void Remove(byte seer, byte target) + public override string ToString() { - ArrowInfo arrowInfo = new(seer, target); - List removeList = new(TargetArrows.Keys.Where(k => k.Equals(arrowInfo))); - foreach (ArrowInfo a in removeList.ToArray()) TargetArrows.Remove(a); - - Utils.SendRPC(CustomRPC.Arrow, true, 2, seer, target); - Logger.Info($"Removed target arrow: {seer} ({seer.GetPlayer()?.GetRealName()}) => {target} ({target.GetPlayer()?.GetRealName()})", "TargetArrow"); - } - - /// - /// Delete all targets for the specified seer - /// - /// - public static void RemoveAllTarget(byte seer) - { - List removeList = new(TargetArrows.Keys.Where(k => k.From == seer)); - foreach (ArrowInfo arrowInfo in removeList.ToArray()) TargetArrows.Remove(arrowInfo); - - Utils.SendRPC(CustomRPC.Arrow, true, 3, seer); - Logger.Info($"Removed all target arrows for {seer} ({seer.GetPlayer()?.GetRealName()})", "TargetArrow"); - } - - /// - /// Get all visible target arrows for the specified seer to the specified target(s) - /// - /// - /// - /// - public static string GetArrows(PlayerControl seer, params byte[] targets) - { - return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId && targets.Contains(ai.To)).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]); - } - - /// - /// Get all visible target arrows for the specified seer - /// - /// - /// - public static string GetAllArrows(PlayerControl seer) - { - return TargetArrows.Keys.Where(ai => ai.From == seer.PlayerId).Aggregate(string.Empty, (current, arrowInfo) => current + TargetArrows[arrowInfo]); - } - - /// - /// Check target arrow every FixedUpdate - /// Issue NotifyRoles when there are updates - /// - /// - public static void OnFixedUpdate(PlayerControl seer) - { - if (!GameStates.IsInTask) return; - - bool seerIsDead = !seer.IsAlive(); - - List arrowList = new(TargetArrows.Keys.Where(a => a.From == seer.PlayerId)); - if (arrowList.Count == 0) return; - - var update = false; - - foreach (ArrowInfo arrowInfo in arrowList.ToArray()) - { - byte targetId = arrowInfo.To; - PlayerControl target = Utils.GetPlayerById(targetId); - - if (seerIsDead || (!target.IsAlive() && !seer.Is(CustomRoles.Spiritualist))) - { - TargetArrows.Remove(arrowInfo); - update = true; - continue; - } - - // Take the direction vector of the target - Vector3 dir = target.transform.position - seer.transform.position; - int index; - - if (dir.magnitude < 2) - { - // Display a dot when close - index = 8; - } - else - { - // Convert to index with -22.5 to 22.5 degrees as 0 - // Bottom is 0 degrees, left side is +180, right side is -180 - // Adding 180 degrees clockwise with top being 0 degrees - // Add 45/2 to make index in 45 degree units - double angle = Vector3.SignedAngle(Vector3.down, dir, Vector3.back) + 180 + 22.5; - index = (int)(angle / 45) % 8; - } - - string arrow = Arrows[index]; - - if (TargetArrows[arrowInfo] != arrow) - { - TargetArrows[arrowInfo] = arrow; - update = true; - } - } - - if (update) Utils.NotifyRoles(SpecifySeer: seer, ForceLoop: false, SpecifyTarget: seer); - } - - private class ArrowInfo(byte from, byte to) - { - public readonly byte From = from; - public readonly byte To = to; - - public bool Equals(ArrowInfo obj) - { - return From == obj.From && To == obj.To; - } - - public override string ToString() - { - return $"(From:{From} To:{To})"; - } + return $"(From:{From} To:{To})"; } } } \ No newline at end of file diff --git a/Modules/TemplateManager.cs b/Modules/TemplateManager.cs index 76baf3a49..27416a930 100644 --- a/Modules/TemplateManager.cs +++ b/Modules/TemplateManager.cs @@ -12,128 +12,127 @@ using InnerNet; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +public static class TemplateManager { - public static class TemplateManager + private const string TemplateFilePath = "./EHR_DATA/template.txt"; + + private static readonly Dictionary> ReplaceDictionary = new() { - private const string TemplateFilePath = "./EHR_DATA/template.txt"; + ["RoomCode"] = () => GameCode.IntToGameName(AmongUsClient.Instance.GameId), + ["PlayerName"] = () => DataManager.Player.Customization.Name, + ["AmongUsVersion"] = () => Application.version, + ["InternalVersion"] = () => Main.PluginVersion, + ["ModVersion"] = () => Main.PluginDisplayVersion, + ["Map"] = () => Constants.MapNames[Main.NormalOptions.MapId], + ["NumEmergencyMeetings"] = () => Main.NormalOptions.NumEmergencyMeetings.ToString(), + ["EmergencyCooldown"] = () => Main.NormalOptions.EmergencyCooldown.ToString(), + ["DiscussionTime"] = () => Main.NormalOptions.DiscussionTime.ToString(), + ["VotingTime"] = () => Main.NormalOptions.VotingTime.ToString(), + ["PlayerSpeedMod"] = () => Main.NormalOptions.PlayerSpeedMod.ToString(), + ["CrewLightMod"] = () => Main.NormalOptions.CrewLightMod.ToString(), + ["ImpostorLightMod"] = () => Main.NormalOptions.ImpostorLightMod.ToString(), + ["KillCooldown"] = () => Main.NormalOptions.KillCooldown.ToString(), + ["NumCommonTasks"] = () => Main.NormalOptions.NumCommonTasks.ToString(), + ["NumLongTasks"] = () => Main.NormalOptions.NumLongTasks.ToString(), + ["NumShortTasks"] = () => Main.NormalOptions.NumShortTasks.ToString(), + ["Date"] = () => DateTime.Now.ToShortDateString(), + ["Time"] = () => DateTime.Now.ToShortTimeString() + }; - private static readonly Dictionary> ReplaceDictionary = new() - { - ["RoomCode"] = () => GameCode.IntToGameName(AmongUsClient.Instance.GameId), - ["PlayerName"] = () => DataManager.Player.Customization.Name, - ["AmongUsVersion"] = () => Application.version, - ["InternalVersion"] = () => Main.PluginVersion, - ["ModVersion"] = () => Main.PluginDisplayVersion, - ["Map"] = () => Constants.MapNames[Main.NormalOptions.MapId], - ["NumEmergencyMeetings"] = () => Main.NormalOptions.NumEmergencyMeetings.ToString(), - ["EmergencyCooldown"] = () => Main.NormalOptions.EmergencyCooldown.ToString(), - ["DiscussionTime"] = () => Main.NormalOptions.DiscussionTime.ToString(), - ["VotingTime"] = () => Main.NormalOptions.VotingTime.ToString(), - ["PlayerSpeedMod"] = () => Main.NormalOptions.PlayerSpeedMod.ToString(), - ["CrewLightMod"] = () => Main.NormalOptions.CrewLightMod.ToString(), - ["ImpostorLightMod"] = () => Main.NormalOptions.ImpostorLightMod.ToString(), - ["KillCooldown"] = () => Main.NormalOptions.KillCooldown.ToString(), - ["NumCommonTasks"] = () => Main.NormalOptions.NumCommonTasks.ToString(), - ["NumLongTasks"] = () => Main.NormalOptions.NumLongTasks.ToString(), - ["NumShortTasks"] = () => Main.NormalOptions.NumShortTasks.ToString(), - ["Date"] = () => DateTime.Now.ToShortDateString(), - ["Time"] = () => DateTime.Now.ToShortTimeString() - }; + public static void Init() + { + CreateIfNotExists(); + } - public static void Init() + public static void CreateIfNotExists() + { + if (!File.Exists(TemplateFilePath)) { - CreateIfNotExists(); - } - - public static void CreateIfNotExists() - { - if (!File.Exists(TemplateFilePath)) + try { - try - { - if (!Directory.Exists("EHR_DATA")) Directory.CreateDirectory("EHR_DATA"); + if (!Directory.Exists("EHR_DATA")) Directory.CreateDirectory("EHR_DATA"); - if (File.Exists("./template.txt")) - File.Move("./template.txt", TemplateFilePath); - else - { - string fileName; - string[] name = CultureInfo.CurrentCulture.Name.Split("-"); - - if (name.Length >= 2) - { - fileName = name[0] switch - { - "zh" => "SChinese", - "ru" => "Russian", - _ => "English" - }; - } - else - fileName = "English"; - - Logger.Warn($"创建新的 Template 文件:{fileName}", "TemplateManager"); - File.WriteAllText(TemplateFilePath, GetResourcesTxt($"EHR.Resources.Config.template.{fileName}.txt")); - } - } - catch (Exception ex) - { - Logger.Exception(ex, "TemplateManager"); - } - } - else - { - string text = File.ReadAllText(TemplateFilePath, Encoding.GetEncoding("UTF-8")); - File.WriteAllText(TemplateFilePath, text.Replace("5PNwUaN5", "hkk2p9ggv4")); - } - } - - private static string GetResourcesTxt(string path) - { - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); - if (stream == null) return string.Empty; - - stream.Position = 0; - using StreamReader reader = new(stream, Encoding.UTF8); - return reader.ReadToEnd(); - } - - public static void SendTemplate(string str = "", byte playerId = 0xff, bool noErr = false) - { - CreateIfNotExists(); - using StreamReader sr = new(TemplateFilePath, Encoding.GetEncoding("UTF-8")); - List sendList = []; - HashSet tags = []; - - while (sr.ReadLine() is { } text) - { - string[] tmp = text.Split(":"); - - if (tmp.Length > 1 && tmp[1] != "") - { - tags.Add(tmp[0]); - if (string.Equals(tmp[0], str, StringComparison.CurrentCultureIgnoreCase)) sendList.Add(tmp.Skip(1).Join(delimiter: ":").Replace("\\n", "\n")); - } - } - - if (sendList.Count == 0 && !noErr) - { - if (playerId == 0xff) - HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, string.Format(GetString("Message.TemplateNotFoundHost"), str, tags.Join(delimiter: ", "))); + if (File.Exists("./template.txt")) + File.Move("./template.txt", TemplateFilePath); else - Utils.SendMessage(string.Format(GetString("Message.TemplateNotFoundClient"), str), playerId); + { + string fileName; + string[] name = CultureInfo.CurrentCulture.Name.Split("-"); + + if (name.Length >= 2) + { + fileName = name[0] switch + { + "zh" => "SChinese", + "ru" => "Russian", + _ => "English" + }; + } + else + fileName = "English"; + + Logger.Warn($"创建新的 Template 文件:{fileName}", "TemplateManager"); + File.WriteAllText(TemplateFilePath, GetResourcesTxt($"EHR.Resources.Config.template.{fileName}.txt")); + } + } + catch (Exception ex) + { + Logger.Exception(ex, "TemplateManager"); } - else - foreach (string x in sendList) - Utils.SendMessage(ApplyReplaceDictionary(x), playerId); } - - private static string ApplyReplaceDictionary(string text) + else { - foreach (KeyValuePair> kvp in ReplaceDictionary) text = Regex.Replace(text, "{{" + kvp.Key + "}}", kvp.Value.Invoke() ?? "", RegexOptions.IgnoreCase); - - return text; + string text = File.ReadAllText(TemplateFilePath, Encoding.GetEncoding("UTF-8")); + File.WriteAllText(TemplateFilePath, text.Replace("5PNwUaN5", "hkk2p9ggv4")); } } + + private static string GetResourcesTxt(string path) + { + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + if (stream == null) return string.Empty; + + stream.Position = 0; + using StreamReader reader = new(stream, Encoding.UTF8); + return reader.ReadToEnd(); + } + + public static void SendTemplate(string str = "", byte playerId = 0xff, bool noErr = false) + { + CreateIfNotExists(); + using StreamReader sr = new(TemplateFilePath, Encoding.GetEncoding("UTF-8")); + List sendList = []; + HashSet tags = []; + + while (sr.ReadLine() is { } text) + { + string[] tmp = text.Split(":"); + + if (tmp.Length > 1 && tmp[1] != "") + { + tags.Add(tmp[0]); + if (string.Equals(tmp[0], str, StringComparison.CurrentCultureIgnoreCase)) sendList.Add(tmp.Skip(1).Join(delimiter: ":").Replace("\\n", "\n")); + } + } + + if (sendList.Count == 0 && !noErr) + { + if (playerId == 0xff) + HudManager.Instance.Chat.AddChat(PlayerControl.LocalPlayer, string.Format(GetString("Message.TemplateNotFoundHost"), str, tags.Join(delimiter: ", "))); + else + Utils.SendMessage(string.Format(GetString("Message.TemplateNotFoundClient"), str), playerId); + } + else + foreach (string x in sendList) + Utils.SendMessage(ApplyReplaceDictionary(x), playerId); + } + + private static string ApplyReplaceDictionary(string text) + { + foreach (KeyValuePair> kvp in ReplaceDictionary) text = Regex.Replace(text, "{{" + kvp.Key + "}}", kvp.Value.Invoke() ?? "", RegexOptions.IgnoreCase); + + return text; + } } \ No newline at end of file diff --git a/Modules/Translator.cs b/Modules/Translator.cs index cf0984326..238d1cee3 100644 --- a/Modules/Translator.cs +++ b/Modules/Translator.cs @@ -10,303 +10,302 @@ using System.Text.RegularExpressions; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; -namespace EHR +namespace EHR; + +public static class Translator { - public static class Translator + public const string LANGUAGE_FOLDER_NAME = "Language"; + public static Dictionary> translateMaps; + + public static void Init() { - public const string LANGUAGE_FOLDER_NAME = "Language"; - public static Dictionary> translateMaps; + Logger.Info("Loading Custom Translations...", "Translator"); + LoadLangs(); + Logger.Info("Loaded Custom Translations", "Translator"); + } - public static void Init() + public static void LoadLangs() + { + try { - Logger.Info("Loading Custom Translations...", "Translator"); - LoadLangs(); - Logger.Info("Loaded Custom Translations", "Translator"); - } + // Get the directory containing the JSON files (e.g., EHR.Resources.Lang) + var jsonDirectory = "EHR.Resources.Lang"; + // Get the assembly containing the resources + var assembly = Assembly.GetExecutingAssembly(); + string[] jsonFileNames = GetJsonFileNames(assembly, jsonDirectory); - public static void LoadLangs() - { - try + translateMaps = []; + + if (jsonFileNames.Length == 0) { - // Get the directory containing the JSON files (e.g., EHR.Resources.Lang) - var jsonDirectory = "EHR.Resources.Lang"; - // Get the assembly containing the resources - var assembly = Assembly.GetExecutingAssembly(); - string[] jsonFileNames = GetJsonFileNames(assembly, jsonDirectory); + Logger.Warn("Json Translation files does not exist.", "Translator"); + return; + } - translateMaps = []; + foreach (string jsonFileName in jsonFileNames) + { + // Read the JSON file content + using Stream resourceStream = assembly.GetManifestResourceStream(jsonFileName); - if (jsonFileNames.Length == 0) + if (resourceStream != null) { - Logger.Warn("Json Translation files does not exist.", "Translator"); - return; - } + using StreamReader reader = new(resourceStream); + string jsonContent = reader.ReadToEnd(); - foreach (string jsonFileName in jsonFileNames) - { - // Read the JSON file content - using Stream resourceStream = assembly.GetManifestResourceStream(jsonFileName); + // Deserialize the JSON into a dictionary + var jsonDictionary = JsonSerializer.Deserialize>(jsonContent); - if (resourceStream != null) + if (jsonDictionary.TryGetValue("LanguageID", out string languageIdObj) && int.TryParse(languageIdObj, out int languageId)) { - using StreamReader reader = new(resourceStream); - string jsonContent = reader.ReadToEnd(); + // Remove the "LanguageID" entry + jsonDictionary.Remove("LanguageID"); - // Deserialize the JSON into a dictionary - var jsonDictionary = JsonSerializer.Deserialize>(jsonContent); - - if (jsonDictionary.TryGetValue("LanguageID", out string languageIdObj) && int.TryParse(languageIdObj, out int languageId)) - { - // Remove the "LanguageID" entry - jsonDictionary.Remove("LanguageID"); - - // Handle the rest of the data and merge it into the resulting translation map - MergeJsonIntoTranslationMap(translateMaps, languageId, jsonDictionary); - } - else - { - //Logger.Warn(jsonDictionary["HostText"], "Translator"); - Logger.Warn($"Invalid JSON format in {jsonFileName}: Missing or invalid 'LanguageID' field.", "Translator"); - } + // Handle the rest of the data and merge it into the resulting translation map + MergeJsonIntoTranslationMap(translateMaps, languageId, jsonDictionary); + } + else + { + //Logger.Warn(jsonDictionary["HostText"], "Translator"); + Logger.Warn($"Invalid JSON format in {jsonFileName}: Missing or invalid 'LanguageID' field.", "Translator"); } } - - // Convert the resulting translation map to JSON - JsonSerializer.Serialize(translateMaps, new JsonSerializerOptions - { - WriteIndented = true - }); } - catch (Exception ex) + + // Convert the resulting translation map to JSON + JsonSerializer.Serialize(translateMaps, new JsonSerializerOptions { - Logger.Error($"Error: {ex}", "Translator"); - } + WriteIndented = true + }); + } + catch (Exception ex) + { + Logger.Error($"Error: {ex}", "Translator"); + } - // Loading custom translation files - if (!Directory.Exists(LANGUAGE_FOLDER_NAME)) Directory.CreateDirectory(LANGUAGE_FOLDER_NAME); + // Loading custom translation files + if (!Directory.Exists(LANGUAGE_FOLDER_NAME)) Directory.CreateDirectory(LANGUAGE_FOLDER_NAME); - // Creating a translation template - CreateTemplateFile(); + // Creating a translation template + CreateTemplateFile(); - foreach (SupportedLangs lang in Enum.GetValues()) + foreach (SupportedLangs lang in Enum.GetValues()) + { + if (File.Exists(@$"./{LANGUAGE_FOLDER_NAME}/{lang}.dat")) { - if (File.Exists(@$"./{LANGUAGE_FOLDER_NAME}/{lang}.dat")) - { - UpdateCustomTranslation($"{lang}.dat" /*, lang*/); - LoadCustomTranslation($"{lang}.dat", lang); - } + UpdateCustomTranslation($"{lang}.dat" /*, lang*/); + LoadCustomTranslation($"{lang}.dat", lang); } } + } - private static void MergeJsonIntoTranslationMap(Dictionary> translationMaps, int languageId, Dictionary jsonDictionary) + private static void MergeJsonIntoTranslationMap(Dictionary> translationMaps, int languageId, Dictionary jsonDictionary) + { + foreach (KeyValuePair kvp in jsonDictionary) { - foreach (KeyValuePair kvp in jsonDictionary) + string textString = kvp.Key; + + if (kvp.Value is string translation) { - string textString = kvp.Key; - - if (kvp.Value is string translation) - { - // If the textString is not already in the translation map, add it - if (!translationMaps.ContainsKey(textString)) translationMaps[textString] = []; + // If the textString is not already in the translation map, add it + if (!translationMaps.ContainsKey(textString)) translationMaps[textString] = []; - // Add or update the translation for the current id and textString - translationMaps[textString][languageId] = translation.Replace("\\n", "\n").Replace("\\r", "\r"); - } + // Add or update the translation for the current id and textString + translationMaps[textString][languageId] = translation.Replace("\\n", "\n").Replace("\\r", "\r"); } } + } - // Function to get a list of JSON file names in a directory - private static string[] GetJsonFileNames(Assembly assembly, string directoryName) - { - string[] resourceNames = assembly.GetManifestResourceNames(); - return resourceNames.Where(resourceName => resourceName.StartsWith(directoryName) && resourceName.EndsWith(".json")).ToArray(); - } + // Function to get a list of JSON file names in a directory + private static string[] GetJsonFileNames(Assembly assembly, string directoryName) + { + string[] resourceNames = assembly.GetManifestResourceNames(); + return resourceNames.Where(resourceName => resourceName.StartsWith(directoryName) && resourceName.EndsWith(".json")).ToArray(); + } + + public static string GetString(string s, Dictionary replacementDic = null, bool console = false) + { + SupportedLangs langId = TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.English; + if (console) langId = SupportedLangs.English; - public static string GetString(string s, Dictionary replacementDic = null, bool console = false) - { - SupportedLangs langId = TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.English; - if (console) langId = SupportedLangs.English; + if (Main.ForceOwnLanguage.Value) langId = GetUserTrueLang(); - if (Main.ForceOwnLanguage.Value) langId = GetUserTrueLang(); + string str = GetString(s, langId); - string str = GetString(s, langId); + if (replacementDic != null) + foreach (KeyValuePair rd in replacementDic) + str = str.Replace(rd.Key, rd.Value); - if (replacementDic != null) - foreach (KeyValuePair rd in replacementDic) - str = str.Replace(rd.Key, rd.Value); + return str; + } - return str; - } + public static string GetString(string str, SupportedLangs langId) + { + var res = $""; - public static string GetString(string str, SupportedLangs langId) + try { - var res = $""; + if (translateMaps.TryGetValue(str, out Dictionary dic) && (!dic.TryGetValue((int)langId, out res) || res == "" || (langId is not SupportedLangs.SChinese and not SupportedLangs.TChinese && Regex.IsMatch(res, @"[\u4e00-\u9fa5]") && res == GetString(str, SupportedLangs.SChinese)))) //strに該当する&無効なlangIdかresが空 + res = langId == SupportedLangs.English ? $"*{str}" : GetString(str, SupportedLangs.English); - try + if (!translateMaps.ContainsKey(str)) //translateMapsにない場合、StringNamesにあれば取得する { - if (translateMaps.TryGetValue(str, out Dictionary dic) && (!dic.TryGetValue((int)langId, out res) || res == "" || (langId is not SupportedLangs.SChinese and not SupportedLangs.TChinese && Regex.IsMatch(res, @"[\u4e00-\u9fa5]") && res == GetString(str, SupportedLangs.SChinese)))) //strに該当する&無効なlangIdかresが空 - res = langId == SupportedLangs.English ? $"*{str}" : GetString(str, SupportedLangs.English); - - if (!translateMaps.ContainsKey(str)) //translateMapsにない場合、StringNamesにあれば取得する - { - StringNames[] stringNames = Enum.GetValues().Where(x => x.ToString() == str).ToArray(); - if (stringNames.Length > 0) res = GetString(stringNames.FirstOrDefault()); - } + StringNames[] stringNames = Enum.GetValues().Where(x => x.ToString() == str).ToArray(); + if (stringNames.Length > 0) res = GetString(stringNames.FirstOrDefault()); } - catch (Exception Ex) - { - Logger.Fatal($"Error oucured at [{str}] in String.csv", "Translator"); - Logger.Error("Here was the error:\n" + Ex, "Translator"); - } - - return res; } - - public static string GetString(StringNames stringName) + catch (Exception Ex) { - return DestroyableSingleton.Instance.GetString(stringName, new Il2CppReferenceArray(0)); + Logger.Fatal($"Error oucured at [{str}] in String.csv", "Translator"); + Logger.Error("Here was the error:\n" + Ex, "Translator"); } + + return res; + } - public static string GetRoleString(string str, bool forUser = true) - { - SupportedLangs CurrentLanguage = TranslationController.Instance.currentLanguage.languageID; - SupportedLangs lang = forUser ? CurrentLanguage : SupportedLangs.English; - if (Main.ForceOwnLanguageRoleName.Value) lang = GetUserTrueLang(); + public static string GetString(StringNames stringName) + { + return DestroyableSingleton.Instance.GetString(stringName, new Il2CppReferenceArray(0)); + } + + public static string GetRoleString(string str, bool forUser = true) + { + SupportedLangs CurrentLanguage = TranslationController.Instance.currentLanguage.languageID; + SupportedLangs lang = forUser ? CurrentLanguage : SupportedLangs.English; + if (Main.ForceOwnLanguageRoleName.Value) lang = GetUserTrueLang(); - return GetString(str, lang); - } + return GetString(str, lang); + } - public static SupportedLangs GetUserTrueLang() + public static SupportedLangs GetUserTrueLang() + { + try { - try - { - string name = CultureInfo.CurrentUICulture.Name; - if (name.StartsWith("en")) return SupportedLangs.English; + string name = CultureInfo.CurrentUICulture.Name; + if (name.StartsWith("en")) return SupportedLangs.English; - if (name.StartsWith("zh_CHT")) return SupportedLangs.TChinese; + if (name.StartsWith("zh_CHT")) return SupportedLangs.TChinese; - if (name.StartsWith("zh")) return SupportedLangs.SChinese; + if (name.StartsWith("zh")) return SupportedLangs.SChinese; - if (name.StartsWith("ru")) return SupportedLangs.Russian; + if (name.StartsWith("ru")) return SupportedLangs.Russian; - return TranslationController.Instance.currentLanguage.languageID; - } - catch - { - return SupportedLangs.English; - } + return TranslationController.Instance.currentLanguage.languageID; + } + catch + { + return SupportedLangs.English; } + } + + private static void UpdateCustomTranslation(string filename /*, SupportedLangs lang*/) + { + var path = @$"./{LANGUAGE_FOLDER_NAME}/{filename}"; - private static void UpdateCustomTranslation(string filename /*, SupportedLangs lang*/) + if (File.Exists(path)) { - var path = @$"./{LANGUAGE_FOLDER_NAME}/{filename}"; + Logger.Info("Updating Custom Translations", "UpdateCustomTranslation"); - if (File.Exists(path)) + try { - Logger.Info("Updating Custom Translations", "UpdateCustomTranslation"); + List textStrings = []; - try + using (StreamReader reader = new(path, Encoding.GetEncoding("UTF-8"))) { - List textStrings = []; + string line; - using (StreamReader reader = new(path, Encoding.GetEncoding("UTF-8"))) + while ((line = reader.ReadLine()) != null) { - string line; + // Split the line by ':' to get the first part + string[] parts = line.Split(':'); - while ((line = reader.ReadLine()) != null) + // Check if there is at least one part before ':' + if (parts.Length >= 1) { - // Split the line by ':' to get the first part - string[] parts = line.Split(':'); - - // Check if there is at least one part before ':' - if (parts.Length >= 1) - { - // Trim any leading or trailing spaces and add it to the list - string textString = parts[0].Trim(); - textStrings.Add(textString); - } + // Trim any leading or trailing spaces and add it to the list + string textString = parts[0].Trim(); + textStrings.Add(textString); } } + } - var sb = new StringBuilder(); + var sb = new StringBuilder(); - foreach (string templateString in translateMaps.Keys) - if (!textStrings.Contains(templateString)) - sb.Append($"{templateString}:\n"); + foreach (string templateString in translateMaps.Keys) + if (!textStrings.Contains(templateString)) + sb.Append($"{templateString}:\n"); - using FileStream fileStream = new(path, FileMode.Append, FileAccess.Write); - using StreamWriter writer = new(fileStream); - writer.WriteLine(sb.ToString()); - } - catch (Exception e) - { - Logger.Error("An error occurred: " + e.Message, "Translator"); - } + using FileStream fileStream = new(path, FileMode.Append, FileAccess.Write); + using StreamWriter writer = new(fileStream); + writer.WriteLine(sb.ToString()); + } + catch (Exception e) + { + Logger.Error("An error occurred: " + e.Message, "Translator"); } } + } + + public static void LoadCustomTranslation(string filename, SupportedLangs lang) + { + var path = @$"./{LANGUAGE_FOLDER_NAME}/{filename}"; - public static void LoadCustomTranslation(string filename, SupportedLangs lang) + if (File.Exists(path)) { - var path = @$"./{LANGUAGE_FOLDER_NAME}/{filename}"; + Logger.Info($"Loading Custom Translation File:{filename}", "LoadCustomTranslation"); - if (File.Exists(path)) + try { - Logger.Info($"Loading Custom Translation File:{filename}", "LoadCustomTranslation"); + using StreamReader sr = new(path, Encoding.GetEncoding("UTF-8")); + string text; + string[] tmp = []; - try + while ((text = sr.ReadLine()) != null) { - using StreamReader sr = new(path, Encoding.GetEncoding("UTF-8")); - string text; - string[] tmp = []; + tmp = text.Split(":"); - while ((text = sr.ReadLine()) != null) + if (tmp.Length > 1 && tmp[1] != "") { - tmp = text.Split(":"); - - if (tmp.Length > 1 && tmp[1] != "") + try + { + translateMaps[tmp[0]][(int)lang] = tmp.Skip(1).Join(delimiter: ":").Replace("\\n", "\n").Replace("\\r", "\r"); + } + catch (KeyNotFoundException) { - try - { - translateMaps[tmp[0]][(int)lang] = tmp.Skip(1).Join(delimiter: ":").Replace("\\n", "\n").Replace("\\r", "\r"); - } - catch (KeyNotFoundException) - { - Logger.Warn($"Invalid Key:{tmp[0]}", "LoadCustomTranslation"); - } + Logger.Warn($"Invalid Key:{tmp[0]}", "LoadCustomTranslation"); } } } - catch (ObjectDisposedException) { } - catch (Exception e) - { - Logger.Error(e.ToString(), "Translator.LoadCustomTranslation"); - } + } + catch (ObjectDisposedException) { } + catch (Exception e) + { + Logger.Error(e.ToString(), "Translator.LoadCustomTranslation"); } - else - Logger.Error($"Custom Translation File Not Found:{filename}", "LoadCustomTranslation"); } + else + Logger.Error($"Custom Translation File Not Found:{filename}", "LoadCustomTranslation"); + } + + private static void CreateTemplateFile() + { + var sb = new StringBuilder(); + foreach (KeyValuePair> title in translateMaps) sb.Append($"{title.Key}:\n"); - private static void CreateTemplateFile() - { - var sb = new StringBuilder(); - foreach (KeyValuePair> title in translateMaps) sb.Append($"{title.Key}:\n"); + File.WriteAllText(@$"./{LANGUAGE_FOLDER_NAME}/template.dat", sb.ToString()); + } - File.WriteAllText(@$"./{LANGUAGE_FOLDER_NAME}/template.dat", sb.ToString()); - } + public static void ExportCustomTranslation() + { + LoadLangs(); + var sb = new StringBuilder(); + SupportedLangs lang = TranslationController.Instance.currentLanguage.languageID; - public static void ExportCustomTranslation() + foreach (KeyValuePair> title in translateMaps) { - LoadLangs(); - var sb = new StringBuilder(); - SupportedLangs lang = TranslationController.Instance.currentLanguage.languageID; - - foreach (KeyValuePair> title in translateMaps) - { - string text = title.Value.GetValueOrDefault((int)lang, ""); - sb.Append($"{title.Key}:{text.Replace("\n", "\\n").Replace("\r", "\\r")}\n"); - } + string text = title.Value.GetValueOrDefault((int)lang, ""); + sb.Append($"{title.Key}:{text.Replace("\n", "\\n").Replace("\r", "\\r")}\n"); + } - File.WriteAllText(@$"./{LANGUAGE_FOLDER_NAME}/export_{lang}.dat", sb.ToString()); - } + File.WriteAllText(@$"./{LANGUAGE_FOLDER_NAME}/export_{lang}.dat", sb.ToString()); } } \ No newline at end of file diff --git a/Modules/Utils.cs b/Modules/Utils.cs index 5f76aa521..ad3bdf0de 100644 --- a/Modules/Utils.cs +++ b/Modules/Utils.cs @@ -28,9 +28,8 @@ using Newtonsoft.Json; using UnityEngine; using static EHR.Translator; -namespace EHR -{ - /* +namespace EHR; +/* List of symbols that work in game 1-stars: ★ ☆ ⁂ ⁑ ✽ @@ -41,1100 +40,3082 @@ List of symbols that work in game 6- random: ‰ § ¶ © ™ ¥ $ ¢ € ƒ £ Æ other: ∟ ⌠ ⌡ ╬ ╨ ▓ ▒ ░ « » █ ▄ ▌▀▐│ ┤ ╡ ╢ ╖ ╕ ╣ ║ ╗ ╝ ╜ ╛ ┐ └ ┴ ┬ ─ ┼ ╞ ╟ ╚ ╔ ╩ ╦ ╠ ═ ╬ ╧ ╨ ╤ ╥ ╙ ╘ ╒ ╓ ╫ ╪ ┘ ┌ Θ ∩ ¿ - */ +*/ - public static class Utils +public static class Utils +{ + public const string EmptyMessage = "."; + private static readonly DateTime TimeStampStartTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private static readonly StringBuilder SelfSuffix = new(); + private static readonly StringBuilder SelfMark = new(20); + private static readonly StringBuilder TargetSuffix = new(); + private static readonly StringBuilder TargetMark = new(20); + + private static readonly Dictionary CachedSprites = []; + + private static long LastNotifyRolesErrorTS = TimeStamp; + + public static long GameStartTimeStamp; + + public static readonly Dictionary LongRoleDescriptions = []; + public static long TimeStamp => (long)(DateTime.Now.ToUniversalTime() - TimeStampStartTime).TotalSeconds; + public static bool DoRPC => AmongUsClient.Instance.AmHost && Main.AllPlayerControls.Any(x => x.IsModClient() && !x.IsHost()); + public static int TotalTaskCount => Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks) + Main.RealOptionsData.GetInt(Int32OptionNames.NumLongTasks) + Main.RealOptionsData.GetInt(Int32OptionNames.NumShortTasks); + private static int AllPlayersCount => Main.PlayerStates.Values.Count(state => state.countTypes != CountTypes.OutOfGame); + public static int AllAlivePlayersCount => Main.AllAlivePlayerControls.Count(pc => !pc.Is(CountTypes.OutOfGame)); + public static bool IsAllAlive => Main.PlayerStates.Values.All(state => state.countTypes == CountTypes.OutOfGame || !state.IsDead); + + public static long GetTimeStamp(DateTime? dateTime = null) { - public const string EmptyMessage = "."; - private static readonly DateTime TimeStampStartTime = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return (long)((dateTime ?? DateTime.Now).ToUniversalTime() - TimeStampStartTime).TotalSeconds; + } - private static readonly StringBuilder SelfSuffix = new(); - private static readonly StringBuilder SelfMark = new(20); - private static readonly StringBuilder TargetSuffix = new(); - private static readonly StringBuilder TargetMark = new(20); - - private static readonly Dictionary CachedSprites = []; - - private static long LastNotifyRolesErrorTS = TimeStamp; - - public static long GameStartTimeStamp; - - public static readonly Dictionary LongRoleDescriptions = []; - public static long TimeStamp => (long)(DateTime.Now.ToUniversalTime() - TimeStampStartTime).TotalSeconds; - public static bool DoRPC => AmongUsClient.Instance.AmHost && Main.AllPlayerControls.Any(x => x.IsModClient() && !x.IsHost()); - public static int TotalTaskCount => Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks) + Main.RealOptionsData.GetInt(Int32OptionNames.NumLongTasks) + Main.RealOptionsData.GetInt(Int32OptionNames.NumShortTasks); - private static int AllPlayersCount => Main.PlayerStates.Values.Count(state => state.countTypes != CountTypes.OutOfGame); - public static int AllAlivePlayersCount => Main.AllAlivePlayerControls.Count(pc => !pc.Is(CountTypes.OutOfGame)); - public static bool IsAllAlive => Main.PlayerStates.Values.All(state => state.countTypes == CountTypes.OutOfGame || !state.IsDead); - - public static long GetTimeStamp(DateTime? dateTime = null) + public static void ErrorEnd(string text) + { + if (AmongUsClient.Instance.AmHost) { - return (long)((dateTime ?? DateTime.Now).ToUniversalTime() - TimeStampStartTime).TotalSeconds; - } + Logger.Fatal($"{text} error, triggering anti-black screen measures", "Anti-Blackout"); + ChatUpdatePatch.DoBlockChat = true; + Main.OverrideWelcomeMsg = GetString("AntiBlackOutNotifyInLobby"); + LateTask.New(() => { Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame") /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); - public static void ErrorEnd(string text) - { - if (AmongUsClient.Instance.AmHost) + LateTask.New(() => { - Logger.Fatal($"{text} error, triggering anti-black screen measures", "Anti-Blackout"); - ChatUpdatePatch.DoBlockChat = true; - Main.OverrideWelcomeMsg = GetString("AntiBlackOutNotifyInLobby"); - LateTask.New(() => { Logger.SendInGame(GetString("AntiBlackOutLoggerSendInGame") /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + RPC.ForceEndGame(CustomWinner.Error); + }, 5.5f, "Anti-Black End Game"); + + LateTask.New(() => ChatUpdatePatch.DoBlockChat = false, 6f, log: false); + } + else + { + MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AntiBlackout); + writer.Write(text); + writer.EndMessage(); + + if (Options.EndWhenPlayerBug.GetBool()) + LateTask.New(() => { Logger.SendInGame(GetString("AntiBlackOutRequestHostToForceEnd") /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); + else + { + LateTask.New(() => { Logger.SendInGame(GetString("AntiBlackOutHostRejectForceEnd") /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); LateTask.New(() => { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Error); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - RPC.ForceEndGame(CustomWinner.Error); - }, 5.5f, "Anti-Black End Game"); - - LateTask.New(() => ChatUpdatePatch.DoBlockChat = false, 6f, log: false); + AmongUsClient.Instance.ExitGame(DisconnectReasons.Custom); + Logger.Fatal($"{text} error, disconnected from game", "Anti-black"); + }, 8f, "Anti-Black Exit Game"); } - else + } + } + + public static void CheckAndSetVentInteractions() + { + var shouldPerformVentInteractions = false; + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (VentilationSystemDeterioratePatch.BlockVentInteraction(pc)) { - MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AntiBlackout); - writer.Write(text); - writer.EndMessage(); - - if (Options.EndWhenPlayerBug.GetBool()) - LateTask.New(() => { Logger.SendInGame(GetString("AntiBlackOutRequestHostToForceEnd") /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); - else - { - LateTask.New(() => { Logger.SendInGame(GetString("AntiBlackOutHostRejectForceEnd") /*, true*/); }, 3f, "Anti-Black Msg SendInGame"); - - LateTask.New(() => - { - AmongUsClient.Instance.ExitGame(DisconnectReasons.Custom); - Logger.Fatal($"{text} error, disconnected from game", "Anti-black"); - }, 8f, "Anti-Black Exit Game"); - } + VentilationSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; + shouldPerformVentInteractions = true; } } - public static void CheckAndSetVentInteractions() + if (shouldPerformVentInteractions) SetAllVentInteractions(); + } + + public static void TPAll(Vector2 location, bool log = true) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) TP(pc.NetTransform, location, log); + } + + public static bool TP(CustomNetworkTransform nt, Vector2 location, bool noCheckState = false, bool log = true) + { + PlayerControl pc = nt.myPlayer; + + if (!noCheckState) { - var shouldPerformVentInteractions = false; + if (pc.Is(CustomRoles.AntiTP)) return false; - foreach (PlayerControl pc in Main.AllPlayerControls) + if (pc.inVent || pc.inMovingPlat || pc.onLadder || !pc.IsAlive() || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || pc.MyPhysics.Animations.IsPlayingEnterVentAnimation()) { - if (VentilationSystemDeterioratePatch.BlockVentInteraction(pc)) - { - VentilationSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = pc.GetVentsFromClosest()[0].Id; - shouldPerformVentInteractions = true; - } - } + if (log) Logger.Warn($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is in an un-teleportable state - Teleporting canceled", "TP"); - if (shouldPerformVentInteractions) SetAllVentInteractions(); - } - - public static void TPAll(Vector2 location, bool log = true) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) TP(pc.NetTransform, location, log); - } - - public static bool TP(CustomNetworkTransform nt, Vector2 location, bool noCheckState = false, bool log = true) - { - PlayerControl pc = nt.myPlayer; - - if (!noCheckState) - { - if (pc.Is(CustomRoles.AntiTP)) return false; - - if (pc.inVent || pc.inMovingPlat || pc.onLadder || !pc.IsAlive() || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || pc.MyPhysics.Animations.IsPlayingEnterVentAnimation()) - { - if (log) Logger.Warn($"Target ({pc.GetNameWithRole().RemoveHtmlTags()}) is in an un-teleportable state - Teleporting canceled", "TP"); - - return false; - } - } - - switch (AmongUsClient.Instance.AmHost) - { - case true: - nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328)); - nt.SetDirtyBit(uint.MaxValue); - break; - case false when !nt.AmOwner: - return false; - } - - var newSid = (ushort)(nt.lastSequenceId + 8); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(nt.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable); - NetHelpers.WriteVector2(location, messageWriter); - messageWriter.Write(newSid); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - - if (log) Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} => {location}", "TP"); - - CheckInvalidMovementPatch.LastPosition[pc.PlayerId] = location; - CheckInvalidMovementPatch.ExemptedPlayers.Add(pc.PlayerId); - - return true; - } - - public static bool TPToRandomVent(CustomNetworkTransform nt, bool log = true) - { - Il2CppReferenceArray vents = ShipStatus.Instance.AllVents; - Vent vent = vents.RandomElement(); - - Logger.Info($"{nt.myPlayer.GetNameWithRole().RemoveHtmlTags()} => {vent.transform.position} (vent)", "TP"); - - return TP(nt, new(vent.transform.position.x, vent.transform.position.y + 0.3636f), log); - } - - public static ClientData GetClientById(int id) - { - try - { - ClientData client = AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Id == id); - return client; - } - catch - { - return null; - } - } - - public static bool IsActive(SystemTypes type) - { - try - { - if (GameStates.IsLobby || !ShipStatus.Instance.Systems.TryGetValue(type, out ISystemType systemType)) return false; - - int mapId = Main.NormalOptions.MapId; - - switch (type) - { - case SystemTypes.Electrical: - { - if (mapId == 5) return false; // if The Fungle return false - - var SwitchSystem = systemType.TryCast(); - return SwitchSystem is { IsActive: true }; - } - case SystemTypes.Reactor: - { - switch (mapId) - { - case 2: - return false; // if Polus return false - // Only Airhip - case 4: - { - var HeliSabotageSystem = systemType.TryCast(); - return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; - } - default: - { - var ReactorSystemType = systemType.TryCast(); - return ReactorSystemType is { IsActive: true }; - } - } - } - case SystemTypes.Laboratory: - { - if (mapId != 2) return false; // Only Polus - - var ReactorSystemType = systemType.TryCast(); - return ReactorSystemType is { IsActive: true }; - } - case SystemTypes.LifeSupp: - { - if (mapId is 2 or 4 or 5) return false; // Only Skeld & Mira HQ - - var LifeSuppSystemType = systemType.TryCast(); - return LifeSuppSystemType is { IsActive: true }; - } - case SystemTypes.Comms: - { - if (mapId is 1 or 5) // Only Mira HQ & The Fungle - { - var HqHudSystemType = systemType.TryCast(); - return HqHudSystemType is { IsActive: true }; - } - - var HudOverrideSystemType = systemType.TryCast(); - return HudOverrideSystemType is { IsActive: true }; - } - case SystemTypes.HeliSabotage: - { - if (mapId != 4) return false; // Only Airhip - - var HeliSabotageSystem = systemType.TryCast(); - return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; - } - case SystemTypes.MushroomMixupSabotage: - { - if (mapId != 5) return false; // Only The Fungle - - var MushroomMixupSabotageSystem = systemType.TryCast(); - return MushroomMixupSabotageSystem != null && MushroomMixupSabotageSystem.IsActive; - } - default: - return false; - } - } - catch (Exception e) - { - ThrowException(e); return false; } } - public static void SetVision(this IGameOptions opt, bool HasImpVision) + switch (AmongUsClient.Instance.AmHost) { - if (HasImpVision) - { - opt.SetFloat(FloatOptionNames.CrewLightMod, opt.GetFloat(FloatOptionNames.ImpostorLightMod)); - - if (IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, opt.GetFloat(FloatOptionNames.CrewLightMod) * 5); - - return; - } - - opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.CrewLightMod)); - - if (IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.ImpostorLightMod) / 5); + case true: + nt.SnapTo(location, (ushort)(nt.lastSequenceId + 328)); + nt.SetDirtyBit(uint.MaxValue); + break; + case false when !nt.AmOwner: + return false; } - public static void SetVisionV2(this IGameOptions opt) + var newSid = (ushort)(nt.lastSequenceId + 8); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(nt.NetId, (byte)RpcCalls.SnapTo, SendOption.Reliable); + NetHelpers.WriteVector2(location, messageWriter); + messageWriter.Write(newSid); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + if (log) Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} => {location}", "TP"); + + CheckInvalidMovementPatch.LastPosition[pc.PlayerId] = location; + CheckInvalidMovementPatch.ExemptedPlayers.Add(pc.PlayerId); + + return true; + } + + public static bool TPToRandomVent(CustomNetworkTransform nt, bool log = true) + { + Il2CppReferenceArray vents = ShipStatus.Instance.AllVents; + Vent vent = vents.RandomElement(); + + Logger.Info($"{nt.myPlayer.GetNameWithRole().RemoveHtmlTags()} => {vent.transform.position} (vent)", "TP"); + + return TP(nt, new(vent.transform.position.x, vent.transform.position.y + 0.3636f), log); + } + + public static ClientData GetClientById(int id) + { + try { - opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.CrewLightMod)); - if (IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.ImpostorLightMod) / 5); + ClientData client = AmongUsClient.Instance.allClients.ToArray().FirstOrDefault(cd => cd.Id == id); + return client; } - - public static void TargetDies(PlayerControl killer, PlayerControl target) + catch { - if (!target.Data.IsDead || GameStates.IsMeeting) return; + return null; + } + } - CustomRoles targetRole = target.GetCustomRole(); + public static bool IsActive(SystemTypes type) + { + try + { + if (GameStates.IsLobby || !ShipStatus.Instance.Systems.TryGetValue(type, out ISystemType systemType)) return false; - foreach (PlayerControl seer in Main.AllPlayerControls) + int mapId = Main.NormalOptions.MapId; + + switch (type) { - if (KillFlashCheck(killer, target, seer)) + case SystemTypes.Electrical: { - seer.KillFlash(); - continue; + if (mapId == 5) return false; // if The Fungle return false + + var SwitchSystem = systemType.TryCast(); + return SwitchSystem is { IsActive: true }; } - - if (targetRole == CustomRoles.CyberStar) + case SystemTypes.Reactor: { - if (!Options.ImpKnowCyberStarDead.GetBool() && seer.GetCustomRole().IsImpostor()) continue; - - if (!Options.NeutralKnowCyberStarDead.GetBool() && seer.GetCustomRole().IsNeutral()) continue; - - seer.KillFlash(); - seer.Notify(ColorString(GetRoleColor(CustomRoles.CyberStar), GetString("OnCyberStarDead"))); - } - } - - switch (targetRole) - { - case CustomRoles.CyberStar when !Main.CyberStarDead.Contains(target.PlayerId): - Main.CyberStarDead.Add(target.PlayerId); - break; - case CustomRoles.Demolitionist: - Demolitionist.OnDeath(killer, target); - break; - } - } - - public static bool KillFlashCheck(PlayerControl killer, PlayerControl target, PlayerControl seer) - { - if (seer.Is(CustomRoles.GM) || seer.Is(CustomRoles.Seer)) return true; - - if (seer.Data.IsDead || killer == seer || target == seer) return false; - - return seer.Is(CustomRoles.EvilTracker) && EvilTracker.KillFlashCheck(killer, target); - } - - public static void BlackOut(this IGameOptions opt, bool IsBlackOut) - { - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultImpostorVision); - opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); - - if (IsBlackOut) - { - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0); - opt.SetFloat(FloatOptionNames.CrewLightMod, 0); - } - } - - public static void SaveComboInfo() - { - SaveFile("./EHR_DATA/AlwaysCombos.json"); - SaveFile("./EHR_DATA/NeverCombos.json"); - return; - - void SaveFile(string path) - { - try - { - Il2CppSystem.Collections.Generic.Dictionary>> data = new(); - Dictionary>> dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos; - - dict.Do(kvp => + switch (mapId) { - data[kvp.Key] = new(); - - kvp.Value.Do(pair => + case 2: + return false; // if Polus return false + // Only Airhip + case 4: { - var key = pair.Key.ToString(); - data[kvp.Key][key] = new(); - pair.Value.Do(x => data[kvp.Key][key].Add(x.ToString())); - }); - }); - - File.WriteAllText(path, JsonConvert.SerializeObject(data, Formatting.Indented)); - } - catch (Exception e) - { - Logger.Error("Failed to save combo info", "SaveComboInfo"); - ThrowException(e); - } - } - } - - public static void LoadComboInfo() - { - LoadFile("./EHR_DATA/AlwaysCombos.json"); - LoadFile("./EHR_DATA/NeverCombos.json"); - return; - - void LoadFile(string path) - { - try - { - if (!File.Exists(path)) return; - - var data = JsonConvert.DeserializeObject>>>(File.ReadAllText(path)); - Dictionary>> dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos; - dict.Clear(); - - foreach (Il2CppSystem.Collections.Generic.KeyValuePair>> kvp in data) - { - dict[kvp.Key] = []; - - foreach (Il2CppSystem.Collections.Generic.KeyValuePair> pair in kvp.Value) + var HeliSabotageSystem = systemType.TryCast(); + return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; + } + default: { - var key = Enum.Parse(pair.Key); - dict[kvp.Key][key] = []; - foreach (string n in pair.Value) dict[kvp.Key][key].Add(Enum.Parse(n)); + var ReactorSystemType = systemType.TryCast(); + return ReactorSystemType is { IsActive: true }; } } } - catch (Exception e) + case SystemTypes.Laboratory: { - Logger.Error("Failed to load combo info", "LoadComboInfo"); - ThrowException(e); + if (mapId != 2) return false; // Only Polus + + var ReactorSystemType = systemType.TryCast(); + return ReactorSystemType is { IsActive: true }; + } + case SystemTypes.LifeSupp: + { + if (mapId is 2 or 4 or 5) return false; // Only Skeld & Mira HQ + + var LifeSuppSystemType = systemType.TryCast(); + return LifeSuppSystemType is { IsActive: true }; + } + case SystemTypes.Comms: + { + if (mapId is 1 or 5) // Only Mira HQ & The Fungle + { + var HqHudSystemType = systemType.TryCast(); + return HqHudSystemType is { IsActive: true }; + } + + var HudOverrideSystemType = systemType.TryCast(); + return HudOverrideSystemType is { IsActive: true }; + } + case SystemTypes.HeliSabotage: + { + if (mapId != 4) return false; // Only Airhip + + var HeliSabotageSystem = systemType.TryCast(); + return HeliSabotageSystem != null && HeliSabotageSystem.IsActive; + } + case SystemTypes.MushroomMixupSabotage: + { + if (mapId != 5) return false; // Only The Fungle + + var MushroomMixupSabotageSystem = systemType.TryCast(); + return MushroomMixupSabotageSystem != null && MushroomMixupSabotageSystem.IsActive; + } + default: + return false; + } + } + catch (Exception e) + { + ThrowException(e); + return false; + } + } + + public static void SetVision(this IGameOptions opt, bool HasImpVision) + { + if (HasImpVision) + { + opt.SetFloat(FloatOptionNames.CrewLightMod, opt.GetFloat(FloatOptionNames.ImpostorLightMod)); + + if (IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, opt.GetFloat(FloatOptionNames.CrewLightMod) * 5); + + return; + } + + opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.CrewLightMod)); + + if (IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.ImpostorLightMod) / 5); + } + + public static void SetVisionV2(this IGameOptions opt) + { + opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.CrewLightMod)); + if (IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.ImpostorLightMod, opt.GetFloat(FloatOptionNames.ImpostorLightMod) / 5); + } + + public static void TargetDies(PlayerControl killer, PlayerControl target) + { + if (!target.Data.IsDead || GameStates.IsMeeting) return; + + CustomRoles targetRole = target.GetCustomRole(); + + foreach (PlayerControl seer in Main.AllPlayerControls) + { + if (KillFlashCheck(killer, target, seer)) + { + seer.KillFlash(); + continue; + } + + if (targetRole == CustomRoles.CyberStar) + { + if (!Options.ImpKnowCyberStarDead.GetBool() && seer.GetCustomRole().IsImpostor()) continue; + + if (!Options.NeutralKnowCyberStarDead.GetBool() && seer.GetCustomRole().IsNeutral()) continue; + + seer.KillFlash(); + seer.Notify(ColorString(GetRoleColor(CustomRoles.CyberStar), GetString("OnCyberStarDead"))); + } + } + + switch (targetRole) + { + case CustomRoles.CyberStar when !Main.CyberStarDead.Contains(target.PlayerId): + Main.CyberStarDead.Add(target.PlayerId); + break; + case CustomRoles.Demolitionist: + Demolitionist.OnDeath(killer, target); + break; + } + } + + public static bool KillFlashCheck(PlayerControl killer, PlayerControl target, PlayerControl seer) + { + if (seer.Is(CustomRoles.GM) || seer.Is(CustomRoles.Seer)) return true; + + if (seer.Data.IsDead || killer == seer || target == seer) return false; + + return seer.Is(CustomRoles.EvilTracker) && EvilTracker.KillFlashCheck(killer, target); + } + + public static void BlackOut(this IGameOptions opt, bool IsBlackOut) + { + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultImpostorVision); + opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); + + if (IsBlackOut) + { + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0); + opt.SetFloat(FloatOptionNames.CrewLightMod, 0); + } + } + + public static void SaveComboInfo() + { + SaveFile("./EHR_DATA/AlwaysCombos.json"); + SaveFile("./EHR_DATA/NeverCombos.json"); + return; + + void SaveFile(string path) + { + try + { + Il2CppSystem.Collections.Generic.Dictionary>> data = new(); + Dictionary>> dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos; + + dict.Do(kvp => + { + data[kvp.Key] = new(); + + kvp.Value.Do(pair => + { + var key = pair.Key.ToString(); + data[kvp.Key][key] = new(); + pair.Value.Do(x => data[kvp.Key][key].Add(x.ToString())); + }); + }); + + File.WriteAllText(path, JsonConvert.SerializeObject(data, Formatting.Indented)); + } + catch (Exception e) + { + Logger.Error("Failed to save combo info", "SaveComboInfo"); + ThrowException(e); + } + } + } + + public static void LoadComboInfo() + { + LoadFile("./EHR_DATA/AlwaysCombos.json"); + LoadFile("./EHR_DATA/NeverCombos.json"); + return; + + void LoadFile(string path) + { + try + { + if (!File.Exists(path)) return; + + var data = JsonConvert.DeserializeObject>>>(File.ReadAllText(path)); + Dictionary>> dict = path.Contains("Always") ? Main.AlwaysSpawnTogetherCombos : Main.NeverSpawnTogetherCombos; + dict.Clear(); + + foreach (Il2CppSystem.Collections.Generic.KeyValuePair>> kvp in data) + { + dict[kvp.Key] = []; + + foreach (Il2CppSystem.Collections.Generic.KeyValuePair> pair in kvp.Value) + { + var key = Enum.Parse(pair.Key); + dict[kvp.Key][key] = []; + foreach (string n in pair.Value) dict[kvp.Key][key].Add(Enum.Parse(n)); + } + } + } + catch (Exception e) + { + Logger.Error("Failed to load combo info", "LoadComboInfo"); + ThrowException(e); + } + } + } + + public static void RemovePlayerFromPreviousRoleData(PlayerControl target) + { + switch (target.GetCustomRole()) + { + case CustomRoles.Enigma: + Enigma.PlayerIdList.Remove(target.PlayerId); + break; + case CustomRoles.Mediumshiper: + Mediumshiper.PlayerIdList.Remove(target.PlayerId); + break; + case CustomRoles.Mortician: + Mortician.PlayerIdList.Remove(target.PlayerId); + break; + case CustomRoles.Spiritualist: + Spiritualist.PlayerIdList.Remove(target.PlayerId); + break; + } + } + + public static string GetDisplayRoleName(byte playerId, bool pure = false, bool seeTargetBetrayalAddons = false) + { + (string, Color) TextData = GetRoleText(playerId, playerId, pure, seeTargetBetrayalAddons); + return ColorString(TextData.Item2, TextData.Item1); + } + + public static string GetRoleName(CustomRoles role, bool forUser = true) + { + return GetRoleString(role.ToString(), forUser); + } + + public static string GetRoleMode(CustomRoles role, bool parentheses = true) + { + if (Options.HideGameSettings.GetBool() && Main.AllPlayerControls.Length > 1) return string.Empty; + + string mode; + + try + { + mode = !role.IsAdditionRole() + ? GetString($"Rate{role.GetMode()}") + : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role].GetInt()}"); + } + catch (KeyNotFoundException) + { + mode = GetString("Rate0"); + } + + mode = mode.Replace("color=", string.Empty); + return parentheses ? $"({mode})" : mode; + } + + public static Color GetRoleColor(CustomRoles role) + { + string hexColor = Main.RoleColors.GetValueOrDefault(role, "#ffffff"); + _ = ColorUtility.TryParseHtmlString(hexColor, out Color c); + return c; + } + + public static string GetRoleColorCode(CustomRoles role) + { + string hexColor = Main.RoleColors.GetValueOrDefault(role, "#ffffff"); + return hexColor; + } + + public static (string, Color) GetRoleText(byte seerId, byte targetId, bool pure = false, bool seeTargetBetrayalAddons = false) + { + PlayerState seerState = Main.PlayerStates[seerId]; + PlayerState targetState = Main.PlayerStates[targetId]; + + CustomRoles seerMainRole = seerState.MainRole; + List seerSubRoles = seerState.SubRoles; + + CustomRoles targetMainRole = targetState.MainRole; + List targetSubRoles = targetState.SubRoles; + + bool self = seerId == targetId || seerState.IsDead; + + if (!self && Main.DiedThisRound.Contains(seerId)) return (string.Empty, Color.white); + + if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated() && targetMainRole == CustomRoles.Agent && HnSManager.PlayerRoles[seerId].Interface.Team != Team.Impostor) + targetMainRole = CustomRoles.Hider; + + if (!self && seerMainRole.IsImpostor() && targetMainRole == CustomRoles.DoubleAgent && DoubleAgent.ShownRoles.TryGetValue(targetId, out var shownRole)) + targetMainRole = shownRole; + + var loversShowDifferentRole = false; + + if (!GameStates.IsEnded && targetMainRole == CustomRoles.LovingImpostor && !self && seerMainRole != CustomRoles.LovingCrewmate && !seerSubRoles.Contains(CustomRoles.Lovers)) + { + targetMainRole = Lovers.LovingImpostorRoleForOtherImps.GetValue() switch + { + 0 => CustomRoles.ImpostorEHR, + 1 => Lovers.LovingImpostorRole, + _ => CustomRoles.LovingImpostor + }; + + loversShowDifferentRole = true; + } + + string RoleText = GetRoleName(targetMainRole); + Color RoleColor = GetRoleColor(loversShowDifferentRole ? CustomRoles.Impostor : targetMainRole); + + if (LastImpostor.CurrentId == targetId) RoleText = GetRoleString("Last-") + RoleText; + + if (Options.NameDisplayAddons.GetBool() && !pure && self) + { + foreach (CustomRoles subRole in targetSubRoles) + { + if (subRole is not CustomRoles.LastImpostor and not CustomRoles.Madmate and not CustomRoles.Charmed and not CustomRoles.Recruit and not CustomRoles.Lovers and not CustomRoles.Contagious and not CustomRoles.Bloodlust) + { + string str = GetString("Prefix." + subRole); + if (!subRole.IsAdditionRole()) str = GetString(subRole.ToString()); + + RoleText = ColorString(GetRoleColor(subRole), (Options.AddBracketsToAddons.GetBool() ? "<#ffffff>(" : string.Empty) + str + (Options.AddBracketsToAddons.GetBool() ? "<#ffffff>)" : string.Empty) + " ") + RoleText; } } } - public static void RemovePlayerFromPreviousRoleData(PlayerControl target) + if (seerMainRole == CustomRoles.LovingImpostor && self) RoleColor = GetRoleColor(CustomRoles.LovingImpostor); + + if (targetSubRoles.Contains(CustomRoles.Madmate)) { - switch (target.GetCustomRole()) + RoleColor = GetRoleColor(CustomRoles.Madmate); + RoleText = GetRoleString("Mad-") + RoleText; + } + + if (targetSubRoles.Contains(CustomRoles.Recruit)) + { + RoleColor = GetRoleColor(CustomRoles.Recruit); + RoleText = GetRoleString("Recruit-") + RoleText; + } + + if (targetSubRoles.Contains(CustomRoles.Charmed) && (self || pure || seeTargetBetrayalAddons || seerMainRole == CustomRoles.Succubus || (Succubus.TargetKnowOtherTarget.GetBool() && seerSubRoles.Contains(CustomRoles.Charmed)))) + { + RoleColor = GetRoleColor(CustomRoles.Charmed); + RoleText = GetRoleString("Charmed-") + RoleText; + } + + if (targetSubRoles.Contains(CustomRoles.Contagious) && (self || pure || seeTargetBetrayalAddons || seerMainRole == CustomRoles.Virus || (Virus.TargetKnowOtherTarget.GetBool() && seerSubRoles.Contains(CustomRoles.Contagious)))) + { + RoleColor = GetRoleColor(CustomRoles.Contagious); + RoleText = GetRoleString("Contagious-") + RoleText; + } + + if (targetSubRoles.Contains(CustomRoles.Bloodlust) && (self || pure || seeTargetBetrayalAddons)) + { + RoleColor = GetRoleColor(CustomRoles.Bloodlust); + RoleText = $"{GetString("Prefix.Bloodlust")} {RoleText}"; + } + + return (RoleText, RoleColor); + } + + public static string GetKillCountText(byte playerId, bool ffa = false) + { + if (Main.PlayerStates.All(x => x.Value.GetRealKiller() != playerId) && !ffa) return string.Empty; + + return ' ' + ColorString(new(255, 69, 0, byte.MaxValue), string.Format(GetString("KillCount"), Main.PlayerStates.Count(x => x.Value.GetRealKiller() == playerId))); + } + + public static string GetVitalText(byte playerId, bool realKillerColor = false) + { + PlayerState state = Main.PlayerStates[playerId]; + string deathReason = state.IsDead ? GetString("DeathReason." + state.deathReason) : GetString("Alive"); + + if (realKillerColor) + { + byte KillerId = state.GetRealKiller(); + Color color = KillerId != byte.MaxValue ? Main.PlayerColors[KillerId] : GetRoleColor(CustomRoles.Doctor); + if (state.deathReason == PlayerState.DeathReason.Disconnected) color = new(255, 255, 255, 50); + + deathReason = ColorString(color, deathReason); + } + + return deathReason; + } + + public static MessageWriter CreateRPC(CustomRPC rpc) + { + return AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)rpc, SendOption.Reliable); + } + + public static void EndRPC(MessageWriter writer) + { + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void SendRPC(CustomRPC rpc, params object[] data) + { + if (!DoRPC) return; + + MessageWriter w; + + try + { + w = CreateRPC(rpc); + } + catch + { + return; + } + + try + { + foreach (object o in data) { - case CustomRoles.Enigma: - Enigma.PlayerIdList.Remove(target.PlayerId); + switch (o) + { + case byte b: + w.Write(b); + break; + case int i: + w.WritePacked(i); + break; + case float f: + w.Write(f); + break; + case string s: + w.Write(s); + break; + case bool b: + w.Write(b); + break; + case long l: + w.Write(l.ToString()); + break; + case char c: + w.Write(c.ToString()); + break; + case Vector2 v: + w.Write(v); + break; + case Vector3 v: + w.Write(v); + break; + case InnerNetObject ino: + w.WriteNetObject(ino); + break; + default: + try + { + if (o != null && Enum.TryParse(o.GetType(), o.ToString(), out object e) && e != null) w.WritePacked((int)e); + } + catch (InvalidCastException e) + { + ThrowException(e); + } + + break; + } + } + } + finally + { + EndRPC(w); + } + } + + public static void IncreaseAbilityUseLimitOnKill(PlayerControl killer) + { + if (Main.PlayerStates[killer.PlayerId].Role is Mafioso { IsEnable: true } mo) mo.OnMurder(killer, null); + + float add = GetSettingNameAndValueForRole(killer.GetCustomRole(), "AbilityUseGainWithEachKill"); + killer.RpcIncreaseAbilityUseLimitBy(add); + } + + public static void ThrowException(Exception ex, [CallerFilePath] string fileName = "", [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string callerMemberName = "") + { + try + { + StackTrace st = new(1, true); + StackFrame[] stFrames = st.GetFrames(); + + StackFrame firstFrame = stFrames.FirstOrDefault(); + + StringBuilder sb = new(); + sb.Append($" {ex.GetType().Name}: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName.Split('\\')[^1]}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); + + var skip = true; + + foreach (StackFrame sf in stFrames) + { + if (skip) + { + skip = false; + continue; + } + + MethodBase callerMethod = sf.GetMethod(); + + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + + sb.Append($"\n at {callerClassName}.{callerMethodName}"); + } + + sb.Append("\n------ End of Method Stack Trace ------"); + sb.Append("\n------ Exception ------\n "); + + sb.Append(ex.StackTrace?.Replace("\r\n", "\n").Replace("\\n", "\n").Replace("\n", "\n ")); + + sb.Append("\n------ End of Exception ------"); + sb.Append("\n------ Exception Stack Trace ------\n"); + + StackTrace stEx = new(ex, true); + StackFrame[] stFramesEx = stEx.GetFrames(); + + foreach (StackFrame sf in stFramesEx) + { + MethodBase callerMethod = sf.GetMethod(); + + string callerMethodName = callerMethod?.Name; + string callerClassName = callerMethod?.DeclaringType?.FullName; + + sb.Append($"\n at {callerClassName}.{callerMethodName} in {sf.GetFileName()}, line {sf.GetFileLineNumber()}"); + } + + sb.Append("\n------ End of Exception Stack Trace ------"); + + Logger.Error(sb.ToString(), firstFrame?.GetMethod()?.ToString(), multiLine: true); + } + catch { } + } + + public static void SetAllVentInteractions() + { + VentilationSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast()); + } + + public static bool HasTasks(NetworkedPlayerInfo p, bool ForRecompute = true) + { + if (GameStates.IsLobby) return false; + + if (p.Tasks == null) return false; + + if (p.Role == null) return false; + + var hasTasks = true; + PlayerState state = Main.PlayerStates[p.PlayerId]; + if (p.Disconnected) return false; + + if (p.Role.IsImpostor) hasTasks = false; + + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: return false; + case CustomGameMode.FFA: return false; + case CustomGameMode.MoveAndStop: return !p.IsDead; + case CustomGameMode.HotPotato: return false; + case CustomGameMode.Speedrun: return !p.IsDead; + case CustomGameMode.CaptureTheFlag: return false; + case CustomGameMode.NaturalDisasters: return false; + case CustomGameMode.RoomRush: return false; + case CustomGameMode.HideAndSeek: return HnSManager.HasTasks(p); + case CustomGameMode.AllInOne: return true; + } + + CustomRoles role = state.MainRole; + + switch (role) + { + case CustomRoles.GM: + case CustomRoles.Sheriff when !Options.UsePets.GetBool() || !Sheriff.UsePet.GetBool(): + case CustomRoles.Arsonist: + case CustomRoles.Jackal: + case CustomRoles.Sidekick: + case CustomRoles.Poisoner: + case CustomRoles.Eclipse: + case CustomRoles.Pyromaniac: + case CustomRoles.NSerialKiller: + case CustomRoles.NoteKiller: + case CustomRoles.Vortex: + case CustomRoles.Beehive: + case CustomRoles.RouleteGrandeur: + case CustomRoles.Nonplus: + case CustomRoles.Tremor: + case CustomRoles.Evolver: + case CustomRoles.Rogue: + case CustomRoles.Patroller: + case CustomRoles.Simon: + case CustomRoles.Chemist: + case CustomRoles.Samurai: + case CustomRoles.QuizMaster: + case CustomRoles.Bargainer: + case CustomRoles.Tiger: + case CustomRoles.SoulHunter: + case CustomRoles.Enderman: + case CustomRoles.Mycologist: + case CustomRoles.Bubble: + case CustomRoles.Hookshot: + case CustomRoles.Sprayer: + case CustomRoles.Doppelganger: + case CustomRoles.PlagueDoctor: + case CustomRoles.Postman: + case CustomRoles.SchrodingersCat: + case CustomRoles.Shifter: + case CustomRoles.Technician: + case CustomRoles.Tank: + case CustomRoles.Gaslighter: + case CustomRoles.Impartial: + case CustomRoles.Backstabber: + case CustomRoles.Predator: + case CustomRoles.Reckless: + case CustomRoles.WeaponMaster: + case CustomRoles.Magician: + case CustomRoles.Vengeance: + case CustomRoles.HeadHunter: + case CustomRoles.Imitator: + case CustomRoles.Werewolf: + case CustomRoles.Bandit: + case CustomRoles.Jailor when !Options.UsePets.GetBool() || !Jailor.UsePet.GetBool(): + case CustomRoles.Traitor: + case CustomRoles.Glitch: + case CustomRoles.Pickpocket: + case CustomRoles.Maverick: + case CustomRoles.Jinx: + case CustomRoles.Parasite: + case CustomRoles.Agitater: + case CustomRoles.Crusader when !Options.UsePets.GetBool() || !Crusader.UsePet.GetBool(): + case CustomRoles.Refugee: + case CustomRoles.Jester: + case CustomRoles.Mario: + case CustomRoles.Vulture: + case CustomRoles.God: + case CustomRoles.SwordsMan when !Options.UsePets.GetBool() || !SwordsMan.UsePet.GetBool(): + case CustomRoles.Innocent: + case CustomRoles.Pelican: + case CustomRoles.Medusa: + case CustomRoles.Revolutionist: + case CustomRoles.FFF: + case CustomRoles.Gamer: + case CustomRoles.HexMaster: + case CustomRoles.Wraith: + case CustomRoles.Juggernaut: + case CustomRoles.Ritualist: + case CustomRoles.DarkHide: + case CustomRoles.Collector: + case CustomRoles.ImperiusCurse: + case CustomRoles.Provocateur: + case CustomRoles.Medic when !Options.UsePets.GetBool() || !Medic.UsePet.GetBool(): + case CustomRoles.BloodKnight: + case CustomRoles.Camouflager: + case CustomRoles.Totocalcio: + case CustomRoles.Romantic: + case CustomRoles.VengefulRomantic: + case CustomRoles.RuthlessRomantic: + case CustomRoles.Succubus: + case CustomRoles.Necromancer: + case CustomRoles.Deathknight: + case CustomRoles.Amnesiac: + case CustomRoles.Monarch when !Options.UsePets.GetBool() || !Monarch.UsePet.GetBool(): + case CustomRoles.Deputy when !Options.UsePets.GetBool() || !Deputy.UsePet.GetBool(): + case CustomRoles.Virus: + case CustomRoles.Farseer when !Options.UsePets.GetBool() || !Farseer.UsePet.GetBool(): + case CustomRoles.Aid when !Options.UsePets.GetBool() || !Aid.UsePet.GetBool(): + case CustomRoles.Socialite when !Options.UsePets.GetBool() || !Socialite.UsePet.GetBool(): + case CustomRoles.Escort when !Options.UsePets.GetBool() || !Escort.UsePet.GetBool(): + case CustomRoles.DonutDelivery when !Options.UsePets.GetBool() || !DonutDelivery.UsePet.GetBool(): + case CustomRoles.Gaulois when !Options.UsePets.GetBool() || !Gaulois.UsePet.GetBool(): + case CustomRoles.Analyst when !Options.UsePets.GetBool() || !Analyst.UsePet.GetBool(): + case CustomRoles.Witness when !Options.UsePets.GetBool() || !Options.WitnessUsePet.GetBool(): + case CustomRoles.Goose: + case CustomRoles.Pursuer: + case CustomRoles.Spiritcaller: + case CustomRoles.PlagueBearer: + case CustomRoles.Pestilence: + case CustomRoles.Doomsayer: + hasTasks = false; + break; + case CustomRoles.Dad when ((Dad)state.Role).DoneTasks: + case CustomRoles.Workaholic: + case CustomRoles.Terrorist: + case CustomRoles.Sunnyboy: + case CustomRoles.Convict: + case CustomRoles.Opportunist: + case CustomRoles.Executioner: + case CustomRoles.Lawyer: + case CustomRoles.Phantasm: + if (ForRecompute) hasTasks = false; + + break; + case CustomRoles.Cherokious: + case CustomRoles.Crewpostor: + if (ForRecompute && !p.IsDead) hasTasks = false; + + if (p.IsDead) hasTasks = false; + + break; + case CustomRoles.Wizard: + hasTasks = true; + break; + default: + if (role.IsImpostor()) hasTasks = false; + + break; + } + + foreach (CustomRoles subRole in state.SubRoles) + { + switch (subRole) + { + case CustomRoles.Madmate: + case CustomRoles.Charmed: + case CustomRoles.Recruit: + case CustomRoles.Egoist: + case CustomRoles.Contagious: + case CustomRoles.Rascal: + case CustomRoles.EvilSpirit: + hasTasks &= !ForRecompute; break; - case CustomRoles.Mediumshiper: - Mediumshiper.PlayerIdList.Remove(target.PlayerId); + case CustomRoles.Bloodlust: + hasTasks = false; break; - case CustomRoles.Mortician: - Mortician.PlayerIdList.Remove(target.PlayerId); - break; - case CustomRoles.Spiritualist: - Spiritualist.PlayerIdList.Remove(target.PlayerId); + case CustomRoles.Specter: + case CustomRoles.Haunter: + hasTasks = !ForRecompute; break; } } - public static string GetDisplayRoleName(byte playerId, bool pure = false, bool seeTargetBetrayalAddons = false) + if (CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == p.PlayerId) && ForRecompute && (!Options.UsePets.GetBool() || CopyCat.UsePet.GetBool())) hasTasks = false; + + hasTasks |= role.UsesPetInsteadOfKill() && role is not (CustomRoles.Refugee or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Sidekick); + + return hasTasks; + } + + public static bool CanBeMadmate(this PlayerControl pc) + { + return pc != null && pc.IsCrewmate() && !pc.Is(CustomRoles.Madmate) + && !( + (pc.Is(CustomRoles.Sheriff) && !Options.SheriffCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Mayor) && !Options.MayorCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.NiceGuesser) && !Options.NGuesserCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Snitch) && !Options.SnitchCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Judge) && !Options.JudgeCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Marshall) && !Options.MarshallCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Farseer) && !Options.FarseerCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.President) && !Options.PresidentCanBeMadmate.GetBool()) || + pc.Is(CustomRoles.NiceSwapper) || + pc.Is(CustomRoles.Speedrunner) || + pc.Is(CustomRoles.Needy) || + pc.Is(CustomRoles.Loyal) || + pc.Is(CustomRoles.SuperStar) || + pc.Is(CustomRoles.CyberStar) || + pc.Is(CustomRoles.Egoist) || + pc.Is(CustomRoles.DualPersonality) + ); + } + + public static bool IsRoleTextEnabled(PlayerControl __instance) + { + switch (Options.CurrentGameMode) { - (string, Color) TextData = GetRoleText(playerId, playerId, pure, seeTargetBetrayalAddons); - return ColorString(TextData.Item2, TextData.Item1); + case CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush: + case CustomGameMode.Standard when CustomRoles.Altruist.RoleExist() && Main.DiedThisRound.Contains(PlayerControl.LocalPlayer.PlayerId): + return PlayerControl.LocalPlayer.Is(CustomRoles.GM); + case CustomGameMode.FFA or CustomGameMode.SoloKombat or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato or CustomGameMode.Speedrun or CustomGameMode.AllInOne: + case CustomGameMode.HideAndSeek when HnSManager.IsRoleTextEnabled(PlayerControl.LocalPlayer, __instance): + return true; } - public static string GetRoleName(CustomRoles role, bool forUser = true) + if ((Main.VisibleTasksCount && PlayerControl.LocalPlayer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || (PlayerControl.LocalPlayer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool())) return true; + + if (__instance.IsLocalPlayer()) return true; + + switch (__instance.GetCustomRole()) { - return GetRoleString(role.ToString(), forUser); + case CustomRoles.Crewpostor when PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool(): + case CustomRoles.Jackal when PlayerControl.LocalPlayer.Is(CustomRoles.Jackal): + case CustomRoles.Jackal when PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick): + case CustomRoles.Jackal when PlayerControl.LocalPlayer.Is(CustomRoles.Recruit): + case CustomRoles.Recruit when PlayerControl.LocalPlayer.Is(CustomRoles.Jackal): + case CustomRoles.Recruit when PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick): + case CustomRoles.Recruit when PlayerControl.LocalPlayer.Is(CustomRoles.Recruit): + case CustomRoles.Sidekick when PlayerControl.LocalPlayer.Is(CustomRoles.Jackal): + case CustomRoles.Sidekick when PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick): + case CustomRoles.Sidekick when PlayerControl.LocalPlayer.Is(CustomRoles.Recruit): + case CustomRoles.Workaholic when Workaholic.WorkaholicVisibleToEveryone.GetBool(): + case CustomRoles.Doctor when !__instance.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool(): + case CustomRoles.Mayor when Mayor.MayorRevealWhenDoneTasks.GetBool() && __instance.GetTaskState().IsTaskFinished: + case CustomRoles.Marshall when Marshall.CanSeeMarshall(PlayerControl.LocalPlayer) && __instance.GetTaskState().IsTaskFinished: + return true; } - public static string GetRoleMode(CustomRoles role, bool parentheses = true) + return (__instance.IsMadmate() && PlayerControl.LocalPlayer.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) || + (__instance.IsMadmate() && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowWhosMadmate.GetBool()) || + (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.IsMadmate() && Options.MadmateKnowWhosImp.GetBool()) || + (__instance.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead) || + (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || + (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || + (Main.LoversPlayers.TrueForAll(x => x.PlayerId == __instance.PlayerId || x.IsLocalPlayer()) && Main.LoversPlayers.Count == 2 && Lovers.LoverKnowRoles.GetBool()) || + (CustomTeamManager.AreInSameCustomTeam(__instance.PlayerId, PlayerControl.LocalPlayer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(__instance.PlayerId, CTAOption.KnowRoles)) || + Main.PlayerStates.Values.Any(x => x.Role.KnowRole(PlayerControl.LocalPlayer, __instance)) || + PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) || + (PlayerControl.LocalPlayer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) || + PlayerControl.LocalPlayer.Is(CustomRoles.GM) || + Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == __instance.PlayerId) || + Main.GodMode.Value; + } + + public static string GetFormattedRoomName(string roomName) + { + return roomName == "Outside" ? "<#00ffa5>Outside" : $"<#ffffff>In <#00ffa5>{roomName}"; + } + + public static string GetFormattedVectorText(Vector2 pos) + { + return $"<#777777>(at {pos.ToString().Replace("(", string.Empty).Replace(")", string.Empty)})"; + } + + public static string GetProgressText(PlayerControl pc) + { + TaskState taskState = pc.GetTaskState(); + var Comms = false; + + if (taskState.HasTasks) { - if (Options.HideGameSettings.GetBool() && Main.AllPlayerControls.Length > 1) return string.Empty; + if (IsActive(SystemTypes.Comms)) Comms = true; + + if (Camouflager.IsActive) Comms = true; + //if (PlayerControl.LocalPlayer.myTasks.ToArray().Any(x => x.TaskType == TaskTypes.FixComms)) Comms = true; + } + + return GetProgressText(pc.PlayerId, Comms); + } + + public static string GetProgressText(byte playerId, bool comms = false) + { + switch (Options.CurrentGameMode) + { + case CustomGameMode.MoveAndStop: return GetTaskCount(playerId, comms, true); + case CustomGameMode.Speedrun: return string.Empty; + } + + StringBuilder ProgressText = new(); + PlayerControl pc = GetPlayerById(playerId); + + try + { + ProgressText.Append(Main.PlayerStates[playerId].Role.GetProgressText(playerId, comms)); + } + catch (Exception ex) + { + Logger.Error($"For {pc.GetNameWithRole().RemoveHtmlTags()}, failed to get progress text: " + ex, "Utils.GetProgressText"); + } + + if (pc.Is(CustomRoles.Damocles)) ProgressText.Append($" {Damocles.GetProgressText(playerId)}"); + if (pc.Is(CustomRoles.Stressed)) ProgressText.Append($" {Stressed.GetProgressText(playerId)}"); + if (pc.Is(CustomRoles.Circumvent)) ProgressText.Append($" {Circumvent.GetProgressText(playerId)}"); + + if (pc.Is(CustomRoles.Taskcounter)) + { + string totalCompleted = comms ? "?" : $"{GameData.Instance.CompletedTasks}"; + ProgressText.Append($" <#00ffa5>{totalCompleted}<#ffffff>/{GameData.Instance.TotalTasks}"); + } + + if (ProgressText.Length != 0 && !ProgressText.ToString().RemoveHtmlTags().StartsWith(' ')) ProgressText.Insert(0, ' '); + + return ProgressText.ToString(); + } + + public static string GetAbilityUseLimitDisplay(byte playerId, bool usingAbility = false) + { + try + { + float limit = playerId.GetAbilityUseLimit(); + if (float.IsNaN(limit) /* || limit is > 100 or < 0*/) return string.Empty; + + Color TextColor; + + if (limit < 1) + TextColor = Color.red; + else if (usingAbility) + TextColor = Color.green; + else + TextColor = GetRoleColor(Main.PlayerStates[playerId].MainRole).ShadeColor(0.25f); + + return ColorString(TextColor, $" ({Math.Round(limit, 1)})"); + } + catch + { + return string.Empty; + } + } + + public static string GetTaskCount(byte playerId, bool comms, bool moveAndStop = false) + { + try + { + if (playerId == 0 && Main.GM.Value) return string.Empty; + + TaskState taskState = Main.PlayerStates[playerId].TaskState; + if (!taskState.HasTasks) return string.Empty; + + NetworkedPlayerInfo info = GetPlayerInfoById(playerId); + Color TaskCompleteColor = HasTasks(info) ? Color.green : GetRoleColor(Main.PlayerStates[playerId].MainRole).ShadeColor(0.5f); + Color NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white; + + if (Workhorse.IsThisRole(playerId)) NonCompleteColor = Workhorse.RoleColor; + + Color NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; + + if (Main.PlayerStates.TryGetValue(playerId, out PlayerState ps)) + { + NormalColor = ps.MainRole switch + { + CustomRoles.Crewpostor => Color.red, + CustomRoles.Cherokious => GetRoleColor(CustomRoles.Cherokious), + _ => NormalColor + }; + } + + Color TextColor = comms ? Color.gray : NormalColor; + string Completed = comms ? "?" : $"{taskState.CompletedTasksCount}"; + return ColorString(TextColor, $" {(moveAndStop ? "" : string.Empty)}{Completed}/{taskState.AllTasksCount}{(moveAndStop ? $" <#ffffff>({MoveAndStop.GetLivesRemaining(playerId)} \u2665)" : string.Empty)}"); + } + catch + { + return string.Empty; + } + } + + public static void ShowActiveSettingsHelp(byte PlayerId = byte.MaxValue) + { + SendMessage(GetString("CurrentActiveSettingsHelp") + ":", PlayerId); + + if (Options.DisableDevices.GetBool()) SendMessage(GetString("DisableDevicesInfo"), PlayerId); + if (Options.SyncButtonMode.GetBool()) SendMessage(GetString("SyncButtonModeInfo"), PlayerId); + if (Options.SabotageTimeControl.GetBool()) SendMessage(GetString("SabotageTimeControlInfo"), PlayerId); + if (Options.RandomMapsMode.GetBool()) SendMessage(GetString("RandomMapsModeInfo"), PlayerId); + + if (Main.GM.Value) SendMessage(GetRoleName(CustomRoles.GM) + GetString("GMInfoLong"), PlayerId); + + foreach (CustomRoles role in Enum.GetValues().Where(role => role.IsEnable() && !role.IsVanilla())) + SendMessage(GetRoleName(role) + GetRoleMode(role) + GetString($"{role}InfoLong"), PlayerId); + + if (Options.NoGameEnd.GetBool()) SendMessage(GetString("NoGameEndInfo"), PlayerId); + } + + /// + /// Gets all players within a specified radius from the specified location + /// + /// The radius + /// The location which the radius is counted from + /// A list containing all PlayerControls within the specified range from the specified location + public static IEnumerable GetPlayersInRadius(float radius, Vector2 from) + { + return from tg in Main.AllAlivePlayerControls let dis = Vector2.Distance(@from, tg.Pos()) where !Pelican.IsEaten(tg.PlayerId) && !tg.inVent where dis <= radius select tg; + } + + public static void ShowActiveSettings(byte PlayerId = byte.MaxValue) + { + if (Options.HideGameSettings.GetBool() && PlayerId != byte.MaxValue) + { + SendMessage(GetString("Message.HideGameSettings"), PlayerId); + return; + } + + if (Options.DIYGameSettings.GetBool()) + { + SendMessage(GetString("Message.NowOverrideText"), PlayerId); + return; + } + + StringBuilder sb = new(); + sb.Append($" \u2605 {GetString("TabGroup.SystemSettings")}"); + Options.GroupedOptions[TabGroup.SystemSettings].Do(CheckAndAppendOptionString); + sb.Append($"\n\n \u2605 {GetString("TabGroup.GameSettings")}"); + Options.GroupedOptions[TabGroup.GameSettings].Do(CheckAndAppendOptionString); + + SendMessage(sb.ToString().RemoveHtmlTags(), PlayerId); + return; + + void CheckAndAppendOptionString(OptionItem item) + { + if (item.GetBool() && item.Parent == null && !item.IsCurrentlyHidden()) + { + sb.Append($"\n{item.GetName(true)}: {item.GetString()}"); + } + } + } + + public static void ShowAllActiveSettings(byte PlayerId = byte.MaxValue) + { + if (Options.HideGameSettings.GetBool() && PlayerId != byte.MaxValue) + { + SendMessage(GetString("Message.HideGameSettings"), PlayerId); + return; + } + + if (Options.DIYGameSettings.GetBool()) + { + SendMessage(GetString("Message.NowOverrideText"), PlayerId); + return; + } + + StringBuilder sb = new(); + + sb.Append(GetString("Settings")).Append(':'); + + foreach (KeyValuePair role in Options.CustomRoleCounts) + { + if (!role.Key.IsEnable()) continue; string mode; try { - mode = !role.IsAdditionRole() + mode = !role.Key.IsAdditionRole() + ? GetString($"Rate{role.Key.GetMode()}") + : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role.Key].GetInt()}"); + } + catch (KeyNotFoundException) + { + continue; + } + + mode = mode.Replace("color=", string.Empty); + + sb.Append($"\n【{GetRoleName(role.Key)}:{mode} ×{role.Key.GetCount()}】\n"); + ShowChildrenSettings(Options.CustomRoleSpawnChances[role.Key], ref sb); + } + + foreach (OptionItem opt in OptionItem.AllOptions) + { + if (opt.GetBool() && opt.Parent == null && opt.Id is >= 80000 and < 640000 && !opt.IsCurrentlyHidden()) + { + if (opt.Name is "KillFlashDuration" or "RoleAssigningAlgorithm") + sb.Append($"\n【{opt.GetName(true)}: {opt.GetString()}】\n"); + else + sb.Append($"\n【{opt.GetName(true)}】\n"); + + ShowChildrenSettings(opt, ref sb); + } + } + + SendMessage(sb.ToString().RemoveHtmlTags(), PlayerId); + } + + public static void CopyCurrentSettings() + { + StringBuilder sb = new(); + + if (Options.HideGameSettings.GetBool() && !AmongUsClient.Instance.AmHost) + { + ClipboardHelper.PutClipboardString(GetString("Message.HideGameSettings")); + return; + } + + sb.Append($"━━━━━━━━━━━━【{GetString("Roles")}】━━━━━━━━━━━━"); + + foreach (KeyValuePair role in Options.CustomRoleCounts) + { + if (!role.Key.IsEnable()) continue; + + string mode; + + try + { + mode = !role.Key.IsAdditionRole() + ? GetString($"Rate{role.Key.GetMode()}") + : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role.Key].GetInt()}"); + } + catch (KeyNotFoundException) + { + continue; + } + + mode = mode.Replace("color=", string.Empty); + + sb.Append($"\n【{GetRoleName(role.Key)}:{mode} ×{role.Key.GetCount()}】\n"); + ShowChildrenSettings(Options.CustomRoleSpawnChances[role.Key], ref sb); + } + + sb.Append($"━━━━━━━━━━━━【{GetString("Settings")}】━━━━━━━━━━━━"); + + foreach (OptionItem opt in OptionItem.AllOptions.Where(x => x.GetBool() && x.Parent == null && x.Id is >= 80000 and < 640000 && !x.IsCurrentlyHidden())) + { + if (opt.Name == "KillFlashDuration") + sb.Append($"\n【{opt.GetName(true)}: {opt.GetString()}】\n"); + else + sb.Append($"\n【{opt.GetName(true)}】\n"); + + ShowChildrenSettings(opt, ref sb); + } + + sb.Append("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"); + ClipboardHelper.PutClipboardString(sb.ToString().RemoveHtmlTags()); + } + + public static void ShowActiveRoles(byte PlayerId = byte.MaxValue) + { + if (Options.HideGameSettings.GetBool() && PlayerId != byte.MaxValue) + { + SendMessage(GetString("Message.HideGameSettings"), PlayerId); + return; + } + + StringBuilder sb = new(); + sb.Append($"\n{GetRoleName(CustomRoles.GM)}: {(Main.GM.Value ? GetString("RoleRate") : GetString("RoleOff"))}"); + + StringBuilder impsb = new(); + StringBuilder neutralsb = new(); + StringBuilder crewsb = new(); + StringBuilder addonsb = new(); + StringBuilder ghostsb = new(); + + foreach (CustomRoles role in CustomGameMode.HideAndSeek.IsActiveOrIntegrated() ? HnSManager.AllHnSRoles : Enum.GetValues().Except(HnSManager.AllHnSRoles)) + { + string mode; + + try + { + mode = !role.IsAdditionRole() || role.IsGhostRole() ? GetString($"Rate{role.GetMode()}") : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role].GetInt()}"); } catch (KeyNotFoundException) { - mode = GetString("Rate0"); + continue; } mode = mode.Replace("color=", string.Empty); - return parentheses ? $"({mode})" : mode; + + if (role.IsEnable()) + { + var roleDisplay = $"\n{ColorString(GetRoleColor(role).ShadeColor(0.25f), GetString(role.ToString()))}: {mode} x{role.GetCount()}"; + + if (role.IsGhostRole()) ghostsb.Append(roleDisplay); + else if (role.IsAdditionRole()) addonsb.Append(roleDisplay); + else if (role.IsCrewmate()) crewsb.Append(roleDisplay); + else if (role.IsImpostor() || role.IsMadmate()) impsb.Append(roleDisplay); + else if (role.IsNeutral()) neutralsb.Append(roleDisplay); + } } - public static Color GetRoleColor(CustomRoles role) + SendMessage(sb.Append("\n.").ToString(), PlayerId, GetString("GMRoles")); + SendMessage(impsb.Append("\n.").ToString(), PlayerId, ColorString(GetRoleColor(CustomRoles.Impostor), GetString("ImpostorRoles"))); + SendMessage(crewsb.Append("\n.").ToString(), PlayerId, ColorString(GetRoleColor(CustomRoles.Crewmate), GetString("CrewmateRoles"))); + SendMessage(neutralsb.Append("\n.").ToString(), PlayerId, GetString("NeutralRoles")); + SendMessage(ghostsb.Append("\n.").ToString(), PlayerId, GetString("GhostRoles")); + SendMessage(addonsb.Append("\n.").ToString(), PlayerId, GetString("AddonRoles")); + } + + public static void ShowChildrenSettings(OptionItem option, ref StringBuilder sb, int deep = 0, bool f1 = false, bool disableColor = true) + { + foreach (var opt in option.Children.Select((v, i) => new { Value = v, Index = i + 1 })) { - string hexColor = Main.RoleColors.GetValueOrDefault(role, "#ffffff"); - _ = ColorUtility.TryParseHtmlString(hexColor, out Color c); - return c; - } + switch (opt.Value.Name) + { + case "DisableSkeldDevices" when Main.CurrentMap is not MapNames.Skeld and not MapNames.Dleks: + case "DisableMiraHQDevices" when Main.CurrentMap != MapNames.Mira: + case "DisablePolusDevices" when Main.CurrentMap != MapNames.Polus: + case "DisableAirshipDevices" when Main.CurrentMap != MapNames.Airship: + case "PolusReactorTimeLimit" when Main.CurrentMap != MapNames.Polus: + case "AirshipReactorTimeLimit" when Main.CurrentMap != MapNames.Airship: + case "ImpCanBeRole" or "CrewCanBeRole" or "NeutralCanBeRole" when f1: + continue; + } - public static string GetRoleColorCode(CustomRoles role) + if (deep > 0) + { + sb.Append(string.Concat(Enumerable.Repeat("┃", Mathf.Max(deep - 1, 0)))); + sb.Append(opt.Index == option.Children.Count ? "┗ " : "┣ "); + } + + string value = opt.Value.GetString().Replace("ON", "<#00ffa5>ON").Replace("OFF", "<#ff0000>OFF"); + var name = $"{opt.Value.GetName(disableColor).Replace("color=", string.Empty)}"; + sb.Append($"{name}: <#ffff00>{value}\n"); + if (opt.Value.GetBool()) ShowChildrenSettings(opt.Value, ref sb, deep + 1, disableColor: disableColor); + } + } + + public static void ShowLastRoles(byte PlayerId = byte.MaxValue) + { + if (AmongUsClient.Instance.IsGameStarted) { - string hexColor = Main.RoleColors.GetValueOrDefault(role, "#ffffff"); - return hexColor; + SendMessage(GetString("CantUse.lastroles"), PlayerId); + return; } - public static (string, Color) GetRoleText(byte seerId, byte targetId, bool pure = false, bool seeTargetBetrayalAddons = false) - { - PlayerState seerState = Main.PlayerStates[seerId]; - PlayerState targetState = Main.PlayerStates[targetId]; + StringBuilder sb = new(); - CustomRoles seerMainRole = seerState.MainRole; - List seerSubRoles = seerState.SubRoles; + sb.Append("<#ffffff>Role Summary:"); - CustomRoles targetMainRole = targetState.MainRole; - List targetSubRoles = targetState.SubRoles; + List cloneRoles = [.. Main.PlayerStates.Keys]; - bool self = seerId == targetId || seerState.IsDead; - - if (!self && Main.DiedThisRound.Contains(seerId)) return (string.Empty, Color.white); - - if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated() && targetMainRole == CustomRoles.Agent && HnSManager.PlayerRoles[seerId].Interface.Team != Team.Impostor) - targetMainRole = CustomRoles.Hider; - - if (!self && seerMainRole.IsImpostor() && targetMainRole == CustomRoles.DoubleAgent && DoubleAgent.ShownRoles.TryGetValue(targetId, out var shownRole)) - targetMainRole = shownRole; - - var loversShowDifferentRole = false; - - if (!GameStates.IsEnded && targetMainRole == CustomRoles.LovingImpostor && !self && seerMainRole != CustomRoles.LovingCrewmate && !seerSubRoles.Contains(CustomRoles.Lovers)) - { - targetMainRole = Lovers.LovingImpostorRoleForOtherImps.GetValue() switch - { - 0 => CustomRoles.ImpostorEHR, - 1 => Lovers.LovingImpostorRole, - _ => CustomRoles.LovingImpostor - }; - - loversShowDifferentRole = true; - } - - string RoleText = GetRoleName(targetMainRole); - Color RoleColor = GetRoleColor(loversShowDifferentRole ? CustomRoles.Impostor : targetMainRole); - - if (LastImpostor.CurrentId == targetId) RoleText = GetRoleString("Last-") + RoleText; - - if (Options.NameDisplayAddons.GetBool() && !pure && self) - { - foreach (CustomRoles subRole in targetSubRoles) - { - if (subRole is not CustomRoles.LastImpostor and not CustomRoles.Madmate and not CustomRoles.Charmed and not CustomRoles.Recruit and not CustomRoles.Lovers and not CustomRoles.Contagious and not CustomRoles.Bloodlust) - { - string str = GetString("Prefix." + subRole); - if (!subRole.IsAdditionRole()) str = GetString(subRole.ToString()); - - RoleText = ColorString(GetRoleColor(subRole), (Options.AddBracketsToAddons.GetBool() ? "<#ffffff>(" : string.Empty) + str + (Options.AddBracketsToAddons.GetBool() ? "<#ffffff>)" : string.Empty) + " ") + RoleText; - } - } - } - - if (seerMainRole == CustomRoles.LovingImpostor && self) RoleColor = GetRoleColor(CustomRoles.LovingImpostor); - - if (targetSubRoles.Contains(CustomRoles.Madmate)) - { - RoleColor = GetRoleColor(CustomRoles.Madmate); - RoleText = GetRoleString("Mad-") + RoleText; - } - - if (targetSubRoles.Contains(CustomRoles.Recruit)) - { - RoleColor = GetRoleColor(CustomRoles.Recruit); - RoleText = GetRoleString("Recruit-") + RoleText; - } - - if (targetSubRoles.Contains(CustomRoles.Charmed) && (self || pure || seeTargetBetrayalAddons || seerMainRole == CustomRoles.Succubus || (Succubus.TargetKnowOtherTarget.GetBool() && seerSubRoles.Contains(CustomRoles.Charmed)))) - { - RoleColor = GetRoleColor(CustomRoles.Charmed); - RoleText = GetRoleString("Charmed-") + RoleText; - } - - if (targetSubRoles.Contains(CustomRoles.Contagious) && (self || pure || seeTargetBetrayalAddons || seerMainRole == CustomRoles.Virus || (Virus.TargetKnowOtherTarget.GetBool() && seerSubRoles.Contains(CustomRoles.Contagious)))) - { - RoleColor = GetRoleColor(CustomRoles.Contagious); - RoleText = GetRoleString("Contagious-") + RoleText; - } - - if (targetSubRoles.Contains(CustomRoles.Bloodlust) && (self || pure || seeTargetBetrayalAddons)) - { - RoleColor = GetRoleColor(CustomRoles.Bloodlust); - RoleText = $"{GetString("Prefix.Bloodlust")} {RoleText}"; - } - - return (RoleText, RoleColor); - } - - public static string GetKillCountText(byte playerId, bool ffa = false) - { - if (Main.PlayerStates.All(x => x.Value.GetRealKiller() != playerId) && !ffa) return string.Empty; - - return ' ' + ColorString(new(255, 69, 0, byte.MaxValue), string.Format(GetString("KillCount"), Main.PlayerStates.Count(x => x.Value.GetRealKiller() == playerId))); - } - - public static string GetVitalText(byte playerId, bool realKillerColor = false) - { - PlayerState state = Main.PlayerStates[playerId]; - string deathReason = state.IsDead ? GetString("DeathReason." + state.deathReason) : GetString("Alive"); - - if (realKillerColor) - { - byte KillerId = state.GetRealKiller(); - Color color = KillerId != byte.MaxValue ? Main.PlayerColors[KillerId] : GetRoleColor(CustomRoles.Doctor); - if (state.deathReason == PlayerState.DeathReason.Disconnected) color = new(255, 255, 255, 50); - - deathReason = ColorString(color, deathReason); - } - - return deathReason; - } - - public static MessageWriter CreateRPC(CustomRPC rpc) - { - return AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)rpc, SendOption.Reliable); - } - - public static void EndRPC(MessageWriter writer) - { - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void SendRPC(CustomRPC rpc, params object[] data) - { - if (!DoRPC) return; - - MessageWriter w; - - try - { - w = CreateRPC(rpc); - } - catch - { - return; - } - - try - { - foreach (object o in data) - { - switch (o) - { - case byte b: - w.Write(b); - break; - case int i: - w.WritePacked(i); - break; - case float f: - w.Write(f); - break; - case string s: - w.Write(s); - break; - case bool b: - w.Write(b); - break; - case long l: - w.Write(l.ToString()); - break; - case char c: - w.Write(c.ToString()); - break; - case Vector2 v: - w.Write(v); - break; - case Vector3 v: - w.Write(v); - break; - case InnerNetObject ino: - w.WriteNetObject(ino); - break; - default: - try - { - if (o != null && Enum.TryParse(o.GetType(), o.ToString(), out object e) && e != null) w.WritePacked((int)e); - } - catch (InvalidCastException e) - { - ThrowException(e); - } - - break; - } - } - } - finally - { - EndRPC(w); - } - } - - public static void IncreaseAbilityUseLimitOnKill(PlayerControl killer) - { - if (Main.PlayerStates[killer.PlayerId].Role is Mafioso { IsEnable: true } mo) mo.OnMurder(killer, null); - - float add = GetSettingNameAndValueForRole(killer.GetCustomRole(), "AbilityUseGainWithEachKill"); - killer.RpcIncreaseAbilityUseLimitBy(add); - } - - public static void ThrowException(Exception ex, [CallerFilePath] string fileName = "", [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string callerMemberName = "") + foreach (byte id in Main.WinnerList) { try { - StackTrace st = new(1, true); - StackFrame[] stFrames = st.GetFrames(); + if (EndGamePatch.SummaryText[id].Contains("")) continue; - StackFrame firstFrame = stFrames.FirstOrDefault(); - - StringBuilder sb = new(); - sb.Append($" {ex.GetType().Name}: {ex.Message}\n thrown by {ex.Source}\n at {ex.TargetSite}\n in {fileName.Split('\\')[^1]}\n at line {lineNumber}\n in method \"{callerMemberName}\"\n------ Method Stack Trace ------"); - - var skip = true; - - foreach (StackFrame sf in stFrames) - { - if (skip) - { - skip = false; - continue; - } - - MethodBase callerMethod = sf.GetMethod(); - - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - - sb.Append($"\n at {callerClassName}.{callerMethodName}"); - } - - sb.Append("\n------ End of Method Stack Trace ------"); - sb.Append("\n------ Exception ------\n "); - - sb.Append(ex.StackTrace?.Replace("\r\n", "\n").Replace("\\n", "\n").Replace("\n", "\n ")); - - sb.Append("\n------ End of Exception ------"); - sb.Append("\n------ Exception Stack Trace ------\n"); - - StackTrace stEx = new(ex, true); - StackFrame[] stFramesEx = stEx.GetFrames(); - - foreach (StackFrame sf in stFramesEx) - { - MethodBase callerMethod = sf.GetMethod(); - - string callerMethodName = callerMethod?.Name; - string callerClassName = callerMethod?.DeclaringType?.FullName; - - sb.Append($"\n at {callerClassName}.{callerMethodName} in {sf.GetFileName()}, line {sf.GetFileLineNumber()}"); - } - - sb.Append("\n------ End of Exception Stack Trace ------"); - - Logger.Error(sb.ToString(), firstFrame?.GetMethod()?.ToString(), multiLine: true); - } - catch { } - } - - public static void SetAllVentInteractions() - { - VentilationSystemDeterioratePatch.SerializeV2(ShipStatus.Instance.Systems[SystemTypes.Ventilation].Cast()); - } - - public static bool HasTasks(NetworkedPlayerInfo p, bool ForRecompute = true) - { - if (GameStates.IsLobby) return false; - - if (p.Tasks == null) return false; - - if (p.Role == null) return false; - - var hasTasks = true; - PlayerState state = Main.PlayerStates[p.PlayerId]; - if (p.Disconnected) return false; - - if (p.Role.IsImpostor) hasTasks = false; - - switch (Options.CurrentGameMode) - { - case CustomGameMode.SoloKombat: return false; - case CustomGameMode.FFA: return false; - case CustomGameMode.MoveAndStop: return !p.IsDead; - case CustomGameMode.HotPotato: return false; - case CustomGameMode.Speedrun: return !p.IsDead; - case CustomGameMode.CaptureTheFlag: return false; - case CustomGameMode.NaturalDisasters: return false; - case CustomGameMode.RoomRush: return false; - case CustomGameMode.HideAndSeek: return HnSManager.HasTasks(p); - case CustomGameMode.AllInOne: return true; - } - - CustomRoles role = state.MainRole; - - switch (role) - { - case CustomRoles.GM: - case CustomRoles.Sheriff when !Options.UsePets.GetBool() || !Sheriff.UsePet.GetBool(): - case CustomRoles.Arsonist: - case CustomRoles.Jackal: - case CustomRoles.Sidekick: - case CustomRoles.Poisoner: - case CustomRoles.Eclipse: - case CustomRoles.Pyromaniac: - case CustomRoles.NSerialKiller: - case CustomRoles.NoteKiller: - case CustomRoles.Vortex: - case CustomRoles.Beehive: - case CustomRoles.RouleteGrandeur: - case CustomRoles.Nonplus: - case CustomRoles.Tremor: - case CustomRoles.Evolver: - case CustomRoles.Rogue: - case CustomRoles.Patroller: - case CustomRoles.Simon: - case CustomRoles.Chemist: - case CustomRoles.Samurai: - case CustomRoles.QuizMaster: - case CustomRoles.Bargainer: - case CustomRoles.Tiger: - case CustomRoles.SoulHunter: - case CustomRoles.Enderman: - case CustomRoles.Mycologist: - case CustomRoles.Bubble: - case CustomRoles.Hookshot: - case CustomRoles.Sprayer: - case CustomRoles.Doppelganger: - case CustomRoles.PlagueDoctor: - case CustomRoles.Postman: - case CustomRoles.SchrodingersCat: - case CustomRoles.Shifter: - case CustomRoles.Technician: - case CustomRoles.Tank: - case CustomRoles.Gaslighter: - case CustomRoles.Impartial: - case CustomRoles.Backstabber: - case CustomRoles.Predator: - case CustomRoles.Reckless: - case CustomRoles.WeaponMaster: - case CustomRoles.Magician: - case CustomRoles.Vengeance: - case CustomRoles.HeadHunter: - case CustomRoles.Imitator: - case CustomRoles.Werewolf: - case CustomRoles.Bandit: - case CustomRoles.Jailor when !Options.UsePets.GetBool() || !Jailor.UsePet.GetBool(): - case CustomRoles.Traitor: - case CustomRoles.Glitch: - case CustomRoles.Pickpocket: - case CustomRoles.Maverick: - case CustomRoles.Jinx: - case CustomRoles.Parasite: - case CustomRoles.Agitater: - case CustomRoles.Crusader when !Options.UsePets.GetBool() || !Crusader.UsePet.GetBool(): - case CustomRoles.Refugee: - case CustomRoles.Jester: - case CustomRoles.Mario: - case CustomRoles.Vulture: - case CustomRoles.God: - case CustomRoles.SwordsMan when !Options.UsePets.GetBool() || !SwordsMan.UsePet.GetBool(): - case CustomRoles.Innocent: - case CustomRoles.Pelican: - case CustomRoles.Medusa: - case CustomRoles.Revolutionist: - case CustomRoles.FFF: - case CustomRoles.Gamer: - case CustomRoles.HexMaster: - case CustomRoles.Wraith: - case CustomRoles.Juggernaut: - case CustomRoles.Ritualist: - case CustomRoles.DarkHide: - case CustomRoles.Collector: - case CustomRoles.ImperiusCurse: - case CustomRoles.Provocateur: - case CustomRoles.Medic when !Options.UsePets.GetBool() || !Medic.UsePet.GetBool(): - case CustomRoles.BloodKnight: - case CustomRoles.Camouflager: - case CustomRoles.Totocalcio: - case CustomRoles.Romantic: - case CustomRoles.VengefulRomantic: - case CustomRoles.RuthlessRomantic: - case CustomRoles.Succubus: - case CustomRoles.Necromancer: - case CustomRoles.Deathknight: - case CustomRoles.Amnesiac: - case CustomRoles.Monarch when !Options.UsePets.GetBool() || !Monarch.UsePet.GetBool(): - case CustomRoles.Deputy when !Options.UsePets.GetBool() || !Deputy.UsePet.GetBool(): - case CustomRoles.Virus: - case CustomRoles.Farseer when !Options.UsePets.GetBool() || !Farseer.UsePet.GetBool(): - case CustomRoles.Aid when !Options.UsePets.GetBool() || !Aid.UsePet.GetBool(): - case CustomRoles.Socialite when !Options.UsePets.GetBool() || !Socialite.UsePet.GetBool(): - case CustomRoles.Escort when !Options.UsePets.GetBool() || !Escort.UsePet.GetBool(): - case CustomRoles.DonutDelivery when !Options.UsePets.GetBool() || !DonutDelivery.UsePet.GetBool(): - case CustomRoles.Gaulois when !Options.UsePets.GetBool() || !Gaulois.UsePet.GetBool(): - case CustomRoles.Analyst when !Options.UsePets.GetBool() || !Analyst.UsePet.GetBool(): - case CustomRoles.Witness when !Options.UsePets.GetBool() || !Options.WitnessUsePet.GetBool(): - case CustomRoles.Goose: - case CustomRoles.Pursuer: - case CustomRoles.Spiritcaller: - case CustomRoles.PlagueBearer: - case CustomRoles.Pestilence: - case CustomRoles.Doomsayer: - hasTasks = false; - break; - case CustomRoles.Dad when ((Dad)state.Role).DoneTasks: - case CustomRoles.Workaholic: - case CustomRoles.Terrorist: - case CustomRoles.Sunnyboy: - case CustomRoles.Convict: - case CustomRoles.Opportunist: - case CustomRoles.Executioner: - case CustomRoles.Lawyer: - case CustomRoles.Phantasm: - if (ForRecompute) hasTasks = false; - - break; - case CustomRoles.Cherokious: - case CustomRoles.Crewpostor: - if (ForRecompute && !p.IsDead) hasTasks = false; - - if (p.IsDead) hasTasks = false; - - break; - case CustomRoles.Wizard: - hasTasks = true; - break; - default: - if (role.IsImpostor()) hasTasks = false; - - break; - } - - foreach (CustomRoles subRole in state.SubRoles) - { - switch (subRole) - { - case CustomRoles.Madmate: - case CustomRoles.Charmed: - case CustomRoles.Recruit: - case CustomRoles.Egoist: - case CustomRoles.Contagious: - case CustomRoles.Rascal: - case CustomRoles.EvilSpirit: - hasTasks &= !ForRecompute; - break; - case CustomRoles.Bloodlust: - hasTasks = false; - break; - case CustomRoles.Specter: - case CustomRoles.Haunter: - hasTasks = !ForRecompute; - break; - } - } - - if (CopyCat.Instances.Any(x => x.CopyCatPC.PlayerId == p.PlayerId) && ForRecompute && (!Options.UsePets.GetBool() || CopyCat.UsePet.GetBool())) hasTasks = false; - - hasTasks |= role.UsesPetInsteadOfKill() && role is not (CustomRoles.Refugee or CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Sidekick); - - return hasTasks; - } - - public static bool CanBeMadmate(this PlayerControl pc) - { - return pc != null && pc.IsCrewmate() && !pc.Is(CustomRoles.Madmate) - && !( - (pc.Is(CustomRoles.Sheriff) && !Options.SheriffCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Mayor) && !Options.MayorCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.NiceGuesser) && !Options.NGuesserCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Snitch) && !Options.SnitchCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Judge) && !Options.JudgeCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Marshall) && !Options.MarshallCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Farseer) && !Options.FarseerCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.President) && !Options.PresidentCanBeMadmate.GetBool()) || - pc.Is(CustomRoles.NiceSwapper) || - pc.Is(CustomRoles.Speedrunner) || - pc.Is(CustomRoles.Needy) || - pc.Is(CustomRoles.Loyal) || - pc.Is(CustomRoles.SuperStar) || - pc.Is(CustomRoles.CyberStar) || - pc.Is(CustomRoles.Egoist) || - pc.Is(CustomRoles.DualPersonality) - ); - } - - public static bool IsRoleTextEnabled(PlayerControl __instance) - { - switch (Options.CurrentGameMode) - { - case CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush: - case CustomGameMode.Standard when CustomRoles.Altruist.RoleExist() && Main.DiedThisRound.Contains(PlayerControl.LocalPlayer.PlayerId): - return PlayerControl.LocalPlayer.Is(CustomRoles.GM); - case CustomGameMode.FFA or CustomGameMode.SoloKombat or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato or CustomGameMode.Speedrun or CustomGameMode.AllInOne: - case CustomGameMode.HideAndSeek when HnSManager.IsRoleTextEnabled(PlayerControl.LocalPlayer, __instance): - return true; - } - - if ((Main.VisibleTasksCount && PlayerControl.LocalPlayer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || (PlayerControl.LocalPlayer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool())) return true; - - if (__instance.IsLocalPlayer()) return true; - - switch (__instance.GetCustomRole()) - { - case CustomRoles.Crewpostor when PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool(): - case CustomRoles.Jackal when PlayerControl.LocalPlayer.Is(CustomRoles.Jackal): - case CustomRoles.Jackal when PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick): - case CustomRoles.Jackal when PlayerControl.LocalPlayer.Is(CustomRoles.Recruit): - case CustomRoles.Recruit when PlayerControl.LocalPlayer.Is(CustomRoles.Jackal): - case CustomRoles.Recruit when PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick): - case CustomRoles.Recruit when PlayerControl.LocalPlayer.Is(CustomRoles.Recruit): - case CustomRoles.Sidekick when PlayerControl.LocalPlayer.Is(CustomRoles.Jackal): - case CustomRoles.Sidekick when PlayerControl.LocalPlayer.Is(CustomRoles.Sidekick): - case CustomRoles.Sidekick when PlayerControl.LocalPlayer.Is(CustomRoles.Recruit): - case CustomRoles.Workaholic when Workaholic.WorkaholicVisibleToEveryone.GetBool(): - case CustomRoles.Doctor when !__instance.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool(): - case CustomRoles.Mayor when Mayor.MayorRevealWhenDoneTasks.GetBool() && __instance.GetTaskState().IsTaskFinished: - case CustomRoles.Marshall when Marshall.CanSeeMarshall(PlayerControl.LocalPlayer) && __instance.GetTaskState().IsTaskFinished: - return true; - } - - return (__instance.IsMadmate() && PlayerControl.LocalPlayer.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) || - (__instance.IsMadmate() && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowWhosMadmate.GetBool()) || - (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.IsMadmate() && Options.MadmateKnowWhosImp.GetBool()) || - (__instance.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && __instance.Data.IsDead) || - (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || - (__instance.Is(CustomRoleTypes.Impostor) && PlayerControl.LocalPlayer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || - (Main.LoversPlayers.TrueForAll(x => x.PlayerId == __instance.PlayerId || x.IsLocalPlayer()) && Main.LoversPlayers.Count == 2 && Lovers.LoverKnowRoles.GetBool()) || - (CustomTeamManager.AreInSameCustomTeam(__instance.PlayerId, PlayerControl.LocalPlayer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(__instance.PlayerId, CTAOption.KnowRoles)) || - Main.PlayerStates.Values.Any(x => x.Role.KnowRole(PlayerControl.LocalPlayer, __instance)) || - PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) || - (PlayerControl.LocalPlayer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) || - PlayerControl.LocalPlayer.Is(CustomRoles.GM) || - Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == __instance.PlayerId) || - Main.GodMode.Value; - } - - public static string GetFormattedRoomName(string roomName) - { - return roomName == "Outside" ? "<#00ffa5>Outside" : $"<#ffffff>In <#00ffa5>{roomName}"; - } - - public static string GetFormattedVectorText(Vector2 pos) - { - return $"<#777777>(at {pos.ToString().Replace("(", string.Empty).Replace(")", string.Empty)})"; - } - - public static string GetProgressText(PlayerControl pc) - { - TaskState taskState = pc.GetTaskState(); - var Comms = false; - - if (taskState.HasTasks) - { - if (IsActive(SystemTypes.Comms)) Comms = true; - - if (Camouflager.IsActive) Comms = true; - //if (PlayerControl.LocalPlayer.myTasks.ToArray().Any(x => x.TaskType == TaskTypes.FixComms)) Comms = true; - } - - return GetProgressText(pc.PlayerId, Comms); - } - - public static string GetProgressText(byte playerId, bool comms = false) - { - switch (Options.CurrentGameMode) - { - case CustomGameMode.MoveAndStop: return GetTaskCount(playerId, comms, true); - case CustomGameMode.Speedrun: return string.Empty; - } - - StringBuilder ProgressText = new(); - PlayerControl pc = GetPlayerById(playerId); - - try - { - ProgressText.Append(Main.PlayerStates[playerId].Role.GetProgressText(playerId, comms)); + sb.Append("\n<#c4aa02>\u2605 ").Append(EndGamePatch.SummaryText[id] /*.RemoveHtmlTags()*/); + cloneRoles.Remove(id); } catch (Exception ex) { - Logger.Error($"For {pc.GetNameWithRole().RemoveHtmlTags()}, failed to get progress text: " + ex, "Utils.GetProgressText"); + ThrowException(ex); } - - if (pc.Is(CustomRoles.Damocles)) ProgressText.Append($" {Damocles.GetProgressText(playerId)}"); - if (pc.Is(CustomRoles.Stressed)) ProgressText.Append($" {Stressed.GetProgressText(playerId)}"); - if (pc.Is(CustomRoles.Circumvent)) ProgressText.Append($" {Circumvent.GetProgressText(playerId)}"); - - if (pc.Is(CustomRoles.Taskcounter)) - { - string totalCompleted = comms ? "?" : $"{GameData.Instance.CompletedTasks}"; - ProgressText.Append($" <#00ffa5>{totalCompleted}<#ffffff>/{GameData.Instance.TotalTasks}"); - } - - if (ProgressText.Length != 0 && !ProgressText.ToString().RemoveHtmlTags().StartsWith(' ')) ProgressText.Insert(0, ' '); - - return ProgressText.ToString(); } - public static string GetAbilityUseLimitDisplay(byte playerId, bool usingAbility = false) + switch (Options.CurrentGameMode) { - try + case CustomGameMode.SoloKombat: + List<(int, byte)> list = []; + list.AddRange(cloneRoles.Select(id => (SoloKombatManager.GetRankOfScore(id), id))); + + list.Sort(); + + foreach ((int, byte) id in list) + { + try + { + sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id.Item2]); + } + catch (Exception ex) + { + ThrowException(ex); + } + } + + break; + case CustomGameMode.FFA: + List<(int, byte)> list2 = []; + list2.AddRange(cloneRoles.Select(id => (FFAManager.GetRankFromScore(id), id))); + + list2.Sort(); + + foreach ((int, byte) id in list2) + { + try + { + sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id.Item2]); + } + catch (Exception ex) + { + ThrowException(ex); + } + } + + break; + case CustomGameMode.MoveAndStop: + List<(int, byte)> list3 = []; + list3.AddRange(cloneRoles.Select(id => (MoveAndStop.GetRankFromScore(id), id))); + + list3.Sort(); + + foreach ((int, byte) id in list3) + { + try + { + sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id.Item2]); + } + catch (Exception ex) + { + ThrowException(ex); + } + } + + break; + case CustomGameMode.AllInOne: + case CustomGameMode.RoomRush: + case CustomGameMode.NaturalDisasters: + case CustomGameMode.CaptureTheFlag: + case CustomGameMode.Speedrun: + case CustomGameMode.HotPotato: + case CustomGameMode.HideAndSeek: + foreach (byte id in cloneRoles) + { + try + { + sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id]); + } + catch (Exception ex) + { + ThrowException(ex); + } + } + + break; + default: + foreach (byte id in cloneRoles) + { + try + { + if (!EndGamePatch.SummaryText.TryGetValue(id, out string summaryText)) continue; + + if (summaryText.Contains("")) continue; + + sb.Append("\n\u3000 ").Append(summaryText); + } + catch (Exception ex) + { + ThrowException(ex); + } + } + + break; + } + + sb.Append(""); + + SendMessage("\n", PlayerId, sb.ToString()); + } + + public static void ShowKillLog(byte PlayerId = byte.MaxValue) + { + if (GameStates.IsInGame) + { + SendMessage(GetString("CantUse.killlog"), PlayerId); + return; + } + + if (EndGamePatch.KillLog != string.Empty) SendMessage(EndGamePatch.KillLog, PlayerId); + } + + public static void ShowLastResult(byte PlayerId = byte.MaxValue) + { + if (GameStates.IsInGame) + { + SendMessage(GetString("CantUse.lastresult"), PlayerId); + return; + } + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + StringBuilder sb = new(); + if (SetEverythingUpPatch.LastWinsText != string.Empty) sb.Append($"{GetString("LastResult")} {SetEverythingUpPatch.LastWinsText}"); + + if (SetEverythingUpPatch.LastWinsReason != string.Empty) sb.Append($"\n{GetString("LastEndReason")} {SetEverythingUpPatch.LastWinsReason}"); + + if (sb.Length > 0) SendMessage("\n", PlayerId, sb.ToString()); + } + + public static void ShowLastAddOns(byte PlayerId = byte.MaxValue) + { + if (GameStates.IsInGame) + { + SendMessage(GetString("CantUse.lastresult"), PlayerId); + return; + } + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + string result = Main.LastAddOns.Values.Join(delimiter: "\n"); + SendMessage("\n", PlayerId, result); + } + + public static string GetSubRolesText(byte id, bool disableColor = false, bool intro = false, bool summary = false) + { + List SubRoles = Main.PlayerStates[id].SubRoles; + if (SubRoles.Count == 0) return string.Empty; + + StringBuilder sb = new(); + + if (intro) + { + bool isLovers = SubRoles.Contains(CustomRoles.Lovers) && Main.PlayerStates[id].MainRole is not CustomRoles.LovingCrewmate and not CustomRoles.LovingImpostor; + SubRoles.RemoveAll(x => x is CustomRoles.NotAssigned or CustomRoles.LastImpostor or CustomRoles.Lovers); + + if (isLovers) sb.Append($"{ColorString(GetRoleColor(CustomRoles.Lovers), " ♥")}"); + + if (SubRoles.Count == 0) return sb.ToString(); + + sb.Append(""); + + if (SubRoles.Count == 1) { - float limit = playerId.GetAbilityUseLimit(); - if (float.IsNaN(limit) /* || limit is > 100 or < 0*/) return string.Empty; + CustomRoles role = SubRoles[0]; - Color TextColor; + string RoleText = ColorString(GetRoleColor(role), GetRoleName(role)); + sb.Append($"{ColorString(Color.gray, GetString("Modifier"))}{RoleText}"); + } + else + { + sb.Append($"{ColorString(Color.gray, GetString("Modifiers"))}"); - if (limit < 1) - TextColor = Color.red; - else if (usingAbility) - TextColor = Color.green; + for (var i = 0; i < SubRoles.Count; i++) + { + if (i != 0) sb.Append(", "); + + CustomRoles role = SubRoles[i]; + + string RoleText = ColorString(GetRoleColor(role), GetRoleName(role)); + sb.Append(RoleText); + } + } + + sb.Append(""); + } + else if (!summary) + { + foreach (CustomRoles role in SubRoles) + { + if (role is CustomRoles.NotAssigned or CustomRoles.LastImpostor) continue; + + string RoleText = disableColor ? GetRoleName(role) : ColorString(GetRoleColor(role), GetRoleName(role)); + sb.Append($"{ColorString(Color.gray, " + ")}{RoleText}"); + } + } + + return sb.ToString(); + } + + public static byte MsgToColor(string text, bool isHost = false) + { + text = text.ToLowerInvariant(); + text = text.Replace("色", string.Empty); + int color; + + try + { + color = int.Parse(text); + } + catch + { + color = -1; + } + + color = text switch + { + "0" or "红" or "紅" or "red" or "Red" or "крас" or "Крас" or "красн" or "Красн" or "красный" or "Красный" or "Vermelho" or "vermelho" => 0, + "1" or "蓝" or "藍" or "深蓝" or "blue" or "Blue" or "син" or "Син" or "синий" or "Синий" or "Azul" or "azul" => 1, + "2" or "绿" or "綠" or "深绿" or "green" or "Green" or "Зел" or "зел" or "Зелёный" or "Зеленый" or "зелёный" or "зеленый" or "Verde" or "verde" or "Verde-Escuro" or "verde-escuro" => 2, + "3" or "粉红" or "pink" or "Pink" or "Роз" or "роз" or "Розовый" or "розовый" or "Rosa" or "rosa" => 3, + "4" or "橘" or "orange" or "Orange" or "оранж" or "Оранж" or "оранжевый" or "Оранжевый" or "Laranja" or "laranja" => 4, + "5" or "黄" or "黃" or "yellow" or "Yellow" or "Жёлт" or "Желт" or "жёлт" or "желт" or "Жёлтый" or "Желтый" or "жёлтый" or "желтый" or "Amarelo" or "amarelo" => 5, + "6" or "黑" or "black" or "Black" or "Чёрный" or "Черный" or "чёрный" or "черный" or "Preto" or "preto" => 6, + "7" or "白" or "white" or "White" or "Белый" or "белый" or "Branco" or "branco" => 7, + "8" or "紫" or "purple" or "Purple" or "Фиол" or "фиол" or "Фиолетовый" or "фиолетовый" or "Roxo" or "roxo" => 8, + "9" or "棕" or "brown" or "Brown" or "Корич" or "корич" or "Коричневый" or "коричевый" or "Marrom" or "marrom" => 9, + "10" or "青" or "cyan" or "Cyan" or "Голуб" or "голуб" or "Голубой" or "голубой" or "Ciano" or "ciano" => 10, + "11" or "黄绿" or "黃綠" or "浅绿" or "lime" or "Lime" or "Лайм" or "лайм" or "Лаймовый" or "лаймовый" or "Lima" or "lima" or "Verde-Claro" or "verde-claro" => 11, + "12" or "红褐" or "紅褐" or "深红" or "maroon" or "Maroon" or "Борд" or "борд" or "Бордовый" or "бордовый" or "Bordô" or "bordô" or "Vinho" or "vinho" => 12, + "13" or "玫红" or "玫紅" or "浅粉" or "rose" or "Rose" or "Светло роз" or "светло роз" or "Светло розовый" or "светло розовый" or "Сирень" or "сирень" or "Сиреневый" or "сиреневый" or "Rosê" or "rosê" or "rosinha" or "Rosinha" or "Rosa-Claro" or "rosa-claro" => 13, + "14" or "焦黄" or "焦黃" or "淡黄" or "banana" or "Banana" or "Банан" or "банан" or "Банановый" or "банановый" or "Amarelo-Claro" or "amarelo-claro" => 14, + "15" or "灰" or "gray" or "Gray" or "Сер" or "сер" or "Серый" or "серый" or "Cinza" or "cinza" => 15, + "16" or "茶" or "tan" or "Tan" or "Загар" or "загар" or "Загаровый" or "загаровый" or "bege" or "bege" or "Creme" or "creme" => 16, + "17" or "珊瑚" or "coral" or "Coral" or "Корал" or "корал" or "Коралл" or "коралл" or "Коралловый" or "коралловый" => 17, + "18" or "隐藏" or "?" or "Fortegreen" or "fortegreen" => 18, + _ => color + }; + + return !isHost && color == 18 ? byte.MaxValue : color is < 0 or > 18 ? byte.MaxValue : Convert.ToByte(color); + } + + public static void ShowHelp(byte ID) + { + PlayerControl player = GetPlayerById(ID); + SendMessage(ChatCommands.AllCommands.Where(x => x.CanUseCommand(player, false)).Aggregate("", (s, c) => s + $"\n/{c.CommandForms.Where(f => f.All(char.IsAscii)).MinBy(f => f.Length)}{(c.Arguments.Length == 0 ? string.Empty : $" {c.Arguments.Split(' ').Select((x, i) => ColorString(GetColor(i), x)).Join(delimiter: " ")}")} \u2192 {c.Description}"), ID, GetString("CommandList")); + return; + + Color GetColor(int i) + { + return i switch + { + 0 => Palette.Orange, + 1 => Color.magenta, + 2 => Color.blue, + 3 => Color.red, + 4 => Palette.Brown, + 5 => Color.cyan, + 6 => Color.green, + 7 => Palette.Purple, + + _ => Color.yellow + }; + } + } + + public static void CheckTerroristWin(NetworkedPlayerInfo Terrorist) + { + if (!AmongUsClient.Instance.AmHost) return; + + TaskState taskState = GetPlayerById(Terrorist.PlayerId).GetTaskState(); + + if (taskState.IsTaskFinished && (!Main.PlayerStates[Terrorist.PlayerId].IsSuicide || Options.CanTerroristSuicideWin.GetBool())) + { + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.Terrorist)) + Main.PlayerStates[pc.PlayerId].deathReason = Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote ? PlayerState.DeathReason.etc : PlayerState.DeathReason.Suicide; + else if (!pc.Data.IsDead) pc.Suicide(PlayerState.DeathReason.Bombed, Terrorist.Object); + } + + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Terrorist); + CustomWinnerHolder.WinnerIds.Add(Terrorist.PlayerId); + } + } + + public static void CheckAndSpawnAdditionalRefugee(NetworkedPlayerInfo deadPlayer) + { + try + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || deadPlayer == null || deadPlayer.Object.Is(CustomRoles.Refugee) || Main.HasJustStarted || !GameStates.InGame || !Options.SpawnAdditionalRefugeeOnImpsDead.GetBool() || Main.AllAlivePlayerControls.Length < Options.SpawnAdditionalRefugeeMinAlivePlayers.GetInt() || CustomRoles.Refugee.RoleExist(true) || Main.AllAlivePlayerControls == null || Main.AllAlivePlayerControls.Length == 0 || Main.AllAlivePlayerControls.Any(x => x.PlayerId != deadPlayer.PlayerId && (x.Is(CustomRoleTypes.Impostor) || (x.IsNeutralKiller() && !Options.SpawnAdditionalRefugeeWhenNKAlive.GetBool())))) return; + + PlayerControl[] ListToChooseFrom = Main.AllAlivePlayerControls.Where(x => x.PlayerId != deadPlayer.PlayerId && x.Is(CustomRoleTypes.Crewmate) && !x.Is(CustomRoles.Loyal)).ToArray(); + + if (ListToChooseFrom.Length > 0) + { + PlayerControl pc = ListToChooseFrom.RandomElement(); + pc.RpcSetCustomRole(CustomRoles.Refugee); + pc.SetKillCooldown(); + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Madmate); + Logger.Warn($"{pc.GetRealName()} is now a Refugee since all Impostors are dead", "Add Refugee"); + } + else + Logger.Msg("No Player to change to Refugee.", "Add Refugee"); + } + catch (Exception e) + { + ThrowException(e); + } + } + + public static void SendMessage(string text, byte sendTo = byte.MaxValue, string title = "", bool noSplit = false) + { + if (!AmongUsClient.Instance.AmHost) + { + if (sendTo == PlayerControl.LocalPlayer.PlayerId) + { + MessageWriter w = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestSendMessage); + w.Write(text); + w.Write(sendTo); + w.Write(title); + w.Write(noSplit); + w.EndMessage(); + } + + return; + } + + if (title == "") title = "" + GetString("DefaultSystemMessageTitle") + ""; + + if (title.Count(x => x == '\u2605') == 2 && !title.Contains('\n')) + { + if (title.Contains('<') && title.Contains('>') && title.Contains('#')) + title = $"{title[..(title.IndexOf('>') + 1)]}\u27a1{title.Replace("\u2605", "")[..(title.LastIndexOf('<') - 2)]}\u2b05"; + else + title = "\u27a1" + title.Replace("\u2605", "") + "\u2b05"; + } + + text = text.Replace("color=", string.Empty); + + if (text.Length >= 1200 && !noSplit) + { + string[] lines = text.Split('\n'); + var shortenedText = string.Empty; + + foreach (string line in lines) + { + if (shortenedText.Length + line.Length < 1200) + { + shortenedText += line + "\n"; + continue; + } + + if (shortenedText.Length >= 1200) + shortenedText.Chunk(1200).Do(x => SendMessage(new(x), sendTo, title, true)); else - TextColor = GetRoleColor(Main.PlayerStates[playerId].MainRole).ShadeColor(0.25f); + SendMessage(shortenedText, sendTo, title, true); - return ColorString(TextColor, $" ({Math.Round(limit, 1)})"); + string sentText = shortenedText; + shortenedText = line + "\n"; + + if (Regex.Matches(sentText, " Regex.Matches(sentText, "").Count) + { + string sizeTag = Regex.Matches(sentText, @"")[^1].Value; + shortenedText = sizeTag + shortenedText; + } } - catch + + if (shortenedText.Length > 0) SendMessage(shortenedText, sendTo, title, true); + + return; + } + + try + { + string pureText = text.RemoveHtmlTags(); + string pureTitle = title.RemoveHtmlTags(); + Logger.Info($" Message: {pureText[..(pureText.Length <= 300 ? pureText.Length : 300)]} - To: {(sendTo == byte.MaxValue ? "Everyone" : $"{GetPlayerById(sendTo)?.GetRealName()}")} - Title: {pureTitle[..(pureTitle.Length <= 300 ? pureTitle.Length : 300)]}", "SendMessage"); + } + catch + { + Logger.Info(" Message sent", "SendMessage"); + } + + text = text.RemoveHtmlTagsTemplate(); + + if (sendTo == byte.MaxValue) + Main.MessagesToSend.Add((text, sendTo, title)); + else + ChatUpdatePatch.SendMessage(Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer, text, sendTo, title); + } + + public static void ApplySuffix(PlayerControl player) + { + if (!AmongUsClient.Instance.AmHost || player == null) return; + + if (!player.AmOwner && !player.FriendCode.GetDevUser().HasTag() && !ChatCommands.IsPlayerModerator(player.FriendCode) && !ChatCommands.IsPlayerVIP(player.FriendCode)) return; + + string name = Main.AllPlayerNames.TryGetValue(player.PlayerId, out string n) ? n : string.Empty; + if (Main.NickName != string.Empty && player.AmOwner) name = Main.NickName; + + if (name == string.Empty) return; + + if (AmongUsClient.Instance.IsGameStarted) + { + if (Options.FormatNameMode.GetInt() == 1 && Main.NickName == string.Empty) name = Palette.GetColorName(player.Data.DefaultOutfit.ColorId); + } + else + { + if (!GameStates.IsLobby) return; + + if (player.AmOwner) { - return string.Empty; + if (GameStates.IsOnlineGame || GameStates.IsLocalGame) name = $"{GetString("HostText")}{GetString("Icon")}{name}"; + + string modeText = GetString($"Mode{Options.CurrentGameMode}"); + + name = Options.CurrentGameMode switch + { + CustomGameMode.SoloKombat => $"{modeText}\r\n{name}", + CustomGameMode.FFA => $"{modeText}\r\n{name}", + CustomGameMode.MoveAndStop => $"{modeText}\r\n{name}", + CustomGameMode.HotPotato => $"{modeText}\r\n{name}", + CustomGameMode.HideAndSeek => $"{modeText}\r\n{name}", + CustomGameMode.CaptureTheFlag => $"{modeText}\r\n{name}", + CustomGameMode.NaturalDisasters => $"{modeText}\r\n{name}", + CustomGameMode.RoomRush => $"{modeText}\r\n{name}", + CustomGameMode.AllInOne => $"{modeText}\r\n{name}", + CustomGameMode.Speedrun => ColorString(GetRoleColor(CustomRoles.Speedrunner), $"{modeText}\r\n") + name, + _ => name + }; + } + + DevUser devUser = player.FriendCode.GetDevUser(); + bool isMod = ChatCommands.IsPlayerModerator(player.FriendCode); + bool isVIP = ChatCommands.IsPlayerVIP(player.FriendCode); + bool hasTag = devUser.HasTag(); + + if (hasTag || isMod || isVIP) + { + string tag = hasTag ? devUser.GetTag() : string.Empty; + if (tag == "null") tag = string.Empty; + + if (player.AmOwner || player.IsModClient()) + { + var modTagModded = $"{GetString("ModeratorTag")}\r\n"; + var vipTagModded = $"{GetString("VIPTag")}\r\n"; + name = $"{(hasTag ? tag : string.Empty)}{(isMod ? modTagModded : string.Empty)}{(isVIP ? vipTagModded : string.Empty)}{name}"; + } + else + { + var modTagVanilla = $"{GetString("ModeratorTag")} - "; + var vipTagVanilla = $"{GetString("VIPTag")} - "; + name = $"{(hasTag ? tag.Replace("\r\n", " - ") : string.Empty)}{(isMod ? modTagVanilla : string.Empty)}{(isVIP ? vipTagVanilla : string.Empty)}{name}"; + } + } + + if (player.AmOwner) + { + name = Options.GetSuffixMode() switch + { + SuffixModes.EHR => $"{name}\r\nEHR v{Main.PluginDisplayVersion}", + SuffixModes.Streaming => $"{name}\r\n{GetString("SuffixMode.Streaming")}", + SuffixModes.Recording => $"{name}\r\n{GetString("SuffixMode.Recording")}", + SuffixModes.RoomHost => $"{name}\r\n{GetString("SuffixMode.RoomHost")}", + SuffixModes.OriginalName => $"{name}\r\n{DataManager.player.Customization.Name}", + SuffixModes.DoNotKillMe => $"{name}\r\n{GetString("SuffixModeText.DoNotKillMe")}", + SuffixModes.NoAndroidPlz => $"{name}\r\n{GetString("SuffixModeText.NoAndroidPlz")}", + SuffixModes.AutoHost => $"{name}\r\n{GetString("SuffixModeText.AutoHost")}", + _ => name + }; } } - public static string GetTaskCount(byte playerId, bool comms, bool moveAndStop = false) + if (name != player.name && player.CurrentOutfitType == PlayerOutfitType.Default) player.RpcSetName(name); + } + + public static Dictionary GetAllPlayerLocationsCount() + { + Dictionary playerRooms = []; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return null; + + Il2CppReferenceArray Rooms = ShipStatus.Instance.AllRooms; + if (Rooms == null) return null; + + foreach (PlainShipRoom room in Rooms) + { + if (!room.roomArea) continue; + + if (!pc.Collider.IsTouching(room.roomArea)) continue; + + string roomName = GetString($"{room.RoomId}"); + if (!playerRooms.TryAdd(roomName, 1)) playerRooms[roomName]++; + } + } + + return playerRooms; + } + + public static float GetSettingNameAndValueForRole(CustomRoles role, string settingName) + { + const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + Type[] types = Assembly.GetExecutingAssembly().GetTypes(); + FieldInfo field = types.SelectMany(x => x.GetFields(flags)).FirstOrDefault(x => x.Name == $"{role}{settingName}"); + + if (field == null) + { + FieldInfo tempField = null; + + foreach (Type x in types) + { + var any = false; + + foreach (FieldInfo f in x.GetFields(flags)) + { + if (f.Name.Contains(settingName)) + { + any = true; + tempField = f; + break; + } + } + + if (any && x.Name == $"{role}") + { + field = tempField; + break; + } + } + } + + float add; + + if (field == null) + add = float.MaxValue; + else + { + if (field.GetValue(null) is OptionItem optionItem) + add = optionItem.GetFloat(); + else + add = float.MaxValue; + } + + return add; + } + + public static string ColoredPlayerName(this byte id) + { + return ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), Main.AllPlayerNames.GetValueOrDefault(id, GetPlayerById(id)?.GetRealName() ?? $"Someone (ID {id})")); + } + + public static PlayerControl GetPlayer(this byte id) + { + return GetPlayerById(id); + } + + public static PlayerControl GetPlayerById(int PlayerId, bool fast = true) + { + if (PlayerId is > byte.MaxValue or < byte.MinValue) return null; + + if (fast && GameStates.IsInGame && Main.PlayerStates.TryGetValue((byte)PlayerId, out PlayerState state) && state.Player != null) return state.Player; + + return Main.AllPlayerControls.FirstOrDefault(x => x.PlayerId == PlayerId); + } + + public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) + { + return GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == PlayerId); + } + + public static IEnumerator NotifyEveryoneAsync() + { + var count = 0; + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + foreach (PlayerControl seer in aapc) + { + foreach (PlayerControl target in aapc) + { + NotifyRoles(SpecifySeer: seer, SpecifyTarget: target); + if (count++ % 2 == 0) yield return null; + } + } + } + + public static void NotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) + { + if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModClient() && (CustomGameMode.Standard.IsActiveOrIntegrated() || SpecifySeer.IsHost())) || !AmongUsClient.Instance.AmHost || Main.AllPlayerControls == null || (GameStates.IsMeeting && !isForMeeting))) return; + + DoNotifyRoles(isForMeeting, SpecifySeer, SpecifyTarget, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting, MushroomMixup); + } + + public static void DoNotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) + { + PlayerControl[] apc = Main.AllPlayerControls; + + PlayerControl[] seerList = SpecifySeer != null ? [SpecifySeer] : apc; + PlayerControl[] targetList = SpecifyTarget != null ? [SpecifyTarget] : apc; + + long now = TimeStamp; + + // seer: Players who can see changes made here + // target: Players subject to changes that seer can see + foreach (PlayerControl seer in seerList) { try { - if (playerId == 0 && Main.GM.Value) return string.Empty; + if (seer == null || seer.Data.Disconnected || (seer.IsModClient() && (seer.IsHost() || CustomGameMode.Standard.IsActiveOrIntegrated()))) continue; - TaskState taskState = Main.PlayerStates[playerId].TaskState; - if (!taskState.HasTasks) return string.Empty; + // During intro scene, set team name for non-modded clients and skip the rest. + string SelfName; + Team seerTeam = seer.GetTeam(); + CustomRoles seerRole = seer.GetCustomRole(); - NetworkedPlayerInfo info = GetPlayerInfoById(playerId); - Color TaskCompleteColor = HasTasks(info) ? Color.green : GetRoleColor(Main.PlayerStates[playerId].MainRole).ShadeColor(0.5f); + if (SetUpRoleTextPatch.IsInIntro && (seerRole.IsDesyncRole() || seer.Is(CustomRoles.Bloodlust)) && CustomGameMode.Standard.IsActiveOrIntegrated()) + { + const string iconTextLeft = "\u21e8"; + const string iconTextRight = "\u21e6"; + const string roleNameUp = "\n \n"; + + var selfTeamName = $"{iconTextLeft} {ColorString(seerTeam.GetTeamColor(), $"{seerTeam}")} {iconTextRight}\n \n"; + SelfName = $"{selfTeamName}\r\n{seerRole.ToColoredString()}{roleNameUp}"; + + seer.RpcSetNamePrivate(SelfName, seer); + continue; + } + + if (GameStates.IsLobby) + { + SelfName = seer.GetRealName(); + seer.RpcSetNameEx(SelfName); + continue; + } + + if (seer.Is(CustomRoles.Car) && !isForMeeting) + { + seer.RpcSetNamePrivate(Car.Name, force: NoCache); + continue; + } + + var fontSize = "1.7"; + if (isForMeeting && (seer.GetClient().PlatformData.Platform == Platforms.Playstation || seer.GetClient().PlatformData.Platform == Platforms.Switch)) fontSize = "70%"; + + // Text containing progress, such as tasks + string SelfTaskText = GameStates.IsLobby ? string.Empty : GetProgressText(seer); + + SelfMark.Clear(); + SelfSuffix.Clear(); + + if (!GameStates.IsLobby) + { + if (AntiBlackout.SkipTasks) SelfSuffix.AppendLine(GetString("AntiBlackoutSkipTasks")); + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) goto GameMode0; + + SelfMark.Append(Snitch.GetWarningArrow(seer)); + if (Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Lovers), " ♥")); + + if (BallLightning.IsGhost(seer)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■")); + + SelfMark.Append(Medic.GetMark(seer, seer)); + SelfMark.Append(Gaslighter.GetMark(seer, seer, isForMeeting)); + SelfMark.Append(Gamer.TargetMark(seer, seer)); + SelfMark.Append(Sniper.GetShotNotify(seer.PlayerId)); + if (Silencer.ForSilencer.Contains(seer.PlayerId)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Silencer), "╳")); + + GameMode0: + + if (Options.CurrentGameMode is not CustomGameMode.Standard and not CustomGameMode.HideAndSeek) goto GameMode; + + Main.PlayerStates.Values.Do(x => SelfSuffix.Append(x.Role.GetSuffix(seer, seer, meeting: isForMeeting))); + + SelfSuffix.Append(Spurt.GetSuffix(seer)); + + SelfSuffix.Append(CustomTeamManager.GetSuffix(seer)); + + if (!isForMeeting) + { + if (Options.UsePets.GetBool() && Main.AbilityCD.TryGetValue(seer.PlayerId, out (long StartTimeStamp, int TotalCooldown) time)) + { + long remainingCD = time.TotalCooldown - (now - time.StartTimeStamp) + 1; + SelfSuffix.Append(string.Format(GetString("CDPT"), remainingCD > 60 ? "> 60" : remainingCD)); + } + + if (seer.Is(CustomRoles.Asthmatic)) SelfSuffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); + if (seer.Is(CustomRoles.Sonar)) SelfSuffix.Append(Sonar.GetSuffix(seer, isForMeeting)); + if (seer.Is(CustomRoles.Deadlined)) SelfSuffix.Append(Deadlined.GetSuffix(seer)); + if (seer.Is(CustomRoles.Introvert)) SelfSuffix.Append(Introvert.GetSelfSuffix(seer)); + if (seer.Is(CustomRoles.Allergic)) SelfSuffix.Append(Allergic.GetSelfSuffix(seer)); + + SelfSuffix.Append(Bloodmoon.GetSuffix(seer)); + SelfSuffix.Append(Haunter.GetSuffix(seer)); + + switch (seerRole) + { + case CustomRoles.SuperStar when Options.EveryOneKnowSuperStar.GetBool(): + SelfMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); + break; + case CustomRoles.Monitor: + if (AntiAdminer.IsAdminWatch) SelfSuffix.Append($"{GetString("AntiAdminerAD")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Admin)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + if (AntiAdminer.IsVitalWatch) SelfSuffix.Append($"{GetString("AntiAdminerVI")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Vitals)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + if (AntiAdminer.IsDoorLogWatch) SelfSuffix.Append($"{GetString("AntiAdminerDL")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.DoorLog)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + if (AntiAdminer.IsCameraWatch) SelfSuffix.Append($"{GetString("AntiAdminerCA")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Camera)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + break; + case CustomRoles.AntiAdminer: + if (AntiAdminer.IsAdminWatch) SelfSuffix.Append(GetString("AntiAdminerAD")); + if (AntiAdminer.IsVitalWatch) SelfSuffix.Append(GetString("AntiAdminerVI")); + if (AntiAdminer.IsDoorLogWatch) SelfSuffix.Append(GetString("AntiAdminerDL")); + if (AntiAdminer.IsCameraWatch) SelfSuffix.Append(GetString("AntiAdminerCA")); + break; + } + } + else + { + SelfMark.Append(Witch.GetSpelledMark(seer.PlayerId, isForMeeting)); + if (isForMeeting) SelfMark.Append(Wasp.GetStungMark(seer.PlayerId)); + } + + GameMode: + + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA: + SelfSuffix.Append(FFAManager.GetPlayerArrow(seer)); + break; + case CustomGameMode.SoloKombat: + SelfSuffix.Append(SoloKombatManager.GetDisplayHealth(seer)); + break; + case CustomGameMode.MoveAndStop: + SelfSuffix.Append(MoveAndStop.GetSuffixText(seer)); + break; + case CustomGameMode.HotPotato: + SelfSuffix.Append(HotPotato.GetSuffixText(seer.PlayerId)); + break; + case CustomGameMode.Speedrun: + SelfSuffix.Append(SpeedrunManager.GetSuffixText(seer)); + break; + case CustomGameMode.HideAndSeek: + SelfSuffix.Append(HnSManager.GetSuffixText(seer, seer)); + break; + case CustomGameMode.CaptureTheFlag: + SelfSuffix.Append(CTFManager.GetSuffixText(seer, seer)); + break; + case CustomGameMode.NaturalDisasters: + SelfSuffix.Append(NaturalDisasters.SuffixText()); + break; + case CustomGameMode.RoomRush: + SelfSuffix.Append(RoomRush.GetSuffix(seer)); + break; + case CustomGameMode.AllInOne: + bool alive = seer.IsAlive(); + if (alive) SelfSuffix.Append(SoloKombatManager.GetDisplayHealth(seer) + "\n"); + if (alive) SelfSuffix.Append(MoveAndStop.GetSuffixText(seer) + "\n"); + SelfSuffix.Append(HotPotato.GetSuffixText(seer.PlayerId) + "\n"); + if (alive && !seer.Is(CustomRoles.Killer)) SelfSuffix.Append(string.Format(GetString("DamoclesTimeLeft"), SpeedrunManager.Timers[seer.PlayerId]) + "\n"); + SelfSuffix.Append(NaturalDisasters.SuffixText() + "\n"); + const StringSplitOptions splitFlags = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; + SelfSuffix.Append(RoomRush.GetSuffix(seer).Split('\n', splitFlags).Join(delimiter: " - ")); + break; + } + } + + string SeerRealName = seer.GetRealName(isForMeeting); + + if (!GameStates.IsLobby) + { + if ((CustomGameMode.FFA.IsActiveOrIntegrated() && FFAManager.FFATeamMode.GetBool()) || CustomGameMode.HotPotato.IsActiveOrIntegrated()) + SeerRealName = SeerRealName.ApplyNameColorData(seer, seer, isForMeeting); + + if (!isForMeeting && MeetingStates.FirstMeeting && Options.ChangeNameToRoleInfo.GetBool() && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.MoveAndStop and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.AllInOne) + { + CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(seer.PlayerId); + + if (team != null) + { + SeerRealName = ColorString( + team.RoleRevealScreenBackgroundColor == "*" || !ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color teamColor) + ? Color.yellow + : teamColor, + string.Format( + GetString("CustomTeamHelp"), + team.RoleRevealScreenTitle == "*" + ? team.TeamName + : team.RoleRevealScreenTitle, + team.RoleRevealScreenSubtitle == "*" + ? string.Empty + : team.RoleRevealScreenSubtitle)); + } + else if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) + { + if (GameStartTimeStamp + 40 > now) SeerRealName = HnSManager.GetRoleInfoText(seer); + } + else if (Options.ChangeNameToRoleInfo.GetBool() && !seer.IsModClient()) + { + bool showLongInfo = LongRoleDescriptions.TryGetValue(seer.PlayerId, out (string Text, int Duration, bool Long) description) && GameStartTimeStamp + description.Duration > now; + string mHelp = (!showLongInfo || description.Long) && CustomGameMode.Standard.IsActiveOrIntegrated() ? "\n" + GetString("MyRoleCommandHelp") : string.Empty; + + SeerRealName = seerTeam switch + { + Team.Impostor when seer.IsMadmate() => $"{GetString("YouAreMadmate")}\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", + Team.Impostor => $"\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", + Team.Crewmate => $"{GetString("YouAreCrewmate")}\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", + Team.Neutral => $"{GetString("YouAreNeutral")}\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", + _ => SeerRealName + }; + } + } + } + + // Combine seer's job title and SelfTaskText with seer's player name and SelfMark + string SelfRoleName = GameStates.IsLobby ? string.Empty : $"{seer.GetDisplayRoleName()}{SelfTaskText}"; + string SelfDeathReason = seer.KnowDeathReason(seer) && !GameStates.IsLobby ? $"\n『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(seer.PlayerId))}』" : string.Empty; + SelfName = $"{ColorString(GameStates.IsLobby ? Color.white : seer.GetRoleColor(), SeerRealName)}{SelfDeathReason}{SelfMark}"; + + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || GameStates.IsLobby) goto GameMode2; + + SelfName = seerRole switch + { + CustomRoles.Arsonist when seer.IsDouseDone() => $"{ColorString(seer.GetRoleColor(), GetString("EnterVentToWin"))}", + CustomRoles.Revolutionist when seer.IsDrawDone() => $">{ColorString(seer.GetRoleColor(), string.Format(GetString("EnterVentWinCountDown"), Revolutionist.RevolutionistCountdown.GetValueOrDefault(seer.PlayerId, 10)))}", + _ => SelfName + }; + + if (Pelican.IsEaten(seer.PlayerId)) SelfName = $"{ColorString(GetRoleColor(CustomRoles.Pelican), GetString("EatenByPelican"))}"; + + if (Deathpact.IsInActiveDeathpact(seer)) SelfName = Deathpact.GetDeathpactString(seer); + + // Devourer + if (Devourer.HideNameOfConsumedPlayer.GetBool() && Devourer.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Devourer { IsEnable: true } dv && dv.PlayerSkinsCosumed.Contains(seer.PlayerId)) && !CamouflageIsForMeeting) SelfName = GetString("DevouredName"); + + // Camouflage + if (Camouflage.IsCamouflage && !CamouflageIsForMeeting) SelfName = $"{SelfName}"; + + GameMode2: + + if (!GameStates.IsLobby) + { + if (NameNotifyManager.GetNameNotify(seer, out string name) && name.Length > 0) SelfName = name; + + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: + SoloKombatManager.GetNameNotify(seer, ref SelfName); + SelfName = $"{SelfTaskText}\r\n{SelfName}"; + break; + case CustomGameMode.FFA: + SelfName = $"{SelfTaskText}\r\n{SelfName}"; + break; + default: + SelfName = $"{SelfRoleName}\r\n{SelfName}"; + break; + } + + SelfName += SelfSuffix.ToString() == string.Empty ? string.Empty : $"\r\n{SelfSuffix}"; + if (!isForMeeting) SelfName += "\r\n"; + } + + seer.RpcSetNamePrivate(SelfName, force: NoCache); + + // Run the second loop only when necessary, such as when seer is dead + if (seer.Data.IsDead || !seer.IsAlive() || NoCache || CamouflageIsForMeeting || MushroomMixup || IsActive(SystemTypes.MushroomMixupSabotage) || ForceLoop || seerList.Length == 1 || targetList.Length == 1) + { + foreach (PlayerControl target in targetList) + { + if (target.PlayerId == seer.PlayerId) continue; + + if (target.Is(CustomRoles.Car) && !isForMeeting) + { + target.RpcSetNamePrivate(Car.Name, seer, NoCache); + continue; + } + + if ((IsActive(SystemTypes.MushroomMixupSabotage) || MushroomMixup) && target.IsAlive() && !seer.Is(CustomRoleTypes.Impostor) && Main.ResetCamPlayerList.Contains(seer.PlayerId)) + { + target.RpcSetNamePrivate("", force: NoCache); + } + else + { + TargetMark.Clear(); + + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || GameStates.IsLobby) goto BeforeEnd2; + + TargetMark.Append(Witch.GetSpelledMark(target.PlayerId, isForMeeting)); + if (isForMeeting) TargetMark.Append(Wasp.GetStungMark(target.PlayerId)); + + if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) + TargetMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); + + if (BallLightning.IsGhost(target)) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■")); + + TargetMark.Append(Snitch.GetWarningMark(seer, target)); + + if ((seer.Data.IsDead || Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId)) && Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId)) + TargetMark.Append($" ♥"); + + if (Randomizer.IsShielded(target)) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Randomizer), "✚")); + + switch (seerRole) + { + case CustomRoles.PlagueBearer: + if (PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId)) + { + TargetMark.Append($"●"); + PlagueBearer.SendRPC(seer, target); + } + + break; + case CustomRoles.Arsonist: + if (seer.IsDousedPlayer(target)) + TargetMark.Append($"▲"); + + else if (Arsonist.ArsonistTimer.TryGetValue(seer.PlayerId, out (PlayerControl Player, float Timer) ar_kvp) && ar_kvp.Player == target) TargetMark.Append($"△"); + + break; + case CustomRoles.Revolutionist: + if (seer.IsDrawPlayer(target)) TargetMark.Append($"●"); + + if (Revolutionist.RevolutionistTimer.TryGetValue(seer.PlayerId, out (PlayerControl PLAYER, float TIMER) ar_kvp1) && ar_kvp1.PLAYER == target) TargetMark.Append($"○"); + + break; + case CustomRoles.Farseer: + if (Farseer.FarseerTimer.TryGetValue(seer.PlayerId, out (PlayerControl PLAYER, float TIMER) ar_kvp2) && ar_kvp2.PLAYER == target) TargetMark.Append($"○"); + + break; + case CustomRoles.Analyst: + if ((Main.PlayerStates[seer.PlayerId].Role as Analyst).CurrentTarget.ID == target.PlayerId) TargetMark.Append($"○"); + + break; + case CustomRoles.Samurai: // Same as Analyst + if ((Main.PlayerStates[seer.PlayerId].Role as Samurai).Target.Id == target.PlayerId) TargetMark.Append($"○"); + + break; + case CustomRoles.Puppeteer when Puppeteer.PuppeteerList.ContainsValue(seer.PlayerId) && Puppeteer.PuppeteerList.ContainsKey(target.PlayerId): + TargetMark.Append($"◆"); + break; + } + + BeforeEnd2: + + bool shouldSeeTargetAddons = seer.PlayerId == target.PlayerId || new[] { seer, target }.All(x => x.Is(Team.Impostor)); + + string TargetRoleText = + (seer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || + (seer.Is(CustomRoles.Mimic) && target.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) || + (target.Is(CustomRoles.Gravestone) && target.Data.IsDead) || + (Main.LoversPlayers.TrueForAll(x => x.PlayerId == seer.PlayerId || x.PlayerId == target.PlayerId) && Main.LoversPlayers.Count == 2 && Lovers.LoverKnowRoles.GetBool()) || + (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || + (seer.IsMadmate() && target.Is(CustomRoleTypes.Impostor) && Options.MadmateKnowWhosImp.GetBool()) || + (seer.Is(CustomRoleTypes.Impostor) && target.IsMadmate() && Options.ImpKnowWhosMadmate.GetBool()) || + (seer.Is(CustomRoles.Crewpostor) && target.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool()) || + (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || + (seer.IsMadmate() && target.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) || + ((seer.Is(CustomRoles.Sidekick) || seer.Is(CustomRoles.Recruit) || seer.Is(CustomRoles.Jackal)) && (target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Recruit) || target.Is(CustomRoles.Jackal))) || + (target.Is(CustomRoles.Workaholic) && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || + (target.Is(CustomRoles.Doctor) && !target.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool()) || + (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) || + (Marshall.CanSeeMarshall(seer) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) || + (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) || + (CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.KnowRoles)) || + Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)) || + Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == target.PlayerId) || + Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato or CustomGameMode.Speedrun || + (CustomGameMode.HideAndSeek.IsActiveOrIntegrated() && HnSManager.IsRoleTextEnabled(seer, target)) || + (seer.IsRevealedPlayer(target) && !target.Is(CustomRoles.Trickster)) || + (seer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) || + target.Is(CustomRoles.GM) + ? $"{target.GetDisplayRoleName(seeTargetBetrayalAddons: shouldSeeTargetAddons)}{GetProgressText(target)}\r\n" + : string.Empty; + + if (CustomRoles.Altruist.RoleExist() && Main.DiedThisRound.Contains(seer.PlayerId)) TargetRoleText = string.Empty; + + if (Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush) TargetRoleText = string.Empty; + + if (!GameStates.IsLobby) + { + if (!seer.Data.IsDead && seer.IsRevealedPlayer(target) && target.Is(CustomRoles.Trickster)) + { + TargetRoleText = Farseer.RandomRole[seer.PlayerId]; + TargetRoleText += Farseer.GetTaskState(); + } + + if (CustomGameMode.SoloKombat.IsActiveOrIntegrated()) TargetRoleText = $"{GetProgressText(target)}\r\n"; + } + else + TargetRoleText = string.Empty; + + string TargetPlayerName = target.GetRealName(isForMeeting); + + if (GameStates.IsLobby) goto End; + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) goto BeforeEnd; + + if (GuesserIsForMeeting || isForMeeting || (seerRole == CustomRoles.Mafia && !seer.IsAlive() && Options.MafiaCanKillNum.GetInt() >= 1)) TargetPlayerName = $"{ColorString(GetRoleColor(seerRole), target.PlayerId.ToString())} {TargetPlayerName}"; + + switch (seerRole) + { + case CustomRoles.EvilTracker: + TargetMark.Append(EvilTracker.GetTargetMark(seer, target)); + if (isForMeeting && EvilTracker.IsTrackTarget(seer, target) && EvilTracker.CanSeeLastRoomInMeeting) TargetRoleText = $"{EvilTracker.GetArrowAndLastRoom(seer, target)}\r\n"; + + break; + case CustomRoles.Scout: + TargetMark.Append(Scout.GetTargetMark(seer, target)); + if (isForMeeting && Scout.IsTrackTarget(seer, target) && Scout.CanSeeLastRoomInMeeting) TargetRoleText = $"{Scout.GetArrowAndLastRoom(seer, target)}\r\n"; + + break; + case CustomRoles.Psychic when seer.IsAlive() && Psychic.IsRedForPsy(target, seer) && isForMeeting: + TargetPlayerName = ColorString(GetRoleColor(CustomRoles.Impostor), TargetPlayerName); + break; + case CustomRoles.HeadHunter when (Main.PlayerStates[seer.PlayerId].Role as HeadHunter).Targets.Contains(target.PlayerId) && seer.IsAlive(): + case CustomRoles.BountyHunter when (Main.PlayerStates[seer.PlayerId].Role as BountyHunter).GetTarget(seer) == target.PlayerId && seer.IsAlive(): + TargetPlayerName = $"{TargetPlayerName}"; + break; + case CustomRoles.Lookout when seer.IsAlive() && target.IsAlive() && !isForMeeting: + TargetPlayerName = $"{ColorString(GetRoleColor(CustomRoles.Lookout), $" {target.PlayerId}")} {TargetPlayerName}"; + break; + } + + BeforeEnd: + + TargetPlayerName = TargetPlayerName.ApplyNameColorData(seer, target, isForMeeting); + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) goto End; + + if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished) + TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); + + if (Marshall.CanSeeMarshall(seer) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) + TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Marshall), "★")); + + TargetMark.Append(Executioner.TargetMark(seer, target)); + TargetMark.Append(Gamer.TargetMark(seer, target)); + TargetMark.Append(Medic.GetMark(seer, target)); + TargetMark.Append(Gaslighter.GetMark(seer, target, isForMeeting)); + TargetMark.Append(Totocalcio.TargetMark(seer, target)); + TargetMark.Append(Romantic.TargetMark(seer, target)); + TargetMark.Append(Lawyer.LawyerMark(seer, target)); + TargetMark.Append(Deathpact.GetDeathpactMark(seer, target)); + TargetMark.Append(PlagueDoctor.GetMarkOthers(seer, target)); + + End: + + TargetSuffix.Clear(); + + if (!GameStates.IsLobby) + { + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: + TargetSuffix.Append(SoloKombatManager.GetDisplayHealth(target)); + break; + case CustomGameMode.HideAndSeek: + TargetSuffix.Append(HnSManager.GetSuffixText(seer, target)); + break; + case CustomGameMode.CaptureTheFlag: + TargetSuffix.Append(CTFManager.GetSuffixText(seer, target)); + break; + } + + Main.PlayerStates.Values.Do(x => TargetSuffix.Append(x.Role.GetSuffix(seer, target, meeting: isForMeeting))); + + if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.FFA or CustomGameMode.Speedrun) TargetSuffix.Append(GetString("DiedR1Warning")); + + TargetSuffix.Append(AFKDetector.GetSuffix(seer, target)); + } + + var TargetDeathReason = string.Empty; + if (seer.KnowDeathReason(target) && !GameStates.IsLobby) TargetDeathReason = $"\n({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))})"; + + // Devourer + if (Devourer.HideNameOfConsumedPlayer.GetBool() && !GameStates.IsLobby && Devourer.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Devourer { IsEnable: true } dv && dv.PlayerSkinsCosumed.Contains(seer.PlayerId)) && !CamouflageIsForMeeting) TargetPlayerName = GetString("DevouredName"); + + // Camouflage + if (Camouflage.IsCamouflage && !CamouflageIsForMeeting) TargetPlayerName = $"{TargetPlayerName}"; + + var TargetName = $"{TargetRoleText}{TargetPlayerName}{TargetDeathReason}{TargetMark}"; + TargetName += GameStates.IsLobby || TargetSuffix.ToString() == string.Empty ? string.Empty : $"\r\n{TargetSuffix}"; + + target.RpcSetNamePrivate(TargetName, seer, NoCache); + } + } + } + } + catch (Exception ex) + { + if (LastNotifyRolesErrorTS != now) + { + Logger.Error($"Error for {seer.GetNameWithRole()}:", "NR"); + ThrowException(ex); + LastNotifyRolesErrorTS = now; + } + else + Logger.Error($"Error for {seer.GetNameWithRole()}: {ex}", "NR"); + } + } + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + string seers = seerList.Length == apc.Length ? "Everyone" : string.Join(", ", seerList.Select(x => x.GetRealName())); + string targets = targetList.Length == apc.Length ? "Everyone" : string.Join(", ", targetList.Select(x => x.GetRealName())); + if (seers.Length == 0) seers = "\u2205"; + + if (targets.Length == 0) targets = "\u2205"; + + Logger.Info($" Seers: {seers} ---- Targets: {targets}", "NR"); + } + + public static void MarkEveryoneDirtySettings() + { + PlayerGameOptionsSender.SetDirtyToAll(); + } + + public static void MarkEveryoneDirtySettingsV2() + { + PlayerGameOptionsSender.SetDirtyToAllV2(); + } + + public static void MarkEveryoneDirtySettingsV3() + { + PlayerGameOptionsSender.SetDirtyToAllV3(); + } + + public static void MarkEveryoneDirtySettingsV4() + { + PlayerGameOptionsSender.SetDirtyToAllV4(); + } + + public static void SyncAllSettings() + { + PlayerGameOptionsSender.SetDirtyToAll(); + Main.Instance.StartCoroutine(GameOptionsSender.SendAllGameOptionsAsync()); + } + + public static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit) + { + Camouflage.SetPetForOutfitIfNecessary(newOutfit); + + var sender = CustomRpcSender.Create($"Utils.RpcChangeSkin({pc.Data.PlayerName})"); + + pc.SetName(newOutfit.PlayerName); + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetName) + .Write(pc.Data.NetId) + .Write(newOutfit.PlayerName) + .EndRpc(); + + Main.AllPlayerNames[pc.PlayerId] = newOutfit.PlayerName; + + pc.SetColor(newOutfit.ColorId); + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) + .Write(pc.Data.NetId) + .Write((byte)newOutfit.ColorId) + .EndRpc(); + + pc.SetHat(newOutfit.HatId, newOutfit.ColorId); + pc.Data.DefaultOutfit.HatSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetHatStr) + .Write(newOutfit.HatId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); + + pc.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + pc.Data.DefaultOutfit.SkinSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetSkinStr) + .Write(newOutfit.SkinId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); + + pc.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + pc.Data.DefaultOutfit.VisorSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetVisorStr) + .Write(newOutfit.VisorId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); + + pc.SetPet(newOutfit.PetId); + pc.Data.DefaultOutfit.PetSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) + .Write(newOutfit.PetId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + + pc.SetNamePlate(newOutfit.NamePlateId); + pc.Data.DefaultOutfit.NamePlateSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetNamePlateStr) + .Write(newOutfit.NamePlateId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) + .EndRpc(); + + sender.SendMessage(); + } + + public static string GetGameStateData(bool clairvoyant = false) + { + Dictionary nums = Enum.GetValues().ToDictionary(x => x, _ => 0); + + if (CustomRoles.Romantic.RoleExist(true)) nums[Options.GameStateInfo.RomanticState] = 1; + + if (Romantic.HasPickedPartner) nums[Options.GameStateInfo.RomanticState] = 2; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.IsMadmate()) + nums[Options.GameStateInfo.MadmateCount]++; + else if (pc.IsNeutralKiller()) + nums[Options.GameStateInfo.NKCount]++; + else if (pc.IsCrewmate()) + nums[Options.GameStateInfo.CrewCount]++; + else if (pc.Is(Team.Impostor)) + nums[Options.GameStateInfo.ImpCount]++; + else if (pc.Is(Team.Neutral)) nums[Options.GameStateInfo.NNKCount]++; + + if (pc.GetCustomSubRoles().Any(x => x.IsConverted())) nums[Options.GameStateInfo.ConvertedCount]++; + + if (Main.LoversPlayers.Exists(x => x.PlayerId == pc.PlayerId)) nums[Options.GameStateInfo.LoversState]++; + + if (pc.Is(CustomRoles.Romantic)) nums[Options.GameStateInfo.RomanticState] *= 3; + + if (Romantic.PartnerId == pc.PlayerId) nums[Options.GameStateInfo.RomanticState] *= 4; + } + + // All possible results of RomanticState from the above code: + // 0: Romantic doesn't exist + // 1: Romantic exists but hasn't picked a partner (and is dead) + // 2: Romantic exists and has picked a partner (but both of them are dead) + // 3: Romantic exists, is alive, but hasn't picked a partner + // 6: Romantic exists, has picked a partner who is dead, but Romantic is alive + // 8: Romantic exists, has picked a partner who is alive, but Romantic is dead + // 24: Romantic exists, has picked a partner who is alive, and Romantic is alive + + StringBuilder sb = new(); + Dictionary checkDict = clairvoyant ? Clairvoyant.Settings : Options.GameStateSettings; + nums[Options.GameStateInfo.Tasks] = GameData.Instance.CompletedTasks; + Dictionary states = nums.ToDictionary(x => x.Key, x => x.Key == Options.GameStateInfo.RomanticState ? GetString($"GSRomanticState.{x.Value}") : (object)x.Value); + states.DoIf(x => checkDict[x.Key].GetBool(), x => sb.AppendLine(string.Format(GetString($"GSInfo.{x.Key}"), x.Value, GameData.Instance.TotalTasks))); + return $"<#ffffff>{sb.ToString().TrimEnd()}"; + } + + public static void AddAbilityCD(CustomRoles role, byte playerId, bool includeDuration = true) + { + if (role.UsesPetInsteadOfKill()) + { + var kcd = (int)Math.Round(Main.AllPlayerKillCooldown.TryGetValue(playerId, out float KCD) ? KCD : Options.DefaultKillCooldown); + Main.AbilityCD[playerId] = (TimeStamp, kcd); + SendRPC(CustomRPC.SyncAbilityCD, 1, playerId, kcd); + return; + } + + int CD = role switch + { + CustomRoles.Mole => Mole.CD.GetInt(), + CustomRoles.Doormaster => Doormaster.VentCooldown.GetInt(), + CustomRoles.Tether => Tether.VentCooldown.GetInt(), + CustomRoles.Mayor when Mayor.MayorHasPortableButton.GetBool() => (int)Math.Round(Options.DefaultKillCooldown), + CustomRoles.Paranoia => (int)Math.Round(Options.DefaultKillCooldown), + CustomRoles.Grenadier => Options.GrenadierSkillCooldown.GetInt() + (includeDuration ? Options.GrenadierSkillDuration.GetInt() : 0), + CustomRoles.Lighter => Options.LighterSkillCooldown.GetInt() + (includeDuration ? Options.LighterSkillDuration.GetInt() : 0), + CustomRoles.SecurityGuard => Options.SecurityGuardSkillCooldown.GetInt() + (includeDuration ? Options.SecurityGuardSkillDuration.GetInt() : 0), + CustomRoles.TimeMaster => Options.TimeMasterSkillCooldown.GetInt() + (includeDuration ? Options.TimeMasterSkillDuration.GetInt() : 0), + CustomRoles.Veteran => Options.VeteranSkillCooldown.GetInt() + (includeDuration ? Options.VeteranSkillDuration.GetInt() : 0), + CustomRoles.Rhapsode => Rhapsode.AbilityCooldown.GetInt() + (includeDuration ? Rhapsode.AbilityDuration.GetInt() : 0), + CustomRoles.Whisperer => Whisperer.Cooldown.GetInt() + (includeDuration ? Whisperer.Duration.GetInt() : 0), + CustomRoles.Perceiver => Perceiver.CD.GetInt(), + CustomRoles.Convener => Convener.CD.GetInt(), + CustomRoles.DovesOfNeace => Options.DovesOfNeaceCooldown.GetInt(), + CustomRoles.Alchemist => Alchemist.VentCooldown.GetInt(), + CustomRoles.NiceHacker => playerId.IsPlayerModClient() ? -1 : NiceHacker.AbilityCD.GetInt(), + CustomRoles.CameraMan => CameraMan.VentCooldown.GetInt(), + CustomRoles.Tornado => Tornado.TornadoCooldown.GetInt(), + CustomRoles.Sentinel => Sentinel.PatrolCooldown.GetInt(), + CustomRoles.Druid => Druid.VentCooldown.GetInt(), + CustomRoles.Catcher => Catcher.AbilityCooldown.GetInt(), + CustomRoles.Sentry => Crewmate.Sentry.ShowInfoCooldown.GetInt(), + CustomRoles.ToiletMaster => ToiletMaster.AbilityCooldown.GetInt(), + CustomRoles.Sniper => Options.DefaultShapeshiftCooldown.GetInt(), + CustomRoles.Assassin => Assassin.AssassinateCooldownOpt.GetInt(), + CustomRoles.Undertaker => Undertaker.UndertakerAssassinateCooldown.GetInt(), + CustomRoles.Bomber => Bomber.BombCooldown.GetInt(), + CustomRoles.Nuker => Bomber.NukeCooldown.GetInt(), + CustomRoles.Sapper => Sapper.ShapeshiftCooldown.GetInt(), + CustomRoles.Miner => Options.MinerSSCD.GetInt(), + CustomRoles.Escapee => Options.EscapeeSSCD.GetInt(), + CustomRoles.QuickShooter => QuickShooter.ShapeshiftCooldown.GetInt(), + CustomRoles.Disperser => Disperser.DisperserShapeshiftCooldown.GetInt(), + CustomRoles.Twister => Twister.ShapeshiftCooldown.GetInt(), + CustomRoles.Abyssbringer => Abyssbringer.BlackHolePlaceCooldown.GetInt(), + CustomRoles.Warlock => Warlock.IsCursed ? -1 : Warlock.ShapeshiftCooldown.GetInt(), + CustomRoles.Swiftclaw => Swiftclaw.DashCD.GetInt() + (includeDuration ? Swiftclaw.DashDuration.GetInt() : 0), + CustomRoles.Hypnotist => Hypnotist.AbilityCooldown.GetInt() + (includeDuration ? Hypnotist.AbilityDuration.GetInt() : 0), + CustomRoles.Parasite => (int)Parasite.SSCD + (includeDuration ? (int)Parasite.SSDur : 0), + CustomRoles.Tiger => Tiger.EnrageCooldown.GetInt() + (includeDuration ? Tiger.EnrageDuration.GetInt() : 0), + CustomRoles.Nonplus => Nonplus.BlindCooldown.GetInt() + (includeDuration ? Nonplus.BlindDuration.GetInt() : 0), + CustomRoles.Cherokious => Cherokious.KillCooldown.GetInt(), + CustomRoles.Shifter => Shifter.KillCooldown.GetInt(), + CustomRoles.NoteKiller => NoteKiller.AbilityCooldown.GetInt(), + CustomRoles.Weatherman => Weatherman.AbilityCooldown.GetInt(), + _ => -1 + }; + + if (CD == -1) return; + + if (Main.PlayerStates[playerId].SubRoles.Contains(CustomRoles.Energetic)) CD = (int)Math.Round(CD * 0.75f); + + Main.AbilityCD[playerId] = (TimeStamp, CD); + SendRPC(CustomRPC.SyncAbilityCD, 1, playerId, CD); + + if (Options.UseUnshiftTrigger.GetBool() && role.SimpleAbilityTrigger() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()) && !role.AlwaysUsesUnshift()) GetPlayerById(playerId)?.RpcResetAbilityCooldown(); + } + + public static (RoleTypes RoleType, CustomRoles CustomRole) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) + { + if (targetId == byte.MaxValue) targetId = seerId; + + return StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seerId, targetId)]; + } + + public static void AfterMeetingTasks() + { + bool loversChat = Lovers.PrivateChat.GetBool() && Main.LoversPlayers.TrueForAll(x => x.IsAlive()); + + if (loversChat && Lovers.PrivateChatForLoversOnly.GetBool()) + Main.LoversPlayers.ForEach(x => x.SetChatVisible()); + else if (!Lovers.IsChatActivated && loversChat && !GameStates.IsEnded && CustomGameMode.Standard.IsActiveOrIntegrated()) + { + LateTask.New(SetChatVisibleForAll, 0.5f, log: false); + Lovers.IsChatActivated = true; + return; + } + + if (loversChat) GameEndChecker.Prefix(); + + Lovers.IsChatActivated = false; + AFKDetector.NumAFK = 0; + AFKDetector.PlayerData.Clear(); + + Camouflage.CheckCamouflage(); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (pc.IsAlive()) + { + pc.AddKillTimerToDict(); + + if (pc.Is(CustomRoles.Truant)) + { + float beforeSpeed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.MarkDirtySettings(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[pc.PlayerId] = beforeSpeed; + pc.MarkDirtySettings(); + }, Options.TruantWaitingTime.GetFloat(), $"Truant Waiting: {pc.GetNameWithRole()}"); + } + + if (Options.UsePets.GetBool()) pc.AddAbilityCD(false); + + pc.CheckAndSetUnshiftState(false); + + AFKDetector.RecordPosition(pc); + + Main.PlayerStates[pc.PlayerId].Role.AfterMeetingTasks(); + } + else + { + TaskState taskState = pc.GetTaskState(); + if (pc.IsCrewmate() && !taskState.IsTaskFinished && taskState.HasTasks) pc.Notify(GetString("DoYourTasksPlease"), 10f); + + GhostRolesManager.NotifyAboutGhostRole(pc); + } + + if (pc.Is(CustomRoles.Specter) || pc.Is(CustomRoles.Haunter)) pc.RpcResetAbilityCooldown(); + + Main.CheckShapeshift[pc.PlayerId] = false; + } + + LateTask.New(() => Main.ProcessShapeshifts = true, 1f, log: false); + + CopyCat.ResetRoles(); + + if (Options.DiseasedCDReset.GetBool()) + { + Main.KilledDiseased.SetAllValues(0); + Main.KilledDiseased.Keys.Select(x => x.GetPlayer()).Do(x => x?.ResetKillCooldown()); + Main.KilledDiseased.Clear(); + } + + if (Options.AntidoteCDReset.GetBool()) + { + Main.KilledAntidote.SetAllValues(0); + Main.KilledAntidote.Keys.Select(x => x.GetPlayer()).Do(x => x?.ResetKillCooldown()); + Main.KilledAntidote.Clear(); + } + + Damocles.AfterMeetingTasks(); + Stressed.AfterMeetingTasks(); + Circumvent.AfterMeetingTasks(); + Deadlined.AfterMeetingTasks(); + + if (Options.AirshipVariableElectrical.GetBool()) AirshipElectricalDoors.Initialize(); + + Main.DontCancelVoteList.Clear(); + + DoorsReset.ResetDoors(); + RoleBlockManager.Reset(); + PhantomRolePatch.AfterMeeting(); + + if ((MapNames)Main.NormalOptions.MapId == MapNames.Airship && AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.Is(CustomRoles.GM)) LateTask.New(() => { PlayerControl.LocalPlayer.NetTransform.SnapTo(new(15.5f, 0.0f), (ushort)(PlayerControl.LocalPlayer.NetTransform.lastSequenceId + 8)); }, 11f, "GM Auto-TP Failsafe"); // TP to Main Hall + + LateTask.New(() => Asthmatic.RunChecks = true, 2f, log: false); + } + + public static void AfterPlayerDeathTasks(PlayerControl target, bool onMeeting = false, bool disconnect = false) + { + try + { + if (!onMeeting) Main.DiedThisRound.Add(target.PlayerId); + + // Record the first death + if (Main.FirstDied == string.Empty) Main.FirstDied = target.FriendCode; + + switch (target.GetCustomRole()) + { + case CustomRoles.Curser: + ((Curser)Main.PlayerStates[target.PlayerId].Role).OnDeath(); + break; + case CustomRoles.Camouflager when Camouflager.IsActive: + Camouflager.IsDead(); + break; + case CustomRoles.Terrorist when !disconnect: + Logger.Info(target?.Data?.PlayerName + " Terrorist died", "MurderPlayer"); + CheckTerroristWin(target?.Data); + break; + case CustomRoles.Executioner: + if (Executioner.Target.ContainsKey(target.PlayerId)) + { + Executioner.Target.Remove(target.PlayerId); + Executioner.SendRPC(target.PlayerId); + } + + break; + case CustomRoles.Lawyer: + if (Lawyer.Target.ContainsKey(target.PlayerId)) + { + Lawyer.Target.Remove(target.PlayerId); + Lawyer.SendRPC(target.PlayerId); + } + + break; + case CustomRoles.PlagueDoctor when !disconnect: + PlagueDoctor.OnPDdeath(target.GetRealKiller(), target); + break; + case CustomRoles.CyberStar when !disconnect: + if (GameStates.IsMeeting) + { + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if ((!Options.ImpKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsImpostor()) + || (!Options.NeutralKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsNeutral())) + continue; + + SendMessage(string.Format(GetString("CyberStarDead"), target.GetRealName()), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.CyberStar), GetString("CyberStarNewsTitle"))); + } + } + else + { + if (!Main.CyberStarDead.Contains(target.PlayerId)) Main.CyberStarDead.Add(target.PlayerId); + } + + break; + case CustomRoles.Pelican: + Pelican.OnPelicanDied(target.PlayerId); + break; + case CustomRoles.Devourer: + Devourer.OnDevourerDied(target.PlayerId); + break; + case CustomRoles.Markseeker when !disconnect: + Markseeker.OnDeath(target); + break; + case CustomRoles.Medic: + Medic.IsDead(target); + break; + } + + if (target == null) return; + + if (!disconnect) Randomizer.OnAnyoneDeath(target); + if (Executioner.Target.ContainsValue(target.PlayerId)) Executioner.ChangeRoleByTarget(target); + if (Lawyer.Target.ContainsValue(target.PlayerId)) Lawyer.ChangeRoleByTarget(target); + if (!disconnect && target.Is(CustomRoles.Stained)) Stained.OnDeath(target, target.GetRealKiller()); + if (!disconnect && target.Is(CustomRoles.Spurt)) Spurt.DeathTask(target); + + Postman.CheckAndResetTargets(target, true); + Hitman.CheckAndResetTargets(); + + if (!disconnect && !onMeeting) + { + Hacker.AddDeadBody(target); + Mortician.OnPlayerDead(target); + Bloodhound.OnPlayerDead(target); + Tracefinder.OnPlayerDead(target); + Vulture.OnPlayerDead(target); + Scout.OnPlayerDeath(target); + Amnesiac.OnAnyoneDeath(target); + Dad.OnAnyoneDeath(target); + Occultist.OnAnyoneDied(target); + Crewmate.Sentry.OnAnyoneMurder(target); + Soothsayer.OnAnyoneDeath(target.GetRealKiller(), target); + + TargetDies(target.GetRealKiller(), target); + } + + Adventurer.OnAnyoneDead(target); + Whisperer.OnAnyoneDied(target); + + if (QuizMaster.On) QuizMaster.Data.NumPlayersDeadThisRound++; + + FixedUpdatePatch.LoversSuicide(target.PlayerId, guess: onMeeting); + + if (!target.HasGhostRole()) + { + Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + target.MarkDirtySettings(); + } + } + catch (Exception ex) + { + Logger.CurrentMethod(); + Logger.Exception(ex, "AfterPlayerDeathTasks"); + } + } + + public static void CountAlivePlayers(bool sendLog = false) + { + int AliveImpostorCount = Main.AllAlivePlayerControls.Count(pc => pc.Is(CustomRoleTypes.Impostor)); + + if (Main.AliveImpostorCount != AliveImpostorCount) + { + Logger.Info("Number of living Impostors: " + AliveImpostorCount, "CountAliveImpostors"); + Main.AliveImpostorCount = AliveImpostorCount; + LastImpostor.SetSubRole(); + } + + if (sendLog) + { + StringBuilder sb = new(100); + + if (CustomGameMode.Standard.IsActiveOrIntegrated()) + { + foreach (CountTypes countTypes in Enum.GetValues()) + { + int playersCount = PlayersCount(countTypes); + if (playersCount == 0) continue; + + sb.Append($"{countTypes}: {AlivePlayersCount(countTypes)}/{playersCount}, "); + } + } + + sb.Append($"All: {AllAlivePlayersCount}/{AllPlayersCount}"); + Logger.Info(sb.ToString(), "CountAlivePlayers"); + } + + if (AmongUsClient.Instance.AmHost && !Main.HasJustStarted) GameEndChecker.Prefix(); + } + + public static string GetVoteName(byte num) + { + PlayerControl player = GetPlayerById(num); + + return num switch + { + < 15 when player != null => player.GetNameWithRole().RemoveHtmlTags(), + 253 => "Skip", + 254 => "None", + 255 => "Dead", + _ => "invalid" + }; + } + + public static string PadRightV2(this object text, int num) + { + var t = text.ToString(); + if (t == null) return string.Empty; + + int bc = t.Sum(c => Encoding.GetEncoding("UTF-8").GetByteCount(c.ToString()) == 1 ? 1 : 2); + + return t.PadRight(Mathf.Max(num - (bc - t.Length), 0)); + } + + public static void DumpLog(bool open = true) + { + var t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"); + var f = $"{Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)}/EHR_Logs/{t}"; + if (!Directory.Exists(f)) Directory.CreateDirectory(f); + + var filename = $"{f}EHR-v{Main.PluginVersion}-LOG"; + FileInfo[] files = [new($"{Environment.CurrentDirectory}/BepInEx/LogOutput.log"), new($"{Environment.CurrentDirectory}/BepInEx/log.html")]; + files.Do(x => x.CopyTo($"{filename}{x.Extension}")); + + if (!open) return; + + if (PlayerControl.LocalPlayer != null) HudManager.Instance?.Chat?.AddChat(PlayerControl.LocalPlayer, string.Format(GetString("Message.DumpfileSaved"), "EHR" + filename.Split("EHR")[1])); + + ProcessStartInfo psi = new("Explorer.exe") + { Arguments = "/e,/select," + filename.Replace("/", "\\") }; + + Process.Start(psi); + } + + public static (int Doused, int All) GetDousedPlayerCount(byte playerId) + { + int doused = 0, all = 0; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.PlayerId == playerId) continue; + + all++; + + if (Arsonist.IsDoused.TryGetValue((playerId, pc.PlayerId), out bool isDoused) && isDoused) + doused++; + } + + return (doused, all); + } + + public static (int Drawn, int All) GetDrawPlayerCount(byte playerId, out List winnerList) + { + var draw = 0; + int all = Options.RevolutionistDrawCount.GetInt(); + int max = Main.AllAlivePlayerControls.Length; + if (!Main.PlayerStates[playerId].IsDead) max--; + + winnerList = []; + if (all > max) all = max; + + foreach (PlayerControl pc in Main.AllPlayerControls.Where(pc => Revolutionist.IsDraw.TryGetValue((playerId, pc.PlayerId), out bool isDraw) && isDraw).ToArray()) + { + winnerList.Add(pc); + draw++; + } + + return (draw, all); + } + + public static string SummaryTexts(byte id, bool disableColor = true, bool check = false) + { + try + { + string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); + + if (id == PlayerControl.LocalPlayer.PlayerId) + name = DataManager.player.Customization.Name; + else + name = GetPlayerById(id)?.Data.PlayerName ?? name; + + TaskState taskState = Main.PlayerStates[id].TaskState; + string TaskCount; + + if (taskState.HasTasks) + { + NetworkedPlayerInfo info = GetPlayerInfoById(id); + Color TaskCompleteColor = HasTasks(info) ? Color.green : Color.cyan; Color NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white; - if (Workhorse.IsThisRole(playerId)) NonCompleteColor = Workhorse.RoleColor; + if (Workhorse.IsThisRole(id)) NonCompleteColor = Workhorse.RoleColor; Color NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; - if (Main.PlayerStates.TryGetValue(playerId, out PlayerState ps)) + if (Main.PlayerStates.TryGetValue(id, out PlayerState ps)) { NormalColor = ps.MainRole switch { @@ -1144,2266 +3125,283 @@ other: ∟ ⌠ ⌡ ╬ ╨ ▓ ▒ ░ « » █ ▄ ▌▀▐│ ┤ ╡ ╢ }; } - Color TextColor = comms ? Color.gray : NormalColor; - string Completed = comms ? "?" : $"{taskState.CompletedTasksCount}"; - return ColorString(TextColor, $" {(moveAndStop ? "" : string.Empty)}{Completed}/{taskState.AllTasksCount}{(moveAndStop ? $" <#ffffff>({MoveAndStop.GetLivesRemaining(playerId)} \u2665)" : string.Empty)}"); + Color TextColor = NormalColor; + var Completed = $"{taskState.CompletedTasksCount}"; + TaskCount = ColorString(TextColor, $" ({Completed}/{taskState.AllTasksCount})"); } - catch - { - return string.Empty; - } - } + else + TaskCount = string.Empty; - public static void ShowActiveSettingsHelp(byte PlayerId = byte.MaxValue) - { - SendMessage(GetString("CurrentActiveSettingsHelp") + ":", PlayerId); - - if (Options.DisableDevices.GetBool()) SendMessage(GetString("DisableDevicesInfo"), PlayerId); - if (Options.SyncButtonMode.GetBool()) SendMessage(GetString("SyncButtonModeInfo"), PlayerId); - if (Options.SabotageTimeControl.GetBool()) SendMessage(GetString("SabotageTimeControlInfo"), PlayerId); - if (Options.RandomMapsMode.GetBool()) SendMessage(GetString("RandomMapsModeInfo"), PlayerId); - - if (Main.GM.Value) SendMessage(GetRoleName(CustomRoles.GM) + GetString("GMInfoLong"), PlayerId); - - foreach (CustomRoles role in Enum.GetValues().Where(role => role.IsEnable() && !role.IsVanilla())) - SendMessage(GetRoleName(role) + GetRoleMode(role) + GetString($"{role}InfoLong"), PlayerId); - - if (Options.NoGameEnd.GetBool()) SendMessage(GetString("NoGameEndInfo"), PlayerId); - } - - /// - /// Gets all players within a specified radius from the specified location - /// - /// The radius - /// The location which the radius is counted from - /// A list containing all PlayerControls within the specified range from the specified location - public static IEnumerable GetPlayersInRadius(float radius, Vector2 from) - { - return from tg in Main.AllAlivePlayerControls let dis = Vector2.Distance(@from, tg.Pos()) where !Pelican.IsEaten(tg.PlayerId) && !tg.inVent where dis <= radius select tg; - } - - public static void ShowActiveSettings(byte PlayerId = byte.MaxValue) - { - if (Options.HideGameSettings.GetBool() && PlayerId != byte.MaxValue) - { - SendMessage(GetString("Message.HideGameSettings"), PlayerId); - return; - } - - if (Options.DIYGameSettings.GetBool()) - { - SendMessage(GetString("Message.NowOverrideText"), PlayerId); - return; - } - - StringBuilder sb = new(); - sb.Append($" \u2605 {GetString("TabGroup.SystemSettings")}"); - Options.GroupedOptions[TabGroup.SystemSettings].Do(CheckAndAppendOptionString); - sb.Append($"\n\n \u2605 {GetString("TabGroup.GameSettings")}"); - Options.GroupedOptions[TabGroup.GameSettings].Do(CheckAndAppendOptionString); - - SendMessage(sb.ToString().RemoveHtmlTags(), PlayerId); - return; - - void CheckAndAppendOptionString(OptionItem item) - { - if (item.GetBool() && item.Parent == null && !item.IsCurrentlyHidden()) - { - sb.Append($"\n{item.GetName(true)}: {item.GetString()}"); - } - } - } - - public static void ShowAllActiveSettings(byte PlayerId = byte.MaxValue) - { - if (Options.HideGameSettings.GetBool() && PlayerId != byte.MaxValue) - { - SendMessage(GetString("Message.HideGameSettings"), PlayerId); - return; - } - - if (Options.DIYGameSettings.GetBool()) - { - SendMessage(GetString("Message.NowOverrideText"), PlayerId); - return; - } - - StringBuilder sb = new(); - - sb.Append(GetString("Settings")).Append(':'); - - foreach (KeyValuePair role in Options.CustomRoleCounts) - { - if (!role.Key.IsEnable()) continue; - - string mode; - - try - { - mode = !role.Key.IsAdditionRole() - ? GetString($"Rate{role.Key.GetMode()}") - : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role.Key].GetInt()}"); - } - catch (KeyNotFoundException) - { - continue; - } - - mode = mode.Replace("color=", string.Empty); - - sb.Append($"\n【{GetRoleName(role.Key)}:{mode} ×{role.Key.GetCount()}】\n"); - ShowChildrenSettings(Options.CustomRoleSpawnChances[role.Key], ref sb); - } - - foreach (OptionItem opt in OptionItem.AllOptions) - { - if (opt.GetBool() && opt.Parent == null && opt.Id is >= 80000 and < 640000 && !opt.IsCurrentlyHidden()) - { - if (opt.Name is "KillFlashDuration" or "RoleAssigningAlgorithm") - sb.Append($"\n【{opt.GetName(true)}: {opt.GetString()}】\n"); - else - sb.Append($"\n【{opt.GetName(true)}】\n"); - - ShowChildrenSettings(opt, ref sb); - } - } - - SendMessage(sb.ToString().RemoveHtmlTags(), PlayerId); - } - - public static void CopyCurrentSettings() - { - StringBuilder sb = new(); - - if (Options.HideGameSettings.GetBool() && !AmongUsClient.Instance.AmHost) - { - ClipboardHelper.PutClipboardString(GetString("Message.HideGameSettings")); - return; - } - - sb.Append($"━━━━━━━━━━━━【{GetString("Roles")}】━━━━━━━━━━━━"); - - foreach (KeyValuePair role in Options.CustomRoleCounts) - { - if (!role.Key.IsEnable()) continue; - - string mode; - - try - { - mode = !role.Key.IsAdditionRole() - ? GetString($"Rate{role.Key.GetMode()}") - : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role.Key].GetInt()}"); - } - catch (KeyNotFoundException) - { - continue; - } - - mode = mode.Replace("color=", string.Empty); - - sb.Append($"\n【{GetRoleName(role.Key)}:{mode} ×{role.Key.GetCount()}】\n"); - ShowChildrenSettings(Options.CustomRoleSpawnChances[role.Key], ref sb); - } - - sb.Append($"━━━━━━━━━━━━【{GetString("Settings")}】━━━━━━━━━━━━"); - - foreach (OptionItem opt in OptionItem.AllOptions.Where(x => x.GetBool() && x.Parent == null && x.Id is >= 80000 and < 640000 && !x.IsCurrentlyHidden())) - { - if (opt.Name == "KillFlashDuration") - sb.Append($"\n【{opt.GetName(true)}: {opt.GetString()}】\n"); - else - sb.Append($"\n【{opt.GetName(true)}】\n"); - - ShowChildrenSettings(opt, ref sb); - } - - sb.Append("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"); - ClipboardHelper.PutClipboardString(sb.ToString().RemoveHtmlTags()); - } - - public static void ShowActiveRoles(byte PlayerId = byte.MaxValue) - { - if (Options.HideGameSettings.GetBool() && PlayerId != byte.MaxValue) - { - SendMessage(GetString("Message.HideGameSettings"), PlayerId); - return; - } - - StringBuilder sb = new(); - sb.Append($"\n{GetRoleName(CustomRoles.GM)}: {(Main.GM.Value ? GetString("RoleRate") : GetString("RoleOff"))}"); - - StringBuilder impsb = new(); - StringBuilder neutralsb = new(); - StringBuilder crewsb = new(); - StringBuilder addonsb = new(); - StringBuilder ghostsb = new(); - - foreach (CustomRoles role in CustomGameMode.HideAndSeek.IsActiveOrIntegrated() ? HnSManager.AllHnSRoles : Enum.GetValues().Except(HnSManager.AllHnSRoles)) - { - string mode; - - try - { - mode = !role.IsAdditionRole() || role.IsGhostRole() - ? GetString($"Rate{role.GetMode()}") - : GetString($"Rate{Options.CustomAdtRoleSpawnRate[role].GetInt()}"); - } - catch (KeyNotFoundException) - { - continue; - } - - mode = mode.Replace("color=", string.Empty); - - if (role.IsEnable()) - { - var roleDisplay = $"\n{ColorString(GetRoleColor(role).ShadeColor(0.25f), GetString(role.ToString()))}: {mode} x{role.GetCount()}"; - - if (role.IsGhostRole()) ghostsb.Append(roleDisplay); - else if (role.IsAdditionRole()) addonsb.Append(roleDisplay); - else if (role.IsCrewmate()) crewsb.Append(roleDisplay); - else if (role.IsImpostor() || role.IsMadmate()) impsb.Append(roleDisplay); - else if (role.IsNeutral()) neutralsb.Append(roleDisplay); - } - } - - SendMessage(sb.Append("\n.").ToString(), PlayerId, GetString("GMRoles")); - SendMessage(impsb.Append("\n.").ToString(), PlayerId, ColorString(GetRoleColor(CustomRoles.Impostor), GetString("ImpostorRoles"))); - SendMessage(crewsb.Append("\n.").ToString(), PlayerId, ColorString(GetRoleColor(CustomRoles.Crewmate), GetString("CrewmateRoles"))); - SendMessage(neutralsb.Append("\n.").ToString(), PlayerId, GetString("NeutralRoles")); - SendMessage(ghostsb.Append("\n.").ToString(), PlayerId, GetString("GhostRoles")); - SendMessage(addonsb.Append("\n.").ToString(), PlayerId, GetString("AddonRoles")); - } - - public static void ShowChildrenSettings(OptionItem option, ref StringBuilder sb, int deep = 0, bool f1 = false, bool disableColor = true) - { - foreach (var opt in option.Children.Select((v, i) => new { Value = v, Index = i + 1 })) - { - switch (opt.Value.Name) - { - case "DisableSkeldDevices" when Main.CurrentMap is not MapNames.Skeld and not MapNames.Dleks: - case "DisableMiraHQDevices" when Main.CurrentMap != MapNames.Mira: - case "DisablePolusDevices" when Main.CurrentMap != MapNames.Polus: - case "DisableAirshipDevices" when Main.CurrentMap != MapNames.Airship: - case "PolusReactorTimeLimit" when Main.CurrentMap != MapNames.Polus: - case "AirshipReactorTimeLimit" when Main.CurrentMap != MapNames.Airship: - case "ImpCanBeRole" or "CrewCanBeRole" or "NeutralCanBeRole" when f1: - continue; - } - - if (deep > 0) - { - sb.Append(string.Concat(Enumerable.Repeat("┃", Mathf.Max(deep - 1, 0)))); - sb.Append(opt.Index == option.Children.Count ? "┗ " : "┣ "); - } - - string value = opt.Value.GetString().Replace("ON", "<#00ffa5>ON").Replace("OFF", "<#ff0000>OFF"); - var name = $"{opt.Value.GetName(disableColor).Replace("color=", string.Empty)}"; - sb.Append($"{name}: <#ffff00>{value}\n"); - if (opt.Value.GetBool()) ShowChildrenSettings(opt.Value, ref sb, deep + 1, disableColor: disableColor); - } - } - - public static void ShowLastRoles(byte PlayerId = byte.MaxValue) - { - if (AmongUsClient.Instance.IsGameStarted) - { - SendMessage(GetString("CantUse.lastroles"), PlayerId); - return; - } - - StringBuilder sb = new(); - - sb.Append("<#ffffff>Role Summary:"); - - List cloneRoles = [.. Main.PlayerStates.Keys]; - - foreach (byte id in Main.WinnerList) - { - try - { - if (EndGamePatch.SummaryText[id].Contains("")) continue; - - sb.Append("\n<#c4aa02>\u2605 ").Append(EndGamePatch.SummaryText[id] /*.RemoveHtmlTags()*/); - cloneRoles.Remove(id); - } - catch (Exception ex) - { - ThrowException(ex); - } - } + var summary = $"{ColorString(Main.PlayerColors[id], name)} - {GetDisplayRoleName(id, true)}{TaskCount}{GetKillCountText(id)} ({GetVitalText(id, true)})"; switch (Options.CurrentGameMode) { case CustomGameMode.SoloKombat: - List<(int, byte)> list = []; - list.AddRange(cloneRoles.Select(id => (SoloKombatManager.GetRankOfScore(id), id))); - - list.Sort(); - - foreach ((int, byte) id in list) - { - try - { - sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id.Item2]); - } - catch (Exception ex) - { - ThrowException(ex); - } - } + summary = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? $"{GetProgressText(id)}\t{ColorString(Main.PlayerColors[id], name)}" : $"{ColorString(Main.PlayerColors[id], name)}{GetProgressText(id)}"; + if (GetProgressText(id).Trim() == string.Empty) return string.Empty; break; case CustomGameMode.FFA: - List<(int, byte)> list2 = []; - list2.AddRange(cloneRoles.Select(id => (FFAManager.GetRankFromScore(id), id))); - - list2.Sort(); - - foreach ((int, byte) id in list2) - { - try - { - sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id.Item2]); - } - catch (Exception ex) - { - ThrowException(ex); - } - } - + summary = $"{ColorString(Main.PlayerColors[id], name)} {GetKillCountText(id, true)}"; break; + case CustomGameMode.Speedrun: case CustomGameMode.MoveAndStop: - List<(int, byte)> list3 = []; - list3.AddRange(cloneRoles.Select(id => (MoveAndStop.GetRankFromScore(id), id))); - - list3.Sort(); - - foreach ((int, byte) id in list3) - { - try - { - sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id.Item2]); - } - catch (Exception ex) - { - ThrowException(ex); - } - } - + summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount.Replace("(", string.Empty).Replace(")", string.Empty)} ({GetVitalText(id, true)})"; + break; + case CustomGameMode.HotPotato: + int time = HotPotato.GetSurvivalTime(id); + summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time}s")} ({GetVitalText(id, true)})"; + break; + case CustomGameMode.NaturalDisasters: + int time2 = NaturalDisasters.SurvivalTime(id); + summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time2 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time2}s")} ({GetVitalText(id, true)})"; + break; + case CustomGameMode.RoomRush: + int time3 = RoomRush.GetSurvivalTime(id); + summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time3 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time3}s")} ({GetVitalText(id, true)})"; + break; + case CustomGameMode.CaptureTheFlag: + summary = $"{ColorString(Main.PlayerColors[id], name)}: {CTFManager.GetStatistics(id)}"; + if (CTFManager.IsDeathPossible) summary += $" ({GetVitalText(id, true)})"; break; case CustomGameMode.AllInOne: - case CustomGameMode.RoomRush: - case CustomGameMode.NaturalDisasters: - case CustomGameMode.CaptureTheFlag: - case CustomGameMode.Speedrun: - case CustomGameMode.HotPotato: - case CustomGameMode.HideAndSeek: - foreach (byte id in cloneRoles) - { - try - { - sb.Append("\n\u3000 ").Append(EndGamePatch.SummaryText[id]); - } - catch (Exception ex) - { - ThrowException(ex); - } - } - - break; - default: - foreach (byte id in cloneRoles) - { - try - { - if (!EndGamePatch.SummaryText.TryGetValue(id, out string summaryText)) continue; - - if (summaryText.Contains("")) continue; - - sb.Append("\n\u3000 ").Append(summaryText); - } - catch (Exception ex) - { - ThrowException(ex); - } - } - + string survivalTimeText = !Main.PlayerStates[id].IsDead ? string.Empty : $" ({GetString("SurvivedTimePrefix")}: <#f542ad>{RoomRush.GetSurvivalTime(id)}s)"; + summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount}{GetKillCountText(id, true)} ({GetVitalText(id, true)}){survivalTimeText}"; break; } - sb.Append(""); + return check && GetDisplayRoleName(id, true).RemoveHtmlTags().Contains("INVALID:NotAssigned") + ? "INVALID" + : disableColor + ? summary.RemoveHtmlTags() + : summary; + } + catch (Exception e) + { + ThrowException(e); + return $"{id.ColoredPlayerName()} - ERROR"; + } + } - SendMessage("\n", PlayerId, sb.ToString()); + public static string GetRemainingKillers(bool notify = false, bool president = false) + { + var impnum = 0; + var neutralnum = 0; + bool impShow = president || Options.ShowImpRemainOnEject.GetBool(); + bool nkShow = president || Options.ShowNKRemainOnEject.GetBool(); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (impShow && pc.Is(Team.Impostor)) + impnum++; + else if (nkShow && pc.IsNeutralKiller()) neutralnum++; } - public static void ShowKillLog(byte PlayerId = byte.MaxValue) - { - if (GameStates.IsInGame) - { - SendMessage(GetString("CantUse.killlog"), PlayerId); - return; - } + StringBuilder sb = new(); - if (EndGamePatch.KillLog != string.Empty) SendMessage(EndGamePatch.KillLog, PlayerId); + sb.Append(notify ? "<#777777>" : string.Empty); + sb.Append(impnum == 1 ? GetString("RemainingText.Prefix.SingleImp") : GetString("RemainingText.Prefix.PluralImp")); + sb.Append(notify ? " " : "\n"); + sb.Append(notify ? "<#ffffff>" : ""); + sb.Append(impnum); + sb.Append(notify ? "" : ""); + sb.Append(' '); + sb.Append($"<#ff1919>{(impnum == 1 ? GetString("RemainingText.ImpSingle") : GetString("RemainingText.ImpPlural"))}"); + sb.Append(" & "); + sb.Append(notify ? "<#ffffff>" : ""); + sb.Append(neutralnum); + sb.Append(notify ? "" : ""); + sb.Append(' '); + sb.Append($"<#ffab1b>{(neutralnum == 1 ? GetString("RemainingText.NKSingle") : GetString("RemainingText.NKPlural"))}"); + sb.Append(GetString("RemainingText.Suffix")); + sb.Append('.'); + sb.Append(notify ? "" : string.Empty); + + return sb.ToString(); + } + + public static string RemoveHtmlTagsTemplate(this string str) + { + return Regex.Replace(str, string.Empty, string.Empty); + } + + public static string RemoveHtmlTags(this string str) + { + return Regex.Replace(str, "<[^>]*?>", string.Empty); + } + + public static bool CanMafiaKill() + { + if (Main.PlayerStates == null) return false; + + return !Main.AllAlivePlayerControls.Select(pc => pc.GetCustomRole()).Any(role => role != CustomRoles.Mafia && role.IsImpostor()); + } + + public static void FlashColor(Color color, float duration = 1f) + { + HudManager hud = DestroyableSingleton.Instance; + if (hud.FullScreen == null) return; + + GameObject obj = hud.transform.FindChild("FlashColor_FullScreen")?.gameObject; + + if (obj == null) + { + obj = Object.Instantiate(hud.FullScreen.gameObject, hud.transform); + obj.name = "FlashColor_FullScreen"; } - public static void ShowLastResult(byte PlayerId = byte.MaxValue) + hud.StartCoroutine(Effects.Lerp(duration, new Action(t => { - if (GameStates.IsInGame) - { - SendMessage(GetString("CantUse.lastresult"), PlayerId); - return; - } + obj.SetActive(Math.Abs(t - 1f) > 0.1f); + obj.GetComponent().color = new(color.r, color.g, color.b, Mathf.Clamp01(((-2f * Mathf.Abs(t - 0.5f)) + 1) * color.a / 2)); + }))); + } - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; + public static Sprite LoadSprite(string path, float pixelsPerUnit = 1f) + { + try + { + if (CachedSprites.TryGetValue(path + pixelsPerUnit, out Sprite sprite)) return sprite; - StringBuilder sb = new(); - if (SetEverythingUpPatch.LastWinsText != string.Empty) sb.Append($"{GetString("LastResult")} {SetEverythingUpPatch.LastWinsText}"); - - if (SetEverythingUpPatch.LastWinsReason != string.Empty) sb.Append($"\n{GetString("LastEndReason")} {SetEverythingUpPatch.LastWinsReason}"); - - if (sb.Length > 0) SendMessage("\n", PlayerId, sb.ToString()); + Texture2D texture = LoadTextureFromResources(path); + sprite = Sprite.Create(texture, new(0, 0, texture.width, texture.height), new(0.5f, 0.5f), pixelsPerUnit); + sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor; + return CachedSprites[path + pixelsPerUnit] = sprite; + } + catch + { + Logger.Error($"Error loading texture from: {path}", "LoadImage"); } - public static void ShowLastAddOns(byte PlayerId = byte.MaxValue) + return null; + } + + public static Texture2D LoadTextureFromResources(string path) + { + try { - if (GameStates.IsInGame) - { - SendMessage(GetString("CantUse.lastresult"), PlayerId); - return; - } - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; - - string result = Main.LastAddOns.Values.Join(delimiter: "\n"); - SendMessage("\n", PlayerId, result); + Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + Texture2D texture = new(1, 1, TextureFormat.ARGB32, false); + using MemoryStream ms = new(); + stream?.CopyTo(ms); + texture.LoadImage(ms.ToArray(), false); + return texture; + } + catch + { + Logger.Error($"Error loading texture: {path}", "LoadImage"); } - public static string GetSubRolesText(byte id, bool disableColor = false, bool intro = false, bool summary = false) + return null; + } + + public static string ColorString(Color32 color, string str) + { + return $"<#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}>{str}"; + } + + /// + /// Darkness:Mix black and original color in a ratio of 1. If it is negative, it will be mixed with white. + /// + public static Color ShadeColor(this Color color, float Darkness = 0) + { + bool IsDarker = Darkness >= 0; + if (!IsDarker) Darkness = -Darkness; + + float Weight = IsDarker ? 0 : Darkness; + float R = (color.r + Weight) / (Darkness + 1); + float G = (color.g + Weight) / (Darkness + 1); + float B = (color.b + Weight) / (Darkness + 1); + return new(R, G, B, color.a); + } + + public static void SetChatVisibleForAll() + { + if (!GameStates.IsInGame) return; + + MeetingHud.Instance = Object.Instantiate(HudManager.Instance.MeetingPrefab); + MeetingHud.Instance.ServerStart(PlayerControl.LocalPlayer.PlayerId); + AmongUsClient.Instance.Spawn(MeetingHud.Instance); + MeetingHud.Instance.RpcClose(); + } + + public static bool TryCast(this Il2CppObjectBase obj, out T casted) where T : Il2CppObjectBase + { + casted = obj.TryCast(); + return casted != null; + } + + public static string GetRegionName(IRegionInfo region = null) + { + region ??= ServerManager.Instance.CurrentRegion; + + string name = region.Name; + + if (AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) { - List SubRoles = Main.PlayerStates[id].SubRoles; - if (SubRoles.Count == 0) return string.Empty; - - StringBuilder sb = new(); - - if (intro) - { - bool isLovers = SubRoles.Contains(CustomRoles.Lovers) && Main.PlayerStates[id].MainRole is not CustomRoles.LovingCrewmate and not CustomRoles.LovingImpostor; - SubRoles.RemoveAll(x => x is CustomRoles.NotAssigned or CustomRoles.LastImpostor or CustomRoles.Lovers); - - if (isLovers) sb.Append($"{ColorString(GetRoleColor(CustomRoles.Lovers), " ♥")}"); - - if (SubRoles.Count == 0) return sb.ToString(); - - sb.Append(""); - - if (SubRoles.Count == 1) - { - CustomRoles role = SubRoles[0]; - - string RoleText = ColorString(GetRoleColor(role), GetRoleName(role)); - sb.Append($"{ColorString(Color.gray, GetString("Modifier"))}{RoleText}"); - } - else - { - sb.Append($"{ColorString(Color.gray, GetString("Modifiers"))}"); - - for (var i = 0; i < SubRoles.Count; i++) - { - if (i != 0) sb.Append(", "); - - CustomRoles role = SubRoles[i]; - - string RoleText = ColorString(GetRoleColor(role), GetRoleName(role)); - sb.Append(RoleText); - } - } - - sb.Append(""); - } - else if (!summary) - { - foreach (CustomRoles role in SubRoles) - { - if (role is CustomRoles.NotAssigned or CustomRoles.LastImpostor) continue; - - string RoleText = disableColor ? GetRoleName(role) : ColorString(GetRoleColor(role), GetRoleName(role)); - sb.Append($"{ColorString(Color.gray, " + ")}{RoleText}"); - } - } - - return sb.ToString(); + name = "Local Game"; + return name; } - public static byte MsgToColor(string text, bool isHost = false) + if (region.PingServer.EndsWith("among.us", StringComparison.Ordinal)) { - text = text.ToLowerInvariant(); - text = text.Replace("色", string.Empty); - int color; - - try + // Official server + name = name switch { - color = int.Parse(text); - } - catch - { - color = -1; - } - - color = text switch - { - "0" or "红" or "紅" or "red" or "Red" or "крас" or "Крас" or "красн" or "Красн" or "красный" or "Красный" or "Vermelho" or "vermelho" => 0, - "1" or "蓝" or "藍" or "深蓝" or "blue" or "Blue" or "син" or "Син" or "синий" or "Синий" or "Azul" or "azul" => 1, - "2" or "绿" or "綠" or "深绿" or "green" or "Green" or "Зел" or "зел" or "Зелёный" or "Зеленый" or "зелёный" or "зеленый" or "Verde" or "verde" or "Verde-Escuro" or "verde-escuro" => 2, - "3" or "粉红" or "pink" or "Pink" or "Роз" or "роз" or "Розовый" or "розовый" or "Rosa" or "rosa" => 3, - "4" or "橘" or "orange" or "Orange" or "оранж" or "Оранж" or "оранжевый" or "Оранжевый" or "Laranja" or "laranja" => 4, - "5" or "黄" or "黃" or "yellow" or "Yellow" or "Жёлт" or "Желт" or "жёлт" or "желт" or "Жёлтый" or "Желтый" or "жёлтый" or "желтый" or "Amarelo" or "amarelo" => 5, - "6" or "黑" or "black" or "Black" or "Чёрный" or "Черный" or "чёрный" or "черный" or "Preto" or "preto" => 6, - "7" or "白" or "white" or "White" or "Белый" or "белый" or "Branco" or "branco" => 7, - "8" or "紫" or "purple" or "Purple" or "Фиол" or "фиол" or "Фиолетовый" or "фиолетовый" or "Roxo" or "roxo" => 8, - "9" or "棕" or "brown" or "Brown" or "Корич" or "корич" or "Коричневый" or "коричевый" or "Marrom" or "marrom" => 9, - "10" or "青" or "cyan" or "Cyan" or "Голуб" or "голуб" or "Голубой" or "голубой" or "Ciano" or "ciano" => 10, - "11" or "黄绿" or "黃綠" or "浅绿" or "lime" or "Lime" or "Лайм" or "лайм" or "Лаймовый" or "лаймовый" or "Lima" or "lima" or "Verde-Claro" or "verde-claro" => 11, - "12" or "红褐" or "紅褐" or "深红" or "maroon" or "Maroon" or "Борд" or "борд" or "Бордовый" or "бордовый" or "Bordô" or "bordô" or "Vinho" or "vinho" => 12, - "13" or "玫红" or "玫紅" or "浅粉" or "rose" or "Rose" or "Светло роз" or "светло роз" or "Светло розовый" or "светло розовый" or "Сирень" or "сирень" or "Сиреневый" or "сиреневый" or "Rosê" or "rosê" or "rosinha" or "Rosinha" or "Rosa-Claro" or "rosa-claro" => 13, - "14" or "焦黄" or "焦黃" or "淡黄" or "banana" or "Banana" or "Банан" or "банан" or "Банановый" or "банановый" or "Amarelo-Claro" or "amarelo-claro" => 14, - "15" or "灰" or "gray" or "Gray" or "Сер" or "сер" or "Серый" or "серый" or "Cinza" or "cinza" => 15, - "16" or "茶" or "tan" or "Tan" or "Загар" or "загар" or "Загаровый" or "загаровый" or "bege" or "bege" or "Creme" or "creme" => 16, - "17" or "珊瑚" or "coral" or "Coral" or "Корал" or "корал" or "Коралл" or "коралл" or "Коралловый" or "коралловый" => 17, - "18" or "隐藏" or "?" or "Fortegreen" or "fortegreen" => 18, - _ => color + "North America" => "NA", + "Europe" => "EU", + "Asia" => "AS", + _ => name }; - return !isHost && color == 18 ? byte.MaxValue : color is < 0 or > 18 ? byte.MaxValue : Convert.ToByte(color); - } - - public static void ShowHelp(byte ID) - { - PlayerControl player = GetPlayerById(ID); - SendMessage(ChatCommands.AllCommands.Where(x => x.CanUseCommand(player, false)).Aggregate("", (s, c) => s + $"\n/{c.CommandForms.Where(f => f.All(char.IsAscii)).MinBy(f => f.Length)}{(c.Arguments.Length == 0 ? string.Empty : $" {c.Arguments.Split(' ').Select((x, i) => ColorString(GetColor(i), x)).Join(delimiter: " ")}")} \u2192 {c.Description}"), ID, GetString("CommandList")); - return; - - Color GetColor(int i) - { - return i switch - { - 0 => Palette.Orange, - 1 => Color.magenta, - 2 => Color.blue, - 3 => Color.red, - 4 => Palette.Brown, - 5 => Color.cyan, - 6 => Color.green, - 7 => Palette.Purple, - - _ => Color.yellow - }; - } - } - - public static void CheckTerroristWin(NetworkedPlayerInfo Terrorist) - { - if (!AmongUsClient.Instance.AmHost) return; - - TaskState taskState = GetPlayerById(Terrorist.PlayerId).GetTaskState(); - - if (taskState.IsTaskFinished && (!Main.PlayerStates[Terrorist.PlayerId].IsSuicide || Options.CanTerroristSuicideWin.GetBool())) - { - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.Is(CustomRoles.Terrorist)) - Main.PlayerStates[pc.PlayerId].deathReason = Main.PlayerStates[pc.PlayerId].deathReason == PlayerState.DeathReason.Vote ? PlayerState.DeathReason.etc : PlayerState.DeathReason.Suicide; - else if (!pc.Data.IsDead) pc.Suicide(PlayerState.DeathReason.Bombed, Terrorist.Object); - } - - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Terrorist); - CustomWinnerHolder.WinnerIds.Add(Terrorist.PlayerId); - } - } - - public static void CheckAndSpawnAdditionalRefugee(NetworkedPlayerInfo deadPlayer) - { - try - { - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || deadPlayer == null || deadPlayer.Object.Is(CustomRoles.Refugee) || Main.HasJustStarted || !GameStates.InGame || !Options.SpawnAdditionalRefugeeOnImpsDead.GetBool() || Main.AllAlivePlayerControls.Length < Options.SpawnAdditionalRefugeeMinAlivePlayers.GetInt() || CustomRoles.Refugee.RoleExist(true) || Main.AllAlivePlayerControls == null || Main.AllAlivePlayerControls.Length == 0 || Main.AllAlivePlayerControls.Any(x => x.PlayerId != deadPlayer.PlayerId && (x.Is(CustomRoleTypes.Impostor) || (x.IsNeutralKiller() && !Options.SpawnAdditionalRefugeeWhenNKAlive.GetBool())))) return; - - PlayerControl[] ListToChooseFrom = Main.AllAlivePlayerControls.Where(x => x.PlayerId != deadPlayer.PlayerId && x.Is(CustomRoleTypes.Crewmate) && !x.Is(CustomRoles.Loyal)).ToArray(); - - if (ListToChooseFrom.Length > 0) - { - PlayerControl pc = ListToChooseFrom.RandomElement(); - pc.RpcSetCustomRole(CustomRoles.Refugee); - pc.SetKillCooldown(); - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Madmate); - Logger.Warn($"{pc.GetRealName()} is now a Refugee since all Impostors are dead", "Add Refugee"); - } - else - Logger.Msg("No Player to change to Refugee.", "Add Refugee"); - } - catch (Exception e) - { - ThrowException(e); - } - } - - public static void SendMessage(string text, byte sendTo = byte.MaxValue, string title = "", bool noSplit = false) - { - if (!AmongUsClient.Instance.AmHost) - { - if (sendTo == PlayerControl.LocalPlayer.PlayerId) - { - MessageWriter w = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestSendMessage); - w.Write(text); - w.Write(sendTo); - w.Write(title); - w.Write(noSplit); - w.EndMessage(); - } - - return; - } - - if (title == "") title = "" + GetString("DefaultSystemMessageTitle") + ""; - - if (title.Count(x => x == '\u2605') == 2 && !title.Contains('\n')) - { - if (title.Contains('<') && title.Contains('>') && title.Contains('#')) - title = $"{title[..(title.IndexOf('>') + 1)]}\u27a1{title.Replace("\u2605", "")[..(title.LastIndexOf('<') - 2)]}\u2b05"; - else - title = "\u27a1" + title.Replace("\u2605", "") + "\u2b05"; - } - - text = text.Replace("color=", string.Empty); - - if (text.Length >= 1200 && !noSplit) - { - string[] lines = text.Split('\n'); - var shortenedText = string.Empty; - - foreach (string line in lines) - { - if (shortenedText.Length + line.Length < 1200) - { - shortenedText += line + "\n"; - continue; - } - - if (shortenedText.Length >= 1200) - shortenedText.Chunk(1200).Do(x => SendMessage(new(x), sendTo, title, true)); - else - SendMessage(shortenedText, sendTo, title, true); - - string sentText = shortenedText; - shortenedText = line + "\n"; - - if (Regex.Matches(sentText, " Regex.Matches(sentText, "").Count) - { - string sizeTag = Regex.Matches(sentText, @"")[^1].Value; - shortenedText = sizeTag + shortenedText; - } - } - - if (shortenedText.Length > 0) SendMessage(shortenedText, sendTo, title, true); - - return; - } - - try - { - string pureText = text.RemoveHtmlTags(); - string pureTitle = title.RemoveHtmlTags(); - Logger.Info($" Message: {pureText[..(pureText.Length <= 300 ? pureText.Length : 300)]} - To: {(sendTo == byte.MaxValue ? "Everyone" : $"{GetPlayerById(sendTo)?.GetRealName()}")} - Title: {pureTitle[..(pureTitle.Length <= 300 ? pureTitle.Length : 300)]}", "SendMessage"); - } - catch - { - Logger.Info(" Message sent", "SendMessage"); - } - - text = text.RemoveHtmlTagsTemplate(); - - if (sendTo == byte.MaxValue) - Main.MessagesToSend.Add((text, sendTo, title)); - else - ChatUpdatePatch.SendMessage(Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer, text, sendTo, title); - } - - public static void ApplySuffix(PlayerControl player) - { - if (!AmongUsClient.Instance.AmHost || player == null) return; - - if (!player.AmOwner && !player.FriendCode.GetDevUser().HasTag() && !ChatCommands.IsPlayerModerator(player.FriendCode) && !ChatCommands.IsPlayerVIP(player.FriendCode)) return; - - string name = Main.AllPlayerNames.TryGetValue(player.PlayerId, out string n) ? n : string.Empty; - if (Main.NickName != string.Empty && player.AmOwner) name = Main.NickName; - - if (name == string.Empty) return; - - if (AmongUsClient.Instance.IsGameStarted) - { - if (Options.FormatNameMode.GetInt() == 1 && Main.NickName == string.Empty) name = Palette.GetColorName(player.Data.DefaultOutfit.ColorId); - } - else - { - if (!GameStates.IsLobby) return; - - if (player.AmOwner) - { - if (GameStates.IsOnlineGame || GameStates.IsLocalGame) name = $"{GetString("HostText")}{GetString("Icon")}{name}"; - - string modeText = GetString($"Mode{Options.CurrentGameMode}"); - - name = Options.CurrentGameMode switch - { - CustomGameMode.SoloKombat => $"{modeText}\r\n{name}", - CustomGameMode.FFA => $"{modeText}\r\n{name}", - CustomGameMode.MoveAndStop => $"{modeText}\r\n{name}", - CustomGameMode.HotPotato => $"{modeText}\r\n{name}", - CustomGameMode.HideAndSeek => $"{modeText}\r\n{name}", - CustomGameMode.CaptureTheFlag => $"{modeText}\r\n{name}", - CustomGameMode.NaturalDisasters => $"{modeText}\r\n{name}", - CustomGameMode.RoomRush => $"{modeText}\r\n{name}", - CustomGameMode.AllInOne => $"{modeText}\r\n{name}", - CustomGameMode.Speedrun => ColorString(GetRoleColor(CustomRoles.Speedrunner), $"{modeText}\r\n") + name, - _ => name - }; - } - - DevUser devUser = player.FriendCode.GetDevUser(); - bool isMod = ChatCommands.IsPlayerModerator(player.FriendCode); - bool isVIP = ChatCommands.IsPlayerVIP(player.FriendCode); - bool hasTag = devUser.HasTag(); - - if (hasTag || isMod || isVIP) - { - string tag = hasTag ? devUser.GetTag() : string.Empty; - if (tag == "null") tag = string.Empty; - - if (player.AmOwner || player.IsModClient()) - { - var modTagModded = $"{GetString("ModeratorTag")}\r\n"; - var vipTagModded = $"{GetString("VIPTag")}\r\n"; - name = $"{(hasTag ? tag : string.Empty)}{(isMod ? modTagModded : string.Empty)}{(isVIP ? vipTagModded : string.Empty)}{name}"; - } - else - { - var modTagVanilla = $"{GetString("ModeratorTag")} - "; - var vipTagVanilla = $"{GetString("VIPTag")} - "; - name = $"{(hasTag ? tag.Replace("\r\n", " - ") : string.Empty)}{(isMod ? modTagVanilla : string.Empty)}{(isVIP ? vipTagVanilla : string.Empty)}{name}"; - } - } - - if (player.AmOwner) - { - name = Options.GetSuffixMode() switch - { - SuffixModes.EHR => $"{name}\r\nEHR v{Main.PluginDisplayVersion}", - SuffixModes.Streaming => $"{name}\r\n{GetString("SuffixMode.Streaming")}", - SuffixModes.Recording => $"{name}\r\n{GetString("SuffixMode.Recording")}", - SuffixModes.RoomHost => $"{name}\r\n{GetString("SuffixMode.RoomHost")}", - SuffixModes.OriginalName => $"{name}\r\n{DataManager.player.Customization.Name}", - SuffixModes.DoNotKillMe => $"{name}\r\n{GetString("SuffixModeText.DoNotKillMe")}", - SuffixModes.NoAndroidPlz => $"{name}\r\n{GetString("SuffixModeText.NoAndroidPlz")}", - SuffixModes.AutoHost => $"{name}\r\n{GetString("SuffixModeText.AutoHost")}", - _ => name - }; - } - } - - if (name != player.name && player.CurrentOutfitType == PlayerOutfitType.Default) player.RpcSetName(name); - } - - public static Dictionary GetAllPlayerLocationsCount() - { - Dictionary playerRooms = []; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return null; - - Il2CppReferenceArray Rooms = ShipStatus.Instance.AllRooms; - if (Rooms == null) return null; - - foreach (PlainShipRoom room in Rooms) - { - if (!room.roomArea) continue; - - if (!pc.Collider.IsTouching(room.roomArea)) continue; - - string roomName = GetString($"{room.RoomId}"); - if (!playerRooms.TryAdd(roomName, 1)) playerRooms[roomName]++; - } - } - - return playerRooms; - } - - public static float GetSettingNameAndValueForRole(CustomRoles role, string settingName) - { - const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; - Type[] types = Assembly.GetExecutingAssembly().GetTypes(); - FieldInfo field = types.SelectMany(x => x.GetFields(flags)).FirstOrDefault(x => x.Name == $"{role}{settingName}"); - - if (field == null) - { - FieldInfo tempField = null; - - foreach (Type x in types) - { - var any = false; - - foreach (FieldInfo f in x.GetFields(flags)) - { - if (f.Name.Contains(settingName)) - { - any = true; - tempField = f; - break; - } - } - - if (any && x.Name == $"{role}") - { - field = tempField; - break; - } - } - } - - float add; - - if (field == null) - add = float.MaxValue; - else - { - if (field.GetValue(null) is OptionItem optionItem) - add = optionItem.GetFloat(); - else - add = float.MaxValue; - } - - return add; - } - - public static string ColoredPlayerName(this byte id) - { - return ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), Main.AllPlayerNames.GetValueOrDefault(id, GetPlayerById(id)?.GetRealName() ?? $"Someone (ID {id})")); - } - - public static PlayerControl GetPlayer(this byte id) - { - return GetPlayerById(id); - } - - public static PlayerControl GetPlayerById(int PlayerId, bool fast = true) - { - if (PlayerId is > byte.MaxValue or < byte.MinValue) return null; - - if (fast && GameStates.IsInGame && Main.PlayerStates.TryGetValue((byte)PlayerId, out PlayerState state) && state.Player != null) return state.Player; - - return Main.AllPlayerControls.FirstOrDefault(x => x.PlayerId == PlayerId); - } - - public static NetworkedPlayerInfo GetPlayerInfoById(int PlayerId) - { - return GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == PlayerId); - } - - public static IEnumerator NotifyEveryoneAsync() - { - var count = 0; - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - foreach (PlayerControl seer in aapc) - { - foreach (PlayerControl target in aapc) - { - NotifyRoles(SpecifySeer: seer, SpecifyTarget: target); - if (count++ % 2 == 0) yield return null; - } - } - } - - public static void NotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) - { - if (!SetUpRoleTextPatch.IsInIntro && ((SpecifySeer != null && SpecifySeer.IsModClient() && (CustomGameMode.Standard.IsActiveOrIntegrated() || SpecifySeer.IsHost())) || !AmongUsClient.Instance.AmHost || Main.AllPlayerControls == null || (GameStates.IsMeeting && !isForMeeting))) return; - - DoNotifyRoles(isForMeeting, SpecifySeer, SpecifyTarget, NoCache, ForceLoop, CamouflageIsForMeeting, GuesserIsForMeeting, MushroomMixup); - } - - public static void DoNotifyRoles(bool isForMeeting = false, PlayerControl SpecifySeer = null, PlayerControl SpecifyTarget = null, bool NoCache = false, bool ForceLoop = false, bool CamouflageIsForMeeting = false, bool GuesserIsForMeeting = false, bool MushroomMixup = false) - { - PlayerControl[] apc = Main.AllPlayerControls; - - PlayerControl[] seerList = SpecifySeer != null ? [SpecifySeer] : apc; - PlayerControl[] targetList = SpecifyTarget != null ? [SpecifyTarget] : apc; - - long now = TimeStamp; - - // seer: Players who can see changes made here - // target: Players subject to changes that seer can see - foreach (PlayerControl seer in seerList) - { - try - { - if (seer == null || seer.Data.Disconnected || (seer.IsModClient() && (seer.IsHost() || CustomGameMode.Standard.IsActiveOrIntegrated()))) continue; - - // During intro scene, set team name for non-modded clients and skip the rest. - string SelfName; - Team seerTeam = seer.GetTeam(); - CustomRoles seerRole = seer.GetCustomRole(); - - if (SetUpRoleTextPatch.IsInIntro && (seerRole.IsDesyncRole() || seer.Is(CustomRoles.Bloodlust)) && CustomGameMode.Standard.IsActiveOrIntegrated()) - { - const string iconTextLeft = "\u21e8"; - const string iconTextRight = "\u21e6"; - const string roleNameUp = "\n \n"; - - var selfTeamName = $"{iconTextLeft} {ColorString(seerTeam.GetTeamColor(), $"{seerTeam}")} {iconTextRight}\n \n"; - SelfName = $"{selfTeamName}\r\n{seerRole.ToColoredString()}{roleNameUp}"; - - seer.RpcSetNamePrivate(SelfName, seer); - continue; - } - - if (GameStates.IsLobby) - { - SelfName = seer.GetRealName(); - seer.RpcSetNameEx(SelfName); - continue; - } - - if (seer.Is(CustomRoles.Car) && !isForMeeting) - { - seer.RpcSetNamePrivate(Car.Name, force: NoCache); - continue; - } - - var fontSize = "1.7"; - if (isForMeeting && (seer.GetClient().PlatformData.Platform == Platforms.Playstation || seer.GetClient().PlatformData.Platform == Platforms.Switch)) fontSize = "70%"; - - // Text containing progress, such as tasks - string SelfTaskText = GameStates.IsLobby ? string.Empty : GetProgressText(seer); - - SelfMark.Clear(); - SelfSuffix.Clear(); - - if (!GameStates.IsLobby) - { - if (AntiBlackout.SkipTasks) SelfSuffix.AppendLine(GetString("AntiBlackoutSkipTasks")); - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) goto GameMode0; - - SelfMark.Append(Snitch.GetWarningArrow(seer)); - if (Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Lovers), " ♥")); - - if (BallLightning.IsGhost(seer)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■")); - - SelfMark.Append(Medic.GetMark(seer, seer)); - SelfMark.Append(Gaslighter.GetMark(seer, seer, isForMeeting)); - SelfMark.Append(Gamer.TargetMark(seer, seer)); - SelfMark.Append(Sniper.GetShotNotify(seer.PlayerId)); - if (Silencer.ForSilencer.Contains(seer.PlayerId)) SelfMark.Append(ColorString(GetRoleColor(CustomRoles.Silencer), "╳")); - - GameMode0: - - if (Options.CurrentGameMode is not CustomGameMode.Standard and not CustomGameMode.HideAndSeek) goto GameMode; - - Main.PlayerStates.Values.Do(x => SelfSuffix.Append(x.Role.GetSuffix(seer, seer, meeting: isForMeeting))); - - SelfSuffix.Append(Spurt.GetSuffix(seer)); - - SelfSuffix.Append(CustomTeamManager.GetSuffix(seer)); - - if (!isForMeeting) - { - if (Options.UsePets.GetBool() && Main.AbilityCD.TryGetValue(seer.PlayerId, out (long StartTimeStamp, int TotalCooldown) time)) - { - long remainingCD = time.TotalCooldown - (now - time.StartTimeStamp) + 1; - SelfSuffix.Append(string.Format(GetString("CDPT"), remainingCD > 60 ? "> 60" : remainingCD)); - } - - if (seer.Is(CustomRoles.Asthmatic)) SelfSuffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); - if (seer.Is(CustomRoles.Sonar)) SelfSuffix.Append(Sonar.GetSuffix(seer, isForMeeting)); - if (seer.Is(CustomRoles.Deadlined)) SelfSuffix.Append(Deadlined.GetSuffix(seer)); - if (seer.Is(CustomRoles.Introvert)) SelfSuffix.Append(Introvert.GetSelfSuffix(seer)); - if (seer.Is(CustomRoles.Allergic)) SelfSuffix.Append(Allergic.GetSelfSuffix(seer)); - - SelfSuffix.Append(Bloodmoon.GetSuffix(seer)); - SelfSuffix.Append(Haunter.GetSuffix(seer)); - - switch (seerRole) - { - case CustomRoles.SuperStar when Options.EveryOneKnowSuperStar.GetBool(): - SelfMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); - break; - case CustomRoles.Monitor: - if (AntiAdminer.IsAdminWatch) SelfSuffix.Append($"{GetString("AntiAdminerAD")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Admin)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - if (AntiAdminer.IsVitalWatch) SelfSuffix.Append($"{GetString("AntiAdminerVI")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Vitals)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - if (AntiAdminer.IsDoorLogWatch) SelfSuffix.Append($"{GetString("AntiAdminerDL")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.DoorLog)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - if (AntiAdminer.IsCameraWatch) SelfSuffix.Append($"{GetString("AntiAdminerCA")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Camera)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - break; - case CustomRoles.AntiAdminer: - if (AntiAdminer.IsAdminWatch) SelfSuffix.Append(GetString("AntiAdminerAD")); - if (AntiAdminer.IsVitalWatch) SelfSuffix.Append(GetString("AntiAdminerVI")); - if (AntiAdminer.IsDoorLogWatch) SelfSuffix.Append(GetString("AntiAdminerDL")); - if (AntiAdminer.IsCameraWatch) SelfSuffix.Append(GetString("AntiAdminerCA")); - break; - } - } - else - { - SelfMark.Append(Witch.GetSpelledMark(seer.PlayerId, isForMeeting)); - if (isForMeeting) SelfMark.Append(Wasp.GetStungMark(seer.PlayerId)); - } - - GameMode: - - switch (Options.CurrentGameMode) - { - case CustomGameMode.FFA: - SelfSuffix.Append(FFAManager.GetPlayerArrow(seer)); - break; - case CustomGameMode.SoloKombat: - SelfSuffix.Append(SoloKombatManager.GetDisplayHealth(seer)); - break; - case CustomGameMode.MoveAndStop: - SelfSuffix.Append(MoveAndStop.GetSuffixText(seer)); - break; - case CustomGameMode.HotPotato: - SelfSuffix.Append(HotPotato.GetSuffixText(seer.PlayerId)); - break; - case CustomGameMode.Speedrun: - SelfSuffix.Append(SpeedrunManager.GetSuffixText(seer)); - break; - case CustomGameMode.HideAndSeek: - SelfSuffix.Append(HnSManager.GetSuffixText(seer, seer)); - break; - case CustomGameMode.CaptureTheFlag: - SelfSuffix.Append(CTFManager.GetSuffixText(seer, seer)); - break; - case CustomGameMode.NaturalDisasters: - SelfSuffix.Append(NaturalDisasters.SuffixText()); - break; - case CustomGameMode.RoomRush: - SelfSuffix.Append(RoomRush.GetSuffix(seer)); - break; - case CustomGameMode.AllInOne: - bool alive = seer.IsAlive(); - if (alive) SelfSuffix.Append(SoloKombatManager.GetDisplayHealth(seer) + "\n"); - if (alive) SelfSuffix.Append(MoveAndStop.GetSuffixText(seer) + "\n"); - SelfSuffix.Append(HotPotato.GetSuffixText(seer.PlayerId) + "\n"); - if (alive && !seer.Is(CustomRoles.Killer)) SelfSuffix.Append(string.Format(GetString("DamoclesTimeLeft"), SpeedrunManager.Timers[seer.PlayerId]) + "\n"); - SelfSuffix.Append(NaturalDisasters.SuffixText() + "\n"); - const StringSplitOptions splitFlags = StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries; - SelfSuffix.Append(RoomRush.GetSuffix(seer).Split('\n', splitFlags).Join(delimiter: " - ")); - break; - } - } - - string SeerRealName = seer.GetRealName(isForMeeting); - - if (!GameStates.IsLobby) - { - if ((CustomGameMode.FFA.IsActiveOrIntegrated() && FFAManager.FFATeamMode.GetBool()) || CustomGameMode.HotPotato.IsActiveOrIntegrated()) - SeerRealName = SeerRealName.ApplyNameColorData(seer, seer, isForMeeting); - - if (!isForMeeting && MeetingStates.FirstMeeting && Options.ChangeNameToRoleInfo.GetBool() && Options.CurrentGameMode is not CustomGameMode.FFA and not CustomGameMode.MoveAndStop and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.AllInOne) - { - CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(seer.PlayerId); - - if (team != null) - { - SeerRealName = ColorString( - team.RoleRevealScreenBackgroundColor == "*" || !ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color teamColor) - ? Color.yellow - : teamColor, - string.Format( - GetString("CustomTeamHelp"), - team.RoleRevealScreenTitle == "*" - ? team.TeamName - : team.RoleRevealScreenTitle, - team.RoleRevealScreenSubtitle == "*" - ? string.Empty - : team.RoleRevealScreenSubtitle)); - } - else if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) - { - if (GameStartTimeStamp + 40 > now) SeerRealName = HnSManager.GetRoleInfoText(seer); - } - else if (Options.ChangeNameToRoleInfo.GetBool() && !seer.IsModClient()) - { - bool showLongInfo = LongRoleDescriptions.TryGetValue(seer.PlayerId, out (string Text, int Duration, bool Long) description) && GameStartTimeStamp + description.Duration > now; - string mHelp = (!showLongInfo || description.Long) && CustomGameMode.Standard.IsActiveOrIntegrated() ? "\n" + GetString("MyRoleCommandHelp") : string.Empty; - - SeerRealName = seerTeam switch - { - Team.Impostor when seer.IsMadmate() => $"{GetString("YouAreMadmate")}\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", - Team.Impostor => $"\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", - Team.Crewmate => $"{GetString("YouAreCrewmate")}\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", - Team.Neutral => $"{GetString("YouAreNeutral")}\n{(showLongInfo ? description.Text : seer.GetRoleInfo()) + mHelp}", - _ => SeerRealName - }; - } - } - } - - // Combine seer's job title and SelfTaskText with seer's player name and SelfMark - string SelfRoleName = GameStates.IsLobby ? string.Empty : $"{seer.GetDisplayRoleName()}{SelfTaskText}"; - string SelfDeathReason = seer.KnowDeathReason(seer) && !GameStates.IsLobby ? $"\n『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(seer.PlayerId))}』" : string.Empty; - SelfName = $"{ColorString(GameStates.IsLobby ? Color.white : seer.GetRoleColor(), SeerRealName)}{SelfDeathReason}{SelfMark}"; - - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || GameStates.IsLobby) goto GameMode2; - - SelfName = seerRole switch - { - CustomRoles.Arsonist when seer.IsDouseDone() => $"{ColorString(seer.GetRoleColor(), GetString("EnterVentToWin"))}", - CustomRoles.Revolutionist when seer.IsDrawDone() => $">{ColorString(seer.GetRoleColor(), string.Format(GetString("EnterVentWinCountDown"), Revolutionist.RevolutionistCountdown.GetValueOrDefault(seer.PlayerId, 10)))}", - _ => SelfName - }; - - if (Pelican.IsEaten(seer.PlayerId)) SelfName = $"{ColorString(GetRoleColor(CustomRoles.Pelican), GetString("EatenByPelican"))}"; - - if (Deathpact.IsInActiveDeathpact(seer)) SelfName = Deathpact.GetDeathpactString(seer); - - // Devourer - if (Devourer.HideNameOfConsumedPlayer.GetBool() && Devourer.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Devourer { IsEnable: true } dv && dv.PlayerSkinsCosumed.Contains(seer.PlayerId)) && !CamouflageIsForMeeting) SelfName = GetString("DevouredName"); - - // Camouflage - if (Camouflage.IsCamouflage && !CamouflageIsForMeeting) SelfName = $"{SelfName}"; - - GameMode2: - - if (!GameStates.IsLobby) - { - if (NameNotifyManager.GetNameNotify(seer, out string name) && name.Length > 0) SelfName = name; - - switch (Options.CurrentGameMode) - { - case CustomGameMode.SoloKombat: - SoloKombatManager.GetNameNotify(seer, ref SelfName); - SelfName = $"{SelfTaskText}\r\n{SelfName}"; - break; - case CustomGameMode.FFA: - SelfName = $"{SelfTaskText}\r\n{SelfName}"; - break; - default: - SelfName = $"{SelfRoleName}\r\n{SelfName}"; - break; - } - - SelfName += SelfSuffix.ToString() == string.Empty ? string.Empty : $"\r\n{SelfSuffix}"; - if (!isForMeeting) SelfName += "\r\n"; - } - - seer.RpcSetNamePrivate(SelfName, force: NoCache); - - // Run the second loop only when necessary, such as when seer is dead - if (seer.Data.IsDead || !seer.IsAlive() || NoCache || CamouflageIsForMeeting || MushroomMixup || IsActive(SystemTypes.MushroomMixupSabotage) || ForceLoop || seerList.Length == 1 || targetList.Length == 1) - { - foreach (PlayerControl target in targetList) - { - if (target.PlayerId == seer.PlayerId) continue; - - if (target.Is(CustomRoles.Car) && !isForMeeting) - { - target.RpcSetNamePrivate(Car.Name, seer, NoCache); - continue; - } - - if ((IsActive(SystemTypes.MushroomMixupSabotage) || MushroomMixup) && target.IsAlive() && !seer.Is(CustomRoleTypes.Impostor) && Main.ResetCamPlayerList.Contains(seer.PlayerId)) - { - target.RpcSetNamePrivate("", force: NoCache); - } - else - { - TargetMark.Clear(); - - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || GameStates.IsLobby) goto BeforeEnd2; - - TargetMark.Append(Witch.GetSpelledMark(target.PlayerId, isForMeeting)); - if (isForMeeting) TargetMark.Append(Wasp.GetStungMark(target.PlayerId)); - - if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) - TargetMark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); - - if (BallLightning.IsGhost(target)) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.BallLightning), "■")); - - TargetMark.Append(Snitch.GetWarningMark(seer, target)); - - if ((seer.Data.IsDead || Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId)) && Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId)) - TargetMark.Append($" ♥"); - - if (Randomizer.IsShielded(target)) TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Randomizer), "✚")); - - switch (seerRole) - { - case CustomRoles.PlagueBearer: - if (PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId)) - { - TargetMark.Append($"●"); - PlagueBearer.SendRPC(seer, target); - } - - break; - case CustomRoles.Arsonist: - if (seer.IsDousedPlayer(target)) - TargetMark.Append($"▲"); - - else if (Arsonist.ArsonistTimer.TryGetValue(seer.PlayerId, out (PlayerControl Player, float Timer) ar_kvp) && ar_kvp.Player == target) TargetMark.Append($"△"); - - break; - case CustomRoles.Revolutionist: - if (seer.IsDrawPlayer(target)) TargetMark.Append($"●"); - - if (Revolutionist.RevolutionistTimer.TryGetValue(seer.PlayerId, out (PlayerControl PLAYER, float TIMER) ar_kvp1) && ar_kvp1.PLAYER == target) TargetMark.Append($"○"); - - break; - case CustomRoles.Farseer: - if (Farseer.FarseerTimer.TryGetValue(seer.PlayerId, out (PlayerControl PLAYER, float TIMER) ar_kvp2) && ar_kvp2.PLAYER == target) TargetMark.Append($"○"); - - break; - case CustomRoles.Analyst: - if ((Main.PlayerStates[seer.PlayerId].Role as Analyst).CurrentTarget.ID == target.PlayerId) TargetMark.Append($"○"); - - break; - case CustomRoles.Samurai: // Same as Analyst - if ((Main.PlayerStates[seer.PlayerId].Role as Samurai).Target.Id == target.PlayerId) TargetMark.Append($"○"); - - break; - case CustomRoles.Puppeteer when Puppeteer.PuppeteerList.ContainsValue(seer.PlayerId) && Puppeteer.PuppeteerList.ContainsKey(target.PlayerId): - TargetMark.Append($"◆"); - break; - } - - BeforeEnd2: - - bool shouldSeeTargetAddons = seer.PlayerId == target.PlayerId || new[] { seer, target }.All(x => x.Is(Team.Impostor)); - - string TargetRoleText = - (seer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || - (seer.Is(CustomRoles.Mimic) && target.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) || - (target.Is(CustomRoles.Gravestone) && target.Data.IsDead) || - (Main.LoversPlayers.TrueForAll(x => x.PlayerId == seer.PlayerId || x.PlayerId == target.PlayerId) && Main.LoversPlayers.Count == 2 && Lovers.LoverKnowRoles.GetBool()) || - (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || - (seer.IsMadmate() && target.Is(CustomRoleTypes.Impostor) && Options.MadmateKnowWhosImp.GetBool()) || - (seer.Is(CustomRoleTypes.Impostor) && target.IsMadmate() && Options.ImpKnowWhosMadmate.GetBool()) || - (seer.Is(CustomRoles.Crewpostor) && target.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool()) || - (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || - (seer.IsMadmate() && target.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) || - ((seer.Is(CustomRoles.Sidekick) || seer.Is(CustomRoles.Recruit) || seer.Is(CustomRoles.Jackal)) && (target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Recruit) || target.Is(CustomRoles.Jackal))) || - (target.Is(CustomRoles.Workaholic) && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || - (target.Is(CustomRoles.Doctor) && !target.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool()) || - (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) || - (Marshall.CanSeeMarshall(seer) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) || - (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) || - (CustomTeamManager.AreInSameCustomTeam(seer.PlayerId, target.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(seer.PlayerId, CTAOption.KnowRoles)) || - Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)) || - Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == target.PlayerId) || - Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop or CustomGameMode.HotPotato or CustomGameMode.Speedrun || - (CustomGameMode.HideAndSeek.IsActiveOrIntegrated() && HnSManager.IsRoleTextEnabled(seer, target)) || - (seer.IsRevealedPlayer(target) && !target.Is(CustomRoles.Trickster)) || - (seer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) || - target.Is(CustomRoles.GM) - ? $"{target.GetDisplayRoleName(seeTargetBetrayalAddons: shouldSeeTargetAddons)}{GetProgressText(target)}\r\n" - : string.Empty; - - if (CustomRoles.Altruist.RoleExist() && Main.DiedThisRound.Contains(seer.PlayerId)) TargetRoleText = string.Empty; - - if (Options.CurrentGameMode is CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush) TargetRoleText = string.Empty; - - if (!GameStates.IsLobby) - { - if (!seer.Data.IsDead && seer.IsRevealedPlayer(target) && target.Is(CustomRoles.Trickster)) - { - TargetRoleText = Farseer.RandomRole[seer.PlayerId]; - TargetRoleText += Farseer.GetTaskState(); - } - - if (CustomGameMode.SoloKombat.IsActiveOrIntegrated()) TargetRoleText = $"{GetProgressText(target)}\r\n"; - } - else - TargetRoleText = string.Empty; - - string TargetPlayerName = target.GetRealName(isForMeeting); - - if (GameStates.IsLobby) goto End; - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) goto BeforeEnd; - - if (GuesserIsForMeeting || isForMeeting || (seerRole == CustomRoles.Mafia && !seer.IsAlive() && Options.MafiaCanKillNum.GetInt() >= 1)) TargetPlayerName = $"{ColorString(GetRoleColor(seerRole), target.PlayerId.ToString())} {TargetPlayerName}"; - - switch (seerRole) - { - case CustomRoles.EvilTracker: - TargetMark.Append(EvilTracker.GetTargetMark(seer, target)); - if (isForMeeting && EvilTracker.IsTrackTarget(seer, target) && EvilTracker.CanSeeLastRoomInMeeting) TargetRoleText = $"{EvilTracker.GetArrowAndLastRoom(seer, target)}\r\n"; - - break; - case CustomRoles.Scout: - TargetMark.Append(Scout.GetTargetMark(seer, target)); - if (isForMeeting && Scout.IsTrackTarget(seer, target) && Scout.CanSeeLastRoomInMeeting) TargetRoleText = $"{Scout.GetArrowAndLastRoom(seer, target)}\r\n"; - - break; - case CustomRoles.Psychic when seer.IsAlive() && Psychic.IsRedForPsy(target, seer) && isForMeeting: - TargetPlayerName = ColorString(GetRoleColor(CustomRoles.Impostor), TargetPlayerName); - break; - case CustomRoles.HeadHunter when (Main.PlayerStates[seer.PlayerId].Role as HeadHunter).Targets.Contains(target.PlayerId) && seer.IsAlive(): - case CustomRoles.BountyHunter when (Main.PlayerStates[seer.PlayerId].Role as BountyHunter).GetTarget(seer) == target.PlayerId && seer.IsAlive(): - TargetPlayerName = $"{TargetPlayerName}"; - break; - case CustomRoles.Lookout when seer.IsAlive() && target.IsAlive() && !isForMeeting: - TargetPlayerName = $"{ColorString(GetRoleColor(CustomRoles.Lookout), $" {target.PlayerId}")} {TargetPlayerName}"; - break; - } - - BeforeEnd: - - TargetPlayerName = TargetPlayerName.ApplyNameColorData(seer, target, isForMeeting); - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) goto End; - - if (seer.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished) - TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); - - if (Marshall.CanSeeMarshall(seer) && target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) - TargetMark.Append(ColorString(GetRoleColor(CustomRoles.Marshall), "★")); - - TargetMark.Append(Executioner.TargetMark(seer, target)); - TargetMark.Append(Gamer.TargetMark(seer, target)); - TargetMark.Append(Medic.GetMark(seer, target)); - TargetMark.Append(Gaslighter.GetMark(seer, target, isForMeeting)); - TargetMark.Append(Totocalcio.TargetMark(seer, target)); - TargetMark.Append(Romantic.TargetMark(seer, target)); - TargetMark.Append(Lawyer.LawyerMark(seer, target)); - TargetMark.Append(Deathpact.GetDeathpactMark(seer, target)); - TargetMark.Append(PlagueDoctor.GetMarkOthers(seer, target)); - - End: - - TargetSuffix.Clear(); - - if (!GameStates.IsLobby) - { - switch (Options.CurrentGameMode) - { - case CustomGameMode.SoloKombat: - TargetSuffix.Append(SoloKombatManager.GetDisplayHealth(target)); - break; - case CustomGameMode.HideAndSeek: - TargetSuffix.Append(HnSManager.GetSuffixText(seer, target)); - break; - case CustomGameMode.CaptureTheFlag: - TargetSuffix.Append(CTFManager.GetSuffixText(seer, target)); - break; - } - - Main.PlayerStates.Values.Do(x => TargetSuffix.Append(x.Role.GetSuffix(seer, target, meeting: isForMeeting))); - - if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.FFA or CustomGameMode.Speedrun) TargetSuffix.Append(GetString("DiedR1Warning")); - - TargetSuffix.Append(AFKDetector.GetSuffix(seer, target)); - } - - var TargetDeathReason = string.Empty; - if (seer.KnowDeathReason(target) && !GameStates.IsLobby) TargetDeathReason = $"\n({ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))})"; - - // Devourer - if (Devourer.HideNameOfConsumedPlayer.GetBool() && !GameStates.IsLobby && Devourer.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Devourer { IsEnable: true } dv && dv.PlayerSkinsCosumed.Contains(seer.PlayerId)) && !CamouflageIsForMeeting) TargetPlayerName = GetString("DevouredName"); - - // Camouflage - if (Camouflage.IsCamouflage && !CamouflageIsForMeeting) TargetPlayerName = $"{TargetPlayerName}"; - - var TargetName = $"{TargetRoleText}{TargetPlayerName}{TargetDeathReason}{TargetMark}"; - TargetName += GameStates.IsLobby || TargetSuffix.ToString() == string.Empty ? string.Empty : $"\r\n{TargetSuffix}"; - - target.RpcSetNamePrivate(TargetName, seer, NoCache); - } - } - } - } - catch (Exception ex) - { - if (LastNotifyRolesErrorTS != now) - { - Logger.Error($"Error for {seer.GetNameWithRole()}:", "NR"); - ThrowException(ex); - LastNotifyRolesErrorTS = now; - } - else - Logger.Error($"Error for {seer.GetNameWithRole()}: {ex}", "NR"); - } - } - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return; - - string seers = seerList.Length == apc.Length ? "Everyone" : string.Join(", ", seerList.Select(x => x.GetRealName())); - string targets = targetList.Length == apc.Length ? "Everyone" : string.Join(", ", targetList.Select(x => x.GetRealName())); - if (seers.Length == 0) seers = "\u2205"; - - if (targets.Length == 0) targets = "\u2205"; - - Logger.Info($" Seers: {seers} ---- Targets: {targets}", "NR"); - } - - public static void MarkEveryoneDirtySettings() - { - PlayerGameOptionsSender.SetDirtyToAll(); - } - - public static void MarkEveryoneDirtySettingsV2() - { - PlayerGameOptionsSender.SetDirtyToAllV2(); - } - - public static void MarkEveryoneDirtySettingsV3() - { - PlayerGameOptionsSender.SetDirtyToAllV3(); - } - - public static void MarkEveryoneDirtySettingsV4() - { - PlayerGameOptionsSender.SetDirtyToAllV4(); - } - - public static void SyncAllSettings() - { - PlayerGameOptionsSender.SetDirtyToAll(); - Main.Instance.StartCoroutine(GameOptionsSender.SendAllGameOptionsAsync()); - } - - public static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit) - { - Camouflage.SetPetForOutfitIfNecessary(newOutfit); - - var sender = CustomRpcSender.Create($"Utils.RpcChangeSkin({pc.Data.PlayerName})"); - - pc.SetName(newOutfit.PlayerName); - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetName) - .Write(pc.Data.NetId) - .Write(newOutfit.PlayerName) - .EndRpc(); - - Main.AllPlayerNames[pc.PlayerId] = newOutfit.PlayerName; - - pc.SetColor(newOutfit.ColorId); - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) - .Write(pc.Data.NetId) - .Write((byte)newOutfit.ColorId) - .EndRpc(); - - pc.SetHat(newOutfit.HatId, newOutfit.ColorId); - pc.Data.DefaultOutfit.HatSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetHatStr) - .Write(newOutfit.HatId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - pc.SetSkin(newOutfit.SkinId, newOutfit.ColorId); - pc.Data.DefaultOutfit.SkinSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetSkinStr) - .Write(newOutfit.SkinId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - pc.SetVisor(newOutfit.VisorId, newOutfit.ColorId); - pc.Data.DefaultOutfit.VisorSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetVisorStr) - .Write(newOutfit.VisorId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - pc.SetPet(newOutfit.PetId); - pc.Data.DefaultOutfit.PetSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) - .Write(newOutfit.PetId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - pc.SetNamePlate(newOutfit.NamePlateId); - pc.Data.DefaultOutfit.NamePlateSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetNamePlateStr) - .Write(newOutfit.NamePlateId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) - .EndRpc(); - - sender.SendMessage(); - } - - public static string GetGameStateData(bool clairvoyant = false) - { - Dictionary nums = Enum.GetValues().ToDictionary(x => x, _ => 0); - - if (CustomRoles.Romantic.RoleExist(true)) nums[Options.GameStateInfo.RomanticState] = 1; - - if (Romantic.HasPickedPartner) nums[Options.GameStateInfo.RomanticState] = 2; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.IsMadmate()) - nums[Options.GameStateInfo.MadmateCount]++; - else if (pc.IsNeutralKiller()) - nums[Options.GameStateInfo.NKCount]++; - else if (pc.IsCrewmate()) - nums[Options.GameStateInfo.CrewCount]++; - else if (pc.Is(Team.Impostor)) - nums[Options.GameStateInfo.ImpCount]++; - else if (pc.Is(Team.Neutral)) nums[Options.GameStateInfo.NNKCount]++; - - if (pc.GetCustomSubRoles().Any(x => x.IsConverted())) nums[Options.GameStateInfo.ConvertedCount]++; - - if (Main.LoversPlayers.Exists(x => x.PlayerId == pc.PlayerId)) nums[Options.GameStateInfo.LoversState]++; - - if (pc.Is(CustomRoles.Romantic)) nums[Options.GameStateInfo.RomanticState] *= 3; - - if (Romantic.PartnerId == pc.PlayerId) nums[Options.GameStateInfo.RomanticState] *= 4; - } - - // All possible results of RomanticState from the above code: - // 0: Romantic doesn't exist - // 1: Romantic exists but hasn't picked a partner (and is dead) - // 2: Romantic exists and has picked a partner (but both of them are dead) - // 3: Romantic exists, is alive, but hasn't picked a partner - // 6: Romantic exists, has picked a partner who is dead, but Romantic is alive - // 8: Romantic exists, has picked a partner who is alive, but Romantic is dead - // 24: Romantic exists, has picked a partner who is alive, and Romantic is alive - - StringBuilder sb = new(); - Dictionary checkDict = clairvoyant ? Clairvoyant.Settings : Options.GameStateSettings; - nums[Options.GameStateInfo.Tasks] = GameData.Instance.CompletedTasks; - Dictionary states = nums.ToDictionary(x => x.Key, x => x.Key == Options.GameStateInfo.RomanticState ? GetString($"GSRomanticState.{x.Value}") : (object)x.Value); - states.DoIf(x => checkDict[x.Key].GetBool(), x => sb.AppendLine(string.Format(GetString($"GSInfo.{x.Key}"), x.Value, GameData.Instance.TotalTasks))); - return $"<#ffffff>{sb.ToString().TrimEnd()}"; - } - - public static void AddAbilityCD(CustomRoles role, byte playerId, bool includeDuration = true) - { - if (role.UsesPetInsteadOfKill()) - { - var kcd = (int)Math.Round(Main.AllPlayerKillCooldown.TryGetValue(playerId, out float KCD) ? KCD : Options.DefaultKillCooldown); - Main.AbilityCD[playerId] = (TimeStamp, kcd); - SendRPC(CustomRPC.SyncAbilityCD, 1, playerId, kcd); - return; - } - - int CD = role switch - { - CustomRoles.Mole => Mole.CD.GetInt(), - CustomRoles.Doormaster => Doormaster.VentCooldown.GetInt(), - CustomRoles.Tether => Tether.VentCooldown.GetInt(), - CustomRoles.Mayor when Mayor.MayorHasPortableButton.GetBool() => (int)Math.Round(Options.DefaultKillCooldown), - CustomRoles.Paranoia => (int)Math.Round(Options.DefaultKillCooldown), - CustomRoles.Grenadier => Options.GrenadierSkillCooldown.GetInt() + (includeDuration ? Options.GrenadierSkillDuration.GetInt() : 0), - CustomRoles.Lighter => Options.LighterSkillCooldown.GetInt() + (includeDuration ? Options.LighterSkillDuration.GetInt() : 0), - CustomRoles.SecurityGuard => Options.SecurityGuardSkillCooldown.GetInt() + (includeDuration ? Options.SecurityGuardSkillDuration.GetInt() : 0), - CustomRoles.TimeMaster => Options.TimeMasterSkillCooldown.GetInt() + (includeDuration ? Options.TimeMasterSkillDuration.GetInt() : 0), - CustomRoles.Veteran => Options.VeteranSkillCooldown.GetInt() + (includeDuration ? Options.VeteranSkillDuration.GetInt() : 0), - CustomRoles.Rhapsode => Rhapsode.AbilityCooldown.GetInt() + (includeDuration ? Rhapsode.AbilityDuration.GetInt() : 0), - CustomRoles.Whisperer => Whisperer.Cooldown.GetInt() + (includeDuration ? Whisperer.Duration.GetInt() : 0), - CustomRoles.Perceiver => Perceiver.CD.GetInt(), - CustomRoles.Convener => Convener.CD.GetInt(), - CustomRoles.DovesOfNeace => Options.DovesOfNeaceCooldown.GetInt(), - CustomRoles.Alchemist => Alchemist.VentCooldown.GetInt(), - CustomRoles.NiceHacker => playerId.IsPlayerModClient() ? -1 : NiceHacker.AbilityCD.GetInt(), - CustomRoles.CameraMan => CameraMan.VentCooldown.GetInt(), - CustomRoles.Tornado => Tornado.TornadoCooldown.GetInt(), - CustomRoles.Sentinel => Sentinel.PatrolCooldown.GetInt(), - CustomRoles.Druid => Druid.VentCooldown.GetInt(), - CustomRoles.Catcher => Catcher.AbilityCooldown.GetInt(), - CustomRoles.Sentry => Crewmate.Sentry.ShowInfoCooldown.GetInt(), - CustomRoles.ToiletMaster => ToiletMaster.AbilityCooldown.GetInt(), - CustomRoles.Sniper => Options.DefaultShapeshiftCooldown.GetInt(), - CustomRoles.Assassin => Assassin.AssassinateCooldownOpt.GetInt(), - CustomRoles.Undertaker => Undertaker.UndertakerAssassinateCooldown.GetInt(), - CustomRoles.Bomber => Bomber.BombCooldown.GetInt(), - CustomRoles.Nuker => Bomber.NukeCooldown.GetInt(), - CustomRoles.Sapper => Sapper.ShapeshiftCooldown.GetInt(), - CustomRoles.Miner => Options.MinerSSCD.GetInt(), - CustomRoles.Escapee => Options.EscapeeSSCD.GetInt(), - CustomRoles.QuickShooter => QuickShooter.ShapeshiftCooldown.GetInt(), - CustomRoles.Disperser => Disperser.DisperserShapeshiftCooldown.GetInt(), - CustomRoles.Twister => Twister.ShapeshiftCooldown.GetInt(), - CustomRoles.Abyssbringer => Abyssbringer.BlackHolePlaceCooldown.GetInt(), - CustomRoles.Warlock => Warlock.IsCursed ? -1 : Warlock.ShapeshiftCooldown.GetInt(), - CustomRoles.Swiftclaw => Swiftclaw.DashCD.GetInt() + (includeDuration ? Swiftclaw.DashDuration.GetInt() : 0), - CustomRoles.Hypnotist => Hypnotist.AbilityCooldown.GetInt() + (includeDuration ? Hypnotist.AbilityDuration.GetInt() : 0), - CustomRoles.Parasite => (int)Parasite.SSCD + (includeDuration ? (int)Parasite.SSDur : 0), - CustomRoles.Tiger => Tiger.EnrageCooldown.GetInt() + (includeDuration ? Tiger.EnrageDuration.GetInt() : 0), - CustomRoles.Nonplus => Nonplus.BlindCooldown.GetInt() + (includeDuration ? Nonplus.BlindDuration.GetInt() : 0), - CustomRoles.Cherokious => Cherokious.KillCooldown.GetInt(), - CustomRoles.Shifter => Shifter.KillCooldown.GetInt(), - CustomRoles.NoteKiller => NoteKiller.AbilityCooldown.GetInt(), - CustomRoles.Weatherman => Weatherman.AbilityCooldown.GetInt(), - _ => -1 - }; - - if (CD == -1) return; - - if (Main.PlayerStates[playerId].SubRoles.Contains(CustomRoles.Energetic)) CD = (int)Math.Round(CD * 0.75f); - - Main.AbilityCD[playerId] = (TimeStamp, CD); - SendRPC(CustomRPC.SyncAbilityCD, 1, playerId, CD); - - if (Options.UseUnshiftTrigger.GetBool() && role.SimpleAbilityTrigger() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()) && !role.AlwaysUsesUnshift()) GetPlayerById(playerId)?.RpcResetAbilityCooldown(); - } - - public static (RoleTypes RoleType, CustomRoles CustomRole) GetRoleMap(byte seerId, byte targetId = byte.MaxValue) - { - if (targetId == byte.MaxValue) targetId = seerId; - - return StartGameHostPatch.RpcSetRoleReplacer.RoleMap[(seerId, targetId)]; - } - - public static void AfterMeetingTasks() - { - bool loversChat = Lovers.PrivateChat.GetBool() && Main.LoversPlayers.TrueForAll(x => x.IsAlive()); - - if (loversChat && Lovers.PrivateChatForLoversOnly.GetBool()) - Main.LoversPlayers.ForEach(x => x.SetChatVisible()); - else if (!Lovers.IsChatActivated && loversChat && !GameStates.IsEnded && CustomGameMode.Standard.IsActiveOrIntegrated()) - { - LateTask.New(SetChatVisibleForAll, 0.5f, log: false); - Lovers.IsChatActivated = true; - return; - } - - if (loversChat) GameEndChecker.Prefix(); - - Lovers.IsChatActivated = false; - AFKDetector.NumAFK = 0; - AFKDetector.PlayerData.Clear(); - - Camouflage.CheckCamouflage(); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.IsAlive()) - { - pc.AddKillTimerToDict(); - - if (pc.Is(CustomRoles.Truant)) - { - float beforeSpeed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; - pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[pc.PlayerId] = beforeSpeed; - pc.MarkDirtySettings(); - }, Options.TruantWaitingTime.GetFloat(), $"Truant Waiting: {pc.GetNameWithRole()}"); - } - - if (Options.UsePets.GetBool()) pc.AddAbilityCD(false); - - pc.CheckAndSetUnshiftState(false); - - AFKDetector.RecordPosition(pc); - - Main.PlayerStates[pc.PlayerId].Role.AfterMeetingTasks(); - } - else - { - TaskState taskState = pc.GetTaskState(); - if (pc.IsCrewmate() && !taskState.IsTaskFinished && taskState.HasTasks) pc.Notify(GetString("DoYourTasksPlease"), 10f); - - GhostRolesManager.NotifyAboutGhostRole(pc); - } - - if (pc.Is(CustomRoles.Specter) || pc.Is(CustomRoles.Haunter)) pc.RpcResetAbilityCooldown(); - - Main.CheckShapeshift[pc.PlayerId] = false; - } - - LateTask.New(() => Main.ProcessShapeshifts = true, 1f, log: false); - - CopyCat.ResetRoles(); - - if (Options.DiseasedCDReset.GetBool()) - { - Main.KilledDiseased.SetAllValues(0); - Main.KilledDiseased.Keys.Select(x => x.GetPlayer()).Do(x => x?.ResetKillCooldown()); - Main.KilledDiseased.Clear(); - } - - if (Options.AntidoteCDReset.GetBool()) - { - Main.KilledAntidote.SetAllValues(0); - Main.KilledAntidote.Keys.Select(x => x.GetPlayer()).Do(x => x?.ResetKillCooldown()); - Main.KilledAntidote.Clear(); - } - - Damocles.AfterMeetingTasks(); - Stressed.AfterMeetingTasks(); - Circumvent.AfterMeetingTasks(); - Deadlined.AfterMeetingTasks(); - - if (Options.AirshipVariableElectrical.GetBool()) AirshipElectricalDoors.Initialize(); - - Main.DontCancelVoteList.Clear(); - - DoorsReset.ResetDoors(); - RoleBlockManager.Reset(); - PhantomRolePatch.AfterMeeting(); - - if ((MapNames)Main.NormalOptions.MapId == MapNames.Airship && AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer.Is(CustomRoles.GM)) LateTask.New(() => { PlayerControl.LocalPlayer.NetTransform.SnapTo(new(15.5f, 0.0f), (ushort)(PlayerControl.LocalPlayer.NetTransform.lastSequenceId + 8)); }, 11f, "GM Auto-TP Failsafe"); // TP to Main Hall - - LateTask.New(() => Asthmatic.RunChecks = true, 2f, log: false); - } - - public static void AfterPlayerDeathTasks(PlayerControl target, bool onMeeting = false, bool disconnect = false) - { - try - { - if (!onMeeting) Main.DiedThisRound.Add(target.PlayerId); - - // Record the first death - if (Main.FirstDied == string.Empty) Main.FirstDied = target.FriendCode; - - switch (target.GetCustomRole()) - { - case CustomRoles.Curser: - ((Curser)Main.PlayerStates[target.PlayerId].Role).OnDeath(); - break; - case CustomRoles.Camouflager when Camouflager.IsActive: - Camouflager.IsDead(); - break; - case CustomRoles.Terrorist when !disconnect: - Logger.Info(target?.Data?.PlayerName + " Terrorist died", "MurderPlayer"); - CheckTerroristWin(target?.Data); - break; - case CustomRoles.Executioner: - if (Executioner.Target.ContainsKey(target.PlayerId)) - { - Executioner.Target.Remove(target.PlayerId); - Executioner.SendRPC(target.PlayerId); - } - - break; - case CustomRoles.Lawyer: - if (Lawyer.Target.ContainsKey(target.PlayerId)) - { - Lawyer.Target.Remove(target.PlayerId); - Lawyer.SendRPC(target.PlayerId); - } - - break; - case CustomRoles.PlagueDoctor when !disconnect: - PlagueDoctor.OnPDdeath(target.GetRealKiller(), target); - break; - case CustomRoles.CyberStar when !disconnect: - if (GameStates.IsMeeting) - { - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if ((!Options.ImpKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsImpostor()) - || (!Options.NeutralKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsNeutral())) - continue; - - SendMessage(string.Format(GetString("CyberStarDead"), target.GetRealName()), pc.PlayerId, ColorString(GetRoleColor(CustomRoles.CyberStar), GetString("CyberStarNewsTitle"))); - } - } - else - { - if (!Main.CyberStarDead.Contains(target.PlayerId)) Main.CyberStarDead.Add(target.PlayerId); - } - - break; - case CustomRoles.Pelican: - Pelican.OnPelicanDied(target.PlayerId); - break; - case CustomRoles.Devourer: - Devourer.OnDevourerDied(target.PlayerId); - break; - case CustomRoles.Markseeker when !disconnect: - Markseeker.OnDeath(target); - break; - case CustomRoles.Medic: - Medic.IsDead(target); - break; - } - - if (target == null) return; - - if (!disconnect) Randomizer.OnAnyoneDeath(target); - if (Executioner.Target.ContainsValue(target.PlayerId)) Executioner.ChangeRoleByTarget(target); - if (Lawyer.Target.ContainsValue(target.PlayerId)) Lawyer.ChangeRoleByTarget(target); - if (!disconnect && target.Is(CustomRoles.Stained)) Stained.OnDeath(target, target.GetRealKiller()); - if (!disconnect && target.Is(CustomRoles.Spurt)) Spurt.DeathTask(target); - - Postman.CheckAndResetTargets(target, true); - Hitman.CheckAndResetTargets(); - - if (!disconnect && !onMeeting) - { - Hacker.AddDeadBody(target); - Mortician.OnPlayerDead(target); - Bloodhound.OnPlayerDead(target); - Tracefinder.OnPlayerDead(target); - Vulture.OnPlayerDead(target); - Scout.OnPlayerDeath(target); - Amnesiac.OnAnyoneDeath(target); - Dad.OnAnyoneDeath(target); - Occultist.OnAnyoneDied(target); - Crewmate.Sentry.OnAnyoneMurder(target); - Soothsayer.OnAnyoneDeath(target.GetRealKiller(), target); - - TargetDies(target.GetRealKiller(), target); - } - - Adventurer.OnAnyoneDead(target); - Whisperer.OnAnyoneDied(target); - - if (QuizMaster.On) QuizMaster.Data.NumPlayersDeadThisRound++; - - FixedUpdatePatch.LoversSuicide(target.PlayerId, guess: onMeeting); - - if (!target.HasGhostRole()) - { - Main.AllPlayerSpeed[target.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - target.MarkDirtySettings(); - } - } - catch (Exception ex) - { - Logger.CurrentMethod(); - Logger.Exception(ex, "AfterPlayerDeathTasks"); - } - } - - public static void CountAlivePlayers(bool sendLog = false) - { - int AliveImpostorCount = Main.AllAlivePlayerControls.Count(pc => pc.Is(CustomRoleTypes.Impostor)); - - if (Main.AliveImpostorCount != AliveImpostorCount) - { - Logger.Info("Number of living Impostors: " + AliveImpostorCount, "CountAliveImpostors"); - Main.AliveImpostorCount = AliveImpostorCount; - LastImpostor.SetSubRole(); - } - - if (sendLog) - { - StringBuilder sb = new(100); - - if (CustomGameMode.Standard.IsActiveOrIntegrated()) - { - foreach (CountTypes countTypes in Enum.GetValues()) - { - int playersCount = PlayersCount(countTypes); - if (playersCount == 0) continue; - - sb.Append($"{countTypes}: {AlivePlayersCount(countTypes)}/{playersCount}, "); - } - } - - sb.Append($"All: {AllAlivePlayersCount}/{AllPlayersCount}"); - Logger.Info(sb.ToString(), "CountAlivePlayers"); - } - - if (AmongUsClient.Instance.AmHost && !Main.HasJustStarted) GameEndChecker.Prefix(); - } - - public static string GetVoteName(byte num) - { - PlayerControl player = GetPlayerById(num); - - return num switch - { - < 15 when player != null => player.GetNameWithRole().RemoveHtmlTags(), - 253 => "Skip", - 254 => "None", - 255 => "Dead", - _ => "invalid" - }; - } - - public static string PadRightV2(this object text, int num) - { - var t = text.ToString(); - if (t == null) return string.Empty; - - int bc = t.Sum(c => Encoding.GetEncoding("UTF-8").GetByteCount(c.ToString()) == 1 ? 1 : 2); - - return t.PadRight(Mathf.Max(num - (bc - t.Length), 0)); - } - - public static void DumpLog(bool open = true) - { - var t = DateTime.Now.ToString("yyyy-MM-dd_HH.mm.ss"); - var f = $"{Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)}/EHR_Logs/{t}"; - if (!Directory.Exists(f)) Directory.CreateDirectory(f); - - var filename = $"{f}EHR-v{Main.PluginVersion}-LOG"; - FileInfo[] files = [new($"{Environment.CurrentDirectory}/BepInEx/LogOutput.log"), new($"{Environment.CurrentDirectory}/BepInEx/log.html")]; - files.Do(x => x.CopyTo($"{filename}{x.Extension}")); - - if (!open) return; - - if (PlayerControl.LocalPlayer != null) HudManager.Instance?.Chat?.AddChat(PlayerControl.LocalPlayer, string.Format(GetString("Message.DumpfileSaved"), "EHR" + filename.Split("EHR")[1])); - - ProcessStartInfo psi = new("Explorer.exe") - { Arguments = "/e,/select," + filename.Replace("/", "\\") }; - - Process.Start(psi); - } - - public static (int Doused, int All) GetDousedPlayerCount(byte playerId) - { - int doused = 0, all = 0; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.PlayerId == playerId) continue; - - all++; - - if (Arsonist.IsDoused.TryGetValue((playerId, pc.PlayerId), out bool isDoused) && isDoused) - doused++; - } - - return (doused, all); - } - - public static (int Drawn, int All) GetDrawPlayerCount(byte playerId, out List winnerList) - { - var draw = 0; - int all = Options.RevolutionistDrawCount.GetInt(); - int max = Main.AllAlivePlayerControls.Length; - if (!Main.PlayerStates[playerId].IsDead) max--; - - winnerList = []; - if (all > max) all = max; - - foreach (PlayerControl pc in Main.AllPlayerControls.Where(pc => Revolutionist.IsDraw.TryGetValue((playerId, pc.PlayerId), out bool isDraw) && isDraw).ToArray()) - { - winnerList.Add(pc); - draw++; - } - - return (draw, all); - } - - public static string SummaryTexts(byte id, bool disableColor = true, bool check = false) - { - try - { - string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); - - if (id == PlayerControl.LocalPlayer.PlayerId) - name = DataManager.player.Customization.Name; - else - name = GetPlayerById(id)?.Data.PlayerName ?? name; - - TaskState taskState = Main.PlayerStates[id].TaskState; - string TaskCount; - - if (taskState.HasTasks) - { - NetworkedPlayerInfo info = GetPlayerInfoById(id); - Color TaskCompleteColor = HasTasks(info) ? Color.green : Color.cyan; - Color NonCompleteColor = HasTasks(info) ? Color.yellow : Color.white; - - if (Workhorse.IsThisRole(id)) NonCompleteColor = Workhorse.RoleColor; - - Color NormalColor = taskState.IsTaskFinished ? TaskCompleteColor : NonCompleteColor; - - if (Main.PlayerStates.TryGetValue(id, out PlayerState ps)) - { - NormalColor = ps.MainRole switch - { - CustomRoles.Crewpostor => Color.red, - CustomRoles.Cherokious => GetRoleColor(CustomRoles.Cherokious), - _ => NormalColor - }; - } - - Color TextColor = NormalColor; - var Completed = $"{taskState.CompletedTasksCount}"; - TaskCount = ColorString(TextColor, $" ({Completed}/{taskState.AllTasksCount})"); - } - else - TaskCount = string.Empty; - - var summary = $"{ColorString(Main.PlayerColors[id], name)} - {GetDisplayRoleName(id, true)}{TaskCount}{GetKillCountText(id)} ({GetVitalText(id, true)})"; - - switch (Options.CurrentGameMode) - { - case CustomGameMode.SoloKombat: - summary = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? $"{GetProgressText(id)}\t{ColorString(Main.PlayerColors[id], name)}" : $"{ColorString(Main.PlayerColors[id], name)}{GetProgressText(id)}"; - if (GetProgressText(id).Trim() == string.Empty) return string.Empty; - - break; - case CustomGameMode.FFA: - summary = $"{ColorString(Main.PlayerColors[id], name)} {GetKillCountText(id, true)}"; - break; - case CustomGameMode.Speedrun: - case CustomGameMode.MoveAndStop: - summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount.Replace("(", string.Empty).Replace(")", string.Empty)} ({GetVitalText(id, true)})"; - break; - case CustomGameMode.HotPotato: - int time = HotPotato.GetSurvivalTime(id); - summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time}s")} ({GetVitalText(id, true)})"; - break; - case CustomGameMode.NaturalDisasters: - int time2 = NaturalDisasters.SurvivalTime(id); - summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time2 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time2}s")} ({GetVitalText(id, true)})"; - break; - case CustomGameMode.RoomRush: - int time3 = RoomRush.GetSurvivalTime(id); - summary = $"{ColorString(Main.PlayerColors[id], name)} - <#e8cd46>{GetString("SurvivedTimePrefix")}: <#ffffff>{(time3 == 0 ? $"{GetString("SurvivedUntilTheEnd")}" : $"{time3}s")} ({GetVitalText(id, true)})"; - break; - case CustomGameMode.CaptureTheFlag: - summary = $"{ColorString(Main.PlayerColors[id], name)}: {CTFManager.GetStatistics(id)}"; - if (CTFManager.IsDeathPossible) summary += $" ({GetVitalText(id, true)})"; - break; - case CustomGameMode.AllInOne: - string survivalTimeText = !Main.PlayerStates[id].IsDead ? string.Empty : $" ({GetString("SurvivedTimePrefix")}: <#f542ad>{RoomRush.GetSurvivalTime(id)}s)"; - summary = $"{ColorString(Main.PlayerColors[id], name)} -{TaskCount}{GetKillCountText(id, true)} ({GetVitalText(id, true)}){survivalTimeText}"; - break; - } - - return check && GetDisplayRoleName(id, true).RemoveHtmlTags().Contains("INVALID:NotAssigned") - ? "INVALID" - : disableColor - ? summary.RemoveHtmlTags() - : summary; - } - catch (Exception e) - { - ThrowException(e); - return $"{id.ColoredPlayerName()} - ERROR"; - } - } - - public static string GetRemainingKillers(bool notify = false, bool president = false) - { - var impnum = 0; - var neutralnum = 0; - bool impShow = president || Options.ShowImpRemainOnEject.GetBool(); - bool nkShow = president || Options.ShowNKRemainOnEject.GetBool(); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (impShow && pc.Is(Team.Impostor)) - impnum++; - else if (nkShow && pc.IsNeutralKiller()) neutralnum++; - } - - StringBuilder sb = new(); - - sb.Append(notify ? "<#777777>" : string.Empty); - sb.Append(impnum == 1 ? GetString("RemainingText.Prefix.SingleImp") : GetString("RemainingText.Prefix.PluralImp")); - sb.Append(notify ? " " : "\n"); - sb.Append(notify ? "<#ffffff>" : ""); - sb.Append(impnum); - sb.Append(notify ? "" : ""); - sb.Append(' '); - sb.Append($"<#ff1919>{(impnum == 1 ? GetString("RemainingText.ImpSingle") : GetString("RemainingText.ImpPlural"))}"); - sb.Append(" & "); - sb.Append(notify ? "<#ffffff>" : ""); - sb.Append(neutralnum); - sb.Append(notify ? "" : ""); - sb.Append(' '); - sb.Append($"<#ffab1b>{(neutralnum == 1 ? GetString("RemainingText.NKSingle") : GetString("RemainingText.NKPlural"))}"); - sb.Append(GetString("RemainingText.Suffix")); - sb.Append('.'); - sb.Append(notify ? "" : string.Empty); - - return sb.ToString(); - } - - public static string RemoveHtmlTagsTemplate(this string str) - { - return Regex.Replace(str, string.Empty, string.Empty); - } - - public static string RemoveHtmlTags(this string str) - { - return Regex.Replace(str, "<[^>]*?>", string.Empty); - } - - public static bool CanMafiaKill() - { - if (Main.PlayerStates == null) return false; - - return !Main.AllAlivePlayerControls.Select(pc => pc.GetCustomRole()).Any(role => role != CustomRoles.Mafia && role.IsImpostor()); - } - - public static void FlashColor(Color color, float duration = 1f) - { - HudManager hud = DestroyableSingleton.Instance; - if (hud.FullScreen == null) return; - - GameObject obj = hud.transform.FindChild("FlashColor_FullScreen")?.gameObject; - - if (obj == null) - { - obj = Object.Instantiate(hud.FullScreen.gameObject, hud.transform); - obj.name = "FlashColor_FullScreen"; - } - - hud.StartCoroutine(Effects.Lerp(duration, new Action(t => - { - obj.SetActive(Math.Abs(t - 1f) > 0.1f); - obj.GetComponent().color = new(color.r, color.g, color.b, Mathf.Clamp01(((-2f * Mathf.Abs(t - 0.5f)) + 1) * color.a / 2)); - }))); - } - - public static Sprite LoadSprite(string path, float pixelsPerUnit = 1f) - { - try - { - if (CachedSprites.TryGetValue(path + pixelsPerUnit, out Sprite sprite)) return sprite; - - Texture2D texture = LoadTextureFromResources(path); - sprite = Sprite.Create(texture, new(0, 0, texture.width, texture.height), new(0.5f, 0.5f), pixelsPerUnit); - sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontSaveInEditor; - return CachedSprites[path + pixelsPerUnit] = sprite; - } - catch - { - Logger.Error($"Error loading texture from: {path}", "LoadImage"); - } - - return null; - } - - public static Texture2D LoadTextureFromResources(string path) - { - try - { - Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); - Texture2D texture = new(1, 1, TextureFormat.ARGB32, false); - using MemoryStream ms = new(); - stream?.CopyTo(ms); - texture.LoadImage(ms.ToArray(), false); - return texture; - } - catch - { - Logger.Error($"Error loading texture: {path}", "LoadImage"); - } - - return null; - } - - public static string ColorString(Color32 color, string str) - { - return $"<#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}>{str}"; - } - - /// - /// Darkness:Mix black and original color in a ratio of 1. If it is negative, it will be mixed with white. - /// - public static Color ShadeColor(this Color color, float Darkness = 0) - { - bool IsDarker = Darkness >= 0; - if (!IsDarker) Darkness = -Darkness; - - float Weight = IsDarker ? 0 : Darkness; - float R = (color.r + Weight) / (Darkness + 1); - float G = (color.g + Weight) / (Darkness + 1); - float B = (color.b + Weight) / (Darkness + 1); - return new(R, G, B, color.a); - } - - public static void SetChatVisibleForAll() - { - if (!GameStates.IsInGame) return; - - MeetingHud.Instance = Object.Instantiate(HudManager.Instance.MeetingPrefab); - MeetingHud.Instance.ServerStart(PlayerControl.LocalPlayer.PlayerId); - AmongUsClient.Instance.Spawn(MeetingHud.Instance); - MeetingHud.Instance.RpcClose(); - } - - public static bool TryCast(this Il2CppObjectBase obj, out T casted) where T : Il2CppObjectBase - { - casted = obj.TryCast(); - return casted != null; - } - - public static string GetRegionName(IRegionInfo region = null) - { - region ??= ServerManager.Instance.CurrentRegion; - - string name = region.Name; - - if (AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) - { - name = "Local Game"; - return name; - } - - if (region.PingServer.EndsWith("among.us", StringComparison.Ordinal)) - { - // Official server - name = name switch - { - "North America" => "NA", - "Europe" => "EU", - "Asia" => "AS", - _ => name - }; - - return name; - } - - string Ip = region.Servers.FirstOrDefault()?.Ip ?? string.Empty; - - if (Ip.Contains("aumods.us", StringComparison.Ordinal) || Ip.Contains("duikbo.at", StringComparison.Ordinal)) - { - // Official Modded Server - if (Ip.Contains("au-eu")) - name = "MEU"; - else if (Ip.Contains("au-as")) - name = "MAS"; - else if (Ip.Contains("www.")) name = "MNA"; - - return name; - } - - if (name.Contains("nikocat233", StringComparison.OrdinalIgnoreCase)) name = name.Replace("nikocat233", "Niko233", StringComparison.OrdinalIgnoreCase); - return name; } - private static int PlayersCount(CountTypes countTypes) + string Ip = region.Servers.FirstOrDefault()?.Ip ?? string.Empty; + + if (Ip.Contains("aumods.us", StringComparison.Ordinal) || Ip.Contains("duikbo.at", StringComparison.Ordinal)) { - var count = 0; + // Official Modded Server + if (Ip.Contains("au-eu")) + name = "MEU"; + else if (Ip.Contains("au-as")) + name = "MAS"; + else if (Ip.Contains("www.")) name = "MNA"; - foreach (PlayerState state in Main.PlayerStates.Values) - if (state.countTypes == countTypes) - count++; - - return count; + return name; } - public static int AlivePlayersCount(CountTypes countTypes) - { - return Main.AllAlivePlayerControls.Count(pc => pc.Is(countTypes)); - } + if (name.Contains("nikocat233", StringComparison.OrdinalIgnoreCase)) name = name.Replace("nikocat233", "Niko233", StringComparison.OrdinalIgnoreCase); - public static bool IsPlayerModClient(this byte id) - { - return Main.PlayerVersion.ContainsKey(id); - } + return name; + } - public static float CalculatePingDelay() - { - // The value of AmongUsClient.Instance.Ping is in milliseconds (ms), so ÷1000 to convert to seconds - float divice = !CustomGameMode.Standard.IsActiveOrIntegrated() ? 3000f : 2000f; - float minTime = Mathf.Max(0.02f, AmongUsClient.Instance.Ping / divice * 6f); - return minTime; - } + private static int PlayersCount(CountTypes countTypes) + { + var count = 0; + + foreach (PlayerState state in Main.PlayerStates.Values) + if (state.countTypes == countTypes) + count++; + + return count; + } + + public static int AlivePlayersCount(CountTypes countTypes) + { + return Main.AllAlivePlayerControls.Count(pc => pc.Is(countTypes)); + } + + public static bool IsPlayerModClient(this byte id) + { + return Main.PlayerVersion.ContainsKey(id); + } + + public static float CalculatePingDelay() + { + // The value of AmongUsClient.Instance.Ping is in milliseconds (ms), so ÷1000 to convert to seconds + float divice = !CustomGameMode.Standard.IsActiveOrIntegrated() ? 3000f : 2000f; + float minTime = Mathf.Max(0.02f, AmongUsClient.Instance.Ping / divice * 6f); + return minTime; } } \ No newline at end of file diff --git a/Modules/Zoom.cs b/Modules/Zoom.cs index fb4efa300..5677040df 100644 --- a/Modules/Zoom.cs +++ b/Modules/Zoom.cs @@ -3,109 +3,109 @@ using System.Collections.Generic; using HarmonyLib; using UnityEngine; -namespace EHR -{ - // Credit:https://github.com/tugaru1975/TownOfPlus/TOPmods/Zoom.cs +namespace EHR; + +// Credit:https://github.com/tugaru1975/TownOfPlus/TOPmods/Zoom.cs // Credit:https://github.com/Yumenopai/TownOfHost_Y - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] - public static class Zoom +[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] +public static class Zoom +{ + private static bool ResetButtons; + + public static void Postfix() { - private static bool ResetButtons; - - public static void Postfix() - { - try - { - if (Camera.main == null) return; - - if (((GameStates.IsShip && !GameStates.IsMeeting && GameStates.IsCanMove && PlayerControl.LocalPlayer.Data != null && PlayerControl.LocalPlayer.Data.IsDead) || (GameStates.IsLobby && GameStates.IsCanMove)) && !InGameRoleInfoMenu.Showing) - { - if (Camera.main.orthographicSize > 3.0f) ResetButtons = true; - - switch (Input.mouseScrollDelta.y) - { - case > 0: - { - if (Camera.main.orthographicSize > 3.0f) SetZoomSize(); - - break; - } - case < 0: - { - if (GameStates.IsDead || GameStates.IsFreePlay || DebugModeManager.AmDebugger || GameStates.IsLobby || - PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) - if (Camera.main.orthographicSize < 18.0f) - SetZoomSize(true); - - break; - } - } - - Flag.NewFlag("Zoom"); - } - else - Flag.Run(() => { SetZoomSize(reset: true); }, "Zoom"); - } - catch { } - } - - public static void SetZoomSize(bool times = false, bool reset = false) + try { if (Camera.main == null) return; - var size = 1.5f; - if (!times) size = 1 / size; - - if (reset) + if (((GameStates.IsShip && !GameStates.IsMeeting && GameStates.IsCanMove && PlayerControl.LocalPlayer.Data != null && PlayerControl.LocalPlayer.Data.IsDead) || (GameStates.IsLobby && GameStates.IsCanMove)) && !InGameRoleInfoMenu.Showing) { - Camera.main.orthographicSize = 3.0f; - HudManager.Instance.UICamera.orthographicSize = 3.0f; - HudManager.Instance.Chat.transform.localScale = Vector3.one; - if (GameStates.IsMeeting) MeetingHud.Instance.transform.localScale = Vector3.one; + if (Camera.main.orthographicSize > 3.0f) ResetButtons = true; + + switch (Input.mouseScrollDelta.y) + { + case > 0: + { + if (Camera.main.orthographicSize > 3.0f) SetZoomSize(); + + break; + } + case < 0: + { + if (GameStates.IsDead || GameStates.IsFreePlay || DebugModeManager.AmDebugger || GameStates.IsLobby || + PlayerControl.LocalPlayer.FriendCode.GetDevUser().DeBug) + if (Camera.main.orthographicSize < 18.0f) + SetZoomSize(true); + + break; + } + } + + Flag.NewFlag("Zoom"); } else - { - Camera.main.orthographicSize *= size; - HudManager.Instance.UICamera.orthographicSize *= size; - } + Flag.Run(() => { SetZoomSize(reset: true); }, "Zoom"); + } + catch { } + } - DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive((reset || Mathf.Approximately(Camera.main.orthographicSize, 3.0f)) && PlayerControl.LocalPlayer.IsAlive()); + public static void SetZoomSize(bool times = false, bool reset = false) + { + if (Camera.main == null) return; - if (ResetButtons) - { - ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); - ResetButtons = false; - } + var size = 1.5f; + if (!times) size = 1 / size; + + if (reset) + { + Camera.main.orthographicSize = 3.0f; + HudManager.Instance.UICamera.orthographicSize = 3.0f; + HudManager.Instance.Chat.transform.localScale = Vector3.one; + if (GameStates.IsMeeting) MeetingHud.Instance.transform.localScale = Vector3.one; + } + else + { + Camera.main.orthographicSize *= size; + HudManager.Instance.UICamera.orthographicSize *= size; } - public static void OnFixedUpdate() - { - if (Camera.main == null) return; + DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive((reset || Mathf.Approximately(Camera.main.orthographicSize, 3.0f)) && PlayerControl.LocalPlayer.IsAlive()); - DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive(Mathf.Approximately(Camera.main.orthographicSize, 3.0f) && PlayerControl.LocalPlayer.IsAlive()); + if (ResetButtons) + { + ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen); + ResetButtons = false; } } - public static class Flag + public static void OnFixedUpdate() { - private static readonly List OneTimeList = []; - private static readonly List FirstRunList = []; + if (Camera.main == null) return; - public static void Run(Action action, string type, bool firstrun = false) + DestroyableSingleton.Instance?.ShadowQuad?.gameObject.SetActive(Mathf.Approximately(Camera.main.orthographicSize, 3.0f) && PlayerControl.LocalPlayer.IsAlive()); + } +} + +public static class Flag +{ + private static readonly List OneTimeList = []; + private static readonly List FirstRunList = []; + + public static void Run(Action action, string type, bool firstrun = false) + { + if (OneTimeList.Contains(type) || (firstrun && !FirstRunList.Contains(type))) { - if (OneTimeList.Contains(type) || (firstrun && !FirstRunList.Contains(type))) - { - if (!FirstRunList.Contains(type)) FirstRunList.Add(type); + if (!FirstRunList.Contains(type)) FirstRunList.Add(type); - OneTimeList.Remove(type); - action(); - } + OneTimeList.Remove(type); + action(); } + } - public static void NewFlag(string type) - { - if (!OneTimeList.Contains(type)) OneTimeList.Add(type); - } + public static void NewFlag(string type) + { + if (!OneTimeList.Contains(type)) OneTimeList.Add(type); + } /* public static void DeleteFlag(string type) @@ -113,5 +113,4 @@ namespace EHR if (OneTimeList.Contains(type)) OneTimeList.Remove(type); } */ - } } \ No newline at end of file diff --git a/Patches/AirShipElectricalDoors.cs b/Patches/AirShipElectricalDoors.cs index 96a495830..67b3d0256 100644 --- a/Patches/AirShipElectricalDoors.cs +++ b/Patches/AirShipElectricalDoors.cs @@ -1,70 +1,69 @@ using System.Collections.Generic; using HarmonyLib; -namespace EHR +namespace EHR; + +public static class AirshipElectricalDoors { - public static class AirshipElectricalDoors + private static ElectricalDoors Instance + => ShipStatus.Instance.Systems[SystemTypes.Decontamination].Cast(); + + public static void Initialize() { - private static ElectricalDoors Instance - => ShipStatus.Instance.Systems[SystemTypes.Decontamination].Cast(); + if (Main.NormalOptions.MapId != 4) return; - public static void Initialize() - { - if (Main.NormalOptions.MapId != 4) return; - - Instance.Initialize(); - } - - public static byte[] GetClosedDoors() - { - List DoorsArray = []; - if (Instance.Doors == null || Instance.Doors.Length == 0) return [.. DoorsArray]; - - for (byte i = 0; i < Instance.Doors.Count; i++) - { - StaticDoor door = Instance.Doors[i]; - if (door != null && !door.IsOpen) DoorsArray.Add(i); - } - - return DoorsArray?.ToArray(); - } - // 0: BottomRightHort - // 1: BottomHort - // 2: TopRightHort - // 3: TopCenterHort - // 4: TopLeftHort - // 5: LeftVert - // 6: RightVert - // 7: TopRightVert - // 8: TopLeftVert - // 9: BottomRightVert - // 10: LeftDoorTop - // 11: LeftDoorBottom + Instance.Initialize(); } - [HarmonyPatch(typeof(ElectricalDoors), nameof(ElectricalDoors.Initialize))] - internal static class ElectricalDoorsInitializePatch + public static byte[] GetClosedDoors() { - public static void Postfix( /*ElectricalDoors __instance*/) + List DoorsArray = []; + if (Instance.Doors == null || Instance.Doors.Length == 0) return [.. DoorsArray]; + + for (byte i = 0; i < Instance.Doors.Count; i++) { - if (!GameStates.IsInGame) return; - - var closedoors = string.Empty; - var isFirst = true; - byte[] array = AirshipElectricalDoors.GetClosedDoors(); - - foreach (byte num in array) - { - if (isFirst) - { - isFirst = false; - closedoors += num.ToString(); - } - else - closedoors += $", {num}"; - } - - Logger.Info($"Closed Doors: {closedoors}", "ElectricalDoors Initialize"); + StaticDoor door = Instance.Doors[i]; + if (door != null && !door.IsOpen) DoorsArray.Add(i); } + + return DoorsArray?.ToArray(); + } + // 0: BottomRightHort + // 1: BottomHort + // 2: TopRightHort + // 3: TopCenterHort + // 4: TopLeftHort + // 5: LeftVert + // 6: RightVert + // 7: TopRightVert + // 8: TopLeftVert + // 9: BottomRightVert + // 10: LeftDoorTop + // 11: LeftDoorBottom +} + +[HarmonyPatch(typeof(ElectricalDoors), nameof(ElectricalDoors.Initialize))] +internal static class ElectricalDoorsInitializePatch +{ + public static void Postfix( /*ElectricalDoors __instance*/) + { + if (!GameStates.IsInGame) return; + + var closedoors = string.Empty; + var isFirst = true; + byte[] array = AirshipElectricalDoors.GetClosedDoors(); + + foreach (byte num in array) + { + if (isFirst) + { + isFirst = false; + closedoors += num.ToString(); + } + else + closedoors += $", {num}"; + } + + Logger.Info($"Closed Doors: {closedoors}", "ElectricalDoors Initialize"); } } \ No newline at end of file diff --git a/Patches/AirshipStatus.cs b/Patches/AirshipStatus.cs index b9633d6d0..22f95eacf 100644 --- a/Patches/AirshipStatus.cs +++ b/Patches/AirshipStatus.cs @@ -1,14 +1,13 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +// From: https://github.com/yukieiji/ExtremeRoles/blob/master/ExtremeRoles/Patches/AirshipStatusPatch.cs +[HarmonyPatch(typeof(AirshipStatus), nameof(AirshipStatus.PrespawnStep))] +public static class AirshipStatusPrespawnStepPatch { - //参考元:https://github.com/yukieiji/ExtremeRoles/blob/master/ExtremeRoles/Patches/AirshipStatusPatch.cs - [HarmonyPatch(typeof(AirshipStatus), nameof(AirshipStatus.PrespawnStep))] - public static class AirshipStatusPrespawnStepPatch + public static bool Prefix() { - public static bool Prefix() - { - return !PlayerControl.LocalPlayer.Is(CustomRoles.GM); - } + return !PlayerControl.LocalPlayer.Is(CustomRoles.GM); } } \ No newline at end of file diff --git a/Patches/AnnouncementPatch.cs b/Patches/AnnouncementPatch.cs index d1094e78e..57c70f62a 100644 --- a/Patches/AnnouncementPatch.cs +++ b/Patches/AnnouncementPatch.cs @@ -7,114 +7,113 @@ using Assets.InnerNet; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; -namespace EHR -{ - // ##https://github.com/Yumenopai/TownOfHost_Y - public class ModNews - { - public int BeforeNumber; - public string Date; - public int Number; - public string ShortTitle; - public string SubTitle; - public string Text; - public string Title; +namespace EHR; - public Announcement ToAnnouncement() +// ##https://github.com/Yumenopai/TownOfHost_Y +public class ModNews +{ + public int BeforeNumber; + public string Date; + public int Number; + public string ShortTitle; + public string SubTitle; + public string Text; + public string Title; + + public Announcement ToAnnouncement() + { + return new() { - return new() + Number = Number, + Title = Title, + SubTitle = SubTitle, + ShortTitle = ShortTitle, + Text = Text, + Language = (uint)DataManager.Settings.Language.CurrentLanguage, + Date = Date, + Id = "ModNews" + }; + } +} + +[HarmonyPatch] +public class ModNewsHistory +{ + public static List AllModNews = []; + + // + public static void Init() + { + // When creating new news, you can not delete old news + { + // EHR v2.4.3 + var news = new ModNews { - Number = Number, - Title = Title, - SubTitle = SubTitle, - ShortTitle = ShortTitle, - Text = Text, - Language = (uint)DataManager.Settings.Language.CurrentLanguage, - Date = Date, - Id = "ModNews" + Number = 100001, + Title = "TownOfHostEdited v2.5.0", + SubTitle = "★★★★Another big update, maybe bigger?★★★★", + ShortTitle = "★EHR v2.5.0", + Text = "Welcome to EHR v2.5.0.\n\nSupport for Among Us v2023.7.11 and v2023.7.12\n" + + "\n【Base】\n - Base on TOH v4.1.2\r\n" + + "\n【Fixes】\n - Various bug fixes\n\r" + + "\n【Changes】\n - Hex Master hex icon changed to separate it from Spellcaster\n - Fortune Teller moved to Experimentals due to a planned and unfinished rework\n\r" + + "\n【New Features】\n - New role: Twister (role by papercut on Discord)\n\r - New role: Chameleon (from Project: Lotus)\n\r - New role: Morphling\n\r - New role: Inspector (role by ryuk on Discord)\n\r - New role: Medusa\n\r - New add-on: Lazy\n\r - New add-on: Gravestone\n\r - New add-on: Autopsy (from TOHY)\n\r - New add-on: Loyal\n\r - New add-on: Visionary\n\r- New experimental role: Spiritcaller (role by papercut on Discord)\n\r" + + "\n【Role Changes】\n - Various changes were made, such as an update to Opportunist\n\r", + + Date = "2023-7-14T00:00:00Z" }; + + AllModNews.Add(news); + } + + { + // EHR v2.4.2 + var news = new ModNews + { + Number = 100000, + Title = "TownOfHostEdited v2.4.2", + SubTitle = "★★★★Ooooh bigger update★★★★", + ShortTitle = "★EHR v2.4.2", + Text = "Added in some new stuff, along with some bug fixes.\r\nAmong Us v2023.3.28 is recommended so the roles work correctly.\n" + + "\n【Base】\n - Base on TOH v4.1.2\r\n" + + "\n【Fixes】\n - Fixed various black screen bugs (some still exist but should be less common)\r\n - Other various bug fixes (they're hard to keep track of)\r\n" + + "\n【Changes】\n - Judge now supports Guesser Mode\r\n - Background image reverted to use the AU v2023.3.28 size due to the recommended Among Us version being v2023.3.28\r\n - Many other unlisted changes\r\n - Mario renamed to Vector due to copyright concerns\r\n" + + "\n【New Features】\n - ###Impostors\n - Councillor\r\n - Deathpact (role by papercut on Discord)\r\n - Saboteur (25% chance to replace Inhibitor)\r\n - Consigliere (by Yumeno from TOHY)\r\n - Dazzler (role by papercut on Discord)\r\n - Devourer (role by papercut on Discord)\r\n" + + "\n ### Crewmates\n - Addict (role by papercut on Discord)\r\n - Tracefinder\r\n - Deputy\r\n - Merchant (role by papercut on Discord)\r\n - Oracle\r\n - Spiritualist (role by papercut on Discord)\r\n - Retributionist\r\n- Guardian\r\n - Monarch\r\n" + + "\n ### Neutrals\n - Maverick\r\n - Cursed Soul\r\n - Vulture (role by ryuk on Discord)\r\n - Jinx\r\n - Pickpocket\r\n - Ritualist\r\n - Traitor\r\n" + + "\n ### Add-ons\n - Double Shot (add-on by TommyXL)\r\n - Rascal\r\n" + + "\n【Role Changes】\n - Mimic now has a setting to see the roles of dead players, due to how useless this add-on was\r\n - A revealed Workaholic can no longer be guessed\r\n - Doctor has a new setting like Workaholic to be revealed to all (currently exposes evil Doctors, use at your own risk)\r\n - Mayor has a setting for a TOS mechanic to reveal themselves\r\n - Warlock balancing\r\n - Cleaner balancing (resets kill cooldown to value set in Cleaner settings)\r\n - Updated Monarch\r\n- Removed speed boost from Mare\r\n" + + "\n【Removals】\n - Removed Flash\r\n - Removed Speed Booster\r\n - Temporarily removed Oblivious", + + Date = "2023-7-5T00:00:00Z" + }; + + AllModNews.Add(news); } } - [HarmonyPatch] - public class ModNewsHistory + [HarmonyPatch(typeof(PlayerAnnouncementData), nameof(PlayerAnnouncementData.SetAnnouncements))] + [HarmonyPrefix] + public static bool SetModAnnouncements( /*PlayerAnnouncementData __instance,*/ [HarmonyArgument(0)] ref Il2CppReferenceArray aRange) { - public static List AllModNews = []; - - // - public static void Init() + if (AllModNews.Count == 0) { - // When creating new news, you can not delete old news - { - // EHR v2.4.3 - var news = new ModNews - { - Number = 100001, - Title = "TownOfHostEdited v2.5.0", - SubTitle = "★★★★Another big update, maybe bigger?★★★★", - ShortTitle = "★EHR v2.5.0", - Text = "Welcome to EHR v2.5.0.\n\nSupport for Among Us v2023.7.11 and v2023.7.12\n" - + "\n【Base】\n - Base on TOH v4.1.2\r\n" - + "\n【Fixes】\n - Various bug fixes\n\r" - + "\n【Changes】\n - Hex Master hex icon changed to separate it from Spellcaster\n - Fortune Teller moved to Experimentals due to a planned and unfinished rework\n\r" - + "\n【New Features】\n - New role: Twister (role by papercut on Discord)\n\r - New role: Chameleon (from Project: Lotus)\n\r - New role: Morphling\n\r - New role: Inspector (role by ryuk on Discord)\n\r - New role: Medusa\n\r - New add-on: Lazy\n\r - New add-on: Gravestone\n\r - New add-on: Autopsy (from TOHY)\n\r - New add-on: Loyal\n\r - New add-on: Visionary\n\r- New experimental role: Spiritcaller (role by papercut on Discord)\n\r" - + "\n【Role Changes】\n - Various changes were made, such as an update to Opportunist\n\r", - - Date = "2023-7-14T00:00:00Z" - }; - - AllModNews.Add(news); - } - - { - // EHR v2.4.2 - var news = new ModNews - { - Number = 100000, - Title = "TownOfHostEdited v2.4.2", - SubTitle = "★★★★Ooooh bigger update★★★★", - ShortTitle = "★EHR v2.4.2", - Text = "Added in some new stuff, along with some bug fixes.\r\nAmong Us v2023.3.28 is recommended so the roles work correctly.\n" - + "\n【Base】\n - Base on TOH v4.1.2\r\n" - + "\n【Fixes】\n - Fixed various black screen bugs (some still exist but should be less common)\r\n - Other various bug fixes (they're hard to keep track of)\r\n" - + "\n【Changes】\n - Judge now supports Guesser Mode\r\n - Background image reverted to use the AU v2023.3.28 size due to the recommended Among Us version being v2023.3.28\r\n - Many other unlisted changes\r\n - Mario renamed to Vector due to copyright concerns\r\n" - + "\n【New Features】\n - ###Impostors\n - Councillor\r\n - Deathpact (role by papercut on Discord)\r\n - Saboteur (25% chance to replace Inhibitor)\r\n - Consigliere (by Yumeno from TOHY)\r\n - Dazzler (role by papercut on Discord)\r\n - Devourer (role by papercut on Discord)\r\n" - + "\n ### Crewmates\n - Addict (role by papercut on Discord)\r\n - Tracefinder\r\n - Deputy\r\n - Merchant (role by papercut on Discord)\r\n - Oracle\r\n - Spiritualist (role by papercut on Discord)\r\n - Retributionist\r\n- Guardian\r\n - Monarch\r\n" - + "\n ### Neutrals\n - Maverick\r\n - Cursed Soul\r\n - Vulture (role by ryuk on Discord)\r\n - Jinx\r\n - Pickpocket\r\n - Ritualist\r\n - Traitor\r\n" - + "\n ### Add-ons\n - Double Shot (add-on by TommyXL)\r\n - Rascal\r\n" - + "\n【Role Changes】\n - Mimic now has a setting to see the roles of dead players, due to how useless this add-on was\r\n - A revealed Workaholic can no longer be guessed\r\n - Doctor has a new setting like Workaholic to be revealed to all (currently exposes evil Doctors, use at your own risk)\r\n - Mayor has a setting for a TOS mechanic to reveal themselves\r\n - Warlock balancing\r\n - Cleaner balancing (resets kill cooldown to value set in Cleaner settings)\r\n - Updated Monarch\r\n- Removed speed boost from Mare\r\n" - + "\n【Removals】\n - Removed Flash\r\n - Removed Speed Booster\r\n - Temporarily removed Oblivious", - - Date = "2023-7-5T00:00:00Z" - }; - - AllModNews.Add(news); - } + Init(); + AllModNews.Sort((a1, a2) => { return DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)); }); } - [HarmonyPatch(typeof(PlayerAnnouncementData), nameof(PlayerAnnouncementData.SetAnnouncements))] - [HarmonyPrefix] - public static bool SetModAnnouncements( /*PlayerAnnouncementData __instance,*/ [HarmonyArgument(0)] ref Il2CppReferenceArray aRange) - { - if (AllModNews.Count == 0) - { - Init(); - AllModNews.Sort((a1, a2) => { return DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)); }); - } + List FinalAllNews = []; + AllModNews.Do(n => FinalAllNews.Add(n.ToAnnouncement())); - List FinalAllNews = []; - AllModNews.Do(n => FinalAllNews.Add(n.ToAnnouncement())); + foreach (Announcement news in aRange.ToArray()) + if (!AllModNews.Any(x => x.Number == news.Number)) + FinalAllNews.Add(news); - foreach (Announcement news in aRange.ToArray()) - if (!AllModNews.Any(x => x.Number == news.Number)) - FinalAllNews.Add(news); + FinalAllNews.Sort((a1, a2) => { return DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)); }); - FinalAllNews.Sort((a1, a2) => { return DateTime.Compare(DateTime.Parse(a2.Date), DateTime.Parse(a1.Date)); }); + aRange = new(FinalAllNews.Count); + for (var i = 0; i < FinalAllNews.Count; i++) aRange[i] = FinalAllNews[i]; - aRange = new(FinalAllNews.Count); - for (var i = 0; i < FinalAllNews.Count; i++) aRange[i] = FinalAllNews[i]; - - return true; - } + return true; } } \ No newline at end of file diff --git a/Patches/AprilFoolsPatch.cs b/Patches/AprilFoolsPatch.cs index f4dbe755e..c31cc8ed9 100644 --- a/Patches/AprilFoolsPatch.cs +++ b/Patches/AprilFoolsPatch.cs @@ -3,177 +3,176 @@ using Il2CppSystem; using static CosmeticsLayer; using Action = Il2CppSystem.Action; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(AprilFoolsMode), nameof(AprilFoolsMode.ShouldShowAprilFoolsToggle))] +public static class ShouldShowTogglePatch { - [HarmonyPatch(typeof(AprilFoolsMode), nameof(AprilFoolsMode.ShouldShowAprilFoolsToggle))] - public static class ShouldShowTogglePatch + public static void Postfix(ref bool __result) { - public static void Postfix(ref bool __result) - { - __result = false; - } + __result = false; } +} - [HarmonyPatch(typeof(NormalGameManager), nameof(NormalGameManager.GetBodyType))] - public static class GetNormalBodyTypePatch +[HarmonyPatch(typeof(NormalGameManager), nameof(NormalGameManager.GetBodyType))] +public static class GetNormalBodyTypePatch +{ + public static void Postfix(ref PlayerBodyTypes __result) { - public static void Postfix(ref PlayerBodyTypes __result) + if (Main.HorseMode.Value) { - if (Main.HorseMode.Value) - { - __result = PlayerBodyTypes.Horse; - return; - } - - if (Main.LongMode.Value) - { - __result = PlayerBodyTypes.Long; - return; - } - - __result = PlayerBodyTypes.Normal; + __result = PlayerBodyTypes.Horse; + return; } - } - [HarmonyPatch(typeof(HideAndSeekManager), nameof(HideAndSeekManager.GetBodyType))] - public static class GetHnsBodyTypePatch - { - public static void Postfix(ref PlayerBodyTypes __result, [HarmonyArgument(0)] PlayerControl player) + if (Main.LongMode.Value) { - try + __result = PlayerBodyTypes.Long; + return; + } + + __result = PlayerBodyTypes.Normal; + } +} + +[HarmonyPatch(typeof(HideAndSeekManager), nameof(HideAndSeekManager.GetBodyType))] +public static class GetHnsBodyTypePatch +{ + public static void Postfix(ref PlayerBodyTypes __result, [HarmonyArgument(0)] PlayerControl player) + { + try + { + if (player == null || player.Data == null || player.Data.Role == null) { - if (player == null || player.Data == null || player.Data.Role == null) + if (Main.HorseMode.Value) { - if (Main.HorseMode.Value) - { - __result = PlayerBodyTypes.Horse; - return; - } - - if (Main.LongMode.Value) - { - __result = PlayerBodyTypes.Long; - return; - } - - __result = PlayerBodyTypes.Normal; - } - else if (Main.HorseMode.Value) - { - if (player.Data.Role.IsImpostor) - { - __result = PlayerBodyTypes.Normal; - return; - } - __result = PlayerBodyTypes.Horse; + return; } - else if (Main.LongMode.Value) - { - if (player.Data.Role.IsImpostor) - { - __result = PlayerBodyTypes.LongSeeker; - return; - } + if (Main.LongMode.Value) + { __result = PlayerBodyTypes.Long; + return; } - else - { - if (player.Data.Role.IsImpostor) - { - __result = PlayerBodyTypes.Seeker; - return; - } + __result = PlayerBodyTypes.Normal; + } + else if (Main.HorseMode.Value) + { + if (player.Data.Role.IsImpostor) + { __result = PlayerBodyTypes.Normal; + return; } + + __result = PlayerBodyTypes.Horse; + } + else if (Main.LongMode.Value) + { + if (player.Data.Role.IsImpostor) + { + __result = PlayerBodyTypes.LongSeeker; + return; + } + + __result = PlayerBodyTypes.Long; + } + else + { + if (player.Data.Role.IsImpostor) + { + __result = PlayerBodyTypes.Seeker; + return; + } + + __result = PlayerBodyTypes.Normal; } - catch { } } + catch { } + } +} + +[HarmonyPatch(typeof(LongBoiPlayerBody))] +public static class LongBoiPatches +{ + [HarmonyPatch(nameof(LongBoiPlayerBody.Awake))] + [HarmonyPrefix] + public static bool LongBoyAwake_Patch(LongBoiPlayerBody __instance) + { + try + { + __instance.cosmeticLayer.OnSetBodyAsGhost += (Action)__instance.SetPoolableGhost; + __instance.cosmeticLayer.OnColorChange += (Action)__instance.SetHeightFromColor; + __instance.cosmeticLayer.OnCosmeticSet += (Action)__instance.OnCosmeticSet; + __instance.gameObject.layer = 8; + } + catch { } + + return false; } - [HarmonyPatch(typeof(LongBoiPlayerBody))] - public static class LongBoiPatches + [HarmonyPatch(nameof(LongBoiPlayerBody.Start))] + [HarmonyPrefix] + public static bool LongBoyStart_Patch(LongBoiPlayerBody __instance) { - [HarmonyPatch(nameof(LongBoiPlayerBody.Awake))] - [HarmonyPrefix] - public static bool LongBoyAwake_Patch(LongBoiPlayerBody __instance) + try { - try - { - __instance.cosmeticLayer.OnSetBodyAsGhost += (Action)__instance.SetPoolableGhost; - __instance.cosmeticLayer.OnColorChange += (Action)__instance.SetHeightFromColor; - __instance.cosmeticLayer.OnCosmeticSet += (Action)__instance.OnCosmeticSet; - __instance.gameObject.layer = 8; - } - catch { } + __instance.ShouldLongAround = true; + if (__instance.hideCosmeticsQC) __instance.cosmeticLayer.SetHatVisorVisible(false); - return false; + __instance.SetupNeckGrowth(); + + if (__instance.isExiledPlayer) + { + var instance = ShipStatus.Instance; + if (instance == null || instance.Type != ShipStatus.MapType.Fungle) __instance.cosmeticLayer.AdjustCosmeticRotations(-17.75f); + } + + if (!__instance.isPoolablePlayer) __instance.cosmeticLayer.ValidateCosmetics(); + + if (__instance.myPlayerControl) + { + __instance.StopAllCoroutines(); + __instance.SetHeightFromColor(__instance.myPlayerControl.Data.DefaultOutfit.ColorId); + } } + catch { } - [HarmonyPatch(nameof(LongBoiPlayerBody.Start))] - [HarmonyPrefix] - public static bool LongBoyStart_Patch(LongBoiPlayerBody __instance) + return false; + } + + [HarmonyPatch(nameof(LongBoiPlayerBody.SetHeighFromDistanceHnS))] + [HarmonyPrefix] + public static bool LongBoyNeckSize_Patch(LongBoiPlayerBody __instance, ref float distance) + { + try { - try - { - __instance.ShouldLongAround = true; - if (__instance.hideCosmeticsQC) __instance.cosmeticLayer.SetHatVisorVisible(false); - - __instance.SetupNeckGrowth(); - - if (__instance.isExiledPlayer) - { - var instance = ShipStatus.Instance; - if (instance == null || instance.Type != ShipStatus.MapType.Fungle) __instance.cosmeticLayer.AdjustCosmeticRotations(-17.75f); - } - - if (!__instance.isPoolablePlayer) __instance.cosmeticLayer.ValidateCosmetics(); - - if (__instance.myPlayerControl) - { - __instance.StopAllCoroutines(); - __instance.SetHeightFromColor(__instance.myPlayerControl.Data.DefaultOutfit.ColorId); - } - } - catch { } - - return false; + __instance.targetHeight = (distance / 10f) + 0.5f; + __instance.SetupNeckGrowth(true); } + catch { } - [HarmonyPatch(nameof(LongBoiPlayerBody.SetHeighFromDistanceHnS))] - [HarmonyPrefix] - public static bool LongBoyNeckSize_Patch(LongBoiPlayerBody __instance, ref float distance) + return false; + } + + [HarmonyPatch(typeof(HatManager), nameof(HatManager.CheckLongModeValidCosmetic))] + [HarmonyPrefix] + public static bool CheckLongMode_Patch(HatManager __instance, out bool __result, ref string cosmeticID, ref bool ignoreLongMode) + { + if (AprilFoolsMode.ShouldHorseAround() || AprilFoolsMode.ShouldLongAround()) { - try - { - __instance.targetHeight = (distance / 10f) + 0.5f; - __instance.SetupNeckGrowth(true); - } - catch { } - - return false; - } - - [HarmonyPatch(typeof(HatManager), nameof(HatManager.CheckLongModeValidCosmetic))] - [HarmonyPrefix] - public static bool CheckLongMode_Patch(HatManager __instance, out bool __result, ref string cosmeticID, ref bool ignoreLongMode) - { - if (AprilFoolsMode.ShouldHorseAround() || AprilFoolsMode.ShouldLongAround()) - { - __result = true; - return false; - } - - if (string.Equals("skin_rhm", cosmeticID)) - { - __result = false; - return false; - } - __result = true; return false; } + + if (string.Equals("skin_rhm", cosmeticID)) + { + __result = false; + return false; + } + + __result = true; + return false; } } \ No newline at end of file diff --git a/Patches/ChatBubblePatch.cs b/Patches/ChatBubblePatch.cs index f5244b8e6..73d4a9109 100644 --- a/Patches/ChatBubblePatch.cs +++ b/Patches/ChatBubblePatch.cs @@ -2,35 +2,34 @@ using AmongUs.GameOptions; using HarmonyLib; using UnityEngine; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetRight))] +internal static class ChatBubbleSetRightPatch { - [HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetRight))] - internal static class ChatBubbleSetRightPatch + public static void Postfix(ChatBubble __instance) { - public static void Postfix(ChatBubble __instance) - { - if (Main.IsChatCommand) __instance.SetLeft(); - } + if (Main.IsChatCommand) __instance.SetLeft(); } +} - [HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetName))] - internal static class ChatBubbleSetNamePatch +[HarmonyPatch(typeof(ChatBubble), nameof(ChatBubble.SetName))] +internal static class ChatBubbleSetNamePatch +{ + public static void Postfix(ChatBubble __instance, [HarmonyArgument(2)] bool voted) { - public static void Postfix(ChatBubble __instance, [HarmonyArgument(2)] bool voted) + PlayerControl seer = PlayerControl.LocalPlayer; + PlayerControl target = __instance.playerInfo.Object; + + if (GameStates.IsInGame && !voted && seer.PlayerId == target.PlayerId) __instance.NameText.color = seer.GetRoleColor(); + + if (seer.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) __instance.NameText.color = Color.white; + + if (Main.DarkTheme.Value) { - PlayerControl seer = PlayerControl.LocalPlayer; - PlayerControl target = __instance.playerInfo.Object; - - if (GameStates.IsInGame && !voted && seer.PlayerId == target.PlayerId) __instance.NameText.color = seer.GetRoleColor(); - - if (seer.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) __instance.NameText.color = Color.white; - - if (Main.DarkTheme.Value) - { - __instance.Background.color = new(0.1f, 0.1f, 0.1f, 1f); - __instance.TextArea.color = Color.white; - if (!__instance.playerInfo.Object.IsAlive() && GameStates.InGame) __instance.Background.color = new(0.1f, 0.1f, 0.1f, 0.7f); - } + __instance.Background.color = new(0.1f, 0.1f, 0.1f, 1f); + __instance.TextArea.color = Color.white; + if (!__instance.playerInfo.Object.IsAlive() && GameStates.InGame) __instance.Background.color = new(0.1f, 0.1f, 0.1f, 0.7f); } } } \ No newline at end of file diff --git a/Patches/ChatCommandPatch.cs b/Patches/ChatCommandPatch.cs index 8ca6e3fcb..3784724d0 100644 --- a/Patches/ChatCommandPatch.cs +++ b/Patches/ChatCommandPatch.cs @@ -18,2552 +18,2551 @@ using static EHR.Translator; // ReSharper disable InconsistentNaming -namespace EHR +namespace EHR; + +internal class Command(string[] commandForms, string arguments, string description, Command.UsageLevels usageLevel, Command.UsageTimes usageTime, Action action, bool isCanceled, string[] argsDescriptions = null) { - internal class Command(string[] commandForms, string arguments, string description, Command.UsageLevels usageLevel, Command.UsageTimes usageTime, Action action, bool isCanceled, string[] argsDescriptions = null) + public enum UsageLevels { - public enum UsageLevels + Everyone, + Modded, + Host, + HostOrModerator + } + + public enum UsageTimes + { + Always, + InLobby, + InGame, + InMeeting, + AfterDeath, + AfterDeathOrLobby + } + + public string[] CommandForms => commandForms; + public string Arguments => arguments; + public string Description => description; + public string[] ArgsDescriptions => argsDescriptions ?? []; + private UsageLevels UsageLevel => usageLevel; + private UsageTimes UsageTime => usageTime; + public Action Action => action; + public bool IsCanceled => isCanceled; + + public bool IsThisCommand(string text) + { + if (!text.StartsWith('/')) return false; + + text = text.ToLower().Trim().TrimStart('/'); + return CommandForms.Any(text.Split(' ')[0].Equals); + } + + public bool CanUseCommand(PlayerControl pc, bool checkTime = true) + { + if (UsageLevel == UsageLevels.Everyone && UsageTime == UsageTimes.Always && !Lovers.PrivateChat.GetBool()) return true; + + if (Lovers.PrivateChat.GetBool() && GameStates.IsInTask && pc.IsAlive()) return false; + + switch (UsageLevel) { - Everyone, - Modded, - Host, - HostOrModerator + case UsageLevels.Host when !pc.IsHost(): + case UsageLevels.Modded when !pc.IsModClient(): + case UsageLevels.HostOrModerator when !pc.IsHost() && !ChatCommands.IsPlayerModerator(pc.FriendCode): + return false; } - public enum UsageTimes + if (!checkTime) return true; + + switch (UsageTime) { - Always, - InLobby, - InGame, - InMeeting, - AfterDeath, - AfterDeathOrLobby + case UsageTimes.InLobby when !GameStates.IsLobby: + case UsageTimes.InGame when !GameStates.InGame: + case UsageTimes.InMeeting when !GameStates.IsMeeting: + case UsageTimes.AfterDeath when pc.IsAlive(): + case UsageTimes.AfterDeathOrLobby when pc.IsAlive() && !GameStates.IsLobby: + return false; } - public string[] CommandForms => commandForms; - public string Arguments => arguments; - public string Description => description; - public string[] ArgsDescriptions => argsDescriptions ?? []; - private UsageLevels UsageLevel => usageLevel; - private UsageTimes UsageTime => usageTime; - public Action Action => action; - public bool IsCanceled => isCanceled; + return true; + } +} - public bool IsThisCommand(string text) +[HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] +internal static class ChatCommands +{ + public static readonly List ChatHistory = []; + public static readonly Dictionary LastSentCommand = []; + public static HashSet AllCommands = []; + + private static readonly Dictionary PollVotes = []; + private static readonly Dictionary PollAnswers = []; + private static readonly List PollVoted = []; + private static float PollTimer = 60f; + + public static readonly Dictionary MutedPlayers = []; + + public static Dictionary> DraftRoles = []; + public static Dictionary DraftResult = []; + + private static HashSet ReadyPlayers = []; + + public static void LoadCommands() + { + AllCommands = + [ + new(["lt", "лт", "大厅关闭时间"], "", GetString("CommandDescription.LT"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, LTCommand, false), + new(["dump", "дамп", "лог", "导出日志"], "", GetString("CommandDescription.Dump"), Command.UsageLevels.Modded, Command.UsageTimes.Always, DumpCommand, false), + new(["v", "version", "в", "версия", "检查版本", "versão"], "", GetString("CommandDescription.Version"), Command.UsageLevels.Modded, Command.UsageTimes.Always, VersionCommand, false), + new(["cs", "changesetting", "измнастр", "修改设置", "mudarconfig", "mudarconfiguração"], "{name} {?} [?]", GetString("CommandDescription.ChangeSetting"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, ChangeSettingCommand, true, [GetString("CommandArgs.ChangeSetting.Name"), GetString("CommandArgs.ChangeSetting.UnknownValue"), GetString("CommandArgs.ChangeSetting.UnknownValue")]), + new(["w", "win", "winner", "победители", "获胜者", "vencedor"], "", GetString("CommandDescription.Winner"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, WinnerCommand, true), + new(["l", "lastresult", "л", "对局职业信息", "resultados", "ultimoresultado"], "", GetString("CommandDescription.LastResult"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, LastResultCommand, true), + new(["rn", "rename", "рн", "ренейм", "переименовать", "修改名称", "renomear"], "{name}", GetString("CommandDescription.Rename"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, RenameCommand, true, [GetString("CommandArgs.Rename.Name")]), + new(["hn", "hidename", "хн", "спрник", "隐藏姓名", "semnome", "escondernome"], "", GetString("CommandDescription.HideName"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, HideNameCommand, true), + new(["level", "лвл", "уровень", "修改等级", "nível"], "{level}", GetString("CommandDescription.Level"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, LevelCommand, true, [GetString("CommandArgs.Level.Level")]), + new(["n", "now", "н", "当前设置", "atual"], "", GetString("CommandDescription.Now"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, NowCommand, true), + new(["dis", "disconnect", "дис", "断连"], "{team}", GetString("CommandDescription.Disconnect"), Command.UsageLevels.Host, Command.UsageTimes.InGame, DisconnectCommand, true, [GetString("CommandArgs.Disconnect.Team")]), + new(["r", "р", "função"], "[role]", GetString("CommandDescription.R"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, RCommand, true, [GetString("CommandArgs.R.Role")]), + new(["up", "指定"], "{role}", GetString("CommandDescription.Up"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, UpCommand, true, [GetString("CommandArgs.Up.Role")]), + new(["setrole", "сетроль", "预设职业", "definir-função"], "{id} {role}", GetString("CommandDescription.SetRole"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, SetRoleCommand, true, [GetString("CommandArgs.SetRole.Id"), GetString("CommandArgs.SetRole.Role")]), + new(["h", "help", "хэлп", "хелп", "помощь", "帮助", "ajuda"], "", GetString("CommandDescription.Help"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, HelpCommand, true), + new(["gamestate", "gstate", "gs", "kcount", "kc", "кубийц", "гс", "статигры", "对局状态", "estadojogo", "status"], "", GetString("CommandDescription.KCount"), Command.UsageLevels.Everyone, Command.UsageTimes.InGame, KCountCommand, true), + new(["addmod", "добмодера", "指定协管", "moderador-add"], "{id}", GetString("CommandDescription.AddMod"), Command.UsageLevels.Host, Command.UsageTimes.Always, AddModCommand, true, [GetString("CommandArgs.AddMod.Id")]), + new(["deletemod", "убрмодера", "удмодера", "убратьмодера", "удалитьмодера", "移除协管", "moderador-remover"], "{id}", GetString("CommandDescription.DeleteMod"), Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteModCommand, true, [GetString("CommandArgs.DeleteMod.Id")]), + new(["combo", "комбо", "设置不会同时出现的职业", "combinação", "combinar"], "{mode} {role} {addon} [all]", GetString("CommandDescription.Combo"), Command.UsageLevels.Host, Command.UsageTimes.Always, ComboCommand, true, [GetString("CommandArgs.Combo.Mode"), GetString("CommandArgs.Combo.Role"), GetString("CommandArgs.Combo.Addon"), GetString("CommandArgs.Combo.All")]), + new(["eff", "effect", "эффект", "效果", "efeito"], "{effect}", GetString("CommandDescription.Effect"), Command.UsageLevels.Host, Command.UsageTimes.InGame, EffectCommand, true, [GetString("CommandArgs.Effect.Effect")]), + new(["afkexempt", "освафк", "афкосв", "挂机检测器不会检测", "afk-isentar"], "{id}", GetString("CommandDescription.AFKExempt"), Command.UsageLevels.Host, Command.UsageTimes.Always, AFKExemptCommand, true, [GetString("CommandArgs.AFKExempt.Id")]), + new(["m", "myrole", "м", "мояроль", "我的职业", "minhafunção"], "", GetString("CommandDescription.MyRole"), Command.UsageLevels.Everyone, Command.UsageTimes.InGame, MyRoleCommand, true), + new(["tpout", "тпаут", "传送出"], "", GetString("CommandDescription.TPOut"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, TPOutCommand, true), + new(["tpin", "тпин", "传送进"], "", GetString("CommandDescription.TPIn"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, TPInCommand, true), + new(["t", "template", "т", "темплейт", "模板"], "{tag}", GetString("CommandDescription.Template"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, TemplateCommand, true, [GetString("CommandArgs.Template.Tag")]), + new(["mw", "messagewait", "мв", "медленныйрежим", "消息冷却", "espera-mensagens"], "{duration}", GetString("CommandDescription.MessageWait"), Command.UsageLevels.Host, Command.UsageTimes.Always, MessageWaitCommand, true, [GetString("CommandArgs.MessageWait.Duration")]), + new(["death", "d", "д", "смерть", "死亡原因", "abate"], "", GetString("CommandDescription.Death"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, DeathCommand, true), + new(["say", "s", "сказать", "с", "说", "falar", "dizer"], "{message}", GetString("CommandDescription.Say"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, SayCommand, true, [GetString("CommandArgs.Say.Message")]), + new(["vote", "голос", "投票给", "votar"], "{id}", GetString("CommandDescription.Vote"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, VoteCommand, true, [GetString("CommandArgs.Vote.Id")]), + new(["ask", "спр", "спросить", "数学家提问", "perguntar"], "{number1} {number2}", GetString("CommandDescription.Ask"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AskCommand, true, [GetString("CommandArgs.Ask.Number1"), GetString("CommandArgs.Ask.Number2")]), + new(["ans", "answer", "отв", "ответить", "回答数学家问题", "responder"], "{number}", GetString("CommandDescription.Answer"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AnswerCommand, true, [GetString("CommandArgs.Answer.Number")]), + new(["qa", "вопротв", "回答测验大师问题", "questão-responder"], "{letter}", GetString("CommandDescription.QA"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, QACommand, true, [GetString("CommandArgs.QA.Letter")]), + new(["qs", "вопрпоказать", "检查测验大师问题", "questão-ver"], "", GetString("CommandDescription.QS"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, QSCommand, true), + new(["target", "цель", "腹语者标记", "alvo"], "{id}", GetString("CommandDescription.Target"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, TargetCommand, true, [GetString("CommandArgs.Target.Id")]), + new(["chat", "сообщение", "腹语者发送消息"], "{message}", GetString("CommandDescription.Chat"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ChatCommand, true, [GetString("CommandArgs.Chat.Message")]), + new(["check", "проверить", "检查", "veificar"], "{id} {role}", GetString("CommandDescription.Check"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, CheckCommand, true, [GetString("CommandArgs.Check.Id"), GetString("CommandArgs.Check.Role")]), + new(["ban", "kick", "бан", "кик", "забанить", "кикнуть", "封禁", "踢出", "banir", "expulsar"], "{id}", GetString("CommandDescription.Ban"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, BanKickCommand, true, [GetString("CommandArgs.Ban.Id")]), + new(["exe", "выкинуть", "驱逐", "executar"], "{id}", GetString("CommandDescription.Exe"), Command.UsageLevels.Host, Command.UsageTimes.Always, ExeCommand, true, [GetString("CommandArgs.Exe.Id")]), + new(["kill", "убить", "击杀", "matar"], "{id}", GetString("CommandDescription.Kill"), Command.UsageLevels.Host, Command.UsageTimes.Always, KillCommand, true, [GetString("CommandArgs.Kill.Id")]), + new(["colour", "color", "цвет", "更改颜色", "cor"], "{color}", GetString("CommandDescription.Colour"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ColorCommand, true, [GetString("CommandArgs.Colour.Color")]), + new(["xf", "испр", "修复"], "", GetString("CommandDescription.XF"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, XFCommand, true), + new(["id", "guesslist", "айди", "ID列表"], "", GetString("CommandDescription.ID"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, IDCommand, true), + new(["changerole", "измроль", "修改职业", "mudar-função"], "{role}", GetString("CommandDescription.ChangeRole"), Command.UsageLevels.Host, Command.UsageTimes.InGame, ChangeRoleCommand, true, [GetString("CommandArgs.ChangeRole.Role")]), + new(["end", "завершить", "结束游戏", "encerrar", "finalizar", "fim"], "", GetString("CommandDescription.End"), Command.UsageLevels.Host, Command.UsageTimes.InGame, EndCommand, true), + new(["cosid", "костюм", "одежда", "服装ID"], "", GetString("CommandDescription.CosID"), Command.UsageLevels.Modded, Command.UsageTimes.Always, CosIDCommand, true), + new(["mt", "hy", "собрание", "开会/结束会议"], "", GetString("CommandDescription.MTHY"), Command.UsageLevels.Host, Command.UsageTimes.InGame, MTHYCommand, true), + new(["csd", "кзвук", "自定义播放声音"], "{sound}", GetString("CommandDescription.CSD"), Command.UsageLevels.Modded, Command.UsageTimes.Always, CSDCommand, true, [GetString("CommandArgs.CSD.Sound")]), + new(["sd", "взвук", "游戏中播放声音"], "{sound}", GetString("CommandDescription.SD"), Command.UsageLevels.Modded, Command.UsageTimes.Always, SDCommand, true, [GetString("CommandArgs.SD.Sound")]), + new(["gno", "гно", "猜数字"], "{number}", GetString("CommandDescription.GNO"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeathOrLobby, GNOCommand, true, [GetString("CommandArgs.GNO.Number")]), + new(["poll", "опрос", "发起调查", "enquete"], "{question} {answerA} {answerB} [answerC] [answerD]", GetString("CommandDescription.Poll"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, PollCommand, true, [GetString("CommandArgs.Poll.Question"), GetString("CommandArgs.Poll.AnswerA"), GetString("CommandArgs.Poll.AnswerB"), GetString("CommandArgs.Poll.AnswerC"), GetString("CommandArgs.Poll.AnswerD")]), + new(["pv", "проголосовать", "选择调查选项"], "{vote}", GetString("CommandDescription.PV"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, PVCommand, false, [GetString("CommandArgs.PV.Vote")]), + new(["hm", "мс", "мессенджер", "送信"], "{id}", GetString("CommandDescription.HM"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, HMCommand, true, [GetString("CommandArgs.HM.Id")]), + new(["decree", "указ", "总统命令", "decretar"], "{number}", GetString("CommandDescription.Decree"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DecreeCommand, true, [GetString("CommandArgs.Decree.Number")]), + new(["addvip", "добавитьвип", "добвип", "指定会员", "vip-add"], "{id}", GetString("CommandDescription.AddVIP"), Command.UsageLevels.Host, Command.UsageTimes.Always, AddVIPCommand, true, [GetString("CommandArgs.AddVIP.Id")]), + new(["deletevip", "удвип", "убрвип", "удалитьвип", "убратьвип", "删除会员", "vip-remover"], "{id}", GetString("CommandDescription.DeleteVIP"), Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteVIPCommand, true, [GetString("CommandArgs.DeleteVIP.Id")]), + new(["assume", "предположить", "传销头目预测投票", "assumir"], "{id} {number}", GetString("CommandDescription.Assume"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AssumeCommand, true, [GetString("CommandArgs.Assume.Id"), GetString("CommandArgs.Assume.Number")]), + new(["note", "заметка", "记者管理笔记", "nota", "anotar"], "{action} [?]", GetString("CommandDescription.Note"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, NoteCommand, true, [GetString("CommandArgs.Note.Action"), GetString("CommandArgs.Note.UnknownValue")]), + new(["os", "optionset", "шансроли", "设置职业生成概率"], "{chance} {role}", GetString("CommandDescription.OS"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, OSCommand, true, [GetString("CommandArgs.OS.Chance"), GetString("CommandArgs.OS.Role")]), + new(["negotiation", "neg", "наказание", "谈判方式", "negociar", "negociação"], "{number}", GetString("CommandDescription.Negotiation"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, NegotiationCommand, true, [GetString("CommandArgs.Negotiation.Number")]), + new(["mute", "мут", "禁言", "mutar", "silenciar"], "{id} [duration]", GetString("CommandDescription.Mute"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.AfterDeathOrLobby, MuteCommand, true, [GetString("CommandArgs.Mute.Id"), GetString("CommandArgs.Mute.Duration")]), + new(["unmute", "размут", "解禁", "desmutar", "desilenciar"], "{id}", GetString("CommandDescription.Unmute"), Command.UsageLevels.Host, Command.UsageTimes.Always, UnmuteCommand, true, [GetString("CommandArgs.Unmute.Id")]), + new(["draftstart", "ds", "драфтстарт", "启用草稿", "todosescolhem-iniciar"], "", GetString("CommandDescription.DraftStart"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, DraftStartCommand, true), + new(["draft", "драфт", "选择草稿", "todosescolhem-escolher"], "{number}", GetString("CommandDescription.Draft"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, DraftCommand, true, [GetString("CommandArgs.Draft.Number")]), + new(["readycheck", "rc", "проверитьготовность", "准备检测", "verificação-de-prontidão"], "", GetString("CommandDescription.ReadyCheck"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, ReadyCheckCommand, true), + new(["ready", "готов", "准备", "pronto"], "", GetString("CommandDescription.Ready"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ReadyCommand, true), + new(["enableallroles", "всероли", "启用所有职业", "habilitar-todas-as-funções"], "", GetString("CommandDescription.EnableAllRoles"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, EnableAllRolesCommand, true), + new(["achievements", "достижения", "成就", "conquistas"], "", GetString("CommandDescription.Achievements"), Command.UsageLevels.Modded, Command.UsageTimes.Always, AchievementsCommand, true), + new(["dn", "deathnote", "заметкамертвого", "死亡笔记"], "{name}", GetString("CommandDescription.DeathNote"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DeathNoteCommand, true, [GetString("CommandArgs.DeathNote.Name")]), + new(["w", "whisper", "шепот", "ш", "私聊", "sussurrar"], "{id} {message}", GetString("CommandDescription.Whisper"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, WhisperCommand, true, [GetString("CommandArgs.Whisper.Id"), GetString("CommandArgs.Whisper.Message")]), + + // Commands with action handled elsewhere + new(["shoot", "guess", "bet", "bt", "st", "угадать", "бт", "猜测", "赌", "adivinhar"], "{id} {role}", GetString("CommandDescription.Guess"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Guess.Id"), GetString("CommandArgs.Guess.Role")]), + new(["tl", "sp", "jj", "trial", "суд", "засудить", "审判", "判", "julgar"], "{id}", GetString("CommandDescription.Trial"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Trial.Id")]), + new(["sw", "swap", "st", "свап", "свапнуть", "换票", "trocar"], "{id}", GetString("CommandDescription.Swap"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Swap.Id")]), + new(["compare", "cp", "cmp", "сравнить", "ср", "检查", "comparar"], "{id1} {id2}", GetString("CommandDescription.Compare"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Compare.Id1"), GetString("CommandArgs.Compare.Id2")]), + new(["ms", "mediumship", "medium", "медиум", "回答"], "{answer}", GetString("CommandDescription.Medium"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Medium.Answer")]), + new(["rv", "месть", "отомстить", "复仇"], "{id}", GetString("CommandDescription.Revenge"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, (_, _, _, _) => { }, true, [GetString("CommandArgs.Revenge.Id")]) + ]; + } + + // Function to check if a player is a moderator + public static bool IsPlayerModerator(string friendCode) + { + friendCode = friendCode.Replace(':', '#'); + + if (friendCode == "" || friendCode == string.Empty || !Options.ApplyModeratorList.GetBool()) return false; + + const string friendCodesFilePath = "./EHR_DATA/Moderators.txt"; + + if (!File.Exists(friendCodesFilePath)) { - if (!text.StartsWith('/')) return false; - - text = text.ToLower().Trim().TrimStart('/'); - return CommandForms.Any(text.Split(' ')[0].Equals); + File.WriteAllText(friendCodesFilePath, string.Empty); + return false; } - public bool CanUseCommand(PlayerControl pc, bool checkTime = true) + string[] friendCodes = File.ReadAllLines(friendCodesFilePath); + return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); + } + + // Function to check if a player is a VIP + public static bool IsPlayerVIP(string friendCode) + { + friendCode = friendCode.Replace(':', '#'); + + if (friendCode == "" || friendCode == string.Empty || !Options.ApplyVIPList.GetBool()) return false; + + const string friendCodesFilePath = "./EHR_DATA/VIPs.txt"; + + if (!File.Exists(friendCodesFilePath)) { - if (UsageLevel == UsageLevels.Everyone && UsageTime == UsageTimes.Always && !Lovers.PrivateChat.GetBool()) return true; + File.WriteAllText(friendCodesFilePath, string.Empty); + return false; + } - if (Lovers.PrivateChat.GetBool() && GameStates.IsInTask && pc.IsAlive()) return false; + string[] friendCodes = File.ReadAllLines(friendCodesFilePath); + return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); + } - switch (UsageLevel) + public static bool Prefix(ChatController __instance) + { + if (__instance.quickChatField.visible) return true; + + if (__instance.freeChatField.textArea.text == string.Empty) return false; + + __instance.timeSinceLastMessage = 3f; + + string text = __instance.freeChatField.textArea.text.Trim(); + + if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); + + ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; + + string[] args = text.Split(' '); + var canceled = false; + var cancelVal = string.Empty; + Main.IsChatCommand = true; + + Logger.Info(text, "SendChat"); + + if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Judge.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (NiceSwapper.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (ParityCop.ParityCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Councillor.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Mediumshiper.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; + if (Mafia.MafiaMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; + + Main.IsChatCommand = false; + + if (text.StartsWith('/')) + { + foreach (Command command in AllCommands) { - case UsageLevels.Host when !pc.IsHost(): - case UsageLevels.Modded when !pc.IsModClient(): - case UsageLevels.HostOrModerator when !pc.IsHost() && !ChatCommands.IsPlayerModerator(pc.FriendCode): - return false; + if (!command.IsThisCommand(text)) continue; + + Logger.Info($" Recognized command: {text}", "ChatCommand"); + Main.IsChatCommand = true; + + if (!command.CanUseCommand(PlayerControl.LocalPlayer)) + { + Utils.SendMessage(GetString("Commands.NoAccess"), PlayerControl.LocalPlayer.PlayerId); + goto Canceled; + } + + command.Action(__instance, PlayerControl.LocalPlayer, text, args); + if (command.IsCanceled) goto Canceled; + + break; } - if (!checkTime) return true; + Statistics.HasUsedAnyCommand = true; + } - switch (UsageTime) + if (CheckMute(PlayerControl.LocalPlayer.PlayerId)) goto Canceled; + + if (GameStates.InGame && (Silencer.ForSilencer.Contains(PlayerControl.LocalPlayer.PlayerId) || (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].Role is Dad { IsEnable: true } dad && dad.UsingAbilities.Contains(Dad.Ability.GoForMilk))) && PlayerControl.LocalPlayer.IsAlive()) goto Canceled; + + if (GameStates.IsInGame && (PlayerControl.LocalPlayer.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting)) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.Lovers) || PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) { - case UsageTimes.InLobby when !GameStates.IsLobby: - case UsageTimes.InGame when !GameStates.InGame: - case UsageTimes.InMeeting when !GameStates.IsMeeting: - case UsageTimes.AfterDeath when pc.IsAlive(): - case UsageTimes.AfterDeathOrLobby when pc.IsAlive() && !GameStates.IsLobby: - return false; + PlayerControl otherLover = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId); + string title = PlayerControl.LocalPlayer.GetRealName(); + ChatUpdatePatch.LoversMessage = true; + Utils.SendMessage(text, otherLover.PlayerId, title); + Utils.SendMessage(text, PlayerControl.LocalPlayer.PlayerId, title); + LateTask.New(() => ChatUpdatePatch.LoversMessage = false, Math.Max(AmongUsClient.Instance.Ping / 1000f * 2f, Main.MessageWait.Value + 0.5f), log: false); } - return true; + goto Canceled; + } + + goto Skip; + Canceled: + Main.IsChatCommand = false; + canceled = true; + Skip: + + if (canceled) + { + Logger.Info("Command Canceled", "ChatCommand"); + __instance.freeChatField.textArea.Clear(); + __instance.freeChatField.textArea.SetText(cancelVal); + } + else ChatManager.SendMessage(PlayerControl.LocalPlayer, text); + + if (text.Contains("666") && PlayerControl.LocalPlayer.Is(CustomRoles.Gamer)) + Achievements.Type.WhatTheHell.Complete(); + + return !canceled; + } + + private static void RequestCommandProcessingFromHost(string methodName, string text) + { + PlayerControl pc = PlayerControl.LocalPlayer; + MessageWriter w = AmongUsClient.Instance.StartRpc(pc.NetId, (byte)CustomRPC.RequestCommandProcessing); + w.Write(methodName); + w.Write(pc.PlayerId); + w.Write(text); + w.EndMessage(); + } + + // --------------------------------------------------------------------------------------------------------------------------------------------- + + private static void WhisperCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (Options.DisableWhisperCommand.GetBool()) + { + Utils.SendMessage("\n", player.PlayerId, GetString("WhisperDisabled")); + return; + } + + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(WhisperCommand), text); + return; + } + + if (args.Length < 3 || !byte.TryParse(args[1], out byte targetId)) return; + if (!player.IsLocalPlayer()) ChatManager.SendPreviousMessagesToAll(); + Utils.SendMessage(args[2..].Join(delimiter: " "), targetId, string.Format(GetString("WhisperTitle"), player.PlayerId.ColoredPlayerName())); + } + + private static void DeathNoteCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(DeathNoteCommand), text); + return; + } + + if (!player.Is(CustomRoles.NoteKiller) || args.Length < 2) return; + + if (!player.IsLocalPlayer()) ChatManager.SendPreviousMessagesToAll(); + + var guess = args[1].ToLower(); + guess = char.ToUpper(guess[0]) + guess[1..]; + var deadPlayer = NoteKiller.RealNames.GetKeyByValue(guess); + + if (deadPlayer == default && (!NoteKiller.RealNames.TryGetValue(default, out var name) || name != guess)) + { + Utils.SendMessage(GetString("DeathNoteCommand.WrongName"), player.PlayerId); + return; + } + + var pc = deadPlayer.GetPlayer(); + + if (pc == null || !pc.IsAlive()) + { + Utils.SendMessage(GetString("DeathNoteCommand.PlayerNotFoundOrDead"), player.PlayerId); + return; + } + + var state = Main.PlayerStates[pc.PlayerId]; + state.deathReason = PlayerState.DeathReason.Kill; + state.RealKiller.ID = player.PlayerId; + state.SetDead(); + + pc.RpcExileV2(); + SoundManager.Instance.PlaySound(pc.KillSfx, false, 0.8f); + + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathNoteCommand.Success"), deadPlayer.ColoredPlayerName())); + Utils.SendMessage(string.Format(GetString("DeathNoteCommand.SuccessForOthers"), deadPlayer.ColoredPlayerName())); + + NoteKiller.Kills++; + } + + private static void AchievementsCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Func ToAchievementString = x => $"{GetString($"Achievement.{x}")} - {GetString($"Achievement.{x}.Description")}"; + + Achievements.Type[] allAchievements = Enum.GetValues(); + Achievements.Type[] union = Achievements.CompletedAchievements.Union(Achievements.WaitingAchievements).ToArray(); + string completedAchievements = $"{union.Join(ToAchievementString, "\n")}"; + string incompleteAchievements = $"{allAchievements.Except(union).Join(ToAchievementString, "\n")}"; + + Utils.SendMessage(incompleteAchievements, player.PlayerId, GetString("IncompleteAchievementsTitle")); + Utils.SendMessage(completedAchievements, player.PlayerId, GetString("CompletedAchievementsTitle") + $" <#00a5ff>(<#00ffa5>{union.Length}/{allAchievements.Length})"); + } + + private static void EnableAllRolesCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Options.CustomRoleSpawnChances.Values.DoIf(x => x.GetValue() == 0, x => x.SetValue(1)); + } + + private static void ReadyCheckCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Utils.SendMessage(GetString("ReadyCheckMessage"), title: GetString("ReadyCheckTitle")); + ReadyPlayers = [player.PlayerId]; + Main.Instance.StartCoroutine(Countdown()); + return; + + IEnumerator Countdown() + { + var timer = 30f; + + while (timer > 0f) + { + if (!GameStates.IsLobby) yield break; + + if (Main.AllPlayerControls.Select(x => x.PlayerId).All(ReadyPlayers.Contains)) break; + + timer -= Time.deltaTime; + yield return null; + } + + byte[] notReadyPlayers = Main.AllPlayerControls.Select(x => x.PlayerId).Except(ReadyPlayers).ToArray(); + + if (notReadyPlayers.Length == 0) + Utils.SendMessage("\n", player.PlayerId, GetString("EveryoneReadyTitle")); + else + Utils.SendMessage(string.Join(", ", notReadyPlayers.Select(x => x.ColoredPlayerName())), player.PlayerId, string.Format(GetString("PlayersNotReadyTitle"), notReadyPlayers.Length)); } } - [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))] - internal static class ChatCommands + private static void ReadyCommand(ChatController __instance, PlayerControl player, string text, string[] args) { - public static readonly List ChatHistory = []; - public static readonly Dictionary LastSentCommand = []; - public static HashSet AllCommands = []; + ReadyPlayers.Add(player.PlayerId); + } - private static readonly Dictionary PollVotes = []; - private static readonly Dictionary PollAnswers = []; - private static readonly List PollVoted = []; - private static float PollTimer = 60f; + private static void DraftStartCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + DraftResult = []; - public static readonly Dictionary MutedPlayers = []; + byte[] allPlayerIds = Main.AllPlayerControls.Select(x => x.PlayerId).ToArray(); + List allRoles = Enum.GetValues().Where(x => x < CustomRoles.NotAssigned && x.IsEnable() && !x.IsForOtherGameMode() && !HnSManager.AllHnSRoles.Contains(x) && !x.IsVanilla() && x is not CustomRoles.GM and not CustomRoles.Konan).ToList(); - public static Dictionary> DraftRoles = []; - public static Dictionary DraftResult = []; - - private static HashSet ReadyPlayers = []; - - public static void LoadCommands() + if (allRoles.Count < allPlayerIds.Length) { - AllCommands = - [ - new(["lt", "лт", "大厅关闭时间"], "", GetString("CommandDescription.LT"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, LTCommand, false), - new(["dump", "дамп", "лог", "导出日志"], "", GetString("CommandDescription.Dump"), Command.UsageLevels.Modded, Command.UsageTimes.Always, DumpCommand, false), - new(["v", "version", "в", "версия", "检查版本", "versão"], "", GetString("CommandDescription.Version"), Command.UsageLevels.Modded, Command.UsageTimes.Always, VersionCommand, false), - new(["cs", "changesetting", "измнастр", "修改设置", "mudarconfig", "mudarconfiguração"], "{name} {?} [?]", GetString("CommandDescription.ChangeSetting"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, ChangeSettingCommand, true, [GetString("CommandArgs.ChangeSetting.Name"), GetString("CommandArgs.ChangeSetting.UnknownValue"), GetString("CommandArgs.ChangeSetting.UnknownValue")]), - new(["w", "win", "winner", "победители", "获胜者", "vencedor"], "", GetString("CommandDescription.Winner"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, WinnerCommand, true), - new(["l", "lastresult", "л", "对局职业信息", "resultados", "ultimoresultado"], "", GetString("CommandDescription.LastResult"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, LastResultCommand, true), - new(["rn", "rename", "рн", "ренейм", "переименовать", "修改名称", "renomear"], "{name}", GetString("CommandDescription.Rename"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, RenameCommand, true, [GetString("CommandArgs.Rename.Name")]), - new(["hn", "hidename", "хн", "спрник", "隐藏姓名", "semnome", "escondernome"], "", GetString("CommandDescription.HideName"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, HideNameCommand, true), - new(["level", "лвл", "уровень", "修改等级", "nível"], "{level}", GetString("CommandDescription.Level"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, LevelCommand, true, [GetString("CommandArgs.Level.Level")]), - new(["n", "now", "н", "当前设置", "atual"], "", GetString("CommandDescription.Now"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, NowCommand, true), - new(["dis", "disconnect", "дис", "断连"], "{team}", GetString("CommandDescription.Disconnect"), Command.UsageLevels.Host, Command.UsageTimes.InGame, DisconnectCommand, true, [GetString("CommandArgs.Disconnect.Team")]), - new(["r", "р", "função"], "[role]", GetString("CommandDescription.R"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, RCommand, true, [GetString("CommandArgs.R.Role")]), - new(["up", "指定"], "{role}", GetString("CommandDescription.Up"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, UpCommand, true, [GetString("CommandArgs.Up.Role")]), - new(["setrole", "сетроль", "预设职业", "definir-função"], "{id} {role}", GetString("CommandDescription.SetRole"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, SetRoleCommand, true, [GetString("CommandArgs.SetRole.Id"), GetString("CommandArgs.SetRole.Role")]), - new(["h", "help", "хэлп", "хелп", "помощь", "帮助", "ajuda"], "", GetString("CommandDescription.Help"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, HelpCommand, true), - new(["gamestate", "gstate", "gs", "kcount", "kc", "кубийц", "гс", "статигры", "对局状态", "estadojogo", "status"], "", GetString("CommandDescription.KCount"), Command.UsageLevels.Everyone, Command.UsageTimes.InGame, KCountCommand, true), - new(["addmod", "добмодера", "指定协管", "moderador-add"], "{id}", GetString("CommandDescription.AddMod"), Command.UsageLevels.Host, Command.UsageTimes.Always, AddModCommand, true, [GetString("CommandArgs.AddMod.Id")]), - new(["deletemod", "убрмодера", "удмодера", "убратьмодера", "удалитьмодера", "移除协管", "moderador-remover"], "{id}", GetString("CommandDescription.DeleteMod"), Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteModCommand, true, [GetString("CommandArgs.DeleteMod.Id")]), - new(["combo", "комбо", "设置不会同时出现的职业", "combinação", "combinar"], "{mode} {role} {addon} [all]", GetString("CommandDescription.Combo"), Command.UsageLevels.Host, Command.UsageTimes.Always, ComboCommand, true, [GetString("CommandArgs.Combo.Mode"), GetString("CommandArgs.Combo.Role"), GetString("CommandArgs.Combo.Addon"), GetString("CommandArgs.Combo.All")]), - new(["eff", "effect", "эффект", "效果", "efeito"], "{effect}", GetString("CommandDescription.Effect"), Command.UsageLevels.Host, Command.UsageTimes.InGame, EffectCommand, true, [GetString("CommandArgs.Effect.Effect")]), - new(["afkexempt", "освафк", "афкосв", "挂机检测器不会检测", "afk-isentar"], "{id}", GetString("CommandDescription.AFKExempt"), Command.UsageLevels.Host, Command.UsageTimes.Always, AFKExemptCommand, true, [GetString("CommandArgs.AFKExempt.Id")]), - new(["m", "myrole", "м", "мояроль", "我的职业", "minhafunção"], "", GetString("CommandDescription.MyRole"), Command.UsageLevels.Everyone, Command.UsageTimes.InGame, MyRoleCommand, true), - new(["tpout", "тпаут", "传送出"], "", GetString("CommandDescription.TPOut"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, TPOutCommand, true), - new(["tpin", "тпин", "传送进"], "", GetString("CommandDescription.TPIn"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, TPInCommand, true), - new(["t", "template", "т", "темплейт", "模板"], "{tag}", GetString("CommandDescription.Template"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, TemplateCommand, true, [GetString("CommandArgs.Template.Tag")]), - new(["mw", "messagewait", "мв", "медленныйрежим", "消息冷却", "espera-mensagens"], "{duration}", GetString("CommandDescription.MessageWait"), Command.UsageLevels.Host, Command.UsageTimes.Always, MessageWaitCommand, true, [GetString("CommandArgs.MessageWait.Duration")]), - new(["death", "d", "д", "смерть", "死亡原因", "abate"], "", GetString("CommandDescription.Death"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, DeathCommand, true), - new(["say", "s", "сказать", "с", "说", "falar", "dizer"], "{message}", GetString("CommandDescription.Say"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, SayCommand, true, [GetString("CommandArgs.Say.Message")]), - new(["vote", "голос", "投票给", "votar"], "{id}", GetString("CommandDescription.Vote"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, VoteCommand, true, [GetString("CommandArgs.Vote.Id")]), - new(["ask", "спр", "спросить", "数学家提问", "perguntar"], "{number1} {number2}", GetString("CommandDescription.Ask"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AskCommand, true, [GetString("CommandArgs.Ask.Number1"), GetString("CommandArgs.Ask.Number2")]), - new(["ans", "answer", "отв", "ответить", "回答数学家问题", "responder"], "{number}", GetString("CommandDescription.Answer"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AnswerCommand, true, [GetString("CommandArgs.Answer.Number")]), - new(["qa", "вопротв", "回答测验大师问题", "questão-responder"], "{letter}", GetString("CommandDescription.QA"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, QACommand, true, [GetString("CommandArgs.QA.Letter")]), - new(["qs", "вопрпоказать", "检查测验大师问题", "questão-ver"], "", GetString("CommandDescription.QS"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, QSCommand, true), - new(["target", "цель", "腹语者标记", "alvo"], "{id}", GetString("CommandDescription.Target"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, TargetCommand, true, [GetString("CommandArgs.Target.Id")]), - new(["chat", "сообщение", "腹语者发送消息"], "{message}", GetString("CommandDescription.Chat"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, ChatCommand, true, [GetString("CommandArgs.Chat.Message")]), - new(["check", "проверить", "检查", "veificar"], "{id} {role}", GetString("CommandDescription.Check"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, CheckCommand, true, [GetString("CommandArgs.Check.Id"), GetString("CommandArgs.Check.Role")]), - new(["ban", "kick", "бан", "кик", "забанить", "кикнуть", "封禁", "踢出", "banir", "expulsar"], "{id}", GetString("CommandDescription.Ban"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, BanKickCommand, true, [GetString("CommandArgs.Ban.Id")]), - new(["exe", "выкинуть", "驱逐", "executar"], "{id}", GetString("CommandDescription.Exe"), Command.UsageLevels.Host, Command.UsageTimes.Always, ExeCommand, true, [GetString("CommandArgs.Exe.Id")]), - new(["kill", "убить", "击杀", "matar"], "{id}", GetString("CommandDescription.Kill"), Command.UsageLevels.Host, Command.UsageTimes.Always, KillCommand, true, [GetString("CommandArgs.Kill.Id")]), - new(["colour", "color", "цвет", "更改颜色", "cor"], "{color}", GetString("CommandDescription.Colour"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ColorCommand, true, [GetString("CommandArgs.Colour.Color")]), - new(["xf", "испр", "修复"], "", GetString("CommandDescription.XF"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, XFCommand, true), - new(["id", "guesslist", "айди", "ID列表"], "", GetString("CommandDescription.ID"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, IDCommand, true), - new(["changerole", "измроль", "修改职业", "mudar-função"], "{role}", GetString("CommandDescription.ChangeRole"), Command.UsageLevels.Host, Command.UsageTimes.InGame, ChangeRoleCommand, true, [GetString("CommandArgs.ChangeRole.Role")]), - new(["end", "завершить", "结束游戏", "encerrar", "finalizar", "fim"], "", GetString("CommandDescription.End"), Command.UsageLevels.Host, Command.UsageTimes.InGame, EndCommand, true), - new(["cosid", "костюм", "одежда", "服装ID"], "", GetString("CommandDescription.CosID"), Command.UsageLevels.Modded, Command.UsageTimes.Always, CosIDCommand, true), - new(["mt", "hy", "собрание", "开会/结束会议"], "", GetString("CommandDescription.MTHY"), Command.UsageLevels.Host, Command.UsageTimes.InGame, MTHYCommand, true), - new(["csd", "кзвук", "自定义播放声音"], "{sound}", GetString("CommandDescription.CSD"), Command.UsageLevels.Modded, Command.UsageTimes.Always, CSDCommand, true, [GetString("CommandArgs.CSD.Sound")]), - new(["sd", "взвук", "游戏中播放声音"], "{sound}", GetString("CommandDescription.SD"), Command.UsageLevels.Modded, Command.UsageTimes.Always, SDCommand, true, [GetString("CommandArgs.SD.Sound")]), - new(["gno", "гно", "猜数字"], "{number}", GetString("CommandDescription.GNO"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeathOrLobby, GNOCommand, true, [GetString("CommandArgs.GNO.Number")]), - new(["poll", "опрос", "发起调查", "enquete"], "{question} {answerA} {answerB} [answerC] [answerD]", GetString("CommandDescription.Poll"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.Always, PollCommand, true, [GetString("CommandArgs.Poll.Question"), GetString("CommandArgs.Poll.AnswerA"), GetString("CommandArgs.Poll.AnswerB"), GetString("CommandArgs.Poll.AnswerC"), GetString("CommandArgs.Poll.AnswerD")]), - new(["pv", "проголосовать", "选择调查选项"], "{vote}", GetString("CommandDescription.PV"), Command.UsageLevels.Everyone, Command.UsageTimes.Always, PVCommand, false, [GetString("CommandArgs.PV.Vote")]), - new(["hm", "мс", "мессенджер", "送信"], "{id}", GetString("CommandDescription.HM"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, HMCommand, true, [GetString("CommandArgs.HM.Id")]), - new(["decree", "указ", "总统命令", "decretar"], "{number}", GetString("CommandDescription.Decree"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DecreeCommand, true, [GetString("CommandArgs.Decree.Number")]), - new(["addvip", "добавитьвип", "добвип", "指定会员", "vip-add"], "{id}", GetString("CommandDescription.AddVIP"), Command.UsageLevels.Host, Command.UsageTimes.Always, AddVIPCommand, true, [GetString("CommandArgs.AddVIP.Id")]), - new(["deletevip", "удвип", "убрвип", "удалитьвип", "убратьвип", "删除会员", "vip-remover"], "{id}", GetString("CommandDescription.DeleteVIP"), Command.UsageLevels.Host, Command.UsageTimes.Always, DeleteVIPCommand, true, [GetString("CommandArgs.DeleteVIP.Id")]), - new(["assume", "предположить", "传销头目预测投票", "assumir"], "{id} {number}", GetString("CommandDescription.Assume"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, AssumeCommand, true, [GetString("CommandArgs.Assume.Id"), GetString("CommandArgs.Assume.Number")]), - new(["note", "заметка", "记者管理笔记", "nota", "anotar"], "{action} [?]", GetString("CommandDescription.Note"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, NoteCommand, true, [GetString("CommandArgs.Note.Action"), GetString("CommandArgs.Note.UnknownValue")]), - new(["os", "optionset", "шансроли", "设置职业生成概率"], "{chance} {role}", GetString("CommandDescription.OS"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, OSCommand, true, [GetString("CommandArgs.OS.Chance"), GetString("CommandArgs.OS.Role")]), - new(["negotiation", "neg", "наказание", "谈判方式", "negociar", "negociação"], "{number}", GetString("CommandDescription.Negotiation"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, NegotiationCommand, true, [GetString("CommandArgs.Negotiation.Number")]), - new(["mute", "мут", "禁言", "mutar", "silenciar"], "{id} [duration]", GetString("CommandDescription.Mute"), Command.UsageLevels.HostOrModerator, Command.UsageTimes.AfterDeathOrLobby, MuteCommand, true, [GetString("CommandArgs.Mute.Id"), GetString("CommandArgs.Mute.Duration")]), - new(["unmute", "размут", "解禁", "desmutar", "desilenciar"], "{id}", GetString("CommandDescription.Unmute"), Command.UsageLevels.Host, Command.UsageTimes.Always, UnmuteCommand, true, [GetString("CommandArgs.Unmute.Id")]), - new(["draftstart", "ds", "драфтстарт", "启用草稿", "todosescolhem-iniciar"], "", GetString("CommandDescription.DraftStart"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, DraftStartCommand, true), - new(["draft", "драфт", "选择草稿", "todosescolhem-escolher"], "{number}", GetString("CommandDescription.Draft"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, DraftCommand, true, [GetString("CommandArgs.Draft.Number")]), - new(["readycheck", "rc", "проверитьготовность", "准备检测", "verificação-de-prontidão"], "", GetString("CommandDescription.ReadyCheck"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, ReadyCheckCommand, true), - new(["ready", "готов", "准备", "pronto"], "", GetString("CommandDescription.Ready"), Command.UsageLevels.Everyone, Command.UsageTimes.InLobby, ReadyCommand, true), - new(["enableallroles", "всероли", "启用所有职业", "habilitar-todas-as-funções"], "", GetString("CommandDescription.EnableAllRoles"), Command.UsageLevels.Host, Command.UsageTimes.InLobby, EnableAllRolesCommand, true), - new(["achievements", "достижения", "成就", "conquistas"], "", GetString("CommandDescription.Achievements"), Command.UsageLevels.Modded, Command.UsageTimes.Always, AchievementsCommand, true), - new(["dn", "deathnote", "заметкамертвого", "死亡笔记"], "{name}", GetString("CommandDescription.DeathNote"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, DeathNoteCommand, true, [GetString("CommandArgs.DeathNote.Name")]), - new(["w", "whisper", "шепот", "ш", "私聊", "sussurrar"], "{id} {message}", GetString("CommandDescription.Whisper"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, WhisperCommand, true, [GetString("CommandArgs.Whisper.Id"), GetString("CommandArgs.Whisper.Message")]), - - // Commands with action handled elsewhere - new(["shoot", "guess", "bet", "bt", "st", "угадать", "бт", "猜测", "赌", "adivinhar"], "{id} {role}", GetString("CommandDescription.Guess"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Guess.Id"), GetString("CommandArgs.Guess.Role")]), - new(["tl", "sp", "jj", "trial", "суд", "засудить", "审判", "判", "julgar"], "{id}", GetString("CommandDescription.Trial"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Trial.Id")]), - new(["sw", "swap", "st", "свап", "свапнуть", "换票", "trocar"], "{id}", GetString("CommandDescription.Swap"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Swap.Id")]), - new(["compare", "cp", "cmp", "сравнить", "ср", "检查", "comparar"], "{id1} {id2}", GetString("CommandDescription.Compare"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Compare.Id1"), GetString("CommandArgs.Compare.Id2")]), - new(["ms", "mediumship", "medium", "медиум", "回答"], "{answer}", GetString("CommandDescription.Medium"), Command.UsageLevels.Everyone, Command.UsageTimes.InMeeting, (_, _, _, _) => { }, true, [GetString("CommandArgs.Medium.Answer")]), - new(["rv", "месть", "отомстить", "复仇"], "{id}", GetString("CommandDescription.Revenge"), Command.UsageLevels.Everyone, Command.UsageTimes.AfterDeath, (_, _, _, _) => { }, true, [GetString("CommandArgs.Revenge.Id")]) - ]; - } - - // Function to check if a player is a moderator - public static bool IsPlayerModerator(string friendCode) - { - friendCode = friendCode.Replace(':', '#'); - - if (friendCode == "" || friendCode == string.Empty || !Options.ApplyModeratorList.GetBool()) return false; - - const string friendCodesFilePath = "./EHR_DATA/Moderators.txt"; - - if (!File.Exists(friendCodesFilePath)) - { - File.WriteAllText(friendCodesFilePath, string.Empty); - return false; - } - - string[] friendCodes = File.ReadAllLines(friendCodesFilePath); - return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); - } - - // Function to check if a player is a VIP - public static bool IsPlayerVIP(string friendCode) - { - friendCode = friendCode.Replace(':', '#'); - - if (friendCode == "" || friendCode == string.Empty || !Options.ApplyVIPList.GetBool()) return false; - - const string friendCodesFilePath = "./EHR_DATA/VIPs.txt"; - - if (!File.Exists(friendCodesFilePath)) - { - File.WriteAllText(friendCodesFilePath, string.Empty); - return false; - } - - string[] friendCodes = File.ReadAllLines(friendCodesFilePath); - return friendCodes.Any(code => code.Contains(friendCode, StringComparison.OrdinalIgnoreCase)); - } - - public static bool Prefix(ChatController __instance) - { - if (__instance.quickChatField.visible) return true; - - if (__instance.freeChatField.textArea.text == string.Empty) return false; - - __instance.timeSinceLastMessage = 3f; - - string text = __instance.freeChatField.textArea.text.Trim(); - - if (ChatHistory.Count == 0 || ChatHistory[^1] != text) ChatHistory.Add(text); - - ChatControllerUpdatePatch.CurrentHistorySelection = ChatHistory.Count; - - string[] args = text.Split(' '); - var canceled = false; - var cancelVal = string.Empty; - Main.IsChatCommand = true; - - Logger.Info(text, "SendChat"); - - if (GuessManager.GuesserMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Judge.TrialMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (NiceSwapper.SwapMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (ParityCop.ParityCheckMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Councillor.MurderMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Mediumshiper.MsMsg(PlayerControl.LocalPlayer, text)) goto Canceled; - if (Mafia.MafiaMsgCheck(PlayerControl.LocalPlayer, text)) goto Canceled; - - Main.IsChatCommand = false; - - if (text.StartsWith('/')) - { - foreach (Command command in AllCommands) - { - if (!command.IsThisCommand(text)) continue; - - Logger.Info($" Recognized command: {text}", "ChatCommand"); - Main.IsChatCommand = true; - - if (!command.CanUseCommand(PlayerControl.LocalPlayer)) - { - Utils.SendMessage(GetString("Commands.NoAccess"), PlayerControl.LocalPlayer.PlayerId); - goto Canceled; - } - - command.Action(__instance, PlayerControl.LocalPlayer, text, args); - if (command.IsCanceled) goto Canceled; - - break; - } - - Statistics.HasUsedAnyCommand = true; - } - - if (CheckMute(PlayerControl.LocalPlayer.PlayerId)) goto Canceled; - - if (GameStates.InGame && (Silencer.ForSilencer.Contains(PlayerControl.LocalPlayer.PlayerId) || (Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].Role is Dad { IsEnable: true } dad && dad.UsingAbilities.Contains(Dad.Ability.GoForMilk))) && PlayerControl.LocalPlayer.IsAlive()) goto Canceled; - - if (GameStates.IsInGame && (PlayerControl.LocalPlayer.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting)) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.Lovers) || PlayerControl.LocalPlayer.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) - { - PlayerControl otherLover = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId); - string title = PlayerControl.LocalPlayer.GetRealName(); - ChatUpdatePatch.LoversMessage = true; - Utils.SendMessage(text, otherLover.PlayerId, title); - Utils.SendMessage(text, PlayerControl.LocalPlayer.PlayerId, title); - LateTask.New(() => ChatUpdatePatch.LoversMessage = false, Math.Max(AmongUsClient.Instance.Ping / 1000f * 2f, Main.MessageWait.Value + 0.5f), log: false); - } - - goto Canceled; - } - - goto Skip; - Canceled: - Main.IsChatCommand = false; - canceled = true; - Skip: - - if (canceled) - { - Logger.Info("Command Canceled", "ChatCommand"); - __instance.freeChatField.textArea.Clear(); - __instance.freeChatField.textArea.SetText(cancelVal); - } - else ChatManager.SendMessage(PlayerControl.LocalPlayer, text); - - if (text.Contains("666") && PlayerControl.LocalPlayer.Is(CustomRoles.Gamer)) - Achievements.Type.WhatTheHell.Complete(); - - return !canceled; - } - - private static void RequestCommandProcessingFromHost(string methodName, string text) - { - PlayerControl pc = PlayerControl.LocalPlayer; - MessageWriter w = AmongUsClient.Instance.StartRpc(pc.NetId, (byte)CustomRPC.RequestCommandProcessing); - w.Write(methodName); - w.Write(pc.PlayerId); - w.Write(text); - w.EndMessage(); - } - - // --------------------------------------------------------------------------------------------------------------------------------------------- - - private static void WhisperCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (Options.DisableWhisperCommand.GetBool()) - { - Utils.SendMessage("\n", player.PlayerId, GetString("WhisperDisabled")); - return; - } - - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(WhisperCommand), text); - return; - } - - if (args.Length < 3 || !byte.TryParse(args[1], out byte targetId)) return; - if (!player.IsLocalPlayer()) ChatManager.SendPreviousMessagesToAll(); - Utils.SendMessage(args[2..].Join(delimiter: " "), targetId, string.Format(GetString("WhisperTitle"), player.PlayerId.ColoredPlayerName())); - } - - private static void DeathNoteCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(DeathNoteCommand), text); - return; - } - - if (!player.Is(CustomRoles.NoteKiller) || args.Length < 2) return; - - if (!player.IsLocalPlayer()) ChatManager.SendPreviousMessagesToAll(); - - var guess = args[1].ToLower(); - guess = char.ToUpper(guess[0]) + guess[1..]; - var deadPlayer = NoteKiller.RealNames.GetKeyByValue(guess); - - if (deadPlayer == default && (!NoteKiller.RealNames.TryGetValue(default, out var name) || name != guess)) - { - Utils.SendMessage(GetString("DeathNoteCommand.WrongName"), player.PlayerId); - return; - } - - var pc = deadPlayer.GetPlayer(); - - if (pc == null || !pc.IsAlive()) - { - Utils.SendMessage(GetString("DeathNoteCommand.PlayerNotFoundOrDead"), player.PlayerId); - return; - } - - var state = Main.PlayerStates[pc.PlayerId]; - state.deathReason = PlayerState.DeathReason.Kill; - state.RealKiller.ID = player.PlayerId; - state.SetDead(); - - pc.RpcExileV2(); - SoundManager.Instance.PlaySound(pc.KillSfx, false, 0.8f); - - Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathNoteCommand.Success"), deadPlayer.ColoredPlayerName())); - Utils.SendMessage(string.Format(GetString("DeathNoteCommand.SuccessForOthers"), deadPlayer.ColoredPlayerName())); - - NoteKiller.Kills++; - } - - private static void AchievementsCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - Func ToAchievementString = x => $"{GetString($"Achievement.{x}")} - {GetString($"Achievement.{x}.Description")}"; - - Achievements.Type[] allAchievements = Enum.GetValues(); - Achievements.Type[] union = Achievements.CompletedAchievements.Union(Achievements.WaitingAchievements).ToArray(); - string completedAchievements = $"{union.Join(ToAchievementString, "\n")}"; - string incompleteAchievements = $"{allAchievements.Except(union).Join(ToAchievementString, "\n")}"; - - Utils.SendMessage(incompleteAchievements, player.PlayerId, GetString("IncompleteAchievementsTitle")); - Utils.SendMessage(completedAchievements, player.PlayerId, GetString("CompletedAchievementsTitle") + $" <#00a5ff>(<#00ffa5>{union.Length}/{allAchievements.Length})"); - } - - private static void EnableAllRolesCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - Options.CustomRoleSpawnChances.Values.DoIf(x => x.GetValue() == 0, x => x.SetValue(1)); - } - - private static void ReadyCheckCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - Utils.SendMessage(GetString("ReadyCheckMessage"), title: GetString("ReadyCheckTitle")); - ReadyPlayers = [player.PlayerId]; - Main.Instance.StartCoroutine(Countdown()); + Utils.SendMessage(GetString("DraftNotEnoughRoles"), player.PlayerId); return; - - IEnumerator Countdown() - { - var timer = 30f; - - while (timer > 0f) - { - if (!GameStates.IsLobby) yield break; - - if (Main.AllPlayerControls.Select(x => x.PlayerId).All(ReadyPlayers.Contains)) break; - - timer -= Time.deltaTime; - yield return null; - } - - byte[] notReadyPlayers = Main.AllPlayerControls.Select(x => x.PlayerId).Except(ReadyPlayers).ToArray(); - - if (notReadyPlayers.Length == 0) - Utils.SendMessage("\n", player.PlayerId, GetString("EveryoneReadyTitle")); - else - Utils.SendMessage(string.Join(", ", notReadyPlayers.Select(x => x.ColoredPlayerName())), player.PlayerId, string.Format(GetString("PlayersNotReadyTitle"), notReadyPlayers.Length)); - } } - private static void ReadyCommand(ChatController __instance, PlayerControl player, string text, string[] args) + int maxNeutrals = Options.FactionMinMaxSettings[Team.Neutral].MaxSetting.GetInt(); + IEnumerable impRoles = allRoles.Where(x => x.IsImpostor()).Shuffle().Take(Options.FactionMinMaxSettings[Team.Impostor].MaxSetting.GetInt()); + IEnumerable nkRoles = allRoles.Where(x => x.IsNK()).Shuffle().Take(maxNeutrals); + IEnumerable nnkRoles = allRoles.Where(x => x.IsNonNK()).Shuffle().Take(maxNeutrals); + + allRoles.RemoveAll(x => x.IsImpostor()); + allRoles.RemoveAll(x => x.IsNK()); + allRoles.RemoveAll(x => x.IsNonNK()); + + DraftRoles = allRoles.CombineWith(impRoles, nkRoles, nnkRoles).Shuffle().Partition(allPlayerIds.Length).Zip(allPlayerIds).ToDictionary(x => x.Second, x => x.First.Take(5).ToList()); + + foreach ((byte id, List roles) in DraftRoles) { - ReadyPlayers.Add(player.PlayerId); + IEnumerable roleList = roles.Select((x, i) => $"{i + 1}. {x.ToColoredString()}"); + string msg = string.Format(GetString("DraftStart"), string.Join('\n', roleList)); + Utils.SendMessage(msg, id, GetString("DraftTitle")); + } + } + + private static void DraftCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (DraftRoles.Count == 0 || !DraftRoles.TryGetValue(player.PlayerId, out List roles) || args.Length < 2 || !int.TryParse(args[1], out int chosenIndex) || roles.Count < chosenIndex) return; + + CustomRoles role = roles[chosenIndex - 1]; + DraftResult[player.PlayerId] = role; + Utils.SendMessage(string.Format(GetString("DraftChosen"), role.ToColoredString()), player.PlayerId, GetString("DraftTitle")); + } + + private static void MuteCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!player.IsHost() && (GameStates.InGame || MutedPlayers.ContainsKey(player.PlayerId))) return; + + if (args.Length < 3 || !byte.TryParse(args[1], out byte id) || id.IsHost()) return; + + int duration = !int.TryParse(args[2], out int dur) ? 60 : dur; + MutedPlayers[id] = (Utils.TimeStamp, duration); + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerMuted"), id.ColoredPlayerName(), duration)); + Utils.SendMessage("\n", id, string.Format(GetString("YouMuted"), player.PlayerId.ColoredPlayerName(), duration)); + if (!player.IsHost()) Utils.SendMessage("\n", 0, string.Format(GetString("ModeratorMuted"), player.PlayerId.ColoredPlayerName(), id.ColoredPlayerName(), duration)); + } + + private static void UnmuteCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !byte.TryParse(args[1], out byte id)) return; + + MutedPlayers.Remove(id); + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerUnmuted"), id.ColoredPlayerName())); + Utils.SendMessage("\n", id, string.Format(GetString("YouUnmuted"), player.PlayerId.ColoredPlayerName())); + } + + private static void NegotiationCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(NegotiationCommand), text); + return; } - private static void DraftStartCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (!Negotiator.On || !player.IsAlive() || args.Length < 2 || !int.TryParse(args[1], out int index)) return; + + Negotiator.ReceiveCommand(player, index); + } + + private static void OSCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!GameStates.IsLobby || args.Length < 3 || !byte.TryParse(args[1], out byte chance) || chance > 100 || chance % 5 != 0 || !GetRoleByName(string.Join(' ', args[2..]), out CustomRoles role) || !Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem option)) return; + + if (role.IsAdditionRole()) { - DraftResult = []; + option.SetValue(chance == 0 ? 0 : 1); + if (!Options.CustomAdtRoleSpawnRate.TryGetValue(role, out IntegerOptionItem adtOption)) return; - byte[] allPlayerIds = Main.AllPlayerControls.Select(x => x.PlayerId).ToArray(); - List allRoles = Enum.GetValues().Where(x => x < CustomRoles.NotAssigned && x.IsEnable() && !x.IsForOtherGameMode() && !HnSManager.AllHnSRoles.Contains(x) && !x.IsVanilla() && x is not CustomRoles.GM and not CustomRoles.Konan).ToList(); + adtOption.SetValue(chance / 5); + } + else + option.SetValue(chance / 5); + } - if (allRoles.Count < allPlayerIds.Length) - { - Utils.SendMessage(GetString("DraftNotEnoughRoles"), player.PlayerId); - return; - } - - int maxNeutrals = Options.FactionMinMaxSettings[Team.Neutral].MaxSetting.GetInt(); - IEnumerable impRoles = allRoles.Where(x => x.IsImpostor()).Shuffle().Take(Options.FactionMinMaxSettings[Team.Impostor].MaxSetting.GetInt()); - IEnumerable nkRoles = allRoles.Where(x => x.IsNK()).Shuffle().Take(maxNeutrals); - IEnumerable nnkRoles = allRoles.Where(x => x.IsNonNK()).Shuffle().Take(maxNeutrals); - - allRoles.RemoveAll(x => x.IsImpostor()); - allRoles.RemoveAll(x => x.IsNK()); - allRoles.RemoveAll(x => x.IsNonNK()); - - DraftRoles = allRoles.CombineWith(impRoles, nkRoles, nnkRoles).Shuffle().Partition(allPlayerIds.Length).Zip(allPlayerIds).ToDictionary(x => x.Second, x => x.First.Take(5).ToList()); - - foreach ((byte id, List roles) in DraftRoles) - { - IEnumerable roleList = roles.Select((x, i) => $"{i + 1}. {x.ToColoredString()}"); - string msg = string.Format(GetString("DraftStart"), string.Join('\n', roleList)); - Utils.SendMessage(msg, id, GetString("DraftTitle")); - } + private static void NoteCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(NoteCommand), text); + return; } - private static void DraftCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (player.Is(CustomRoles.Journalist) && player.IsAlive()) { - if (DraftRoles.Count == 0 || !DraftRoles.TryGetValue(player.PlayerId, out List roles) || args.Length < 2 || !int.TryParse(args[1], out int chosenIndex) || roles.Count < chosenIndex) return; - - CustomRoles role = roles[chosenIndex - 1]; - DraftResult[player.PlayerId] = role; - Utils.SendMessage(string.Format(GetString("DraftChosen"), role.ToColoredString()), player.PlayerId, GetString("DraftTitle")); - } - - private static void MuteCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!player.IsHost() && (GameStates.InGame || MutedPlayers.ContainsKey(player.PlayerId))) return; - - if (args.Length < 3 || !byte.TryParse(args[1], out byte id) || id.IsHost()) return; - - int duration = !int.TryParse(args[2], out int dur) ? 60 : dur; - MutedPlayers[id] = (Utils.TimeStamp, duration); - Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerMuted"), id.ColoredPlayerName(), duration)); - Utils.SendMessage("\n", id, string.Format(GetString("YouMuted"), player.PlayerId.ColoredPlayerName(), duration)); - if (!player.IsHost()) Utils.SendMessage("\n", 0, string.Format(GetString("ModeratorMuted"), player.PlayerId.ColoredPlayerName(), id.ColoredPlayerName(), duration)); - } - - private static void UnmuteCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 2 || !byte.TryParse(args[1], out byte id)) return; - - MutedPlayers.Remove(id); - Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerUnmuted"), id.ColoredPlayerName())); - Utils.SendMessage("\n", id, string.Format(GetString("YouUnmuted"), player.PlayerId.ColoredPlayerName())); - } - - private static void NegotiationCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(NegotiationCommand), text); - return; - } - - if (!Negotiator.On || !player.IsAlive() || args.Length < 2 || !int.TryParse(args[1], out int index)) return; - - Negotiator.ReceiveCommand(player, index); - } - - private static void OSCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!GameStates.IsLobby || args.Length < 3 || !byte.TryParse(args[1], out byte chance) || chance > 100 || chance % 5 != 0 || !GetRoleByName(string.Join(' ', args[2..]), out CustomRoles role) || !Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem option)) return; - - if (role.IsAdditionRole()) - { - option.SetValue(chance == 0 ? 0 : 1); - if (!Options.CustomAdtRoleSpawnRate.TryGetValue(role, out IntegerOptionItem adtOption)) return; - - adtOption.SetValue(chance / 5); - } - else - option.SetValue(chance / 5); - } - - private static void NoteCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(NoteCommand), text); - return; - } - - if (player.Is(CustomRoles.Journalist) && player.IsAlive()) - { - if (PlayerControl.LocalPlayer.PlayerId != player.PlayerId) ChatManager.SendPreviousMessagesToAll(); - - Journalist.OnReceiveCommand(player, args); - } - } - - private static void AssumeCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(AssumeCommand), text); - return; - } - - if (args.Length < 3 || !byte.TryParse(args[1], out byte id) || !int.TryParse(args[2], out int num) || !player.Is(CustomRoles.Assumer) || !player.IsAlive()) return; - if (PlayerControl.LocalPlayer.PlayerId != player.PlayerId) ChatManager.SendPreviousMessagesToAll(); - Assumer.Assume(player.PlayerId, id, num); + Journalist.OnReceiveCommand(player, args); + } + } + + private static void AssumeCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(AssumeCommand), text); + return; } - private static void DeleteVIPCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (args.Length < 3 || !byte.TryParse(args[1], out byte id) || !int.TryParse(args[2], out int num) || !player.Is(CustomRoles.Assumer) || !player.IsAlive()) return; + + if (PlayerControl.LocalPlayer.PlayerId != player.PlayerId) ChatManager.SendPreviousMessagesToAll(); + + Assumer.Assume(player.PlayerId, id, num); + } + + private static void DeleteVIPCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !byte.TryParse(args[1], out byte VIPId)) return; + + PlayerControl VIPPc = Utils.GetPlayerById(VIPId); + if (VIPPc == null) return; + + string fc = VIPPc.FriendCode.Replace(':', '#'); + if (!IsPlayerVIP(fc)) Utils.SendMessage(GetString("PlayerNotVIP"), player.PlayerId); + + string[] lines = File.ReadAllLines("./EHR_DATA/VIPs.txt").Where(line => !line.Contains(fc)).ToArray(); + File.WriteAllLines("./EHR_DATA/VIPs.txt", lines); + Utils.SendMessage(GetString("PlayerRemovedFromVIPList"), player.PlayerId); + } + + private static void AddVIPCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !byte.TryParse(args[1], out byte newVIPId)) return; + + PlayerControl newVIPPc = Utils.GetPlayerById(newVIPId); + if (newVIPPc == null) return; + + string fc = newVIPPc.FriendCode.Replace(':', '#'); + if (IsPlayerVIP(fc)) Utils.SendMessage(GetString("PlayerAlreadyVIP"), player.PlayerId); + + File.AppendAllText("./EHR_DATA/VIPs.txt", $"\n{fc}"); + Utils.SendMessage(GetString("PlayerAddedToVIPList"), player.PlayerId); + } + + private static void DecreeCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) { - if (args.Length < 2 || !byte.TryParse(args[1], out byte VIPId)) return; - - PlayerControl VIPPc = Utils.GetPlayerById(VIPId); - if (VIPPc == null) return; - - string fc = VIPPc.FriendCode.Replace(':', '#'); - if (!IsPlayerVIP(fc)) Utils.SendMessage(GetString("PlayerNotVIP"), player.PlayerId); - - string[] lines = File.ReadAllLines("./EHR_DATA/VIPs.txt").Where(line => !line.Contains(fc)).ToArray(); - File.WriteAllLines("./EHR_DATA/VIPs.txt", lines); - Utils.SendMessage(GetString("PlayerRemovedFromVIPList"), player.PlayerId); + RequestCommandProcessingFromHost(nameof(DecreeCommand), text); + return; } - private static void AddVIPCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (!player.Is(CustomRoles.President)) return; + + if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); + + LateTask.New(() => { - if (args.Length < 2 || !byte.TryParse(args[1], out byte newVIPId)) return; - - PlayerControl newVIPPc = Utils.GetPlayerById(newVIPId); - if (newVIPPc == null) return; - - string fc = newVIPPc.FriendCode.Replace(':', '#'); - if (IsPlayerVIP(fc)) Utils.SendMessage(GetString("PlayerAlreadyVIP"), player.PlayerId); - - File.AppendAllText("./EHR_DATA/VIPs.txt", $"\n{fc}"); - Utils.SendMessage(GetString("PlayerAddedToVIPList"), player.PlayerId); - } - - private static void DecreeCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) + if (args.Length < 2) { - RequestCommandProcessingFromHost(nameof(DecreeCommand), text); + Utils.SendMessage(President.GetHelpMessage(), player.PlayerId); return; } - if (!player.Is(CustomRoles.President)) return; + President.UseDecree(player, args[1]); + }, 0.2f, log: false); + } - if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); + private static void HMCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(HMCommand), text); + return; + } - LateTask.New(() => + if (Messenger.Sent.Contains(player.PlayerId) || args.Length < 2 || !int.TryParse(args[1], out int id) || id is > 3 or < 1) return; + + Main.Instance.StartCoroutine(SendOnMeeting()); + return; + + IEnumerator SendOnMeeting() + { + bool meeting = GameStates.IsMeeting; + while (!GameStates.IsMeeting) yield return null; + + if (!meeting) yield return new WaitForSeconds(7f); + + PlayerControl killer = player.GetRealKiller(); + if (killer == null && id != 3) yield break; + + Team team = player.GetTeam(); + + string message = id switch { - if (args.Length < 2) + 1 => string.Format(GetString("MessengerMessage.1"), GetString(Main.PlayerStates[killer.PlayerId].LastRoom.RoomId.ToString())), + 2 => string.Format(GetString("MessengerMessage.2"), killer.GetCustomRole().ToColoredString()), + _ => string.Format(GetString("MessengerMessage.3"), Utils.ColorString(team.GetTeamColor(), GetString($"{team}"))) + }; + + Utils.SendMessage(message, title: string.Format(GetString("MessengerTitle"), player.PlayerId.ColoredPlayerName())); + Messenger.Sent.Add(player.PlayerId); + } + } + + // Credit: Drakos for the base code + private static void PollCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + PollVotes.Clear(); + PollAnswers.Clear(); + PollVoted.Clear(); + + if (!args.Any(x => x.Contains('?'))) + { + Utils.SendMessage(GetString("PollUsage"), player.PlayerId); + return; + } + + PollTimer = 60f; + + int splitIndex = Array.IndexOf(args, args.First(x => x.Contains('?'))) + 1; + string[] answers = args.Skip(splitIndex).ToArray(); + + string msg = string.Join(" ", args.Take(splitIndex).Skip(1)) + "\n"; + + for (var i = 0; i < Math.Clamp(answers.Length, 2, 5); i++) + { + var choiceLetter = (char)(i + 65); + msg += Utils.ColorString(RandomColor(), $"{char.ToUpper(choiceLetter)}) {answers[i]}\n"); + PollVotes[choiceLetter] = 0; + PollAnswers[choiceLetter] = $"〖 {answers[i]} 〗"; + } + + msg += $"\n{GetString("Poll.Begin")}\n{GetString("Poll.TimeInfo")}"; + string title = GetString("Poll.Title"); + Utils.SendMessage(msg, title: title); + + Main.Instance.StartCoroutine(StartPollCountdown()); + return; + + IEnumerator StartPollCountdown() + { + if (PollVotes.Count == 0) yield break; + + bool playervoted = Main.AllPlayerControls.Length - 1 > PollVotes.Values.Sum(); + + var resendTimer = 0f; + + while (playervoted && PollTimer > 0f) + { + playervoted = Main.AllPlayerControls.Length - 1 > PollVotes.Values.Sum(); + PollTimer -= Time.deltaTime; + resendTimer += Time.deltaTime; + + if (resendTimer >= 15f) { - Utils.SendMessage(President.GetHelpMessage(), player.PlayerId); - return; + resendTimer = 0f; + Utils.SendMessage(msg, title: title); } - President.UseDecree(player, args[1]); - }, 0.2f, log: false); - } - - private static void HMCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(HMCommand), text); - return; + yield return null; } - if (Messenger.Sent.Contains(player.PlayerId) || args.Length < 2 || !int.TryParse(args[1], out int id) || id is > 3 or < 1) return; - - Main.Instance.StartCoroutine(SendOnMeeting()); - return; - - IEnumerator SendOnMeeting() - { - bool meeting = GameStates.IsMeeting; - while (!GameStates.IsMeeting) yield return null; - - if (!meeting) yield return new WaitForSeconds(7f); - - PlayerControl killer = player.GetRealKiller(); - if (killer == null && id != 3) yield break; - - Team team = player.GetTeam(); - - string message = id switch - { - 1 => string.Format(GetString("MessengerMessage.1"), GetString(Main.PlayerStates[killer.PlayerId].LastRoom.RoomId.ToString())), - 2 => string.Format(GetString("MessengerMessage.2"), killer.GetCustomRole().ToColoredString()), - _ => string.Format(GetString("MessengerMessage.3"), Utils.ColorString(team.GetTeamColor(), GetString($"{team}"))) - }; - - Utils.SendMessage(message, title: string.Format(GetString("MessengerTitle"), player.PlayerId.ColoredPlayerName())); - Messenger.Sent.Add(player.PlayerId); - } + DetermineResults(); } - // Credit: Drakos for the base code - private static void PollCommand(ChatController __instance, PlayerControl player, string text, string[] args) + static void DetermineResults() { + int maxVotes = PollVotes.Values.Max(); + KeyValuePair[] winners = PollVotes.Where(x => x.Value == maxVotes).ToArray(); + + string msg = winners.Length == 1 + ? string.Format(GetString("Poll.Winner"), winners[0].Key, PollAnswers[winners[0].Key], winners[0].Value) + + PollVotes.Where(x => x.Key != winners[0].Key).Aggregate("", (s, t) => s + $"{t.Key} / {t.Value} {PollAnswers[t.Key]}\n") + : string.Format(GetString("Poll.Tie"), string.Join(" & ", winners.Select(x => $"{x.Key}{PollAnswers[x.Key]}")), maxVotes); + + Utils.SendMessage(msg, title: Utils.ColorString(new(0, 255, 165, 255), GetString("PollResultTitle"))); + PollVotes.Clear(); PollAnswers.Clear(); PollVoted.Clear(); + } - if (!args.Any(x => x.Contains('?'))) - { - Utils.SendMessage(GetString("PollUsage"), player.PlayerId); - return; - } + static Color32 RandomColor() + { + byte[] colors = IRandom.Sequence(3, 0, 160).Select(x => (byte)x).ToArray(); + return new(colors[0], colors[1], colors[2], 255); + } + } - PollTimer = 60f; - - int splitIndex = Array.IndexOf(args, args.First(x => x.Contains('?'))) + 1; - string[] answers = args.Skip(splitIndex).ToArray(); - - string msg = string.Join(" ", args.Take(splitIndex).Skip(1)) + "\n"; - - for (var i = 0; i < Math.Clamp(answers.Length, 2, 5); i++) - { - var choiceLetter = (char)(i + 65); - msg += Utils.ColorString(RandomColor(), $"{char.ToUpper(choiceLetter)}) {answers[i]}\n"); - PollVotes[choiceLetter] = 0; - PollAnswers[choiceLetter] = $"〖 {answers[i]} 〗"; - } - - msg += $"\n{GetString("Poll.Begin")}\n{GetString("Poll.TimeInfo")}"; - string title = GetString("Poll.Title"); - Utils.SendMessage(msg, title: title); - - Main.Instance.StartCoroutine(StartPollCountdown()); + private static void PVCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (PollVotes.Count == 0) + { + Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); return; - - IEnumerator StartPollCountdown() - { - if (PollVotes.Count == 0) yield break; - - bool playervoted = Main.AllPlayerControls.Length - 1 > PollVotes.Values.Sum(); - - var resendTimer = 0f; - - while (playervoted && PollTimer > 0f) - { - playervoted = Main.AllPlayerControls.Length - 1 > PollVotes.Values.Sum(); - PollTimer -= Time.deltaTime; - resendTimer += Time.deltaTime; - - if (resendTimer >= 15f) - { - resendTimer = 0f; - Utils.SendMessage(msg, title: title); - } - - yield return null; - } - - DetermineResults(); - } - - static void DetermineResults() - { - int maxVotes = PollVotes.Values.Max(); - KeyValuePair[] winners = PollVotes.Where(x => x.Value == maxVotes).ToArray(); - - string msg = winners.Length == 1 - ? string.Format(GetString("Poll.Winner"), winners[0].Key, PollAnswers[winners[0].Key], winners[0].Value) + - PollVotes.Where(x => x.Key != winners[0].Key).Aggregate("", (s, t) => s + $"{t.Key} / {t.Value} {PollAnswers[t.Key]}\n") - : string.Format(GetString("Poll.Tie"), string.Join(" & ", winners.Select(x => $"{x.Key}{PollAnswers[x.Key]}")), maxVotes); - - Utils.SendMessage(msg, title: Utils.ColorString(new(0, 255, 165, 255), GetString("PollResultTitle"))); - - PollVotes.Clear(); - PollAnswers.Clear(); - PollVoted.Clear(); - } - - static Color32 RandomColor() - { - byte[] colors = IRandom.Sequence(3, 0, 160).Select(x => (byte)x).ToArray(); - return new(colors[0], colors[1], colors[2], 255); - } } - private static void PVCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (PollVoted.Contains(player.PlayerId)) { - if (PollVotes.Count == 0) - { - Utils.SendMessage(GetString("Poll.Inactive"), player.PlayerId); - return; - } - - if (PollVoted.Contains(player.PlayerId)) - { - Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); - return; - } - - if (args.Length != 2 || !char.TryParse(args[1], out char vote) || !PollVotes.ContainsKey(char.ToUpper(vote))) - { - Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); - return; - } - - vote = char.ToUpper(vote); - - PollVoted.Add(player.PlayerId); - PollVotes[vote]++; - Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, PollVotes[vote]), player.PlayerId); + Utils.SendMessage(GetString("Poll.AlreadyVoted"), player.PlayerId); + return; } - private static void HelpCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (args.Length != 2 || !char.TryParse(args[1], out char vote) || !PollVotes.ContainsKey(char.ToUpper(vote))) { - Utils.ShowHelp(player.PlayerId); + Utils.SendMessage(GetString("Poll.VotingInfo"), player.PlayerId); + return; } - private static void DumpCommand(ChatController __instance, PlayerControl player, string text, string[] args) + vote = char.ToUpper(vote); + + PollVoted.Add(player.PlayerId); + PollVotes[vote]++; + Utils.SendMessage(string.Format(GetString("Poll.YouVoted"), vote, PollVotes[vote]), player.PlayerId); + } + + private static void HelpCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Utils.ShowHelp(player.PlayerId); + } + + private static void DumpCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Utils.DumpLog(); + } + + private static void GNOCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!GameStates.IsLobby && player.IsAlive()) { - Utils.DumpLog(); + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + return; } - private static void GNOCommand(ChatController __instance, PlayerControl player, string text, string[] args) + string subArgs = args.Length != 2 ? "" : args[1]; + + if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) { - if (!GameStates.IsLobby && player.IsAlive()) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - return; - } + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + return; + } - string subArgs = args.Length != 2 ? "" : args[1]; + if (guessedNo is < 0 or > 99) + { + Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); + return; + } - if (subArgs == "" || !int.TryParse(subArgs, out int guessedNo)) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - return; - } + int targetNumber = Main.GuessNumber[player.PlayerId][0]; - if (guessedNo is < 0 or > 99) - { - Utils.SendMessage(GetString("GNoCommandInfo"), player.PlayerId); - return; - } + if (Main.GuessNumber[player.PlayerId][0] == -1) + { + var rand = IRandom.Instance; + Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); + targetNumber = Main.GuessNumber[player.PlayerId][0]; + } - int targetNumber = Main.GuessNumber[player.PlayerId][0]; + Main.GuessNumber[player.PlayerId][1]--; - if (Main.GuessNumber[player.PlayerId][0] == -1) - { - var rand = IRandom.Instance; - Main.GuessNumber[player.PlayerId][0] = rand.Next(0, 100); - targetNumber = Main.GuessNumber[player.PlayerId][0]; - } - - Main.GuessNumber[player.PlayerId][1]--; - - if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) - { - Main.GuessNumber[player.PlayerId][0] = -1; - Main.GuessNumber[player.PlayerId][1] = 7; - Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); - return; - } - - if (guessedNo < targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - return; - } - - if (guessedNo > targetNumber) - { - Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); - return; - } - - Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + if (Main.GuessNumber[player.PlayerId][1] == 0 && guessedNo != targetNumber) + { Main.GuessNumber[player.PlayerId][0] = -1; Main.GuessNumber[player.PlayerId][1] = 7; + Utils.SendMessage(string.Format(GetString("GNoLost"), targetNumber), player.PlayerId); + return; } - private static void SDCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (guessedNo < targetNumber) { - if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) return; - - RPC.PlaySoundRPC(player.PlayerId, (Sounds)sound1); + Utils.SendMessage(string.Format(GetString("GNoLow"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + return; } - private static void CSDCommand(ChatController __instance, PlayerControl player, string text, string[] args) + if (guessedNo > targetNumber) { - string subArgs = text.Remove(0, 3); - player.RPCPlayCustomSound(subArgs.Trim()); + Utils.SendMessage(string.Format(GetString("GNoHigh"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + return; } - private static void MTHYCommand(ChatController __instance, PlayerControl player, string text, string[] args) + Utils.SendMessage(string.Format(GetString("GNoWon"), Main.GuessNumber[player.PlayerId][1]), player.PlayerId); + Main.GuessNumber[player.PlayerId][0] = -1; + Main.GuessNumber[player.PlayerId][1] = 7; + } + + private static void SDCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 1 || !int.TryParse(args[1], out int sound1)) return; + + RPC.PlaySoundRPC(player.PlayerId, (Sounds)sound1); + } + + private static void CSDCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = text.Remove(0, 3); + player.RPCPlayCustomSound(subArgs.Trim()); + } + + private static void MTHYCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (GameStates.IsMeeting) + MeetingHud.Instance.RpcClose(); + else + player.NoCheckStartMeeting(null, true); + } + + private static void CosIDCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + NetworkedPlayerInfo.PlayerOutfit of = player.Data.DefaultOutfit; + Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); + Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); + Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); + Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); + Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); + Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); + } + + private static void EndCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + } + + private static void ChangeRoleCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (GameStates.IsLobby || !player.FriendCode.GetDevUser().IsUp) return; + + string subArgs = text.Remove(0, 8); + string setRole = FixRoleNameInput(subArgs.Trim()); + + foreach (CustomRoles rl in Enum.GetValues()) { - if (GameStates.IsMeeting) - MeetingHud.Instance.RpcClose(); - else - player.NoCheckStartMeeting(null, true); - } + if (rl.IsVanilla()) continue; - private static void CosIDCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - NetworkedPlayerInfo.PlayerOutfit of = player.Data.DefaultOutfit; - Logger.Warn($"ColorId: {of.ColorId}", "Get Cos Id"); - Logger.Warn($"PetId: {of.PetId}", "Get Cos Id"); - Logger.Warn($"HatId: {of.HatId}", "Get Cos Id"); - Logger.Warn($"SkinId: {of.SkinId}", "Get Cos Id"); - Logger.Warn($"VisorId: {of.VisorId}", "Get Cos Id"); - Logger.Warn($"NamePlateId: {of.NamePlateId}", "Get Cos Id"); - } + string roleName = GetString(rl.ToString()).ToLower().Trim(); - private static void EndCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - } - - private static void ChangeRoleCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (GameStates.IsLobby || !player.FriendCode.GetDevUser().IsUp) return; - - string subArgs = text.Remove(0, 8); - string setRole = FixRoleNameInput(subArgs.Trim()); - - foreach (CustomRoles rl in Enum.GetValues()) + if (setRole.Contains(roleName)) { - if (rl.IsVanilla()) continue; + if (!rl.IsAdditionRole()) player.SetRole(rl.GetRoleTypes()); - string roleName = GetString(rl.ToString()).ToLower().Trim(); + player.RpcSetCustomRole(rl); - if (setRole.Contains(roleName)) + if (rl.IsGhostRole()) GhostRolesManager.SpecificAssignGhostRole(player.PlayerId, rl, true); + + Main.PlayerStates[player.PlayerId].RemoveSubRole(CustomRoles.NotAssigned); + Main.ChangedRole = true; + break; + } + } + } + + private static void IDCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string msgText = GetString("PlayerIdList"); + msgText = Main.AllPlayerControls.Aggregate(msgText, (current, pc) => $"{current}\n{pc.PlayerId} \u2192 {pc.GetRealName()}"); + + Utils.SendMessage(msgText, player.PlayerId); + } + + private static void XFCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!GameStates.IsInGame && !player.IsHost()) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + return; + } + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) pc.RpcSetNameEx(pc.GetRealName(true)); + + ChatUpdatePatch.DoBlockChat = false; + Utils.NotifyRoles(GameStates.IsMeeting, NoCache: true); + Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); + } + + private static void ColorCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (GameStates.IsInGame) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + return; + } + + if (!player.IsHost() && !Options.PlayerCanSetColor.GetBool() && !IsPlayerVIP(player.FriendCode) && !player.FriendCode.GetDevUser().IsUp) + { + Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); + return; + } + + string subArgs = args.Length < 2 ? string.Empty : args[1]; + byte color = Utils.MsgToColor(subArgs, true); + + if (color == byte.MaxValue) + { + Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); + return; + } + + player.RpcSetColor(color); + Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); + } + + private static void KillCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + return; + } + + if (args.Length < 2 || !int.TryParse(args[1], out int id2)) return; + + PlayerControl target = Utils.GetPlayerById(id2); + + if (target != null) + { + target.Kill(target); + + if (target.AmOwner) + Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else + Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); + } + } + + private static void ExeCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); + return; + } + + if (args.Length < 2 || !int.TryParse(args[1], out int id)) return; + + PlayerControl pc = Utils.GetPlayerById(id); + + if (pc != null) + { + pc.Data.IsDead = true; + Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.etc; + pc.RpcExileV2(); + Main.PlayerStates[pc.PlayerId].SetDead(); + + if (pc.AmOwner) + Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + else + Utils.SendMessage(string.Format(GetString("Message.Executed"), pc.Data.PlayerName)); + } + } + + private static void BanKickCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + // Check if the kick command is enabled in the settings + if (!Options.ApplyModeratorList.GetBool() && !player.IsHost()) + { + Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); + return; + } + + // Check if the player has the necessary privileges to use the command + if (!IsPlayerModerator(player.FriendCode) && !player.IsHost()) + { + Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); + return; + } + + string subArgs = args.Length < 2 ? string.Empty : args[1]; + + if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + return; + } + + if (kickPlayerId.IsHost()) + { + Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); + return; + } + + PlayerControl kickedPlayer = Utils.GetPlayerById(kickPlayerId); + + if (kickedPlayer == null) + { + Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); + return; + } + + // Prevent moderators from kicking other moderators + if (IsPlayerModerator(kickedPlayer.FriendCode)) + { + Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); + return; + } + + // Kick the specified player + AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), args[0] == "/ban"); + string kickedPlayerName = kickedPlayer.GetRealName(); + var textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; + if (GameStates.IsInGame) textToSend += $"{GetString("KickCommandKickedRole")} {kickedPlayer.GetCustomRole().ToColoredString()}"; + + Utils.SendMessage(textToSend); + } + + private static void CheckCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(CheckCommand), text); + return; + } + + if (!player.IsAlive() || !player.Is(CustomRoles.Inquirer) || player.GetAbilityUseLimit() < 1) return; + + if (args.Length < 3 || !GuessManager.MsgToPlayerAndRole(text[6..], out byte checkId, out CustomRoles checkRole, out _)) return; + + bool hasRole = Utils.GetPlayerById(checkId).Is(checkRole); + if (IRandom.Instance.Next(100) < Inquirer.FailChance.GetInt()) hasRole = !hasRole; + + if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); + + LateTask.New(() => Utils.SendMessage(GetString(hasRole ? "Inquirer.MessageTrue" : "Inquirer.MessageFalse"), player.PlayerId), 0.2f, log: false); + player.RpcRemoveAbilityUse(); + } + + private static void ChatCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(ChatCommand), text); + return; + } + + if (!Ventriloquist.On || !player.IsAlive() || !player.Is(CustomRoles.Ventriloquist) || player.PlayerId.GetAbilityUseLimit() < 1) return; + + var vl2 = (Ventriloquist)Main.PlayerStates[player.PlayerId].Role; + if (vl2.Target == byte.MaxValue) return; + + if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); + + LateTask.New(() => Utils.GetPlayerById(vl2.Target)?.RpcSendChat(text[6..]), 0.2f, log: false); + player.RpcRemoveAbilityUse(); + } + + private static void TargetCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(TargetCommand), text); + return; + } + + if (!Ventriloquist.On || !player.IsAlive() || !player.Is(CustomRoles.Ventriloquist) || player.PlayerId.GetAbilityUseLimit() < 1) return; + + var vl = (Ventriloquist)Main.PlayerStates[player.PlayerId].Role; + vl.Target = args.Length < 2 ? byte.MaxValue : byte.TryParse(args[1], out byte targetId) ? targetId : byte.MaxValue; + if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); + } + + private static void QSCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(QSCommand), text); + return; + } + + if (!QuizMaster.On || !player.IsAlive()) return; + + var qm2 = (QuizMaster)Main.PlayerStates.Values.First(x => x.Role is QuizMaster).Role; + if (qm2.Target != player.PlayerId || !QuizMaster.MessagesToSend.TryGetValue(player.PlayerId, out string msg)) return; + + Utils.SendMessage(msg, player.PlayerId, GetString("QuizMaster.QuestionSample.Title")); + } + + private static void QACommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(QACommand), text); + return; + } + + if (args.Length < 2 || !QuizMaster.On || !player.IsAlive()) return; + + var qm = (QuizMaster)Main.PlayerStates.Values.First(x => x.Role is QuizMaster).Role; + if (qm.Target != player.PlayerId) return; + + qm.Answer(args[1].ToUpper()); + } + + private static void AnswerCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(AnswerCommand), text); + return; + } + + if (args.Length < 2) return; + + Mathematician.Reply(player, args[1]); + } + + private static void AskCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(AskCommand), text); + return; + } + + if (args.Length < 3 || !player.Is(CustomRoles.Mathematician)) return; + + if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); + + Mathematician.Ask(player, args[1], args[2]); + } + + private static void VoteCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost) + { + RequestCommandProcessingFromHost(nameof(VoteCommand), text); + return; + } + + if (text.Length < 6 || !GameStates.IsMeeting) return; + + string toVote = text[6..].Replace(" ", string.Empty); + if (!byte.TryParse(toVote, out byte voteId) || MeetingHud.Instance.playerStates?.FirstOrDefault(x => x.TargetPlayerId == player.PlayerId)?.DidVote is true or null) return; + + if (voteId > Main.AllPlayerControls.Length) return; + + if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) + ChatManager.SendPreviousMessagesToAll(); + + var votedPlayer = voteId.GetPlayer(); + if (Main.PlayerStates.TryGetValue(player.PlayerId, out var state) && votedPlayer != null && state.Role.OnVote(player, votedPlayer)) return; + + if (!player.IsHost()) + MeetingHud.Instance.CastVote(player.PlayerId, voteId); + else + MeetingHud.Instance.CmdCastVote(player.PlayerId, voteId); + } + + private static void SayCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!AmongUsClient.Instance.AmHost && !IsPlayerModerator(player.FriendCode)) return; + if (args.Length > 1) Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString(player.IsHost() ? "MessageFromTheHost" : "SayTitle")}"); + } + + private static void DeathCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!GameStates.IsInGame) return; + + PlayerControl killer = player.GetRealKiller(); + + if (killer == null) + { + Utils.SendMessage("\n", player.PlayerId, GetString("DeathCommandFail")); + return; + } + + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathCommand"), Utils.ColorString(Main.PlayerColors.TryGetValue(killer.PlayerId, out Color32 kColor) ? kColor : Color.white, killer.GetRealName()), (killer.Is(CustomRoles.Bloodlust) ? CustomRoles.Bloodlust.ToColoredString() : string.Empty) + killer.GetCustomRole().ToColoredString())); + } + + private static void MessageWaitCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length > 1 && int.TryParse(args[1], out int sec)) + { + Main.MessageWait.Value = sec; + Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); + } + else + Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); + } + + private static void TemplateCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (player.IsLocalPlayer()) + { + if (args.Length > 1) + TemplateManager.SendTemplate(args[1]); + else + HudManager.Instance.Chat.AddChat(player, (player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{GetString("ForExample")}:\n{args[0]} test"); + } + else + { + if (args.Length > 1) + TemplateManager.SendTemplate(args[1], player.PlayerId); + else + Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); + } + } + + private static void TPInCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!GameStates.IsLobby || !Options.PlayerCanTPInAndOut.GetBool()) return; + + player.TP(new Vector2(-0.2f, 1.3f)); + } + + private static void TPOutCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (!GameStates.IsLobby || !Options.PlayerCanTPInAndOut.GetBool()) return; + + player.TP(new Vector2(0.1f, 3.8f)); + } + + private static void MyRoleCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + CustomRoles role = player.GetCustomRole(); + + if (GameStates.IsInGame) + { + StringBuilder sb = new(); + StringBuilder titleSb = new(); + StringBuilder settings = new(); + settings.Append(""); + titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); + sb.Append(""); + sb.Append(player.GetRoleInfo(true).TrimStart()); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, ref settings, disableColor: false); + + settings.Append(""); + if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); + + string searchStr = GetString(role.ToString()); + sb.Replace(searchStr, role.ToColoredString()); + sb.Replace(searchStr.ToLower(), role.ToColoredString()); + sb.Append(""); + + foreach (CustomRoles subRole in Main.PlayerStates[player.PlayerId].SubRoles) + { + sb.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong")}"); + string searchSubStr = GetString(subRole.ToString()); + sb.Replace(searchSubStr, subRole.ToColoredString()); + sb.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); + } + + if (settings.Length > 0) Utils.SendMessage("\n", player.PlayerId, settings.ToString()); + + Utils.SendMessage(sb.Append("").ToString(), player.PlayerId, titleSb.ToString()); + if (role.UsesPetInsteadOfKill()) Utils.SendMessage("\n", player.PlayerId, GetString("UsesPetInsteadOfKillNotice")); + } + else + Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), player.PlayerId); + } + + private static void AFKExemptCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !byte.TryParse(args[1], out byte afkId)) return; + + AFKDetector.ExemptedPlayers.Add(afkId); + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerExemptedFromAFK"), afkId.ColoredPlayerName())); + } + + private static void EffectCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !GameStates.IsInTask || !Randomizer.Exists) return; + + if (Enum.TryParse(args[1], true, out Randomizer.Effect effect)) effect.Apply(player); + } + + private static void ComboCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 4) + { + if (Main.AlwaysSpawnTogetherCombos.Count == 0 && Main.NeverSpawnTogetherCombos.Count == 0) return; + + StringBuilder sb = new(); + sb.Append(""); + + if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.Count > 0) + { + sb.AppendLine(GetString("AlwaysComboListTitle")); + sb.AppendLine(alwaysList.Join(x => $"{x.Key.ToColoredString()} \u00a7 {x.Value.Join(r => r.ToColoredString())}", "\n")); + sb.AppendLine(); + } + + if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.Count > 0) + { + sb.AppendLine(GetString("NeverComboListTitle")); + sb.AppendLine(neverList.Join(x => $"{x.Key.ToColoredString()} \u2194 {x.Value.Join(r => r.ToColoredString())}", "\n")); + sb.AppendLine(); + } + + sb.Append(GetString("ComboUsage")); + + Utils.SendMessage("\n", player.PlayerId, sb.ToString()); + return; + } + + switch (args[1]) + { + case "add": + case "ban": + if (GetRoleByName(args[2], out CustomRoles mainRole) && GetRoleByName(args[3], out CustomRoles addOn)) { - if (!rl.IsAdditionRole()) player.SetRole(rl.GetRoleTypes()); + if (mainRole.IsAdditionRole() || !addOn.IsAdditionRole() || (addOn == CustomRoles.Lovers && args[1] == "add")) break; - player.RpcSetCustomRole(rl); - - if (rl.IsGhostRole()) GhostRolesManager.SpecificAssignGhostRole(player.PlayerId, rl, true); - - Main.PlayerStates[player.PlayerId].RemoveSubRole(CustomRoles.NotAssigned); - Main.ChangedRole = true; - break; - } - } - } - - private static void IDCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string msgText = GetString("PlayerIdList"); - msgText = Main.AllPlayerControls.Aggregate(msgText, (current, pc) => $"{current}\n{pc.PlayerId} \u2192 {pc.GetRealName()}"); - - Utils.SendMessage(msgText, player.PlayerId); - } - - private static void XFCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!GameStates.IsInGame && !player.IsHost()) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - return; - } - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) pc.RpcSetNameEx(pc.GetRealName(true)); - - ChatUpdatePatch.DoBlockChat = false; - Utils.NotifyRoles(GameStates.IsMeeting, NoCache: true); - Utils.SendMessage(GetString("Message.TryFixName"), player.PlayerId); - } - - private static void ColorCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - return; - } - - if (!player.IsHost() && !Options.PlayerCanSetColor.GetBool() && !IsPlayerVIP(player.FriendCode) && !player.FriendCode.GetDevUser().IsUp) - { - Utils.SendMessage(GetString("DisableUseCommand"), player.PlayerId); - return; - } - - string subArgs = args.Length < 2 ? string.Empty : args[1]; - byte color = Utils.MsgToColor(subArgs, true); - - if (color == byte.MaxValue) - { - Utils.SendMessage(GetString("IllegalColor"), player.PlayerId); - return; - } - - player.RpcSetColor(color); - Utils.SendMessage(string.Format(GetString("Message.SetColor"), subArgs), player.PlayerId); - } - - private static void KillCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - return; - } - - if (args.Length < 2 || !int.TryParse(args[1], out int id2)) return; - - PlayerControl target = Utils.GetPlayerById(id2); - - if (target != null) - { - target.Kill(target); - - if (target.AmOwner) - Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else - Utils.SendMessage(string.Format(GetString("Message.Executed"), target.Data.PlayerName)); - } - } - - private static void ExeCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.CanNotUseInLobby"), player.PlayerId); - return; - } - - if (args.Length < 2 || !int.TryParse(args[1], out int id)) return; - - PlayerControl pc = Utils.GetPlayerById(id); - - if (pc != null) - { - pc.Data.IsDead = true; - Main.PlayerStates[pc.PlayerId].deathReason = PlayerState.DeathReason.etc; - pc.RpcExileV2(); - Main.PlayerStates[pc.PlayerId].SetDead(); - - if (pc.AmOwner) - Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); - else - Utils.SendMessage(string.Format(GetString("Message.Executed"), pc.Data.PlayerName)); - } - } - - private static void BanKickCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - // Check if the kick command is enabled in the settings - if (!Options.ApplyModeratorList.GetBool() && !player.IsHost()) - { - Utils.SendMessage(GetString("KickCommandDisabled"), player.PlayerId); - return; - } - - // Check if the player has the necessary privileges to use the command - if (!IsPlayerModerator(player.FriendCode) && !player.IsHost()) - { - Utils.SendMessage(GetString("KickCommandNoAccess"), player.PlayerId); - return; - } - - string subArgs = args.Length < 2 ? string.Empty : args[1]; - - if (string.IsNullOrEmpty(subArgs) || !byte.TryParse(subArgs, out byte kickPlayerId)) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); - return; - } - - if (kickPlayerId.IsHost()) - { - Utils.SendMessage(GetString("KickCommandKickHost"), player.PlayerId); - return; - } - - PlayerControl kickedPlayer = Utils.GetPlayerById(kickPlayerId); - - if (kickedPlayer == null) - { - Utils.SendMessage(GetString("KickCommandInvalidID"), player.PlayerId); - return; - } - - // Prevent moderators from kicking other moderators - if (IsPlayerModerator(kickedPlayer.FriendCode)) - { - Utils.SendMessage(GetString("KickCommandKickMod"), player.PlayerId); - return; - } - - // Kick the specified player - AmongUsClient.Instance.KickPlayer(kickedPlayer.GetClientId(), args[0] == "/ban"); - string kickedPlayerName = kickedPlayer.GetRealName(); - var textToSend = $"{kickedPlayerName} {GetString("KickCommandKicked")}"; - if (GameStates.IsInGame) textToSend += $"{GetString("KickCommandKickedRole")} {kickedPlayer.GetCustomRole().ToColoredString()}"; - - Utils.SendMessage(textToSend); - } - - private static void CheckCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(CheckCommand), text); - return; - } - - if (!player.IsAlive() || !player.Is(CustomRoles.Inquirer) || player.GetAbilityUseLimit() < 1) return; - - if (args.Length < 3 || !GuessManager.MsgToPlayerAndRole(text[6..], out byte checkId, out CustomRoles checkRole, out _)) return; - - bool hasRole = Utils.GetPlayerById(checkId).Is(checkRole); - if (IRandom.Instance.Next(100) < Inquirer.FailChance.GetInt()) hasRole = !hasRole; - - if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); - - LateTask.New(() => Utils.SendMessage(GetString(hasRole ? "Inquirer.MessageTrue" : "Inquirer.MessageFalse"), player.PlayerId), 0.2f, log: false); - player.RpcRemoveAbilityUse(); - } - - private static void ChatCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(ChatCommand), text); - return; - } - - if (!Ventriloquist.On || !player.IsAlive() || !player.Is(CustomRoles.Ventriloquist) || player.PlayerId.GetAbilityUseLimit() < 1) return; - - var vl2 = (Ventriloquist)Main.PlayerStates[player.PlayerId].Role; - if (vl2.Target == byte.MaxValue) return; - - if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); - - LateTask.New(() => Utils.GetPlayerById(vl2.Target)?.RpcSendChat(text[6..]), 0.2f, log: false); - player.RpcRemoveAbilityUse(); - } - - private static void TargetCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(TargetCommand), text); - return; - } - - if (!Ventriloquist.On || !player.IsAlive() || !player.Is(CustomRoles.Ventriloquist) || player.PlayerId.GetAbilityUseLimit() < 1) return; - - var vl = (Ventriloquist)Main.PlayerStates[player.PlayerId].Role; - vl.Target = args.Length < 2 ? byte.MaxValue : byte.TryParse(args[1], out byte targetId) ? targetId : byte.MaxValue; - if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); - } - - private static void QSCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(QSCommand), text); - return; - } - - if (!QuizMaster.On || !player.IsAlive()) return; - - var qm2 = (QuizMaster)Main.PlayerStates.Values.First(x => x.Role is QuizMaster).Role; - if (qm2.Target != player.PlayerId || !QuizMaster.MessagesToSend.TryGetValue(player.PlayerId, out string msg)) return; - - Utils.SendMessage(msg, player.PlayerId, GetString("QuizMaster.QuestionSample.Title")); - } - - private static void QACommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(QACommand), text); - return; - } - - if (args.Length < 2 || !QuizMaster.On || !player.IsAlive()) return; - - var qm = (QuizMaster)Main.PlayerStates.Values.First(x => x.Role is QuizMaster).Role; - if (qm.Target != player.PlayerId) return; - - qm.Answer(args[1].ToUpper()); - } - - private static void AnswerCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(AnswerCommand), text); - return; - } - - if (args.Length < 2) return; - - Mathematician.Reply(player, args[1]); - } - - private static void AskCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(AskCommand), text); - return; - } - - if (args.Length < 3 || !player.Is(CustomRoles.Mathematician)) return; - - if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) ChatManager.SendPreviousMessagesToAll(); - - Mathematician.Ask(player, args[1], args[2]); - } - - private static void VoteCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost) - { - RequestCommandProcessingFromHost(nameof(VoteCommand), text); - return; - } - - if (text.Length < 6 || !GameStates.IsMeeting) return; - - string toVote = text[6..].Replace(" ", string.Empty); - if (!byte.TryParse(toVote, out byte voteId) || MeetingHud.Instance.playerStates?.FirstOrDefault(x => x.TargetPlayerId == player.PlayerId)?.DidVote is true or null) return; - - if (voteId > Main.AllPlayerControls.Length) return; - - if (player.PlayerId != PlayerControl.LocalPlayer.PlayerId) - ChatManager.SendPreviousMessagesToAll(); - - var votedPlayer = voteId.GetPlayer(); - if (Main.PlayerStates.TryGetValue(player.PlayerId, out var state) && votedPlayer != null && state.Role.OnVote(player, votedPlayer)) return; - - if (!player.IsHost()) - MeetingHud.Instance.CastVote(player.PlayerId, voteId); - else - MeetingHud.Instance.CmdCastVote(player.PlayerId, voteId); - } - - private static void SayCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!AmongUsClient.Instance.AmHost && !IsPlayerModerator(player.FriendCode)) return; - if (args.Length > 1) Utils.SendMessage(args.Skip(1).Join(delimiter: " "), title: $"{GetString(player.IsHost() ? "MessageFromTheHost" : "SayTitle")}"); - } - - private static void DeathCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!GameStates.IsInGame) return; - - PlayerControl killer = player.GetRealKiller(); - - if (killer == null) - { - Utils.SendMessage("\n", player.PlayerId, GetString("DeathCommandFail")); - return; - } - - Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("DeathCommand"), Utils.ColorString(Main.PlayerColors.TryGetValue(killer.PlayerId, out Color32 kColor) ? kColor : Color.white, killer.GetRealName()), (killer.Is(CustomRoles.Bloodlust) ? CustomRoles.Bloodlust.ToColoredString() : string.Empty) + killer.GetCustomRole().ToColoredString())); - } - - private static void MessageWaitCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length > 1 && int.TryParse(args[1], out int sec)) - { - Main.MessageWait.Value = sec; - Utils.SendMessage(string.Format(GetString("Message.SetToSeconds"), sec), 0); - } - else - Utils.SendMessage($"{GetString("Message.MessageWaitHelp")}\n{GetString("ForExample")}:\n{args[0]} 3", 0); - } - - private static void TemplateCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (player.IsLocalPlayer()) - { - if (args.Length > 1) - TemplateManager.SendTemplate(args[1]); - else - HudManager.Instance.Chat.AddChat(player, (player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + $"{GetString("ForExample")}:\n{args[0]} test"); - } - else - { - if (args.Length > 1) - TemplateManager.SendTemplate(args[1], player.PlayerId); - else - Utils.SendMessage($"{GetString("ForExample")}:\n{args[0]} test", player.PlayerId); - } - } - - private static void TPInCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!GameStates.IsLobby || !Options.PlayerCanTPInAndOut.GetBool()) return; - - player.TP(new Vector2(-0.2f, 1.3f)); - } - - private static void TPOutCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!GameStates.IsLobby || !Options.PlayerCanTPInAndOut.GetBool()) return; - - player.TP(new Vector2(0.1f, 3.8f)); - } - - private static void MyRoleCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - CustomRoles role = player.GetCustomRole(); - - if (GameStates.IsInGame) - { - StringBuilder sb = new(); - StringBuilder titleSb = new(); - StringBuilder settings = new(); - settings.Append(""); - titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); - sb.Append(""); - sb.Append(player.GetRoleInfo(true).TrimStart()); - if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, ref settings, disableColor: false); - - settings.Append(""); - if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); - - string searchStr = GetString(role.ToString()); - sb.Replace(searchStr, role.ToColoredString()); - sb.Replace(searchStr.ToLower(), role.ToColoredString()); - sb.Append(""); - - foreach (CustomRoles subRole in Main.PlayerStates[player.PlayerId].SubRoles) - { - sb.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong")}"); - string searchSubStr = GetString(subRole.ToString()); - sb.Replace(searchSubStr, subRole.ToColoredString()); - sb.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); - } - - if (settings.Length > 0) Utils.SendMessage("\n", player.PlayerId, settings.ToString()); - - Utils.SendMessage(sb.Append("").ToString(), player.PlayerId, titleSb.ToString()); - if (role.UsesPetInsteadOfKill()) Utils.SendMessage("\n", player.PlayerId, GetString("UsesPetInsteadOfKillNotice")); - } - else - Utils.SendMessage((player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + GetString("Message.CanNotUseInLobby"), player.PlayerId); - } - - private static void AFKExemptCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 2 || !byte.TryParse(args[1], out byte afkId)) return; - - AFKDetector.ExemptedPlayers.Add(afkId); - Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("PlayerExemptedFromAFK"), afkId.ColoredPlayerName())); - } - - private static void EffectCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 2 || !GameStates.IsInTask || !Randomizer.Exists) return; - - if (Enum.TryParse(args[1], true, out Randomizer.Effect effect)) effect.Apply(player); - } - - private static void ComboCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 4) - { - if (Main.AlwaysSpawnTogetherCombos.Count == 0 && Main.NeverSpawnTogetherCombos.Count == 0) return; - - StringBuilder sb = new(); - sb.Append(""); - - if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.Count > 0) - { - sb.AppendLine(GetString("AlwaysComboListTitle")); - sb.AppendLine(alwaysList.Join(x => $"{x.Key.ToColoredString()} \u00a7 {x.Value.Join(r => r.ToColoredString())}", "\n")); - sb.AppendLine(); - } - - if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.Count > 0) - { - sb.AppendLine(GetString("NeverComboListTitle")); - sb.AppendLine(neverList.Join(x => $"{x.Key.ToColoredString()} \u2194 {x.Value.Join(r => r.ToColoredString())}", "\n")); - sb.AppendLine(); - } - - sb.Append(GetString("ComboUsage")); - - Utils.SendMessage("\n", player.PlayerId, sb.ToString()); - return; - } - - switch (args[1]) - { - case "add": - case "ban": - if (GetRoleByName(args[2], out CustomRoles mainRole) && GetRoleByName(args[3], out CustomRoles addOn)) + if (args[1] == "add") { - if (mainRole.IsAdditionRole() || !addOn.IsAdditionRole() || (addOn == CustomRoles.Lovers && args[1] == "add")) break; + if (!Main.AlwaysSpawnTogetherCombos.ContainsKey(OptionItem.CurrentPreset)) Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset] = []; - if (args[1] == "add") - { - if (!Main.AlwaysSpawnTogetherCombos.ContainsKey(OptionItem.CurrentPreset)) Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset] = []; - - if (!Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].TryGetValue(mainRole, out List list1)) - Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset][mainRole] = [addOn]; - else if (!list1.Contains(addOn)) list1.Add(addOn); - - if (text.EndsWith(" all")) - { - for (var preset = 0; preset < OptionItem.NumPresets; preset++) - { - if (preset == OptionItem.CurrentPreset) continue; - - if (!Main.AlwaysSpawnTogetherCombos.ContainsKey(preset)) Main.AlwaysSpawnTogetherCombos[preset] = []; - - if (!Main.AlwaysSpawnTogetherCombos[preset].TryGetValue(mainRole, out List list2)) - Main.AlwaysSpawnTogetherCombos[preset][mainRole] = [addOn]; - else if (!list2.Contains(addOn)) list2.Add(addOn); - } - } - } - else - { - if (!Main.NeverSpawnTogetherCombos.ContainsKey(OptionItem.CurrentPreset)) Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset] = []; - - if (!Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset].TryGetValue(mainRole, out List list2)) - Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset][mainRole] = [addOn]; - else if (!list2.Contains(addOn)) list2.Add(addOn); - - if (text.EndsWith(" all")) - { - for (var preset = 0; preset < OptionItem.NumPresets; preset++) - { - if (preset == OptionItem.CurrentPreset) continue; - - if (!Main.NeverSpawnTogetherCombos.ContainsKey(preset)) Main.NeverSpawnTogetherCombos[preset] = []; - - if (!Main.NeverSpawnTogetherCombos[preset].TryGetValue(mainRole, out List list3)) - Main.NeverSpawnTogetherCombos[preset][mainRole] = [addOn]; - else if (!list3.Contains(addOn)) list3.Add(addOn); - } - } - } - - Utils.SendMessage(string.Format(args[1] == "add" ? GetString("ComboAdd") : GetString("ComboBan"), GetString(mainRole.ToString()), GetString(addOn.ToString())), player.PlayerId); - Utils.SaveComboInfo(); - } - - break; - case "remove": - case "allow": - if (GetRoleByName(args[2], out CustomRoles mainRole2) && GetRoleByName(args[3], out CustomRoles addOn2)) - { - if (mainRole2.IsAdditionRole() || !addOn2.IsAdditionRole()) break; + if (!Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset].TryGetValue(mainRole, out List list1)) + Main.AlwaysSpawnTogetherCombos[OptionItem.CurrentPreset][mainRole] = [addOn]; + else if (!list1.Contains(addOn)) list1.Add(addOn); if (text.EndsWith(" all")) { for (var preset = 0; preset < OptionItem.NumPresets; preset++) { - if (Main.AlwaysSpawnTogetherCombos.TryGetValue(preset, out Dictionary> list1)) + if (preset == OptionItem.CurrentPreset) continue; + + if (!Main.AlwaysSpawnTogetherCombos.ContainsKey(preset)) Main.AlwaysSpawnTogetherCombos[preset] = []; + + if (!Main.AlwaysSpawnTogetherCombos[preset].TryGetValue(mainRole, out List list2)) + Main.AlwaysSpawnTogetherCombos[preset][mainRole] = [addOn]; + else if (!list2.Contains(addOn)) list2.Add(addOn); + } + } + } + else + { + if (!Main.NeverSpawnTogetherCombos.ContainsKey(OptionItem.CurrentPreset)) Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset] = []; + + if (!Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset].TryGetValue(mainRole, out List list2)) + Main.NeverSpawnTogetherCombos[OptionItem.CurrentPreset][mainRole] = [addOn]; + else if (!list2.Contains(addOn)) list2.Add(addOn); + + if (text.EndsWith(" all")) + { + for (var preset = 0; preset < OptionItem.NumPresets; preset++) + { + if (preset == OptionItem.CurrentPreset) continue; + + if (!Main.NeverSpawnTogetherCombos.ContainsKey(preset)) Main.NeverSpawnTogetherCombos[preset] = []; + + if (!Main.NeverSpawnTogetherCombos[preset].TryGetValue(mainRole, out List list3)) + Main.NeverSpawnTogetherCombos[preset][mainRole] = [addOn]; + else if (!list3.Contains(addOn)) list3.Add(addOn); + } + } + } + + Utils.SendMessage(string.Format(args[1] == "add" ? GetString("ComboAdd") : GetString("ComboBan"), GetString(mainRole.ToString()), GetString(addOn.ToString())), player.PlayerId); + Utils.SaveComboInfo(); + } + + break; + case "remove": + case "allow": + if (GetRoleByName(args[2], out CustomRoles mainRole2) && GetRoleByName(args[3], out CustomRoles addOn2)) + { + if (mainRole2.IsAdditionRole() || !addOn2.IsAdditionRole()) break; + + if (text.EndsWith(" all")) + { + for (var preset = 0; preset < OptionItem.NumPresets; preset++) + { + if (Main.AlwaysSpawnTogetherCombos.TryGetValue(preset, out Dictionary> list1)) + { + if (list1.TryGetValue(mainRole2, out List list2)) { - if (list1.TryGetValue(mainRole2, out List list2)) - { - list2.Remove(addOn2); - if (list2.Count == 0) list1.Remove(mainRole2); + list2.Remove(addOn2); + if (list2.Count == 0) list1.Remove(mainRole2); - if (list1.Count == 0) Main.AlwaysSpawnTogetherCombos.Remove(preset); - } - } - - if (Main.NeverSpawnTogetherCombos.TryGetValue(preset, out Dictionary> list3)) - { - if (list3.TryGetValue(mainRole2, out List list4)) - { - list4.Remove(addOn2); - if (list4.Count == 0) list3.Remove(mainRole2); - - if (list3.Count == 0) Main.NeverSpawnTogetherCombos.Remove(preset); - } + if (list1.Count == 0) Main.AlwaysSpawnTogetherCombos.Remove(preset); } } + if (Main.NeverSpawnTogetherCombos.TryGetValue(preset, out Dictionary> list3)) + { + if (list3.TryGetValue(mainRole2, out List list4)) + { + list4.Remove(addOn2); + if (list4.Count == 0) list3.Remove(mainRole2); + + if (list3.Count == 0) Main.NeverSpawnTogetherCombos.Remove(preset); + } + } + } + + Utils.SendMessage(string.Format(GetString("ComboRemove"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); + Utils.SaveComboInfo(); + } + else + { + if (args[1] == "remove" && Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.TryGetValue(mainRole2, out List list3)) + { + list3.Remove(addOn2); + if (list3.Count == 0) alwaysList.Remove(mainRole2); + Utils.SendMessage(string.Format(GetString("ComboRemove"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); Utils.SaveComboInfo(); } - else + else if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.TryGetValue(mainRole2, out List list4)) { - if (args[1] == "remove" && Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.TryGetValue(mainRole2, out List list3)) - { - list3.Remove(addOn2); - if (list3.Count == 0) alwaysList.Remove(mainRole2); + list4.Remove(addOn2); + if (list4.Count == 0) neverList.Remove(mainRole2); - Utils.SendMessage(string.Format(GetString("ComboRemove"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); - Utils.SaveComboInfo(); - } - else if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.TryGetValue(mainRole2, out List list4)) - { - list4.Remove(addOn2); - if (list4.Count == 0) neverList.Remove(mainRole2); - - Utils.SendMessage(string.Format(GetString("ComboAllow"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); - Utils.SaveComboInfo(); - } + Utils.SendMessage(string.Format(GetString("ComboAllow"), GetString(mainRole2.ToString()), GetString(addOn2.ToString())), player.PlayerId); + Utils.SaveComboInfo(); } } - - break; - } - } - - private static void DeleteModCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 2 || !byte.TryParse(args[1], out byte remModId)) return; - - PlayerControl remModPc = Utils.GetPlayerById(remModId); - if (remModPc == null) return; - - string remFc = remModPc.FriendCode.Replace(':', '#'); - if (!IsPlayerModerator(remFc)) Utils.SendMessage(GetString("PlayerNotMod"), player.PlayerId); - - File.WriteAllLines("./EHR_DATA/Moderators.txt", File.ReadAllLines("./EHR_DATA/Moderators.txt").Where(x => !x.Contains(remFc))); - Utils.SendMessage(GetString("PlayerRemovedFromModList"), player.PlayerId); - } - - private static void AddModCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 2 || !byte.TryParse(args[1], out byte newModId)) return; - - PlayerControl newModPc = Utils.GetPlayerById(newModId); - if (newModPc == null) return; - - string fc = newModPc.FriendCode.Replace(':', '#'); - if (IsPlayerModerator(fc)) Utils.SendMessage(GetString("PlayerAlreadyMod"), player.PlayerId); - - File.AppendAllText("./EHR_DATA/Moderators.txt", $"\n{fc}"); - Utils.SendMessage(GetString("PlayerAddedToModList"), player.PlayerId); - } - - private static void KCountCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool() || Main.AllAlivePlayerControls.Length < Options.MinPlayersForGameStateCommand.GetInt()) return; - - Utils.SendMessage("\n", player.PlayerId, Utils.GetGameStateData()); - } - - private static void SetRoleCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string subArgs = text.Remove(0, 8); - - if (!GameStates.IsLobby) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - return; - } - - if (!GuessManager.MsgToPlayerAndRole(subArgs, out byte resultId, out CustomRoles roleToSet, out _)) - { - Utils.SendMessage($"{GetString("InvalidArguments")}", player.PlayerId); - return; - } - - if (resultId != 0 && !player.FriendCode.GetDevUser().IsUp) - { - Utils.SendMessage($"{GetString("Message.NoPermissionSetRoleOthers")}", player.PlayerId); - return; - } - - PlayerControl targetPc = Utils.GetPlayerById(resultId); - if (targetPc == null) return; - - if (roleToSet.IsAdditionRole()) - { - if (!Main.SetAddOns.ContainsKey(resultId)) Main.SetAddOns[resultId] = []; - - if (Main.SetAddOns[resultId].Contains(roleToSet)) - Main.SetAddOns[resultId].Remove(roleToSet); - else - Main.SetAddOns[resultId].Add(roleToSet); - } - else - Main.SetRoles[targetPc.PlayerId] = roleToSet; - - var playername = $"{Utils.ColorString(Main.PlayerColors.TryGetValue(resultId, out Color32 textColor) ? textColor : Color.white, targetPc.GetRealName())}"; - var rolename = $" {GetString(roleToSet.ToString())} "; - Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("RoleSelected"), playername, rolename)); - } - - private static void UpCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - Utils.SendMessage($"{GetString("UpReplacedMessage")}", player.PlayerId); - } - - private static void RCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string subArgs = text.Remove(0, 2); - SendRolesInfo(subArgs, player.PlayerId, player.FriendCode.GetDevUser().DeBug); - } - - private static void DisconnectCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string subArgs = args.Length < 2 ? string.Empty : args[1]; - - switch (subArgs) - { - case "crew": - GameManager.Instance.enabled = false; - GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); - break; - - case "imp": - GameManager.Instance.enabled = false; - GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); - break; - - default: - __instance?.AddChat(player, "crew | imp"); - break; - } - - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); - } - - private static void NowCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string subArgs = args.Length < 2 ? string.Empty : args[1]; - - switch (subArgs) - { - case "r": - case "roles": - Utils.ShowActiveRoles(player.PlayerId); - break; - case "a": - case "all": - Utils.ShowAllActiveSettings(player.PlayerId); - break; - default: - Utils.ShowActiveSettings(player.PlayerId); - break; - } - } - - private static void LevelCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string subArgs = args.Length < 2 ? string.Empty : args[1]; - Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), player.PlayerId); - _ = int.TryParse(subArgs, out int input); - - if (input is < 1 or > 999) - { - Utils.SendMessage(GetString("Message.AllowLevelRange"), player.PlayerId); - return; - } - - var number = Convert.ToUInt32(input); - player.RpcSetLevel(number - 1); - } - - private static void HideNameCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); - - GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = - ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) - ? $"{Main.HideName.Value}" - : $"{Main.HideName.Value}"; - } - - private static void RenameCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (args.Length < 2) return; - - if (args[1].Length is > 50 or < 1) - Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); - else - { - if (player.IsLocalPlayer()) - Main.NickName = args[1]; - else - { - if (!Options.PlayerCanSetName.GetBool() && !IsPlayerVIP(player.FriendCode)) return; - - if (GameStates.IsInGame) - { - Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); - return; - } - - string name = args.Skip(1).Join(delimiter: " "); - - if (name.Length is > 50 or < 1) - { - Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); - return; - } - - Main.AllPlayerNames[player.PlayerId] = name; - player.RpcSetName(name); - } - } - } - - private static void LastResultCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - Utils.ShowKillLog(player.PlayerId); - Utils.ShowLastAddOns(player.PlayerId); - Utils.ShowLastRoles(player.PlayerId); - Utils.ShowLastResult(player.PlayerId); - } - - private static void WinnerCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (Main.WinnerNameList.Count == 0) - Utils.SendMessage(GetString("NoInfoExists")); - else - Utils.SendMessage("Winners:\n" + string.Join(", ", Main.WinnerNameList)); - } - - private static void ChangeSettingCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string subArgs = args.Length < 2 ? "" : args[1]; - - switch (subArgs) - { - case "map": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "theskeld": - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 0); - break; - case "mirahq": - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 1); - break; - case "polus": - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 2); - break; - case "dlekseht": - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 3); - break; - case "airship": - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 4); - break; - case "thefungle": - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 5); - break; - case "custom": - subArgs = args.Length < 4 ? "" : args[3]; - GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, byte.Parse(subArgs)); - break; - } - - break; - case "impostors": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.NumImpostors, int.Parse(subArgs)); - AmongUsClient.Instance.StartGame(); - break; - case "players": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.MaxPlayers, int.Parse(subArgs)); - AmongUsClient.Instance.StartGame(); - break; - case "recommended": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.IsDefaults, true); - break; - case "off": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.IsDefaults, false); - break; - } - - break; - case "confirmejects": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.ConfirmImpostor, true); - break; - case "off": - GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.ConfirmImpostor, false); - break; - } - - break; - case "emergencymeetings": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.NumEmergencyMeetings, int.Parse(subArgs)); - break; - case "anonymousvotes": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.AnonymousVotes, true); - break; - case "off": - GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.AnonymousVotes, false); - break; - } - - break; - case "emergencycooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.EmergencyCooldown, int.Parse(subArgs)); - break; - case "discussiontime": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.DiscussionTime, int.Parse(subArgs)); - break; - case "votingtime": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.VotingTime, int.Parse(subArgs)); - break; - case "playerspeed": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.PlayerSpeedMod, float.Parse(subArgs)); - break; - case "crewmatevision": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.CrewLightMod, float.Parse(subArgs)); - break; - case "impostorvision": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.ImpostorLightMod, float.Parse(subArgs)); - break; - case "killcooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.KillCooldown, float.Parse(subArgs)); - break; - case "killdistance": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "short": - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 0); - break; - case "medium": - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 1); - break; - case "long": - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 2); - break; - case "custom": - subArgs = args.Length < 4 ? "" : args[3]; - GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, int.Parse(subArgs)); - break; - } - - break; - case "taskbarupdates": - subArgs = args.Length < 3 ? "" : args[2]; - - GameOptionsManager.Instance.currentNormalGameOptions.TaskBarMode = subArgs switch - { - "always" => AmongUs.GameOptions.TaskBarMode.Normal, - "meetings" => AmongUs.GameOptions.TaskBarMode.MeetingOnly, - "never" => AmongUs.GameOptions.TaskBarMode.Invisible, - _ => GameOptionsManager.Instance.currentNormalGameOptions.TaskBarMode - }; - - break; - case "visualtasks": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.VisualTasks, true); - break; - case "off": - GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.VisualTasks, false); - break; - } - - break; - case "commontasks": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumCommonTasks, int.Parse(subArgs)); - break; - case "longtasks": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumLongTasks, int.Parse(subArgs)); - break; - case "shorttasks": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumShortTasks, int.Parse(subArgs)); - break; - case "scientistcount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Scientist, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Scientist)); - break; - case "scientistchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Scientist, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Scientist), int.Parse(subArgs)); - break; - case "vitalsdisplaycooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ScientistCooldown, float.Parse(subArgs)); - break; - case "batteryduration": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ScientistBatteryCharge, float.Parse(subArgs)); - break; - case "engineercount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.currentNormalGameOptions.RoleOptions.SetRoleRate(RoleTypes.Engineer, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Engineer)); - break; - case "engineerchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Engineer, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Engineer), int.Parse(subArgs)); - break; - case "ventusecooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.EngineerCooldown, float.Parse(subArgs)); - break; - case "maxtimeinvents": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.EngineerInVentMaxTime, float.Parse(subArgs)); - break; - case "guardianangelcount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.GuardianAngel, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.GuardianAngel)); - break; - case "guardianangelchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.GuardianAngel, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.GuardianAngel), int.Parse(subArgs)); - break; - case "protectcooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.GuardianAngelCooldown, float.Parse(subArgs)); - break; - case "protectduration": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ProtectionDurationSeconds, float.Parse(subArgs)); - break; - case "protectvisibletoimpostors": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, true); - break; - case "off": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, false); - break; - } - - break; - case "shapeshiftercount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Shapeshifter, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Shapeshifter)); - break; - case "shapeshifterchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Shapeshifter, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Shapeshifter), int.Parse(subArgs)); - break; - case "shapeshiftduration": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ShapeshifterDuration, float.Parse(subArgs)); - break; - case "shapeshiftcooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ShapeshifterCooldown, float.Parse(subArgs)); - break; - case "leaveshapeshiftevidence": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, true); - break; - case "off": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, false); - break; - } - - break; - case "phantomcount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Phantom, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Phantom)); - break; - case "phantomchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Phantom, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Phantom), int.Parse(subArgs)); - break; - case "invisduration": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.PhantomDuration, float.Parse(subArgs)); - break; - case "inviscooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.PhantomCooldown, float.Parse(subArgs)); - break; - case "noisemakercount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Noisemaker, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Noisemaker)); - break; - case "noisemakerchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Noisemaker, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Noisemaker), int.Parse(subArgs)); - break; - case "noisemakerimpostoralert": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.NoisemakerImpostorAlert, true); - break; - case "off": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.NoisemakerImpostorAlert, false); - break; - } - - break; - case "noisemakeralertduration": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.NoisemakerAlertDuration, int.Parse(subArgs)); - break; - case "trackercount": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Tracker, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Tracker)); - break; - case "trackerchance": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Tracker, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Tracker), int.Parse(subArgs)); - break; - case "trackduration": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDuration, float.Parse(subArgs)); - break; - case "trackcooldown": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerCooldown, float.Parse(subArgs)); - break; - case "trackdelay": - subArgs = args.Length < 3 ? "" : args[2]; - GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDelay, float.Parse(subArgs)); - break; - case "ghostdotasks": - subArgs = args.Length < 3 ? "" : args[2]; - - switch (subArgs) - { - case "on": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.GhostsDoTasks, true); - break; - case "off": - GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.GhostsDoTasks, false); - break; - } - - break; - default: - Utils.SendMessage(GetString("Commands.ChangeSettingHelp"), player.PlayerId); - break; - } - - GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions; - GameManager.Instance.LogicOptions.SyncOptions(); - } - - private static void VersionCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - string version_text = Main.PlayerVersion.OrderBy(pair => pair.Key).Aggregate(string.Empty, (current, kvp) => current + $"{kvp.Key}: ({Main.AllPlayerNames[kvp.Key]}) {kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"); - if (version_text != string.Empty) HudManager.Instance.Chat.AddChat(player, (player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + version_text); - } - - private static void LTCommand(ChatController __instance, PlayerControl player, string text, string[] args) - { - if (!GameStates.IsLobby) return; - - float timer = GameStartManagerPatch.Timer; - int minutes = (int)timer / 60; - int seconds = (int)timer % 60; - string lt = string.Format(GetString("LobbyCloseTimer"), $"{minutes:00}:{seconds:00}"); - if (timer <= 60) lt = Utils.ColorString(Color.red, lt); - - Utils.SendMessage(lt, player.PlayerId); - } - - // ------------------------------------------------------------------------------------------------------------------------- - - private static bool CheckMute(byte id) - { - if (!MutedPlayers.TryGetValue(id, out (long MuteTimeStamp, int Duration) mute)) return false; - - long timeLeft = mute.Duration - (Utils.TimeStamp - mute.MuteTimeStamp); - - if (timeLeft <= 0) - { - MutedPlayers.Remove(id); - return false; - } - - Utils.SendMessage("\n", id, string.Format(GetString("MuteMessage"), timeLeft)); - return true; - } - - public static string FixRoleNameInput(string text) - { - text = text.Replace("着", "者").Trim().ToLower(); - - return text switch - { - "管理員" or "管理" or "gm" => GetString("GM"), - "賞金獵人" or "赏金" or "bh" or "bounty" => GetString("BountyHunter"), - "自爆兵" or "自爆" => GetString("Bomber"), - "邪惡的追踪者" or "邪恶追踪者" or "追踪" or "et" => GetString("EvilTracker"), - "煙花商人" or "烟花" or "fw" => GetString("FireWorks"), - "夢魘" or "夜魇" => GetString("Mare"), - "詭雷" => GetString("BoobyTrap"), - "黑手黨" or "黑手" => GetString("Mafia"), - "嗜血殺手" or "嗜血" or "sk" => GetString("SerialKiller"), - "千面鬼" or "千面" => GetString("ShapeMaster"), - "狂妄殺手" or "狂妄" or "arr" => GetString("Sans"), - "殺戮機器" or "杀戮" or "机器" or "杀戮兵器" or "km" => GetString("Minimalism"), - "蝕時者" or "蚀时" or "偷时" or "tt" => GetString("TimeThief"), - "狙擊手" or "狙击" => GetString("Sniper"), - "傀儡師" or "傀儡" => GetString("Puppeteer"), - "殭屍" or "丧尸" => GetString("Zombie"), - "吸血鬼" or "吸血" or "vamp" => GetString("Vampire"), - "術士" => GetString("Warlock"), - "駭客" or "黑客" => GetString("Hacker"), - "刺客" or "忍者" => GetString("Assassin"), - "礦工" => GetString("Miner"), - "逃逸者" or "逃逸" => GetString("Escapee"), - "女巫" => GetString("Witch"), - "監視者" or "监管" or "aa" => GetString("AntiAdminer"), - "清道夫" or "清道" or "scav" => GetString("Scavenger"), - "窺視者" or "窥视" => GetString("Watcher"), - "誘餌" or "大奖" or "头奖" => GetString("Bait"), - "擺爛人" or "摆烂" => GetString("Needy"), - "獨裁者" or "独裁" or "dict" => GetString("Dictator"), - "法醫" or "doc" => GetString("Doctor"), - "偵探" or "det" => GetString("Detective"), - "幸運兒" or "幸运" => GetString("Luckey"), - "大明星" or "明星" or "ss" => GetString("SuperStar"), - "網紅" or "cel" or "celeb" => GetString("CyberStar"), - "demo" => GetString("Demolitionist"), - "俠客" => GetString("SwordsMan"), - "正義賭怪" or "正义的赌怪" or "好赌" or "正义赌" or "ng" => GetString("NiceGuesser"), - "邪惡賭怪" or "邪恶的赌怪" or "坏赌" or "恶赌" or "邪恶赌" or "赌怪" or "eg" => GetString("EvilGuesser"), - "市長" or "逝长" => GetString("Mayor"), - "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), - "愚者" or "愚" => GetString("Psychic"), - "修理大师" or "修理" or "维修" or "sm" => GetString("SabotageMaster"), - "警長" => GetString("Sheriff"), - "告密者" or "告密" => GetString("Snitch"), - "增速者" or "增速" => GetString("SpeedBooster"), - "時間操控者" or "时间操控人" or "时间操控" or "tm" => GetString("TimeManager"), - "陷阱師" or "陷阱" or "小奖" => GetString("Trapper"), - "傳送師" or "传送" or "trans" => GetString("Transporter"), - "縱火犯" or "纵火" or "arso" => GetString("Arsonist"), - "處刑人" or "处刑" or "exe" => GetString("Executioner"), - "小丑" or "丑皇" or "jest" => GetString("Jester"), - "投機者" or "投机" or "oppo" => GetString("Opportunist"), - "馬里奧" or "马力欧" => GetString("Mario"), - "恐怖分子" or "恐怖" or "terro" => GetString("Terrorist"), - "豺狼" or "蓝狼" or "狼" => GetString("Jackal"), - "神" or "上帝" => GetString("God"), - "情人" or "愛人" or "链子" or "老婆" or "老公" or "lover" => GetString("Lovers"), - "絕境者" or "绝境" or "last" or "lastimp" or "last imp" or "Last" => GetString("LastImpostor"), - "閃電俠" or "闪电" => GetString("Flashman"), - "靈媒" => GetString("Seer"), - "破平者" or "破平" => GetString("Brakar"), - "執燈人" or "执灯" or "灯人" => GetString("Torch"), - "膽小" or "胆小" or "obli" => GetString("Oblivious"), - "迷惑者" or "迷幻" or "bew" => GetString("Bewilder"), - "sun" => GetString("Sunglasses"), - "蠢蛋" or "笨蛋" or "蠢狗" or "傻逼" => GetString("Fool"), - "冤罪師" or "冤罪" or "inno" => GetString("Innocent"), - "資本家" or "资本主义" or "资本" or "cap" or "capi" => GetString("Capitalism"), - "老兵" or "vet" => GetString("Veteran"), - "加班狂" or "加班" => GetString("Workhorse"), - "復仇者" or "复仇" => GetString("Avanger"), - "鵜鶘" or "pel" or "peli" => GetString("Pelican"), - "保鏢" or "bg" => GetString("Bodyguard"), - "up" or "up主" or "yt" => GetString("Youtuber"), - "利己主義者" or "利己主义" or "利己" or "ego" => GetString("Egoist"), - "贗品商" or "赝品" => GetString("Counterfeiter"), - "擲雷兵" or "掷雷" or "闪光弹" or "gren" or "grena" => GetString("Grenadier"), - "竊票者" or "偷票" or "偷票者" or "窃票师" or "窃票" => GetString("TicketsStealer"), - "教父" => GetString("Gangster"), - "革命家" or "革命" or "revo" => GetString("Revolutionist"), - "fff團" or "fff" or "fff团" => GetString("FFF"), - "清理工" or "清潔工" or "清洁工" or "清理" or "清洁" or "janitor" => GetString("Cleaner"), - "醫生" => GetString("Medicaler"), - "占卜師" or "占卜" or "ft" => GetString("Divinator"), - "雙重人格" or "双重" or "双人格" or "人格" or "schizo" or "scizo" or "shizo" => GetString("DualPersonality"), - "玩家" => GetString("Gamer"), - "情報販子" or "情报" or "贩子" => GetString("Messenger"), - "球狀閃電" or "球闪" or "球状" => GetString("BallLightning"), - "潛藏者" or "潜藏" => GetString("DarkHide"), - "貪婪者" or "贪婪" => GetString("Greedier"), - "工作狂" or "工作" or "worka" => GetString("Workaholic"), - "呪狼" or "咒狼" or "cw" => GetString("CursedWolf"), - "寶箱怪" or "宝箱" => GetString("Mimic"), - "集票者" or "集票" or "寄票" or "机票" => GetString("Collector"), - "活死人" or "活死" => GetString("Glitch"), - "奪魂者" or "多混" or "夺魂" or "sc" => GetString("ImperiusCurse"), - "自爆卡車" or "自爆" or "卡车" or "provo" => GetString("Provocateur"), - "快槍手" or "快枪" or "qs" => GetString("QuickShooter"), - "隱蔽者" or "隐蔽" or "小黑人" => GetString("Concealer"), - "抹除者" or "抹除" => GetString("Eraser"), - "肢解者" or "肢解" => GetString("OverKiller"), - "劊子手" or "侩子手" or "柜子手" => GetString("Hangman"), - "陽光開朗大男孩" or "阳光" or "开朗" or "大男孩" or "阳光开朗" or "开朗大男孩" or "阳光大男孩" or "sunny" => GetString("Sunnyboy"), - "法官" or "审判" => GetString("Judge"), - "入殮師" or "入检师" or "入殓" or "mor" => GetString("Mortician"), - "通靈師" or "通灵" => GetString("Mediumshiper"), - "吟游詩人" or "诗人" => GetString("Bard"), - "隱匿者" or "隐匿" or "隐身" or "隐身人" or "印尼" => GetString("Swooper"), - "船鬼" or "cp" => GetString("Crewpostor"), - "嗜血騎士" or "血骑" or "骑士" or "bk" => GetString("BloodKnight"), - "賭徒" => GetString("Totocalcio"), - "分散机" => GetString("Disperser"), - "和平之鸽" or "和平之鴿" or "和平的鸽子" or "和平" or "dop" or "dove of peace" => GetString("DovesOfNeace"), - "持槍" or "持械" or "手长" => GetString("Reach"), - "monarch" => GetString("Monarch"), - "sch" => GetString("SchrodingersCat"), - "glitch" => GetString("Glitch"), - "безумный" or "mad" => GetString("Madmate"), - "анти админер" or "anti adminer" => GetString("AntiAdminer"), - _ => text - }; - } - - public static bool GetRoleByName(string name, out CustomRoles role) - { - role = new(); - if (name == "") return false; - - if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) - { - Regex r = new("[\u4e00-\u9fa5]+$"); - MatchCollection mc = r.Matches(name); - var result = string.Empty; - - for (var i = 0; i < mc.Count; i++) - { - if (mc[i].ToString() == "是") continue; - - result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 } - name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); - } - else - name = name.Trim().ToLower(); + break; + } + } - foreach (CustomRoles rl in Enum.GetValues()) - { - if (rl.IsVanilla()) continue; + private static void DeleteModCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !byte.TryParse(args[1], out byte remModId)) return; - string roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", string.Empty); - string nameWithoutId = Regex.Replace(name.Replace(" ", string.Empty), @"^\d+", string.Empty); + PlayerControl remModPc = Utils.GetPlayerById(remModId); + if (remModPc == null) return; - if (nameWithoutId == roleName) - { - role = rl; - return true; - } - } + string remFc = remModPc.FriendCode.Replace(':', '#'); + if (!IsPlayerModerator(remFc)) Utils.SendMessage(GetString("PlayerNotMod"), player.PlayerId); - return false; + File.WriteAllLines("./EHR_DATA/Moderators.txt", File.ReadAllLines("./EHR_DATA/Moderators.txt").Where(x => !x.Contains(remFc))); + Utils.SendMessage(GetString("PlayerRemovedFromModList"), player.PlayerId); + } + + private static void AddModCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2 || !byte.TryParse(args[1], out byte newModId)) return; + + PlayerControl newModPc = Utils.GetPlayerById(newModId); + if (newModPc == null) return; + + string fc = newModPc.FriendCode.Replace(':', '#'); + if (IsPlayerModerator(fc)) Utils.SendMessage(GetString("PlayerAlreadyMod"), player.PlayerId); + + File.AppendAllText("./EHR_DATA/Moderators.txt", $"\n{fc}"); + Utils.SendMessage(GetString("PlayerAddedToModList"), player.PlayerId); + } + + private static void KCountCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (GameStates.IsLobby || !Options.EnableKillerLeftCommand.GetBool() || Main.AllAlivePlayerControls.Length < Options.MinPlayersForGameStateCommand.GetInt()) return; + + Utils.SendMessage("\n", player.PlayerId, Utils.GetGameStateData()); + } + + private static void SetRoleCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = text.Remove(0, 8); + + if (!GameStates.IsLobby) + { + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); + return; } - private static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) + if (!GuessManager.MsgToPlayerAndRole(subArgs, out byte resultId, out CustomRoles roleToSet, out _)) { - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) + Utils.SendMessage($"{GetString("InvalidArguments")}", player.PlayerId); + return; + } + + if (resultId != 0 && !player.FriendCode.GetDevUser().IsUp) + { + Utils.SendMessage($"{GetString("Message.NoPermissionSetRoleOthers")}", player.PlayerId); + return; + } + + PlayerControl targetPc = Utils.GetPlayerById(resultId); + if (targetPc == null) return; + + if (roleToSet.IsAdditionRole()) + { + if (!Main.SetAddOns.ContainsKey(resultId)) Main.SetAddOns[resultId] = []; + + if (Main.SetAddOns[resultId].Contains(roleToSet)) + Main.SetAddOns[resultId].Remove(roleToSet); + else + Main.SetAddOns[resultId].Add(roleToSet); + } + else + Main.SetRoles[targetPc.PlayerId] = roleToSet; + + var playername = $"{Utils.ColorString(Main.PlayerColors.TryGetValue(resultId, out Color32 textColor) ? textColor : Color.white, targetPc.GetRealName())}"; + var rolename = $" {GetString(roleToSet.ToString())} "; + Utils.SendMessage("\n", player.PlayerId, string.Format(GetString("RoleSelected"), playername, rolename)); + } + + private static void UpCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Utils.SendMessage($"{GetString("UpReplacedMessage")}", player.PlayerId); + } + + private static void RCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = text.Remove(0, 2); + SendRolesInfo(subArgs, player.PlayerId, player.FriendCode.GetDevUser().DeBug); + } + + private static void DisconnectCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = args.Length < 2 ? string.Empty : args[1]; + + switch (subArgs) + { + case "crew": + GameManager.Instance.enabled = false; + GameManager.Instance.RpcEndGame(GameOverReason.HumansDisconnect, false); + break; + + case "imp": + GameManager.Instance.enabled = false; + GameManager.Instance.RpcEndGame(GameOverReason.ImpostorDisconnect, false); + break; + + default: + __instance?.AddChat(player, "crew | imp"); + break; + } + + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Admin, 0); + } + + private static void NowCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = args.Length < 2 ? string.Empty : args[1]; + + switch (subArgs) + { + case "r": + case "roles": + Utils.ShowActiveRoles(player.PlayerId); + break; + case "a": + case "all": + Utils.ShowAllActiveSettings(player.PlayerId); + break; + default: + Utils.ShowActiveSettings(player.PlayerId); + break; + } + } + + private static void LevelCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = args.Length < 2 ? string.Empty : args[1]; + Utils.SendMessage(string.Format(GetString("Message.SetLevel"), subArgs), player.PlayerId); + _ = int.TryParse(subArgs, out int input); + + if (input is < 1 or > 999) + { + Utils.SendMessage(GetString("Message.AllowLevelRange"), player.PlayerId); + return; + } + + var number = Convert.ToUInt32(input); + player.RpcSetLevel(number - 1); + } + + private static void HideNameCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Main.HideName.Value = args.Length > 1 ? args.Skip(1).Join(delimiter: " ") : Main.HideName.DefaultValue.ToString(); + + GameStartManagerPatch.GameStartManagerStartPatch.HideName.text = + ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) + ? $"{Main.HideName.Value}" + : $"{Main.HideName.Value}"; + } + + private static void RenameCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (args.Length < 2) return; + + if (args[1].Length is > 50 or < 1) + Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); + else + { + if (player.IsLocalPlayer()) + Main.NickName = args[1]; + else { - string text = GetString($"ModeDescribe.{Options.CurrentGameMode}"); - bool allInOne = Options.CurrentGameMode == CustomGameMode.AllInOne; - Utils.SendMessage(allInOne ? "\n" : text, playerId, allInOne ? text : ""); - if (!CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) return; - } + if (!Options.PlayerCanSetName.GetBool() && !IsPlayerVIP(player.FriendCode)) return; - role = role.Trim().ToLower(); - if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); - if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); - if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); - if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); - - if (role == "") - { - Utils.ShowActiveRoles(playerId); - return; - } - - role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); - - foreach (CustomRoles rl in Enum.GetValues()) - { - if (rl.IsVanilla()) continue; - - string roleName = GetString(rl.ToString()); - - if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) + if (GameStates.IsInGame) { - if ((isDev || isUp) && GameStates.IsLobby) - { - var devMark = "▲"; - if (rl.IsAdditionRole() || rl is CustomRoles.GM) devMark = string.Empty; - - if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = string.Empty; - - if (isUp) Utils.SendMessage(devMark == "▲" ? string.Format(GetString("Message.YTPlanSelected"), roleName) : string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); - - //if (devMark == "▲") - //{ - // byte pid = playerId == 255 ? (byte)0 : playerId; - // _ = Main.DevRole.Remove(pid); - // Main.DevRole.Add(pid, rl); - //} - - if (isUp) return; - } - - StringBuilder sb = new(); - var title = $"<{Main.RoleColors[rl]}>{roleName} {Utils.GetRoleMode(rl)}"; - StringBuilder settings = new(); - sb.Append(GetString($"{rl}InfoLong").TrimStart()); - if (Options.CustomRoleSpawnChances.TryGetValue(rl, out StringOptionItem chance)) AddSettings(chance); - - if (rl is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor && Options.CustomRoleSpawnChances.TryGetValue(CustomRoles.Lovers, out chance)) AddSettings(chance); - - string txt = $"{sb}".Replace(roleName, rl.ToColoredString()).Replace(roleName.ToLower(), rl.ToColoredString()); - sb.Clear().Append(txt); - - if (rl.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); - - if (settings.Length > 0) Utils.SendMessage("\n", playerId, settings.ToString()); - - Utils.SendMessage(sb.ToString(), playerId, title); - return; - - void AddSettings(StringOptionItem stringOptionItem) - { - settings.AppendLine($"{GetString("SettingsForRoleText")} <{Main.RoleColors[rl]}>{roleName}:"); - Utils.ShowChildrenSettings(stringOptionItem, ref settings, disableColor: false); - settings.Append(""); - } - } - } - - foreach (CustomGameMode gameMode in Enum.GetValues()) - { - string gmString = GetString(gameMode.ToString()); - string match = gmString.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); - - if (role.Equals(match, StringComparison.OrdinalIgnoreCase)) - { - string text = GetString($"ModeDescribe.{gameMode}"); - bool allInOne = gameMode == CustomGameMode.AllInOne; - Utils.SendMessage(allInOne ? "\n" : text, playerId, allInOne ? text : gmString); + Utils.SendMessage(GetString("Message.OnlyCanUseInLobby"), player.PlayerId); return; } - } - Utils.SendMessage(isUp ? GetString("Message.YTPlanCanNotFindRoleThePlayerEnter") : GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); - } + string name = args.Skip(1).Join(delimiter: " "); - // ------------------------------------------------------------------------------------------------------------------------- - - public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) - { - canceled = false; - if (!AmongUsClient.Instance.AmHost || player.IsHost()) return; - - long now = Utils.TimeStamp; - - if (LastSentCommand.TryGetValue(player.PlayerId, out long ts) && ts + 2 >= now) - { - Logger.Warn("Chat message ignored, it was sent too soon after their last message", "ReceiveChat"); - return; - } - - if (text.StartsWith("\n")) text = text[1..]; - - string[] args = text.Split(' '); - - if (GuessManager.GuesserMsg(player, text) || - Judge.TrialMsg(player, text) || - NiceSwapper.SwapMsg(player, text) || - ParityCop.ParityCheckMsg(player, text) || - Councillor.MurderMsg(player, text)) - { - canceled = true; - LastSentCommand[player.PlayerId] = now; - return; - } - - if (Mediumshiper.MsMsg(player, text) || Mafia.MafiaMsgCheck(player, text)) - { - LastSentCommand[player.PlayerId] = now; - return; - } - - var isCommand = false; - - if (text.StartsWith('/')) - { - foreach (Command command in AllCommands) + if (name.Length is > 50 or < 1) { - if (!command.IsThisCommand(text)) continue; + Utils.SendMessage(GetString("Message.AllowNameLength"), player.PlayerId); + return; + } - Logger.Info($" Recognized command: {text}", "ReceiveChat"); - isCommand = true; + Main.AllPlayerNames[player.PlayerId] = name; + player.RpcSetName(name); + } + } + } - if (!command.CanUseCommand(player)) - { - Utils.SendMessage(GetString("Commands.NoAccess"), player.PlayerId); - canceled = true; + private static void LastResultCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + Utils.ShowKillLog(player.PlayerId); + Utils.ShowLastAddOns(player.PlayerId); + Utils.ShowLastRoles(player.PlayerId); + Utils.ShowLastResult(player.PlayerId); + } + + private static void WinnerCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + if (Main.WinnerNameList.Count == 0) + Utils.SendMessage(GetString("NoInfoExists")); + else + Utils.SendMessage("Winners:\n" + string.Join(", ", Main.WinnerNameList)); + } + + private static void ChangeSettingCommand(ChatController __instance, PlayerControl player, string text, string[] args) + { + string subArgs = args.Length < 2 ? "" : args[1]; + + switch (subArgs) + { + case "map": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "theskeld": + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 0); + break; + case "mirahq": + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 1); + break; + case "polus": + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 2); + break; + case "dlekseht": + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 3); + break; + case "airship": + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 4); + break; + case "thefungle": + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, 5); + break; + case "custom": + subArgs = args.Length < 4 ? "" : args[3]; + GameOptionsManager.Instance.CurrentGameOptions.SetByte(ByteOptionNames.MapId, byte.Parse(subArgs)); break; - } - - command.Action(null, player, text, args); - if (command.IsCanceled) canceled = true; - - break; } - } - if (CheckMute(player.PlayerId)) - { - canceled = true; - ChatManager.SendPreviousMessagesToAll(); - return; - } + break; + case "impostors": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.NumImpostors, int.Parse(subArgs)); + AmongUsClient.Instance.StartGame(); + break; + case "players": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.MaxPlayers, int.Parse(subArgs)); + AmongUsClient.Instance.StartGame(); + break; + case "recommended": + subArgs = args.Length < 3 ? "" : args[2]; - if (GameStates.InGame && (Silencer.ForSilencer.Contains(player.PlayerId) || (Main.PlayerStates[player.PlayerId].Role is Dad { IsEnable: true } dad && dad.UsingAbilities.Contains(Dad.Ability.GoForMilk))) && player.IsAlive()) - { - ChatManager.SendPreviousMessagesToAll(); - canceled = true; - LastSentCommand[player.PlayerId] = now; - return; - } - - if (GameStates.IsInGame && !ChatUpdatePatch.LoversMessage && (player.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting)) - { - ChatManager.SendPreviousMessagesToAll(true); - canceled = true; - - if (player.Is(CustomRoles.Lovers) || player.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) + switch (subArgs) { - PlayerControl otherLover = Main.LoversPlayers.FirstOrDefault(x => x.PlayerId != player.PlayerId); - - if (otherLover != null) - { - LateTask.New(() => - { - string title = player.GetRealName(); - ChatUpdatePatch.LoversMessage = true; - Utils.SendMessage(text, otherLover.PlayerId, title); - Utils.SendMessage(text, player.PlayerId, title); - LateTask.New(() => ChatUpdatePatch.LoversMessage = false, Math.Max(AmongUsClient.Instance.Ping / 1000f * 2f, Main.MessageWait.Value + 0.5f), log: false); - }, 0.2f, log: false); - } + case "on": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.IsDefaults, true); + break; + case "off": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.IsDefaults, false); + break; } - else - LateTask.New(() => Utils.SendMessage(GetString("LoversChatCannotTalkMsg"), player.PlayerId, GetString("LoversChatCannotTalkTitle")), 0.5f, log: false); - } - if (!canceled) ChatManager.SendMessage(player, text); + break; + case "confirmejects": + subArgs = args.Length < 3 ? "" : args[2]; - if (isCommand) LastSentCommand[player.PlayerId] = now; + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.ConfirmImpostor, true); + break; + case "off": + GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.ConfirmImpostor, false); + break; + } - SpamManager.CheckSpam(player, text); + break; + case "emergencymeetings": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.NumEmergencyMeetings, int.Parse(subArgs)); + break; + case "anonymousvotes": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.AnonymousVotes, true); + break; + case "off": + GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.AnonymousVotes, false); + break; + } + + break; + case "emergencycooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.EmergencyCooldown, int.Parse(subArgs)); + break; + case "discussiontime": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.DiscussionTime, int.Parse(subArgs)); + break; + case "votingtime": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.VotingTime, int.Parse(subArgs)); + break; + case "playerspeed": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.PlayerSpeedMod, float.Parse(subArgs)); + break; + case "crewmatevision": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.CrewLightMod, float.Parse(subArgs)); + break; + case "impostorvision": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.ImpostorLightMod, float.Parse(subArgs)); + break; + case "killcooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.SetFloat(FloatOptionNames.KillCooldown, float.Parse(subArgs)); + break; + case "killdistance": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "short": + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 0); + break; + case "medium": + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 1); + break; + case "long": + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, 2); + break; + case "custom": + subArgs = args.Length < 4 ? "" : args[3]; + GameOptionsManager.Instance.currentNormalGameOptions.SetInt(Int32OptionNames.KillDistance, int.Parse(subArgs)); + break; + } + + break; + case "taskbarupdates": + subArgs = args.Length < 3 ? "" : args[2]; + + GameOptionsManager.Instance.currentNormalGameOptions.TaskBarMode = subArgs switch + { + "always" => AmongUs.GameOptions.TaskBarMode.Normal, + "meetings" => AmongUs.GameOptions.TaskBarMode.MeetingOnly, + "never" => AmongUs.GameOptions.TaskBarMode.Invisible, + _ => GameOptionsManager.Instance.currentNormalGameOptions.TaskBarMode + }; + + break; + case "visualtasks": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.VisualTasks, true); + break; + case "off": + GameOptionsManager.Instance.currentNormalGameOptions.SetBool(BoolOptionNames.VisualTasks, false); + break; + } + + break; + case "commontasks": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumCommonTasks, int.Parse(subArgs)); + break; + case "longtasks": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumLongTasks, int.Parse(subArgs)); + break; + case "shorttasks": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetInt(Int32OptionNames.NumShortTasks, int.Parse(subArgs)); + break; + case "scientistcount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Scientist, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Scientist)); + break; + case "scientistchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Scientist, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Scientist), int.Parse(subArgs)); + break; + case "vitalsdisplaycooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ScientistCooldown, float.Parse(subArgs)); + break; + case "batteryduration": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ScientistBatteryCharge, float.Parse(subArgs)); + break; + case "engineercount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.currentNormalGameOptions.RoleOptions.SetRoleRate(RoleTypes.Engineer, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Engineer)); + break; + case "engineerchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Engineer, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Engineer), int.Parse(subArgs)); + break; + case "ventusecooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.EngineerCooldown, float.Parse(subArgs)); + break; + case "maxtimeinvents": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.EngineerInVentMaxTime, float.Parse(subArgs)); + break; + case "guardianangelcount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.GuardianAngel, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.GuardianAngel)); + break; + case "guardianangelchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.GuardianAngel, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.GuardianAngel), int.Parse(subArgs)); + break; + case "protectcooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.GuardianAngelCooldown, float.Parse(subArgs)); + break; + case "protectduration": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ProtectionDurationSeconds, float.Parse(subArgs)); + break; + case "protectvisibletoimpostors": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, true); + break; + case "off": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ImpostorsCanSeeProtect, false); + break; + } + + break; + case "shapeshiftercount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Shapeshifter, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Shapeshifter)); + break; + case "shapeshifterchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Shapeshifter, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Shapeshifter), int.Parse(subArgs)); + break; + case "shapeshiftduration": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ShapeshifterDuration, float.Parse(subArgs)); + break; + case "shapeshiftcooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.ShapeshifterCooldown, float.Parse(subArgs)); + break; + case "leaveshapeshiftevidence": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, true); + break; + case "off": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.ShapeshifterLeaveSkin, false); + break; + } + + break; + case "phantomcount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Phantom, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Phantom)); + break; + case "phantomchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Phantom, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Phantom), int.Parse(subArgs)); + break; + case "invisduration": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.PhantomDuration, float.Parse(subArgs)); + break; + case "inviscooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.PhantomCooldown, float.Parse(subArgs)); + break; + case "noisemakercount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Noisemaker, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Noisemaker)); + break; + case "noisemakerchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Noisemaker, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Noisemaker), int.Parse(subArgs)); + break; + case "noisemakerimpostoralert": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.NoisemakerImpostorAlert, true); + break; + case "off": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.NoisemakerImpostorAlert, false); + break; + } + + break; + case "noisemakeralertduration": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.NoisemakerAlertDuration, int.Parse(subArgs)); + break; + case "trackercount": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Tracker, int.Parse(subArgs), GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(RoleTypes.Tracker)); + break; + case "trackerchance": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.SetRoleRate(RoleTypes.Tracker, GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(RoleTypes.Tracker), int.Parse(subArgs)); + break; + case "trackduration": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDuration, float.Parse(subArgs)); + break; + case "trackcooldown": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerCooldown, float.Parse(subArgs)); + break; + case "trackdelay": + subArgs = args.Length < 3 ? "" : args[2]; + GameOptionsManager.Instance.CurrentGameOptions.SetFloat(FloatOptionNames.TrackerDelay, float.Parse(subArgs)); + break; + case "ghostdotasks": + subArgs = args.Length < 3 ? "" : args[2]; + + switch (subArgs) + { + case "on": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.GhostsDoTasks, true); + break; + case "off": + GameOptionsManager.Instance.CurrentGameOptions.SetBool(BoolOptionNames.GhostsDoTasks, false); + break; + } + + break; + default: + Utils.SendMessage(GetString("Commands.ChangeSettingHelp"), player.PlayerId); + break; } + + GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions; + GameManager.Instance.LogicOptions.SyncOptions(); } - [HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] - internal static class ChatUpdatePatch + private static void VersionCommand(ChatController __instance, PlayerControl player, string text, string[] args) { - public static bool DoBlockChat; - public static bool LoversMessage; - - private static readonly List<(string Text, byte SendTo, string Title, long SendTimeStamp)> LastMessages = []; - - public static void Postfix(ChatController __instance) - { - var chatBubble = __instance.chatBubblePool.Prefab.Cast(); - chatBubble.TextArea.overrideColorTags = false; - - if (Main.DarkTheme.Value) - { - chatBubble.TextArea.color = Color.white; - chatBubble.Background.color = new(0.1f, 0.1f, 0.1f, 1f); - } - - LastMessages.RemoveAll(x => Utils.TimeStamp - x.SendTimeStamp > 10); - - if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].ReceiverID == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage) || DoBlockChat) return; - - PlayerControl player = Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer; - if (player == null) return; - - (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; - Main.MessagesToSend.RemoveAt(0); - - SendMessage(player, msg, sendTo, title); - - __instance.timeSinceLastMessage = 0f; - - LastMessages.Add((msg, sendTo, title, Utils.TimeStamp)); - } - - internal static void SendLastMessages() - { - PlayerControl player = Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer; - if (player == null) return; - - foreach ((string msg, byte sendTo, string title, _) in LastMessages) - SendMessage(player, msg, sendTo, title); - } - - internal static void SendMessage(PlayerControl player, string msg, byte sendTo, string title) - { - int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); - - string name = player.Data.PlayerName; - - if (clientId == -1) - { - player.SetName(title); - DestroyableSingleton.Instance.Chat.AddChat(player, msg); - player.SetName(name); - } - - var writer = CustomRpcSender.Create("MessagesToSend"); - writer.StartMessage(clientId); - - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(title) - .EndRpc(); - - writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) - .Write(msg) - .EndRpc(); - - writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - .Write(player.Data.NetId) - .Write(player.Data.PlayerName) - .EndRpc(); - - writer.EndMessage(); - writer.SendMessage(); - } + string version_text = Main.PlayerVersion.OrderBy(pair => pair.Key).Aggregate(string.Empty, (current, kvp) => current + $"{kvp.Key}: ({Main.AllPlayerNames[kvp.Key]}) {kvp.Value.forkId}/{kvp.Value.version}({kvp.Value.tag})\n"); + if (version_text != string.Empty) HudManager.Instance.Chat.AddChat(player, (player.FriendCode.GetDevUser().HasTag() ? "\n" : string.Empty) + version_text); } - [HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] - internal static class UpdateCharCountPatch + private static void LTCommand(ChatController __instance, PlayerControl player, string text, string[] args) { - public static void Postfix(FreeChatInputField __instance) - { - int length = __instance.textArea.text.Length; - __instance.charCountText.SetText(length <= 0 ? GetString("ThankYouForUsingEHR") : $"{length}/{__instance.textArea.characterLimit}"); - __instance.charCountText.enableWordWrapping = false; + if (!GameStates.IsLobby) return; - __instance.charCountText.color = length switch - { - < 1000 => Color.black, - < 1200 => new(1f, 1f, 0f, 1f), - _ => Color.red - }; - } + float timer = GameStartManagerPatch.Timer; + int minutes = (int)timer / 60; + int seconds = (int)timer % 60; + string lt = string.Format(GetString("LobbyCloseTimer"), $"{minutes:00}:{seconds:00}"); + if (timer <= 60) lt = Utils.ColorString(Color.red, lt); + + Utils.SendMessage(lt, player.PlayerId); } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] - internal static class RpcSendChatPatch + // ------------------------------------------------------------------------------------------------------------------------- + + private static bool CheckMute(byte id) { - public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) + if (!MutedPlayers.TryGetValue(id, out (long MuteTimeStamp, int Duration) mute)) return false; + + long timeLeft = mute.Duration - (Utils.TimeStamp - mute.MuteTimeStamp); + + if (timeLeft <= 0) { - if (string.IsNullOrWhiteSpace(chatText)) - { - __result = false; - return false; - } - - int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); - chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); - if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); - - if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) DestroyableSingleton.Instance.SendWho(); - - MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); - messageWriter.Write(chatText); - messageWriter.EndMessage(); - __result = true; + MutedPlayers.Remove(id); return false; } + + Utils.SendMessage("\n", id, string.Format(GetString("MuteMessage"), timeLeft)); + return true; + } + + public static string FixRoleNameInput(string text) + { + text = text.Replace("着", "者").Trim().ToLower(); + + return text switch + { + "管理員" or "管理" or "gm" => GetString("GM"), + "賞金獵人" or "赏金" or "bh" or "bounty" => GetString("BountyHunter"), + "自爆兵" or "自爆" => GetString("Bomber"), + "邪惡的追踪者" or "邪恶追踪者" or "追踪" or "et" => GetString("EvilTracker"), + "煙花商人" or "烟花" or "fw" => GetString("FireWorks"), + "夢魘" or "夜魇" => GetString("Mare"), + "詭雷" => GetString("BoobyTrap"), + "黑手黨" or "黑手" => GetString("Mafia"), + "嗜血殺手" or "嗜血" or "sk" => GetString("SerialKiller"), + "千面鬼" or "千面" => GetString("ShapeMaster"), + "狂妄殺手" or "狂妄" or "arr" => GetString("Sans"), + "殺戮機器" or "杀戮" or "机器" or "杀戮兵器" or "km" => GetString("Minimalism"), + "蝕時者" or "蚀时" or "偷时" or "tt" => GetString("TimeThief"), + "狙擊手" or "狙击" => GetString("Sniper"), + "傀儡師" or "傀儡" => GetString("Puppeteer"), + "殭屍" or "丧尸" => GetString("Zombie"), + "吸血鬼" or "吸血" or "vamp" => GetString("Vampire"), + "術士" => GetString("Warlock"), + "駭客" or "黑客" => GetString("Hacker"), + "刺客" or "忍者" => GetString("Assassin"), + "礦工" => GetString("Miner"), + "逃逸者" or "逃逸" => GetString("Escapee"), + "女巫" => GetString("Witch"), + "監視者" or "监管" or "aa" => GetString("AntiAdminer"), + "清道夫" or "清道" or "scav" => GetString("Scavenger"), + "窺視者" or "窥视" => GetString("Watcher"), + "誘餌" or "大奖" or "头奖" => GetString("Bait"), + "擺爛人" or "摆烂" => GetString("Needy"), + "獨裁者" or "独裁" or "dict" => GetString("Dictator"), + "法醫" or "doc" => GetString("Doctor"), + "偵探" or "det" => GetString("Detective"), + "幸運兒" or "幸运" => GetString("Luckey"), + "大明星" or "明星" or "ss" => GetString("SuperStar"), + "網紅" or "cel" or "celeb" => GetString("CyberStar"), + "demo" => GetString("Demolitionist"), + "俠客" => GetString("SwordsMan"), + "正義賭怪" or "正义的赌怪" or "好赌" or "正义赌" or "ng" => GetString("NiceGuesser"), + "邪惡賭怪" or "邪恶的赌怪" or "坏赌" or "恶赌" or "邪恶赌" or "赌怪" or "eg" => GetString("EvilGuesser"), + "市長" or "逝长" => GetString("Mayor"), + "被害妄想症" or "被害妄想" or "被迫害妄想症" or "被害" or "妄想" or "妄想症" => GetString("Paranoia"), + "愚者" or "愚" => GetString("Psychic"), + "修理大师" or "修理" or "维修" or "sm" => GetString("SabotageMaster"), + "警長" => GetString("Sheriff"), + "告密者" or "告密" => GetString("Snitch"), + "增速者" or "增速" => GetString("SpeedBooster"), + "時間操控者" or "时间操控人" or "时间操控" or "tm" => GetString("TimeManager"), + "陷阱師" or "陷阱" or "小奖" => GetString("Trapper"), + "傳送師" or "传送" or "trans" => GetString("Transporter"), + "縱火犯" or "纵火" or "arso" => GetString("Arsonist"), + "處刑人" or "处刑" or "exe" => GetString("Executioner"), + "小丑" or "丑皇" or "jest" => GetString("Jester"), + "投機者" or "投机" or "oppo" => GetString("Opportunist"), + "馬里奧" or "马力欧" => GetString("Mario"), + "恐怖分子" or "恐怖" or "terro" => GetString("Terrorist"), + "豺狼" or "蓝狼" or "狼" => GetString("Jackal"), + "神" or "上帝" => GetString("God"), + "情人" or "愛人" or "链子" or "老婆" or "老公" or "lover" => GetString("Lovers"), + "絕境者" or "绝境" or "last" or "lastimp" or "last imp" or "Last" => GetString("LastImpostor"), + "閃電俠" or "闪电" => GetString("Flashman"), + "靈媒" => GetString("Seer"), + "破平者" or "破平" => GetString("Brakar"), + "執燈人" or "执灯" or "灯人" => GetString("Torch"), + "膽小" or "胆小" or "obli" => GetString("Oblivious"), + "迷惑者" or "迷幻" or "bew" => GetString("Bewilder"), + "sun" => GetString("Sunglasses"), + "蠢蛋" or "笨蛋" or "蠢狗" or "傻逼" => GetString("Fool"), + "冤罪師" or "冤罪" or "inno" => GetString("Innocent"), + "資本家" or "资本主义" or "资本" or "cap" or "capi" => GetString("Capitalism"), + "老兵" or "vet" => GetString("Veteran"), + "加班狂" or "加班" => GetString("Workhorse"), + "復仇者" or "复仇" => GetString("Avanger"), + "鵜鶘" or "pel" or "peli" => GetString("Pelican"), + "保鏢" or "bg" => GetString("Bodyguard"), + "up" or "up主" or "yt" => GetString("Youtuber"), + "利己主義者" or "利己主义" or "利己" or "ego" => GetString("Egoist"), + "贗品商" or "赝品" => GetString("Counterfeiter"), + "擲雷兵" or "掷雷" or "闪光弹" or "gren" or "grena" => GetString("Grenadier"), + "竊票者" or "偷票" or "偷票者" or "窃票师" or "窃票" => GetString("TicketsStealer"), + "教父" => GetString("Gangster"), + "革命家" or "革命" or "revo" => GetString("Revolutionist"), + "fff團" or "fff" or "fff团" => GetString("FFF"), + "清理工" or "清潔工" or "清洁工" or "清理" or "清洁" or "janitor" => GetString("Cleaner"), + "醫生" => GetString("Medicaler"), + "占卜師" or "占卜" or "ft" => GetString("Divinator"), + "雙重人格" or "双重" or "双人格" or "人格" or "schizo" or "scizo" or "shizo" => GetString("DualPersonality"), + "玩家" => GetString("Gamer"), + "情報販子" or "情报" or "贩子" => GetString("Messenger"), + "球狀閃電" or "球闪" or "球状" => GetString("BallLightning"), + "潛藏者" or "潜藏" => GetString("DarkHide"), + "貪婪者" or "贪婪" => GetString("Greedier"), + "工作狂" or "工作" or "worka" => GetString("Workaholic"), + "呪狼" or "咒狼" or "cw" => GetString("CursedWolf"), + "寶箱怪" or "宝箱" => GetString("Mimic"), + "集票者" or "集票" or "寄票" or "机票" => GetString("Collector"), + "活死人" or "活死" => GetString("Glitch"), + "奪魂者" or "多混" or "夺魂" or "sc" => GetString("ImperiusCurse"), + "自爆卡車" or "自爆" or "卡车" or "provo" => GetString("Provocateur"), + "快槍手" or "快枪" or "qs" => GetString("QuickShooter"), + "隱蔽者" or "隐蔽" or "小黑人" => GetString("Concealer"), + "抹除者" or "抹除" => GetString("Eraser"), + "肢解者" or "肢解" => GetString("OverKiller"), + "劊子手" or "侩子手" or "柜子手" => GetString("Hangman"), + "陽光開朗大男孩" or "阳光" or "开朗" or "大男孩" or "阳光开朗" or "开朗大男孩" or "阳光大男孩" or "sunny" => GetString("Sunnyboy"), + "法官" or "审判" => GetString("Judge"), + "入殮師" or "入检师" or "入殓" or "mor" => GetString("Mortician"), + "通靈師" or "通灵" => GetString("Mediumshiper"), + "吟游詩人" or "诗人" => GetString("Bard"), + "隱匿者" or "隐匿" or "隐身" or "隐身人" or "印尼" => GetString("Swooper"), + "船鬼" or "cp" => GetString("Crewpostor"), + "嗜血騎士" or "血骑" or "骑士" or "bk" => GetString("BloodKnight"), + "賭徒" => GetString("Totocalcio"), + "分散机" => GetString("Disperser"), + "和平之鸽" or "和平之鴿" or "和平的鸽子" or "和平" or "dop" or "dove of peace" => GetString("DovesOfNeace"), + "持槍" or "持械" or "手长" => GetString("Reach"), + "monarch" => GetString("Monarch"), + "sch" => GetString("SchrodingersCat"), + "glitch" => GetString("Glitch"), + "безумный" or "mad" => GetString("Madmate"), + "анти админер" or "anti adminer" => GetString("AntiAdminer"), + _ => text + }; + } + + public static bool GetRoleByName(string name, out CustomRoles role) + { + role = new(); + if (name == "") return false; + + if ((TranslationController.InstanceExists ? TranslationController.Instance.currentLanguage.languageID : SupportedLangs.SChinese) == SupportedLangs.SChinese) + { + Regex r = new("[\u4e00-\u9fa5]+$"); + MatchCollection mc = r.Matches(name); + var result = string.Empty; + + for (var i = 0; i < mc.Count; i++) + { + if (mc[i].ToString() == "是") continue; + + result += mc[i]; //匹配结果是完整的数字,此处可以不做拼接的 + } + + name = FixRoleNameInput(result.Replace("是", string.Empty).Trim()); + } + else + name = name.Trim().ToLower(); + + foreach (CustomRoles rl in Enum.GetValues()) + { + if (rl.IsVanilla()) continue; + + string roleName = GetString(rl.ToString()).ToLower().Trim().Replace(" ", string.Empty); + string nameWithoutId = Regex.Replace(name.Replace(" ", string.Empty), @"^\d+", string.Empty); + + if (nameWithoutId == roleName) + { + role = rl; + return true; + } + } + + return false; + } + + private static void SendRolesInfo(string role, byte playerId, bool isDev = false, bool isUp = false) + { + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) + { + string text = GetString($"ModeDescribe.{Options.CurrentGameMode}"); + bool allInOne = Options.CurrentGameMode == CustomGameMode.AllInOne; + Utils.SendMessage(allInOne ? "\n" : text, playerId, allInOne ? text : ""); + if (!CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) return; + } + + role = role.Trim().ToLower(); + if (role.StartsWith("/r")) _ = role.Replace("/r", string.Empty); + if (role.StartsWith("/up")) _ = role.Replace("/up", string.Empty); + if (role.EndsWith("\r\n")) _ = role.Replace("\r\n", string.Empty); + if (role.EndsWith("\n")) _ = role.Replace("\n", string.Empty); + + if (role == "") + { + Utils.ShowActiveRoles(playerId); + return; + } + + role = FixRoleNameInput(role).ToLower().Trim().Replace(" ", string.Empty); + + foreach (CustomRoles rl in Enum.GetValues()) + { + if (rl.IsVanilla()) continue; + + string roleName = GetString(rl.ToString()); + + if (role == roleName.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty)) + { + if ((isDev || isUp) && GameStates.IsLobby) + { + var devMark = "▲"; + if (rl.IsAdditionRole() || rl is CustomRoles.GM) devMark = string.Empty; + + if (rl.GetCount() < 1 || rl.GetMode() == 0) devMark = string.Empty; + + if (isUp) Utils.SendMessage(devMark == "▲" ? string.Format(GetString("Message.YTPlanSelected"), roleName) : string.Format(GetString("Message.YTPlanSelectFailed"), roleName), playerId); + + //if (devMark == "▲") + //{ + // byte pid = playerId == 255 ? (byte)0 : playerId; + // _ = Main.DevRole.Remove(pid); + // Main.DevRole.Add(pid, rl); + //} + + if (isUp) return; + } + + StringBuilder sb = new(); + var title = $"<{Main.RoleColors[rl]}>{roleName} {Utils.GetRoleMode(rl)}"; + StringBuilder settings = new(); + sb.Append(GetString($"{rl}InfoLong").TrimStart()); + if (Options.CustomRoleSpawnChances.TryGetValue(rl, out StringOptionItem chance)) AddSettings(chance); + + if (rl is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor && Options.CustomRoleSpawnChances.TryGetValue(CustomRoles.Lovers, out chance)) AddSettings(chance); + + string txt = $"{sb}".Replace(roleName, rl.ToColoredString()).Replace(roleName.ToLower(), rl.ToColoredString()); + sb.Clear().Append(txt); + + if (rl.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); + + if (settings.Length > 0) Utils.SendMessage("\n", playerId, settings.ToString()); + + Utils.SendMessage(sb.ToString(), playerId, title); + return; + + void AddSettings(StringOptionItem stringOptionItem) + { + settings.AppendLine($"{GetString("SettingsForRoleText")} <{Main.RoleColors[rl]}>{roleName}:"); + Utils.ShowChildrenSettings(stringOptionItem, ref settings, disableColor: false); + settings.Append(""); + } + } + } + + foreach (CustomGameMode gameMode in Enum.GetValues()) + { + string gmString = GetString(gameMode.ToString()); + string match = gmString.ToLower().Trim().TrimStart('*').Replace(" ", string.Empty); + + if (role.Equals(match, StringComparison.OrdinalIgnoreCase)) + { + string text = GetString($"ModeDescribe.{gameMode}"); + bool allInOne = gameMode == CustomGameMode.AllInOne; + Utils.SendMessage(allInOne ? "\n" : text, playerId, allInOne ? text : gmString); + return; + } + } + + Utils.SendMessage(isUp ? GetString("Message.YTPlanCanNotFindRoleThePlayerEnter") : GetString("Message.CanNotFindRoleThePlayerEnter"), playerId); + } + + // ------------------------------------------------------------------------------------------------------------------------- + + public static void OnReceiveChat(PlayerControl player, string text, out bool canceled) + { + canceled = false; + if (!AmongUsClient.Instance.AmHost || player.IsHost()) return; + + long now = Utils.TimeStamp; + + if (LastSentCommand.TryGetValue(player.PlayerId, out long ts) && ts + 2 >= now) + { + Logger.Warn("Chat message ignored, it was sent too soon after their last message", "ReceiveChat"); + return; + } + + if (text.StartsWith("\n")) text = text[1..]; + + string[] args = text.Split(' '); + + if (GuessManager.GuesserMsg(player, text) || + Judge.TrialMsg(player, text) || + NiceSwapper.SwapMsg(player, text) || + ParityCop.ParityCheckMsg(player, text) || + Councillor.MurderMsg(player, text)) + { + canceled = true; + LastSentCommand[player.PlayerId] = now; + return; + } + + if (Mediumshiper.MsMsg(player, text) || Mafia.MafiaMsgCheck(player, text)) + { + LastSentCommand[player.PlayerId] = now; + return; + } + + var isCommand = false; + + if (text.StartsWith('/')) + { + foreach (Command command in AllCommands) + { + if (!command.IsThisCommand(text)) continue; + + Logger.Info($" Recognized command: {text}", "ReceiveChat"); + isCommand = true; + + if (!command.CanUseCommand(player)) + { + Utils.SendMessage(GetString("Commands.NoAccess"), player.PlayerId); + canceled = true; + break; + } + + command.Action(null, player, text, args); + if (command.IsCanceled) canceled = true; + + break; + } + } + + if (CheckMute(player.PlayerId)) + { + canceled = true; + ChatManager.SendPreviousMessagesToAll(); + return; + } + + if (GameStates.InGame && (Silencer.ForSilencer.Contains(player.PlayerId) || (Main.PlayerStates[player.PlayerId].Role is Dad { IsEnable: true } dad && dad.UsingAbilities.Contains(Dad.Ability.GoForMilk))) && player.IsAlive()) + { + ChatManager.SendPreviousMessagesToAll(); + canceled = true; + LastSentCommand[player.PlayerId] = now; + return; + } + + if (GameStates.IsInGame && !ChatUpdatePatch.LoversMessage && (player.IsAlive() || ExileController.Instance) && Lovers.PrivateChat.GetBool() && (ExileController.Instance || !GameStates.IsMeeting)) + { + ChatManager.SendPreviousMessagesToAll(true); + canceled = true; + + if (player.Is(CustomRoles.Lovers) || player.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) + { + PlayerControl otherLover = Main.LoversPlayers.FirstOrDefault(x => x.PlayerId != player.PlayerId); + + if (otherLover != null) + { + LateTask.New(() => + { + string title = player.GetRealName(); + ChatUpdatePatch.LoversMessage = true; + Utils.SendMessage(text, otherLover.PlayerId, title); + Utils.SendMessage(text, player.PlayerId, title); + LateTask.New(() => ChatUpdatePatch.LoversMessage = false, Math.Max(AmongUsClient.Instance.Ping / 1000f * 2f, Main.MessageWait.Value + 0.5f), log: false); + }, 0.2f, log: false); + } + } + else + LateTask.New(() => Utils.SendMessage(GetString("LoversChatCannotTalkMsg"), player.PlayerId, GetString("LoversChatCannotTalkTitle")), 0.5f, log: false); + } + + if (!canceled) ChatManager.SendMessage(player, text); + + if (isCommand) LastSentCommand[player.PlayerId] = now; + + SpamManager.CheckSpam(player, text); + } +} + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +internal static class ChatUpdatePatch +{ + public static bool DoBlockChat; + public static bool LoversMessage; + + private static readonly List<(string Text, byte SendTo, string Title, long SendTimeStamp)> LastMessages = []; + + public static void Postfix(ChatController __instance) + { + var chatBubble = __instance.chatBubblePool.Prefab.Cast(); + chatBubble.TextArea.overrideColorTags = false; + + if (Main.DarkTheme.Value) + { + chatBubble.TextArea.color = Color.white; + chatBubble.Background.color = new(0.1f, 0.1f, 0.1f, 1f); + } + + LastMessages.RemoveAll(x => Utils.TimeStamp - x.SendTimeStamp > 10); + + if (!AmongUsClient.Instance.AmHost || Main.MessagesToSend.Count == 0 || (Main.MessagesToSend[0].ReceiverID == byte.MaxValue && Main.MessageWait.Value > __instance.timeSinceLastMessage) || DoBlockChat) return; + + PlayerControl player = Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer; + if (player == null) return; + + (string msg, byte sendTo, string title) = Main.MessagesToSend[0]; + Main.MessagesToSend.RemoveAt(0); + + SendMessage(player, msg, sendTo, title); + + __instance.timeSinceLastMessage = 0f; + + LastMessages.Add((msg, sendTo, title, Utils.TimeStamp)); + } + + internal static void SendLastMessages() + { + PlayerControl player = Main.AllAlivePlayerControls.MinBy(x => x.PlayerId) ?? Main.AllPlayerControls.MinBy(x => x.PlayerId) ?? PlayerControl.LocalPlayer; + if (player == null) return; + + foreach ((string msg, byte sendTo, string title, _) in LastMessages) + SendMessage(player, msg, sendTo, title); + } + + internal static void SendMessage(PlayerControl player, string msg, byte sendTo, string title) + { + int clientId = sendTo == byte.MaxValue ? -1 : Utils.GetPlayerById(sendTo).GetClientId(); + + string name = player.Data.PlayerName; + + if (clientId == -1) + { + player.SetName(title); + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + player.SetName(name); + } + + var writer = CustomRpcSender.Create("MessagesToSend"); + writer.StartMessage(clientId); + + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(title) + .EndRpc(); + + writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + .Write(msg) + .EndRpc(); + + writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + .Write(player.Data.NetId) + .Write(player.Data.PlayerName) + .EndRpc(); + + writer.EndMessage(); + writer.SendMessage(); + } +} + +[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] +internal static class UpdateCharCountPatch +{ + public static void Postfix(FreeChatInputField __instance) + { + int length = __instance.textArea.text.Length; + __instance.charCountText.SetText(length <= 0 ? GetString("ThankYouForUsingEHR") : $"{length}/{__instance.textArea.characterLimit}"); + __instance.charCountText.enableWordWrapping = false; + + __instance.charCountText.color = length switch + { + < 1000 => Color.black, + < 1200 => new(1f, 1f, 0f, 1f), + _ => Color.red + }; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSendChat))] +internal static class RpcSendChatPatch +{ + public static bool Prefix(PlayerControl __instance, string chatText, ref bool __result) + { + if (string.IsNullOrWhiteSpace(chatText)) + { + __result = false; + return false; + } + + int return_count = PlayerControl.LocalPlayer.name.Count(x => x == '\n'); + chatText = new StringBuilder(chatText).Insert(0, "\n", return_count).ToString(); + if (AmongUsClient.Instance.AmClient && DestroyableSingleton.Instance) DestroyableSingleton.Instance.Chat.AddChat(__instance, chatText); + + if (chatText.Contains("who", StringComparison.OrdinalIgnoreCase)) DestroyableSingleton.Instance.SendWho(); + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, (byte)RpcCalls.SendChat, SendOption.None); + messageWriter.Write(chatText); + messageWriter.EndMessage(); + __result = true; + return false; } } \ No newline at end of file diff --git a/Patches/ChatControlPatch.cs b/Patches/ChatControlPatch.cs index af30426b7..f60881074 100644 --- a/Patches/ChatControlPatch.cs +++ b/Patches/ChatControlPatch.cs @@ -8,265 +8,264 @@ using HarmonyLib; using InnerNet; using UnityEngine; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +internal class ChatControllerUpdatePatch { - [HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] - internal class ChatControllerUpdatePatch + public static int CurrentHistorySelection = -1; + + private static SpriteRenderer QuickChatIcon; + private static SpriteRenderer OpenBanMenuIcon; + private static SpriteRenderer OpenKeyboardIcon; + + public static void Prefix() { - public static int CurrentHistorySelection = -1; + if (AmongUsClient.Instance.AmHost && DataManager.Settings.Multiplayer.ChatMode == QuickChatModes.QuickChatOnly) + DataManager.Settings.Multiplayer.ChatMode = QuickChatModes.FreeChatOrQuickChat; + } - private static SpriteRenderer QuickChatIcon; - private static SpriteRenderer OpenBanMenuIcon; - private static SpriteRenderer OpenKeyboardIcon; - - public static void Prefix() + public static void Postfix(ChatController __instance) + { + if (Main.DarkTheme.Value) { - if (AmongUsClient.Instance.AmHost && DataManager.Settings.Multiplayer.ChatMode == QuickChatModes.QuickChatOnly) - DataManager.Settings.Multiplayer.ChatMode = QuickChatModes.FreeChatOrQuickChat; + __instance.freeChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + + if (!TextBoxTMPSetTextPatch.IsInvalidCommand) + { + __instance.freeChatField.textArea.compoText.Color(Color.white); + __instance.freeChatField.textArea.outputText.color = Color.white; + } + + __instance.quickChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + __instance.quickChatField.text.color = Color.white; + + if (QuickChatIcon == null) + QuickChatIcon = GameObject.Find("QuickChatIcon")?.transform.GetComponent(); + else + QuickChatIcon.sprite = Utils.LoadSprite("EHR.Resources.Images.DarkQuickChat.png", 100f); + + if (OpenBanMenuIcon == null) + OpenBanMenuIcon = GameObject.Find("OpenBanMenuIcon")?.transform.GetComponent(); + else + OpenBanMenuIcon.sprite = Utils.LoadSprite("EHR.Resources.Images.DarkReport.png", 100f); + + if (OpenKeyboardIcon == null) + OpenKeyboardIcon = GameObject.Find("OpenKeyboardIcon")?.transform.GetComponent(); + else + OpenKeyboardIcon.sprite = Utils.LoadSprite("EHR.Resources.Images.DarkKeyboard.png", 100f); + } + else __instance.freeChatField.textArea.outputText.color = Color.black; + + if (!__instance.freeChatField.textArea.hasFocus) return; + + __instance.freeChatField.textArea.characterLimit = 1200; + + if (Input.GetKeyDown(KeyCode.Tab)) TextBoxTMPSetTextPatch.OnTabPress(__instance); + + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) + ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); + + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.V)) + __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + GUIUtility.systemCopyBuffer.Trim()); + + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X)) + { + ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); + __instance.freeChatField.textArea.SetText(""); } - public static void Postfix(ChatController __instance) + if (Input.GetKeyDown(KeyCode.UpArrow) && ChatCommands.ChatHistory.Count > 0) { - if (Main.DarkTheme.Value) - { - __instance.freeChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); + CurrentHistorySelection = Mathf.Clamp(--CurrentHistorySelection, 0, ChatCommands.ChatHistory.Count - 1); + __instance.freeChatField.textArea.SetText(ChatCommands.ChatHistory[CurrentHistorySelection]); + } - if (!TextBoxTMPSetTextPatch.IsInvalidCommand) + if (Input.GetKeyDown(KeyCode.DownArrow) && ChatCommands.ChatHistory.Count > 0) + { + CurrentHistorySelection++; + __instance.freeChatField.textArea.SetText(CurrentHistorySelection < ChatCommands.ChatHistory.Count ? ChatCommands.ChatHistory[CurrentHistorySelection] : string.Empty); + } + } +} + +[HarmonyPatch(typeof(UrlFinder), nameof(UrlFinder.TryFindUrl))] +internal class UrlFinderPatch +{ + public static bool Prefix(ref bool __result) + { + __result = false; + return false; + } +} + +public static class ChatManager +{ + private const int MaxHistorySize = 20; + private static readonly List ChatHistory = []; + + public static void ResetHistory() + { + ChatHistory.Clear(); + } + + private static bool CheckCommand(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); + + foreach (string str in comList) + { + if (exact) + { + if (msg == "/" + str) return true; + } + else + { + if (msg.StartsWith("/" + str)) { - __instance.freeChatField.textArea.compoText.Color(Color.white); - __instance.freeChatField.textArea.outputText.color = Color.white; + msg = msg.Replace("/" + str, string.Empty); + return true; } - - __instance.quickChatField.background.color = new Color32(40, 40, 40, byte.MaxValue); - __instance.quickChatField.text.color = Color.white; - - if (QuickChatIcon == null) - QuickChatIcon = GameObject.Find("QuickChatIcon")?.transform.GetComponent(); - else - QuickChatIcon.sprite = Utils.LoadSprite("EHR.Resources.Images.DarkQuickChat.png", 100f); - - if (OpenBanMenuIcon == null) - OpenBanMenuIcon = GameObject.Find("OpenBanMenuIcon")?.transform.GetComponent(); - else - OpenBanMenuIcon.sprite = Utils.LoadSprite("EHR.Resources.Images.DarkReport.png", 100f); - - if (OpenKeyboardIcon == null) - OpenKeyboardIcon = GameObject.Find("OpenKeyboardIcon")?.transform.GetComponent(); - else - OpenKeyboardIcon.sprite = Utils.LoadSprite("EHR.Resources.Images.DarkKeyboard.png", 100f); } - else __instance.freeChatField.textArea.outputText.color = Color.black; + } - if (!__instance.freeChatField.textArea.hasFocus) return; + return false; + } - __instance.freeChatField.textArea.characterLimit = 1200; + private static bool CheckName(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); - if (Input.GetKeyDown(KeyCode.Tab)) TextBoxTMPSetTextPatch.OnTabPress(__instance); - - if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) - ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); - - if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.V)) - __instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + GUIUtility.systemCopyBuffer.Trim()); - - if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.X)) + foreach (string com in comList) + { + if (exact) { - ClipboardHelper.PutClipboardString(__instance.freeChatField.textArea.text); - __instance.freeChatField.textArea.SetText(""); + if (msg.Contains(com)) return true; + } + else + { + int index = msg.IndexOf(com, StringComparison.Ordinal); + + if (index != -1) + { + msg = msg.Remove(index, com.Length); + return true; + } + } + } + + return false; + } + + public static void SendMessage(PlayerControl player, string message) + { + string playername = player.GetNameWithRole(); + message = message.ToLower().TrimStart().TrimEnd(); + + if (!player.IsAlive() || !AmongUsClient.Instance.AmHost || (Silencer.ForSilencer.Contains(player.PlayerId) && player.IsAlive())) return; + + int operate = message switch + { + { } str when CheckCommand(ref str, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id|shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|trial|审判|判|审|xp|效颦|效|颦|sw|换票|换|swap", false) || CheckName(ref playername, "系统消息", false) => 1, + { } str when CheckCommand(ref str, "up|ask|target|vote|chat|check|decree|assume|note", false) => 2, + { } str when CheckCommand(ref str, "r|role|m|myrole|n|now") => 4, + _ => 3 + }; + + switch (operate) + { + case 1 when player.IsAlive(): // Guessing Command & Such + Logger.Info("Special Command", "ChatManager"); + if (player.IsLocalPlayer()) break; + + LateTask.New(() => + { + if (!ChatCommands.LastSentCommand.ContainsKey(player.PlayerId)) + { + GuessManager.GuesserMsg(player, message); + Logger.Info("Delayed Guess", "ChatManager"); + } + else + Logger.Info("Delayed Guess was not necessary", "ChatManager"); + }, 0.3f, "Trying Delayed Guess"); + + break; + case 2: // /up and role ability commands + case 4: // /r, /n, /m + Logger.Info($"Command: {message}", "ChatManager"); + break; + case 3: // In Lobby & Evertything Else + var chatEntry = $"{player.PlayerId}: {message}"; + ChatHistory.Add(chatEntry); + if (ChatHistory.Count > MaxHistorySize) ChatHistory.RemoveAt(0); + + break; + } + + if (CustomGameMode.FFA.IsActiveOrIntegrated() && GameStates.InGame && !message.StartsWith('/')) + FFAManager.UpdateLastChatMessage(player.GetRealName(), message); + } + + public static void SendPreviousMessagesToAll(bool clear = false) + { + if (!AmongUsClient.Instance.AmHost) return; + + ChatUpdatePatch.DoBlockChat = true; + Logger.Info($" clear: {clear}", "ChatManager.SendPreviousMessagesToAll"); + + try + { + string msg = Utils.EmptyMessage; + int totalAlive = Main.AllAlivePlayerControls.Length; + if (totalAlive == 0) return; + + PlayerControl[] x = Main.AllAlivePlayerControls; + var r = IRandom.Instance; + + string[] filtered = ChatHistory.SkipLast(1).Where(a => Utils.GetPlayerById(Convert.ToByte(a.Split(':')[0].Trim())).IsAlive()).ToArray(); + + for (int i = clear ? 0 : filtered.Length; i < 20; i++) + { + PlayerControl player = x[r.Next(0, totalAlive)]; + DestroyableSingleton.Instance.Chat.AddChat(player, msg); + SendRPC(player, msg); } - if (Input.GetKeyDown(KeyCode.UpArrow) && ChatCommands.ChatHistory.Count > 0) + if (!clear) { - CurrentHistorySelection = Mathf.Clamp(--CurrentHistorySelection, 0, ChatCommands.ChatHistory.Count - 1); - __instance.freeChatField.textArea.SetText(ChatCommands.ChatHistory[CurrentHistorySelection]); + foreach (string str in filtered) + { + string[] entryParts = str.Split(':'); + string senderId = entryParts[0].Trim(); + string senderMessage = entryParts[1].Trim(); + for (var j = 2; j < entryParts.Length; j++) senderMessage += ':' + entryParts[j].Trim(); + + PlayerControl senderPlayer = Utils.GetPlayerById(Convert.ToByte(senderId)); + if (senderPlayer == null) continue; + + DestroyableSingleton.Instance.Chat.AddChat(senderPlayer, senderMessage); + SendRPC(senderPlayer, senderMessage); + } } - if (Input.GetKeyDown(KeyCode.DownArrow) && ChatCommands.ChatHistory.Count > 0) - { - CurrentHistorySelection++; - __instance.freeChatField.textArea.SetText(CurrentHistorySelection < ChatCommands.ChatHistory.Count ? ChatCommands.ChatHistory[CurrentHistorySelection] : string.Empty); - } + ChatUpdatePatch.SendLastMessages(); + } + finally + { + ChatUpdatePatch.DoBlockChat = false; } } - [HarmonyPatch(typeof(UrlFinder), nameof(UrlFinder.TryFindUrl))] - internal class UrlFinderPatch + private static void SendRPC(InnerNetObject senderPlayer, string senderMessage) { - public static bool Prefix(ref bool __result) - { - __result = false; - return false; - } - } + var writer = CustomRpcSender.Create("MessagesToSend"); + writer.StartMessage(); - public static class ChatManager - { - private const int MaxHistorySize = 20; - private static readonly List ChatHistory = []; + writer.StartRpc(senderPlayer.NetId, (byte)RpcCalls.SendChat) + .Write(senderMessage) + .EndRpc(); - public static void ResetHistory() - { - ChatHistory.Clear(); - } - - private static bool CheckCommand(ref string msg, string command, bool exact = true) - { - string[] comList = command.Split('|'); - - foreach (string str in comList) - { - if (exact) - { - if (msg == "/" + str) return true; - } - else - { - if (msg.StartsWith("/" + str)) - { - msg = msg.Replace("/" + str, string.Empty); - return true; - } - } - } - - return false; - } - - private static bool CheckName(ref string msg, string command, bool exact = true) - { - string[] comList = command.Split('|'); - - foreach (string com in comList) - { - if (exact) - { - if (msg.Contains(com)) return true; - } - else - { - int index = msg.IndexOf(com, StringComparison.Ordinal); - - if (index != -1) - { - msg = msg.Remove(index, com.Length); - return true; - } - } - } - - return false; - } - - public static void SendMessage(PlayerControl player, string message) - { - string playername = player.GetNameWithRole(); - message = message.ToLower().TrimStart().TrimEnd(); - - if (!player.IsAlive() || !AmongUsClient.Instance.AmHost || (Silencer.ForSilencer.Contains(player.PlayerId) && player.IsAlive())) return; - - int operate = message switch - { - { } str when CheckCommand(ref str, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id|shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|trial|审判|判|审|xp|效颦|效|颦|sw|换票|换|swap", false) || CheckName(ref playername, "系统消息", false) => 1, - { } str when CheckCommand(ref str, "up|ask|target|vote|chat|check|decree|assume|note", false) => 2, - { } str when CheckCommand(ref str, "r|role|m|myrole|n|now") => 4, - _ => 3 - }; - - switch (operate) - { - case 1 when player.IsAlive(): // Guessing Command & Such - Logger.Info("Special Command", "ChatManager"); - if (player.IsLocalPlayer()) break; - - LateTask.New(() => - { - if (!ChatCommands.LastSentCommand.ContainsKey(player.PlayerId)) - { - GuessManager.GuesserMsg(player, message); - Logger.Info("Delayed Guess", "ChatManager"); - } - else - Logger.Info("Delayed Guess was not necessary", "ChatManager"); - }, 0.3f, "Trying Delayed Guess"); - - break; - case 2: // /up and role ability commands - case 4: // /r, /n, /m - Logger.Info($"Command: {message}", "ChatManager"); - break; - case 3: // In Lobby & Evertything Else - var chatEntry = $"{player.PlayerId}: {message}"; - ChatHistory.Add(chatEntry); - if (ChatHistory.Count > MaxHistorySize) ChatHistory.RemoveAt(0); - - break; - } - - if (CustomGameMode.FFA.IsActiveOrIntegrated() && GameStates.InGame && !message.StartsWith('/')) - FFAManager.UpdateLastChatMessage(player.GetRealName(), message); - } - - public static void SendPreviousMessagesToAll(bool clear = false) - { - if (!AmongUsClient.Instance.AmHost) return; - - ChatUpdatePatch.DoBlockChat = true; - Logger.Info($" clear: {clear}", "ChatManager.SendPreviousMessagesToAll"); - - try - { - string msg = Utils.EmptyMessage; - int totalAlive = Main.AllAlivePlayerControls.Length; - if (totalAlive == 0) return; - - PlayerControl[] x = Main.AllAlivePlayerControls; - var r = IRandom.Instance; - - string[] filtered = ChatHistory.SkipLast(1).Where(a => Utils.GetPlayerById(Convert.ToByte(a.Split(':')[0].Trim())).IsAlive()).ToArray(); - - for (int i = clear ? 0 : filtered.Length; i < 20; i++) - { - PlayerControl player = x[r.Next(0, totalAlive)]; - DestroyableSingleton.Instance.Chat.AddChat(player, msg); - SendRPC(player, msg); - } - - if (!clear) - { - foreach (string str in filtered) - { - string[] entryParts = str.Split(':'); - string senderId = entryParts[0].Trim(); - string senderMessage = entryParts[1].Trim(); - for (var j = 2; j < entryParts.Length; j++) senderMessage += ':' + entryParts[j].Trim(); - - PlayerControl senderPlayer = Utils.GetPlayerById(Convert.ToByte(senderId)); - if (senderPlayer == null) continue; - - DestroyableSingleton.Instance.Chat.AddChat(senderPlayer, senderMessage); - SendRPC(senderPlayer, senderMessage); - } - } - - ChatUpdatePatch.SendLastMessages(); - } - finally - { - ChatUpdatePatch.DoBlockChat = false; - } - } - - private static void SendRPC(InnerNetObject senderPlayer, string senderMessage) - { - var writer = CustomRpcSender.Create("MessagesToSend"); - writer.StartMessage(); - - writer.StartRpc(senderPlayer.NetId, (byte)RpcCalls.SendChat) - .Write(senderMessage) - .EndRpc(); - - writer.EndMessage(); - writer.SendMessage(); - } + writer.EndMessage(); + writer.SendMessage(); } } \ No newline at end of file diff --git a/Patches/CheckGameEndPatch.cs b/Patches/CheckGameEndPatch.cs index 082ab2dd7..e0f142793 100644 --- a/Patches/CheckGameEndPatch.cs +++ b/Patches/CheckGameEndPatch.cs @@ -18,306 +18,33 @@ using static EHR.CustomWinnerHolder; using static EHR.Translator; using static EHR.Utils; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] +internal static class GameEndChecker { - [HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] - internal static class GameEndChecker + private const float EndGameDelay = 0.2f; + public static GameEndPredicate Predicate; + public static bool ShouldNotCheck = false; + public static bool Ended; + + public static bool Prefix() { - private const float EndGameDelay = 0.2f; - public static GameEndPredicate Predicate; - public static bool ShouldNotCheck = false; - public static bool Ended; + if (!AmongUsClient.Instance.AmHost) return true; - public static bool Prefix() + if (Predicate == null || ShouldNotCheck || Main.HasJustStarted) return false; + + if (Options.NoGameEnd.GetBool() && WinnerTeam is not CustomWinner.Draw and not CustomWinner.Error) return false; + + Ended = false; + + Predicate.CheckForGameEnd(out GameOverReason reason); + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) { - if (!AmongUsClient.Instance.AmHost) return true; - - if (Predicate == null || ShouldNotCheck || Main.HasJustStarted) return false; - - if (Options.NoGameEnd.GetBool() && WinnerTeam is not CustomWinner.Draw and not CustomWinner.Error) return false; - - Ended = false; - - Predicate.CheckForGameEnd(out GameOverReason reason); - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) + if (WinnerIds.Count > 0 || WinnerTeam != CustomWinner.Default) { - if (WinnerIds.Count > 0 || WinnerTeam != CustomWinner.Default) - { - Ended = true; - ShipStatus.Instance.enabled = false; - StartEndGame(reason); - Predicate = null; - } - - return false; - } - - if (WinnerTeam != CustomWinner.Default) - { - NameNotifyManager.Reset(); - NotifyRoles(NoCache: true); - - Main.AllPlayerControls.Do(pc => Camouflage.RpcSetSkin(pc, true, true, true)); - Ended = true; - - if (reason == GameOverReason.ImpostorBySabotage && Options.NKWinsBySabotageIfNoImpAlive.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.IsImpostor()) && Main.AllAlivePlayerControls.Count(x => x.IsNeutralKiller()) == 1) - { - PlayerControl winner = Main.AllAlivePlayerControls.First(x => x.IsNeutralKiller()); - CustomRoles winnerRole = winner.GetCustomRole(); - - ResetAndSetWinner((CustomWinner)winnerRole); - WinnerRoles.Add(winnerRole); - WinnerIds.Add(winner.PlayerId); - } - - switch (WinnerTeam) - { - case CustomWinner.Crewmate: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => (pc.Is(CustomRoleTypes.Crewmate) || (pc.Is(CustomRoles.Haunter) && Haunter.CanWinWithCrew(pc))) && !pc.IsMadmate() && !pc.IsConverted() && !pc.Is(CustomRoles.EvilSpirit)) - .Select(pc => pc.PlayerId)); - - break; - case CustomWinner.Impostor: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => ((pc.Is(CustomRoleTypes.Impostor) && (!pc.Is(CustomRoles.DeadlyQuota) || Main.PlayerStates.Count(x => x.Value.GetRealKiller() == pc.PlayerId) >= Options.DQNumOfKillsNeeded.GetInt())) || pc.IsMadmate()) && !pc.IsConverted() && !pc.Is(CustomRoles.EvilSpirit)) - .Select(pc => pc.PlayerId)); - - break; - case CustomWinner.Succubus: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => pc.Is(CustomRoles.Succubus) || pc.Is(CustomRoles.Charmed)) - .Select(pc => pc.PlayerId)); - - break; - case CustomWinner.Necromancer: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => pc.Is(CustomRoles.Necromancer) || pc.Is(CustomRoles.Deathknight) || pc.Is(CustomRoles.Undead)) - .Select(pc => pc.PlayerId)); - - break; - case CustomWinner.Virus: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => pc.Is(CustomRoles.Virus) || pc.Is(CustomRoles.Contagious)) - .Select(pc => pc.PlayerId)); - - break; - case CustomWinner.Jackal: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => pc.Is(CustomRoles.Jackal) || pc.Is(CustomRoles.Sidekick) || pc.Is(CustomRoles.Recruit)) - .Select(pc => pc.PlayerId)); - - break; - case CustomWinner.Spiritcaller: - WinnerIds.UnionWith(Main.AllPlayerControls - .Where(pc => pc.Is(CustomRoles.Spiritcaller) || pc.Is(CustomRoles.EvilSpirit)) - .Select(pc => pc.PlayerId)); - - WinnerRoles.Add(CustomRoles.Spiritcaller); - break; - case CustomWinner.RuthlessRomantic: - WinnerIds.Add(Romantic.PartnerId); - break; - } - - if (WinnerTeam is not CustomWinner.Draw and not CustomWinner.None and not CustomWinner.Error) - { - foreach (PlayerControl pc in Main.AllPlayerControls) - { - switch (pc.GetCustomRole()) - { - case CustomRoles.DarkHide when pc.IsAlive() && ((WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorBySabotage)) || WinnerTeam == CustomWinner.DarkHide || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && Main.PlayerStates[pc.PlayerId].Role is DarkHide { IsWinKill: true } && DarkHide.SnatchesWin.GetBool())): - ResetAndSetWinner(CustomWinner.DarkHide); - WinnerIds.Add(pc.PlayerId); - break; - case CustomRoles.Phantasm when pc.GetTaskState().RemainingTasksCount <= 0 && !pc.IsAlive() && Options.PhantomSnatchesWin.GetBool(): - reason = GameOverReason.ImpostorByKill; - ResetAndSetWinner(CustomWinner.Phantom); - WinnerIds.Add(pc.PlayerId); - break; - case CustomRoles.Phantasm when pc.GetTaskState().RemainingTasksCount <= 0 && !pc.IsAlive() && !Options.PhantomSnatchesWin.GetBool(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Phantom); - break; - case CustomRoles.Opportunist when pc.IsAlive(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Opportunist); - break; - case CustomRoles.Pursuer when pc.IsAlive() && WinnerTeam is not CustomWinner.Jester and not CustomWinner.Lovers and not CustomWinner.Terrorist and not CustomWinner.Executioner and not CustomWinner.Collector and not CustomWinner.Innocent and not CustomWinner.Youtuber: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Pursuer); - break; - case CustomRoles.Sunnyboy when !pc.IsAlive(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Sunnyboy); - break; - case CustomRoles.Maverick when pc.IsAlive() && Main.PlayerStates[pc.PlayerId].Role is Maverick mr && mr.NumOfKills >= Maverick.MinKillsToWin.GetInt(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Maverick); - break; - case CustomRoles.Provocateur when Provocateur.Provoked.TryGetValue(pc.PlayerId, out byte tar) && !WinnerIds.Contains(tar): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Provocateur); - break; - case CustomRoles.FFF when (Main.PlayerStates[pc.PlayerId].Role as FFF).IsWon: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.FFF); - break; - case CustomRoles.Totocalcio when Main.PlayerStates[pc.PlayerId].Role is Totocalcio tc && tc.BetPlayer != byte.MaxValue && (WinnerIds.Contains(tc.BetPlayer) || (Main.PlayerStates.TryGetValue(tc.BetPlayer, out PlayerState ps) && (WinnerRoles.Contains(ps.MainRole) || (WinnerTeam == CustomWinner.Bloodlust && ps.SubRoles.Contains(CustomRoles.Bloodlust))))): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Totocalcio); - break; - case CustomRoles.Romantic when WinnerIds.Contains(Romantic.PartnerId) || (Main.PlayerStates.TryGetValue(Romantic.PartnerId, out PlayerState ps) && (WinnerRoles.Contains(ps.MainRole) || (WinnerTeam == CustomWinner.Bloodlust && ps.SubRoles.Contains(CustomRoles.Bloodlust)))): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Romantic); - break; - case CustomRoles.VengefulRomantic when VengefulRomantic.HasKilledKiller: - WinnerIds.Add(pc.PlayerId); - WinnerIds.Add(Romantic.PartnerId); - AdditionalWinnerTeams.Add(AdditionalWinners.VengefulRomantic); - break; - case CustomRoles.Lawyer when Lawyer.Target.TryGetValue(pc.PlayerId, out byte lawyertarget) && (WinnerIds.Contains(lawyertarget) || (Main.PlayerStates.TryGetValue(lawyertarget, out PlayerState ps) && (WinnerRoles.Contains(ps.MainRole) || (WinnerTeam == CustomWinner.Bloodlust && ps.SubRoles.Contains(CustomRoles.Bloodlust))))): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); - break; - case CustomRoles.Postman when (Main.PlayerStates[pc.PlayerId].Role as Postman).IsFinished: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Postman); - break; - case CustomRoles.Impartial when (Main.PlayerStates[pc.PlayerId].Role as Impartial).IsWon: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Impartial); - break; - case CustomRoles.Tank when (Main.PlayerStates[pc.PlayerId].Role as Tank).IsWon: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Tank); - break; - case CustomRoles.Technician when (Main.PlayerStates[pc.PlayerId].Role as Technician).IsWon: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Technician); - break; - case CustomRoles.Backstabber when (Main.PlayerStates[pc.PlayerId].Role as Backstabber).CheckWin(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Backstabber); - break; - case CustomRoles.Predator when (Main.PlayerStates[pc.PlayerId].Role as Predator).IsWon: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Predator); - break; - case CustomRoles.Gaslighter when (Main.PlayerStates[pc.PlayerId].Role as Gaslighter).AddAsAdditionalWinner(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Gaslighter); - break; - case CustomRoles.SoulHunter when (Main.PlayerStates[pc.PlayerId].Role as SoulHunter).Souls >= SoulHunter.NumOfSoulsToWin.GetInt(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.SoulHunter); - break; - case CustomRoles.SchrodingersCat when WinnerTeam == CustomWinner.Crewmate && SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool(): - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.SchrodingersCat); - break; - case CustomRoles.SchrodingersCat when !pc.IsConverted(): - WinnerIds.Remove(pc.PlayerId); - break; - case CustomRoles.Curser when WinnerTeam != CustomWinner.Crewmate: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.Curser); - break; - case CustomRoles.NoteKiller when !NoteKiller.CountsAsNeutralKiller && NoteKiller.Kills >= NoteKiller.NumKillsNeededToWin: - WinnerIds.Add(pc.PlayerId); - AdditionalWinnerTeams.Add(AdditionalWinners.NoteKiller); - break; - } - } - - if (WinnerTeam == CustomWinner.Impostor) - { - IEnumerable aliveImps = Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoleTypes.Impostor)); - PlayerControl[] imps = aliveImps as PlayerControl[] ?? aliveImps.ToArray(); - int aliveImpCount = imps.Length; - - switch (aliveImpCount) - { - // If there's an Egoist, and there is at least 1 non-Egoist impostor alive, Egoist loses - case > 1 when WinnerIds.Any(x => GetPlayerById(x).Is(CustomRoles.Egoist)): - WinnerIds.RemoveWhere(x => GetPlayerById(x).Is(CustomRoles.Egoist)); - break; - // If there's only 1 impostor alive, and all alive impostors are Egoists, the Egoist wins alone - case 1 when imps.All(x => x.Is(CustomRoles.Egoist)): - PlayerControl pc = imps[0]; - reason = GameOverReason.ImpostorByKill; - WinnerTeam = CustomWinner.Egoist; - WinnerIds.RemoveWhere(x => Main.PlayerStates[x].MainRole.IsImpostor() || x.GetPlayer().IsMadmate()); - WinnerIds.Add(pc.PlayerId); - break; - } - } - - byte[] winningSpecters = GhostRolesManager.AssignedGhostRoles.Where(x => x.Value.Instance is Specter { IsWon: true }).Select(x => x.Key).ToArray(); - - if (winningSpecters.Length > 0) - { - AdditionalWinnerTeams.Add(AdditionalWinners.Specter); - WinnerIds.UnionWith(winningSpecters); - } - - if (CustomRoles.God.RoleExist()) - { - ResetAndSetWinner(CustomWinner.God); - - Main.AllPlayerControls - .Where(p => p.Is(CustomRoles.God) && p.IsAlive()) - .Do(p => WinnerIds.Add(p.PlayerId)); - } - - if (WinnerTeam != CustomWinner.CustomTeam && CustomTeamManager.EnabledCustomTeams.Count > 0) - { - Main.AllPlayerControls - .Select(x => new { Team = CustomTeamManager.GetCustomTeam(x.PlayerId), Player = x }) - .Where(x => x.Team != null) - .GroupBy(x => x.Team) - .ToDictionary(x => x.Key, x => x.Select(y => y.Player.PlayerId)) - .Do(x => - { - bool canWin = CustomTeamManager.IsSettingEnabledForTeam(x.Key, CTAOption.WinWithOriginalTeam); - - if (!canWin) - WinnerIds.ExceptWith(x.Value); - else - WinnerIds.UnionWith(x.Value); - }); - } - - if ((WinnerTeam == CustomWinner.Lovers || WinnerIds.Any(x => Main.PlayerStates[x].SubRoles.Contains(CustomRoles.Lovers))) && Main.LoversPlayers.TrueForAll(x => x.IsAlive()) && reason != GameOverReason.HumansByTask) - { - if (WinnerTeam != CustomWinner.Lovers) AdditionalWinnerTeams.Add(AdditionalWinners.Lovers); - - WinnerIds.UnionWith(Main.LoversPlayers.Select(x => x.PlayerId)); - } - - if (Options.NeutralWinTogether.GetBool() && (WinnerRoles.Any(x => x.IsNeutral()) || WinnerIds.Select(x => GetPlayerById(x)).Any(x => x != null && x.GetCustomRole().IsNeutral() && !x.IsMadmate()))) - WinnerIds.UnionWith(Main.AllPlayerControls.Where(x => x.GetCustomRole().IsNeutral()).Select(x => x.PlayerId)); - else if (Options.NeutralRoleWinTogether.GetBool()) - { - foreach (byte id in WinnerIds.ToArray()) - { - PlayerControl pc = GetPlayerById(id); - if (pc == null || !pc.GetCustomRole().IsNeutral()) continue; - - foreach (PlayerControl tar in Main.AllPlayerControls) - if (!WinnerIds.Contains(tar.PlayerId) && tar.GetCustomRole() == pc.GetCustomRole()) - WinnerIds.Add(tar.PlayerId); - } - - foreach (CustomRoles role in WinnerRoles) WinnerIds.UnionWith(Main.AllPlayerControls.Where(x => x.GetCustomRole() == role).Select(x => x.PlayerId)); - } - - WinnerIds.RemoveWhere(x => Main.PlayerStates[x].MainRole == CustomRoles.Shifter); - } - - Camouflage.BlockCamouflage = true; ShipStatus.Instance.enabled = false; StartEndGame(reason); Predicate = null; @@ -326,702 +53,974 @@ namespace EHR return false; } - private static void StartEndGame(GameOverReason reason) + if (WinnerTeam != CustomWinner.Default) { - try + NameNotifyManager.Reset(); + NotifyRoles(NoCache: true); + + Main.AllPlayerControls.Do(pc => Camouflage.RpcSetSkin(pc, true, true, true)); + + Ended = true; + + if (reason == GameOverReason.ImpostorBySabotage && Options.NKWinsBySabotageIfNoImpAlive.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.IsImpostor()) && Main.AllAlivePlayerControls.Count(x => x.IsNeutralKiller()) == 1) { - LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.Ended); - } - catch (Exception e) - { - ThrowException(e); + PlayerControl winner = Main.AllAlivePlayerControls.First(x => x.IsNeutralKiller()); + CustomRoles winnerRole = winner.GetCustomRole(); + + ResetAndSetWinner((CustomWinner)winnerRole); + WinnerRoles.Add(winnerRole); + WinnerIds.Add(winner.PlayerId); } - string msg = GetString("NotifyGameEnding"); - - Main.AllPlayerControls.DoIf( - x => x.GetClient() != null && !x.Data.Disconnected, - x => ChatUpdatePatch.SendMessage(PlayerControl.LocalPlayer, "\n", x.PlayerId, msg)); - - SetEverythingUpPatch.LastWinsReason = WinnerTeam is CustomWinner.Crewmate or CustomWinner.Impostor ? GetString($"GameOverReason.{reason}") : string.Empty; - var self = AmongUsClient.Instance; - self.StartCoroutine(CoEndGame(self, reason).WrapToIl2Cpp()); - - Statistics.OnGameEnd(); - } - - private static IEnumerator CoEndGame(InnerNetClient self, GameOverReason reason) - { - Silencer.ForSilencer.Clear(); - - // Set ghost role - List ReviveRequiredPlayerIds = []; - CustomWinner winner = WinnerTeam; - - foreach (PlayerControl pc in Main.AllPlayerControls) + switch (WinnerTeam) { - if (winner == CustomWinner.Draw) - { - SetGhostRole(true); - continue; - } + case CustomWinner.Crewmate: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => (pc.Is(CustomRoleTypes.Crewmate) || (pc.Is(CustomRoles.Haunter) && Haunter.CanWinWithCrew(pc))) && !pc.IsMadmate() && !pc.IsConverted() && !pc.Is(CustomRoles.EvilSpirit)) + .Select(pc => pc.PlayerId)); - bool canWin = WinnerIds.Contains(pc.PlayerId) || WinnerRoles.Contains(pc.GetCustomRole()) || (winner == CustomWinner.Bloodlust && pc.Is(CustomRoles.Bloodlust)); - bool isCrewmateWin = reason.Equals(GameOverReason.HumansByVote) || reason.Equals(GameOverReason.HumansByTask); - SetGhostRole(canWin ^ isCrewmateWin); // XOR - continue; + break; + case CustomWinner.Impostor: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => ((pc.Is(CustomRoleTypes.Impostor) && (!pc.Is(CustomRoles.DeadlyQuota) || Main.PlayerStates.Count(x => x.Value.GetRealKiller() == pc.PlayerId) >= Options.DQNumOfKillsNeeded.GetInt())) || pc.IsMadmate()) && !pc.IsConverted() && !pc.Is(CustomRoles.EvilSpirit)) + .Select(pc => pc.PlayerId)); - void SetGhostRole(bool ToGhostImpostor) - { - if (!pc.Data.IsDead) ReviveRequiredPlayerIds.Add(pc.PlayerId); + break; + case CustomWinner.Succubus: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Succubus) || pc.Is(CustomRoles.Charmed)) + .Select(pc => pc.PlayerId)); - if (ToGhostImpostor) - { - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()}: changed to ImpostorGhost", "ResetRoleAndEndGame"); - pc.RpcSetRole(RoleTypes.ImpostorGhost); - } - else - { - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()}: changed to CrewmateGhost", "ResetRoleAndEndGame"); - pc.RpcSetRole(RoleTypes.CrewmateGhost); - } - } + break; + case CustomWinner.Necromancer: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Necromancer) || pc.Is(CustomRoles.Deathknight) || pc.Is(CustomRoles.Undead)) + .Select(pc => pc.PlayerId)); + + break; + case CustomWinner.Virus: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Virus) || pc.Is(CustomRoles.Contagious)) + .Select(pc => pc.PlayerId)); + + break; + case CustomWinner.Jackal: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Jackal) || pc.Is(CustomRoles.Sidekick) || pc.Is(CustomRoles.Recruit)) + .Select(pc => pc.PlayerId)); + + break; + case CustomWinner.Spiritcaller: + WinnerIds.UnionWith(Main.AllPlayerControls + .Where(pc => pc.Is(CustomRoles.Spiritcaller) || pc.Is(CustomRoles.EvilSpirit)) + .Select(pc => pc.PlayerId)); + + WinnerRoles.Add(CustomRoles.Spiritcaller); + break; + case CustomWinner.RuthlessRomantic: + WinnerIds.Add(Romantic.PartnerId); + break; } - // Sync of CustomWinnerHolder info - MessageWriter winnerWriter = self.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EndGame, SendOption.Reliable); - WriteTo(winnerWriter); - self.FinishRpcImmediately(winnerWriter); - - // Delay to ensure that resuscitation is delivered after the ghost roll setting - yield return new WaitForSeconds(EndGameDelay); - - if (ReviveRequiredPlayerIds.Count > 0) + if (WinnerTeam is not CustomWinner.Draw and not CustomWinner.None and not CustomWinner.Error) { - // Resuscitation Resuscitate one person per transmission to prevent the packet from swelling up and dying - foreach (byte playerId in ReviveRequiredPlayerIds) + foreach (PlayerControl pc in Main.AllPlayerControls) { - NetworkedPlayerInfo playerInfo = GameData.Instance.GetPlayerById(playerId); - // resuscitation - playerInfo.IsDead = false; - // transmission - playerInfo.SetDirtyBit(0b_1u << playerId); - self.SendAllStreamedObjects(); - } - - // Delay to ensure that the end of the game is delivered at the end of the game - yield return new WaitForSeconds(EndGameDelay); - } - - // Start End Game - GameManager.Instance.RpcEndGame(reason, false); - } - - public static void SetPredicateToNormal() - { - Predicate = new NormalGameEndPredicate(); - } - - public static void SetPredicateToSoloKombat() - { - Predicate = new SoloKombatGameEndPredicate(); - } - - public static void SetPredicateToFFA() - { - Predicate = new FFAGameEndPredicate(); - } - - public static void SetPredicateToMoveAndStop() - { - Predicate = new MoveAndStopGameEndPredicate(); - } - - public static void SetPredicateToHotPotato() - { - Predicate = new HotPotatoGameEndPredicate(); - } - - public static void SetPredicateToSpeedrun() - { - Predicate = new SpeedrunGameEndPredicate(); - } - - public static void SetPredicateToHideAndSeek() - { - Predicate = new HideAndSeekGameEndPredicate(); - } - - public static void SetPredicateToCaptureTheFlag() - { - Predicate = new CaptureTheFlagGameEndPredicate(); - } - - public static void SetPredicateToNaturalDisasters() - { - Predicate = new NaturalDisastersGameEndPredicate(); - } - - public static void SetPredicateToRoomRush() - { - Predicate = new RoomRushGameEndPredicate(); - } - - public static void SetPredicateToAllInOne() - { - Predicate = new AllInOneGameEndPredicate(); - } - - private class NormalGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - if (WinnerTeam != CustomWinner.Default) return false; - - return CheckGameEndBySabotage(out reason) || CheckGameEndByTask(out reason) || CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - if (Main.HasJustStarted) return false; - - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - if (CustomRoles.Sunnyboy.RoleExist() && aapc.Length > 1) return false; - - if (CustomTeamManager.CheckCustomTeamGameEnd()) return true; - - if (aapc.All(x => Main.LoversPlayers.Exists(l => l.PlayerId == x.PlayerId)) && (!Main.LoversPlayers.TrueForAll(x => x.Is(Team.Crewmate)) || !Lovers.CrewLoversWinWithCrew.GetBool())) - { - ResetAndSetWinner(CustomWinner.Lovers); - WinnerIds.UnionWith(Main.LoversPlayers.ConvertAll(x => x.PlayerId)); - return true; - } - - int sheriffCount = AlivePlayersCount(CountTypes.Sheriff); - - int Imp = AlivePlayersCount(CountTypes.Impostor); - int Crew = AlivePlayersCount(CountTypes.Crew) + sheriffCount; - - Dictionary<(CustomRoles? ROLE, CustomWinner WINNER), int> roleCounts = []; - - foreach (CustomRoles role in Enum.GetValues()) - { - if ((!role.IsNK() && role is not CustomRoles.Bloodlust and not CustomRoles.Gaslighter) || role.IsMadmate() || role is CustomRoles.Sidekick) continue; - - CountTypes countTypes = role.GetCountTypes(); - if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame or CountTypes.CustomTeam) continue; - - CustomRoles? keyRole = role.IsRecruitingRole() ? null : role; - var keyWinner = (CustomWinner)role; - int value = AlivePlayersCount(countTypes); - - roleCounts[(keyRole, keyWinner)] = value; - } - - if (CustomRoles.DualPersonality.IsEnable()) - { - foreach (PlayerControl x in aapc) + switch (pc.GetCustomRole()) { - if (!x.Is(CustomRoles.DualPersonality)) continue; - - if (x.Is(Team.Impostor)) - Imp++; - else if (x.Is(Team.Crewmate)) Crew++; - - if (x.Is(CustomRoles.Charmed)) roleCounts[(null, CustomWinner.Succubus)]++; - - if (x.Is(CustomRoles.Undead)) roleCounts[(null, CustomWinner.Necromancer)]++; - - if (x.Is(CustomRoles.Sidekick)) roleCounts[(null, CustomWinner.Jackal)]++; - - if (x.Is(CustomRoles.Recruit)) roleCounts[(null, CustomWinner.Jackal)]++; - - if (x.Is(CustomRoles.Contagious)) roleCounts[(null, CustomWinner.Virus)]++; - } - } - - int totalNKAlive = roleCounts.Values.Sum(); - - CustomWinner? winner = null; - CustomRoles? rl = null; - - if (totalNKAlive == 0) - { - if (Crew == 0 && Imp == 0) - { - reason = GameOverReason.ImpostorByKill; - winner = CustomWinner.None; - } - else if (Crew <= Imp) - { - reason = GameOverReason.ImpostorByKill; - winner = CustomWinner.Impostor; - } - else if (Imp == 0) - { - reason = GameOverReason.HumansByVote; - winner = CustomWinner.Crewmate; - } - else - return false; - - Logger.Info($"Crew: {Crew}, Imp: {Imp}", "CheckGameEndPatch.CheckGameEndByLivingPlayers"); - ResetAndSetWinner((CustomWinner)winner); - return true; - } - - if (Imp >= 1) return false; // both imps and NKs are alive, game must continue - - if (Crew > totalNKAlive) return false; // Imps are dead, but crew still outnumbers NKs, game must continue - - // Imps dead, Crew <= NK, Checking if all NKs alive are in 1 team - List aliveCounts = roleCounts.Values.Where(x => x > 0).ToList(); - - switch (aliveCounts.Count) - { - // There are multiple types of NKs alive, game must continue - case > 1: - return false; - // If the Sheriff keeps the game going, the game must continue - case 1 when Sheriff.KeepsGameGoing.GetBool() && sheriffCount > 0: - return false; - // There is only one type of NK alive, they've won - case 1: - { - if (aliveCounts[0] != roleCounts.Values.Max()) Logger.Warn("There is something wrong here.", "CheckGameEndPatch"); - - foreach (KeyValuePair<(CustomRoles? ROLE, CustomWinner WINNER), int> keyValuePair in roleCounts.Where(keyValuePair => keyValuePair.Value == aliveCounts[0])) - { + case CustomRoles.DarkHide when pc.IsAlive() && ((WinnerTeam == CustomWinner.Impostor && !reason.Equals(GameOverReason.ImpostorBySabotage)) || WinnerTeam == CustomWinner.DarkHide || (WinnerTeam == CustomWinner.Crewmate && !reason.Equals(GameOverReason.HumansByTask) && Main.PlayerStates[pc.PlayerId].Role is DarkHide { IsWinKill: true } && DarkHide.SnatchesWin.GetBool())): + ResetAndSetWinner(CustomWinner.DarkHide); + WinnerIds.Add(pc.PlayerId); + break; + case CustomRoles.Phantasm when pc.GetTaskState().RemainingTasksCount <= 0 && !pc.IsAlive() && Options.PhantomSnatchesWin.GetBool(): reason = GameOverReason.ImpostorByKill; - winner = keyValuePair.Key.WINNER; - rl = keyValuePair.Key.ROLE; + ResetAndSetWinner(CustomWinner.Phantom); + WinnerIds.Add(pc.PlayerId); + break; + case CustomRoles.Phantasm when pc.GetTaskState().RemainingTasksCount <= 0 && !pc.IsAlive() && !Options.PhantomSnatchesWin.GetBool(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Phantom); + break; + case CustomRoles.Opportunist when pc.IsAlive(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Opportunist); + break; + case CustomRoles.Pursuer when pc.IsAlive() && WinnerTeam is not CustomWinner.Jester and not CustomWinner.Lovers and not CustomWinner.Terrorist and not CustomWinner.Executioner and not CustomWinner.Collector and not CustomWinner.Innocent and not CustomWinner.Youtuber: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Pursuer); + break; + case CustomRoles.Sunnyboy when !pc.IsAlive(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Sunnyboy); + break; + case CustomRoles.Maverick when pc.IsAlive() && Main.PlayerStates[pc.PlayerId].Role is Maverick mr && mr.NumOfKills >= Maverick.MinKillsToWin.GetInt(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Maverick); + break; + case CustomRoles.Provocateur when Provocateur.Provoked.TryGetValue(pc.PlayerId, out byte tar) && !WinnerIds.Contains(tar): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Provocateur); + break; + case CustomRoles.FFF when (Main.PlayerStates[pc.PlayerId].Role as FFF).IsWon: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.FFF); + break; + case CustomRoles.Totocalcio when Main.PlayerStates[pc.PlayerId].Role is Totocalcio tc && tc.BetPlayer != byte.MaxValue && (WinnerIds.Contains(tc.BetPlayer) || (Main.PlayerStates.TryGetValue(tc.BetPlayer, out PlayerState ps) && (WinnerRoles.Contains(ps.MainRole) || (WinnerTeam == CustomWinner.Bloodlust && ps.SubRoles.Contains(CustomRoles.Bloodlust))))): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Totocalcio); + break; + case CustomRoles.Romantic when WinnerIds.Contains(Romantic.PartnerId) || (Main.PlayerStates.TryGetValue(Romantic.PartnerId, out PlayerState ps) && (WinnerRoles.Contains(ps.MainRole) || (WinnerTeam == CustomWinner.Bloodlust && ps.SubRoles.Contains(CustomRoles.Bloodlust)))): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Romantic); + break; + case CustomRoles.VengefulRomantic when VengefulRomantic.HasKilledKiller: + WinnerIds.Add(pc.PlayerId); + WinnerIds.Add(Romantic.PartnerId); + AdditionalWinnerTeams.Add(AdditionalWinners.VengefulRomantic); + break; + case CustomRoles.Lawyer when Lawyer.Target.TryGetValue(pc.PlayerId, out byte lawyertarget) && (WinnerIds.Contains(lawyertarget) || (Main.PlayerStates.TryGetValue(lawyertarget, out PlayerState ps) && (WinnerRoles.Contains(ps.MainRole) || (WinnerTeam == CustomWinner.Bloodlust && ps.SubRoles.Contains(CustomRoles.Bloodlust))))): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Lawyer); + break; + case CustomRoles.Postman when (Main.PlayerStates[pc.PlayerId].Role as Postman).IsFinished: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Postman); + break; + case CustomRoles.Impartial when (Main.PlayerStates[pc.PlayerId].Role as Impartial).IsWon: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Impartial); + break; + case CustomRoles.Tank when (Main.PlayerStates[pc.PlayerId].Role as Tank).IsWon: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Tank); + break; + case CustomRoles.Technician when (Main.PlayerStates[pc.PlayerId].Role as Technician).IsWon: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Technician); + break; + case CustomRoles.Backstabber when (Main.PlayerStates[pc.PlayerId].Role as Backstabber).CheckWin(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Backstabber); + break; + case CustomRoles.Predator when (Main.PlayerStates[pc.PlayerId].Role as Predator).IsWon: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Predator); + break; + case CustomRoles.Gaslighter when (Main.PlayerStates[pc.PlayerId].Role as Gaslighter).AddAsAdditionalWinner(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Gaslighter); + break; + case CustomRoles.SoulHunter when (Main.PlayerStates[pc.PlayerId].Role as SoulHunter).Souls >= SoulHunter.NumOfSoulsToWin.GetInt(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.SoulHunter); + break; + case CustomRoles.SchrodingersCat when WinnerTeam == CustomWinner.Crewmate && SchrodingersCat.WinsWithCrewIfNotAttacked.GetBool(): + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.SchrodingersCat); + break; + case CustomRoles.SchrodingersCat when !pc.IsConverted(): + WinnerIds.Remove(pc.PlayerId); + break; + case CustomRoles.Curser when WinnerTeam != CustomWinner.Crewmate: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.Curser); + break; + case CustomRoles.NoteKiller when !NoteKiller.CountsAsNeutralKiller && NoteKiller.Kills >= NoteKiller.NumKillsNeededToWin: + WinnerIds.Add(pc.PlayerId); + AdditionalWinnerTeams.Add(AdditionalWinners.NoteKiller); break; - } - - break; } - default: - Logger.Fatal("Error while selecting NK winner", "CheckGameEndPatch.CheckGameEndByLivingPlayers"); - Logger.SendInGame("There was an error while selecting the winner. Please report this bug to the developer! (Do /dump to get logs)"); - ResetAndSetWinner(CustomWinner.Error); - return true; } - if (winner != null) ResetAndSetWinner((CustomWinner)winner); + if (WinnerTeam == CustomWinner.Impostor) + { + IEnumerable aliveImps = Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoleTypes.Impostor)); + PlayerControl[] imps = aliveImps as PlayerControl[] ?? aliveImps.ToArray(); + int aliveImpCount = imps.Length; - if (rl != null) WinnerRoles.Add((CustomRoles)rl); + switch (aliveImpCount) + { + // If there's an Egoist, and there is at least 1 non-Egoist impostor alive, Egoist loses + case > 1 when WinnerIds.Any(x => GetPlayerById(x).Is(CustomRoles.Egoist)): + WinnerIds.RemoveWhere(x => GetPlayerById(x).Is(CustomRoles.Egoist)); + break; + // If there's only 1 impostor alive, and all alive impostors are Egoists, the Egoist wins alone + case 1 when imps.All(x => x.Is(CustomRoles.Egoist)): + PlayerControl pc = imps[0]; + reason = GameOverReason.ImpostorByKill; + WinnerTeam = CustomWinner.Egoist; + WinnerIds.RemoveWhere(x => Main.PlayerStates[x].MainRole.IsImpostor() || x.GetPlayer().IsMadmate()); + WinnerIds.Add(pc.PlayerId); + break; + } + } + byte[] winningSpecters = GhostRolesManager.AssignedGhostRoles.Where(x => x.Value.Instance is Specter { IsWon: true }).Select(x => x.Key).ToArray(); + + if (winningSpecters.Length > 0) + { + AdditionalWinnerTeams.Add(AdditionalWinners.Specter); + WinnerIds.UnionWith(winningSpecters); + } + + if (CustomRoles.God.RoleExist()) + { + ResetAndSetWinner(CustomWinner.God); + + Main.AllPlayerControls + .Where(p => p.Is(CustomRoles.God) && p.IsAlive()) + .Do(p => WinnerIds.Add(p.PlayerId)); + } + + if (WinnerTeam != CustomWinner.CustomTeam && CustomTeamManager.EnabledCustomTeams.Count > 0) + { + Main.AllPlayerControls + .Select(x => new { Team = CustomTeamManager.GetCustomTeam(x.PlayerId), Player = x }) + .Where(x => x.Team != null) + .GroupBy(x => x.Team) + .ToDictionary(x => x.Key, x => x.Select(y => y.Player.PlayerId)) + .Do(x => + { + bool canWin = CustomTeamManager.IsSettingEnabledForTeam(x.Key, CTAOption.WinWithOriginalTeam); + + if (!canWin) + WinnerIds.ExceptWith(x.Value); + else + WinnerIds.UnionWith(x.Value); + }); + } + + if ((WinnerTeam == CustomWinner.Lovers || WinnerIds.Any(x => Main.PlayerStates[x].SubRoles.Contains(CustomRoles.Lovers))) && Main.LoversPlayers.TrueForAll(x => x.IsAlive()) && reason != GameOverReason.HumansByTask) + { + if (WinnerTeam != CustomWinner.Lovers) AdditionalWinnerTeams.Add(AdditionalWinners.Lovers); + + WinnerIds.UnionWith(Main.LoversPlayers.Select(x => x.PlayerId)); + } + + if (Options.NeutralWinTogether.GetBool() && (WinnerRoles.Any(x => x.IsNeutral()) || WinnerIds.Select(x => GetPlayerById(x)).Any(x => x != null && x.GetCustomRole().IsNeutral() && !x.IsMadmate()))) + WinnerIds.UnionWith(Main.AllPlayerControls.Where(x => x.GetCustomRole().IsNeutral()).Select(x => x.PlayerId)); + else if (Options.NeutralRoleWinTogether.GetBool()) + { + foreach (byte id in WinnerIds.ToArray()) + { + PlayerControl pc = GetPlayerById(id); + if (pc == null || !pc.GetCustomRole().IsNeutral()) continue; + + foreach (PlayerControl tar in Main.AllPlayerControls) + if (!WinnerIds.Contains(tar.PlayerId) && tar.GetCustomRole() == pc.GetCustomRole()) + WinnerIds.Add(tar.PlayerId); + } + + foreach (CustomRoles role in WinnerRoles) WinnerIds.UnionWith(Main.AllPlayerControls.Where(x => x.GetCustomRole() == role).Select(x => x.PlayerId)); + } + + WinnerIds.RemoveWhere(x => Main.PlayerStates[x].MainRole == CustomRoles.Shifter); + } + + Camouflage.BlockCamouflage = true; + ShipStatus.Instance.enabled = false; + StartEndGame(reason); + Predicate = null; + } + + return false; + } + + private static void StartEndGame(GameOverReason reason) + { + try + { + LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.Ended); + } + catch (Exception e) + { + ThrowException(e); + } + + string msg = GetString("NotifyGameEnding"); + + Main.AllPlayerControls.DoIf( + x => x.GetClient() != null && !x.Data.Disconnected, + x => ChatUpdatePatch.SendMessage(PlayerControl.LocalPlayer, "\n", x.PlayerId, msg)); + + SetEverythingUpPatch.LastWinsReason = WinnerTeam is CustomWinner.Crewmate or CustomWinner.Impostor ? GetString($"GameOverReason.{reason}") : string.Empty; + var self = AmongUsClient.Instance; + self.StartCoroutine(CoEndGame(self, reason).WrapToIl2Cpp()); + + Statistics.OnGameEnd(); + } + + private static IEnumerator CoEndGame(InnerNetClient self, GameOverReason reason) + { + Silencer.ForSilencer.Clear(); + + // Set ghost role + List ReviveRequiredPlayerIds = []; + CustomWinner winner = WinnerTeam; + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (winner == CustomWinner.Draw) + { + SetGhostRole(true); + continue; + } + + bool canWin = WinnerIds.Contains(pc.PlayerId) || WinnerRoles.Contains(pc.GetCustomRole()) || (winner == CustomWinner.Bloodlust && pc.Is(CustomRoles.Bloodlust)); + bool isCrewmateWin = reason.Equals(GameOverReason.HumansByVote) || reason.Equals(GameOverReason.HumansByTask); + SetGhostRole(canWin ^ isCrewmateWin); // XOR + continue; + + void SetGhostRole(bool ToGhostImpostor) + { + if (!pc.Data.IsDead) ReviveRequiredPlayerIds.Add(pc.PlayerId); + + if (ToGhostImpostor) + { + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()}: changed to ImpostorGhost", "ResetRoleAndEndGame"); + pc.RpcSetRole(RoleTypes.ImpostorGhost); + } + else + { + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()}: changed to CrewmateGhost", "ResetRoleAndEndGame"); + pc.RpcSetRole(RoleTypes.CrewmateGhost); + } + } + } + + // Sync of CustomWinnerHolder info + MessageWriter winnerWriter = self.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EndGame, SendOption.Reliable); + WriteTo(winnerWriter); + self.FinishRpcImmediately(winnerWriter); + + // Delay to ensure that resuscitation is delivered after the ghost roll setting + yield return new WaitForSeconds(EndGameDelay); + + if (ReviveRequiredPlayerIds.Count > 0) + { + // Resuscitation Resuscitate one person per transmission to prevent the packet from swelling up and dying + foreach (byte playerId in ReviveRequiredPlayerIds) + { + NetworkedPlayerInfo playerInfo = GameData.Instance.GetPlayerById(playerId); + // resuscitation + playerInfo.IsDead = false; + // transmission + playerInfo.SetDirtyBit(0b_1u << playerId); + self.SendAllStreamedObjects(); + } + + // Delay to ensure that the end of the game is delivered at the end of the game + yield return new WaitForSeconds(EndGameDelay); + } + + // Start End Game + GameManager.Instance.RpcEndGame(reason, false); + } + + public static void SetPredicateToNormal() + { + Predicate = new NormalGameEndPredicate(); + } + + public static void SetPredicateToSoloKombat() + { + Predicate = new SoloKombatGameEndPredicate(); + } + + public static void SetPredicateToFFA() + { + Predicate = new FFAGameEndPredicate(); + } + + public static void SetPredicateToMoveAndStop() + { + Predicate = new MoveAndStopGameEndPredicate(); + } + + public static void SetPredicateToHotPotato() + { + Predicate = new HotPotatoGameEndPredicate(); + } + + public static void SetPredicateToSpeedrun() + { + Predicate = new SpeedrunGameEndPredicate(); + } + + public static void SetPredicateToHideAndSeek() + { + Predicate = new HideAndSeekGameEndPredicate(); + } + + public static void SetPredicateToCaptureTheFlag() + { + Predicate = new CaptureTheFlagGameEndPredicate(); + } + + public static void SetPredicateToNaturalDisasters() + { + Predicate = new NaturalDisastersGameEndPredicate(); + } + + public static void SetPredicateToRoomRush() + { + Predicate = new RoomRushGameEndPredicate(); + } + + public static void SetPredicateToAllInOne() + { + Predicate = new AllInOneGameEndPredicate(); + } + + private class NormalGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (WinnerTeam != CustomWinner.Default) return false; + + return CheckGameEndBySabotage(out reason) || CheckGameEndByTask(out reason) || CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (Main.HasJustStarted) return false; + + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + if (CustomRoles.Sunnyboy.RoleExist() && aapc.Length > 1) return false; + + if (CustomTeamManager.CheckCustomTeamGameEnd()) return true; + + if (aapc.All(x => Main.LoversPlayers.Exists(l => l.PlayerId == x.PlayerId)) && (!Main.LoversPlayers.TrueForAll(x => x.Is(Team.Crewmate)) || !Lovers.CrewLoversWinWithCrew.GetBool())) + { + ResetAndSetWinner(CustomWinner.Lovers); + WinnerIds.UnionWith(Main.LoversPlayers.ConvertAll(x => x.PlayerId)); return true; } - } - private class SoloKombatGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) + int sheriffCount = AlivePlayersCount(CountTypes.Sheriff); + + int Imp = AlivePlayersCount(CountTypes.Impostor); + int Crew = AlivePlayersCount(CountTypes.Crew) + sheriffCount; + + Dictionary<(CustomRoles? ROLE, CustomWinner WINNER), int> roleCounts = []; + + foreach (CustomRoles role in Enum.GetValues()) { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + if ((!role.IsNK() && role is not CustomRoles.Bloodlust and not CustomRoles.Gaslighter) || role.IsMadmate() || role is CustomRoles.Sidekick) continue; + + CountTypes countTypes = role.GetCountTypes(); + if (countTypes is CountTypes.Crew or CountTypes.Impostor or CountTypes.None or CountTypes.OutOfGame or CountTypes.CustomTeam) continue; + + CustomRoles? keyRole = role.IsRecruitingRole() ? null : role; + var keyWinner = (CustomWinner)role; + int value = AlivePlayersCount(countTypes); + + roleCounts[(keyRole, keyWinner)] = value; } - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + if (CustomRoles.DualPersonality.IsEnable()) { - reason = GameOverReason.ImpostorByKill; + foreach (PlayerControl x in aapc) + { + if (!x.Is(CustomRoles.DualPersonality)) continue; - if (SoloKombatManager.RoundTime > 0) return false; + if (x.Is(Team.Impostor)) + Imp++; + else if (x.Is(Team.Crewmate)) Crew++; - PlayerControl winner = Main.AllPlayerControls.FirstOrDefault(x => !x.Is(CustomRoles.GM) && SoloKombatManager.GetRankOfScore(x.PlayerId) == 1) ?? Main.AllAlivePlayerControls[0]; + if (x.Is(CustomRoles.Charmed)) roleCounts[(null, CustomWinner.Succubus)]++; - WinnerIds = - [ - winner.PlayerId - ]; + if (x.Is(CustomRoles.Undead)) roleCounts[(null, CustomWinner.Necromancer)]++; + + if (x.Is(CustomRoles.Sidekick)) roleCounts[(null, CustomWinner.Jackal)]++; + + if (x.Is(CustomRoles.Recruit)) roleCounts[(null, CustomWinner.Jackal)]++; + + if (x.Is(CustomRoles.Contagious)) roleCounts[(null, CustomWinner.Virus)]++; + } + } + + int totalNKAlive = roleCounts.Values.Sum(); + + CustomWinner? winner = null; + CustomRoles? rl = null; + + if (totalNKAlive == 0) + { + if (Crew == 0 && Imp == 0) + { + reason = GameOverReason.ImpostorByKill; + winner = CustomWinner.None; + } + else if (Crew <= Imp) + { + reason = GameOverReason.ImpostorByKill; + winner = CustomWinner.Impostor; + } + else if (Imp == 0) + { + reason = GameOverReason.HumansByVote; + winner = CustomWinner.Crewmate; + } + else + return false; + + Logger.Info($"Crew: {Crew}, Imp: {Imp}", "CheckGameEndPatch.CheckGameEndByLivingPlayers"); + ResetAndSetWinner((CustomWinner)winner); + return true; + } + + if (Imp >= 1) return false; // both imps and NKs are alive, game must continue + + if (Crew > totalNKAlive) return false; // Imps are dead, but crew still outnumbers NKs, game must continue + + // Imps dead, Crew <= NK, Checking if all NKs alive are in 1 team + List aliveCounts = roleCounts.Values.Where(x => x > 0).ToList(); + + switch (aliveCounts.Count) + { + // There are multiple types of NKs alive, game must continue + case > 1: + return false; + // If the Sheriff keeps the game going, the game must continue + case 1 when Sheriff.KeepsGameGoing.GetBool() && sheriffCount > 0: + return false; + // There is only one type of NK alive, they've won + case 1: + { + if (aliveCounts[0] != roleCounts.Values.Max()) Logger.Warn("There is something wrong here.", "CheckGameEndPatch"); + + foreach (KeyValuePair<(CustomRoles? ROLE, CustomWinner WINNER), int> keyValuePair in roleCounts.Where(keyValuePair => keyValuePair.Value == aliveCounts[0])) + { + reason = GameOverReason.ImpostorByKill; + winner = keyValuePair.Key.WINNER; + rl = keyValuePair.Key.ROLE; + break; + } + + break; + } + default: + Logger.Fatal("Error while selecting NK winner", "CheckGameEndPatch.CheckGameEndByLivingPlayers"); + Logger.SendInGame("There was an error while selecting the winner. Please report this bug to the developer! (Do /dump to get logs)"); + ResetAndSetWinner(CustomWinner.Error); + return true; + } + + if (winner != null) ResetAndSetWinner((CustomWinner)winner); + + if (rl != null) WinnerRoles.Add((CustomRoles)rl); + + return true; + } + } + + private class SoloKombatGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (SoloKombatManager.RoundTime > 0) return false; + + PlayerControl winner = Main.AllPlayerControls.FirstOrDefault(x => !x.Is(CustomRoles.GM) && SoloKombatManager.GetRankOfScore(x.PlayerId) == 1) ?? Main.AllAlivePlayerControls[0]; + + WinnerIds = + [ + winner.PlayerId + ]; + + Main.DoBlockNameChange = true; + + return true; + } + } + + private class FFAGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (FFAManager.RoundTime <= 0) + { + PlayerControl winner = Main.GM.Value && Main.AllPlayerControls.Length == 1 ? PlayerControl.LocalPlayer : Main.AllPlayerControls.Where(x => !x.Is(CustomRoles.GM) && x != null).OrderBy(x => FFAManager.GetRankFromScore(x.PlayerId)).First(); + + byte winnerId = winner.PlayerId; + + Logger.Warn($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "FFA"); + + WinnerIds = [winnerId]; Main.DoBlockNameChange = true; return true; } - } - private class FFAGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) + if (FFAManager.FFATeamMode.GetBool()) { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + IEnumerable> teams = FFAManager.PlayerTeams.GroupBy(x => x.Value, x => x.Key).Select(x => x.Where(p => + { + PlayerControl pc = GetPlayerById(p); + return pc != null && !pc.Data.Disconnected; + }).ToHashSet()).Where(x => x.Count > 0); + + foreach (HashSet team in teams) + { + if (Main.AllAlivePlayerControls.All(x => team.Contains(x.PlayerId))) + { + WinnerIds = team; + + Main.DoBlockNameChange = true; + return true; + } + } } - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + switch (Main.AllAlivePlayerControls.Length) { - reason = GameOverReason.ImpostorByKill; - - if (FFAManager.RoundTime <= 0) + case 1: { - PlayerControl winner = Main.GM.Value && Main.AllPlayerControls.Length == 1 ? PlayerControl.LocalPlayer : Main.AllPlayerControls.Where(x => !x.Is(CustomRoles.GM) && x != null).OrderBy(x => FFAManager.GetRankFromScore(x.PlayerId)).First(); + PlayerControl winner = Main.AllAlivePlayerControls[0]; - byte winnerId = winner.PlayerId; + Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "FFA"); - Logger.Warn($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "FFA"); - - WinnerIds = [winnerId]; + WinnerIds = + [ + winner.PlayerId + ]; Main.DoBlockNameChange = true; return true; } - - if (FFAManager.FFATeamMode.GetBool()) - { - IEnumerable> teams = FFAManager.PlayerTeams.GroupBy(x => x.Value, x => x.Key).Select(x => x.Where(p => - { - PlayerControl pc = GetPlayerById(p); - return pc != null && !pc.Data.Disconnected; - }).ToHashSet()).Where(x => x.Count > 0); - - foreach (HashSet team in teams) - { - if (Main.AllAlivePlayerControls.All(x => team.Contains(x.PlayerId))) - { - WinnerIds = team; - - Main.DoBlockNameChange = true; - return true; - } - } - } - - switch (Main.AllAlivePlayerControls.Length) - { - case 1: - { - PlayerControl winner = Main.AllAlivePlayerControls[0]; - - Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "FFA"); - - WinnerIds = - [ - winner.PlayerId - ]; - - Main.DoBlockNameChange = true; - - return true; - } - case 0: - FFAManager.RoundTime = 0; - Logger.Warn("No players alive. Force ending the game", "FFA"); - return false; - default: - return false; - } - } - } - - private class MoveAndStopGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - if (MoveAndStop.RoundTime <= 0) - { - PlayerControl[] apc = Main.AllPlayerControls; - SetWinner(Main.GM.Value && apc.Length == 1 ? PlayerControl.LocalPlayer : apc.Where(x => !x.Is(CustomRoles.GM) && x != null).OrderBy(x => MoveAndStop.GetRankFromScore(x.PlayerId)).ThenByDescending(x => x.IsAlive()).First()); - return true; - } - - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - if (aapc.Any(x => x.GetTaskState().IsTaskFinished)) - { - SetWinner(aapc.First(x => x.GetTaskState().IsTaskFinished)); - return true; - } - - switch (aapc.Length) - { - case 1 when !GameStates.IsLocalGame: - SetWinner(aapc[0]); - return true; - case 0: - MoveAndStop.RoundTime = 0; - Logger.Warn("No players alive. Force ending the game", "MoveAndStop"); - return false; - } - - return false; - - void SetWinner(PlayerControl winner) - { - Logger.Warn($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "MoveAndStop"); - WinnerIds = [winner.PlayerId]; - Main.DoBlockNameChange = true; - } - } - } - - private class HotPotatoGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - switch (Main.AllAlivePlayerControls.Length) - { - case 1: - PlayerControl winner = Main.AllAlivePlayerControls[0]; - Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "HotPotato"); - WinnerIds = [winner.PlayerId]; - Main.DoBlockNameChange = true; - return true; - case 0: - ResetAndSetWinner(CustomWinner.Error); - Logger.Warn("No players alive. Force ending the game", "HotPotato"); - return true; - default: - return false; - } - } - } - - private class HideAndSeekGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - return HnSManager.CheckForGameEnd(out reason); - } - } - - private class SpeedrunGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - return SpeedrunManager.CheckForGameEnd(out reason); - } - } - - private class CaptureTheFlagGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - return CTFManager.CheckForGameEnd(out reason); - } - } - - private class NaturalDisastersGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - switch (Main.AllAlivePlayerControls.Length) - { - case 1: - PlayerControl winner = Main.AllAlivePlayerControls[0]; - Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "NaturalDisasters"); - WinnerIds = [winner.PlayerId]; - Main.DoBlockNameChange = true; - return true; - case 0: - ResetAndSetWinner(CustomWinner.Error); - Logger.Warn("No players alive. Force ending the game", "NaturalDisasters"); - return true; - default: - return false; - } - } - } - - private class RoomRushGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - PlayerControl[] appc = Main.AllAlivePlayerControls; - - switch (appc.Length) - { - case 1: - PlayerControl winner = appc[0]; - Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "RoomRush"); - WinnerIds = [winner.PlayerId]; - Main.DoBlockNameChange = true; - return true; - case 0: - ResetAndSetWinner(CustomWinner.None); - Logger.Warn("No players alive. Force ending the game", "RoomRush"); - return true; - default: - return false; - } - } - } - - private class AllInOneGameEndPredicate : GameEndPredicate - { - public override bool CheckForGameEnd(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); - } - - private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - - PlayerControl[] appc = Main.AllAlivePlayerControls; - - switch (appc.Length) - { - case 1: - PlayerControl winner = appc[0]; - Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "AllInOne"); - WinnerIds = [winner.PlayerId]; - Main.DoBlockNameChange = true; - return true; - case 0: - ResetAndSetWinner(CustomWinner.None); - Logger.Warn("No players alive. Force ending the game", "AllInOne"); - return true; - default: - return false; - } - } - } - - public abstract class GameEndPredicate - { - /// Checks the game ending condition and stores the value in CustomWinnerHolder. - /// GameOverReason used for vanilla game end processing - /// Whether the conditions for ending the game are met - public abstract bool CheckForGameEnd(out GameOverReason reason); - - /// Determine whether the task can be won based on GameData.TotalTasks and CompletedTasks. - public virtual bool CheckGameEndByTask(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - if (Options.DisableTaskWin.GetBool() || TaskState.InitialTotalTasks == 0) return false; - - if (Options.DisableTaskWinIfAllCrewsAreDead.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoleTypes.Crewmate))) return false; - - if (Options.DisableTaskWinIfAllCrewsAreConverted.GetBool() && Main.AllAlivePlayerControls.Where(x => x.Is(Team.Crewmate) && x.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel).All(x => x.IsConverted())) return false; - - if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) - { - reason = GameOverReason.HumansByTask; - ResetAndSetWinner(CustomWinner.Crewmate); - return true; - } - - return false; - } - - /// Determines whether sabotage victory is possible based on the elements in ShipStatus.Systems. - public virtual bool CheckGameEndBySabotage(out GameOverReason reason) - { - reason = GameOverReason.ImpostorByKill; - if (ShipStatus.Instance.Systems == null) return false; - - // TryGetValue is not available - Il2CppSystem.Collections.Generic.Dictionary systems = ShipStatus.Instance.Systems; - LifeSuppSystemType LifeSupp; - - if (systems.ContainsKey(SystemTypes.LifeSupp) && // Confirmation of sabotage existence - (LifeSupp = systems[SystemTypes.LifeSupp].TryCast()) != null && // Confirmation that cast is possible - LifeSupp.Countdown <= 0f) // Time up confirmation - { - // oxygen sabotage - ResetAndSetWinner(CustomWinner.Impostor); - reason = GameOverReason.ImpostorBySabotage; - LifeSupp.Countdown = 10000f; - return true; - } - - ISystemType sys = null; - - if (systems.ContainsKey(SystemTypes.Reactor)) - sys = systems[SystemTypes.Reactor]; - else if (systems.ContainsKey(SystemTypes.Laboratory)) - sys = systems[SystemTypes.Laboratory]; - else if (systems.ContainsKey(SystemTypes.HeliSabotage)) sys = systems[SystemTypes.HeliSabotage]; - - ICriticalSabotage critical; - - if (sys != null && // Confirmation of sabotage existence - (critical = sys.TryCast()) != null && // Confirmation that cast is possible - critical.Countdown <= 0f) // Time up confirmation - { - // reactor sabotage - ResetAndSetWinner(CustomWinner.Impostor); - reason = GameOverReason.ImpostorBySabotage; - critical.ClearSabotage(); - return true; - } - - return false; + case 0: + FFAManager.RoundTime = 0; + Logger.Warn("No players alive. Force ending the game", "FFA"); + return false; + default: + return false; } } } - [HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckEndGameViaTasks))] - internal static class CheckGameEndPatch + private class MoveAndStopGameEndPredicate : GameEndPredicate { - public static bool Prefix(ref bool __result) + public override bool CheckForGameEnd(out GameOverReason reason) { - if (GameEndChecker.ShouldNotCheck) + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + if (MoveAndStop.RoundTime <= 0) { - __result = false; - return false; + PlayerControl[] apc = Main.AllPlayerControls; + SetWinner(Main.GM.Value && apc.Length == 1 ? PlayerControl.LocalPlayer : apc.Where(x => !x.Is(CustomRoles.GM) && x != null).OrderBy(x => MoveAndStop.GetRankFromScore(x.PlayerId)).ThenByDescending(x => x.IsAlive()).First()); + return true; + } + + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + if (aapc.Any(x => x.GetTaskState().IsTaskFinished)) + { + SetWinner(aapc.First(x => x.GetTaskState().IsTaskFinished)); + return true; + } + + switch (aapc.Length) + { + case 1 when !GameStates.IsLocalGame: + SetWinner(aapc[0]); + return true; + case 0: + MoveAndStop.RoundTime = 0; + Logger.Warn("No players alive. Force ending the game", "MoveAndStop"); + return false; + } + + return false; + + void SetWinner(PlayerControl winner) + { + Logger.Warn($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "MoveAndStop"); + WinnerIds = [winner.PlayerId]; + Main.DoBlockNameChange = true; + } + } + } + + private class HotPotatoGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + switch (Main.AllAlivePlayerControls.Length) + { + case 1: + PlayerControl winner = Main.AllAlivePlayerControls[0]; + Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "HotPotato"); + WinnerIds = [winner.PlayerId]; + Main.DoBlockNameChange = true; + return true; + case 0: + ResetAndSetWinner(CustomWinner.Error); + Logger.Warn("No players alive. Force ending the game", "HotPotato"); + return true; + default: + return false; + } + } + } + + private class HideAndSeekGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + return HnSManager.CheckForGameEnd(out reason); + } + } + + private class SpeedrunGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + return SpeedrunManager.CheckForGameEnd(out reason); + } + } + + private class CaptureTheFlagGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + return CTFManager.CheckForGameEnd(out reason); + } + } + + private class NaturalDisastersGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + switch (Main.AllAlivePlayerControls.Length) + { + case 1: + PlayerControl winner = Main.AllAlivePlayerControls[0]; + Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "NaturalDisasters"); + WinnerIds = [winner.PlayerId]; + Main.DoBlockNameChange = true; + return true; + case 0: + ResetAndSetWinner(CustomWinner.Error); + Logger.Warn("No players alive. Force ending the game", "NaturalDisasters"); + return true; + default: + return false; + } + } + } + + private class RoomRushGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + PlayerControl[] appc = Main.AllAlivePlayerControls; + + switch (appc.Length) + { + case 1: + PlayerControl winner = appc[0]; + Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "RoomRush"); + WinnerIds = [winner.PlayerId]; + Main.DoBlockNameChange = true; + return true; + case 0: + ResetAndSetWinner(CustomWinner.None); + Logger.Warn("No players alive. Force ending the game", "RoomRush"); + return true; + default: + return false; + } + } + } + + private class AllInOneGameEndPredicate : GameEndPredicate + { + public override bool CheckForGameEnd(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + return WinnerIds.Count <= 0 && CheckGameEndByLivingPlayers(out reason); + } + + private static bool CheckGameEndByLivingPlayers(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + + PlayerControl[] appc = Main.AllAlivePlayerControls; + + switch (appc.Length) + { + case 1: + PlayerControl winner = appc[0]; + Logger.Info($"Winner: {winner.GetRealName().RemoveHtmlTags()}", "AllInOne"); + WinnerIds = [winner.PlayerId]; + Main.DoBlockNameChange = true; + return true; + case 0: + ResetAndSetWinner(CustomWinner.None); + Logger.Warn("No players alive. Force ending the game", "AllInOne"); + return true; + default: + return false; + } + } + } + + public abstract class GameEndPredicate + { + /// Checks the game ending condition and stores the value in CustomWinnerHolder. + /// GameOverReason used for vanilla game end processing + /// Whether the conditions for ending the game are met + public abstract bool CheckForGameEnd(out GameOverReason reason); + + /// Determine whether the task can be won based on GameData.TotalTasks and CompletedTasks. + public virtual bool CheckGameEndByTask(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (Options.DisableTaskWin.GetBool() || TaskState.InitialTotalTasks == 0) return false; + + if (Options.DisableTaskWinIfAllCrewsAreDead.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoleTypes.Crewmate))) return false; + + if (Options.DisableTaskWinIfAllCrewsAreConverted.GetBool() && Main.AllAlivePlayerControls.Where(x => x.Is(Team.Crewmate) && x.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist or RoleTypes.Noisemaker or RoleTypes.Tracker or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel).All(x => x.IsConverted())) return false; + + if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) + { + reason = GameOverReason.HumansByTask; + ResetAndSetWinner(CustomWinner.Crewmate); + return true; + } + + return false; + } + + /// Determines whether sabotage victory is possible based on the elements in ShipStatus.Systems. + public virtual bool CheckGameEndBySabotage(out GameOverReason reason) + { + reason = GameOverReason.ImpostorByKill; + if (ShipStatus.Instance.Systems == null) return false; + + // TryGetValue is not available + Il2CppSystem.Collections.Generic.Dictionary systems = ShipStatus.Instance.Systems; + LifeSuppSystemType LifeSupp; + + if (systems.ContainsKey(SystemTypes.LifeSupp) && // Confirmation of sabotage existence + (LifeSupp = systems[SystemTypes.LifeSupp].TryCast()) != null && // Confirmation that cast is possible + LifeSupp.Countdown <= 0f) // Time up confirmation + { + // oxygen sabotage + ResetAndSetWinner(CustomWinner.Impostor); + reason = GameOverReason.ImpostorBySabotage; + LifeSupp.Countdown = 10000f; + return true; + } + + ISystemType sys = null; + + if (systems.ContainsKey(SystemTypes.Reactor)) + sys = systems[SystemTypes.Reactor]; + else if (systems.ContainsKey(SystemTypes.Laboratory)) + sys = systems[SystemTypes.Laboratory]; + else if (systems.ContainsKey(SystemTypes.HeliSabotage)) sys = systems[SystemTypes.HeliSabotage]; + + ICriticalSabotage critical; + + if (sys != null && // Confirmation of sabotage existence + (critical = sys.TryCast()) != null && // Confirmation that cast is possible + critical.Countdown <= 0f) // Time up confirmation + { + // reactor sabotage + ResetAndSetWinner(CustomWinner.Impostor); + reason = GameOverReason.ImpostorBySabotage; + critical.ClearSabotage(); + return true; } - __result = GameEndChecker.Predicate?.CheckGameEndByTask(out _) ?? false; return false; } } +} + +[HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckEndGameViaTasks))] +internal static class CheckGameEndPatch +{ + public static bool Prefix(ref bool __result) + { + if (GameEndChecker.ShouldNotCheck) + { + __result = false; + return false; + } + + __result = GameEndChecker.Predicate?.CheckGameEndByTask(out _) ?? false; + return false; + } } \ No newline at end of file diff --git a/Patches/ClientOptionsPatch.cs b/Patches/ClientOptionsPatch.cs index 438c3a595..68e9fe9af 100644 --- a/Patches/ClientOptionsPatch.cs +++ b/Patches/ClientOptionsPatch.cs @@ -1,190 +1,189 @@ using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +//��Դ��https://github.com/tukasa0001/TownOfHost/pull/1265 +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))] +public static class OptionsMenuBehaviourStartPatch { - //��Դ��https://github.com/tukasa0001/TownOfHost/pull/1265 - [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))] - public static class OptionsMenuBehaviourStartPatch + private static ClientOptionItem GM; + private static ClientOptionItem UnlockFPS; + private static ClientOptionItem AutoStart; + private static ClientOptionItem ForceOwnLanguage; + private static ClientOptionItem ForceOwnLanguageRoleName; + private static ClientOptionItem EnableCustomButton; + private static ClientOptionItem EnableCustomSoundEffect; + private static ClientOptionItem SwitchVanilla; + private static ClientOptionItem DarkTheme; + private static ClientOptionItem HorseMode; + private static ClientOptionItem LongMode; + private static ClientOptionItem ShowPlayerInfoInLobby; + private static ClientOptionItem LobbyMusic; + private static ClientOptionItem EnableCommandHelper; +#if DEBUG + private static ClientOptionItem GodMode; +#endif + + public static void Postfix(OptionsMenuBehaviour __instance) { - private static ClientOptionItem GM; - private static ClientOptionItem UnlockFPS; - private static ClientOptionItem AutoStart; - private static ClientOptionItem ForceOwnLanguage; - private static ClientOptionItem ForceOwnLanguageRoleName; - private static ClientOptionItem EnableCustomButton; - private static ClientOptionItem EnableCustomSoundEffect; - private static ClientOptionItem SwitchVanilla; - private static ClientOptionItem DarkTheme; - private static ClientOptionItem HorseMode; - private static ClientOptionItem LongMode; - private static ClientOptionItem ShowPlayerInfoInLobby; - private static ClientOptionItem LobbyMusic; - private static ClientOptionItem EnableCommandHelper; -#if DEBUG - private static ClientOptionItem GodMode; -#endif + if (__instance.DisableMouseMovement == null) return; - public static void Postfix(OptionsMenuBehaviour __instance) + Main.SwitchVanilla.Value = false; + + if (Main.ResetOptions || !DebugModeManager.AmDebugger) { - if (__instance.DisableMouseMovement == null) return; + Main.ResetOptions = false; + Main.GodMode.Value = false; + } - Main.SwitchVanilla.Value = false; + if (GM == null || GM.ToggleButton == null) + { + GM = ClientOptionItem.Create("GM", Main.GM, __instance, GMButtonToggle); - if (Main.ResetOptions || !DebugModeManager.AmDebugger) + static void GMButtonToggle() { - Main.ResetOptions = false; - Main.GodMode.Value = false; + if (Main.GM.Value) HudManager.Instance.ShowPopUp(Translator.GetString("EnabledGMWarning")); } + } - if (GM == null || GM.ToggleButton == null) + if (UnlockFPS == null || UnlockFPS.ToggleButton == null) + { + UnlockFPS = ClientOptionItem.Create("UnlockFPS", Main.UnlockFps, __instance, UnlockFPSButtonToggle); + + static void UnlockFPSButtonToggle() { - GM = ClientOptionItem.Create("GM", Main.GM, __instance, GMButtonToggle); + Application.targetFrameRate = Main.UnlockFps.Value ? 120 : 60; + Logger.SendInGame(string.Format(Translator.GetString("FPSSetTo"), Application.targetFrameRate)); + } + } - static void GMButtonToggle() + if (AutoStart == null || AutoStart.ToggleButton == null) + { + AutoStart = ClientOptionItem.Create("AutoStart", Main.AutoStart, __instance, AutoStartButtonToggle); + + static void AutoStartButtonToggle() + { + if (Main.AutoStart.Value == false && GameStates.IsCountDown) { - if (Main.GM.Value) HudManager.Instance.ShowPopUp(Translator.GetString("EnabledGMWarning")); + GameStartManager.Instance.ResetStartState(); + Logger.SendInGame(Translator.GetString("CancelStartCountDown")); } } + } - if (UnlockFPS == null || UnlockFPS.ToggleButton == null) + if (ForceOwnLanguage == null || ForceOwnLanguage.ToggleButton == null) ForceOwnLanguage = ClientOptionItem.Create("ForceOwnLanguage", Main.ForceOwnLanguage, __instance); + + if (ForceOwnLanguageRoleName == null || ForceOwnLanguageRoleName.ToggleButton == null) ForceOwnLanguageRoleName = ClientOptionItem.Create("ForceOwnLanguageRoleName", Main.ForceOwnLanguageRoleName, __instance); + + if (EnableCustomButton == null || EnableCustomButton.ToggleButton == null) EnableCustomButton = ClientOptionItem.Create("EnableCustomButton", Main.EnableCustomButton, __instance); + + if (EnableCustomSoundEffect == null || EnableCustomSoundEffect.ToggleButton == null) EnableCustomSoundEffect = ClientOptionItem.Create("EnableCustomSoundEffect", Main.EnableCustomSoundEffect, __instance); + + if (SwitchVanilla == null || SwitchVanilla.ToggleButton == null) + { + SwitchVanilla = ClientOptionItem.Create("SwitchVanilla", Main.SwitchVanilla, __instance, SwitchVanillaButtonToggle); + + static void SwitchVanillaButtonToggle() { - UnlockFPS = ClientOptionItem.Create("UnlockFPS", Main.UnlockFps, __instance, UnlockFPSButtonToggle); - - static void UnlockFPSButtonToggle() + if (PlayerControl.LocalPlayer != null) { - Application.targetFrameRate = Main.UnlockFps.Value ? 120 : 60; - Logger.SendInGame(string.Format(Translator.GetString("FPSSetTo"), Application.targetFrameRate)); + Zoom.SetZoomSize(reset: true); + AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); + SceneChanger.ChangeScene("MainMenu"); + LateTask.New(() => HudManager.Instance.ShowPopUp(Translator.GetString("RejoinRequiredDueToVanillaSwitch")), 1.9f, log: false); + LateTask.New(Unload, 2f, log: false); + } + else + Unload(); + + return; + + static void Unload() + { + MainMenuManagerPatch.ShowRightPanelImmediately(); + + Harmony.UnpatchAll(); + Main.Instance.Unload(); } } + } - if (AutoStart == null || AutoStart.ToggleButton == null) + if (DarkTheme == null || DarkTheme.ToggleButton == null) DarkTheme = ClientOptionItem.Create("EnableDarkTheme", Main.DarkTheme, __instance); + + if (HorseMode == null || HorseMode.ToggleButton == null) + { + HorseMode = ClientOptionItem.Create("HorseMode", Main.HorseMode, __instance, SwitchHorseMode); + + static void SwitchHorseMode() { - AutoStart = ClientOptionItem.Create("AutoStart", Main.AutoStart, __instance, AutoStartButtonToggle); + Main.LongMode.Value = false; + HorseMode.UpdateToggle(); + LongMode.UpdateToggle(); - static void AutoStartButtonToggle() + foreach (PlayerControl pc in Main.AllPlayerControls) { - if (Main.AutoStart.Value == false && GameStates.IsCountDown) + pc.MyPhysics.SetBodyType(pc.BodyType); + if (pc.BodyType == PlayerBodyTypes.Normal) pc.cosmetics.currentBodySprite.BodySprite.transform.localScale = new(0.5f, 0.5f, 1f); + } + } + } + + if (LongMode == null || LongMode.ToggleButton == null) + { + LongMode = ClientOptionItem.Create("LongMode", Main.LongMode, __instance, SwitchLongMode); + + static void SwitchLongMode() + { + Main.HorseMode.Value = false; + HorseMode.UpdateToggle(); + LongMode.UpdateToggle(); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + pc.MyPhysics.SetBodyType(pc.BodyType); + if (pc.BodyType == PlayerBodyTypes.Normal) pc.cosmetics.currentBodySprite.BodySprite.transform.localScale = new(0.5f, 0.5f, 1f); + } + } + } + + if (ShowPlayerInfoInLobby == null || ShowPlayerInfoInLobby.ToggleButton == null) ShowPlayerInfoInLobby = ClientOptionItem.Create("ShowPlayerInfoInLobby", Main.ShowPlayerInfoInLobby, __instance); + + if (LobbyMusic == null || LobbyMusic.ToggleButton == null) + { + LobbyMusic = ClientOptionItem.Create("LobbyMusic", Main.LobbyMusic, __instance, LobbyMusicButtonToggle); + + void LobbyMusicButtonToggle() + { + if (!Main.LobbyMusic.Value && GameStates.IsLobby) + { + SoundManager.Instance.StopAllSound(); + + LateTask.New(() => { - GameStartManager.Instance.ResetStartState(); - Logger.SendInGame(Translator.GetString("CancelStartCountDown")); - } + Main.LobbyMusic.Value = true; + LobbyMusic.UpdateToggle(); + }, 5f, log: false); } } + } - if (ForceOwnLanguage == null || ForceOwnLanguage.ToggleButton == null) ForceOwnLanguage = ClientOptionItem.Create("ForceOwnLanguage", Main.ForceOwnLanguage, __instance); - - if (ForceOwnLanguageRoleName == null || ForceOwnLanguageRoleName.ToggleButton == null) ForceOwnLanguageRoleName = ClientOptionItem.Create("ForceOwnLanguageRoleName", Main.ForceOwnLanguageRoleName, __instance); - - if (EnableCustomButton == null || EnableCustomButton.ToggleButton == null) EnableCustomButton = ClientOptionItem.Create("EnableCustomButton", Main.EnableCustomButton, __instance); - - if (EnableCustomSoundEffect == null || EnableCustomSoundEffect.ToggleButton == null) EnableCustomSoundEffect = ClientOptionItem.Create("EnableCustomSoundEffect", Main.EnableCustomSoundEffect, __instance); - - if (SwitchVanilla == null || SwitchVanilla.ToggleButton == null) - { - SwitchVanilla = ClientOptionItem.Create("SwitchVanilla", Main.SwitchVanilla, __instance, SwitchVanillaButtonToggle); - - static void SwitchVanillaButtonToggle() - { - if (PlayerControl.LocalPlayer != null) - { - Zoom.SetZoomSize(reset: true); - AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); - SceneChanger.ChangeScene("MainMenu"); - LateTask.New(() => HudManager.Instance.ShowPopUp(Translator.GetString("RejoinRequiredDueToVanillaSwitch")), 1.9f, log: false); - LateTask.New(Unload, 2f, log: false); - } - else - Unload(); - - return; - - static void Unload() - { - MainMenuManagerPatch.ShowRightPanelImmediately(); - - Harmony.UnpatchAll(); - Main.Instance.Unload(); - } - } - } - - if (DarkTheme == null || DarkTheme.ToggleButton == null) DarkTheme = ClientOptionItem.Create("EnableDarkTheme", Main.DarkTheme, __instance); - - if (HorseMode == null || HorseMode.ToggleButton == null) - { - HorseMode = ClientOptionItem.Create("HorseMode", Main.HorseMode, __instance, SwitchHorseMode); - - static void SwitchHorseMode() - { - Main.LongMode.Value = false; - HorseMode.UpdateToggle(); - LongMode.UpdateToggle(); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - pc.MyPhysics.SetBodyType(pc.BodyType); - if (pc.BodyType == PlayerBodyTypes.Normal) pc.cosmetics.currentBodySprite.BodySprite.transform.localScale = new(0.5f, 0.5f, 1f); - } - } - } - - if (LongMode == null || LongMode.ToggleButton == null) - { - LongMode = ClientOptionItem.Create("LongMode", Main.LongMode, __instance, SwitchLongMode); - - static void SwitchLongMode() - { - Main.HorseMode.Value = false; - HorseMode.UpdateToggle(); - LongMode.UpdateToggle(); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - pc.MyPhysics.SetBodyType(pc.BodyType); - if (pc.BodyType == PlayerBodyTypes.Normal) pc.cosmetics.currentBodySprite.BodySprite.transform.localScale = new(0.5f, 0.5f, 1f); - } - } - } - - if (ShowPlayerInfoInLobby == null || ShowPlayerInfoInLobby.ToggleButton == null) ShowPlayerInfoInLobby = ClientOptionItem.Create("ShowPlayerInfoInLobby", Main.ShowPlayerInfoInLobby, __instance); - - if (LobbyMusic == null || LobbyMusic.ToggleButton == null) - { - LobbyMusic = ClientOptionItem.Create("LobbyMusic", Main.LobbyMusic, __instance, LobbyMusicButtonToggle); - - void LobbyMusicButtonToggle() - { - if (!Main.LobbyMusic.Value && GameStates.IsLobby) - { - SoundManager.Instance.StopAllSound(); - - LateTask.New(() => - { - Main.LobbyMusic.Value = true; - LobbyMusic.UpdateToggle(); - }, 5f, log: false); - } - } - } - - if (EnableCommandHelper == null || EnableCommandHelper.ToggleButton == null) EnableCommandHelper = ClientOptionItem.Create("EnableCommandHelper", Main.EnableCommandHelper, __instance); + if (EnableCommandHelper == null || EnableCommandHelper.ToggleButton == null) EnableCommandHelper = ClientOptionItem.Create("EnableCommandHelper", Main.EnableCommandHelper, __instance); #if DEBUG - if ((GodMode == null || GodMode.ToggleButton == null) && DebugModeManager.AmDebugger) - { - GodMode = ClientOptionItem.Create("GodMode", Main.GodMode, __instance); - } -#endif + if ((GodMode == null || GodMode.ToggleButton == null) && DebugModeManager.AmDebugger) + { + GodMode = ClientOptionItem.Create("GodMode", Main.GodMode, __instance); } +#endif } +} - [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] - public static class OptionsMenuBehaviourClosePatch +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Close))] +public static class OptionsMenuBehaviourClosePatch +{ + public static void Postfix() { - public static void Postfix() - { - ClientOptionItem.CustomBackground?.gameObject.SetActive(false); - } + ClientOptionItem.CustomBackground?.gameObject.SetActive(false); } } \ No newline at end of file diff --git a/Patches/ControlPatch.cs b/Patches/ControlPatch.cs index 7508bd731..7a8fc8f26 100644 --- a/Patches/ControlPatch.cs +++ b/Patches/ControlPatch.cs @@ -8,406 +8,405 @@ using TMPro; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(ControllerManager), nameof(ControllerManager.Update))] +internal class ControllerManagerUpdatePatch { - [HarmonyPatch(typeof(ControllerManager), nameof(ControllerManager.Update))] - internal class ControllerManagerUpdatePatch + private static readonly (int, int)[] Resolutions = [(480, 270), (640, 360), (800, 450), (1280, 720), (1600, 900), (1920, 1080)]; + private static int ResolutionIndex; + + private static bool IsResetting; + + public static void Postfix( /*ControllerManager __instance*/) { - private static readonly (int, int)[] Resolutions = [(480, 270), (640, 360), (800, 450), (1280, 720), (1600, 900), (1920, 1080)]; - private static int ResolutionIndex; - - private static bool IsResetting; - - public static void Postfix( /*ControllerManager __instance*/) + if (GameStates.IsLobby) { - if (GameStates.IsLobby) + if (Input.GetKeyDown(KeyCode.Tab)) OptionShower.Next(); + + for (var i = 0; i < 9; i++) + if (OrGetKeysDown(KeyCode.Alpha1 + i, KeyCode.Keypad1 + i) && OptionShower.Pages.Count >= i + 1) + OptionShower.CurrentPage = i; + + if (KeysDown(KeyCode.Return) && GameSettingMenu.Instance != null && GameSettingMenu.Instance.isActiveAndEnabled) GameSettingMenuPatch.SearchForOptionsAction?.Invoke(); + } + + if (KeysDown(KeyCode.LeftShift, KeyCode.LeftControl, KeyCode.X)) ExileController.Instance?.ReEnableGameplay(); + + if (KeysDown(KeyCode.LeftAlt, KeyCode.Return)) LateTask.New(SetResolutionManager.Postfix, 0.01f, "Fix Button Position"); + + if (GameStates.IsInGame && (GameStates.IsCanMove || GameStates.IsMeeting) && CustomGameMode.Standard.IsActiveOrIntegrated()) + { + if (Input.GetKey(KeyCode.F1)) { - if (Input.GetKeyDown(KeyCode.Tab)) OptionShower.Next(); + if (!InGameRoleInfoMenu.Showing) InGameRoleInfoMenu.SetRoleInfoRef(PlayerControl.LocalPlayer); - for (var i = 0; i < 9; i++) - if (OrGetKeysDown(KeyCode.Alpha1 + i, KeyCode.Keypad1 + i) && OptionShower.Pages.Count >= i + 1) - OptionShower.CurrentPage = i; - - if (KeysDown(KeyCode.Return) && GameSettingMenu.Instance != null && GameSettingMenu.Instance.isActiveAndEnabled) GameSettingMenuPatch.SearchForOptionsAction?.Invoke(); - } - - if (KeysDown(KeyCode.LeftShift, KeyCode.LeftControl, KeyCode.X)) ExileController.Instance?.ReEnableGameplay(); - - if (KeysDown(KeyCode.LeftAlt, KeyCode.Return)) LateTask.New(SetResolutionManager.Postfix, 0.01f, "Fix Button Position"); - - if (GameStates.IsInGame && (GameStates.IsCanMove || GameStates.IsMeeting) && CustomGameMode.Standard.IsActiveOrIntegrated()) - { - if (Input.GetKey(KeyCode.F1)) - { - if (!InGameRoleInfoMenu.Showing) InGameRoleInfoMenu.SetRoleInfoRef(PlayerControl.LocalPlayer); - - InGameRoleInfoMenu.Show(); - } - else - InGameRoleInfoMenu.Hide(); + InGameRoleInfoMenu.Show(); } else InGameRoleInfoMenu.Hide(); + } + else + InGameRoleInfoMenu.Hide(); - if (Input.GetKeyDown(KeyCode.F11)) + if (Input.GetKeyDown(KeyCode.F11)) + { + ResolutionIndex++; + if (ResolutionIndex >= Resolutions.Length) ResolutionIndex = 0; + + ResolutionManager.SetResolution(Resolutions[ResolutionIndex].Item1, Resolutions[ResolutionIndex].Item2, false); + SetResolutionManager.Postfix(); + } + + if (KeysDown(KeyCode.F5, KeyCode.T)) + { + Logger.Info("Reloading Custom Translation File", "KeyCommand"); + LoadLangs(); + Logger.SendInGame("Reloaded Custom Translation File"); + } + + if (KeysDown(KeyCode.F5, KeyCode.X)) + { + Logger.Info("Exported Custom Translation File", "KeyCommand"); + ExportCustomTranslation(); + Logger.SendInGame("Exported Custom Translation File"); + } + + if (KeysDown(KeyCode.F1, KeyCode.LeftControl)) + { + Logger.Info("Log dumped", "KeyCommand"); + Utils.DumpLog(); + } + + if (KeysDown(KeyCode.LeftAlt, KeyCode.C) && !Input.GetKey(KeyCode.LeftShift) && !GameStates.IsNotJoined) Utils.CopyCurrentSettings(); + + if (!AmongUsClient.Instance.AmHost) return; + + if (KeysDown(KeyCode.Return, KeyCode.C, KeyCode.LeftShift)) HudManager.Instance.Chat.SetVisible(true); + + if (KeysDown(KeyCode.Return, KeyCode.L, KeyCode.LeftShift) && GameStates.IsInGame) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); + GameManager.Instance.LogicFlow.CheckEndCriteria(); + } + + if (KeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) + { + if (GameStates.IsMeeting) + MeetingHud.Instance.RpcClose(); + else + PlayerControl.LocalPlayer.NoCheckStartMeeting(null, true); + } + + if (Input.GetKeyDown(KeyCode.LeftShift) && GameStates.IsCountDown && !HudManager.Instance.Chat.IsOpenOrOpening) + { + Logger.Info("Countdown timer set to 0", "KeyCommand"); + GameStartManager.Instance.countDownTimer = 0; + } + + if (Input.GetKeyDown(KeyCode.C) && GameStates.IsCountDown && GameStates.IsLobby) + { + GameStartManager.Instance.ResetStartState(); + Logger.SendInGame(GetString("CancelStartCountDown")); + } + + if (KeysDown(KeyCode.N, KeyCode.LeftShift, KeyCode.LeftControl)) + { + Main.IsChatCommand = true; + Utils.ShowActiveSettingsHelp(); + } + + if (KeysDown(KeyCode.N, KeyCode.LeftControl) && !Input.GetKey(KeyCode.LeftShift)) + { + Main.IsChatCommand = true; + Utils.ShowActiveSettings(); + } + + if (KeysDown(KeyCode.Delete, KeyCode.LeftControl, KeyCode.LeftShift) && !IsResetting) + { + IsResetting = true; + Main.Instance.StartCoroutine(Reset()); + + IEnumerator Reset() { - ResolutionIndex++; - if (ResolutionIndex >= Resolutions.Length) ResolutionIndex = 0; - - ResolutionManager.SetResolution(Resolutions[ResolutionIndex].Item1, Resolutions[ResolutionIndex].Item2, false); - SetResolutionManager.Postfix(); - } - - if (KeysDown(KeyCode.F5, KeyCode.T)) - { - Logger.Info("Reloading Custom Translation File", "KeyCommand"); - LoadLangs(); - Logger.SendInGame("Reloaded Custom Translation File"); - } - - if (KeysDown(KeyCode.F5, KeyCode.X)) - { - Logger.Info("Exported Custom Translation File", "KeyCommand"); - ExportCustomTranslation(); - Logger.SendInGame("Exported Custom Translation File"); - } - - if (KeysDown(KeyCode.F1, KeyCode.LeftControl)) - { - Logger.Info("Log dumped", "KeyCommand"); - Utils.DumpLog(); - } - - if (KeysDown(KeyCode.LeftAlt, KeyCode.C) && !Input.GetKey(KeyCode.LeftShift) && !GameStates.IsNotJoined) Utils.CopyCurrentSettings(); - - if (!AmongUsClient.Instance.AmHost) return; - - if (KeysDown(KeyCode.Return, KeyCode.C, KeyCode.LeftShift)) HudManager.Instance.Chat.SetVisible(true); - - if (KeysDown(KeyCode.Return, KeyCode.L, KeyCode.LeftShift) && GameStates.IsInGame) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Draw); - GameManager.Instance.LogicFlow.CheckEndCriteria(); - } - - if (KeysDown(KeyCode.Return, KeyCode.M, KeyCode.LeftShift) && GameStates.IsInGame) - { - if (GameStates.IsMeeting) - MeetingHud.Instance.RpcClose(); - else - PlayerControl.LocalPlayer.NoCheckStartMeeting(null, true); - } - - if (Input.GetKeyDown(KeyCode.LeftShift) && GameStates.IsCountDown && !HudManager.Instance.Chat.IsOpenOrOpening) - { - Logger.Info("Countdown timer set to 0", "KeyCommand"); - GameStartManager.Instance.countDownTimer = 0; - } - - if (Input.GetKeyDown(KeyCode.C) && GameStates.IsCountDown && GameStates.IsLobby) - { - GameStartManager.Instance.ResetStartState(); - Logger.SendInGame(GetString("CancelStartCountDown")); - } - - if (KeysDown(KeyCode.N, KeyCode.LeftShift, KeyCode.LeftControl)) - { - Main.IsChatCommand = true; - Utils.ShowActiveSettingsHelp(); - } - - if (KeysDown(KeyCode.N, KeyCode.LeftControl) && !Input.GetKey(KeyCode.LeftShift)) - { - Main.IsChatCommand = true; - Utils.ShowActiveSettings(); - } - - if (KeysDown(KeyCode.Delete, KeyCode.LeftControl, KeyCode.LeftShift) && !IsResetting) - { - IsResetting = true; - Main.Instance.StartCoroutine(Reset()); - - IEnumerator Reset() + for (var index = 0; index < OptionItem.AllOptions.Count; index++) { - for (var index = 0; index < OptionItem.AllOptions.Count; index++) - { - OptionItem option = OptionItem.AllOptions[index]; - if (option.Id > 0) option.SetValue(option.DefaultValue); + OptionItem option = OptionItem.AllOptions[index]; + if (option.Id > 0) option.SetValue(option.DefaultValue); - if (index % 100 == 0) yield return null; - } - - IsResetting = false; + if (index % 100 == 0) yield return null; } - } - if (KeysDown(KeyCode.Return, KeyCode.E, KeyCode.LeftShift) && GameStates.IsInGame) - { - PlayerControl.LocalPlayer.Data.IsDead = true; - Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason = PlayerState.DeathReason.etc; - PlayerControl.LocalPlayer.RpcExileV2(); - Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); - Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + IsResetting = false; } + } - if (KeysDown(KeyCode.F2, KeyCode.LeftControl)) - { - Logger.IsAlsoInGame = !Logger.IsAlsoInGame; - Logger.SendInGame($"In-game output log:{Logger.IsAlsoInGame}"); - } + if (KeysDown(KeyCode.Return, KeyCode.E, KeyCode.LeftShift) && GameStates.IsInGame) + { + PlayerControl.LocalPlayer.Data.IsDead = true; + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].deathReason = PlayerState.DeathReason.etc; + PlayerControl.LocalPlayer.RpcExileV2(); + Main.PlayerStates[PlayerControl.LocalPlayer.PlayerId].SetDead(); + Utils.SendMessage(GetString("HostKillSelfByCommand"), title: $"{GetString("DefaultSystemMessageTitle")}"); + } - if (!Options.NoGameEnd.GetBool()) return; + if (KeysDown(KeyCode.F2, KeyCode.LeftControl)) + { + Logger.IsAlsoInGame = !Logger.IsAlsoInGame; + Logger.SendInGame($"In-game output log:{Logger.IsAlsoInGame}"); + } + + if (!Options.NoGameEnd.GetBool()) return; #if DEBUG - if (KeysDown(KeyCode.Return, KeyCode.F, KeyCode.LeftShift)) + if (KeysDown(KeyCode.Return, KeyCode.F, KeyCode.LeftShift)) + { + Utils.FlashColor(new(1f, 0f, 0f, 0.3f)); + if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, Sounds.KillSound); + } + + if (KeysDown(KeyCode.Return, KeyCode.G, KeyCode.LeftShift) && GameStates.IsInGame) + { + HudManager.Instance.StartCoroutine(HudManager.Instance.CoFadeFullScreen(Color.clear, Color.black)); + HudManager.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); + } + + if (KeysDown(KeyCode.Return, KeyCode.V, KeyCode.LeftShift) && GameStates.IsMeeting) MeetingHud.Instance.RpcClearVote(AmongUsClient.Instance.ClientId); + + if (KeysDown(KeyCode.Return, KeyCode.D, KeyCode.LeftShift) && GameStates.IsInGame) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 79); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 80); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 81); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 82); + } + + if (KeysDown(KeyCode.Return, KeyCode.K, KeyCode.LeftShift) && GameStates.IsInGame) + { + PlayerControl.LocalPlayer.SetKillTimer(0f); + PlayerControl.LocalPlayer.SetKillCooldown(0f); + } + + if (KeysDown(KeyCode.Return, KeyCode.T, KeyCode.LeftShift) && GameStates.IsInGame) + foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) + PlayerControl.LocalPlayer.RpcCompleteTask(task.Id); + + if (Input.GetKeyDown(KeyCode.Y) && !GameStates.IsMeeting) + { + RPC.SyncCustomSettingsRPC(); + Logger.SendInGame(GetString("SyncCustomSettingsRPC")); + } + + if (Input.GetKeyDown(KeyCode.Equals) && !GameStates.IsMeeting) + { + Main.VisibleTasksCount = !Main.VisibleTasksCount; + DestroyableSingleton.Instance.Notifier.AddDisconnectMessage($"VisibleTaskCount changed to {Main.VisibleTasksCount}."); + } + + if (Input.GetKeyDown(KeyCode.I) && !GameStates.IsMeeting) + { + Logger.SendInGame(PlayerControl.LocalPlayer.Pos().ToString()); + Logger.SendInGame(PlayerControl.LocalPlayer.GetPlainShipRoom()?.RoomId.ToString() ?? "null"); + } + + if (Input.GetKeyDown(KeyCode.C) && !GameStates.IsMeeting) + { + foreach (PlayerControl pc in PlayerControl.AllPlayerControls) + if (!pc.AmOwner) + pc.MyPhysics.RpcEnterVent(2); + } + + if (Input.GetKeyDown(KeyCode.V) && !GameStates.IsMeeting) + { + Vector2 pos = PlayerControl.LocalPlayer.NetTransform.transform.position; + + foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { - Utils.FlashColor(new(1f, 0f, 0f, 0.3f)); - if (Constants.ShouldPlaySfx()) RPC.PlaySound(PlayerControl.LocalPlayer.PlayerId, Sounds.KillSound); - } - - if (KeysDown(KeyCode.Return, KeyCode.G, KeyCode.LeftShift) && GameStates.IsInGame) - { - HudManager.Instance.StartCoroutine(HudManager.Instance.CoFadeFullScreen(Color.clear, Color.black)); - HudManager.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); - } - - if (KeysDown(KeyCode.Return, KeyCode.V, KeyCode.LeftShift) && GameStates.IsMeeting) MeetingHud.Instance.RpcClearVote(AmongUsClient.Instance.ClientId); - - if (KeysDown(KeyCode.Return, KeyCode.D, KeyCode.LeftShift) && GameStates.IsInGame) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 79); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 80); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 81); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Doors, 82); - } - - if (KeysDown(KeyCode.Return, KeyCode.K, KeyCode.LeftShift) && GameStates.IsInGame) - { - PlayerControl.LocalPlayer.SetKillTimer(0f); - PlayerControl.LocalPlayer.SetKillCooldown(0f); - } - - if (KeysDown(KeyCode.Return, KeyCode.T, KeyCode.LeftShift) && GameStates.IsInGame) - foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) - PlayerControl.LocalPlayer.RpcCompleteTask(task.Id); - - if (Input.GetKeyDown(KeyCode.Y) && !GameStates.IsMeeting) - { - RPC.SyncCustomSettingsRPC(); - Logger.SendInGame(GetString("SyncCustomSettingsRPC")); - } - - if (Input.GetKeyDown(KeyCode.Equals) && !GameStates.IsMeeting) - { - Main.VisibleTasksCount = !Main.VisibleTasksCount; - DestroyableSingleton.Instance.Notifier.AddDisconnectMessage($"VisibleTaskCount changed to {Main.VisibleTasksCount}."); - } - - if (Input.GetKeyDown(KeyCode.I) && !GameStates.IsMeeting) - { - Logger.SendInGame(PlayerControl.LocalPlayer.Pos().ToString()); - Logger.SendInGame(PlayerControl.LocalPlayer.GetPlainShipRoom()?.RoomId.ToString() ?? "null"); - } - - if (Input.GetKeyDown(KeyCode.C) && !GameStates.IsMeeting) - { - foreach (PlayerControl pc in PlayerControl.AllPlayerControls) - if (!pc.AmOwner) - pc.MyPhysics.RpcEnterVent(2); - } - - if (Input.GetKeyDown(KeyCode.V) && !GameStates.IsMeeting) - { - Vector2 pos = PlayerControl.LocalPlayer.NetTransform.transform.position; - - foreach (PlayerControl pc in PlayerControl.AllPlayerControls) + if (!pc.AmOwner) { - if (!pc.AmOwner) - { - pc.TP(pos); - pos.x += 0.5f; - } + pc.TP(pos); + pos.x += 0.5f; } } + } - if (Input.GetKeyDown(KeyCode.B) && !GameStates.IsMeeting) - { - foreach (PlayerControl pc in PlayerControl.AllPlayerControls) - if (!pc.AmOwner) - pc.MyPhysics.RpcExitVent(2); - } + if (Input.GetKeyDown(KeyCode.B) && !GameStates.IsMeeting) + { + foreach (PlayerControl pc in PlayerControl.AllPlayerControls) + if (!pc.AmOwner) + pc.MyPhysics.RpcExitVent(2); + } - if (Input.GetKeyDown(KeyCode.N) && !GameStates.IsMeeting) VentilationSystem.Update(VentilationSystem.Operation.StartCleaning, 0); + if (Input.GetKeyDown(KeyCode.N) && !GameStates.IsMeeting) VentilationSystem.Update(VentilationSystem.Operation.StartCleaning, 0); #endif - } - - private static bool KeysDown(params KeyCode[] keys) - { - if (keys.Any(Input.GetKeyDown) && keys.All(Input.GetKey)) - { - Logger.Info($"Shortcut Key:{keys.Where(Input.GetKeyDown).First()} in [{string.Join(",", keys)}]", "GetKeysDown"); - return true; - } - - return false; - } - - private static bool OrGetKeysDown(params KeyCode[] keys) - { - return keys.Any(Input.GetKeyDown); - } } - [HarmonyPatch(typeof(ConsoleJoystick), nameof(ConsoleJoystick.HandleHUD))] - internal class ConsoleJoystickHandleHUDPatch + private static bool KeysDown(params KeyCode[] keys) { - public static void Postfix() + if (keys.Any(Input.GetKeyDown) && keys.All(Input.GetKey)) { - HandleHUDPatch.Postfix(ConsoleJoystick.player); + Logger.Info($"Shortcut Key:{keys.Where(Input.GetKeyDown).First()} in [{string.Join(",", keys)}]", "GetKeysDown"); + return true; } + + return false; } - [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.HandleHud))] - internal class KeyboardJoystickHandleHUDPatch + private static bool OrGetKeysDown(params KeyCode[] keys) { - public static void Postfix() - { - HandleHUDPatch.Postfix(KeyboardJoystick.player); - } + return keys.Any(Input.GetKeyDown); } +} - internal static class HandleHUDPatch +[HarmonyPatch(typeof(ConsoleJoystick), nameof(ConsoleJoystick.HandleHUD))] +internal class ConsoleJoystickHandleHUDPatch +{ + public static void Postfix() { - public static void Postfix(Player player) - { - if (player.GetButtonDown(8) && // 8: Kill button actionId - PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == false && - PlayerControl.LocalPlayer.CanUseKillButton()) - DestroyableSingleton.Instance.KillButton.DoClick(); - - if (player.GetButtonDown(50) && // 50: Impostor vent button actionId - PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == false && - PlayerControl.LocalPlayer.CanUseImpostorVentButton()) - DestroyableSingleton.Instance.ImpostorVentButton.DoClick(); - } + HandleHUDPatch.Postfix(ConsoleJoystick.player); } +} + +[HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.HandleHud))] +internal class KeyboardJoystickHandleHUDPatch +{ + public static void Postfix() + { + HandleHUDPatch.Postfix(KeyboardJoystick.player); + } +} + +internal static class HandleHUDPatch +{ + public static void Postfix(Player player) + { + if (player.GetButtonDown(8) && // 8: Kill button actionId + PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == false && + PlayerControl.LocalPlayer.CanUseKillButton()) + DestroyableSingleton.Instance.KillButton.DoClick(); + + if (player.GetButtonDown(50) && // 50: Impostor vent button actionId + PlayerControl.LocalPlayer.Data?.Role?.IsImpostor == false && + PlayerControl.LocalPlayer.CanUseImpostorVentButton()) + DestroyableSingleton.Instance.ImpostorVentButton.DoClick(); + } +} // Credit: https://github.com/KARPED1EM/TownOfNext/blob/57c675c0f43cb714801d475b8e1722373f711f10/TONX/Modules/InGameRoleInfoMenu.cs#L8 - public static class InGameRoleInfoMenu +public static class InGameRoleInfoMenu +{ + private static GameObject Fill; + + private static GameObject Menu; + + private static GameObject MainInfo; + private static GameObject AddonsInfo; + public static bool Showing => Fill != null && Fill.active && Menu != null && Menu.active; + private static SpriteRenderer FillSp => Fill.GetComponent(); + private static TextMeshPro MainInfoTMP => MainInfo.GetComponent(); + private static TextMeshPro AddonsInfoTMP => AddonsInfo.GetComponent(); + + private static void Init() { - private static GameObject Fill; + Transform DOBScreen = AccountManager.Instance.transform.FindChild("DOBEnterScreen"); - private static GameObject Menu; + Fill = new("EHR Role Info Menu Fill") { layer = 5 }; + Fill.transform.SetParent(HudManager.Instance.transform.parent, true); + Fill.transform.localPosition = new(0f, 0f, -980f); + Fill.transform.localScale = new(20f, 10f, 1f); + Fill.AddComponent().sprite = DOBScreen.FindChild("Fill").GetComponent().sprite; + FillSp.color = new(0f, 0f, 0f, 0.75f); - private static GameObject MainInfo; - private static GameObject AddonsInfo; - public static bool Showing => Fill != null && Fill.active && Menu != null && Menu.active; - private static SpriteRenderer FillSp => Fill.GetComponent(); - private static TextMeshPro MainInfoTMP => MainInfo.GetComponent(); - private static TextMeshPro AddonsInfoTMP => AddonsInfo.GetComponent(); + Menu = Object.Instantiate(DOBScreen.FindChild("InfoPage").gameObject, HudManager.Instance.transform.parent); + Menu.name = "EHR Role Info Menu Page"; + Menu.transform.SetLocalZ(-990f); - private static void Init() + Object.Destroy(Menu.transform.FindChild("Title Text").gameObject); + Object.Destroy(Menu.transform.FindChild("BackButton").gameObject); + Object.Destroy(Menu.transform.FindChild("EvenMoreInfo").gameObject); + + MainInfo = Menu.transform.FindChild("InfoText_TMP").gameObject; + MainInfo.name = "Main Role Info"; + MainInfo.DestroyTranslator(); + MainInfo.transform.localPosition = new(-2.3f, 0.8f, 4f); + MainInfo.GetComponent().sizeDelta = new(4.5f, 10f); + MainInfoTMP.alignment = TextAlignmentOptions.Left; + MainInfoTMP.fontSize = MainInfoTMP.fontSizeMax = MainInfoTMP.fontSizeMin = 1.75f; + + AddonsInfo = Object.Instantiate(MainInfo, MainInfo.transform.parent); + AddonsInfo.name = "Addons Info"; + AddonsInfo.DestroyTranslator(); + AddonsInfo.transform.SetLocalX(2.3f); + AddonsInfo.transform.localScale = new(0.7f, 0.7f, 0.7f); + } + + public static void SetRoleInfoRef(PlayerControl player) + { + if (player == null) return; + + if (!Fill || !Menu) Init(); + + CustomRoles role = player.GetCustomRole(); + StringBuilder sb = new(); + StringBuilder titleSb = new(); + StringBuilder settings = new(); + StringBuilder addons = new(); + settings.Append(""); + titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); + sb.Append(""); + sb.Append(player.GetRoleInfo(true).TrimStart()); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, ref settings, f1: true, disableColor: false); + + settings.Append(""); + if (settings.Length > 0) addons.Append($"{settings}\n\n"); + + if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); + + string searchStr = GetString(role.ToString()); + sb.Replace(searchStr, role.ToColoredString()); + sb.Replace(searchStr.ToLower(), role.ToColoredString()); + sb.Append(""); + List subRoles = Main.PlayerStates[player.PlayerId].SubRoles; + if (subRoles.Count > 0) addons.Append(GetString("AddonListTitle")); + + addons.Append(""); + + subRoles.ForEach(subRole => { - Transform DOBScreen = AccountManager.Instance.transform.FindChild("DOBEnterScreen"); + addons.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong")}"); + string searchSubStr = GetString(subRole.ToString()); + addons.Replace(searchSubStr, subRole.ToColoredString()); + addons.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); + }); - Fill = new("EHR Role Info Menu Fill") { layer = 5 }; - Fill.transform.SetParent(HudManager.Instance.transform.parent, true); - Fill.transform.localPosition = new(0f, 0f, -980f); - Fill.transform.localScale = new(20f, 10f, 1f); - Fill.AddComponent().sprite = DOBScreen.FindChild("Fill").GetComponent().sprite; - FillSp.color = new(0f, 0f, 0f, 0.75f); + addons.Append(""); + if (role.UsesPetInsteadOfKill()) sb.Append($"\n\n{GetString("UsesPetInsteadOfKillNotice")}"); - Menu = Object.Instantiate(DOBScreen.FindChild("InfoPage").gameObject, HudManager.Instance.transform.parent); - Menu.name = "EHR Role Info Menu Page"; - Menu.transform.SetLocalZ(-990f); + sb.Insert(0, $"{titleSb}\n"); - Object.Destroy(Menu.transform.FindChild("Title Text").gameObject); - Object.Destroy(Menu.transform.FindChild("BackButton").gameObject); - Object.Destroy(Menu.transform.FindChild("EvenMoreInfo").gameObject); + MainInfoTMP.text = sb.ToString(); + AddonsInfoTMP.text = addons.ToString(); + } - MainInfo = Menu.transform.FindChild("InfoText_TMP").gameObject; - MainInfo.name = "Main Role Info"; - MainInfo.DestroyTranslator(); - MainInfo.transform.localPosition = new(-2.3f, 0.8f, 4f); - MainInfo.GetComponent().sizeDelta = new(4.5f, 10f); - MainInfoTMP.alignment = TextAlignmentOptions.Left; - MainInfoTMP.fontSize = MainInfoTMP.fontSizeMax = MainInfoTMP.fontSizeMin = 1.75f; + public static void Show() + { + if (!Fill || !Menu) Init(); - AddonsInfo = Object.Instantiate(MainInfo, MainInfo.transform.parent); - AddonsInfo.name = "Addons Info"; - AddonsInfo.DestroyTranslator(); - AddonsInfo.transform.SetLocalX(2.3f); - AddonsInfo.transform.localScale = new(0.7f, 0.7f, 0.7f); + if (!Showing) + { + Fill?.SetActive(true); + Menu?.SetActive(true); } + } - public static void SetRoleInfoRef(PlayerControl player) + public static void Hide() + { + if (Showing) { - if (player == null) return; - - if (!Fill || !Menu) Init(); - - CustomRoles role = player.GetCustomRole(); - StringBuilder sb = new(); - StringBuilder titleSb = new(); - StringBuilder settings = new(); - StringBuilder addons = new(); - settings.Append(""); - titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); - sb.Append(""); - sb.Append(player.GetRoleInfo(true).TrimStart()); - if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, ref settings, f1: true, disableColor: false); - - settings.Append(""); - if (settings.Length > 0) addons.Append($"{settings}\n\n"); - - if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); - - string searchStr = GetString(role.ToString()); - sb.Replace(searchStr, role.ToColoredString()); - sb.Replace(searchStr.ToLower(), role.ToColoredString()); - sb.Append(""); - List subRoles = Main.PlayerStates[player.PlayerId].SubRoles; - if (subRoles.Count > 0) addons.Append(GetString("AddonListTitle")); - - addons.Append(""); - - subRoles.ForEach(subRole => - { - addons.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong")}"); - string searchSubStr = GetString(subRole.ToString()); - addons.Replace(searchSubStr, subRole.ToColoredString()); - addons.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); - }); - - addons.Append(""); - if (role.UsesPetInsteadOfKill()) sb.Append($"\n\n{GetString("UsesPetInsteadOfKillNotice")}"); - - sb.Insert(0, $"{titleSb}\n"); - - MainInfoTMP.text = sb.ToString(); - AddonsInfoTMP.text = addons.ToString(); - } - - public static void Show() - { - if (!Fill || !Menu) Init(); - - if (!Showing) - { - Fill?.SetActive(true); - Menu?.SetActive(true); - } - } - - public static void Hide() - { - if (Showing) - { - Fill?.SetActive(false); - Menu?.SetActive(false); - } + Fill?.SetActive(false); + Menu?.SetActive(false); } } } \ No newline at end of file diff --git a/Patches/CosmeticsPatch.cs b/Patches/CosmeticsPatch.cs index 0c6e27edc..89e350b62 100644 --- a/Patches/CosmeticsPatch.cs +++ b/Patches/CosmeticsPatch.cs @@ -1,17 +1,16 @@ using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; + +public static class CosmeticsPatch { - public static class CosmeticsPatch + [HarmonyPatch(typeof(HatManager), nameof(HatManager.CheckLongModeValidCosmetic))] + private class CheckLongModeValidCosmeticPatch { - [HarmonyPatch(typeof(HatManager), nameof(HatManager.CheckLongModeValidCosmetic))] - private class CheckLongModeValidCosmeticPatch + public static bool Prefix(ref bool __result) { - public static bool Prefix(ref bool __result) - { - __result = true; - return false; - } + __result = true; + return false; } } } \ No newline at end of file diff --git a/Patches/CredentialsPatch.cs b/Patches/CredentialsPatch.cs index 498b3f270..6dad0ffc7 100644 --- a/Patches/CredentialsPatch.cs +++ b/Patches/CredentialsPatch.cs @@ -8,433 +8,432 @@ using UnityEngine.SceneManagement; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] +internal static class PingTrackerUpdatePatch { - [HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))] - internal static class PingTrackerUpdatePatch + public static PingTracker Instance; + private static readonly StringBuilder Sb = new(); + private static long LastUpdate; + private static int Delay => GameStates.IsInTask ? 8 : 1; + + private static void Postfix(PingTracker __instance) { - public static PingTracker Instance; - private static readonly StringBuilder Sb = new(); - private static long LastUpdate; - private static int Delay => GameStates.IsInTask ? 8 : 1; + Instance = __instance; - private static void Postfix(PingTracker __instance) + Instance.text.alignment = TextAlignmentOptions.Center; + Instance.text.text = Sb.ToString(); + + long now = Utils.TimeStamp; + if (now + Delay <= LastUpdate) return; // Only update every 2 seconds + + LastUpdate = now; + + Sb.Clear(); + + if (GameStates.IsLobby) Sb.Append("\r\n"); + + Sb.Append(Main.CredentialsText); + + int ping = AmongUsClient.Instance.Ping; + + string color = ping switch { - Instance = __instance; + < 30 => "#44dfcc", + < 100 => "#7bc690", + < 200 => "#f3920e", + < 400 => "#ff146e", + _ => "#ff4500" + }; - Instance.text.alignment = TextAlignmentOptions.Center; - Instance.text.text = Sb.ToString(); - - long now = Utils.TimeStamp; - if (now + Delay <= LastUpdate) return; // Only update every 2 seconds - - LastUpdate = now; - - Sb.Clear(); - - if (GameStates.IsLobby) Sb.Append("\r\n"); - - Sb.Append(Main.CredentialsText); - - int ping = AmongUsClient.Instance.Ping; - - string color = ping switch - { - < 30 => "#44dfcc", - < 100 => "#7bc690", - < 200 => "#f3920e", - < 400 => "#ff146e", - _ => "#ff4500" - }; - - Sb.Append(GameStates.InGame ? " - " : "\r\n"); - Sb.Append($"{ping} {GetString("PingText")}"); - Sb.Append(GameStates.InGame ? " - " : "\r\n"); - Sb.Append(string.Format(GetString("Server"), Utils.GetRegionName())); - if (GameStates.InGame) Sb.Append("\r\n."); - } + Sb.Append(GameStates.InGame ? " - " : "\r\n"); + Sb.Append($"{ping} {GetString("PingText")}"); + Sb.Append(GameStates.InGame ? " - " : "\r\n"); + Sb.Append(string.Format(GetString("Server"), Utils.GetRegionName())); + if (GameStates.InGame) Sb.Append("\r\n."); } +} - [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] - internal static class VersionShowerStartPatch +[HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))] +internal static class VersionShowerStartPatch +{ + private static void Postfix(VersionShower __instance) { - private static void Postfix(VersionShower __instance) - { #pragma warning disable CS0162 // Unreachable code detected - // ReSharper disable once HeuristicUnreachableCode - string testBuildIndicator = Main.TestBuild ? " <#ff0000>TEST" : string.Empty; + // ReSharper disable once HeuristicUnreachableCode + string testBuildIndicator = Main.TestBuild ? " <#ff0000>TEST" : string.Empty; #pragma warning restore CS0162 // Unreachable code detected - Main.CredentialsText = $"Endless Host Roles v{Main.PluginDisplayVersion}{testBuildIndicator} by Gurge44"; + Main.CredentialsText = $"Endless Host Roles v{Main.PluginDisplayVersion}{testBuildIndicator} by Gurge44"; - if (Main.IsAprilFools) Main.CredentialsText = "Endless Madness v11.45.14 by No one"; + if (Main.IsAprilFools) Main.CredentialsText = "Endless Madness v11.45.14 by No one"; - ErrorText.Create(__instance.text); + ErrorText.Create(__instance.text); - if (Main.HasArgumentException && ErrorText.Instance != null) - ErrorText.Instance.AddError(ErrorCode.Main_DictionaryError); + if (Main.HasArgumentException && ErrorText.Instance != null) + ErrorText.Instance.AddError(ErrorCode.Main_DictionaryError); - VersionChecker.Check(); + VersionChecker.Check(); + } +} + +// From TONX/Patches/AccountManagerPatch.cs, by KARPED1EM +[HarmonyPatch(typeof(AccountTab), nameof(AccountTab.Awake))] +public static class UpdateFriendCodeUIPatch +{ + private static GameObject VersionShower; + + public static void Prefix() + { + string credentialsText = $"Gurge44 \u00a9 2024"; + credentialsText += "\t\t\t"; + credentialsText += $"{Main.ModName} - {Main.PluginVersion}"; + + var friendCode = GameObject.Find("FriendCode"); + + if (friendCode != null && VersionShower == null) + { + VersionShower = Object.Instantiate(friendCode, friendCode.transform.parent); + VersionShower.name = "EHR Version Shower"; + VersionShower.transform.localPosition = friendCode.transform.localPosition + new Vector3(3.2f, 0f, 0f); + VersionShower.transform.localScale *= 1.7f; + var tmp = VersionShower.GetComponent(); + tmp.alignment = TextAlignmentOptions.Right; + tmp.fontSize = 30f; + tmp.SetText(credentialsText); + } + + var newRequest = GameObject.Find("NewRequest"); + + if (newRequest != null) + { + newRequest.transform.localPosition -= new Vector3(0f, 0f, 10f); + newRequest.transform.localScale = new(0.8f, 1f, 1f); + } + + var friendsButton = GameObject.Find("FriendsButton"); + + if (friendsButton != null) + { + friendsButton.transform.FindChild("Highlight").GetComponent().color = new(0f, 0.647f, 1f, 1f); + friendsButton.transform.FindChild("Inactive").GetComponent().color = new(0f, 0.847f, 1f, 1f); } } +} - // From TONX/Patches/AccountManagerPatch.cs, by KARPED1EM - [HarmonyPatch(typeof(AccountTab), nameof(AccountTab.Awake))] - public static class UpdateFriendCodeUIPatch +[HarmonyPatch(typeof(FriendsListUI), nameof(FriendsListUI.Open))] +static class FriendsListUIOpenPatch +{ + public static bool Prefix(FriendsListUI __instance) { - private static GameObject VersionShower; - - public static void Prefix() + try { - string credentialsText = $"Gurge44 \u00a9 2024"; - credentialsText += "\t\t\t"; - credentialsText += $"{Main.ModName} - {Main.PluginVersion}"; - - var friendCode = GameObject.Find("FriendCode"); - - if (friendCode != null && VersionShower == null) + if (__instance.gameObject.activeSelf || __instance.currentSceneName == "") { - VersionShower = Object.Instantiate(friendCode, friendCode.transform.parent); - VersionShower.name = "EHR Version Shower"; - VersionShower.transform.localPosition = friendCode.transform.localPosition + new Vector3(3.2f, 0f, 0f); - VersionShower.transform.localScale *= 1.7f; - var tmp = VersionShower.GetComponent(); - tmp.alignment = TextAlignmentOptions.Right; - tmp.fontSize = 30f; - tmp.SetText(credentialsText); - } - - var newRequest = GameObject.Find("NewRequest"); - - if (newRequest != null) - { - newRequest.transform.localPosition -= new Vector3(0f, 0f, 10f); - newRequest.transform.localScale = new(0.8f, 1f, 1f); - } - - var friendsButton = GameObject.Find("FriendsButton"); - - if (friendsButton != null) - { - friendsButton.transform.FindChild("Highlight").GetComponent().color = new(0f, 0.647f, 1f, 1f); - friendsButton.transform.FindChild("Inactive").GetComponent().color = new(0f, 0.847f, 1f, 1f); - } - } - } - - [HarmonyPatch(typeof(FriendsListUI), nameof(FriendsListUI.Open))] - static class FriendsListUIOpenPatch - { - public static bool Prefix(FriendsListUI __instance) - { - try - { - if (__instance.gameObject.activeSelf || __instance.currentSceneName == "") - { - __instance.Close(); - } - else - { - FriendsListBar[] componentsInChildren = __instance.GetComponentsInChildren(true); - - for (int index = 0; index < componentsInChildren.Length; ++index) - { - if (componentsInChildren[index] != null) - Object.Destroy(componentsInChildren[index].gameObject); - } - - Scene activeScene = SceneManager.GetActiveScene(); - __instance.currentSceneName = activeScene.name; - __instance.UpdateFriendCodeUI(); - - if (DestroyableSingleton.InstanceExists && DestroyableSingleton.Instance != null && DestroyableSingleton.Instance.Chat != null && DestroyableSingleton.Instance.Chat.IsOpenOrOpening || ShipStatus.Instance != null) - return false; - - __instance.friendBars = new(); - __instance.lobbyBars = new(); - __instance.notifBars = new(); - __instance.platformFriendBars = new(); - __instance.viewingAllFriends = true; - __instance.gameObject.SetActive(true); - __instance.guestAccountWarnings.ForEach((Action)(t => t.gameObject.SetActive(false))); - __instance.ViewRequestsButton.color = __instance.NoRequestsColor; - __instance.ViewRequestsText.text = DestroyableSingleton.Instance.GetString(StringNames.NoNewRequests); - - __instance.StartCoroutine(DestroyableSingleton.Instance.RefreshFriendsList((Action)(() => - { - __instance.ClearNotifs(); - - if (DestroyableSingleton.Instance.IsFriendsListAllowed()) - { - __instance.AddFriendObjects.SetActive(true); - __instance.RefreshBlockedPlayers(); - __instance.RefreshFriends(); - __instance.RefreshNotifications(); - } - else - { - __instance.AddFriendObjects.SetActive(false); - __instance.guestAccountWarnings.ForEach((Action)(g => g.SetUp())); - } - - __instance.RefreshRecentlyPlayed(); - __instance.RefreshPlatformFriends(); - - foreach (FriendsListBar friendBar in __instance.friendBars) - { - foreach (PassiveButton passiveButton in friendBar.ControllerSelectable) - ControllerManager.Instance.AddSelectableUiElement(passiveButton); - } - - foreach (FriendsListBar platformFriendBar in __instance.platformFriendBars) - { - foreach (PassiveButton passiveButton in platformFriendBar.ControllerSelectable) - ControllerManager.Instance.AddSelectableUiElement(passiveButton); - } - - foreach (FriendsListBar notifBar in __instance.notifBars) - { - foreach (PassiveButton passiveButton in notifBar.ControllerSelectable) - ControllerManager.Instance.AddSelectableUiElement(passiveButton); - } - - foreach (FriendsListBar lobbyBar in __instance.lobbyBars) - { - foreach (PassiveButton passiveButton in lobbyBar.ControllerSelectable) - ControllerManager.Instance.AddSelectableUiElement(passiveButton); - } - - ControllerManager.Instance.PickTopSelectable(); - }))); - - if (__instance.currentSceneName == "OnlineGame") - { - __instance.RefreshLobbyPlayers(); - __instance.LobbyPlayersTab.SetActive(true); - __instance.LobbyPlayersInactiveTab.SetActive(false); - __instance.OpenTab(0); - } - else - { - __instance.LobbyPlayersInactiveTab.SetActive(true); - __instance.LobbyPlayersTab.SetActive(false); - __instance.OpenTab(2); - } - - ControllerManager.Instance.OpenOverlayMenu(__instance.name, __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable); - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - - return false; - } - } - - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - [HarmonyPriority(Priority.First)] - internal static class TitleLogoPatch - { - private static GameObject ModStamp; - - private static GameObject Ambience; - - // public static GameObject LoadingHint; - private static GameObject LeftPanel; - public static GameObject RightPanel; - private static GameObject CloseRightButton; - private static GameObject Tint; - private static GameObject BottomButtonBounds; - - public static Vector3 RightPanelOp; - - private static void Postfix(MainMenuManager __instance) - { - GameObject.Find("BackgroundTexture")?.SetActive(!MainMenuManagerPatch.ShowedBak); - - if (!(ModStamp = GameObject.Find("ModStamp"))) return; - - ModStamp.transform.localScale = new(0.3f, 0.3f, 0.3f); - - Ambience = GameObject.Find("Ambience"); - - if (Ambience != null) - { - try - { - __instance.playButton.transform.gameObject.SetActive(true); - } - catch (Exception ex) - { - Logger.Warn(ex.ToString(), "MainMenuLoader"); - } - } - - if (!(LeftPanel = GameObject.Find("LeftPanel"))) return; - - LeftPanel.transform.localScale = new(0.7f, 0.7f, 0.7f); - LeftPanel.ForEachChild((Il2CppSystem.Action)ResetParent); - LeftPanel.SetActive(false); - - Color shade = new(0f, 0f, 0f, 0f); - Sprite standardActiveSprite = __instance.newsButton.activeSprites.GetComponent().sprite; - Sprite minorActiveSprite = __instance.quitButton.activeSprites.GetComponent().sprite; - - Dictionary, (Sprite, Color, Color, Color, Color)> mainButtons = new() - { - { [__instance.playButton, __instance.inventoryButton, __instance.shopButton], (standardActiveSprite, new(0f, 0.647f, 1f, 0.8f), shade, Color.white, Color.white) }, - { [__instance.newsButton, __instance.myAccountButton, __instance.settingsButton], (minorActiveSprite, new(0.825f, 0.825f, 0.286f, 0.8f), shade, Color.white, Color.white) }, - { [__instance.creditsButton, __instance.quitButton], (minorActiveSprite, new(0.226f, 1f, 0.792f, 0.8f), shade, Color.white, Color.white) } - }; - - foreach (KeyValuePair, (Sprite, Color, Color, Color, Color)> kvp in mainButtons) - kvp.Key.Do(button => FormatButtonColor(button, kvp.Value.Item2, kvp.Value.Item3, kvp.Value.Item4, kvp.Value.Item5)); - - try - { - mainButtons.Keys.Flatten().DoIf(x => x != null, x => x.buttonText.color = Color.white); - } - catch { } - - GameObject.Find("Divider")?.SetActive(false); - - if (!(RightPanel = GameObject.Find("RightPanel"))) return; - - var rpap = RightPanel.GetComponent(); - if (rpap) Object.Destroy(rpap); - - RightPanelOp = RightPanel.transform.localPosition; - RightPanel.transform.localPosition = RightPanelOp + new Vector3(10f, 0f, 0f); - RightPanel.GetComponent().color = new(1f, 0.78f, 0.9f, 1f); - - CloseRightButton = new("CloseRightPanelButton"); - CloseRightButton.transform.SetParent(RightPanel.transform); - CloseRightButton.transform.localPosition = new(-4.78f, 1.3f, 1f); - CloseRightButton.transform.localScale = new(1f, 1f, 1f); - CloseRightButton.AddComponent().size = new(0.6f, 1.5f); - var closeRightSpriteRenderer = CloseRightButton.AddComponent(); - closeRightSpriteRenderer.sprite = Utils.LoadSprite("EHR.Resources.Images.RightPanelCloseButton.png", 100f); - closeRightSpriteRenderer.color = new(1f, 0.78f, 0.9f, 1f); - var closeRightPassiveButton = CloseRightButton.AddComponent(); - closeRightPassiveButton.OnClick = new(); - closeRightPassiveButton.OnClick.AddListener((Action)MainMenuManagerPatch.HideRightPanel); - closeRightPassiveButton.OnMouseOut = new(); - closeRightPassiveButton.OnMouseOut.AddListener((Action)(() => closeRightSpriteRenderer.color = new(1f, 0.78f, 0.9f, 1f))); - closeRightPassiveButton.OnMouseOver = new(); - closeRightPassiveButton.OnMouseOver.AddListener((Action)(() => closeRightSpriteRenderer.color = new(1f, 0.68f, 0.99f, 1f))); - - Tint = __instance.screenTint.gameObject; - var ttap = Tint.GetComponent(); - if (ttap) Object.Destroy(ttap); - - Tint.transform.SetParent(RightPanel.transform); - Tint.transform.localPosition = new(-0.0824f, 0.0513f, Tint.transform.localPosition.z); - Tint.transform.localScale = new(1f, 1f, 1f); - __instance.howToPlayButton.gameObject.SetActive(false); - __instance.howToPlayButton.transform.parent.Find("FreePlayButton").gameObject.SetActive(false); - - if (!(BottomButtonBounds = GameObject.Find("BottomButtonBounds"))) return; - - BottomButtonBounds.transform.localPosition -= new Vector3(0f, 0.1f, 0f); - - return; - - static void ResetParent(GameObject obj) - { - obj.transform.SetParent(LeftPanel.transform.parent); - } - - void FormatButtonColor(PassiveButton button, Color inActiveColor, Color activeColor, Color inActiveTextColor, Color activeTextColor) - { - button.activeSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); - button.inactiveSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); - var activeRenderer = button.activeSprites.GetComponent(); - var inActiveRenderer = button.inactiveSprites.GetComponent(); - activeRenderer.sprite = minorActiveSprite; - inActiveRenderer.sprite = minorActiveSprite; - activeRenderer.color = activeColor.a == 0f ? new(inActiveColor.r, inActiveColor.g, inActiveColor.b, 1f) : activeColor; - inActiveRenderer.color = inActiveColor; - button.activeTextColor = activeTextColor; - button.inactiveTextColor = inActiveTextColor; - } - } - } - - [HarmonyPatch(typeof(ModManager), nameof(ModManager.LateUpdate))] - internal static class ModManagerLateUpdatePatch - { - public static bool Prefix(ModManager __instance) - { - __instance.ShowModStamp(); - - LateTask.Update(Time.deltaTime); - CheckMurderPatch.Update(); - ChatBubbleShower.Update(); - - return false; - } - - public static void Postfix(ModManager __instance) - { - __instance.localCamera = !DestroyableSingleton.InstanceExists - ? Camera.main - : DestroyableSingleton.Instance.GetComponentInChildren(); - - if (__instance.localCamera != null) - { - float offsetY = HudManager.InstanceExists ? 1.1f : 0.9f; - - __instance.ModStamp.transform.position = AspectPosition.ComputeWorldPosition( - __instance.localCamera, AspectPosition.EdgeAlignments.RightTop, - new(0.4f, offsetY, __instance.localCamera.nearClipPlane + 0.1f)); - } - - if (Options.IsLoaded && LobbyNotifierForDiscord.LastRoomCode != string.Empty && Utils.TimeStamp - LobbyNotifierForDiscord.LastRequestTimeStamp > 150) - LobbyNotifierForDiscord.NotifyLobbyStatusChanged(PlayerControl.LocalPlayer == null ? LobbyStatus.Closed : GameStates.InGame ? LobbyStatus.In_Game : LobbyStatus.In_Lobby); - } - } - - [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] - internal static class OptionsMenuBehaviourOpenPatch - { - public static bool Prefix(OptionsMenuBehaviour __instance) - { - try - { - if (DestroyableSingleton.InstanceExists && !DestroyableSingleton.Instance.SettingsButton.activeSelf) return false; - } - catch { } - - __instance.ResetText(); - - if (__instance.gameObject.activeSelf) - { - if (!__instance.Toggle) return false; - - __instance.GetComponent().Close(); + __instance.Close(); } else { - if (Minigame.Instance != null) Minigame.Instance.Close(); + FriendsListBar[] componentsInChildren = __instance.GetComponentsInChildren(true); - __instance.OpenTabGroup(0); - __instance.UpdateButtons(); - __instance.gameObject.SetActive(true); - __instance.MenuButton?.SelectButton(true); - if (DestroyableSingleton.InstanceExists) ConsoleJoystick.SetMode_MenuAdditive(); - - if (!__instance.grabbedControllerButtons) + for (int index = 0; index < componentsInChildren.Length; ++index) { - __instance.grabbedControllerButtons = true; - __instance.GrabControllerButtons(); + if (componentsInChildren[index] != null) + Object.Destroy(componentsInChildren[index].gameObject); } - ControllerManager.Instance.OpenOverlayMenu("OptionsMenu", __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable); - } + Scene activeScene = SceneManager.GetActiveScene(); + __instance.currentSceneName = activeScene.name; + __instance.UpdateFriendCodeUI(); - return false; + if (DestroyableSingleton.InstanceExists && DestroyableSingleton.Instance != null && DestroyableSingleton.Instance.Chat != null && DestroyableSingleton.Instance.Chat.IsOpenOrOpening || ShipStatus.Instance != null) + return false; + + __instance.friendBars = new(); + __instance.lobbyBars = new(); + __instance.notifBars = new(); + __instance.platformFriendBars = new(); + __instance.viewingAllFriends = true; + __instance.gameObject.SetActive(true); + __instance.guestAccountWarnings.ForEach((Action)(t => t.gameObject.SetActive(false))); + __instance.ViewRequestsButton.color = __instance.NoRequestsColor; + __instance.ViewRequestsText.text = DestroyableSingleton.Instance.GetString(StringNames.NoNewRequests); + + __instance.StartCoroutine(DestroyableSingleton.Instance.RefreshFriendsList((Action)(() => + { + __instance.ClearNotifs(); + + if (DestroyableSingleton.Instance.IsFriendsListAllowed()) + { + __instance.AddFriendObjects.SetActive(true); + __instance.RefreshBlockedPlayers(); + __instance.RefreshFriends(); + __instance.RefreshNotifications(); + } + else + { + __instance.AddFriendObjects.SetActive(false); + __instance.guestAccountWarnings.ForEach((Action)(g => g.SetUp())); + } + + __instance.RefreshRecentlyPlayed(); + __instance.RefreshPlatformFriends(); + + foreach (FriendsListBar friendBar in __instance.friendBars) + { + foreach (PassiveButton passiveButton in friendBar.ControllerSelectable) + ControllerManager.Instance.AddSelectableUiElement(passiveButton); + } + + foreach (FriendsListBar platformFriendBar in __instance.platformFriendBars) + { + foreach (PassiveButton passiveButton in platformFriendBar.ControllerSelectable) + ControllerManager.Instance.AddSelectableUiElement(passiveButton); + } + + foreach (FriendsListBar notifBar in __instance.notifBars) + { + foreach (PassiveButton passiveButton in notifBar.ControllerSelectable) + ControllerManager.Instance.AddSelectableUiElement(passiveButton); + } + + foreach (FriendsListBar lobbyBar in __instance.lobbyBars) + { + foreach (PassiveButton passiveButton in lobbyBar.ControllerSelectable) + ControllerManager.Instance.AddSelectableUiElement(passiveButton); + } + + ControllerManager.Instance.PickTopSelectable(); + }))); + + if (__instance.currentSceneName == "OnlineGame") + { + __instance.RefreshLobbyPlayers(); + __instance.LobbyPlayersTab.SetActive(true); + __instance.LobbyPlayersInactiveTab.SetActive(false); + __instance.OpenTab(0); + } + else + { + __instance.LobbyPlayersInactiveTab.SetActive(true); + __instance.LobbyPlayersTab.SetActive(false); + __instance.OpenTab(2); + } + + ControllerManager.Instance.OpenOverlayMenu(__instance.name, __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable); + } + } + catch (Exception e) + { + Utils.ThrowException(e); + } + + return false; + } +} + +[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] +[HarmonyPriority(Priority.First)] +internal static class TitleLogoPatch +{ + private static GameObject ModStamp; + + private static GameObject Ambience; + + // public static GameObject LoadingHint; + private static GameObject LeftPanel; + public static GameObject RightPanel; + private static GameObject CloseRightButton; + private static GameObject Tint; + private static GameObject BottomButtonBounds; + + public static Vector3 RightPanelOp; + + private static void Postfix(MainMenuManager __instance) + { + GameObject.Find("BackgroundTexture")?.SetActive(!MainMenuManagerPatch.ShowedBak); + + if (!(ModStamp = GameObject.Find("ModStamp"))) return; + + ModStamp.transform.localScale = new(0.3f, 0.3f, 0.3f); + + Ambience = GameObject.Find("Ambience"); + + if (Ambience != null) + { + try + { + __instance.playButton.transform.gameObject.SetActive(true); + } + catch (Exception ex) + { + Logger.Warn(ex.ToString(), "MainMenuLoader"); + } + } + + if (!(LeftPanel = GameObject.Find("LeftPanel"))) return; + + LeftPanel.transform.localScale = new(0.7f, 0.7f, 0.7f); + LeftPanel.ForEachChild((Il2CppSystem.Action)ResetParent); + LeftPanel.SetActive(false); + + Color shade = new(0f, 0f, 0f, 0f); + Sprite standardActiveSprite = __instance.newsButton.activeSprites.GetComponent().sprite; + Sprite minorActiveSprite = __instance.quitButton.activeSprites.GetComponent().sprite; + + Dictionary, (Sprite, Color, Color, Color, Color)> mainButtons = new() + { + { [__instance.playButton, __instance.inventoryButton, __instance.shopButton], (standardActiveSprite, new(0f, 0.647f, 1f, 0.8f), shade, Color.white, Color.white) }, + { [__instance.newsButton, __instance.myAccountButton, __instance.settingsButton], (minorActiveSprite, new(0.825f, 0.825f, 0.286f, 0.8f), shade, Color.white, Color.white) }, + { [__instance.creditsButton, __instance.quitButton], (minorActiveSprite, new(0.226f, 1f, 0.792f, 0.8f), shade, Color.white, Color.white) } + }; + + foreach (KeyValuePair, (Sprite, Color, Color, Color, Color)> kvp in mainButtons) + kvp.Key.Do(button => FormatButtonColor(button, kvp.Value.Item2, kvp.Value.Item3, kvp.Value.Item4, kvp.Value.Item5)); + + try + { + mainButtons.Keys.Flatten().DoIf(x => x != null, x => x.buttonText.color = Color.white); + } + catch { } + + GameObject.Find("Divider")?.SetActive(false); + + if (!(RightPanel = GameObject.Find("RightPanel"))) return; + + var rpap = RightPanel.GetComponent(); + if (rpap) Object.Destroy(rpap); + + RightPanelOp = RightPanel.transform.localPosition; + RightPanel.transform.localPosition = RightPanelOp + new Vector3(10f, 0f, 0f); + RightPanel.GetComponent().color = new(1f, 0.78f, 0.9f, 1f); + + CloseRightButton = new("CloseRightPanelButton"); + CloseRightButton.transform.SetParent(RightPanel.transform); + CloseRightButton.transform.localPosition = new(-4.78f, 1.3f, 1f); + CloseRightButton.transform.localScale = new(1f, 1f, 1f); + CloseRightButton.AddComponent().size = new(0.6f, 1.5f); + var closeRightSpriteRenderer = CloseRightButton.AddComponent(); + closeRightSpriteRenderer.sprite = Utils.LoadSprite("EHR.Resources.Images.RightPanelCloseButton.png", 100f); + closeRightSpriteRenderer.color = new(1f, 0.78f, 0.9f, 1f); + var closeRightPassiveButton = CloseRightButton.AddComponent(); + closeRightPassiveButton.OnClick = new(); + closeRightPassiveButton.OnClick.AddListener((Action)MainMenuManagerPatch.HideRightPanel); + closeRightPassiveButton.OnMouseOut = new(); + closeRightPassiveButton.OnMouseOut.AddListener((Action)(() => closeRightSpriteRenderer.color = new(1f, 0.78f, 0.9f, 1f))); + closeRightPassiveButton.OnMouseOver = new(); + closeRightPassiveButton.OnMouseOver.AddListener((Action)(() => closeRightSpriteRenderer.color = new(1f, 0.68f, 0.99f, 1f))); + + Tint = __instance.screenTint.gameObject; + var ttap = Tint.GetComponent(); + if (ttap) Object.Destroy(ttap); + + Tint.transform.SetParent(RightPanel.transform); + Tint.transform.localPosition = new(-0.0824f, 0.0513f, Tint.transform.localPosition.z); + Tint.transform.localScale = new(1f, 1f, 1f); + __instance.howToPlayButton.gameObject.SetActive(false); + __instance.howToPlayButton.transform.parent.Find("FreePlayButton").gameObject.SetActive(false); + + if (!(BottomButtonBounds = GameObject.Find("BottomButtonBounds"))) return; + + BottomButtonBounds.transform.localPosition -= new Vector3(0f, 0.1f, 0f); + + return; + + static void ResetParent(GameObject obj) + { + obj.transform.SetParent(LeftPanel.transform.parent); + } + + void FormatButtonColor(PassiveButton button, Color inActiveColor, Color activeColor, Color inActiveTextColor, Color activeTextColor) + { + button.activeSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); + button.inactiveSprites.transform.FindChild("Shine")?.gameObject.SetActive(false); + var activeRenderer = button.activeSprites.GetComponent(); + var inActiveRenderer = button.inactiveSprites.GetComponent(); + activeRenderer.sprite = minorActiveSprite; + inActiveRenderer.sprite = minorActiveSprite; + activeRenderer.color = activeColor.a == 0f ? new(inActiveColor.r, inActiveColor.g, inActiveColor.b, 1f) : activeColor; + inActiveRenderer.color = inActiveColor; + button.activeTextColor = activeTextColor; + button.inactiveTextColor = inActiveTextColor; } } +} + +[HarmonyPatch(typeof(ModManager), nameof(ModManager.LateUpdate))] +internal static class ModManagerLateUpdatePatch +{ + public static bool Prefix(ModManager __instance) + { + __instance.ShowModStamp(); + + LateTask.Update(Time.deltaTime); + CheckMurderPatch.Update(); + ChatBubbleShower.Update(); + + return false; + } + + public static void Postfix(ModManager __instance) + { + __instance.localCamera = !DestroyableSingleton.InstanceExists + ? Camera.main + : DestroyableSingleton.Instance.GetComponentInChildren(); + + if (__instance.localCamera != null) + { + float offsetY = HudManager.InstanceExists ? 1.1f : 0.9f; + + __instance.ModStamp.transform.position = AspectPosition.ComputeWorldPosition( + __instance.localCamera, AspectPosition.EdgeAlignments.RightTop, + new(0.4f, offsetY, __instance.localCamera.nearClipPlane + 0.1f)); + } + + if (Options.IsLoaded && LobbyNotifierForDiscord.LastRoomCode != string.Empty && Utils.TimeStamp - LobbyNotifierForDiscord.LastRequestTimeStamp > 150) + LobbyNotifierForDiscord.NotifyLobbyStatusChanged(PlayerControl.LocalPlayer == null ? LobbyStatus.Closed : GameStates.InGame ? LobbyStatus.In_Game : LobbyStatus.In_Lobby); + } +} + +[HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] +internal static class OptionsMenuBehaviourOpenPatch +{ + public static bool Prefix(OptionsMenuBehaviour __instance) + { + try + { + if (DestroyableSingleton.InstanceExists && !DestroyableSingleton.Instance.SettingsButton.activeSelf) return false; + } + catch { } + + __instance.ResetText(); + + if (__instance.gameObject.activeSelf) + { + if (!__instance.Toggle) return false; + + __instance.GetComponent().Close(); + } + else + { + if (Minigame.Instance != null) Minigame.Instance.Close(); + + __instance.OpenTabGroup(0); + __instance.UpdateButtons(); + __instance.gameObject.SetActive(true); + __instance.MenuButton?.SelectButton(true); + if (DestroyableSingleton.InstanceExists) ConsoleJoystick.SetMode_MenuAdditive(); + + if (!__instance.grabbedControllerButtons) + { + __instance.grabbedControllerButtons = true; + __instance.GrabControllerButtons(); + } + + ControllerManager.Instance.OpenOverlayMenu("OptionsMenu", __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable); + } + + return false; + } } \ No newline at end of file diff --git a/Patches/DeconSystemPatch.cs b/Patches/DeconSystemPatch.cs index 8a02f1d8b..be8cc013e 100644 --- a/Patches/DeconSystemPatch.cs +++ b/Patches/DeconSystemPatch.cs @@ -1,31 +1,30 @@ using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(DeconSystem), nameof(DeconSystem.UpdateSystem))] +public static class DeconSystemUpdateSystemPatch { - [HarmonyPatch(typeof(DeconSystem), nameof(DeconSystem.UpdateSystem))] - public static class DeconSystemUpdateSystemPatch + public static void Prefix(DeconSystem __instance) { - public static void Prefix(DeconSystem __instance) + if (!AmongUsClient.Instance.AmHost) return; + + if (Options.ChangeDecontaminationTime.GetBool()) { - if (!AmongUsClient.Instance.AmHost) return; - - if (Options.ChangeDecontaminationTime.GetBool()) + float deconTime = Main.CurrentMap switch { - float deconTime = Main.CurrentMap switch - { - MapNames.Mira => Options.DecontaminationTimeOnMiraHQ.GetFloat(), - MapNames.Polus => Options.DecontaminationTimeOnPolus.GetFloat(), - _ => 3f - }; + MapNames.Mira => Options.DecontaminationTimeOnMiraHQ.GetFloat(), + MapNames.Polus => Options.DecontaminationTimeOnPolus.GetFloat(), + _ => 3f + }; - __instance.DoorOpenTime = deconTime; - __instance.DeconTime = deconTime; - } - else - { - __instance.DoorOpenTime = 3f; - __instance.DeconTime = 3f; - } + __instance.DoorOpenTime = deconTime; + __instance.DeconTime = deconTime; + } + else + { + __instance.DoorOpenTime = 3f; + __instance.DeconTime = 3f; } } } \ No newline at end of file diff --git a/Patches/DisconnectPatch.cs b/Patches/DisconnectPatch.cs index 10bb9bad8..eb5d401a9 100644 --- a/Patches/DisconnectPatch.cs +++ b/Patches/DisconnectPatch.cs @@ -1,15 +1,15 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnDisconnected))] +internal static class OnDisconnectedPatch { - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnDisconnected))] - internal static class OnDisconnectedPatch + public static void Postfix( /*AmongUsClient __instance*/) { - public static void Postfix( /*AmongUsClient __instance*/) - { - Main.VisibleTasksCount = false; - } + Main.VisibleTasksCount = false; } +} /* [HarmonyPatch(typeof(DisconnectPopup), nameof(DisconnectPopup.DoShow))] internal static class ShowDisconnectPopupPatch @@ -140,5 +140,4 @@ namespace EHR }, 0.01f, "Override Disconnect Text"); } } -*/ -} \ No newline at end of file +*/ \ No newline at end of file diff --git a/Patches/DisconnectPenaltyPatch.cs b/Patches/DisconnectPenaltyPatch.cs index 6db3da289..8098df2fe 100644 --- a/Patches/DisconnectPenaltyPatch.cs +++ b/Patches/DisconnectPenaltyPatch.cs @@ -1,24 +1,23 @@ using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(StatsManager), nameof(StatsManager.BanMinutesLeft), MethodType.Getter)] +public static class DisconnectPenaltyPatch { - [HarmonyPatch(typeof(StatsManager), nameof(StatsManager.BanMinutesLeft), MethodType.Getter)] - public static class DisconnectPenaltyPatch + public static bool Prefix(StatsManager __instance, ref int __result) { - public static bool Prefix(StatsManager __instance, ref int __result) + if (!DebugModeManager.AmDebugger) return true; + + if (__instance.BanPoints != 0f) { - if (!DebugModeManager.AmDebugger) return true; - - if (__instance.BanPoints != 0f) - { - __instance.BanPoints = 0f; - __result = 0; - Logger.Info("Debug Removed Disconnect ban", "PenaltyPatch"); - return false; - } - - return true; + __instance.BanPoints = 0f; + __result = 0; + Logger.Info("Debug Removed Disconnect ban", "PenaltyPatch"); + return false; } + + return true; } } //Code from https://github.com/scp222thj/MalumMenu/blob/main/src/Passive/PenaltyPatch.cs \ No newline at end of file diff --git a/Patches/DiscordPatch.cs b/Patches/DiscordPatch.cs index 872deb8b6..489dc0b0a 100644 --- a/Patches/DiscordPatch.cs +++ b/Patches/DiscordPatch.cs @@ -3,50 +3,49 @@ using AmongUs.Data; using Discord; using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; + +// Originally from "Town of Us Rewritten", by Det +[HarmonyPatch(typeof(ActivityManager), nameof(ActivityManager.UpdateActivity))] +public class DiscordRPC { - // Originally from "Town of Us Rewritten", by Det - [HarmonyPatch(typeof(ActivityManager), nameof(ActivityManager.UpdateActivity))] - public class DiscordRPC + private static string Lobbycode = ""; + private static string Region = ""; + + public static void Prefix([HarmonyArgument(0)] Activity activity) { - private static string Lobbycode = ""; - private static string Region = ""; + if (activity == null) return; - public static void Prefix([HarmonyArgument(0)] Activity activity) + var details = $"EHR v{Main.PluginDisplayVersion}"; + activity.Details = details; + + try { - if (activity == null) return; - - var details = $"EHR v{Main.PluginDisplayVersion}"; - activity.Details = details; - - try + if (activity.State != "In Menus") { - if (activity.State != "In Menus") + if (!DataManager.Settings.Gameplay.StreamerMode) { - if (!DataManager.Settings.Gameplay.StreamerMode) + if (GameStates.IsLobby) { - if (GameStates.IsLobby) - { - Lobbycode = GameStartManager.Instance.GameRoomNameCode.text; - Region = Utils.GetRegionName(); - } - - if (Lobbycode != "" && Region != "") details = $"EHR - {Lobbycode} ({Region})"; + Lobbycode = GameStartManager.Instance.GameRoomNameCode.text; + Region = Utils.GetRegionName(); } - else - details = $"EHR v{Main.PluginDisplayVersion}"; - activity.Details = details; + if (Lobbycode != "" && Region != "") details = $"EHR - {Lobbycode} ({Region})"; } - } + else + details = $"EHR v{Main.PluginDisplayVersion}"; - catch (ArgumentException ex) - { - Logger.Error("Error in updating discord rpc", "DiscordPatch"); - Logger.Exception(ex, "DiscordPatch"); - details = $"EHR v{Main.PluginDisplayVersion}"; activity.Details = details; } } + + catch (ArgumentException ex) + { + Logger.Error("Error in updating discord rpc", "DiscordPatch"); + Logger.Exception(ex, "DiscordPatch"); + details = $"EHR v{Main.PluginDisplayVersion}"; + activity.Details = details; + } } } \ No newline at end of file diff --git a/Patches/DleksPatch.cs b/Patches/DleksPatch.cs index 53776d38f..a41637209 100644 --- a/Patches/DleksPatch.cs +++ b/Patches/DleksPatch.cs @@ -3,157 +3,156 @@ using EHR.Patches; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(GameStartManager))] +internal static class AllMapIconsPatch { - [HarmonyPatch(typeof(GameStartManager))] - internal static class AllMapIconsPatch + [HarmonyPatch(nameof(GameStartManager.Start))] + [HarmonyPostfix] + public static void Postfix_AllMapIcons(GameStartManager __instance) { - [HarmonyPatch(nameof(GameStartManager.Start))] - [HarmonyPostfix] - public static void Postfix_AllMapIcons(GameStartManager __instance) + try { - try - { - if (__instance == null) return; + if (__instance == null) return; - LateTask.New(() => + LateTask.New(() => + { + if (Main.NormalOptions.MapId == 3) { - if (Main.NormalOptions.MapId == 3) - { - Main.NormalOptions.MapId = 0; - __instance.UpdateMapImage(MapNames.Skeld); + Main.NormalOptions.MapId = 0; + __instance.UpdateMapImage(MapNames.Skeld); - if (!Options.RandomMapsMode.GetBool()) CreateOptionsPickerPatch.SetDleks = true; - } - }, AmongUsClient.Instance.AmHost ? 1f : 4f, "Set Skeld Icon For Dleks Map"); + if (!Options.RandomMapsMode.GetBool()) CreateOptionsPickerPatch.SetDleks = true; + } + }, AmongUsClient.Instance.AmHost ? 1f : 4f, "Set Skeld Icon For Dleks Map"); - MapIconByName dleksIcon = Object.Instantiate(__instance, __instance.gameObject.transform).AllMapIcons[0]; - dleksIcon.Name = MapNames.Dleks; - dleksIcon.MapImage = Utils.LoadSprite("EHR.Resources.Images.DleksBanner.png", 100f); - dleksIcon.NameImage = Utils.LoadSprite("EHR.Resources.Images.DleksBanner-Wordart.png", 100f); - __instance.AllMapIcons.Add(dleksIcon); - } - catch (Exception e) - { - Utils.ThrowException(e); - } + MapIconByName dleksIcon = Object.Instantiate(__instance, __instance.gameObject.transform).AllMapIcons[0]; + dleksIcon.Name = MapNames.Dleks; + dleksIcon.MapImage = Utils.LoadSprite("EHR.Resources.Images.DleksBanner.png", 100f); + dleksIcon.NameImage = Utils.LoadSprite("EHR.Resources.Images.DleksBanner-Wordart.png", 100f); + __instance.AllMapIcons.Add(dleksIcon); + } + catch (Exception e) + { + Utils.ThrowException(e); } } +} - [HarmonyPatch(typeof(AmongUsClient._CoStartGameHost_d__32), nameof(AmongUsClient._CoStartGameHost_d__32.MoveNext))] - public static class DleksPatch +[HarmonyPatch(typeof(AmongUsClient._CoStartGameHost_d__32), nameof(AmongUsClient._CoStartGameHost_d__32.MoveNext))] +public static class DleksPatch +{ + public static bool Prefix(AmongUsClient._CoStartGameHost_d__32 __instance, ref bool __result) { - public static bool Prefix(AmongUsClient._CoStartGameHost_d__32 __instance, ref bool __result) + if (__instance.__1__state != 0) return true; + + __instance.__1__state = -1; + if (LobbyBehaviour.Instance) LobbyBehaviour.Instance.Despawn(); + + if (ShipStatus.Instance) { - if (__instance.__1__state != 0) return true; - - __instance.__1__state = -1; - if (LobbyBehaviour.Instance) LobbyBehaviour.Instance.Despawn(); - - if (ShipStatus.Instance) - { - __instance.__2__current = null; - __instance.__1__state = 2; - __result = true; - return false; - } - - // removed dleks check as it's always false - int num2 = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); - __instance.__2__current = __instance.__4__this.ShipLoadingAsyncHandle = __instance.__4__this.ShipPrefabs[num2].InstantiateAsync(); - __instance.__1__state = 1; - + __instance.__2__current = null; + __instance.__1__state = 2; __result = true; return false; } + + // removed dleks check as it's always false + int num2 = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); + __instance.__2__current = __instance.__4__this.ShipLoadingAsyncHandle = __instance.__4__this.ShipPrefabs[num2].InstantiateAsync(); + __instance.__1__state = 1; + + __result = true; + return false; } +} - [HarmonyPatch(typeof(Vent), nameof(Vent.SetButtons))] - public static class VentSetButtonsPatch +[HarmonyPatch(typeof(Vent), nameof(Vent.SetButtons))] +public static class VentSetButtonsPatch +{ + public static bool ShowButtons; + + // Fix arrows buttons in vent on Dleks map and "Index was outside the bounds of the array" errors + private static bool Prefix( /*Vent __instance,*/ [HarmonyArgument(0)] ref bool enabled) { - public static bool ShowButtons; - - // Fix arrows buttons in vent on Dleks map and "Index was outside the bounds of the array" errors - private static bool Prefix( /*Vent __instance,*/ [HarmonyArgument(0)] ref bool enabled) + if (Main.CurrentMap == MapNames.Dleks && Main.IntroDestroyed) { - if (Main.CurrentMap == MapNames.Dleks && Main.IntroDestroyed) - { - enabled = false; - if (GameStates.IsMeeting) ShowButtons = false; - } - - return true; + enabled = false; + if (GameStates.IsMeeting) ShowButtons = false; } - public static void Postfix(Vent __instance, [HarmonyArgument(0)] bool enabled) - { - if (Main.CurrentMap != MapNames.Dleks) return; - - if (enabled || !Main.IntroDestroyed) return; - - bool setActive = ShowButtons || (!PlayerControl.LocalPlayer.inVent && !GameStates.IsMeeting); - - switch (__instance.Id) - { - case 0: - case 1: - case 2: - case 3: - case 5: - case 6: - __instance.Buttons[0].gameObject.SetActive(setActive); - __instance.Buttons[1].gameObject.SetActive(setActive); - break; - case 7: - case 12: - case 13: - __instance.Buttons[0].gameObject.SetActive(setActive); - break; - case 4: - case 8: - case 9: - case 10: - case 11: - __instance.Buttons[1].gameObject.SetActive(setActive); - break; - } - } + return true; } - [HarmonyPatch(typeof(Vent), nameof(Vent.TryMoveToVent))] - internal class VentTryMoveToVentPatch + public static void Postfix(Vent __instance, [HarmonyArgument(0)] bool enabled) { - // Update arrows buttons when player move to vents - private static void Postfix(Vent __instance, [HarmonyArgument(0)] Vent otherVent) - { - if (__instance == null || otherVent == null || Main.CurrentMap != MapNames.Dleks) return; + if (Main.CurrentMap != MapNames.Dleks) return; - VentSetButtonsPatch.ShowButtons = true; - VentSetButtonsPatch.Postfix(otherVent, false); - VentSetButtonsPatch.ShowButtons = false; + if (enabled || !Main.IntroDestroyed) return; + + bool setActive = ShowButtons || (!PlayerControl.LocalPlayer.inVent && !GameStates.IsMeeting); + + switch (__instance.Id) + { + case 0: + case 1: + case 2: + case 3: + case 5: + case 6: + __instance.Buttons[0].gameObject.SetActive(setActive); + __instance.Buttons[1].gameObject.SetActive(setActive); + break; + case 7: + case 12: + case 13: + __instance.Buttons[0].gameObject.SetActive(setActive); + break; + case 4: + case 8: + case 9: + case 10: + case 11: + __instance.Buttons[1].gameObject.SetActive(setActive); + break; } } +} - [HarmonyPatch(typeof(Vent), nameof(Vent.UpdateArrows))] - internal class VentUpdateArrowsPatch +[HarmonyPatch(typeof(Vent), nameof(Vent.TryMoveToVent))] +internal class VentTryMoveToVentPatch +{ + // Update arrows buttons when player move to vents + private static void Postfix(Vent __instance, [HarmonyArgument(0)] Vent otherVent) { - // Fixes "Index was outside the bounds of the array" errors when arrows updates in vent on Dleks map - private static bool Prefix() - { - return Main.CurrentMap != MapNames.Dleks; - } + if (__instance == null || otherVent == null || Main.CurrentMap != MapNames.Dleks) return; + + VentSetButtonsPatch.ShowButtons = true; + VentSetButtonsPatch.Postfix(otherVent, false); + VentSetButtonsPatch.ShowButtons = false; } +} - [HarmonyPatch(typeof(StringOption), nameof(StringOption.Start))] - public static class AutoselectDleksPatch +[HarmonyPatch(typeof(Vent), nameof(Vent.UpdateArrows))] +internal class VentUpdateArrowsPatch +{ + // Fixes "Index was outside the bounds of the array" errors when arrows updates in vent on Dleks map + private static bool Prefix() { - public static void Postfix(StringOption __instance) + return Main.CurrentMap != MapNames.Dleks; + } +} + +[HarmonyPatch(typeof(StringOption), nameof(StringOption.Start))] +public static class AutoselectDleksPatch +{ + public static void Postfix(StringOption __instance) + { + if (__instance.Title == StringNames.GameMapName) { - if (__instance.Title == StringNames.GameMapName) - { - // vanilla clamps this to not autoselect dleks - __instance.Value = GameOptionsManager.Instance.CurrentGameOptions.MapId; - } + // vanilla clamps this to not autoselect dleks + __instance.Value = GameOptionsManager.Instance.CurrentGameOptions.MapId; } } } \ No newline at end of file diff --git a/Patches/EHROnlySearch.cs b/Patches/EHROnlySearch.cs index 1d2399c75..d4c416414 100644 --- a/Patches/EHROnlySearch.cs +++ b/Patches/EHROnlySearch.cs @@ -1,50 +1,49 @@ using AmongUs.Data; using HarmonyLib; -namespace EHR +namespace EHR; + +// ˛ÎżĽŁşhttps://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/SNROnlySearch.cs +public static class EHROnlySearch { - // ˛ÎżĽŁşhttps://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/SNROnlySearch.cs - public static class EHROnlySearch + public const string FilterText = "EHR"; + + [HarmonyPatch(typeof(FilterTagManager), nameof(FilterTagManager.RefreshTags))] + public static class FilterTagManagerPatch { - public const string FilterText = "EHR"; - - [HarmonyPatch(typeof(FilterTagManager), nameof(FilterTagManager.RefreshTags))] - public static class FilterTagManagerPatch + public static void Postfix() { - public static void Postfix() - { - DataManager.Settings.Multiplayer.ValidGameFilterOptions.FilterTags.Add(FilterText); - } + DataManager.Settings.Multiplayer.ValidGameFilterOptions.FilterTags.Add(FilterText); } + } - [HarmonyPatch(typeof(FilterTagsMenu), nameof(FilterTagsMenu.ChooseOption))] - public static class FilterTagsMenuChooseOptionPatch + [HarmonyPatch(typeof(FilterTagsMenu), nameof(FilterTagsMenu.ChooseOption))] + public static class FilterTagsMenuChooseOptionPatch + { + public static void Postfix(FilterTagsMenu __instance, ChatLanguageButton button, string filter) { - public static void Postfix(FilterTagsMenu __instance, ChatLanguageButton button, string filter) + if (__instance.targetOpts.FilterTags.Contains(FilterText)) { - if (__instance.targetOpts.FilterTags.Contains(FilterText)) + if (filter == FilterText) { - if (filter == FilterText) - { - __instance.targetOpts.FilterTags = new(); - __instance.targetOpts.FilterTags.Add(FilterText); - foreach (UiElement btn in __instance.controllerSelectable) btn.GetComponent().SetSelected(false); + __instance.targetOpts.FilterTags = new(); + __instance.targetOpts.FilterTags.Add(FilterText); + foreach (UiElement btn in __instance.controllerSelectable) btn.GetComponent().SetSelected(false); - button.SetSelected(true); - } - else - { - __instance.targetOpts.FilterTags.Remove(FilterText); - - foreach (UiElement btn in __instance.controllerSelectable) - { - var LangBtn = btn.GetComponent(); - if (LangBtn.Text.text == FilterText) LangBtn.SetSelected(false); - } - } - - __instance.UpdateButtonText(); + button.SetSelected(true); } + else + { + __instance.targetOpts.FilterTags.Remove(FilterText); + + foreach (UiElement btn in __instance.controllerSelectable) + { + var LangBtn = btn.GetComponent(); + if (LangBtn.Text.text == FilterText) LangBtn.SetSelected(false); + } + } + + __instance.UpdateButtonText(); } } } diff --git a/Patches/EndGameManagerPatch.cs b/Patches/EndGameManagerPatch.cs index 8c61400ec..18e30cad5 100644 --- a/Patches/EndGameManagerPatch.cs +++ b/Patches/EndGameManagerPatch.cs @@ -3,64 +3,63 @@ using TMPro; using UnityEngine; using static EHR.Translator; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.ShowButtons))] +public class EndGameManagerPatch { - [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.ShowButtons))] - public class EndGameManagerPatch + public static GameObject CountdownText; + public static bool IsRestarting { get; private set; } + + public static void Postfix(EndGameManager __instance) { - public static GameObject CountdownText; - public static bool IsRestarting { get; private set; } + if (!AmongUsClient.Instance.AmHost || !Options.AutoPlayAgain.GetBool()) return; - public static void Postfix(EndGameManager __instance) + IsRestarting = false; + + LateTask.New(() => { - if (!AmongUsClient.Instance.AmHost || !Options.AutoPlayAgain.GetBool()) return; + Logger.Msg("Beginning Auto Play Again Countdown!", "AutoPlayAgain"); + IsRestarting = true; + BeginAutoPlayAgainCountdown(__instance, Options.AutoPlayAgainCountdown.GetInt()); + }, 0.5f, "Auto Play Again"); + } - IsRestarting = false; + public static void CancelPlayAgain() + { + IsRestarting = false; + } - LateTask.New(() => - { - Logger.Msg("Beginning Auto Play Again Countdown!", "AutoPlayAgain"); - IsRestarting = true; - BeginAutoPlayAgainCountdown(__instance, Options.AutoPlayAgainCountdown.GetInt()); - }, 0.5f, "Auto Play Again"); + private static void BeginAutoPlayAgainCountdown(EndGameManager endGameManager, int seconds) + { + if (!IsRestarting) return; + + if (endGameManager == null) return; + + EndGameNavigation navigation = endGameManager.Navigation; + if (navigation == null) return; + + if (seconds == Options.AutoPlayAgainCountdown.GetInt()) + { + CountdownText = new("CountdownText"); + CountdownText.transform.position = new(0f, 2.5f, 10f); + var CountdownTextText = CountdownText.AddComponent(); + CountdownTextText.text = string.Format(GetString("CountdownText"), seconds); + CountdownTextText.alignment = TextAlignmentOptions.Center; + CountdownTextText.fontSize = 3f; + } + else + { + var CountdownTextText = CountdownText.GetComponent(); + CountdownTextText.text = string.Format(GetString("CountdownText"), seconds); } - public static void CancelPlayAgain() + if (seconds == 0) { - IsRestarting = false; - } - - private static void BeginAutoPlayAgainCountdown(EndGameManager endGameManager, int seconds) - { - if (!IsRestarting) return; - - if (endGameManager == null) return; - - EndGameNavigation navigation = endGameManager.Navigation; - if (navigation == null) return; - - if (seconds == Options.AutoPlayAgainCountdown.GetInt()) - { - CountdownText = new("CountdownText"); - CountdownText.transform.position = new(0f, 2.5f, 10f); - var CountdownTextText = CountdownText.AddComponent(); - CountdownTextText.text = string.Format(GetString("CountdownText"), seconds); - CountdownTextText.alignment = TextAlignmentOptions.Center; - CountdownTextText.fontSize = 3f; - } - else - { - var CountdownTextText = CountdownText.GetComponent(); - CountdownTextText.text = string.Format(GetString("CountdownText"), seconds); - } - - if (seconds == 0) - { - navigation.NextGame(); - CountdownText.transform.DestroyChildren(); - } - else - LateTask.New(() => { BeginAutoPlayAgainCountdown(endGameManager, seconds - 1); }, 1f, log: false); + navigation.NextGame(); + CountdownText.transform.DestroyChildren(); } + else + LateTask.New(() => { BeginAutoPlayAgainCountdown(endGameManager, seconds - 1); }, 1f, log: false); } } \ No newline at end of file diff --git a/Patches/ExilePatch.cs b/Patches/ExilePatch.cs index 0ba9be037..d99b04318 100644 --- a/Patches/ExilePatch.cs +++ b/Patches/ExilePatch.cs @@ -9,233 +9,232 @@ using EHR.Impostor; using EHR.Neutral; using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; + +internal static class ExileControllerWrapUpPatch { - internal static class ExileControllerWrapUpPatch + private static void WrapUpPostfix(NetworkedPlayerInfo exiled) { - private static void WrapUpPostfix(NetworkedPlayerInfo exiled) + var DecidedWinner = false; + if (!AmongUsClient.Instance.AmHost) return; + + AntiBlackout.RestoreIsDead(false); + + if (!Collector.CollectorWin(false) && exiled != null) { - var DecidedWinner = false; - if (!AmongUsClient.Instance.AmHost) return; + exiled.IsDead = true; + Main.PlayerStates[exiled.PlayerId].deathReason = PlayerState.DeathReason.Vote; + CustomRoles role = exiled.GetCustomRole(); - AntiBlackout.RestoreIsDead(false); - - if (!Collector.CollectorWin(false) && exiled != null) + if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId)) { - exiled.IsDead = true; - Main.PlayerStates[exiled.PlayerId].deathReason = PlayerState.DeathReason.Vote; - CustomRoles role = exiled.GetCustomRole(); - - if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId)) + if (!Options.InnocentCanWinByImp.GetBool() && role.IsImpostor()) + Logger.Info("The exiled player is an impostor, but the Innocent cannot win due to the settings", "Exeiled Winner Check"); + else { - if (!Options.InnocentCanWinByImp.GetBool() && role.IsImpostor()) - Logger.Info("The exiled player is an impostor, but the Innocent cannot win due to the settings", "Exeiled Winner Check"); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Innocent); + + Main.AllPlayerControls + .Where(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId) + .Do(x => CustomWinnerHolder.WinnerIds.Add(x.PlayerId)); + + DecidedWinner = true; + } + } + + if (role.Is(Team.Impostor) || role.Is(Team.Neutral)) + Stressed.OnNonCrewmateEjected(); + else + Stressed.OnCrewmateEjected(); + + if (role.Is(Team.Impostor)) + Damocles.OnImpostorEjected(); + else + { + Cantankerous.OnCrewmateEjected(); + Mafioso.OnCrewmateEjected(); + Damocles.OnCrewmateEjected(); + } + + switch (role) + { + case CustomRoles.Jester: + if (DecidedWinner) + CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Jester); else + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Jester); + + CustomWinnerHolder.WinnerIds.Add(exiled.PlayerId); + DecidedWinner = true; + break; + case CustomRoles.Terrorist: + Utils.CheckTerroristWin(exiled); + break; + case CustomRoles.Devourer: + Devourer.OnDevourerDied(exiled.PlayerId); + break; + case CustomRoles.Medic: + Medic.IsDead(exiled.Object); + break; + } + + if (Executioner.CheckExileTarget(exiled)) DecidedWinner = true; + + if (Lawyer.CheckExileTarget(exiled /*, DecidedWinner*/)) DecidedWinner = false; + + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Terrorist) + Main.PlayerStates[exiled.PlayerId].SetDead(); + } + + if (AmongUsClient.Instance.AmHost && Main.IsFixedCooldown) Main.RefixCooldownDelay = Options.DefaultKillCooldown - 3f; + + Witch.RemoveSpelledPlayer(); + + NiceSwapper.OnExileFinish(); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.Warlock)) + { + Warlock.CursedPlayers[pc.PlayerId] = null; + Warlock.IsCurseAndKill[pc.PlayerId] = false; + } + + pc.ResetKillCooldown(); + pc.RpcResetAbilityCooldown(); + PetsPatch.RpcRemovePet(pc); + } + + if (Options.RandomSpawn.GetBool()) + { + RandomSpawn.SpawnMap map = Main.NormalOptions.MapId switch + { + 0 => new RandomSpawn.SkeldSpawnMap(), + 1 => new RandomSpawn.MiraHQSpawnMap(), + 2 => new RandomSpawn.PolusSpawnMap(), + 3 => new RandomSpawn.DleksSpawnMap(), + 5 => new RandomSpawn.FungleSpawnMap(), + _ => null + }; + + if (map != null) Main.AllAlivePlayerControls.Do(map.RandomTeleport); + } + + FallFromLadder.Reset(); + Utils.CountAlivePlayers(true); + } + + private static void WrapUpFinalizer() + { + // Even if an exception occurs in WrapUpPostfix, this part will be executed reliably. + if (AmongUsClient.Instance.AmHost) + { + Utils.NotifyRoles(); + + LateTask.New(() => + { + AntiBlackout.SendGameData(); + AntiBlackout.SetRealPlayerRoles(); + }, 0.7f, "Restore IsDead Task"); + + LateTask.New(() => + { + if (!GameStates.IsEnded) + { + AntiBlackout.ResetAfterMeeting(); + + Main.AfterMeetingDeathPlayers.Do(x => { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Innocent); + PlayerControl player = Utils.GetPlayerById(x.Key); + PlayerState state = Main.PlayerStates[x.Key]; + Logger.Info($"{player?.GetNameWithRole().RemoveHtmlTags()} died with {x.Value}", "AfterMeetingDeath"); + state.deathReason = x.Value; + state.SetDead(); + player?.RpcExileV2(); + if (x.Value == PlayerState.DeathReason.Suicide) player?.SetRealKiller(player, true); + Utils.AfterPlayerDeathTasks(player); + }); - Main.AllPlayerControls - .Where(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exiled.PlayerId) - .Do(x => CustomWinnerHolder.WinnerIds.Add(x.PlayerId)); - - DecidedWinner = true; - } + Main.AfterMeetingDeathPlayers.Clear(); + Utils.AfterMeetingTasks(); + Utils.SyncAllSettings(); + Utils.NotifyRoles(NoCache: true); + Utils.CheckAndSetVentInteractions(); } - - if (role.Is(Team.Impostor) || role.Is(Team.Neutral)) - Stressed.OnNonCrewmateEjected(); - else - Stressed.OnCrewmateEjected(); - - if (role.Is(Team.Impostor)) - Damocles.OnImpostorEjected(); - else - { - Cantankerous.OnCrewmateEjected(); - Mafioso.OnCrewmateEjected(); - Damocles.OnCrewmateEjected(); - } - - switch (role) - { - case CustomRoles.Jester: - if (DecidedWinner) - CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Jester); - else - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Jester); - - CustomWinnerHolder.WinnerIds.Add(exiled.PlayerId); - DecidedWinner = true; - break; - case CustomRoles.Terrorist: - Utils.CheckTerroristWin(exiled); - break; - case CustomRoles.Devourer: - Devourer.OnDevourerDied(exiled.PlayerId); - break; - case CustomRoles.Medic: - Medic.IsDead(exiled.Object); - break; - } - - if (Executioner.CheckExileTarget(exiled)) DecidedWinner = true; - - if (Lawyer.CheckExileTarget(exiled /*, DecidedWinner*/)) DecidedWinner = false; - - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Terrorist) - Main.PlayerStates[exiled.PlayerId].SetDead(); - } - - if (AmongUsClient.Instance.AmHost && Main.IsFixedCooldown) Main.RefixCooldownDelay = Options.DefaultKillCooldown - 3f; - - Witch.RemoveSpelledPlayer(); - - NiceSwapper.OnExileFinish(); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.Is(CustomRoles.Warlock)) - { - Warlock.CursedPlayers[pc.PlayerId] = null; - Warlock.IsCurseAndKill[pc.PlayerId] = false; - } - - pc.ResetKillCooldown(); - pc.RpcResetAbilityCooldown(); - PetsPatch.RpcRemovePet(pc); - } - - if (Options.RandomSpawn.GetBool()) - { - RandomSpawn.SpawnMap map = Main.NormalOptions.MapId switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null - }; - - if (map != null) Main.AllAlivePlayerControls.Do(map.RandomTeleport); - } - - FallFromLadder.Reset(); - Utils.CountAlivePlayers(true); + }, 2f, "AntiBlackout Reset & AfterMeetingTasks"); } - private static void WrapUpFinalizer() + GameStates.AlreadyDied |= !Utils.IsAllAlive; + RemoveDisableDevicesPatch.UpdateDisableDevices(); + SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); + Logger.Info("Start task phase", "Phase"); + + if (!AmongUsClient.Instance.AmHost || (Lovers.IsChatActivated && Lovers.PrivateChat.GetBool())) return; + + bool showRemainingKillers = Options.EnableKillerLeftCommand.GetBool() && Options.ShowImpRemainOnEject.GetBool(); + bool appendEjectionNotify = CheckForEndVotingPatch.EjectionText != string.Empty; + Logger.Msg($"Ejection Text: {CheckForEndVotingPatch.EjectionText}", "ExilePatch"); + + if ((showRemainingKillers || appendEjectionNotify) && CustomGameMode.Standard.IsActiveOrIntegrated()) { - // Even if an exception occurs in WrapUpPostfix, this part will be executed reliably. - if (AmongUsClient.Instance.AmHost) + string text = showRemainingKillers ? Utils.GetRemainingKillers(true) : string.Empty; + text = $"<#ffffff>{text}"; + var r = IRandom.Instance; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - Utils.NotifyRoles(); + string finalText = text; - LateTask.New(() => - { - AntiBlackout.SendGameData(); - AntiBlackout.SetRealPlayerRoles(); - }, 0.7f, "Restore IsDead Task"); + if (appendEjectionNotify && !finalText.Contains(CheckForEndVotingPatch.EjectionText, StringComparison.OrdinalIgnoreCase)) + finalText = $"\n<#ffffff>{CheckForEndVotingPatch.EjectionText}\n{finalText}"; - LateTask.New(() => - { - if (!GameStates.IsEnded) - { - AntiBlackout.ResetAfterMeeting(); + if (!showRemainingKillers) finalText = finalText.TrimStart(); - Main.AfterMeetingDeathPlayers.Do(x => - { - PlayerControl player = Utils.GetPlayerById(x.Key); - PlayerState state = Main.PlayerStates[x.Key]; - Logger.Info($"{player?.GetNameWithRole().RemoveHtmlTags()} died with {x.Value}", "AfterMeetingDeath"); - state.deathReason = x.Value; - state.SetDead(); - player?.RpcExileV2(); - if (x.Value == PlayerState.DeathReason.Suicide) player?.SetRealKiller(player, true); - Utils.AfterPlayerDeathTasks(player); - }); - - Main.AfterMeetingDeathPlayers.Clear(); - Utils.AfterMeetingTasks(); - Utils.SyncAllSettings(); - Utils.NotifyRoles(NoCache: true); - Utils.CheckAndSetVentInteractions(); - } - }, 2f, "AntiBlackout Reset & AfterMeetingTasks"); - } - - GameStates.AlreadyDied |= !Utils.IsAllAlive; - RemoveDisableDevicesPatch.UpdateDisableDevices(); - SoundManager.Instance.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); - Logger.Info("Start task phase", "Phase"); - - if (!AmongUsClient.Instance.AmHost || (Lovers.IsChatActivated && Lovers.PrivateChat.GetBool())) return; - - bool showRemainingKillers = Options.EnableKillerLeftCommand.GetBool() && Options.ShowImpRemainOnEject.GetBool(); - bool appendEjectionNotify = CheckForEndVotingPatch.EjectionText != string.Empty; - Logger.Msg($"Ejection Text: {CheckForEndVotingPatch.EjectionText}", "ExilePatch"); - - if ((showRemainingKillers || appendEjectionNotify) && CustomGameMode.Standard.IsActiveOrIntegrated()) - { - string text = showRemainingKillers ? Utils.GetRemainingKillers(true) : string.Empty; - text = $"<#ffffff>{text}"; - var r = IRandom.Instance; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - string finalText = text; - - if (appendEjectionNotify && !finalText.Contains(CheckForEndVotingPatch.EjectionText, StringComparison.OrdinalIgnoreCase)) - finalText = $"\n<#ffffff>{CheckForEndVotingPatch.EjectionText}\n{finalText}"; - - if (!showRemainingKillers) finalText = finalText.TrimStart(); - - pc.Notify(finalText, r.Next(7, 13)); - } - } - - LateTask.New(() => ChatManager.SendPreviousMessagesToAll(true), 3f, log: false); - } - - [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] - private static class BaseExileControllerPatch - { - public static void Postfix(ExileController __instance) - { - try - { - WrapUpPostfix(__instance.initData.networkedPlayer); - } - finally - { - WrapUpFinalizer(); - } + pc.Notify(finalText, r.Next(7, 13)); } } - [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))] - private static class AirshipExileControllerPatch + LateTask.New(() => ChatManager.SendPreviousMessagesToAll(true), 3f, log: false); + } + + [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))] + private static class BaseExileControllerPatch + { + public static void Postfix(ExileController __instance) { - public static void Postfix(AirshipExileController __instance) + try { - try - { - WrapUpPostfix(__instance.initData.networkedPlayer); - } - finally - { - WrapUpFinalizer(); - } + WrapUpPostfix(__instance.initData.networkedPlayer); + } + finally + { + WrapUpFinalizer(); } } } - [HarmonyPatch(typeof(PbExileController), nameof(PbExileController.PlayerSpin))] - internal class PolusExileHatFixPatch + [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))] + private static class AirshipExileControllerPatch { - public static void Prefix(PbExileController __instance) + public static void Postfix(AirshipExileController __instance) { - __instance.Player.cosmetics.hat.transform.localPosition = new(-0.2f, 0.6f, 1.1f); + try + { + WrapUpPostfix(__instance.initData.networkedPlayer); + } + finally + { + WrapUpFinalizer(); + } } } +} + +[HarmonyPatch(typeof(PbExileController), nameof(PbExileController.PlayerSpin))] +internal class PolusExileHatFixPatch +{ + public static void Prefix(PbExileController __instance) + { + __instance.Player.cosmetics.hat.transform.localPosition = new(-0.2f, 0.6f, 1.1f); + } } \ No newline at end of file diff --git a/Patches/GameManagerPatch.cs b/Patches/GameManagerPatch.cs index 92ad5d0e6..e8daaa048 100644 --- a/Patches/GameManagerPatch.cs +++ b/Patches/GameManagerPatch.cs @@ -1,53 +1,52 @@ using HarmonyLib; using Hazel; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(GameManager), nameof(GameManager.Serialize))] +internal class GameManagerSerializeFix { - [HarmonyPatch(typeof(GameManager), nameof(GameManager.Serialize))] - internal class GameManagerSerializeFix + public static bool Prefix(GameManager __instance, [HarmonyArgument(0)] MessageWriter writer, [HarmonyArgument(1)] bool initialState, ref bool __result) { - public static bool Prefix(GameManager __instance, [HarmonyArgument(0)] MessageWriter writer, [HarmonyArgument(1)] bool initialState, ref bool __result) + var flag = false; + + for (var index = 0; index < __instance.LogicComponents.Count; ++index) { - var flag = false; + GameLogicComponent logicComponent = __instance.LogicComponents[index]; - for (var index = 0; index < __instance.LogicComponents.Count; ++index) + if (initialState || logicComponent.IsDirty) { - GameLogicComponent logicComponent = __instance.LogicComponents[index]; + flag = true; + writer.StartMessage((byte)index); + bool hasBody = logicComponent.Serialize(writer, initialState); - if (initialState || logicComponent.IsDirty) - { - flag = true; - writer.StartMessage((byte)index); - bool hasBody = logicComponent.Serialize(writer, initialState); + if (hasBody) + writer.EndMessage(); + else + writer.CancelMessage(); - if (hasBody) - writer.EndMessage(); - else - writer.CancelMessage(); - - logicComponent.ClearDirtyFlag(); - } + logicComponent.ClearDirtyFlag(); } + } - __instance.ClearDirtyBits(); - __result = flag; + __instance.ClearDirtyBits(); + __result = flag; + return false; + } +} + +[HarmonyPatch(typeof(LogicOptions), nameof(LogicOptions.Serialize))] +internal class LogicOptionsSerializePatch +{ + public static bool Prefix(ref bool __result, /*MessageWriter writer,*/ bool initialState) + { + // Block all but the first time and synchronize only with CustomSyncSettings + if (!initialState) + { + __result = false; return false; } - } - [HarmonyPatch(typeof(LogicOptions), nameof(LogicOptions.Serialize))] - internal class LogicOptionsSerializePatch - { - public static bool Prefix(ref bool __result, /*MessageWriter writer,*/ bool initialState) - { - // Block all but the first time and synchronize only with CustomSyncSettings - if (!initialState) - { - __result = false; - return false; - } - - return true; - } + return true; } } \ No newline at end of file diff --git a/Patches/GameOptionsMenuPatch.cs b/Patches/GameOptionsMenuPatch.cs index c247f1bc0..bb93febab 100644 --- a/Patches/GameOptionsMenuPatch.cs +++ b/Patches/GameOptionsMenuPatch.cs @@ -10,1304 +10,1302 @@ using Il2CppSystem.Collections.Generic; using TMPro; using UnityEngine; -namespace EHR -{ - // Credit: https://github.com/Yumenopai/TownOfHost_Y +namespace EHR; +// Credit: https://github.com/Yumenopai/TownOfHost_Y - public static class ModGameOptionsMenu +public static class ModGameOptionsMenu +{ + public static int TabIndex; + public static Dictionary OptionList = new(); + public static Dictionary BehaviourList = new(); + public static Dictionary CategoryHeaderList = new(); +} + +[HarmonyPatch(typeof(GameOptionsMenu))] +public static class GameOptionsMenuPatch +{ + private static GameOptionsMenu Instance; + + [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] + [HarmonyPrefix] + private static bool InitializePrefix(GameOptionsMenu __instance) { - public static int TabIndex; - public static Dictionary OptionList = new(); - public static Dictionary BehaviourList = new(); - public static Dictionary CategoryHeaderList = new(); + Instance ??= __instance; + if (ModGameOptionsMenu.TabIndex < 3) return true; + + if (__instance.Children == null || __instance.Children.Count == 0) + { + __instance.MapPicker.gameObject.SetActive(false); + __instance.Children = new(); + __instance.CreateSettings(); + __instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions; + __instance.InitializeControllerNavigation(); + } + + return false; } - [HarmonyPatch(typeof(GameOptionsMenu))] - public static class GameOptionsMenuPatch + [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] + [HarmonyPostfix] + private static void InitializePostfix() { - private static GameOptionsMenu Instance; + GameObject optionMenu = GameObject.Find("PlayerOptionsMenu(Clone)"); + optionMenu?.transform.FindChild("Background")?.gameObject.SetActive(false); - [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] - [HarmonyPrefix] - private static bool InitializePrefix(GameOptionsMenu __instance) + // By TommyXL + LateTask.New(() => { - Instance ??= __instance; - if (ModGameOptionsMenu.TabIndex < 3) return true; + Transform menuDescription = optionMenu?.transform.FindChild("What Is This?"); + if (menuDescription == null) return; - if (__instance.Children == null || __instance.Children.Count == 0) + Transform infoImage = menuDescription.transform.FindChild("InfoImage"); + + if (infoImage != null) { - __instance.MapPicker.gameObject.SetActive(false); - __instance.Children = new(); - __instance.CreateSettings(); - __instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions; - __instance.InitializeControllerNavigation(); + infoImage.transform.localPosition = new(-4.65f, 0.16f, -1f); + infoImage.transform.localScale = new(0.2202f, 0.2202f, 0.3202f); } - return false; - } + Transform infoText = menuDescription.transform.FindChild("InfoText"); - [HarmonyPatch(nameof(GameOptionsMenu.Initialize))] - [HarmonyPostfix] - private static void InitializePostfix() + if (infoText != null) + { + infoText.transform.localPosition = new(-3.5f, 0.83f, -2f); + infoText.transform.localScale = new(1f, 1f, 1f); + } + + Transform cubeObject = menuDescription.transform.FindChild("Cube"); + + if (cubeObject != null) + { + cubeObject.transform.localPosition = new(-3.2f, 0.55f, -0.1f); + cubeObject.transform.localScale = new(0.61f, 0.64f, 1f); + } + + GameSettingMenu.Instance.MenuDescriptionText.m_marginWidth = 2.5f; + }, 0.2f, log: false); + } + + [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] + [HarmonyPrefix] + private static bool CreateSettingsPrefix(GameOptionsMenu __instance) + { + if (ModGameOptionsMenu.TabIndex < 3) return true; + + __instance.scrollBar.SetYBoundsMax(CalculateScrollBarYBoundsMax()); + __instance.StartCoroutine(CoRoutine().WrapToIl2Cpp()); + return false; + + IEnumerator CoRoutine() { - GameObject optionMenu = GameObject.Find("PlayerOptionsMenu(Clone)"); - optionMenu?.transform.FindChild("Background")?.gameObject.SetActive(false); - - // By TommyXL - LateTask.New(() => - { - Transform menuDescription = optionMenu?.transform.FindChild("What Is This?"); - if (menuDescription == null) return; - - Transform infoImage = menuDescription.transform.FindChild("InfoImage"); - - if (infoImage != null) - { - infoImage.transform.localPosition = new(-4.65f, 0.16f, -1f); - infoImage.transform.localScale = new(0.2202f, 0.2202f, 0.3202f); - } - - Transform infoText = menuDescription.transform.FindChild("InfoText"); - - if (infoText != null) - { - infoText.transform.localPosition = new(-3.5f, 0.83f, -2f); - infoText.transform.localScale = new(1f, 1f, 1f); - } - - Transform cubeObject = menuDescription.transform.FindChild("Cube"); - - if (cubeObject != null) - { - cubeObject.transform.localPosition = new(-3.2f, 0.55f, -0.1f); - cubeObject.transform.localScale = new(0.61f, 0.64f, 1f); - } - - GameSettingMenu.Instance.MenuDescriptionText.m_marginWidth = 2.5f; - }, 0.2f, log: false); - } - - [HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))] - [HarmonyPrefix] - private static bool CreateSettingsPrefix(GameOptionsMenu __instance) - { - if (ModGameOptionsMenu.TabIndex < 3) return true; - - __instance.scrollBar.SetYBoundsMax(CalculateScrollBarYBoundsMax()); - __instance.StartCoroutine(CoRoutine().WrapToIl2Cpp()); - return false; - - IEnumerator CoRoutine() - { - var modTab = (TabGroup)(ModGameOptionsMenu.TabIndex - 3); - - var num = 2.0f; - const float posX = 0.952f; - const float posZ = -2.0f; - - for (var index = 0; index < OptionItem.AllOptions.Count; index++) - { - OptionItem option = OptionItem.AllOptions[index]; - if (option.Tab != modTab) continue; - - bool enabled = !option.IsCurrentlyHidden() && (option.Parent == null || (!option.Parent.IsCurrentlyHidden() && option.Parent.GetBool())) && (option.Parent?.Parent == null || (!option.Parent.Parent.IsCurrentlyHidden() && option.Parent.Parent.GetBool())) && (option.Parent?.Parent?.Parent == null || (!option.Parent.Parent.Parent.IsCurrentlyHidden() && option.Parent.Parent.Parent.GetBool())); - - if (option is TextOptionItem) - { - CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); - categoryHeaderMasked.SetHeader(StringNames.RolesCategory, 20); - categoryHeaderMasked.Title.text = option.GetName(); - categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; - categoryHeaderMasked.transform.localPosition = new(-0.903f, num, posZ); - var chmText = categoryHeaderMasked.transform.FindChild("HeaderText").GetComponent(); - chmText.fontStyle = FontStyles.Bold; - chmText.outlineWidth = 0.17f; - categoryHeaderMasked.gameObject.SetActive(enabled); - ModGameOptionsMenu.CategoryHeaderList.TryAdd(index, categoryHeaderMasked); - - if (enabled) num -= 0.63f; - } - else if (option.IsHeader && enabled) num -= 0.25f; - - if (option is TextOptionItem) continue; - - BaseGameSetting baseGameSetting = GetSetting(option); - if (baseGameSetting == null) continue; - - - OptionBehaviour optionBehaviour; - - switch (baseGameSetting.Type) - { - case OptionTypes.Checkbox: - { - optionBehaviour = Object.Instantiate(__instance.checkboxOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); - optionBehaviour.transform.localPosition = new(posX, num, posZ); - - OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type); - - break; - } - case OptionTypes.String: - { - optionBehaviour = Object.Instantiate(__instance.stringOptionOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); - optionBehaviour.transform.localPosition = new(posX, num, posZ); - - OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type); - - if (option.Name == "GameMode" && !ModGameOptionsMenu.OptionList.ContainsValue(index)) - GameSettingMenuPatch.GameModeBehaviour = (StringOption)optionBehaviour; - - break; - } - case OptionTypes.Float: - case OptionTypes.Int: - { - optionBehaviour = Object.Instantiate(__instance.numberOptionOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); - optionBehaviour.transform.localPosition = new(posX, num, posZ); - - OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type); - - if (option.Name == "Preset" && !ModGameOptionsMenu.OptionList.ContainsValue(index)) - GameSettingMenuPatch.PresetBehaviour = (NumberOption)optionBehaviour; - - break; - } - default: - continue; - } - - optionBehaviour.SetClickMask(__instance.ButtonClickMask); - optionBehaviour.SetUpFromData(baseGameSetting, 20); - ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index); - - optionBehaviour.transform.localPosition = new(0.952f, num, -2f); - optionBehaviour.SetClickMask(__instance.ButtonClickMask); - optionBehaviour.SetUpFromData(baseGameSetting, 20); - ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index); - ModGameOptionsMenu.BehaviourList.TryAdd(index, optionBehaviour); - optionBehaviour.gameObject.SetActive(enabled); - optionBehaviour.OnValueChanged = new Action(__instance.ValueChanged); - __instance.Children.Add(optionBehaviour); - - option.OptionBehaviour = optionBehaviour; - - if (enabled) num -= 0.45f; - - if (index % 50 == 0) yield return null; - } - - yield return null; - - __instance.ControllerSelectable.Clear(); - foreach (UiElement x in __instance.scrollBar.GetComponentsInChildren()) __instance.ControllerSelectable.Add(x); - } - - float CalculateScrollBarYBoundsMax() - { - var num = 2.0f; - - foreach (OptionItem option in OptionItem.AllOptions) - { - if (option.Tab != (TabGroup)(ModGameOptionsMenu.TabIndex - 3)) continue; - - bool enabled = !option.IsCurrentlyHidden() && (option.Parent == null || (!option.Parent.IsCurrentlyHidden() && option.Parent.GetBool())); - - if (option is TextOptionItem) - num -= 0.63f; - else if (enabled) - { - if (option.IsHeader) num -= 0.25f; - - num -= 0.45f; - } - } - - return -num - 1.65f; - } - } - - private static void OptionBehaviourSetSizeAndPosition(OptionBehaviour optionBehaviour, OptionItem option, OptionTypes type) - { - Vector3 positionOffset = new(0f, 0f, 0f); - Vector3 scaleOffset = new(0f, 0f, 0f); - Color color = new(0.35f, 0.35f, 0.35f); - var sizeDelta_x = 5.7f; - - if (option.Parent?.Parent?.Parent != null) - { - scaleOffset = new(-0.18f, 0, 0); - positionOffset = new(0.3f, 0f, 0f); - color = new(0.35f, 0f, 0f); - sizeDelta_x = 5.1f; - } - else if (option.Parent?.Parent != null) - { - scaleOffset = new(-0.12f, 0, 0); - positionOffset = new(0.2f, 0f, 0f); - color = new(0.35f, 0.35f, 0f); - sizeDelta_x = 5.3f; - } - else if (option.Parent != null) - { - scaleOffset = new(-0.05f, 0, 0); - positionOffset = new(0.1f, 0f, 0f); - color = new(0f, 0f, 0.35f); - sizeDelta_x = 5.5f; - } - - Transform labelBackground = optionBehaviour.transform.FindChild("LabelBackground"); - labelBackground.GetComponent().color = color; - labelBackground.localScale += new Vector3(0.9f, -0.2f, 0f) + scaleOffset; - labelBackground.localPosition += new Vector3(-0.4f, 0f, 0f) + positionOffset; - - Transform titleText = optionBehaviour.transform.FindChild("Title Text"); - titleText.localPosition += new Vector3(-0.4f, 0f, 0f) + positionOffset; - titleText.GetComponent().sizeDelta = new(sizeDelta_x, 0.37f); - var textMeshPro = titleText.GetComponent(); - textMeshPro.alignment = TextAlignmentOptions.MidlineLeft; - textMeshPro.fontStyle = FontStyles.Bold; - textMeshPro.outlineWidth = 0.17f; - - switch (type) - { - case OptionTypes.Checkbox: - optionBehaviour.transform.FindChild("Toggle").localPosition = new(1.46f, -0.042f); - break; - - case OptionTypes.String: - Transform plusButton = optionBehaviour.transform.FindChild("PlusButton"); - Transform minusButton = optionBehaviour.transform.FindChild("MinusButton"); - plusButton.localPosition += new Vector3(option.IsText ? 500f : 1.7f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); - minusButton.localPosition += new Vector3(option.IsText ? 500f : 0.9f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); - Transform valueTMP = optionBehaviour.transform.FindChild("Value_TMP (1)"); - valueTMP.localPosition += new Vector3(1.3f, 0f, 0f); - valueTMP.GetComponent().sizeDelta = new(2.3f, 0.4f); - goto default; - - case OptionTypes.Float: - case OptionTypes.Int: - optionBehaviour.transform.FindChild("PlusButton").localPosition += new Vector3(option.IsText ? 500f : 1.7f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); - optionBehaviour.transform.FindChild("MinusButton").localPosition += new Vector3(option.IsText ? 500f : 0.9f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); - optionBehaviour.transform.FindChild("Value_TMP").localPosition += new Vector3(1.3f, 0f, 0f); - goto default; - - default: - Transform valueBox = optionBehaviour.transform.FindChild("ValueBox"); - valueBox.localScale += new Vector3(0.2f, 0f, 0f); - valueBox.localPosition += new Vector3(1.3f, 0f, 0f); - break; - } - } - - [HarmonyPatch(nameof(GameOptionsMenu.ValueChanged))] - [HarmonyPrefix] - private static bool ValueChangedPrefix(GameOptionsMenu __instance, OptionBehaviour option) - { - if (ModGameOptionsMenu.TabIndex < 3) return true; - - if (ModGameOptionsMenu.OptionList.TryGetValue(option, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - if (item != null && item.Children.Count > 0) ReCreateSettings(__instance); - } - - return false; - } - - public static void ReCreateSettings(GameOptionsMenu __instance) - { - if (ModGameOptionsMenu.TabIndex < 3) return; - var modTab = (TabGroup)(ModGameOptionsMenu.TabIndex - 3); var num = 2.0f; + const float posX = 0.952f; + const float posZ = -2.0f; for (var index = 0; index < OptionItem.AllOptions.Count; index++) { OptionItem option = OptionItem.AllOptions[index]; if (option.Tab != modTab) continue; - bool enabled = !option.IsCurrentlyHidden() && (option.Parent == null || (!option.Parent.IsCurrentlyHidden() && option.Parent.GetBool())); + bool enabled = !option.IsCurrentlyHidden() && (option.Parent == null || (!option.Parent.IsCurrentlyHidden() && option.Parent.GetBool())) && (option.Parent?.Parent == null || (!option.Parent.Parent.IsCurrentlyHidden() && option.Parent.Parent.GetBool())) && (option.Parent?.Parent?.Parent == null || (!option.Parent.Parent.Parent.IsCurrentlyHidden() && option.Parent.Parent.Parent.GetBool())); - if (ModGameOptionsMenu.CategoryHeaderList.TryGetValue(index, out CategoryHeaderMasked categoryHeaderMasked)) + if (option is TextOptionItem) { - categoryHeaderMasked.transform.localPosition = new(-0.903f, num, -2f); + CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + categoryHeaderMasked.SetHeader(StringNames.RolesCategory, 20); + categoryHeaderMasked.Title.text = option.GetName(); + categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; + categoryHeaderMasked.transform.localPosition = new(-0.903f, num, posZ); + var chmText = categoryHeaderMasked.transform.FindChild("HeaderText").GetComponent(); + chmText.fontStyle = FontStyles.Bold; + chmText.outlineWidth = 0.17f; categoryHeaderMasked.gameObject.SetActive(enabled); + ModGameOptionsMenu.CategoryHeaderList.TryAdd(index, categoryHeaderMasked); + if (enabled) num -= 0.63f; } else if (option.IsHeader && enabled) num -= 0.25f; - if (ModGameOptionsMenu.BehaviourList.TryGetValue(index, out OptionBehaviour optionBehaviour)) + if (option is TextOptionItem) continue; + + BaseGameSetting baseGameSetting = GetSetting(option); + if (baseGameSetting == null) continue; + + + OptionBehaviour optionBehaviour; + + switch (baseGameSetting.Type) { - optionBehaviour.transform.localPosition = new(0.952f, num, -2f); - optionBehaviour.gameObject.SetActive(enabled); - if (enabled) num -= 0.45f; + case OptionTypes.Checkbox: + { + optionBehaviour = Object.Instantiate(__instance.checkboxOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + optionBehaviour.transform.localPosition = new(posX, num, posZ); + + OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type); + + break; + } + case OptionTypes.String: + { + optionBehaviour = Object.Instantiate(__instance.stringOptionOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + optionBehaviour.transform.localPosition = new(posX, num, posZ); + + OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type); + + if (option.Name == "GameMode" && !ModGameOptionsMenu.OptionList.ContainsValue(index)) + GameSettingMenuPatch.GameModeBehaviour = (StringOption)optionBehaviour; + + break; + } + case OptionTypes.Float: + case OptionTypes.Int: + { + optionBehaviour = Object.Instantiate(__instance.numberOptionOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + optionBehaviour.transform.localPosition = new(posX, num, posZ); + + OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type); + + if (option.Name == "Preset" && !ModGameOptionsMenu.OptionList.ContainsValue(index)) + GameSettingMenuPatch.PresetBehaviour = (NumberOption)optionBehaviour; + + break; + } + default: + continue; } + + optionBehaviour.SetClickMask(__instance.ButtonClickMask); + optionBehaviour.SetUpFromData(baseGameSetting, 20); + ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index); + + optionBehaviour.transform.localPosition = new(0.952f, num, -2f); + optionBehaviour.SetClickMask(__instance.ButtonClickMask); + optionBehaviour.SetUpFromData(baseGameSetting, 20); + ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index); + ModGameOptionsMenu.BehaviourList.TryAdd(index, optionBehaviour); + optionBehaviour.gameObject.SetActive(enabled); + optionBehaviour.OnValueChanged = new Action(__instance.ValueChanged); + __instance.Children.Add(optionBehaviour); + + option.OptionBehaviour = optionBehaviour; + + if (enabled) num -= 0.45f; + + if (index % 50 == 0) yield return null; } + yield return null; + __instance.ControllerSelectable.Clear(); foreach (UiElement x in __instance.scrollBar.GetComponentsInChildren()) __instance.ControllerSelectable.Add(x); - - __instance.scrollBar.SetYBoundsMax(-num - 1.65f); } - private static BaseGameSetting GetSetting(OptionItem item) + float CalculateScrollBarYBoundsMax() { - BaseGameSetting baseGameSetting; + var num = 2.0f; + + foreach (OptionItem option in OptionItem.AllOptions) + { + if (option.Tab != (TabGroup)(ModGameOptionsMenu.TabIndex - 3)) continue; + + bool enabled = !option.IsCurrentlyHidden() && (option.Parent == null || (!option.Parent.IsCurrentlyHidden() && option.Parent.GetBool())); + + if (option is TextOptionItem) + num -= 0.63f; + else if (enabled) + { + if (option.IsHeader) num -= 0.25f; + + num -= 0.45f; + } + } + + return -num - 1.65f; + } + } + + private static void OptionBehaviourSetSizeAndPosition(OptionBehaviour optionBehaviour, OptionItem option, OptionTypes type) + { + Vector3 positionOffset = new(0f, 0f, 0f); + Vector3 scaleOffset = new(0f, 0f, 0f); + Color color = new(0.35f, 0.35f, 0.35f); + var sizeDelta_x = 5.7f; + + if (option.Parent?.Parent?.Parent != null) + { + scaleOffset = new(-0.18f, 0, 0); + positionOffset = new(0.3f, 0f, 0f); + color = new(0.35f, 0f, 0f); + sizeDelta_x = 5.1f; + } + else if (option.Parent?.Parent != null) + { + scaleOffset = new(-0.12f, 0, 0); + positionOffset = new(0.2f, 0f, 0f); + color = new(0.35f, 0.35f, 0f); + sizeDelta_x = 5.3f; + } + else if (option.Parent != null) + { + scaleOffset = new(-0.05f, 0, 0); + positionOffset = new(0.1f, 0f, 0f); + color = new(0f, 0f, 0.35f); + sizeDelta_x = 5.5f; + } + + Transform labelBackground = optionBehaviour.transform.FindChild("LabelBackground"); + labelBackground.GetComponent().color = color; + labelBackground.localScale += new Vector3(0.9f, -0.2f, 0f) + scaleOffset; + labelBackground.localPosition += new Vector3(-0.4f, 0f, 0f) + positionOffset; + + Transform titleText = optionBehaviour.transform.FindChild("Title Text"); + titleText.localPosition += new Vector3(-0.4f, 0f, 0f) + positionOffset; + titleText.GetComponent().sizeDelta = new(sizeDelta_x, 0.37f); + var textMeshPro = titleText.GetComponent(); + textMeshPro.alignment = TextAlignmentOptions.MidlineLeft; + textMeshPro.fontStyle = FontStyles.Bold; + textMeshPro.outlineWidth = 0.17f; + + switch (type) + { + case OptionTypes.Checkbox: + optionBehaviour.transform.FindChild("Toggle").localPosition = new(1.46f, -0.042f); + break; + + case OptionTypes.String: + Transform plusButton = optionBehaviour.transform.FindChild("PlusButton"); + Transform minusButton = optionBehaviour.transform.FindChild("MinusButton"); + plusButton.localPosition += new Vector3(option.IsText ? 500f : 1.7f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); + minusButton.localPosition += new Vector3(option.IsText ? 500f : 0.9f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); + Transform valueTMP = optionBehaviour.transform.FindChild("Value_TMP (1)"); + valueTMP.localPosition += new Vector3(1.3f, 0f, 0f); + valueTMP.GetComponent().sizeDelta = new(2.3f, 0.4f); + goto default; + + case OptionTypes.Float: + case OptionTypes.Int: + optionBehaviour.transform.FindChild("PlusButton").localPosition += new Vector3(option.IsText ? 500f : 1.7f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); + optionBehaviour.transform.FindChild("MinusButton").localPosition += new Vector3(option.IsText ? 500f : 0.9f, option.IsText ? 500f : 0f, option.IsText ? 500f : 0f); + optionBehaviour.transform.FindChild("Value_TMP").localPosition += new Vector3(1.3f, 0f, 0f); + goto default; + + default: + Transform valueBox = optionBehaviour.transform.FindChild("ValueBox"); + valueBox.localScale += new Vector3(0.2f, 0f, 0f); + valueBox.localPosition += new Vector3(1.3f, 0f, 0f); + break; + } + } + + [HarmonyPatch(nameof(GameOptionsMenu.ValueChanged))] + [HarmonyPrefix] + private static bool ValueChangedPrefix(GameOptionsMenu __instance, OptionBehaviour option) + { + if (ModGameOptionsMenu.TabIndex < 3) return true; + + if (ModGameOptionsMenu.OptionList.TryGetValue(option, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; + if (item != null && item.Children.Count > 0) ReCreateSettings(__instance); + } + + return false; + } + + public static void ReCreateSettings(GameOptionsMenu __instance) + { + if (ModGameOptionsMenu.TabIndex < 3) return; + + var modTab = (TabGroup)(ModGameOptionsMenu.TabIndex - 3); + + var num = 2.0f; + + for (var index = 0; index < OptionItem.AllOptions.Count; index++) + { + OptionItem option = OptionItem.AllOptions[index]; + if (option.Tab != modTab) continue; + + bool enabled = !option.IsCurrentlyHidden() && (option.Parent == null || (!option.Parent.IsCurrentlyHidden() && option.Parent.GetBool())); + + if (ModGameOptionsMenu.CategoryHeaderList.TryGetValue(index, out CategoryHeaderMasked categoryHeaderMasked)) + { + categoryHeaderMasked.transform.localPosition = new(-0.903f, num, -2f); + categoryHeaderMasked.gameObject.SetActive(enabled); + if (enabled) num -= 0.63f; + } + else if (option.IsHeader && enabled) num -= 0.25f; + + if (ModGameOptionsMenu.BehaviourList.TryGetValue(index, out OptionBehaviour optionBehaviour)) + { + optionBehaviour.transform.localPosition = new(0.952f, num, -2f); + optionBehaviour.gameObject.SetActive(enabled); + if (enabled) num -= 0.45f; + } + } + + __instance.ControllerSelectable.Clear(); + foreach (UiElement x in __instance.scrollBar.GetComponentsInChildren()) __instance.ControllerSelectable.Add(x); + + __instance.scrollBar.SetYBoundsMax(-num - 1.65f); + } + + private static BaseGameSetting GetSetting(OptionItem item) + { + BaseGameSetting baseGameSetting; + + switch (item) + { + case BooleanOptionItem: + var checkboxGameSetting = ScriptableObject.CreateInstance(); + checkboxGameSetting.Type = OptionTypes.Checkbox; + baseGameSetting = checkboxGameSetting; + break; + case IntegerOptionItem integerOptionItem: + var intGameSetting = ScriptableObject.CreateInstance(); + intGameSetting.Type = OptionTypes.Int; + intGameSetting.Value = integerOptionItem.GetInt(); + intGameSetting.Increment = integerOptionItem.Rule.Step; + intGameSetting.ValidRange = new(integerOptionItem.Rule.MinValue, integerOptionItem.Rule.MaxValue); + intGameSetting.ZeroIsInfinity = false; + intGameSetting.SuffixType = NumberSuffixes.Multiplier; + intGameSetting.FormatString = string.Empty; + baseGameSetting = intGameSetting; + break; + case FloatOptionItem floatOptionItem: + var floatGameSetting = ScriptableObject.CreateInstance(); + floatGameSetting.Type = OptionTypes.Float; + floatGameSetting.Value = floatOptionItem.GetFloat(); + floatGameSetting.Increment = floatOptionItem.Rule.Step; + floatGameSetting.ValidRange = new(floatOptionItem.Rule.MinValue, floatOptionItem.Rule.MaxValue); + floatGameSetting.ZeroIsInfinity = false; + floatGameSetting.SuffixType = NumberSuffixes.Multiplier; + floatGameSetting.FormatString = string.Empty; + baseGameSetting = floatGameSetting; + break; + case StringOptionItem stringOptionItem: + var stringGameSetting = ScriptableObject.CreateInstance(); + stringGameSetting.Type = OptionTypes.String; + stringGameSetting.Values = new StringNames[stringOptionItem.Selections.Count]; + stringGameSetting.Index = stringOptionItem.GetInt(); + baseGameSetting = stringGameSetting; + break; + case PresetOptionItem presetOptionItem: + var presetIntGameSetting = ScriptableObject.CreateInstance(); + presetIntGameSetting.Type = OptionTypes.Int; + presetIntGameSetting.Value = presetOptionItem.GetInt(); + presetIntGameSetting.Increment = presetOptionItem.Rule.Step; + presetIntGameSetting.ValidRange = new(presetOptionItem.Rule.MinValue, presetOptionItem.Rule.MaxValue); + presetIntGameSetting.ZeroIsInfinity = false; + presetIntGameSetting.SuffixType = NumberSuffixes.Multiplier; + presetIntGameSetting.FormatString = string.Empty; + baseGameSetting = presetIntGameSetting; + break; + default: + baseGameSetting = null; + break; + } + + if (baseGameSetting != null) baseGameSetting.Title = StringNames.Accept; + + return baseGameSetting; + } + + public static void ReloadUI(int index) + { + GameSettingMenu.Instance?.Close(); + + LateTask.New(() => + { + if (GameStates.IsLobby) GameObject.Find("Host Buttons")?.transform.FindChild("Edit")?.GetComponent()?.ReceiveClickDown(); + }, 0.1f, log: false); + + LateTask.New(() => + { + if (GameStates.IsLobby) + { + GameSettingMenu.Instance?.ChangeTab(index, Controller.currentTouchType == Controller.TouchType.Joystick); + ModGameOptionsMenu.TabIndex = index; + } + }, 0.38f, log: false); + } +} + +[HarmonyPatch(typeof(ToggleOption))] +public static class ToggleOptionPatch +{ + [HarmonyPatch(nameof(ToggleOption.Initialize))] + [HarmonyPrefix] + private static bool InitializePrefix(ToggleOption __instance) + { + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; + __instance.TitleText.text = item.GetName(); + __instance.CheckMark.enabled = item.GetBool(); + item.OptionBehaviour = __instance; + return false; + } + + return true; + } + + [HarmonyPatch(nameof(ToggleOption.UpdateValue))] + [HarmonyPrefix] + private static bool UpdateValuePrefix(ToggleOption __instance) + { + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; + item.SetValue(__instance.GetBool() ? 1 : 0); + NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); + return false; + } + + return true; + } +} + +[HarmonyPatch(typeof(NumberOption))] +public static class NumberOptionPatch +{ + private static int IncrementMultiplier + { + get + { + if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) return 5; + + if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) return 10; + + return 1; + } + } + + [HarmonyPatch(nameof(NumberOption.Initialize))] + [HarmonyPrefix] + private static bool InitializePrefix(NumberOption __instance) + { + switch (__instance.Title) + { + case StringNames.GameVotingTime: + __instance.ValidRange = new(0, 600); + __instance.Value = (float)Math.Round(__instance.Value, 2); + break; + case StringNames.GameShortTasks: + case StringNames.GameLongTasks: + case StringNames.GameCommonTasks: + __instance.ValidRange = new(0, 90); + __instance.Value = (float)Math.Round(__instance.Value, 2); + break; + case StringNames.GameKillCooldown: + __instance.ValidRange = new(0, 180); + __instance.Increment = 0.5f; + __instance.Value = (float)Math.Round(__instance.Value, 2); + break; + case StringNames.GamePlayerSpeed: + case StringNames.GameCrewLight: + case StringNames.GameImpostorLight: + __instance.Increment = 0.05f; + __instance.Value = (float)Math.Round(__instance.Value, 2); + break; + case StringNames.GameNumImpostors: + __instance.ValidRange = new(0, Crowded.MaxImpostors); + __instance.Value = (float)Math.Round(__instance.Value, 2); + if (DebugModeManager.AmDebugger) __instance.ValidRange.min = 0; + break; + } + + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; + __instance.TitleText.text = item.GetName(); + item.OptionBehaviour = __instance; + return false; + } + + return true; + } + + [HarmonyPatch(nameof(NumberOption.UpdateValue))] + [HarmonyPrefix] + private static bool UpdateValuePrefix(NumberOption __instance) + { + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; switch (item) { - case BooleanOptionItem: - var checkboxGameSetting = ScriptableObject.CreateInstance(); - checkboxGameSetting.Type = OptionTypes.Checkbox; - baseGameSetting = checkboxGameSetting; - break; case IntegerOptionItem integerOptionItem: - var intGameSetting = ScriptableObject.CreateInstance(); - intGameSetting.Type = OptionTypes.Int; - intGameSetting.Value = integerOptionItem.GetInt(); - intGameSetting.Increment = integerOptionItem.Rule.Step; - intGameSetting.ValidRange = new(integerOptionItem.Rule.MinValue, integerOptionItem.Rule.MaxValue); - intGameSetting.ZeroIsInfinity = false; - intGameSetting.SuffixType = NumberSuffixes.Multiplier; - intGameSetting.FormatString = string.Empty; - baseGameSetting = intGameSetting; + integerOptionItem.SetValue(integerOptionItem.Rule.GetNearestIndex(__instance.GetInt())); break; case FloatOptionItem floatOptionItem: - var floatGameSetting = ScriptableObject.CreateInstance(); - floatGameSetting.Type = OptionTypes.Float; - floatGameSetting.Value = floatOptionItem.GetFloat(); - floatGameSetting.Increment = floatOptionItem.Rule.Step; - floatGameSetting.ValidRange = new(floatOptionItem.Rule.MinValue, floatOptionItem.Rule.MaxValue); - floatGameSetting.ZeroIsInfinity = false; - floatGameSetting.SuffixType = NumberSuffixes.Multiplier; - floatGameSetting.FormatString = string.Empty; - baseGameSetting = floatGameSetting; - break; - case StringOptionItem stringOptionItem: - var stringGameSetting = ScriptableObject.CreateInstance(); - stringGameSetting.Type = OptionTypes.String; - stringGameSetting.Values = new StringNames[stringOptionItem.Selections.Count]; - stringGameSetting.Index = stringOptionItem.GetInt(); - baseGameSetting = stringGameSetting; + floatOptionItem.SetValue(floatOptionItem.Rule.GetNearestIndex(__instance.GetFloat())); break; case PresetOptionItem presetOptionItem: - var presetIntGameSetting = ScriptableObject.CreateInstance(); - presetIntGameSetting.Type = OptionTypes.Int; - presetIntGameSetting.Value = presetOptionItem.GetInt(); - presetIntGameSetting.Increment = presetOptionItem.Rule.Step; - presetIntGameSetting.ValidRange = new(presetOptionItem.Rule.MinValue, presetOptionItem.Rule.MaxValue); - presetIntGameSetting.ZeroIsInfinity = false; - presetIntGameSetting.SuffixType = NumberSuffixes.Multiplier; - presetIntGameSetting.FormatString = string.Empty; - baseGameSetting = presetIntGameSetting; - break; - default: - baseGameSetting = null; + presetOptionItem.SetValue(presetOptionItem.Rule.GetNearestIndex(__instance.GetInt())); + GameOptionsMenuPatch.ReloadUI(ModGameOptionsMenu.TabIndex); break; } - if (baseGameSetting != null) baseGameSetting.Title = StringNames.Accept; - - return baseGameSetting; + NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); + return false; } - public static void ReloadUI(int index) + return true; + } + + [HarmonyPatch(nameof(NumberOption.FixedUpdate))] + [HarmonyPrefix] + private static bool FixedUpdatePrefix(NumberOption __instance) + { + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) { - GameSettingMenu.Instance?.Close(); + __instance.MinusBtn.SetInteractable(true); + __instance.PlusBtn.SetInteractable(true); - LateTask.New(() => + if (!Mathf.Approximately(__instance.oldValue, __instance.Value)) { - if (GameStates.IsLobby) GameObject.Find("Host Buttons")?.transform.FindChild("Edit")?.GetComponent()?.ReceiveClickDown(); - }, 0.1f, log: false); + __instance.oldValue = __instance.Value; + __instance.ValueText.text = GetValueString(__instance, __instance.Value, OptionItem.AllOptions[index]); + } - LateTask.New(() => + return false; + } + + return true; + } + + private static string GetValueString(NumberOption __instance, float value, OptionItem item) + { + if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f) return "∞"; + + return item == null ? value.ToString(__instance.FormatString) : item.GetString(); + } + + [HarmonyPatch(nameof(NumberOption.Increase))] + [HarmonyPrefix] + public static bool IncreasePrefix(NumberOption __instance) + { + if (Mathf.Approximately(__instance.Value, __instance.ValidRange.max)) + { + __instance.Value = __instance.ValidRange.min; + __instance.UpdateValue(); + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + float increment = IncrementMultiplier * __instance.Increment; + + if (__instance.Value + increment < __instance.ValidRange.max) + { + __instance.Value += increment; + __instance.UpdateValue(); + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + return true; + } + + [HarmonyPatch(nameof(NumberOption.Decrease))] + [HarmonyPrefix] + public static bool DecreasePrefix(NumberOption __instance) + { + if (Mathf.Approximately(__instance.Value, __instance.ValidRange.min)) + { + __instance.Value = __instance.ValidRange.max; + __instance.UpdateValue(); + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + float increment = IncrementMultiplier * __instance.Increment; + + if (__instance.Value - increment > __instance.ValidRange.min) + { + __instance.Value -= increment; + __instance.UpdateValue(); + __instance.OnValueChanged.Invoke(__instance); + return false; + } + + return true; + } +} + +[HarmonyPatch(typeof(StringOption))] +public static class StringOptionPatch +{ + [HarmonyPatch(nameof(StringOption.Initialize))] + [HarmonyPrefix] + private static bool InitializePrefix(StringOption __instance) + { + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; + string name = item.GetName(); + item.OptionBehaviour = __instance; + string name1 = name; + + if (Enum.GetValues().FindFirst(x => Translator.GetString($"{x}") == name1.RemoveHtmlTags(), out CustomRoles role)) { - if (GameStates.IsLobby) + if (role.ToString().Contains("GuardianAngel")) role = CustomRoles.GA; + + name = name.RemoveHtmlTags(); + if (Options.UsePets.GetBool() && role.PetActivatedAbility()) name += Translator.GetString("SupportsPetIndicator"); + + if (!Options.UsePets.GetBool() && role.OnlySpawnsWithPets()) name += Translator.GetString("RequiresPetIndicator"); + + if (role.IsGhostRole()) name += GetGhostRoleTeam(role); + + __instance.TitleText.fontWeight = FontWeight.Black; + __instance.TitleText.outlineColor = new(255, 255, 255, 255); + __instance.TitleText.outlineWidth = 0.04f; + __instance.LabelBackground.color = Utils.GetRoleColor(role); + __instance.TitleText.color = Color.white; + name = $"{name}"; + SetupHelpIcon(role, __instance); + } + + __instance.TitleText.text = name; + return false; + } + + return true; + } + + private static void SetupHelpIcon(CustomRoles role, StringOption option) + { + Transform template = option.transform.FindChild("MinusButton"); + Transform icon = Object.Instantiate(template, template.parent, true); + icon.name = $"{role}HelpIcon"; + var text = icon.GetComponentInChildren(); + text.text = "?"; + text.color = Color.white; + icon.FindChild("ButtonSprite").GetComponent().color = Color.black; + var gameOptionButton = icon.GetComponent(); + gameOptionButton.OnClick = new(); + + gameOptionButton.OnClick.AddListener((Action)(() => + { + if (ModGameOptionsMenu.OptionList.TryGetValue(option, out int index)) + { + OptionItem item = OptionItem.AllOptions[index]; + string name = item.GetName(); + + if (Enum.GetValues().FindFirst(x => Translator.GetString($"{x}") == name.RemoveHtmlTags(), out CustomRoles value)) { - GameSettingMenu.Instance?.ChangeTab(index, Controller.currentTouchType == Controller.TouchType.Joystick); - ModGameOptionsMenu.TabIndex = index; + string roleName = value.IsVanilla() ? value + "EHR" : value.ToString(); + string str = Translator.GetString($"{roleName}InfoLong"); + string infoLong; + + try + { + infoLong = HnSManager.AllHnSRoles.Contains(value) ? str : str[(str.IndexOf('\n') + 1)..str.Split("\n\n")[0].Length]; + } + catch + { + infoLong = str; + } + + var info = $"{value.ToColoredString()}: {infoLong}"; + GameSettingMenu.Instance.MenuDescriptionText.text = info; } - }, 0.38f, log: false); + } + })); + + gameOptionButton.interactableColor = Color.black; + gameOptionButton.interactableHoveredColor = Color.blue; + icon.localPosition += new Vector3(-0.8f, 0f, 0f); + icon.SetAsLastSibling(); + } + + private static string GetGhostRoleTeam(CustomRoles role) + { + IGhostRole instance = GhostRolesManager.CreateGhostRoleInstance(role); + if (instance == null) return string.Empty; + + Team team = instance.Team; + if ((int)team is 1 or 2 or 4) return $" {GetColoredShortTeamName(team)}"; + + Team[] teams = (int)team switch + { + 3 => [Team.Impostor, Team.Neutral], + 5 => [Team.Impostor, Team.Crewmate], + 6 => [Team.Neutral, Team.Crewmate], + 7 => [Team.Impostor, Team.Neutral, Team.Crewmate], + _ => [] + }; + + return $" {string.Join('/', teams.Select(GetColoredShortTeamName))}"; + + string GetColoredShortTeamName(Team t) + { + return Utils.ColorString(t.GetTeamColor(), Translator.GetString($"ShortTeamName.{t}").ToUpper()); } } - [HarmonyPatch(typeof(ToggleOption))] - public static class ToggleOptionPatch + [HarmonyPatch(nameof(StringOption.UpdateValue))] + [HarmonyPrefix] + private static bool UpdateValuePrefix(StringOption __instance) { - [HarmonyPatch(nameof(ToggleOption.Initialize))] - [HarmonyPrefix] - private static bool InitializePrefix(ToggleOption __instance) + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) + OptionItem item = OptionItem.AllOptions[index]; + item.SetValue(__instance.GetInt()); + string name = item.GetName(); + if (item.Name == "GameMode") GameOptionsMenuPatch.ReloadUI(ModGameOptionsMenu.TabIndex); + + string name1 = name; + + if (Enum.GetValues().FindFirst(x => Translator.GetString($"{x}") == name1.RemoveHtmlTags(), out CustomRoles role)) { - OptionItem item = OptionItem.AllOptions[index]; - __instance.TitleText.text = item.GetName(); - __instance.CheckMark.enabled = item.GetBool(); - item.OptionBehaviour = __instance; - return false; + if (role.ToString().Contains("GuardianAngel")) role = CustomRoles.GA; + + name = name.RemoveHtmlTags(); + if (Options.UsePets.GetBool() && role.PetActivatedAbility()) name += Translator.GetString("SupportsPetIndicator"); + + if (!Options.UsePets.GetBool() && role.OnlySpawnsWithPets()) name += Translator.GetString("RequiresPetIndicator"); + + if (role.IsGhostRole()) name += GetGhostRoleTeam(role); + + __instance.TitleText.fontWeight = FontWeight.Black; + __instance.TitleText.outlineColor = new(255, 255, 255, 255); + __instance.TitleText.outlineWidth = 0.04f; + __instance.LabelBackground.color = Utils.GetRoleColor(role); + __instance.TitleText.color = Color.white; + name = $"{name}"; + NotificationPopperPatch.AddRoleSettingsChangeMessage(index, item, role, true); } - - return true; - } - - [HarmonyPatch(nameof(ToggleOption.UpdateValue))] - [HarmonyPrefix] - private static bool UpdateValuePrefix(ToggleOption __instance) - { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - item.SetValue(__instance.GetBool() ? 1 : 0); + else NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); - return false; - } - return true; + __instance.TitleText.text = name; + return false; } + + return true; } - [HarmonyPatch(typeof(NumberOption))] - public static class NumberOptionPatch + [HarmonyPatch(nameof(StringOption.FixedUpdate))] + [HarmonyPrefix] + private static bool FixedUpdatePrefix(StringOption __instance) { - private static int IncrementMultiplier + if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) { - get + __instance.MinusBtn.SetInteractable(true); + __instance.PlusBtn.SetInteractable(true); + + if (OptionItem.AllOptions[index] is StringOptionItem stringOptionItem) { - if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift)) return 5; - - if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) return 10; - - return 1; - } - } - - [HarmonyPatch(nameof(NumberOption.Initialize))] - [HarmonyPrefix] - private static bool InitializePrefix(NumberOption __instance) - { - switch (__instance.Title) - { - case StringNames.GameVotingTime: - __instance.ValidRange = new(0, 600); - __instance.Value = (float)Math.Round(__instance.Value, 2); - break; - case StringNames.GameShortTasks: - case StringNames.GameLongTasks: - case StringNames.GameCommonTasks: - __instance.ValidRange = new(0, 90); - __instance.Value = (float)Math.Round(__instance.Value, 2); - break; - case StringNames.GameKillCooldown: - __instance.ValidRange = new(0, 180); - __instance.Increment = 0.5f; - __instance.Value = (float)Math.Round(__instance.Value, 2); - break; - case StringNames.GamePlayerSpeed: - case StringNames.GameCrewLight: - case StringNames.GameImpostorLight: - __instance.Increment = 0.05f; - __instance.Value = (float)Math.Round(__instance.Value, 2); - break; - case StringNames.GameNumImpostors: - __instance.ValidRange = new(0, Crowded.MaxImpostors); - __instance.Value = (float)Math.Round(__instance.Value, 2); - if (DebugModeManager.AmDebugger) __instance.ValidRange.min = 0; - break; - } - - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - __instance.TitleText.text = item.GetName(); - item.OptionBehaviour = __instance; - return false; - } - - return true; - } - - [HarmonyPatch(nameof(NumberOption.UpdateValue))] - [HarmonyPrefix] - private static bool UpdateValuePrefix(NumberOption __instance) - { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - - switch (item) - { - case IntegerOptionItem integerOptionItem: - integerOptionItem.SetValue(integerOptionItem.Rule.GetNearestIndex(__instance.GetInt())); - break; - case FloatOptionItem floatOptionItem: - floatOptionItem.SetValue(floatOptionItem.Rule.GetNearestIndex(__instance.GetFloat())); - break; - case PresetOptionItem presetOptionItem: - presetOptionItem.SetValue(presetOptionItem.Rule.GetNearestIndex(__instance.GetInt())); - GameOptionsMenuPatch.ReloadUI(ModGameOptionsMenu.TabIndex); - break; - } - - NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); - return false; - } - - return true; - } - - [HarmonyPatch(nameof(NumberOption.FixedUpdate))] - [HarmonyPrefix] - private static bool FixedUpdatePrefix(NumberOption __instance) - { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - __instance.MinusBtn.SetInteractable(true); - __instance.PlusBtn.SetInteractable(true); - - if (!Mathf.Approximately(__instance.oldValue, __instance.Value)) + if (__instance.oldValue != __instance.Value) { __instance.oldValue = __instance.Value; - __instance.ValueText.text = GetValueString(__instance, __instance.Value, OptionItem.AllOptions[index]); + + __instance.ValueText.text = stringOptionItem.noTranslation + ? stringOptionItem.Selections[stringOptionItem.Rule.GetValueByIndex(__instance.Value)] + : Translator.GetString(stringOptionItem.Selections[stringOptionItem.Rule.GetValueByIndex(__instance.Value)]); } - - return false; } - return true; + return false; } - private static string GetValueString(NumberOption __instance, float value, OptionItem item) - { - if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f) return "∞"; - - return item == null ? value.ToString(__instance.FormatString) : item.GetString(); - } - - [HarmonyPatch(nameof(NumberOption.Increase))] - [HarmonyPrefix] - public static bool IncreasePrefix(NumberOption __instance) - { - if (Mathf.Approximately(__instance.Value, __instance.ValidRange.max)) - { - __instance.Value = __instance.ValidRange.min; - __instance.UpdateValue(); - __instance.OnValueChanged.Invoke(__instance); - return false; - } - - float increment = IncrementMultiplier * __instance.Increment; - - if (__instance.Value + increment < __instance.ValidRange.max) - { - __instance.Value += increment; - __instance.UpdateValue(); - __instance.OnValueChanged.Invoke(__instance); - return false; - } - - return true; - } - - [HarmonyPatch(nameof(NumberOption.Decrease))] - [HarmonyPrefix] - public static bool DecreasePrefix(NumberOption __instance) - { - if (Mathf.Approximately(__instance.Value, __instance.ValidRange.min)) - { - __instance.Value = __instance.ValidRange.max; - __instance.UpdateValue(); - __instance.OnValueChanged.Invoke(__instance); - return false; - } - - float increment = IncrementMultiplier * __instance.Increment; - - if (__instance.Value - increment > __instance.ValidRange.min) - { - __instance.Value -= increment; - __instance.UpdateValue(); - __instance.OnValueChanged.Invoke(__instance); - return false; - } - - return true; - } + return true; } - [HarmonyPatch(typeof(StringOption))] - public static class StringOptionPatch + [HarmonyPatch(nameof(StringOption.Increase))] + [HarmonyPrefix] + public static bool IncreasePrefix(StringOption __instance) { - [HarmonyPatch(nameof(StringOption.Initialize))] - [HarmonyPrefix] - private static bool InitializePrefix(StringOption __instance) + if (__instance.Value == __instance.Values.Length - 1) { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - string name = item.GetName(); - item.OptionBehaviour = __instance; - string name1 = name; - - if (Enum.GetValues().FindFirst(x => Translator.GetString($"{x}") == name1.RemoveHtmlTags(), out CustomRoles role)) - { - if (role.ToString().Contains("GuardianAngel")) role = CustomRoles.GA; - - name = name.RemoveHtmlTags(); - if (Options.UsePets.GetBool() && role.PetActivatedAbility()) name += Translator.GetString("SupportsPetIndicator"); - - if (!Options.UsePets.GetBool() && role.OnlySpawnsWithPets()) name += Translator.GetString("RequiresPetIndicator"); - - if (role.IsGhostRole()) name += GetGhostRoleTeam(role); - - __instance.TitleText.fontWeight = FontWeight.Black; - __instance.TitleText.outlineColor = new(255, 255, 255, 255); - __instance.TitleText.outlineWidth = 0.04f; - __instance.LabelBackground.color = Utils.GetRoleColor(role); - __instance.TitleText.color = Color.white; - name = $"{name}"; - SetupHelpIcon(role, __instance); - } - - __instance.TitleText.text = name; - return false; - } - - return true; + __instance.Value = 0; + __instance.UpdateValue(); + __instance.OnValueChanged?.Invoke(__instance); + return false; } - private static void SetupHelpIcon(CustomRoles role, StringOption option) + return true; + } + + [HarmonyPatch(nameof(StringOption.Decrease))] + [HarmonyPrefix] + public static bool DecreasePrefix(StringOption __instance) + { + if (__instance.Value == 0) { - Transform template = option.transform.FindChild("MinusButton"); - Transform icon = Object.Instantiate(template, template.parent, true); - icon.name = $"{role}HelpIcon"; - var text = icon.GetComponentInChildren(); - text.text = "?"; - text.color = Color.white; - icon.FindChild("ButtonSprite").GetComponent().color = Color.black; - var gameOptionButton = icon.GetComponent(); - gameOptionButton.OnClick = new(); - - gameOptionButton.OnClick.AddListener((Action)(() => - { - if (ModGameOptionsMenu.OptionList.TryGetValue(option, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - string name = item.GetName(); - - if (Enum.GetValues().FindFirst(x => Translator.GetString($"{x}") == name.RemoveHtmlTags(), out CustomRoles value)) - { - string roleName = value.IsVanilla() ? value + "EHR" : value.ToString(); - string str = Translator.GetString($"{roleName}InfoLong"); - string infoLong; - - try - { - infoLong = HnSManager.AllHnSRoles.Contains(value) ? str : str[(str.IndexOf('\n') + 1)..str.Split("\n\n")[0].Length]; - } - catch - { - infoLong = str; - } - - var info = $"{value.ToColoredString()}: {infoLong}"; - GameSettingMenu.Instance.MenuDescriptionText.text = info; - } - } - })); - - gameOptionButton.interactableColor = Color.black; - gameOptionButton.interactableHoveredColor = Color.blue; - icon.localPosition += new Vector3(-0.8f, 0f, 0f); - icon.SetAsLastSibling(); + __instance.Value = __instance.Values.Length - 1; + __instance.UpdateValue(); + __instance.OnValueChanged?.Invoke(__instance); + return false; } - private static string GetGhostRoleTeam(CustomRoles role) + return true; + } +} + +[HarmonyPatch(typeof(GameSettingMenu))] +public class GameSettingMenuPatch +{ + private static readonly Vector3 ButtonPositionLeft = new(-3.9f, -0.4f, 0f); + private static readonly Vector3 ButtonPositionRight = new(-2.4f, -0.4f, 0f); + + private static readonly Vector3 ButtonSize = new(0.45f, 0.4f, 1f); + // private static readonly Vector3 ButtonSize = new(0.45f, 0.6f, 1f); + + private static GameOptionsMenu TemplateGameOptionsMenu; + private static PassiveButton TemplateGameSettingsButton; + + private static System.Collections.Generic.Dictionary ModSettingsButtons = []; + private static System.Collections.Generic.Dictionary ModSettingsTabs = []; + + public static NumberOption PresetBehaviour; + public static StringOption GameModeBehaviour; + + public static FreeChatInputField InputField; + private static System.Collections.Generic.List HiddenBySearch = []; + public static Action SearchForOptionsAction; + + [HarmonyPatch(nameof(GameSettingMenu.Start))] + [HarmonyPrefix] + [HarmonyPriority(Priority.First)] + public static void StartPostfix(GameSettingMenu __instance) + { + ModSettingsButtons = []; + + foreach (TabGroup tab in Enum.GetValues()) { - IGhostRole instance = GhostRolesManager.CreateGhostRoleInstance(role); - if (instance == null) return string.Empty; + PassiveButton button = Object.Instantiate(TemplateGameSettingsButton, __instance.GameSettingsButton.transform.parent); + button.gameObject.SetActive(true); + button.name = "Button_" + tab; + var label = button.GetComponentInChildren(); + label.DestroyTranslator(); + label.text = Translator.GetString($"TabGroup.{tab}"); + label.color = Color.white; + button.activeTextColor = button.inactiveTextColor = Color.white; + button.selectedTextColor = new(0.7f, 0.7f, 0.7f); - Team team = instance.Team; - if ((int)team is 1 or 2 or 4) return $" {GetColoredShortTeamName(team)}"; - - Team[] teams = (int)team switch + // var activeButton = Utils.LoadSprite($"EHR.Resources.Images.TabIcon_{tab}.png", 100f); + // button.inactiveSprites.GetComponent().sprite = activeButton /*Utils.LoadSprite($"EHR.Resources.Tab_Small_{tab}.png", 100f)*/; + // button.activeSprites.GetComponent().sprite = activeButton; + // button.selectedSprites.GetComponent().sprite = activeButton; + Color color = tab switch { - 3 => [Team.Impostor, Team.Neutral], - 5 => [Team.Impostor, Team.Crewmate], - 6 => [Team.Neutral, Team.Crewmate], - 7 => [Team.Impostor, Team.Neutral, Team.Crewmate], - _ => [] + TabGroup.SystemSettings => new(0.2f, 0.2f, 0.2f), + TabGroup.GameSettings => new(0.2f, 0.4f, 0.3f), + TabGroup.TaskSettings => new(0.4f, 0.2f, 0.5f), + TabGroup.ImpostorRoles => new(0.5f, 0.2f, 0.2f), + TabGroup.CrewmateRoles => new(0.2f, 0.4f, 0.5f), + TabGroup.NeutralRoles => new(0.5f, 0.4f, 0.2f), + TabGroup.Addons => new(0.5f, 0.2f, 0.4f), + TabGroup.OtherRoles => new(0.4f, 0.4f, 0.4f), + _ => new(0.3f, 0.3f, 0.3f) }; - return $" {string.Join('/', teams.Select(GetColoredShortTeamName))}"; + button.inactiveSprites.GetComponent().color = color; + button.activeSprites.GetComponent().color = color; + button.selectedSprites.GetComponent().color = color; - string GetColoredShortTeamName(Team t) - { - return Utils.ColorString(t.GetTeamColor(), Translator.GetString($"ShortTeamName.{t}").ToUpper()); - } + // ReSharper disable once PossibleLossOfFraction + Vector3 offset = new(0f, 0.4f * (((int)tab + 1) / 2), 0f); + button.transform.localPosition = (((int)tab + 1) % 2 == 0 ? ButtonPositionLeft : ButtonPositionRight) - offset; + button.transform.localScale = ButtonSize; + + var buttonComponent = button.GetComponent(); + buttonComponent.OnClick = new(); + buttonComponent.OnClick.AddListener((Action)(() => __instance.ChangeTab((int)tab + 3, false))); + + ModSettingsButtons.Add(tab, button); } - [HarmonyPatch(nameof(StringOption.UpdateValue))] - [HarmonyPrefix] - private static bool UpdateValuePrefix(StringOption __instance) + ModSettingsTabs = []; + + foreach (TabGroup tab in Enum.GetValues()) { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - OptionItem item = OptionItem.AllOptions[index]; - item.SetValue(__instance.GetInt()); - string name = item.GetName(); - if (item.Name == "GameMode") GameOptionsMenuPatch.ReloadUI(ModGameOptionsMenu.TabIndex); + GameOptionsMenu setTab = Object.Instantiate(TemplateGameOptionsMenu, __instance.GameSettingsTab.transform.parent); + setTab.name = "tab_" + tab; + setTab.gameObject.SetActive(false); - string name1 = name; - - if (Enum.GetValues().FindFirst(x => Translator.GetString($"{x}") == name1.RemoveHtmlTags(), out CustomRoles role)) - { - if (role.ToString().Contains("GuardianAngel")) role = CustomRoles.GA; - - name = name.RemoveHtmlTags(); - if (Options.UsePets.GetBool() && role.PetActivatedAbility()) name += Translator.GetString("SupportsPetIndicator"); - - if (!Options.UsePets.GetBool() && role.OnlySpawnsWithPets()) name += Translator.GetString("RequiresPetIndicator"); - - if (role.IsGhostRole()) name += GetGhostRoleTeam(role); - - __instance.TitleText.fontWeight = FontWeight.Black; - __instance.TitleText.outlineColor = new(255, 255, 255, 255); - __instance.TitleText.outlineWidth = 0.04f; - __instance.LabelBackground.color = Utils.GetRoleColor(role); - __instance.TitleText.color = Color.white; - name = $"{name}"; - NotificationPopperPatch.AddRoleSettingsChangeMessage(index, item, role, true); - } - else - NotificationPopperPatch.AddSettingsChangeMessage(index, item, true); - - __instance.TitleText.text = name; - return false; - } - - return true; + ModSettingsTabs.Add(tab, setTab); } - [HarmonyPatch(nameof(StringOption.FixedUpdate))] - [HarmonyPrefix] - private static bool FixedUpdatePrefix(StringOption __instance) - { - if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out int index)) - { - __instance.MinusBtn.SetInteractable(true); - __instance.PlusBtn.SetInteractable(true); + foreach (TabGroup tab in Enum.GetValues()) + if (ModSettingsButtons.TryGetValue(tab, out PassiveButton button)) + __instance.ControllerSelectable.Add(button); - if (OptionItem.AllOptions[index] is StringOptionItem stringOptionItem) - { - if (__instance.oldValue != __instance.Value) - { - __instance.oldValue = __instance.Value; + HiddenBySearch.Do(x => x.SetHidden(false)); + HiddenBySearch.Clear(); - __instance.ValueText.text = stringOptionItem.noTranslation - ? stringOptionItem.Selections[stringOptionItem.Rule.GetValueByIndex(__instance.Value)] - : Translator.GetString(stringOptionItem.Selections[stringOptionItem.Rule.GetValueByIndex(__instance.Value)]); - } - } - - return false; - } - - return true; - } - - [HarmonyPatch(nameof(StringOption.Increase))] - [HarmonyPrefix] - public static bool IncreasePrefix(StringOption __instance) - { - if (__instance.Value == __instance.Values.Length - 1) - { - __instance.Value = 0; - __instance.UpdateValue(); - __instance.OnValueChanged?.Invoke(__instance); - return false; - } - - return true; - } - - [HarmonyPatch(nameof(StringOption.Decrease))] - [HarmonyPrefix] - public static bool DecreasePrefix(StringOption __instance) - { - if (__instance.Value == 0) - { - __instance.Value = __instance.Values.Length - 1; - __instance.UpdateValue(); - __instance.OnValueChanged?.Invoke(__instance); - return false; - } - - return true; - } + SetupExtendedUI(__instance); } - [HarmonyPatch(typeof(GameSettingMenu))] - public class GameSettingMenuPatch + // Thanks: Drakos for the preset button and search bar code (https://github.com/0xDrMoe/TownofHost-Enhanced/pull/1115) + private static void SetupExtendedUI(GameSettingMenu __instance) { - private static readonly Vector3 ButtonPositionLeft = new(-3.9f, -0.4f, 0f); - private static readonly Vector3 ButtonPositionRight = new(-2.4f, -0.4f, 0f); + Transform parentLeftPanel = __instance.GamePresetsButton.transform.parent; + GameObject preset = Object.Instantiate(GameObject.Find("ModeValue"), parentLeftPanel); - private static readonly Vector3 ButtonSize = new(0.45f, 0.4f, 1f); - // private static readonly Vector3 ButtonSize = new(0.45f, 0.6f, 1f); + preset.transform.localPosition = new(-1.83f, 0.1f, -2f); + preset.transform.localScale = new(0.65f, 0.63f, 1f); + var renderer = preset.GetComponentInChildren(); + renderer.color = Color.white; + renderer.sprite = null; - private static GameOptionsMenu TemplateGameOptionsMenu; - private static PassiveButton TemplateGameSettingsButton; + var presetTmp = preset.GetComponentInChildren(); + presetTmp.DestroyTranslator(); + presetTmp.text = Translator.GetString($"Preset_{OptionItem.CurrentPreset + 1}"); - private static System.Collections.Generic.Dictionary ModSettingsButtons = []; - private static System.Collections.Generic.Dictionary ModSettingsTabs = []; + bool russian = DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.Russian; + float size = !russian ? 2.45f : 1.45f; + presetTmp.fontSizeMax = presetTmp.fontSizeMin = size; - public static NumberOption PresetBehaviour; - public static StringOption GameModeBehaviour; - public static FreeChatInputField InputField; - private static System.Collections.Generic.List HiddenBySearch = []; - public static Action SearchForOptionsAction; + GameObject tempMinus = GameObject.Find("MinusButton").gameObject; + GameObject gMinus = Object.Instantiate(__instance.GamePresetsButton.gameObject, preset.transform); + gMinus.gameObject.SetActive(true); + gMinus.transform.localScale = new(0.08f, 0.4f, 1f); - [HarmonyPatch(nameof(GameSettingMenu.Start))] - [HarmonyPrefix] - [HarmonyPriority(Priority.First)] - public static void StartPostfix(GameSettingMenu __instance) + var mLabel = gMinus.transform.Find("FontPlacer/Text_TMP").GetComponent(); + mLabel.alignment = TextAlignmentOptions.Center; + mLabel.DestroyTranslator(); + mLabel.text = "-"; + mLabel.transform.localPosition = new(mLabel.transform.localPosition.x, mLabel.transform.localPosition.y + 0.26f, mLabel.transform.localPosition.z); + mLabel.color = Color.white; + mLabel.SetFaceColor(new Color(255f, 255f, 255f)); + mLabel.transform.localScale = new(12f, 4f, 1f); + + var minus = gMinus.GetComponent(); + minus.OnClick.RemoveAllListeners(); + + minus.OnClick.AddListener((Action)(() => { - ModSettingsButtons = []; + if (PresetBehaviour == null) __instance.ChangeTab(3, false); - foreach (TabGroup tab in Enum.GetValues()) - { - PassiveButton button = Object.Instantiate(TemplateGameSettingsButton, __instance.GameSettingsButton.transform.parent); - button.gameObject.SetActive(true); - button.name = "Button_" + tab; - var label = button.GetComponentInChildren(); - label.DestroyTranslator(); - label.text = Translator.GetString($"TabGroup.{tab}"); - label.color = Color.white; - button.activeTextColor = button.inactiveTextColor = Color.white; - button.selectedTextColor = new(0.7f, 0.7f, 0.7f); + PresetBehaviour.Decrease(); + })); - // var activeButton = Utils.LoadSprite($"EHR.Resources.Images.TabIcon_{tab}.png", 100f); - // button.inactiveSprites.GetComponent().sprite = activeButton /*Utils.LoadSprite($"EHR.Resources.Tab_Small_{tab}.png", 100f)*/; - // button.activeSprites.GetComponent().sprite = activeButton; - // button.selectedSprites.GetComponent().sprite = activeButton; - Color color = tab switch - { - TabGroup.SystemSettings => new(0.2f, 0.2f, 0.2f), - TabGroup.GameSettings => new(0.2f, 0.4f, 0.3f), - TabGroup.TaskSettings => new(0.4f, 0.2f, 0.5f), - TabGroup.ImpostorRoles => new(0.5f, 0.2f, 0.2f), - TabGroup.CrewmateRoles => new(0.2f, 0.4f, 0.5f), - TabGroup.NeutralRoles => new(0.5f, 0.4f, 0.2f), - TabGroup.Addons => new(0.5f, 0.2f, 0.4f), - TabGroup.OtherRoles => new(0.4f, 0.4f, 0.4f), - _ => new(0.3f, 0.3f, 0.3f) - }; + minus.activeTextColor = minus.inactiveTextColor = minus.disabledTextColor = minus.selectedTextColor = Color.white; - button.inactiveSprites.GetComponent().color = color; - button.activeSprites.GetComponent().color = color; - button.selectedSprites.GetComponent().color = color; + minus.transform.localPosition = new(-2f, -3.37f, -4f); + minus.inactiveSprites.GetComponent().sprite = tempMinus.GetComponentInChildren().sprite; + minus.activeSprites.GetComponent().sprite = tempMinus.GetComponentInChildren().sprite; + minus.selectedSprites.GetComponent().sprite = tempMinus.GetComponentInChildren().sprite; - // ReSharper disable once PossibleLossOfFraction - Vector3 offset = new(0f, 0.4f * (((int)tab + 1) / 2), 0f); - button.transform.localPosition = (((int)tab + 1) % 2 == 0 ? ButtonPositionLeft : ButtonPositionRight) - offset; - button.transform.localScale = ButtonSize; + minus.inactiveSprites.GetComponent().color = new Color32(55, 59, 60, 255); + minus.activeSprites.GetComponent().color = new Color32(0, 255, 165, 255); + minus.selectedSprites.GetComponent().color = new Color32(0, 165, 255, 255); - var buttonComponent = button.GetComponent(); - buttonComponent.OnClick = new(); - buttonComponent.OnClick.AddListener((Action)(() => __instance.ChangeTab((int)tab + 3, false))); - ModSettingsButtons.Add(tab, button); - } + GameObject plusFab = Object.Instantiate(gMinus, preset.transform); + var plusLabel = plusFab.transform.Find("FontPlacer/Text_TMP").GetComponent(); + plusLabel.alignment = TextAlignmentOptions.Center; + plusLabel.DestroyTranslator(); + plusLabel.text = "+"; + plusLabel.color = Color.white; + plusLabel.transform.localPosition = new(plusLabel.transform.localPosition.x, plusLabel.transform.localPosition.y + 0.26f, plusLabel.transform.localPosition.z); + plusLabel.transform.localScale = new(18f, 4f, 1f); - ModSettingsTabs = []; + var plus = plusFab.GetComponent(); + plus.OnClick.RemoveAllListeners(); - foreach (TabGroup tab in Enum.GetValues()) - { - GameOptionsMenu setTab = Object.Instantiate(TemplateGameOptionsMenu, __instance.GameSettingsTab.transform.parent); - setTab.name = "tab_" + tab; - setTab.gameObject.SetActive(false); + plus.OnClick.AddListener((Action)(() => + { + if (PresetBehaviour == null) __instance.ChangeTab(3, false); - ModSettingsTabs.Add(tab, setTab); - } + PresetBehaviour.Increase(); + })); - foreach (TabGroup tab in Enum.GetValues()) - if (ModSettingsButtons.TryGetValue(tab, out PassiveButton button)) - __instance.ControllerSelectable.Add(button); + plus.activeTextColor = plus.inactiveTextColor = plus.disabledTextColor = plus.selectedTextColor = Color.white; + plus.transform.localPosition = new(-0.4f, -3.37f, -4f); + + + var gameSettingsLabel = __instance.GameSettingsButton.transform.parent.parent.FindChild("GameSettingsLabel").GetComponent(); + gameSettingsLabel.DestroyTranslator(); + string gameModeText = Translator.GetString($"Mode{Options.CurrentGameMode}").Split(':').Last().TrimStart(' '); + if (gameModeText.Length >= 15) gameModeText = $"{gameModeText}"; + + gameSettingsLabel.text = gameModeText; + + if (russian) + { + gameSettingsLabel.transform.localScale = new(0.7f, 0.7f, 1f); + gameSettingsLabel.transform.localPosition = new(-3.77f, 1.62f, -4); + } + + Vector3 gameSettingsLabelPos = gameSettingsLabel.transform.localPosition; + + GameObject gmCycler = Object.Instantiate(gMinus, gameSettingsLabel.transform, true); + + gmCycler.transform.localScale = new(0.25f, 0.7f, 1f); + gmCycler.transform.localPosition = new(gameSettingsLabelPos.x + 0.8f, gameSettingsLabelPos.y - 2.9f, gameSettingsLabelPos.z); + var gmTmp = gmCycler.transform.Find("FontPlacer/Text_TMP").GetComponent(); + gmTmp.alignment = TextAlignmentOptions.Center; + gmTmp.DestroyTranslator(); + gmTmp.text = "\u21c4"; + gmTmp.color = Color.white; + float Offset2 = !russian ? 3.35f : 3.65f; + gmTmp.transform.localPosition = new(gameSettingsLabelPos.x + Offset2, gameSettingsLabelPos.y - 1.52f, gameSettingsLabelPos.z); + gmTmp.transform.localScale = new(4f, 1.5f, 1f); + + var cycle = gmCycler.GetComponent(); + cycle.OnClick.RemoveAllListeners(); + + cycle.OnClick.AddListener((Action)(() => + { + if (GameModeBehaviour == null) __instance.ChangeTab(4, false); + + GameModeBehaviour.Increase(); + })); + + float Offset = !russian ? 1.15f : 2.25f; + cycle.activeTextColor = cycle.inactiveTextColor = cycle.disabledTextColor = cycle.selectedTextColor = Color.white; + cycle.transform.localPosition = new(Offset, 0.08f, 1f); + + + FreeChatInputField freeChatField = DestroyableSingleton.Instance.freeChatField; + FreeChatInputField field = Object.Instantiate(freeChatField, parentLeftPanel.parent); + field.transform.localScale = new(0.3f, 0.59f, 1); + field.transform.localPosition = new(-0.7f, -2.5f, -5f); + field.textArea.outputText.transform.localScale = new(3.5f, 2f, 1f); + field.textArea.outputText.font = plusLabel.font; + + InputField = field; + + Transform button = field.transform.FindChild("ChatSendButton"); + + Object.Destroy(button.FindChild("Normal").FindChild("Icon").GetComponent()); + Object.Destroy(button.FindChild("Hover").FindChild("Icon").GetComponent()); + Object.Destroy(button.FindChild("Disabled").FindChild("Icon").GetComponent()); + Object.Destroy(button.transform.FindChild("Text").GetComponent()); + + button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("EHR.Resources.Images.SearchIconActive.png", 100f); + button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("EHR.Resources.Images.SearchIconHover.png", 100f); + button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("EHR.Resources.Images.SearchIcon.png", 100f); + + if (russian) + { + Vector3 fixedScale = new(0.7f, 1f, 1f); + button.FindChild("Normal").FindChild("Background").transform.localScale = fixedScale; + button.FindChild("Hover").FindChild("Background").transform.localScale = fixedScale; + button.FindChild("Disabled").FindChild("Background").transform.localScale = fixedScale; + } + + + var passiveButton = button.GetComponent(); + + passiveButton.OnClick = new(); + passiveButton.OnClick.AddListener((Action)(() => SearchForOptions(field))); + + SearchForOptionsAction = () => + { + if (field.textArea.text != string.Empty) SearchForOptions(field); + }; + + return; + + static void SearchForOptions(FreeChatInputField textField) + { + if (ModGameOptionsMenu.TabIndex < 3) return; HiddenBySearch.Do(x => x.SetHidden(false)); - HiddenBySearch.Clear(); + string text = textField.textArea.text.Trim().ToLower(); + System.Collections.Generic.List Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsCurrentlyHidden() && !Translator.GetString($"{x.Name}").Contains(text, StringComparison.OrdinalIgnoreCase) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); + HiddenBySearch = Result; + System.Collections.Generic.List SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsCurrentlyHidden() && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - SetupExtendedUI(__instance); - } - - // Thanks: Drakos for the preset button and search bar code (https://github.com/0xDrMoe/TownofHost-Enhanced/pull/1115) - private static void SetupExtendedUI(GameSettingMenu __instance) - { - Transform parentLeftPanel = __instance.GamePresetsButton.transform.parent; - GameObject preset = Object.Instantiate(GameObject.Find("ModeValue"), parentLeftPanel); - - preset.transform.localPosition = new(-1.83f, 0.1f, -2f); - preset.transform.localScale = new(0.65f, 0.63f, 1f); - var renderer = preset.GetComponentInChildren(); - renderer.color = Color.white; - renderer.sprite = null; - - var presetTmp = preset.GetComponentInChildren(); - presetTmp.DestroyTranslator(); - presetTmp.text = Translator.GetString($"Preset_{OptionItem.CurrentPreset + 1}"); - - bool russian = DestroyableSingleton.Instance.currentLanguage.languageID == SupportedLangs.Russian; - float size = !russian ? 2.45f : 1.45f; - presetTmp.fontSizeMax = presetTmp.fontSizeMin = size; - - - GameObject tempMinus = GameObject.Find("MinusButton").gameObject; - GameObject gMinus = Object.Instantiate(__instance.GamePresetsButton.gameObject, preset.transform); - gMinus.gameObject.SetActive(true); - gMinus.transform.localScale = new(0.08f, 0.4f, 1f); - - var mLabel = gMinus.transform.Find("FontPlacer/Text_TMP").GetComponent(); - mLabel.alignment = TextAlignmentOptions.Center; - mLabel.DestroyTranslator(); - mLabel.text = "-"; - mLabel.transform.localPosition = new(mLabel.transform.localPosition.x, mLabel.transform.localPosition.y + 0.26f, mLabel.transform.localPosition.z); - mLabel.color = Color.white; - mLabel.SetFaceColor(new Color(255f, 255f, 255f)); - mLabel.transform.localScale = new(12f, 4f, 1f); - - var minus = gMinus.GetComponent(); - minus.OnClick.RemoveAllListeners(); - - minus.OnClick.AddListener((Action)(() => + if (SearchWinners.Count == 0 || !ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out GameOptionsMenu GameSettings) || GameSettings == null) { - if (PresetBehaviour == null) __instance.ChangeTab(3, false); - - PresetBehaviour.Decrease(); - })); - - minus.activeTextColor = minus.inactiveTextColor = minus.disabledTextColor = minus.selectedTextColor = Color.white; - - minus.transform.localPosition = new(-2f, -3.37f, -4f); - minus.inactiveSprites.GetComponent().sprite = tempMinus.GetComponentInChildren().sprite; - minus.activeSprites.GetComponent().sprite = tempMinus.GetComponentInChildren().sprite; - minus.selectedSprites.GetComponent().sprite = tempMinus.GetComponentInChildren().sprite; - - minus.inactiveSprites.GetComponent().color = new Color32(55, 59, 60, 255); - minus.activeSprites.GetComponent().color = new Color32(0, 255, 165, 255); - minus.selectedSprites.GetComponent().color = new Color32(0, 165, 255, 255); - - - GameObject plusFab = Object.Instantiate(gMinus, preset.transform); - var plusLabel = plusFab.transform.Find("FontPlacer/Text_TMP").GetComponent(); - plusLabel.alignment = TextAlignmentOptions.Center; - plusLabel.DestroyTranslator(); - plusLabel.text = "+"; - plusLabel.color = Color.white; - plusLabel.transform.localPosition = new(plusLabel.transform.localPosition.x, plusLabel.transform.localPosition.y + 0.26f, plusLabel.transform.localPosition.z); - plusLabel.transform.localScale = new(18f, 4f, 1f); - - var plus = plusFab.GetComponent(); - plus.OnClick.RemoveAllListeners(); - - plus.OnClick.AddListener((Action)(() => - { - if (PresetBehaviour == null) __instance.ChangeTab(3, false); - - PresetBehaviour.Increase(); - })); - - plus.activeTextColor = plus.inactiveTextColor = plus.disabledTextColor = plus.selectedTextColor = Color.white; - plus.transform.localPosition = new(-0.4f, -3.37f, -4f); - - - var gameSettingsLabel = __instance.GameSettingsButton.transform.parent.parent.FindChild("GameSettingsLabel").GetComponent(); - gameSettingsLabel.DestroyTranslator(); - string gameModeText = Translator.GetString($"Mode{Options.CurrentGameMode}").Split(':').Last().TrimStart(' '); - if (gameModeText.Length >= 15) gameModeText = $"{gameModeText}"; - - gameSettingsLabel.text = gameModeText; - - if (russian) - { - gameSettingsLabel.transform.localScale = new(0.7f, 0.7f, 1f); - gameSettingsLabel.transform.localPosition = new(-3.77f, 1.62f, -4); - } - - Vector3 gameSettingsLabelPos = gameSettingsLabel.transform.localPosition; - - GameObject gmCycler = Object.Instantiate(gMinus, gameSettingsLabel.transform, true); - - gmCycler.transform.localScale = new(0.25f, 0.7f, 1f); - gmCycler.transform.localPosition = new(gameSettingsLabelPos.x + 0.8f, gameSettingsLabelPos.y - 2.9f, gameSettingsLabelPos.z); - var gmTmp = gmCycler.transform.Find("FontPlacer/Text_TMP").GetComponent(); - gmTmp.alignment = TextAlignmentOptions.Center; - gmTmp.DestroyTranslator(); - gmTmp.text = "\u21c4"; - gmTmp.color = Color.white; - float Offset2 = !russian ? 3.35f : 3.65f; - gmTmp.transform.localPosition = new(gameSettingsLabelPos.x + Offset2, gameSettingsLabelPos.y - 1.52f, gameSettingsLabelPos.z); - gmTmp.transform.localScale = new(4f, 1.5f, 1f); - - var cycle = gmCycler.GetComponent(); - cycle.OnClick.RemoveAllListeners(); - - cycle.OnClick.AddListener((Action)(() => - { - if (GameModeBehaviour == null) __instance.ChangeTab(4, false); - - GameModeBehaviour.Increase(); - })); - - float Offset = !russian ? 1.15f : 2.25f; - cycle.activeTextColor = cycle.inactiveTextColor = cycle.disabledTextColor = cycle.selectedTextColor = Color.white; - cycle.transform.localPosition = new(Offset, 0.08f, 1f); - - - FreeChatInputField freeChatField = DestroyableSingleton.Instance.freeChatField; - FreeChatInputField field = Object.Instantiate(freeChatField, parentLeftPanel.parent); - field.transform.localScale = new(0.3f, 0.59f, 1); - field.transform.localPosition = new(-0.7f, -2.5f, -5f); - field.textArea.outputText.transform.localScale = new(3.5f, 2f, 1f); - field.textArea.outputText.font = plusLabel.font; - - InputField = field; - - Transform button = field.transform.FindChild("ChatSendButton"); - - Object.Destroy(button.FindChild("Normal").FindChild("Icon").GetComponent()); - Object.Destroy(button.FindChild("Hover").FindChild("Icon").GetComponent()); - Object.Destroy(button.FindChild("Disabled").FindChild("Icon").GetComponent()); - Object.Destroy(button.transform.FindChild("Text").GetComponent()); - - button.FindChild("Normal").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("EHR.Resources.Images.SearchIconActive.png", 100f); - button.FindChild("Hover").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("EHR.Resources.Images.SearchIconHover.png", 100f); - button.FindChild("Disabled").FindChild("Background").GetComponent().sprite = Utils.LoadSprite("EHR.Resources.Images.SearchIcon.png", 100f); - - if (russian) - { - Vector3 fixedScale = new(0.7f, 1f, 1f); - button.FindChild("Normal").FindChild("Background").transform.localScale = fixedScale; - button.FindChild("Hover").FindChild("Background").transform.localScale = fixedScale; - button.FindChild("Disabled").FindChild("Background").transform.localScale = fixedScale; - } - - - var passiveButton = button.GetComponent(); - - passiveButton.OnClick = new(); - passiveButton.OnClick.AddListener((Action)(() => SearchForOptions(field))); - - SearchForOptionsAction = () => - { - if (field.textArea.text != string.Empty) SearchForOptions(field); - }; - - return; - - static void SearchForOptions(FreeChatInputField textField) - { - if (ModGameOptionsMenu.TabIndex < 3) return; - - HiddenBySearch.Do(x => x.SetHidden(false)); - string text = textField.textArea.text.Trim().ToLower(); - System.Collections.Generic.List Result = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsCurrentlyHidden() && !Translator.GetString($"{x.Name}").Contains(text, StringComparison.OrdinalIgnoreCase) && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3)).ToList(); - HiddenBySearch = Result; - System.Collections.Generic.List SearchWinners = OptionItem.AllOptions.Where(x => x.Parent == null && !x.IsCurrentlyHidden() && x.Tab == (TabGroup)(ModGameOptionsMenu.TabIndex - 3) && !Result.Contains(x)).ToList(); - - if (SearchWinners.Count == 0 || !ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out GameOptionsMenu GameSettings) || GameSettings == null) - { - HiddenBySearch.Clear(); - Logger.SendInGame(Translator.GetString("SearchNoResult")); - return; - } - - Result.ForEach(x => x.SetHidden(true)); - - GameOptionsMenuPatch.ReCreateSettings(GameSettings); - textField.Clear(); - } - } - - private static void SetDefaultButton(GameSettingMenu __instance) - { - __instance.GamePresetsButton.gameObject.SetActive(false); - - PassiveButton gameSettingButton = __instance.GameSettingsButton; - gameSettingButton.transform.localPosition = new(-3f, -0.4f, 0f); - - var textLabel = gameSettingButton.GetComponentInChildren(); - textLabel.DestroyTranslator(); - textLabel.text = Translator.GetString("TabGroup.VanillaSettings"); - - gameSettingButton.activeTextColor = gameSettingButton.inactiveTextColor = Color.white; - gameSettingButton.selectedTextColor = Color.gray; - - gameSettingButton.inactiveSprites.GetComponent().color = Color.black; - gameSettingButton.activeSprites.GetComponent().color = Color.gray; - gameSettingButton.selectedSprites.GetComponent().color = Color.black; - - gameSettingButton.transform.localPosition = ButtonPositionLeft; - gameSettingButton.transform.localScale = ButtonSize; - - __instance.RoleSettingsButton.gameObject.SetActive(false); - - __instance.DefaultButtonSelected = gameSettingButton; - __instance.ControllerSelectable = new(); - __instance.ControllerSelectable.Add(gameSettingButton); - } - - [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] - [HarmonyPrefix] - public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [HarmonyArgument(1)] bool previewOnly) - { - if (HiddenBySearch.Any()) - { - HiddenBySearch.Do(x => x.SetHidden(false)); - if (ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out GameOptionsMenu GameSettings) && GameSettings != null) GameOptionsMenuPatch.ReCreateSettings(GameSettings); - HiddenBySearch.Clear(); + Logger.SendInGame(Translator.GetString("SearchNoResult")); + return; } - if (!previewOnly || tabNum != 1) ModGameOptionsMenu.TabIndex = tabNum; + Result.ForEach(x => x.SetHidden(true)); - GameOptionsMenu settingsTab; - PassiveButton button; + GameOptionsMenuPatch.ReCreateSettings(GameSettings); + textField.Clear(); + } + } - if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) + private static void SetDefaultButton(GameSettingMenu __instance) + { + __instance.GamePresetsButton.gameObject.SetActive(false); + + PassiveButton gameSettingButton = __instance.GameSettingsButton; + gameSettingButton.transform.localPosition = new(-3f, -0.4f, 0f); + + var textLabel = gameSettingButton.GetComponentInChildren(); + textLabel.DestroyTranslator(); + textLabel.text = Translator.GetString("TabGroup.VanillaSettings"); + + gameSettingButton.activeTextColor = gameSettingButton.inactiveTextColor = Color.white; + gameSettingButton.selectedTextColor = Color.gray; + + gameSettingButton.inactiveSprites.GetComponent().color = Color.black; + gameSettingButton.activeSprites.GetComponent().color = Color.gray; + gameSettingButton.selectedSprites.GetComponent().color = Color.black; + + gameSettingButton.transform.localPosition = ButtonPositionLeft; + gameSettingButton.transform.localScale = ButtonSize; + + __instance.RoleSettingsButton.gameObject.SetActive(false); + + __instance.DefaultButtonSelected = gameSettingButton; + __instance.ControllerSelectable = new(); + __instance.ControllerSelectable.Add(gameSettingButton); + } + + [HarmonyPatch(nameof(GameSettingMenu.ChangeTab))] + [HarmonyPrefix] + public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [HarmonyArgument(1)] bool previewOnly) + { + if (HiddenBySearch.Any()) + { + HiddenBySearch.Do(x => x.SetHidden(false)); + if (ModSettingsTabs.TryGetValue((TabGroup)(ModGameOptionsMenu.TabIndex - 3), out GameOptionsMenu GameSettings) && GameSettings != null) GameOptionsMenuPatch.ReCreateSettings(GameSettings); + + HiddenBySearch.Clear(); + } + + if (!previewOnly || tabNum != 1) ModGameOptionsMenu.TabIndex = tabNum; + + GameOptionsMenu settingsTab; + PassiveButton button; + + if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) + { + foreach (TabGroup tab in Enum.GetValues()) + if (ModSettingsTabs.TryGetValue(tab, out settingsTab) && settingsTab != null) + settingsTab.gameObject.SetActive(false); + + foreach (TabGroup tab in Enum.GetValues()) + if (ModSettingsButtons.TryGetValue(tab, out button) && button != null) + button.SelectButton(false); + } + + if (tabNum < 3) return true; + + var tabGroup = (TabGroup)(tabNum - 3); + + if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) + { + __instance.PresetsTab.gameObject.SetActive(false); + __instance.GameSettingsTab.gameObject.SetActive(false); + __instance.RoleSettingsTab.gameObject.SetActive(false); + __instance.GamePresetsButton.SelectButton(false); + __instance.GameSettingsButton.SelectButton(false); + __instance.RoleSettingsButton.SelectButton(false); + + if (ModSettingsTabs.TryGetValue(tabGroup, out settingsTab) && settingsTab != null) { - foreach (TabGroup tab in Enum.GetValues()) - if (ModSettingsTabs.TryGetValue(tab, out settingsTab) && settingsTab != null) - settingsTab.gameObject.SetActive(false); - - foreach (TabGroup tab in Enum.GetValues()) - if (ModSettingsButtons.TryGetValue(tab, out button) && button != null) - button.SelectButton(false); + settingsTab.gameObject.SetActive(true); + __instance.MenuDescriptionText.DestroyTranslator(); + __instance.MenuDescriptionText.text = Translator.GetString("TabInfoTip"); } + } - if (tabNum < 3) return true; - - var tabGroup = (TabGroup)(tabNum - 3); - - if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly) - { - __instance.PresetsTab.gameObject.SetActive(false); - __instance.GameSettingsTab.gameObject.SetActive(false); - __instance.RoleSettingsTab.gameObject.SetActive(false); - __instance.GamePresetsButton.SelectButton(false); - __instance.GameSettingsButton.SelectButton(false); - __instance.RoleSettingsButton.SelectButton(false); - - if (ModSettingsTabs.TryGetValue(tabGroup, out settingsTab) && settingsTab != null) - { - settingsTab.gameObject.SetActive(true); - __instance.MenuDescriptionText.DestroyTranslator(); - __instance.MenuDescriptionText.text = Translator.GetString("TabInfoTip"); - } - } - - if (previewOnly) - { - __instance.ToggleLeftSideDarkener(false); - __instance.ToggleRightSideDarkener(true); - return false; - } - - __instance.ToggleLeftSideDarkener(true); - __instance.ToggleRightSideDarkener(false); - - if (ModSettingsButtons.TryGetValue(tabGroup, out button) && button != null) button.SelectButton(true); - + if (previewOnly) + { + __instance.ToggleLeftSideDarkener(false); + __instance.ToggleRightSideDarkener(true); return false; } - [HarmonyPatch(nameof(GameSettingMenu.OnEnable))] - [HarmonyPrefix] - private static bool OnEnablePrefix(GameSettingMenu __instance) + __instance.ToggleLeftSideDarkener(true); + __instance.ToggleRightSideDarkener(false); + + if (ModSettingsButtons.TryGetValue(tabGroup, out button) && button != null) button.SelectButton(true); + + return false; + } + + [HarmonyPatch(nameof(GameSettingMenu.OnEnable))] + [HarmonyPrefix] + private static bool OnEnablePrefix(GameSettingMenu __instance) + { + if (TemplateGameOptionsMenu == null) { - if (TemplateGameOptionsMenu == null) - { - TemplateGameOptionsMenu = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); - TemplateGameOptionsMenu.gameObject.SetActive(false); - } + TemplateGameOptionsMenu = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent); + TemplateGameOptionsMenu.gameObject.SetActive(false); + } - if (TemplateGameSettingsButton == null) - { - TemplateGameSettingsButton = Object.Instantiate(__instance.GameSettingsButton, __instance.GameSettingsButton.transform.parent); - TemplateGameSettingsButton.gameObject.SetActive(false); - } + if (TemplateGameSettingsButton == null) + { + TemplateGameSettingsButton = Object.Instantiate(__instance.GameSettingsButton, __instance.GameSettingsButton.transform.parent); + TemplateGameSettingsButton.gameObject.SetActive(false); + } - SetDefaultButton(__instance); + SetDefaultButton(__instance); - ModGameOptionsMenu.OptionList = new(); - ModGameOptionsMenu.BehaviourList = new(); - ModGameOptionsMenu.CategoryHeaderList = new(); + ModGameOptionsMenu.OptionList = new(); + ModGameOptionsMenu.BehaviourList = new(); + ModGameOptionsMenu.CategoryHeaderList = new(); - ControllerManager.Instance.OpenOverlayMenu(__instance.name, __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable); - DestroyableSingleton.Instance.menuNavigationPrompts.SetActive(false); - if (Controller.currentTouchType != Controller.TouchType.Joystick) __instance.ChangeTab(1, false); + ControllerManager.Instance.OpenOverlayMenu(__instance.name, __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable); + DestroyableSingleton.Instance.menuNavigationPrompts.SetActive(false); + if (Controller.currentTouchType != Controller.TouchType.Joystick) __instance.ChangeTab(1, false); - __instance.StartCoroutine(__instance.CoSelectDefault()); + __instance.StartCoroutine(__instance.CoSelectDefault()); + return false; + } + + [HarmonyPatch(nameof(GameSettingMenu.Close))] + [HarmonyPostfix] + public static void ClosePostfix() + { + foreach (PassiveButton button in ModSettingsButtons.Values) Object.Destroy(button); + + foreach (GameOptionsMenu tab in ModSettingsTabs.Values) Object.Destroy(tab); + + ModSettingsButtons = []; + ModSettingsTabs = []; + } +} + +[HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] +public static class FixInputChatField +{ + public static bool Prefix(FreeChatInputField __instance) + { + if (GameSettingMenuPatch.InputField != null && __instance == GameSettingMenuPatch.InputField) + { + Vector2 size = __instance.Background.size; + size.y = Math.Max(0.62f, __instance.textArea.TextHeight + 0.2f); + __instance.Background.size = size; return false; } - [HarmonyPatch(nameof(GameSettingMenu.Close))] - [HarmonyPostfix] - public static void ClosePostfix() + return true; + } +} + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] +public static class FixDarkThemeForSearchBar +{ + public static void Postfix() + { + if (!GameSettingMenu.Instance) return; + + FreeChatInputField field = GameSettingMenuPatch.InputField; + + if (field != null) { - foreach (PassiveButton button in ModSettingsButtons.Values) Object.Destroy(button); - - foreach (GameOptionsMenu tab in ModSettingsTabs.Values) Object.Destroy(tab); - - ModSettingsButtons = []; - ModSettingsTabs = []; + field.background.color = new Color32(40, 40, 40, byte.MaxValue); + field.textArea.compoText.Color(Color.white); + field.textArea.outputText.color = Color.white; } } +} - [HarmonyPatch(typeof(FreeChatInputField), nameof(FreeChatInputField.UpdateCharCount))] - public static class FixInputChatField +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] +public class RpcSyncSettingsPatch +{ + public static void Postfix() { - public static bool Prefix(FreeChatInputField __instance) - { - if (GameSettingMenuPatch.InputField != null && __instance == GameSettingMenuPatch.InputField) - { - Vector2 size = __instance.Background.size; - size.y = Math.Max(0.62f, __instance.textArea.TextHeight + 0.2f); - __instance.Background.size = size; - return false; - } - - return true; - } - } - - [HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))] - public static class FixDarkThemeForSearchBar - { - public static void Postfix() - { - if (!GameSettingMenu.Instance) return; - - FreeChatInputField field = GameSettingMenuPatch.InputField; - - if (field != null) - { - field.background.color = new Color32(40, 40, 40, byte.MaxValue); - field.textArea.compoText.Color(Color.white); - field.textArea.outputText.color = Color.white; - } - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))] - public class RpcSyncSettingsPatch - { - public static void Postfix() - { - OptionItem.SyncAllOptions(); - } + OptionItem.SyncAllOptions(); } } \ No newline at end of file diff --git a/Patches/GameOptionsPatch.cs b/Patches/GameOptionsPatch.cs index 356209160..54aaa4fd4 100644 --- a/Patches/GameOptionsPatch.cs +++ b/Patches/GameOptionsPatch.cs @@ -2,125 +2,124 @@ using System; using AmongUs.GameOptions; using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.SwitchGameMode))] +internal static class SwitchGameModePatch { - [HarmonyPatch(typeof(GameOptionsManager), nameof(GameOptionsManager.SwitchGameMode))] - internal static class SwitchGameModePatch + private static bool Warned; + + public static bool Prefix(GameModes gameMode) { - private static bool Warned; + if (!Options.IsLoaded || (AmongUsClient.Instance != null && !AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer != null) || gameMode != GameModes.HideNSeek || Warned || !HudManager.Instance) return true; - public static bool Prefix(GameModes gameMode) - { - if (!Options.IsLoaded || (AmongUsClient.Instance != null && !AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer != null) || gameMode != GameModes.HideNSeek || Warned || !HudManager.Instance) return true; - - HudManager.Instance.ShowPopUp(Translator.GetString("HnSUnloadWarning")); - return false; - } - - public static void Postfix(GameModes gameMode) - { - if (!Options.IsLoaded || (!AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer != null) || gameMode != GameModes.HideNSeek) return; - - if (!Warned) - { - Warned = true; - return; - } - - ErrorText.Instance.HnSFlag = true; - ErrorText.Instance.AddError(ErrorCode.HnsUnload); - Zoom.SetZoomSize(reset: true); - Harmony.UnpatchAll(); - Main.Instance.Unload(); - } + HudManager.Instance.ShowPopUp(Translator.GetString("HnSUnloadWarning")); + return false; } - [HarmonyPatch(typeof(NormalGameOptionsV08), nameof(NormalGameOptionsV08.SetRecommendations), typeof(int), typeof(bool), typeof(RulesPresets))] - [HarmonyPatch(typeof(NormalGameOptionsV08), nameof(NormalGameOptionsV08.SetRecommendations), typeof(int), typeof(bool))] - internal static class SetRecommendationsPatch + public static void Postfix(GameModes gameMode) { - public static void Postfix(NormalGameOptionsV08 __instance, - [HarmonyArgument(0)] int numPlayers, - [HarmonyArgument(1)] bool isOnline) + if (!Options.IsLoaded || (!AmongUsClient.Instance.AmHost && PlayerControl.LocalPlayer != null) || gameMode != GameModes.HideNSeek) return; + + if (!Warned) { - numPlayers = Math.Clamp(numPlayers, 4, 15); - __instance.PlayerSpeedMod = __instance.MapId == 4 ? 1.5f : 1.25f; - __instance.CrewLightMod = 0.5f; - __instance.ImpostorLightMod = 1.25f; - __instance.KillCooldown = 27.5f; - __instance.NumCommonTasks = 1; - __instance.NumLongTasks = 3; - __instance.NumShortTasks = 4; - __instance.NumEmergencyMeetings = 1; - __instance.NumImpostors = GetRecommendedImpostors(); - __instance.KillDistance = 1; - __instance.DiscussionTime = 0; - __instance.VotingTime = 120; - __instance.IsDefaults = true; - __instance.ConfirmImpostor = false; - __instance.VisualTasks = false; - - __instance.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); - __instance.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); - __instance.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); - __instance.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); - __instance.roleOptions.SetRoleRecommended(RoleTypes.Shapeshifter); - __instance.roleOptions.SetRoleRecommended(RoleTypes.Scientist); - __instance.roleOptions.SetRoleRecommended(RoleTypes.GuardianAngel); - __instance.roleOptions.SetRoleRecommended(RoleTypes.Engineer); - - switch (Options.CurrentGameMode) - { - case CustomGameMode.AllInOne: - case CustomGameMode.SoloKombat: - case CustomGameMode.FFA: - case CustomGameMode.RoomRush: - case CustomGameMode.NaturalDisasters: - case CustomGameMode.HotPotato: - case CustomGameMode.CaptureTheFlag: - __instance.CrewLightMod = __instance.ImpostorLightMod = 1.25f; - __instance.NumImpostors = 3; - __instance.NumCommonTasks = 0; - __instance.NumLongTasks = 0; - __instance.NumShortTasks = 0; - __instance.KillCooldown = 0f; - __instance.NumEmergencyMeetings = 0; - __instance.KillDistance = 0; - break; - case CustomGameMode.Speedrun: - case CustomGameMode.MoveAndStop: - __instance.CrewLightMod = 1.25f; - __instance.ImpostorLightMod = 1.25f; - __instance.KillCooldown = 60f; - __instance.NumCommonTasks = 2; - __instance.NumLongTasks = 3; - __instance.NumShortTasks = 5; - __instance.NumEmergencyMeetings = 0; - __instance.VisualTasks = true; - break; - case CustomGameMode.HideAndSeek: - __instance.CrewLightMod = 1.25f; - __instance.ImpostorLightMod = 0.5f; - __instance.KillCooldown = 10f; - __instance.NumCommonTasks = 2; - __instance.NumLongTasks = 3; - __instance.NumShortTasks = 5; - __instance.NumEmergencyMeetings = 0; - __instance.VisualTasks = true; - break; - } - + Warned = true; return; + } - int GetRecommendedImpostors() + ErrorText.Instance.HnSFlag = true; + ErrorText.Instance.AddError(ErrorCode.HnsUnload); + Zoom.SetZoomSize(reset: true); + Harmony.UnpatchAll(); + Main.Instance.Unload(); + } +} + +[HarmonyPatch(typeof(NormalGameOptionsV08), nameof(NormalGameOptionsV08.SetRecommendations), typeof(int), typeof(bool), typeof(RulesPresets))] +[HarmonyPatch(typeof(NormalGameOptionsV08), nameof(NormalGameOptionsV08.SetRecommendations), typeof(int), typeof(bool))] +internal static class SetRecommendationsPatch +{ + public static void Postfix(NormalGameOptionsV08 __instance, + [HarmonyArgument(0)] int numPlayers, + [HarmonyArgument(1)] bool isOnline) + { + numPlayers = Math.Clamp(numPlayers, 4, 15); + __instance.PlayerSpeedMod = __instance.MapId == 4 ? 1.5f : 1.25f; + __instance.CrewLightMod = 0.5f; + __instance.ImpostorLightMod = 1.25f; + __instance.KillCooldown = 27.5f; + __instance.NumCommonTasks = 1; + __instance.NumLongTasks = 3; + __instance.NumShortTasks = 4; + __instance.NumEmergencyMeetings = 1; + __instance.NumImpostors = GetRecommendedImpostors(); + __instance.KillDistance = 1; + __instance.DiscussionTime = 0; + __instance.VotingTime = 120; + __instance.IsDefaults = true; + __instance.ConfirmImpostor = false; + __instance.VisualTasks = false; + + __instance.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); + __instance.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Shapeshifter); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Scientist); + __instance.roleOptions.SetRoleRecommended(RoleTypes.GuardianAngel); + __instance.roleOptions.SetRoleRecommended(RoleTypes.Engineer); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.AllInOne: + case CustomGameMode.SoloKombat: + case CustomGameMode.FFA: + case CustomGameMode.RoomRush: + case CustomGameMode.NaturalDisasters: + case CustomGameMode.HotPotato: + case CustomGameMode.CaptureTheFlag: + __instance.CrewLightMod = __instance.ImpostorLightMod = 1.25f; + __instance.NumImpostors = 3; + __instance.NumCommonTasks = 0; + __instance.NumLongTasks = 0; + __instance.NumShortTasks = 0; + __instance.KillCooldown = 0f; + __instance.NumEmergencyMeetings = 0; + __instance.KillDistance = 0; + break; + case CustomGameMode.Speedrun: + case CustomGameMode.MoveAndStop: + __instance.CrewLightMod = 1.25f; + __instance.ImpostorLightMod = 1.25f; + __instance.KillCooldown = 60f; + __instance.NumCommonTasks = 2; + __instance.NumLongTasks = 3; + __instance.NumShortTasks = 5; + __instance.NumEmergencyMeetings = 0; + __instance.VisualTasks = true; + break; + case CustomGameMode.HideAndSeek: + __instance.CrewLightMod = 1.25f; + __instance.ImpostorLightMod = 0.5f; + __instance.KillCooldown = 10f; + __instance.NumCommonTasks = 2; + __instance.NumLongTasks = 3; + __instance.NumShortTasks = 5; + __instance.NumEmergencyMeetings = 0; + __instance.VisualTasks = true; + break; + } + + return; + + int GetRecommendedImpostors() + { + return numPlayers switch { - return numPlayers switch - { - > 13 => 3, - > 8 => 2, - _ => 1 - }; - } + > 13 => 3, + > 8 => 2, + _ => 1 + }; } } } \ No newline at end of file diff --git a/Patches/GameStartManagerPatch.cs b/Patches/GameStartManagerPatch.cs index fc4e5cd38..d68cdcfc8 100644 --- a/Patches/GameStartManagerPatch.cs +++ b/Patches/GameStartManagerPatch.cs @@ -13,537 +13,536 @@ using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] +public static class GameStartManagerUpdatePatch { - [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] - public static class GameStartManagerUpdatePatch + public static void Prefix(GameStartManager __instance) { - public static void Prefix(GameStartManager __instance) + try + { + __instance.MinPlayers = 1; + } + catch (Exception ex) + { + Logger.Error(ex.ToString(), "Surely this can't be causing an issue, right?"); + } + } +} + +public static class GameStartManagerPatch +{ + public static float Timer { get; set; } = 600f; + + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))] + public static class GameStartManagerStartPatch + { + public static TextMeshPro HideName; + public static TextMeshPro GameCountdown; + + public static void Postfix(GameStartManager __instance) { try { - __instance.MinPlayers = 1; - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "Surely this can't be causing an issue, right?"); - } - } - } + if (__instance == null) return; - public static class GameStartManagerPatch - { - public static float Timer { get; set; } = 600f; - - [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))] - public static class GameStartManagerStartPatch - { - public static TextMeshPro HideName; - public static TextMeshPro GameCountdown; - - public static void Postfix(GameStartManager __instance) - { - try - { - if (__instance == null) return; - - TextMeshPro temp = __instance.PlayerCounter; - GameCountdown = Object.Instantiate(temp, AmongUsClient.Instance.AmHost ? __instance.StartButton.transform : __instance.HostInfoPanel.transform); - GameCountdown.text = string.Empty; - - if (AmongUsClient.Instance.AmHost) - { - __instance.GameStartTextParent.GetComponent().sprite = null; - __instance.StartButton.ChangeButtonText(DestroyableSingleton.Instance.GetString(StringNames.StartLabel)); - __instance.GameStartText.transform.localPosition = new(__instance.GameStartText.transform.localPosition.x, 2f, __instance.GameStartText.transform.localPosition.z); - __instance.StartButton.activeTextColor = __instance.StartButton.inactiveTextColor = Color.white; - - __instance.EditButton.activeTextColor = __instance.EditButton.inactiveTextColor = Color.black; - __instance.EditButton.inactiveSprites.GetComponent().color = new(0f, 0.647f, 1f, 1f); - __instance.EditButton.activeSprites.GetComponent().color = new(0f, 0.847f, 1f, 1f); - __instance.EditButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0f, 1f, 1f, 0.5f); - - __instance.HostViewButton.activeTextColor = __instance.HostViewButton.inactiveTextColor = Color.black; - __instance.HostViewButton.inactiveSprites.GetComponent().color = new(0f, 0.647f, 1f, 1f); - __instance.HostViewButton.activeSprites.GetComponent().color = new(0f, 0.847f, 1f, 1f); - __instance.HostViewButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0f, 1f, 1f, 0.5f); - } - - if (AmongUsClient.Instance == null || AmongUsClient.Instance.IsGameStarted || GameStates.IsInGame || __instance.startState == GameStartManager.StartingStates.Starting) return; - - __instance.GameRoomNameCode.text = GameCode.IntToGameName(AmongUsClient.Instance.GameId); - // Reset lobby countdown timer - Timer = 600f; - - HideName = Object.Instantiate(__instance.GameRoomNameCode, __instance.GameRoomNameCode.transform); - - HideName.text = ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) - ? $"{Main.HideName.Value}" - : $"{Main.HideName.Value}"; - - if (!AmongUsClient.Instance.AmHost) return; - - if (ModUpdater.IsBroken || (ModUpdater.HasUpdate && ModUpdater.ForceUpdate) || !Main.AllowPublicRoom) - { - // __instance.MakePublicButton.color = Palette.DisabledClear; - // __instance.privatePublicText.color = Palette.DisabledClear; - } - - if (Main.NormalOptions.KillCooldown == 0f) Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; - - AURoleOptions.SetOpt(Main.NormalOptions.Cast()); - if (AURoleOptions.ShapeshifterCooldown == 0f) AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; - - AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); - AURoleOptions.ProtectionDurationSeconds = 0f; - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "GameStartManagerStartPatch.Postfix (1)"); - } - } - } - - [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] - public static class GameStartManagerUpdatePatch - { - public static float ExitTimer = -1f; - private static float MinWait, MaxWait; - private static int MinPlayer; - - public static bool Prefix(GameStartManager __instance) - { - try - { - if (AmongUsClient.Instance == null) return false; - - if (AmongUsClient.Instance.AmHost) VanillaUpdate(__instance); - - if (AmongUsClient.Instance.IsGameStarted || GameStates.IsInGame || __instance == null || __instance.startState == GameStartManager.StartingStates.Starting) return false; - - MinWait = Options.MinWaitAutoStart.GetFloat(); - MaxWait = Options.MaxWaitAutoStart.GetFloat(); - MinPlayer = Options.PlayerAutoStart.GetInt(); - MinWait = 600f - (MinWait * 60f); - MaxWait *= 60f; - - // Lobby code - if (DataManager.Settings != null && DataManager.Settings.Gameplay != null) - { - if (DataManager.Settings.Gameplay.StreamerMode) - { - if (__instance != null && __instance.GameRoomNameCode != null) __instance.GameRoomNameCode.color = new(255, 255, 255, 0); - - if (GameStartManagerStartPatch.HideName != null) GameStartManagerStartPatch.HideName.enabled = true; - } - else - { - if (__instance != null && __instance.GameRoomNameCode != null) __instance.GameRoomNameCode.color = new(255, 255, 255, 255); - - if (GameStartManagerStartPatch.HideName != null) GameStartManagerStartPatch.HideName.enabled = false; - } - } - - if (AmongUsClient.Instance == null || GameData.Instance == null || !AmongUsClient.Instance.AmHost || !GameData.Instance) return true; - - if (Main.AutoStart != null && Main.AutoStart.Value) - { - Main.UpdateTime++; - - if (Main.UpdateTime >= 50) - { - Main.UpdateTime = 0; - - if (((GameData.Instance?.PlayerCount >= MinPlayer && Timer <= MinWait) || Timer <= MaxWait) && !GameStates.IsCountDown) - { - PlayerControl[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).ToArray(); - - if (invalidColor.Length > 0) - { - Main.AllPlayerControls - .Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId) - .Do(p => AmongUsClient.Instance.KickPlayer(p.GetClientId(), false)); - - Logger.SendInGame(GetString("Error.InvalidColorPreventStart")); - string msg = GetString("Error.InvalidColor"); - msg += "\n" + string.Join(",", invalidColor.Select(p => $"{p.GetRealName()}")); - Utils.SendMessage(msg); - } - - if (Options.RandomMapsMode.GetBool()) - { - Main.NormalOptions.MapId = GameStartRandomMap.SelectRandomMap(); - CreateOptionsPickerPatch.SetDleks = Main.CurrentMap == MapNames.Dleks; - } - else if (CreateOptionsPickerPatch.SetDleks) Main.NormalOptions.MapId = 3; - - if (Main.CurrentMap == MapNames.Dleks) - { - var opt = Main.NormalOptions.Cast(); - - Options.DefaultKillCooldown = Main.NormalOptions.KillCooldown; - Main.LastKillCooldown.Value = Main.NormalOptions.KillCooldown; - Main.NormalOptions.KillCooldown = 0f; - AURoleOptions.SetOpt(opt); - Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; - AURoleOptions.ShapeshifterCooldown = 0f; - AURoleOptions.ImpostorsCanSeeProtect = false; - - PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt, AprilFoolsMode.IsAprilFoolsModeToggledOn)); - } - - RPC.RpcVersionCheck(); - - GameStartManager.Instance.startState = GameStartManager.StartingStates.Countdown; - GameStartManager.Instance.countDownTimer = Options.AutoStartTimer.GetInt(); - __instance?.StartButton.gameObject.SetActive(false); - } - } - } - } - catch (NullReferenceException) { } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "GameStartManagerUpdatePatch.Prefix (2)"); - } - - return false; - } - - private static void VanillaUpdate(GameStartManager instance) - { - if (!GameData.Instance || !GameManager.Instance || !AmongUsClient.Instance.AmHost) return; - - try - { - instance.UpdateMapImage((MapNames)GameManager.Instance.LogicOptions.MapId); - } - catch (Exception e) - { - ErrorText.Instance.AddError(ErrorCode.UnsupportedMap); - Utils.ThrowException(e); - } - - instance.CheckSettingsDiffs(); - instance.StartButton.gameObject.SetActive(true); - instance.RulesPresetText.text = DestroyableSingleton.Instance.GetString(GameOptionsManager.Instance.CurrentGameOptions.GetRulesPresetTitle()); - - if (GameCode.IntToGameName(AmongUsClient.Instance.GameId) == null) instance.privatePublicPanelText.text = DestroyableSingleton.Instance.GetString(StringNames.LocalButton); - else if (AmongUsClient.Instance.IsGamePublic) instance.privatePublicPanelText.text = DestroyableSingleton.Instance.GetString(StringNames.PublicHeader); - else instance.privatePublicPanelText.text = DestroyableSingleton.Instance.GetString(StringNames.PrivateHeader); - - instance.HostPrivateButton.gameObject.SetActive(!AmongUsClient.Instance.IsGamePublic); - instance.HostPublicButton.gameObject.SetActive(AmongUsClient.Instance.IsGamePublic); - - if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) - ClipboardHelper.PutClipboardString(GameCode.IntToGameName(AmongUsClient.Instance.GameId)); - - if (GameData.Instance.PlayerCount != instance.LastPlayerCount) - { - instance.LastPlayerCount = GameData.Instance.PlayerCount; - var text = ""; - if (instance.LastPlayerCount > instance.MinPlayers) text = ""; - if (instance.LastPlayerCount == instance.MinPlayers) text = ""; - - instance.PlayerCounter.text = $"{text}{instance.LastPlayerCount}/{(AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame ? 15 : GameManager.Instance.LogicOptions.MaxPlayers)}"; - instance.StartButton.SetButtonEnableState(instance.LastPlayerCount >= instance.MinPlayers); - ActionMapGlyphDisplay startButtonGlyph = instance.StartButtonGlyph; - startButtonGlyph?.SetColor(instance.LastPlayerCount >= instance.MinPlayers ? Palette.EnabledColor : Palette.DisabledClear); - - if (DestroyableSingleton.InstanceExists) - { - if (AmongUsClient.Instance.AmHost && AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame) - DestroyableSingleton.Instance.SetInLobbyHost(instance.LastPlayerCount, GameManager.Instance.LogicOptions.MaxPlayers, AmongUsClient.Instance.GameId); - else - DestroyableSingleton.Instance.SetInLobbyClient(instance.LastPlayerCount, GameManager.Instance.LogicOptions.MaxPlayers, AmongUsClient.Instance.GameId); - } - } + TextMeshPro temp = __instance.PlayerCounter; + GameCountdown = Object.Instantiate(temp, AmongUsClient.Instance.AmHost ? __instance.StartButton.transform : __instance.HostInfoPanel.transform); + GameCountdown.text = string.Empty; if (AmongUsClient.Instance.AmHost) { - if (instance.startState == GameStartManager.StartingStates.Countdown) - { - instance.StartButton.ChangeButtonText(GetString("Cancel")); + __instance.GameStartTextParent.GetComponent().sprite = null; + __instance.StartButton.ChangeButtonText(DestroyableSingleton.Instance.GetString(StringNames.StartLabel)); + __instance.GameStartText.transform.localPosition = new(__instance.GameStartText.transform.localPosition.x, 2f, __instance.GameStartText.transform.localPosition.z); + __instance.StartButton.activeTextColor = __instance.StartButton.inactiveTextColor = Color.white; - instance.StartButton.inactiveSprites.GetComponent().color = new(0.8f, 0f, 0f, 1f); - instance.StartButton.activeSprites.GetComponent().color = Color.red; - instance.StartButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0.8f, 0.4f, 0.4f, 1f); - instance.StartButton.activeTextColor = instance.StartButton.inactiveTextColor = Color.white; - int num = Mathf.CeilToInt(instance.countDownTimer); - instance.countDownTimer -= Time.deltaTime; - int num2 = Mathf.CeilToInt(instance.countDownTimer); - if (!instance.GameStartTextParent.activeSelf) SoundManager.Instance.PlaySound(instance.gameStartSound, false); + __instance.EditButton.activeTextColor = __instance.EditButton.inactiveTextColor = Color.black; + __instance.EditButton.inactiveSprites.GetComponent().color = new(0f, 0.647f, 1f, 1f); + __instance.EditButton.activeSprites.GetComponent().color = new(0f, 0.847f, 1f, 1f); + __instance.EditButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0f, 1f, 1f, 0.5f); - instance.GameStartTextParent.SetActive(true); - instance.GameStartText.text = DestroyableSingleton.Instance.GetString(StringNames.GameStarting, num2); - if (num != num2) PlayerControl.LocalPlayer.RpcSetStartCounter(num2); - - if (num2 <= 0) instance.FinallyBegin(); - } - else - { - instance.StartButton.ChangeButtonText(DestroyableSingleton.Instance.GetString(StringNames.StartLabel)); - instance.StartButton.inactiveSprites.GetComponent().color = new(0.1f, 0.1f, 0.1f, 1f); - instance.StartButton.activeSprites.GetComponent().color = new(0.2f, 0.2f, 0.2f, 1f); - instance.StartButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0.3f, 0.3f, 0.3f, 0.5f); - instance.StartButton.activeTextColor = instance.StartButton.inactiveTextColor = Color.white; - instance.GameStartTextParent.SetActive(false); - instance.GameStartText.text = string.Empty; - } + __instance.HostViewButton.activeTextColor = __instance.HostViewButton.inactiveTextColor = Color.black; + __instance.HostViewButton.inactiveSprites.GetComponent().color = new(0f, 0.647f, 1f, 1f); + __instance.HostViewButton.activeSprites.GetComponent().color = new(0f, 0.847f, 1f, 1f); + __instance.HostViewButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0f, 1f, 1f, 0.5f); } - if (instance.LobbyInfoPane.gameObject.activeSelf && DestroyableSingleton.Instance.Chat.IsOpenOrOpening) instance.LobbyInfoPane.DeactivatePane(); + if (AmongUsClient.Instance == null || AmongUsClient.Instance.IsGameStarted || GameStates.IsInGame || __instance.startState == GameStartManager.StartingStates.Starting) return; - instance.LobbyInfoPane.gameObject.SetActive(!DestroyableSingleton.Instance.Chat.IsOpenOrOpening); - } + __instance.GameRoomNameCode.text = GameCode.IntToGameName(AmongUsClient.Instance.GameId); + // Reset lobby countdown timer + Timer = 600f; - public static void Postfix(GameStartManager __instance) - { - try + HideName = Object.Instantiate(__instance.GameRoomNameCode, __instance.GameRoomNameCode.transform); + + HideName.text = ColorUtility.TryParseHtmlString(Main.HideColor.Value, out _) + ? $"{Main.HideName.Value}" + : $"{Main.HideName.Value}"; + + if (!AmongUsClient.Instance.AmHost) return; + + if (ModUpdater.IsBroken || (ModUpdater.HasUpdate && ModUpdater.ForceUpdate) || !Main.AllowPublicRoom) { - if (AmongUsClient.Instance == null || AmongUsClient.Instance.IsGameStarted || GameStates.IsInGame || __instance == null || __instance.startState == GameStartManager.StartingStates.Starting) return; - - if (AmongUsClient.Instance.AmHost) - { - var canStartGame = true; - - foreach (ClientData client in AmongUsClient.Instance.allClients) - { - if (client.Character == null) continue; - - var dummyComponent = client.Character.GetComponent(); - if (dummyComponent != null && dummyComponent.enabled) continue; - - if (!MatchVersions(client.Character.PlayerId, true)) canStartGame = false; - } - - if (!canStartGame) __instance.StartButton.gameObject.SetActive(false); - } - else - { - if (MatchVersions(0, true)) - ExitTimer = 0; - else - { - ExitTimer += Time.deltaTime; - - if (ExitTimer >= 5) - { - ExitTimer = 0; - AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); - SceneChanger.ChangeScene("MainMenu"); - } - } - } - - __instance.RulesPresetText.text = GetString($"Preset_{OptionItem.CurrentPreset + 1}"); - - // Lobby timer - if (!GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return; - - Timer = Mathf.Max(0f, Timer -= Time.deltaTime); - int minutes = (int)Timer / 60; - int seconds = (int)Timer % 60; - var suffix = $"{minutes:00}:{seconds:00}"; - if (Timer <= 60) suffix = Utils.ColorString((int)Timer % 2 == 0 ? Color.yellow : Color.red, suffix); - - TextMeshPro tmp = GameStartManagerStartPatch.GameCountdown; - - if (tmp.text == string.Empty) - { - tmp.name = "LobbyTimer"; - tmp.fontSize = tmp.fontSizeMin = tmp.fontSizeMax = 3f; - tmp.autoSizeTextContainer = true; - tmp.alignment = TextAlignmentOptions.Center; - tmp.color = Color.cyan; - tmp.outlineColor = Color.black; - tmp.outlineWidth = 0.4f; - tmp.transform.localPosition += new Vector3(-0.8f, -0.42f, 0f); - tmp.transform.localScale = new(0.5f, 0.5f, 1f); - } - - tmp.text = suffix; - } - catch (NullReferenceException) { } - catch (Exception e) - { - Logger.Error(e.ToString(), "GameStartManagerUpdatePatch.Postfix (3)"); + // __instance.MakePublicButton.color = Palette.DisabledClear; + // __instance.privatePublicText.color = Palette.DisabledClear; } + + if (Main.NormalOptions.KillCooldown == 0f) Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; + + AURoleOptions.SetOpt(Main.NormalOptions.Cast()); + if (AURoleOptions.ShapeshifterCooldown == 0f) AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; + + AURoleOptions.GuardianAngelCooldown = Spiritcaller.SpiritAbilityCooldown.GetFloat(); + AURoleOptions.ProtectionDurationSeconds = 0f; } - - private static bool MatchVersions(byte playerId, bool acceptVanilla = false) + catch (Exception ex) { - if (!Main.PlayerVersion.TryGetValue(playerId, out PlayerVersion version)) return acceptVanilla; - - return Main.ForkId == version.forkId - && Main.Version.CompareTo(version.version) == 0 - && version.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})"; - } - } - - [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] - public static class HiddenTextPatch - { - private static void Postfix(TextBoxTMP __instance) - { - if (__instance.name == "GameIdText") __instance.outputText.text = new('*', __instance.text.Length); + Logger.Error(ex.ToString(), "GameStartManagerStartPatch.Postfix (1)"); } } } - [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.BeginGame))] - public static class GameStartRandomMap + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] + public static class GameStartManagerUpdatePatch + { + public static float ExitTimer = -1f; + private static float MinWait, MaxWait; + private static int MinPlayer; + + public static bool Prefix(GameStartManager __instance) + { + try + { + if (AmongUsClient.Instance == null) return false; + + if (AmongUsClient.Instance.AmHost) VanillaUpdate(__instance); + + if (AmongUsClient.Instance.IsGameStarted || GameStates.IsInGame || __instance == null || __instance.startState == GameStartManager.StartingStates.Starting) return false; + + MinWait = Options.MinWaitAutoStart.GetFloat(); + MaxWait = Options.MaxWaitAutoStart.GetFloat(); + MinPlayer = Options.PlayerAutoStart.GetInt(); + MinWait = 600f - (MinWait * 60f); + MaxWait *= 60f; + + // Lobby code + if (DataManager.Settings != null && DataManager.Settings.Gameplay != null) + { + if (DataManager.Settings.Gameplay.StreamerMode) + { + if (__instance != null && __instance.GameRoomNameCode != null) __instance.GameRoomNameCode.color = new(255, 255, 255, 0); + + if (GameStartManagerStartPatch.HideName != null) GameStartManagerStartPatch.HideName.enabled = true; + } + else + { + if (__instance != null && __instance.GameRoomNameCode != null) __instance.GameRoomNameCode.color = new(255, 255, 255, 255); + + if (GameStartManagerStartPatch.HideName != null) GameStartManagerStartPatch.HideName.enabled = false; + } + } + + if (AmongUsClient.Instance == null || GameData.Instance == null || !AmongUsClient.Instance.AmHost || !GameData.Instance) return true; + + if (Main.AutoStart != null && Main.AutoStart.Value) + { + Main.UpdateTime++; + + if (Main.UpdateTime >= 50) + { + Main.UpdateTime = 0; + + if (((GameData.Instance?.PlayerCount >= MinPlayer && Timer <= MinWait) || Timer <= MaxWait) && !GameStates.IsCountDown) + { + PlayerControl[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).ToArray(); + + if (invalidColor.Length > 0) + { + Main.AllPlayerControls + .Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId) + .Do(p => AmongUsClient.Instance.KickPlayer(p.GetClientId(), false)); + + Logger.SendInGame(GetString("Error.InvalidColorPreventStart")); + string msg = GetString("Error.InvalidColor"); + msg += "\n" + string.Join(",", invalidColor.Select(p => $"{p.GetRealName()}")); + Utils.SendMessage(msg); + } + + if (Options.RandomMapsMode.GetBool()) + { + Main.NormalOptions.MapId = GameStartRandomMap.SelectRandomMap(); + CreateOptionsPickerPatch.SetDleks = Main.CurrentMap == MapNames.Dleks; + } + else if (CreateOptionsPickerPatch.SetDleks) Main.NormalOptions.MapId = 3; + + if (Main.CurrentMap == MapNames.Dleks) + { + var opt = Main.NormalOptions.Cast(); + + Options.DefaultKillCooldown = Main.NormalOptions.KillCooldown; + Main.LastKillCooldown.Value = Main.NormalOptions.KillCooldown; + Main.NormalOptions.KillCooldown = 0f; + AURoleOptions.SetOpt(opt); + Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; + AURoleOptions.ShapeshifterCooldown = 0f; + AURoleOptions.ImpostorsCanSeeProtect = false; + + PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt, AprilFoolsMode.IsAprilFoolsModeToggledOn)); + } + + RPC.RpcVersionCheck(); + + GameStartManager.Instance.startState = GameStartManager.StartingStates.Countdown; + GameStartManager.Instance.countDownTimer = Options.AutoStartTimer.GetInt(); + __instance?.StartButton.gameObject.SetActive(false); + } + } + } + } + catch (NullReferenceException) { } + catch (Exception ex) + { + Logger.Error(ex.ToString(), "GameStartManagerUpdatePatch.Prefix (2)"); + } + + return false; + } + + private static void VanillaUpdate(GameStartManager instance) + { + if (!GameData.Instance || !GameManager.Instance || !AmongUsClient.Instance.AmHost) return; + + try + { + instance.UpdateMapImage((MapNames)GameManager.Instance.LogicOptions.MapId); + } + catch (Exception e) + { + ErrorText.Instance.AddError(ErrorCode.UnsupportedMap); + Utils.ThrowException(e); + } + + instance.CheckSettingsDiffs(); + instance.StartButton.gameObject.SetActive(true); + instance.RulesPresetText.text = DestroyableSingleton.Instance.GetString(GameOptionsManager.Instance.CurrentGameOptions.GetRulesPresetTitle()); + + if (GameCode.IntToGameName(AmongUsClient.Instance.GameId) == null) instance.privatePublicPanelText.text = DestroyableSingleton.Instance.GetString(StringNames.LocalButton); + else if (AmongUsClient.Instance.IsGamePublic) instance.privatePublicPanelText.text = DestroyableSingleton.Instance.GetString(StringNames.PublicHeader); + else instance.privatePublicPanelText.text = DestroyableSingleton.Instance.GetString(StringNames.PrivateHeader); + + instance.HostPrivateButton.gameObject.SetActive(!AmongUsClient.Instance.IsGamePublic); + instance.HostPublicButton.gameObject.SetActive(AmongUsClient.Instance.IsGamePublic); + + if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKeyDown(KeyCode.C)) + ClipboardHelper.PutClipboardString(GameCode.IntToGameName(AmongUsClient.Instance.GameId)); + + if (GameData.Instance.PlayerCount != instance.LastPlayerCount) + { + instance.LastPlayerCount = GameData.Instance.PlayerCount; + var text = ""; + if (instance.LastPlayerCount > instance.MinPlayers) text = ""; + if (instance.LastPlayerCount == instance.MinPlayers) text = ""; + + instance.PlayerCounter.text = $"{text}{instance.LastPlayerCount}/{(AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame ? 15 : GameManager.Instance.LogicOptions.MaxPlayers)}"; + instance.StartButton.SetButtonEnableState(instance.LastPlayerCount >= instance.MinPlayers); + ActionMapGlyphDisplay startButtonGlyph = instance.StartButtonGlyph; + startButtonGlyph?.SetColor(instance.LastPlayerCount >= instance.MinPlayers ? Palette.EnabledColor : Palette.DisabledClear); + + if (DestroyableSingleton.InstanceExists) + { + if (AmongUsClient.Instance.AmHost && AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame) + DestroyableSingleton.Instance.SetInLobbyHost(instance.LastPlayerCount, GameManager.Instance.LogicOptions.MaxPlayers, AmongUsClient.Instance.GameId); + else + DestroyableSingleton.Instance.SetInLobbyClient(instance.LastPlayerCount, GameManager.Instance.LogicOptions.MaxPlayers, AmongUsClient.Instance.GameId); + } + } + + if (AmongUsClient.Instance.AmHost) + { + if (instance.startState == GameStartManager.StartingStates.Countdown) + { + instance.StartButton.ChangeButtonText(GetString("Cancel")); + + instance.StartButton.inactiveSprites.GetComponent().color = new(0.8f, 0f, 0f, 1f); + instance.StartButton.activeSprites.GetComponent().color = Color.red; + instance.StartButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0.8f, 0.4f, 0.4f, 1f); + instance.StartButton.activeTextColor = instance.StartButton.inactiveTextColor = Color.white; + int num = Mathf.CeilToInt(instance.countDownTimer); + instance.countDownTimer -= Time.deltaTime; + int num2 = Mathf.CeilToInt(instance.countDownTimer); + if (!instance.GameStartTextParent.activeSelf) SoundManager.Instance.PlaySound(instance.gameStartSound, false); + + instance.GameStartTextParent.SetActive(true); + instance.GameStartText.text = DestroyableSingleton.Instance.GetString(StringNames.GameStarting, num2); + if (num != num2) PlayerControl.LocalPlayer.RpcSetStartCounter(num2); + + if (num2 <= 0) instance.FinallyBegin(); + } + else + { + instance.StartButton.ChangeButtonText(DestroyableSingleton.Instance.GetString(StringNames.StartLabel)); + instance.StartButton.inactiveSprites.GetComponent().color = new(0.1f, 0.1f, 0.1f, 1f); + instance.StartButton.activeSprites.GetComponent().color = new(0.2f, 0.2f, 0.2f, 1f); + instance.StartButton.inactiveSprites.transform.Find("Shine").GetComponent().color = new(0.3f, 0.3f, 0.3f, 0.5f); + instance.StartButton.activeTextColor = instance.StartButton.inactiveTextColor = Color.white; + instance.GameStartTextParent.SetActive(false); + instance.GameStartText.text = string.Empty; + } + } + + if (instance.LobbyInfoPane.gameObject.activeSelf && DestroyableSingleton.Instance.Chat.IsOpenOrOpening) instance.LobbyInfoPane.DeactivatePane(); + + instance.LobbyInfoPane.gameObject.SetActive(!DestroyableSingleton.Instance.Chat.IsOpenOrOpening); + } + + public static void Postfix(GameStartManager __instance) + { + try + { + if (AmongUsClient.Instance == null || AmongUsClient.Instance.IsGameStarted || GameStates.IsInGame || __instance == null || __instance.startState == GameStartManager.StartingStates.Starting) return; + + if (AmongUsClient.Instance.AmHost) + { + var canStartGame = true; + + foreach (ClientData client in AmongUsClient.Instance.allClients) + { + if (client.Character == null) continue; + + var dummyComponent = client.Character.GetComponent(); + if (dummyComponent != null && dummyComponent.enabled) continue; + + if (!MatchVersions(client.Character.PlayerId, true)) canStartGame = false; + } + + if (!canStartGame) __instance.StartButton.gameObject.SetActive(false); + } + else + { + if (MatchVersions(0, true)) + ExitTimer = 0; + else + { + ExitTimer += Time.deltaTime; + + if (ExitTimer >= 5) + { + ExitTimer = 0; + AmongUsClient.Instance.ExitGame(DisconnectReasons.ExitGame); + SceneChanger.ChangeScene("MainMenu"); + } + } + } + + __instance.RulesPresetText.text = GetString($"Preset_{OptionItem.CurrentPreset + 1}"); + + // Lobby timer + if (!GameData.Instance || AmongUsClient.Instance.NetworkMode == NetworkModes.LocalGame) return; + + Timer = Mathf.Max(0f, Timer -= Time.deltaTime); + int minutes = (int)Timer / 60; + int seconds = (int)Timer % 60; + var suffix = $"{minutes:00}:{seconds:00}"; + if (Timer <= 60) suffix = Utils.ColorString((int)Timer % 2 == 0 ? Color.yellow : Color.red, suffix); + + TextMeshPro tmp = GameStartManagerStartPatch.GameCountdown; + + if (tmp.text == string.Empty) + { + tmp.name = "LobbyTimer"; + tmp.fontSize = tmp.fontSizeMin = tmp.fontSizeMax = 3f; + tmp.autoSizeTextContainer = true; + tmp.alignment = TextAlignmentOptions.Center; + tmp.color = Color.cyan; + tmp.outlineColor = Color.black; + tmp.outlineWidth = 0.4f; + tmp.transform.localPosition += new Vector3(-0.8f, -0.42f, 0f); + tmp.transform.localScale = new(0.5f, 0.5f, 1f); + } + + tmp.text = suffix; + } + catch (NullReferenceException) { } + catch (Exception e) + { + Logger.Error(e.ToString(), "GameStartManagerUpdatePatch.Postfix (3)"); + } + } + + private static bool MatchVersions(byte playerId, bool acceptVanilla = false) + { + if (!Main.PlayerVersion.TryGetValue(playerId, out PlayerVersion version)) return acceptVanilla; + + return Main.ForkId == version.forkId + && Main.Version.CompareTo(version.version) == 0 + && version.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})"; + } + } + + [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] + public static class HiddenTextPatch + { + private static void Postfix(TextBoxTMP __instance) + { + if (__instance.name == "GameIdText") __instance.outputText.text = new('*', __instance.text.Length); + } + } +} + +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.BeginGame))] +public static class GameStartRandomMap +{ + public static bool Prefix(GameStartManager __instance) + { + PlayerControl[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).ToArray(); + + if (invalidColor.Length > 0) + { + Logger.SendInGame(GetString("Error.InvalidColorPreventStart")); + string msg = GetString("Error.InvalidColor"); + msg += "\n" + string.Join(",", invalidColor.Select(p => $"{p.GetRealName()}")); + Utils.SendMessage(msg); + return false; + } + + if (__instance.startState == GameStartManager.StartingStates.Countdown) + Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; + else + { + Options.DefaultKillCooldown = Main.NormalOptions.KillCooldown; + Main.LastKillCooldown.Value = Main.NormalOptions.KillCooldown; + Main.NormalOptions.KillCooldown = 0f; + } + + var opt = Main.NormalOptions.Cast(); + AURoleOptions.SetOpt(opt); + + if (__instance.startState == GameStartManager.StartingStates.Countdown) + AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; + else + { + Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; + AURoleOptions.ShapeshifterCooldown = 0f; + } + + PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt, AprilFoolsMode.IsAprilFoolsModeToggledOn)); + + __instance.ReallyBegin(false); + return false; + } + + public static void Prefix( /*GameStartRandomMap __instance*/) + { + if (Options.RandomMapsMode.GetBool()) + { + Main.NormalOptions.MapId = SelectRandomMap(); + CreateOptionsPickerPatch.SetDleks = Main.CurrentMap == MapNames.Dleks; + } + else if (CreateOptionsPickerPatch.SetDleks) Main.NormalOptions.MapId = 3; + } + + public static byte SelectRandomMap() + { + var rand = IRandom.Instance; + List randomMaps = []; + + int tempRand = rand.Next(1, 100); + + if (tempRand <= Options.SkeldChance.GetInt()) randomMaps.Add(0); + + if (tempRand <= Options.MiraChance.GetInt()) randomMaps.Add(1); + + if (tempRand <= Options.PolusChance.GetInt()) randomMaps.Add(2); + + if (tempRand <= Options.DleksChance.GetInt()) randomMaps.Add(3); + + if (tempRand <= Options.AirshipChance.GetInt()) randomMaps.Add(4); + + if (tempRand <= Options.FungleChance.GetInt()) randomMaps.Add(5); + + if (randomMaps.Count > 0) + { + byte mapsId = randomMaps[0]; + + Logger.Info($"{mapsId}", "Chance Select MapId"); + return mapsId; + } + else + { + if (Options.SkeldChance.GetInt() > 0) randomMaps.Add(0); + + if (Options.MiraChance.GetInt() > 0) randomMaps.Add(1); + + if (Options.PolusChance.GetInt() > 0) randomMaps.Add(2); + + if (Options.DleksChance.GetInt() > 0) randomMaps.Add(3); + + if (Options.AirshipChance.GetInt() > 0) randomMaps.Add(4); + + if (Options.FungleChance.GetInt() > 0) randomMaps.Add(5); + + byte mapsId = randomMaps.RandomElement(); + + Logger.Info($"{mapsId}", "Random Select MapId"); + return mapsId; + } + } +} + +[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ResetStartState))] +internal static class ResetStartStatePatch +{ + public static void Prefix(GameStartManager __instance) + { + SoundManager.Instance.StopSound(__instance.gameStartSound); + + if (__instance.startState == GameStartManager.StartingStates.Countdown) + { + Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; + PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameOptionsManager.Instance.CurrentGameOptions, AprilFoolsMode.IsAprilFoolsModeToggledOn)); + } + } +} + +[HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.GetAdjustedNumImpostors))] +internal static class UnrestrictedNumImpostorsPatch +{ + public static bool Prefix(ref int __result) + { + __result = Main.NormalOptions.NumImpostors; + return false; + } +} + +public static class GameStartManagerBeginPatch +{ + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ReallyBegin))] + public static class GameStartManagerStartPatch { public static bool Prefix(GameStartManager __instance) { - PlayerControl[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).ToArray(); + if (!AmongUsClient.Instance.AmHost) return true; - if (invalidColor.Length > 0) + if (__instance.startState == GameStartManager.StartingStates.Countdown) { - Logger.SendInGame(GetString("Error.InvalidColorPreventStart")); - string msg = GetString("Error.InvalidColor"); - msg += "\n" + string.Join(",", invalidColor.Select(p => $"{p.GetRealName()}")); - Utils.SendMessage(msg); + __instance.ResetStartState(); return false; } - if (__instance.startState == GameStartManager.StartingStates.Countdown) - Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; - else - { - Options.DefaultKillCooldown = Main.NormalOptions.KillCooldown; - Main.LastKillCooldown.Value = Main.NormalOptions.KillCooldown; - Main.NormalOptions.KillCooldown = 0f; - } - - var opt = Main.NormalOptions.Cast(); - AURoleOptions.SetOpt(opt); - - if (__instance.startState == GameStartManager.StartingStates.Countdown) - AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; - else - { - Main.LastShapeshifterCooldown.Value = AURoleOptions.ShapeshifterCooldown; - AURoleOptions.ShapeshifterCooldown = 0f; - } - - PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(opt, AprilFoolsMode.IsAprilFoolsModeToggledOn)); - - __instance.ReallyBegin(false); + __instance.startState = GameStartManager.StartingStates.Countdown; + __instance.GameSizePopup.SetActive(false); + DataManager.Player.Onboarding.AlwaysShowMinPlayerWarning = false; + DataManager.Player.Onboarding.ViewedMinPlayerWarning = true; + DataManager.Player.Save(); + __instance.StartButton.gameObject.SetActive(false); + __instance.StartButtonClient.gameObject.SetActive(false); + __instance.GameStartTextParent.SetActive(false); + __instance.countDownTimer = 5.0001f; + __instance.startState = GameStartManager.StartingStates.Countdown; + AmongUsClient.Instance.KickNotJoinedPlayers(); return false; } - - public static void Prefix( /*GameStartRandomMap __instance*/) - { - if (Options.RandomMapsMode.GetBool()) - { - Main.NormalOptions.MapId = SelectRandomMap(); - CreateOptionsPickerPatch.SetDleks = Main.CurrentMap == MapNames.Dleks; - } - else if (CreateOptionsPickerPatch.SetDleks) Main.NormalOptions.MapId = 3; - } - - public static byte SelectRandomMap() - { - var rand = IRandom.Instance; - List randomMaps = []; - - int tempRand = rand.Next(1, 100); - - if (tempRand <= Options.SkeldChance.GetInt()) randomMaps.Add(0); - - if (tempRand <= Options.MiraChance.GetInt()) randomMaps.Add(1); - - if (tempRand <= Options.PolusChance.GetInt()) randomMaps.Add(2); - - if (tempRand <= Options.DleksChance.GetInt()) randomMaps.Add(3); - - if (tempRand <= Options.AirshipChance.GetInt()) randomMaps.Add(4); - - if (tempRand <= Options.FungleChance.GetInt()) randomMaps.Add(5); - - if (randomMaps.Count > 0) - { - byte mapsId = randomMaps[0]; - - Logger.Info($"{mapsId}", "Chance Select MapId"); - return mapsId; - } - else - { - if (Options.SkeldChance.GetInt() > 0) randomMaps.Add(0); - - if (Options.MiraChance.GetInt() > 0) randomMaps.Add(1); - - if (Options.PolusChance.GetInt() > 0) randomMaps.Add(2); - - if (Options.DleksChance.GetInt() > 0) randomMaps.Add(3); - - if (Options.AirshipChance.GetInt() > 0) randomMaps.Add(4); - - if (Options.FungleChance.GetInt() > 0) randomMaps.Add(5); - - byte mapsId = randomMaps.RandomElement(); - - Logger.Info($"{mapsId}", "Random Select MapId"); - return mapsId; - } - } - } - - [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ResetStartState))] - internal static class ResetStartStatePatch - { - public static void Prefix(GameStartManager __instance) - { - SoundManager.Instance.StopSound(__instance.gameStartSound); - - if (__instance.startState == GameStartManager.StartingStates.Countdown) - { - Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; - PlayerControl.LocalPlayer.RpcSyncSettings(GameOptionsManager.Instance.gameOptionsFactory.ToBytes(GameOptionsManager.Instance.CurrentGameOptions, AprilFoolsMode.IsAprilFoolsModeToggledOn)); - } - } - } - - [HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.GetAdjustedNumImpostors))] - internal static class UnrestrictedNumImpostorsPatch - { - public static bool Prefix(ref int __result) - { - __result = Main.NormalOptions.NumImpostors; - return false; - } - } - - public static class GameStartManagerBeginPatch - { - [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ReallyBegin))] - public static class GameStartManagerStartPatch - { - public static bool Prefix(GameStartManager __instance) - { - if (!AmongUsClient.Instance.AmHost) return true; - - if (__instance.startState == GameStartManager.StartingStates.Countdown) - { - __instance.ResetStartState(); - return false; - } - - __instance.startState = GameStartManager.StartingStates.Countdown; - __instance.GameSizePopup.SetActive(false); - DataManager.Player.Onboarding.AlwaysShowMinPlayerWarning = false; - DataManager.Player.Onboarding.ViewedMinPlayerWarning = true; - DataManager.Player.Save(); - __instance.StartButton.gameObject.SetActive(false); - __instance.StartButtonClient.gameObject.SetActive(false); - __instance.GameStartTextParent.SetActive(false); - __instance.countDownTimer = 5.0001f; - __instance.startState = GameStartManager.StartingStates.Countdown; - AmongUsClient.Instance.KickNotJoinedPlayers(); - return false; - } - } } } \ No newline at end of file diff --git a/Patches/GuardAngelPatch.cs b/Patches/GuardAngelPatch.cs index 665f13c61..28321e473 100644 --- a/Patches/GuardAngelPatch.cs +++ b/Patches/GuardAngelPatch.cs @@ -1,19 +1,18 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +internal class GuardAngelPatch { - internal class GuardAngelPatch + [HarmonyPatch(typeof(MeetingIntroAnimation), nameof(MeetingIntroAnimation.Start))] + public static class ProtectedRecentlyPatch { - [HarmonyPatch(typeof(MeetingIntroAnimation), nameof(MeetingIntroAnimation.Start))] - public static class ProtectedRecentlyPatch + public static bool Prefix(MeetingIntroAnimation __instance) { - public static bool Prefix(MeetingIntroAnimation __instance) - { - __instance.ProtectedRecently.active = false; - __instance.ProtectedRecently.transform.localPosition = new(100f, 100f, 100f); - __instance.ProtectedRecentlySound = new(); - return true; - } + __instance.ProtectedRecently.active = false; + __instance.ProtectedRecently.transform.localPosition = new(100f, 100f, 100f); + __instance.ProtectedRecentlySound = new(); + return true; } } } \ No newline at end of file diff --git a/Patches/HashRandomPatch.cs b/Patches/HashRandomPatch.cs index c56e36cbc..4a277e295 100644 --- a/Patches/HashRandomPatch.cs +++ b/Patches/HashRandomPatch.cs @@ -1,41 +1,40 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(HashRandom))] +internal class HashRandomPatch { - [HarmonyPatch(typeof(HashRandom))] - internal class HashRandomPatch + [HarmonyPatch(nameof(HashRandom.FastNext))] + [HarmonyPrefix] + private static bool FastNext([HarmonyArgument(0)] int maxInt, ref int __result) { - [HarmonyPatch(nameof(HashRandom.FastNext))] - [HarmonyPrefix] - private static bool FastNext([HarmonyArgument(0)] int maxInt, ref int __result) - { - if (IRandom.Instance is HashRandomWrapper) return true; + if (IRandom.Instance is HashRandomWrapper) return true; - __result = IRandom.Instance.Next(maxInt); + __result = IRandom.Instance.Next(maxInt); - return false; - } + return false; + } - [HarmonyPatch(nameof(HashRandom.Next), typeof(int))] - [HarmonyPrefix] - private static bool MaxNext([HarmonyArgument(0)] int maxInt, ref int __result) - { - if (IRandom.Instance is HashRandomWrapper) return true; + [HarmonyPatch(nameof(HashRandom.Next), typeof(int))] + [HarmonyPrefix] + private static bool MaxNext([HarmonyArgument(0)] int maxInt, ref int __result) + { + if (IRandom.Instance is HashRandomWrapper) return true; - __result = IRandom.Instance.Next(maxInt); + __result = IRandom.Instance.Next(maxInt); - return false; - } + return false; + } - [HarmonyPatch(nameof(HashRandom.Next), typeof(int), typeof(int))] - [HarmonyPrefix] - private static bool MinMaxNext([HarmonyArgument(0)] int minInt, [HarmonyArgument(1)] int maxInt, ref int __result) - { - if (IRandom.Instance is HashRandomWrapper) return true; + [HarmonyPatch(nameof(HashRandom.Next), typeof(int), typeof(int))] + [HarmonyPrefix] + private static bool MinMaxNext([HarmonyArgument(0)] int minInt, [HarmonyArgument(1)] int maxInt, ref int __result) + { + if (IRandom.Instance is HashRandomWrapper) return true; - __result = IRandom.Instance.Next(minInt, maxInt); + __result = IRandom.Instance.Next(minInt, maxInt); - return false; - } + return false; } } \ No newline at end of file diff --git a/Patches/HideBanButtonPatch.cs b/Patches/HideBanButtonPatch.cs index d13beb58e..4c73a12df 100644 --- a/Patches/HideBanButtonPatch.cs +++ b/Patches/HideBanButtonPatch.cs @@ -1,13 +1,12 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(ChatController), nameof(ChatController.Toggle))] +internal class CancelBanMenuStuckPatch { - [HarmonyPatch(typeof(ChatController), nameof(ChatController.Toggle))] - internal class CancelBanMenuStuckPatch + public static void Prefix(ChatController __instance) { - public static void Prefix(ChatController __instance) - { - if (__instance.IsOpenOrOpening && !__instance.IsAnimating) __instance.banButton.SetVisible(false); - } + if (__instance.IsOpenOrOpening && !__instance.IsAnimating) __instance.banButton.SetVisible(false); } } \ No newline at end of file diff --git a/Patches/HudPatch.cs b/Patches/HudPatch.cs index 459298d02..f9f21bce2 100644 --- a/Patches/HudPatch.cs +++ b/Patches/HudPatch.cs @@ -12,925 +12,924 @@ using UnityEngine; using static EHR.Translator; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] +internal static class HudManagerPatch { - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] - internal static class HudManagerPatch + private static TextMeshPro LowerInfoText; + private static TextMeshPro OverriddenRolesText; + private static TextMeshPro SettingsText; + private static long LastNullError; + + public static Color? CooldownTimerFlashColor = null; + + public static string AchievementUnlockedText = string.Empty; + + public static void ClearLowerInfoText() => LowerInfoText.text = string.Empty; + + public static bool Prefix(HudManager __instance) { - private static TextMeshPro LowerInfoText; - private static TextMeshPro OverriddenRolesText; - private static TextMeshPro SettingsText; - private static long LastNullError; + if (PlayerControl.LocalPlayer != null) return true; - public static Color? CooldownTimerFlashColor = null; + __instance.taskDirtyTimer += Time.deltaTime; + if (__instance.taskDirtyTimer <= 0.25) return false; - public static string AchievementUnlockedText = string.Empty; - - public static void ClearLowerInfoText() => LowerInfoText.text = string.Empty; - - public static bool Prefix(HudManager __instance) - { - if (PlayerControl.LocalPlayer != null) return true; - - __instance.taskDirtyTimer += Time.deltaTime; - if (__instance.taskDirtyTimer <= 0.25) return false; - - __instance.taskDirtyTimer = 0.0f; - __instance.TaskPanel?.SetTaskText(string.Empty); - return false; - } - - public static void Postfix(HudManager __instance) - { - try - { - LoadingScreen.Update(); - - PlayerControl player = PlayerControl.LocalPlayer; - if (player == null) return; - - if (Input.GetKeyDown(KeyCode.LeftControl)) - if ((!AmongUsClient.Instance.IsGameStarted || !GameStates.IsOnlineGame) && player.CanMove) - player.Collider.offset = new(0f, 127f); - - if (Math.Abs(player.Collider.offset.y - 127f) < 0.1f) - if (!Input.GetKey(KeyCode.LeftControl) || (AmongUsClient.Instance.IsGameStarted && GameStates.IsOnlineGame)) - player.Collider.offset = new(0f, -0.3636f); - - if (__instance == null) return; - - if (GameStates.IsLobby) - { - if (PingTrackerUpdatePatch.Instance != null) - { - if (SettingsText != null) Object.Destroy(SettingsText.gameObject); - - SettingsText = Object.Instantiate(PingTrackerUpdatePatch.Instance.text, __instance.transform, true); - SettingsText.alignment = TextAlignmentOptions.TopLeft; - SettingsText.verticalAlignment = VerticalAlignmentOptions.Top; - SettingsText.transform.localPosition = new(-4.9f, 2.9f, 0); - SettingsText.fontSize = SettingsText.fontSizeMin = SettingsText.fontSizeMax = 1.5f; - } - - if (SettingsText != null) - { - SettingsText.text = OptionShower.GetTextNoFresh(); - SettingsText.enabled = SettingsText.text != string.Empty; - } - } - else if (SettingsText != null) Object.Destroy(SettingsText.gameObject); - - if (AmongUsClient.Instance.AmHost) - { - if (OverriddenRolesText == null) - { - OverriddenRolesText = Object.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform, true); - OverriddenRolesText.alignment = TextAlignmentOptions.Right; - OverriddenRolesText.verticalAlignment = VerticalAlignmentOptions.Top; - OverriddenRolesText.transform.localPosition = new(2.5f, 2.5f, 0); - OverriddenRolesText.overflowMode = TextOverflowModes.Overflow; - OverriddenRolesText.enableWordWrapping = false; - OverriddenRolesText.color = Color.white; - OverriddenRolesText.fontSize = OverriddenRolesText.fontSizeMax = OverriddenRolesText.fontSizeMin = 2.5f; - } - - if (Main.SetRoles.Count > 0 || Main.SetAddOns.Count > 0) - { - Dictionary resultText = []; - var first = true; - - foreach (KeyValuePair item in Main.SetRoles) - { - PlayerControl pc = Utils.GetPlayerById(item.Key); - string prefix = first ? string.Empty : "\n"; - var text = $"{prefix}{(item.Key == 0 ? "Host" : $"{(pc == null ? $"ID {item.Key}" : $"{pc.GetRealName()}")}")} - {GetString(item.Value.ToString())}"; - resultText[item.Key] = text; - first = false; - } - - if (Main.SetRoles.Count == 0) first = true; - - foreach (KeyValuePair> item in Main.SetAddOns) - { - foreach (CustomRoles role in item.Value) - { - PlayerControl pc = Utils.GetPlayerById(item.Key); - - if (resultText.ContainsKey(item.Key)) - { - var text = $" <#ffffff>({GetString(role.ToString())}<#ffffff>)"; - resultText[item.Key] += text; - } - else - { - string prefix = first ? string.Empty : "\n"; - var text = $"{prefix}{(item.Key == 0 ? "Host" : $"{(pc == null ? $"ID {item.Key}" : $"{pc.GetRealName()}")}")} - <#ffffff>({GetString(role.ToString())}<#ffffff>)"; - resultText[item.Key] = text; - first = false; - } - } - } - - OverriddenRolesText.text = string.Join(string.Empty, resultText.Values); - } - else - OverriddenRolesText.text = string.Empty; - - OverriddenRolesText.enabled = OverriddenRolesText.text != string.Empty; - } - else if (GameStates.IsLobby) - { - new ActionButton[] - { - __instance.ReportButton, - __instance.KillButton, - __instance.AbilityButton, - __instance.ImpostorVentButton, - __instance.SabotageButton - }.Do(x => x?.Hide()); - } - else if (!CustomGameMode.Standard.IsActiveOrIntegrated()) __instance.ReportButton?.Hide(); - - // The following will not be executed unless the game is in progress - if (!AmongUsClient.Instance.IsGameStarted) return; - - Utils.CountAlivePlayers(); - - bool shapeshifting = player.IsShifted(); - - if (SetHudActivePatch.IsActive) - { - if (player.IsAlive() || !CustomGameMode.Standard.IsActiveOrIntegrated()) - { - if (player.Data.Role is ShapeshifterRole ssrole) - { - float timer = shapeshifting ? ssrole.durationSecondsRemaining : ssrole.cooldownSecondsRemaining; - AbilityButton button = __instance.AbilityButton; - - if (timer > 0f) - { - Color color = shapeshifting ? Color.green : Color.white; - button.cooldownTimerText.text = Utils.ColorString(color, Mathf.CeilToInt(timer).ToString()); - button.cooldownTimerText.gameObject.SetActive(true); - } - } - - CustomRoles role = player.GetCustomRole(); - - bool usesPetInsteadOfKill = role.UsesPetInsteadOfKill(); - if (usesPetInsteadOfKill) __instance.PetButton?.OverrideText(GetString("KillButtonText")); - - ActionButton usedButton = __instance.KillButton; - if (usesPetInsteadOfKill) usedButton = __instance.PetButton; - - __instance.KillButton?.OverrideText(GetString("KillButtonText")); - __instance.ReportButton?.OverrideText(GetString("ReportButtonText")); - __instance.PetButton?.OverrideText(GetString("PetButtonText")); - __instance.ImpostorVentButton?.OverrideText(GetString("VentButtonText")); - __instance.SabotageButton?.OverrideText(GetString("SabotageButtonText")); - - RoleTypes roleTypes = player.GetRoleTypes(); - __instance.AbilityButton?.OverrideText(GetString($"AbilityButtonText.{roleTypes}")); - - PlayerState state = Main.PlayerStates[player.PlayerId]; - - state.Role.SetButtonTexts(__instance, player.PlayerId); - - switch (role) - { - case CustomRoles.Farseer: - case CustomRoles.Escort: - case CustomRoles.Gaulois: - case CustomRoles.Sheriff: - case CustomRoles.Crusader: - case CustomRoles.Monarch: - usedButton?.OverrideText(GetString($"{role}KillButtonText")); - break; - case CustomRoles.Analyst: - usedButton?.OverrideText(GetString("AnalyzerKillButtonText")); - break; - case CustomRoles.Aid: - case CustomRoles.DonutDelivery: - case CustomRoles.Medic: - usedButton?.OverrideText(GetString("MedicalerButtonText")); - break; - case CustomRoles.KB_Normal: - __instance.KillButton?.OverrideText(GetString("GamerButtonText")); - break; - case CustomRoles.Deputy: - usedButton?.OverrideText(GetString("DeputyHandcuffText")); - break; - case CustomRoles.CTFPlayer: - __instance.AbilityButton?.OverrideText(GetString("CTF_ButtonText")); - break; - case CustomRoles.RRPlayer when __instance.AbilityButton != null && __instance.AbilityButton && RoomRush.VentLimit.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var ventLimit): - __instance.AbilityButton.SetUsesRemaining(ventLimit); - break; - } - - if (role.PetActivatedAbility() && CustomGameMode.Standard.IsActiveOrIntegrated() && !role.OnlySpawnsWithPets() && !role.AlwaysUsesUnshift() && !player.GetCustomSubRoles().Any(StartGameHostPatch.BasisChangingAddons.ContainsKey) && role != CustomRoles.Changeling && !Options.UseUnshiftTrigger.GetBool() && !(player.IsNeutralKiller() && Options.UseUnshiftTriggerForNKs.GetBool())) - __instance.AbilityButton?.Hide(); - - if (LowerInfoText == null) - { - LowerInfoText = Object.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform, true); - LowerInfoText.alignment = TextAlignmentOptions.Center; - LowerInfoText.transform.localPosition = new(0, -2f, 0); - LowerInfoText.overflowMode = TextOverflowModes.Overflow; - LowerInfoText.enableWordWrapping = false; - LowerInfoText.color = Color.white; - LowerInfoText.fontSize = LowerInfoText.fontSizeMax = LowerInfoText.fontSizeMin = 2.7f; - } - - LowerInfoText.text = Options.CurrentGameMode switch - { - CustomGameMode.SoloKombat => SoloKombatManager.GetHudText(), - CustomGameMode.FFA when player.IsHost() => FFAManager.GetHudText(), - CustomGameMode.MoveAndStop when player.IsHost() => MoveAndStop.HUDText, - CustomGameMode.HotPotato when player.IsHost() => HotPotato.GetSuffixText(player.PlayerId), - CustomGameMode.HideAndSeek when player.IsHost() => HnSManager.GetSuffixText(player, player, true), - CustomGameMode.NaturalDisasters => NaturalDisasters.SuffixText(), - CustomGameMode.AllInOne => $"{NaturalDisasters.SuffixText()}\n{HotPotato.GetSuffixText(player.PlayerId)}", - CustomGameMode.Standard => state.Role.GetSuffix(player, player, true, GameStates.IsMeeting) + GetAddonSuffixes(), - _ => string.Empty - }; - - string GetAddonSuffixes() - { - IEnumerable suffixes = state.SubRoles.Select(s => s switch - { - CustomRoles.Asthmatic => Asthmatic.GetSuffixText(player.PlayerId), - CustomRoles.Spurt => Spurt.GetSuffix(player, true), - CustomRoles.Deadlined => Deadlined.GetSuffix(player, true), - CustomRoles.Introvert => Introvert.GetSelfSuffix(player), - _ => string.Empty - }); - - return string.Join(string.Empty, suffixes); - } - - string CD_HUDText = !Options.UsePets.GetBool() || !Main.AbilityCD.TryGetValue(player.PlayerId, out (long StartTimeStamp, int TotalCooldown) CD) - ? string.Empty - : string.Format(GetString("CDPT"), CD.TotalCooldown - (Utils.TimeStamp - CD.StartTimeStamp) + 1); - - bool hasCD = CD_HUDText != string.Empty; - - if (hasCD) - { - if (CooldownTimerFlashColor.HasValue) CD_HUDText = $"{Utils.ColorString(CooldownTimerFlashColor.Value, CD_HUDText.RemoveHtmlTags())}"; - - LowerInfoText.text = $"{CD_HUDText}\n{LowerInfoText.text}"; - } - - if (AchievementUnlockedText != string.Empty) - { - LowerInfoText.text = LowerInfoText.text == string.Empty - ? AchievementUnlockedText - : $"{AchievementUnlockedText}\n\n{LowerInfoText.text}\n\n\n\n"; - } - - LowerInfoText.enabled = hasCD || LowerInfoText.text != string.Empty; - - if ((!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) || GameStates.IsMeeting) LowerInfoText.enabled = false; - - bool allowedRole = role is CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee or CustomRoles.Sidekick; - - if (player.CanUseKillButton() && (allowedRole || !role.UsesPetInsteadOfKill())) - { - __instance.KillButton?.ToggleVisible(player.IsAlive() && GameStates.IsInTask); - player.Data.Role.CanUseKillButton = true; - } - else - { - __instance.KillButton?.SetDisabled(); - __instance.KillButton?.ToggleVisible(false); - } - - __instance.ImpostorVentButton?.ToggleVisible((player.CanUseImpostorVentButton() || (player.inVent && player.GetRoleTypes() != RoleTypes.Engineer)) && GameStates.IsInTask); - player.Data.Role.CanVent = player.CanUseVent(); - - if ((usesPetInsteadOfKill && player.Is(CustomRoles.Nimble) && player.GetRoleTypes() == RoleTypes.Engineer) || player.Is(CustomRoles.GM)) __instance.AbilityButton.SetEnabled(); - } - else - { - __instance.ReportButton?.Hide(); - __instance.ImpostorVentButton?.Hide(); - __instance.KillButton?.Hide(); - __instance.AbilityButton?.Show(); - __instance.AbilityButton?.OverrideText(GetString(StringNames.HauntAbilityName)); - } - } - - - if (Input.GetKeyDown(KeyCode.Y) && AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) - { - __instance.ToggleMapVisible(new() - { - Mode = MapOptions.Modes.Sabotage, - AllowMovementWhileMapOpen = true - }); - - if (player.AmOwner) - { - player.MyPhysics.inputHandler.enabled = true; - ConsoleJoystick.SetMode_Task(); - } - } - - if (AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame) RepairSender.Enabled = false; - - if (Input.GetKeyDown(KeyCode.RightShift) && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) - { - RepairSender.Enabled = !RepairSender.Enabled; - RepairSender.Reset(); - } - - if (RepairSender.Enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) - { - if (Input.GetKeyDown(KeyCode.Alpha0)) RepairSender.Input(0); - - if (Input.GetKeyDown(KeyCode.Alpha1)) RepairSender.Input(1); - - if (Input.GetKeyDown(KeyCode.Alpha2)) RepairSender.Input(2); - - if (Input.GetKeyDown(KeyCode.Alpha3)) RepairSender.Input(3); - - if (Input.GetKeyDown(KeyCode.Alpha4)) RepairSender.Input(4); - - if (Input.GetKeyDown(KeyCode.Alpha5)) RepairSender.Input(5); - - if (Input.GetKeyDown(KeyCode.Alpha6)) RepairSender.Input(6); - - if (Input.GetKeyDown(KeyCode.Alpha7)) RepairSender.Input(7); - - if (Input.GetKeyDown(KeyCode.Alpha8)) RepairSender.Input(8); - - if (Input.GetKeyDown(KeyCode.Alpha9)) RepairSender.Input(9); - - if (Input.GetKeyDown(KeyCode.Return)) RepairSender.InputEnter(); - } - } - catch (NullReferenceException e) - { - if (LastNullError >= Utils.TimeStamp) return; - - LastNullError = Utils.TimeStamp + 2; - Utils.ThrowException(e); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } + __instance.taskDirtyTimer = 0.0f; + __instance.TaskPanel?.SetTaskText(string.Empty); + return false; } - [HarmonyPatch(typeof(ActionButton), nameof(ActionButton.SetFillUp))] - internal static class ActionButtonSetFillUpPatch + public static void Postfix(HudManager __instance) { - public static void Postfix(ActionButton __instance, [HarmonyArgument(0)] float timer) + try { - if (__instance.isCoolingDown && timer is <= 90f and > 0f) - { - RoleTypes roleType = PlayerControl.LocalPlayer.GetRoleTypes(); - - bool usingAbility = roleType switch - { - RoleTypes.Engineer => PlayerControl.LocalPlayer.inVent || VentButtonDoClickPatch.Animating, - RoleTypes.Shapeshifter => PlayerControl.LocalPlayer.IsShifted(), - _ => false - }; - - Color color = usingAbility ? Color.green : Color.white; - __instance.cooldownTimerText.text = Utils.ColorString(color, Mathf.CeilToInt(timer).ToString()); - __instance.cooldownTimerText.gameObject.SetActive(true); - } - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ToggleHighlight))] - internal static class ToggleHighlightPatch - { - private static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); - - public static void Postfix(PlayerControl __instance /*[HarmonyArgument(0)] bool active,*/ /*[HarmonyArgument(1)] RoleTeamTypes team*/) - { - if (!GameStates.IsInTask) return; - - PlayerControl player = PlayerControl.LocalPlayer; - - if (player.CanUseKillButton()) __instance.cosmetics.currentBodySprite.BodySprite.material.SetColor(OutlineColor, Utils.GetRoleColor(player.GetCustomRole())); - } - } - - [HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))] - internal static class SetVentOutlinePatch - { - private static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); - private static readonly int AddColor = Shader.PropertyToID("_AddColor"); - - public static void Postfix(Vent __instance, [HarmonyArgument(1)] ref bool mainTarget) - { - Color color = PlayerControl.LocalPlayer.GetRoleColor(); - __instance.myRend.material.SetColor(OutlineColor, color); - __instance.myRend.material.SetColor(AddColor, mainTarget ? color : Color.clear); - } - } - - [HarmonyPatch(typeof(HudManager), nameof(HudManager.SetHudActive), typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool))] - internal static class SetHudActivePatch - { - public static bool IsActive; - - public static void Prefix( /*HudManager __instance,*/ [HarmonyArgument(2)] ref bool isActive) - { - isActive &= !GameStates.IsMeeting; - } - - public static void Postfix(HudManager __instance, [HarmonyArgument(2)] bool isActive) - { - __instance?.ReportButton?.ToggleVisible(!GameStates.IsLobby && isActive); - - if (__instance == null) - { - Logger.Fatal("HudManager __instance ended up being null", "SetHudActivePatch.Postfix"); - return; - } - - IsActive = isActive; - if (!isActive) return; - - switch (Options.CurrentGameMode) - { - case CustomGameMode.MoveAndStop: - case CustomGameMode.HotPotato: - case CustomGameMode.Speedrun: - case CustomGameMode.NaturalDisasters: - __instance.ReportButton?.ToggleVisible(false); - __instance.KillButton?.ToggleVisible(false); - __instance.SabotageButton?.ToggleVisible(false); - __instance.ImpostorVentButton?.ToggleVisible(false); - __instance.AbilityButton?.ToggleVisible(false); - return; - case CustomGameMode.FFA: - __instance.ReportButton?.ToggleVisible(false); - __instance.SabotageButton?.ToggleVisible(false); - __instance.AbilityButton?.ToggleVisible(false); - return; - case CustomGameMode.RoomRush: - __instance.ImpostorVentButton?.ToggleVisible(false); - goto case CustomGameMode.CaptureTheFlag; - case CustomGameMode.CaptureTheFlag: - case CustomGameMode.HideAndSeek: - __instance.ReportButton?.ToggleVisible(false); - __instance.SabotageButton?.ToggleVisible(false); - return; - } + LoadingScreen.Update(); PlayerControl player = PlayerControl.LocalPlayer; if (player == null) return; - switch (player.GetCustomRole()) - { - case CustomRoles.Sheriff: - case CustomRoles.Arsonist: - case CustomRoles.SwordsMan: - case CustomRoles.Deputy: - case CustomRoles.Monarch: - case CustomRoles.Pelican: - case CustomRoles.FFF: - case CustomRoles.Medic: - case CustomRoles.Gamer: - case CustomRoles.DarkHide: - case CustomRoles.Farseer: - case CustomRoles.Crusader: - __instance.SabotageButton?.ToggleVisible(false); - __instance.ImpostorVentButton?.ToggleVisible(false); - break; + if (Input.GetKeyDown(KeyCode.LeftControl)) + if ((!AmongUsClient.Instance.IsGameStarted || !GameStates.IsOnlineGame) && player.CanMove) + player.Collider.offset = new(0f, 127f); - case CustomRoles.KB_Normal: - __instance.SabotageButton?.ToggleVisible(false); - __instance.AbilityButton?.ToggleVisible(false); - __instance.ReportButton?.ToggleVisible(false); - break; - case CustomRoles.Parasite: - case CustomRoles.Refugee: - __instance.SabotageButton?.ToggleVisible(true); - break; - case CustomRoles.Magician: - __instance.SabotageButton?.ToggleVisible(true); - break; + if (Math.Abs(player.Collider.offset.y - 127f) < 0.1f) + if (!Input.GetKey(KeyCode.LeftControl) || (AmongUsClient.Instance.IsGameStarted && GameStates.IsOnlineGame)) + player.Collider.offset = new(0f, -0.3636f); + + if (__instance == null) return; + + if (GameStates.IsLobby) + { + if (PingTrackerUpdatePatch.Instance != null) + { + if (SettingsText != null) Object.Destroy(SettingsText.gameObject); + + SettingsText = Object.Instantiate(PingTrackerUpdatePatch.Instance.text, __instance.transform, true); + SettingsText.alignment = TextAlignmentOptions.TopLeft; + SettingsText.verticalAlignment = VerticalAlignmentOptions.Top; + SettingsText.transform.localPosition = new(-4.9f, 2.9f, 0); + SettingsText.fontSize = SettingsText.fontSizeMin = SettingsText.fontSizeMax = 1.5f; + } + + if (SettingsText != null) + { + SettingsText.text = OptionShower.GetTextNoFresh(); + SettingsText.enabled = SettingsText.text != string.Empty; + } } + else if (SettingsText != null) Object.Destroy(SettingsText.gameObject); - if (Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState ps) && ps.SubRoles.Contains(CustomRoles.Oblivious)) __instance.ReportButton?.ToggleVisible(false); - - __instance.KillButton?.ToggleVisible(player.CanUseKillButton()); - __instance.ImpostorVentButton?.ToggleVisible(player.CanUseImpostorVentButton()); - __instance.SabotageButton?.ToggleVisible(player.CanUseSabotage()); - } - } - - [HarmonyPatch(typeof(VentButton), nameof(VentButton.DoClick))] - internal static class VentButtonDoClickPatch - { - public static bool Animating; - - public static void Prefix() - { - Animating = true; - LateTask.New(() => { Animating = false; }, 0.6f, log: false); - } - } - - [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show))] - internal static class MapBehaviourShowPatch - { - public static bool Prefix(MapBehaviour __instance, ref MapOptions opts) - { - if (GameStates.IsMeeting) return true; - - PlayerControl player = PlayerControl.LocalPlayer; - - if (player.GetCustomRole() == CustomRoles.NiceHacker && NiceHacker.PlayerIdList.ContainsKey(player.PlayerId)) + if (AmongUsClient.Instance.AmHost) { - Logger.Info("Modded Client uses Map", "NiceHacker"); - NiceHacker.MapHandle(player, __instance, opts); - } - else if (opts.Mode is MapOptions.Modes.Normal or MapOptions.Modes.Sabotage) - { - if (player.Is(CustomRoleTypes.Impostor) || player.CanUseSabotage() || player.Is(CustomRoles.Glitch) || player.Is(CustomRoles.WeaponMaster) || player.Is(CustomRoles.Magician) || player.Is(CustomRoles.Parasite) || player.Is(CustomRoles.Refugee) || (player.Is(CustomRoles.Jackal) && Jackal.CanSabotage.GetBool()) || (player.Is(CustomRoles.Traitor) && Traitor.CanSabotage.GetBool())) - opts.Mode = MapOptions.Modes.Sabotage; + if (OverriddenRolesText == null) + { + OverriddenRolesText = Object.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform, true); + OverriddenRolesText.alignment = TextAlignmentOptions.Right; + OverriddenRolesText.verticalAlignment = VerticalAlignmentOptions.Top; + OverriddenRolesText.transform.localPosition = new(2.5f, 2.5f, 0); + OverriddenRolesText.overflowMode = TextOverflowModes.Overflow; + OverriddenRolesText.enableWordWrapping = false; + OverriddenRolesText.color = Color.white; + OverriddenRolesText.fontSize = OverriddenRolesText.fontSizeMax = OverriddenRolesText.fontSizeMin = 2.5f; + } + + if (Main.SetRoles.Count > 0 || Main.SetAddOns.Count > 0) + { + Dictionary resultText = []; + var first = true; + + foreach (KeyValuePair item in Main.SetRoles) + { + PlayerControl pc = Utils.GetPlayerById(item.Key); + string prefix = first ? string.Empty : "\n"; + var text = $"{prefix}{(item.Key == 0 ? "Host" : $"{(pc == null ? $"ID {item.Key}" : $"{pc.GetRealName()}")}")} - {GetString(item.Value.ToString())}"; + resultText[item.Key] = text; + first = false; + } + + if (Main.SetRoles.Count == 0) first = true; + + foreach (KeyValuePair> item in Main.SetAddOns) + { + foreach (CustomRoles role in item.Value) + { + PlayerControl pc = Utils.GetPlayerById(item.Key); + + if (resultText.ContainsKey(item.Key)) + { + var text = $" <#ffffff>({GetString(role.ToString())}<#ffffff>)"; + resultText[item.Key] += text; + } + else + { + string prefix = first ? string.Empty : "\n"; + var text = $"{prefix}{(item.Key == 0 ? "Host" : $"{(pc == null ? $"ID {item.Key}" : $"{pc.GetRealName()}")}")} - <#ffffff>({GetString(role.ToString())}<#ffffff>)"; + resultText[item.Key] = text; + first = false; + } + } + } + + OverriddenRolesText.text = string.Join(string.Empty, resultText.Values); + } else - opts.Mode = MapOptions.Modes.Normal; + OverriddenRolesText.text = string.Empty; + + OverriddenRolesText.enabled = OverriddenRolesText.text != string.Empty; } - - if (Main.GodMode.Value) opts.ShowLivePlayerPosition = true; - - return true; - } - } - - [HarmonyPatch(typeof(InfectedOverlay), nameof(InfectedOverlay.Update))] - internal static class SabotageMapPatch - { - public static Dictionary TimerTexts = []; - - public static void Postfix(InfectedOverlay __instance) - { - float perc = __instance.sabSystem.PercentCool; - int total = __instance.sabSystem.initialCooldown ? 10 : 30; - if (SabotageSystemTypeRepairDamagePatch.IsCooldownModificationEnabled) total = (int)SabotageSystemTypeRepairDamagePatch.ModifiedCooldownSec; - - int remaining = Math.Clamp(total - (int)Math.Ceiling((1f - perc) * total) + 1, 0, total); - - foreach (MapRoom mr in __instance.rooms) + else if (GameStates.IsLobby) { - if (mr.special == null || mr.special.transform == null) continue; - - SystemTypes room = mr.room; - - if (!TimerTexts.ContainsKey(room)) + new ActionButton[] { - TimerTexts[room] = Object.Instantiate(HudManager.Instance.KillButton.cooldownTimerText, mr.special.transform, true); - TimerTexts[room].alignment = TextAlignmentOptions.Center; - TimerTexts[room].transform.localPosition = mr.special.transform.localPosition; - TimerTexts[room].transform.localPosition = new(0, -0.4f, 0f); - TimerTexts[room].overflowMode = TextOverflowModes.Overflow; - TimerTexts[room].enableWordWrapping = false; - TimerTexts[room].color = Color.white; - TimerTexts[room].fontSize = TimerTexts[room].fontSizeMax = TimerTexts[room].fontSizeMin = 2.5f; - TimerTexts[room].sortingOrder = 100; - } - - bool isActive = Utils.IsActive(room); - bool isOtherActive = TimerTexts.Keys.Any(Utils.IsActive); - bool doorBlock = __instance.DoorsPreventingSabotage; - TimerTexts[room].text = $"<#ff{(isActive || isOtherActive || doorBlock ? "00" : "ff")}00>{(!isActive && !isOtherActive && !doorBlock ? remaining : GetString(isActive && !doorBlock ? "SabotageActiveIndicator" : "SabotageDisabledIndicator"))}"; - TimerTexts[room].enabled = remaining > 0 || isActive || isOtherActive || doorBlock; + __instance.ReportButton, + __instance.KillButton, + __instance.AbilityButton, + __instance.ImpostorVentButton, + __instance.SabotageButton + }.Do(x => x?.Hide()); } - } - } + else if (!CustomGameMode.Standard.IsActiveOrIntegrated()) __instance.ReportButton?.Hide(); - [HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.SetTaskText))] - internal static class TaskPanelBehaviourPatch - { - public static void Postfix(TaskPanelBehaviour __instance) - { - PlayerControl player = PlayerControl.LocalPlayer; + // The following will not be executed unless the game is in progress + if (!AmongUsClient.Instance.IsGameStarted) return; - string taskText = __instance.taskText.text; - if (taskText == "None" || GameStates.IsLobby || player == null) return; + Utils.CountAlivePlayers(); - CustomRoles role = player.GetCustomRole(); + bool shapeshifting = player.IsShifted(); - if (!role.IsVanilla()) + if (SetHudActivePatch.IsActive) { - string roleInfo = player.GetRoleInfo(); - var RoleWithInfo = $"{role.ToColoredString()}:\r\n{roleInfo}"; - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) + if (player.IsAlive() || !CustomGameMode.Standard.IsActiveOrIntegrated()) { - string[] splitted = roleInfo.Split(' '); + if (player.Data.Role is ShapeshifterRole ssrole) + { + float timer = shapeshifting ? ssrole.durationSecondsRemaining : ssrole.cooldownSecondsRemaining; + AbilityButton button = __instance.AbilityButton; - RoleWithInfo = splitted.Length <= 3 - ? $"{roleInfo}\r\n" - : $"{string.Join(' ', splitted[..3])}\r\n{string.Join(' ', splitted[3..])}\r\n"; + if (timer > 0f) + { + Color color = shapeshifting ? Color.green : Color.white; + button.cooldownTimerText.text = Utils.ColorString(color, Mathf.CeilToInt(timer).ToString()); + button.cooldownTimerText.gameObject.SetActive(true); + } + } + + CustomRoles role = player.GetCustomRole(); + + bool usesPetInsteadOfKill = role.UsesPetInsteadOfKill(); + if (usesPetInsteadOfKill) __instance.PetButton?.OverrideText(GetString("KillButtonText")); + + ActionButton usedButton = __instance.KillButton; + if (usesPetInsteadOfKill) usedButton = __instance.PetButton; + + __instance.KillButton?.OverrideText(GetString("KillButtonText")); + __instance.ReportButton?.OverrideText(GetString("ReportButtonText")); + __instance.PetButton?.OverrideText(GetString("PetButtonText")); + __instance.ImpostorVentButton?.OverrideText(GetString("VentButtonText")); + __instance.SabotageButton?.OverrideText(GetString("SabotageButtonText")); + + RoleTypes roleTypes = player.GetRoleTypes(); + __instance.AbilityButton?.OverrideText(GetString($"AbilityButtonText.{roleTypes}")); + + PlayerState state = Main.PlayerStates[player.PlayerId]; + + state.Role.SetButtonTexts(__instance, player.PlayerId); + + switch (role) + { + case CustomRoles.Farseer: + case CustomRoles.Escort: + case CustomRoles.Gaulois: + case CustomRoles.Sheriff: + case CustomRoles.Crusader: + case CustomRoles.Monarch: + usedButton?.OverrideText(GetString($"{role}KillButtonText")); + break; + case CustomRoles.Analyst: + usedButton?.OverrideText(GetString("AnalyzerKillButtonText")); + break; + case CustomRoles.Aid: + case CustomRoles.DonutDelivery: + case CustomRoles.Medic: + usedButton?.OverrideText(GetString("MedicalerButtonText")); + break; + case CustomRoles.KB_Normal: + __instance.KillButton?.OverrideText(GetString("GamerButtonText")); + break; + case CustomRoles.Deputy: + usedButton?.OverrideText(GetString("DeputyHandcuffText")); + break; + case CustomRoles.CTFPlayer: + __instance.AbilityButton?.OverrideText(GetString("CTF_ButtonText")); + break; + case CustomRoles.RRPlayer when __instance.AbilityButton != null && __instance.AbilityButton && RoomRush.VentLimit.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out var ventLimit): + __instance.AbilityButton.SetUsesRemaining(ventLimit); + break; + } + + if (role.PetActivatedAbility() && CustomGameMode.Standard.IsActiveOrIntegrated() && !role.OnlySpawnsWithPets() && !role.AlwaysUsesUnshift() && !player.GetCustomSubRoles().Any(StartGameHostPatch.BasisChangingAddons.ContainsKey) && role != CustomRoles.Changeling && !Options.UseUnshiftTrigger.GetBool() && !(player.IsNeutralKiller() && Options.UseUnshiftTriggerForNKs.GetBool())) + __instance.AbilityButton?.Hide(); + + if (LowerInfoText == null) + { + LowerInfoText = Object.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform, true); + LowerInfoText.alignment = TextAlignmentOptions.Center; + LowerInfoText.transform.localPosition = new(0, -2f, 0); + LowerInfoText.overflowMode = TextOverflowModes.Overflow; + LowerInfoText.enableWordWrapping = false; + LowerInfoText.color = Color.white; + LowerInfoText.fontSize = LowerInfoText.fontSizeMax = LowerInfoText.fontSizeMin = 2.7f; + } + + LowerInfoText.text = Options.CurrentGameMode switch + { + CustomGameMode.SoloKombat => SoloKombatManager.GetHudText(), + CustomGameMode.FFA when player.IsHost() => FFAManager.GetHudText(), + CustomGameMode.MoveAndStop when player.IsHost() => MoveAndStop.HUDText, + CustomGameMode.HotPotato when player.IsHost() => HotPotato.GetSuffixText(player.PlayerId), + CustomGameMode.HideAndSeek when player.IsHost() => HnSManager.GetSuffixText(player, player, true), + CustomGameMode.NaturalDisasters => NaturalDisasters.SuffixText(), + CustomGameMode.AllInOne => $"{NaturalDisasters.SuffixText()}\n{HotPotato.GetSuffixText(player.PlayerId)}", + CustomGameMode.Standard => state.Role.GetSuffix(player, player, true, GameStates.IsMeeting) + GetAddonSuffixes(), + _ => string.Empty + }; + + string GetAddonSuffixes() + { + IEnumerable suffixes = state.SubRoles.Select(s => s switch + { + CustomRoles.Asthmatic => Asthmatic.GetSuffixText(player.PlayerId), + CustomRoles.Spurt => Spurt.GetSuffix(player, true), + CustomRoles.Deadlined => Deadlined.GetSuffix(player, true), + CustomRoles.Introvert => Introvert.GetSelfSuffix(player), + _ => string.Empty + }); + + return string.Join(string.Empty, suffixes); + } + + string CD_HUDText = !Options.UsePets.GetBool() || !Main.AbilityCD.TryGetValue(player.PlayerId, out (long StartTimeStamp, int TotalCooldown) CD) + ? string.Empty + : string.Format(GetString("CDPT"), CD.TotalCooldown - (Utils.TimeStamp - CD.StartTimeStamp) + 1); + + bool hasCD = CD_HUDText != string.Empty; + + if (hasCD) + { + if (CooldownTimerFlashColor.HasValue) CD_HUDText = $"{Utils.ColorString(CooldownTimerFlashColor.Value, CD_HUDText.RemoveHtmlTags())}"; + + LowerInfoText.text = $"{CD_HUDText}\n{LowerInfoText.text}"; + } + + if (AchievementUnlockedText != string.Empty) + { + LowerInfoText.text = LowerInfoText.text == string.Empty + ? AchievementUnlockedText + : $"{AchievementUnlockedText}\n\n{LowerInfoText.text}\n\n\n\n"; + } + + LowerInfoText.enabled = hasCD || LowerInfoText.text != string.Empty; + + if ((!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) || GameStates.IsMeeting) LowerInfoText.enabled = false; + + bool allowedRole = role is CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee or CustomRoles.Sidekick; + + if (player.CanUseKillButton() && (allowedRole || !role.UsesPetInsteadOfKill())) + { + __instance.KillButton?.ToggleVisible(player.IsAlive() && GameStates.IsInTask); + player.Data.Role.CanUseKillButton = true; + } + else + { + __instance.KillButton?.SetDisabled(); + __instance.KillButton?.ToggleVisible(false); + } + + __instance.ImpostorVentButton?.ToggleVisible((player.CanUseImpostorVentButton() || (player.inVent && player.GetRoleTypes() != RoleTypes.Engineer)) && GameStates.IsInTask); + player.Data.Role.CanVent = player.CanUseVent(); + + if ((usesPetInsteadOfKill && player.Is(CustomRoles.Nimble) && player.GetRoleTypes() == RoleTypes.Engineer) || player.Is(CustomRoles.GM)) __instance.AbilityButton.SetEnabled(); } - else if (roleInfo.RemoveHtmlTags().Length > 35) + else { - string[] split = roleInfo.Split(' '); - int half = split.Length / 2; - RoleWithInfo = $"{role.ToColoredString()}:\r\n{string.Join(' ', split[..half])}\r\n{string.Join(' ', split[half..])}"; + __instance.ReportButton?.Hide(); + __instance.ImpostorVentButton?.Hide(); + __instance.KillButton?.Hide(); + __instance.AbilityButton?.Show(); + __instance.AbilityButton?.OverrideText(GetString(StringNames.HauntAbilityName)); } - - string finalText = Utils.ColorString(player.GetRoleColor(), RoleWithInfo); - - switch (Options.CurrentGameMode) - { - case CustomGameMode.Standard: - - List subRoles = player.GetCustomSubRoles(); - - if (subRoles.Count > 0) - { - const int max = 3; - IEnumerable s = subRoles.Take(max).Select(x => Utils.ColorString(Utils.GetRoleColor(x), $"\r\n\r\n{x.ToColoredString()}:\r\n{GetString($"{x}Info")}")); - finalText += s.Aggregate("", (current, next) => current + next) + ""; - int chunk = subRoles.Any(x => GetString(x.ToString()).Contains(' ')) ? 3 : 4; - if (subRoles.Count > max) finalText += $"\r\n....\r\n({subRoles.Skip(max).Chunk(chunk).Select(x => x.Join(r => r.ToColoredString())).Join(delimiter: ",\r\n")})"; - } - - string[] lines = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); - StringBuilder sb = new(); - - foreach (string eachLine in lines) - { - string line = eachLine.Trim(); - if ((line.StartsWith("") || line.StartsWith("")) && sb.Length < 1 && !line.Contains('(') && !line.Contains(DestroyableSingleton.Instance.GetString(TaskTypes.FixComms))) continue; - - sb.Append(line + "\r\n"); - } - - if (sb.Length > 1) - { - string text = sb.ToString().TrimEnd('\n').TrimEnd('\r'); - - if (!Utils.HasTasks(player.Data, false) && sb.ToString().Count(s => s == '\n') >= 2) - text = $"{Utils.ColorString(Utils.GetRoleColor(role).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; - else if (player.myTasks.ToArray().Any(x => x.TaskType == TaskTypes.FixComms)) goto Skip; - - finalText += $"\r\n\r\n{text}"; - } - - Skip: - if (MeetingStates.FirstMeeting) finalText += $"\r\n\r\n{GetString("PressF1ShowMainRoleDes")}"; - - break; - - case CustomGameMode.SoloKombat: - - PlayerControl lpc = PlayerControl.LocalPlayer; - - finalText += "\r\n"; - finalText += $"\r\n{GetString("PVP.ATK")}: {SoloKombatManager.PlayerATK[lpc.PlayerId]:N1}"; - finalText += $"\r\n{GetString("PVP.DF")}: {SoloKombatManager.PlayerDF[lpc.PlayerId]:N1}"; - finalText += $"\r\n{GetString("PVP.RCO")}: {SoloKombatManager.PlayerHPReco[lpc.PlayerId]:N1}"; - finalText += "\r\n"; - - finalText += Main.PlayerStates.Keys.OrderBy(SoloKombatManager.GetRankOfScore).Aggregate("", (s, x) => $"{s}\r\n{SoloKombatManager.GetRankOfScore(x)}. {x.ColoredPlayerName()} - {string.Format(GetString("KillCount").TrimStart(' '), SoloKombatManager.KBScore.GetValueOrDefault(x, 0))}"); - - finalText += ""; - break; - - case CustomGameMode.FFA: - - finalText += Main.PlayerStates.Keys.OrderBy(FFAManager.GetRankFromScore).Aggregate("", (s, x) => $"{s}\r\n{FFAManager.GetRankFromScore(x)}. {x.ColoredPlayerName()} -{string.Format(GetString("KillCount"), FFAManager.KillCount.GetValueOrDefault(x, 0))}"); - - finalText += ""; - break; - - case CustomGameMode.MoveAndStop: - - Dictionary SummaryText3 = []; - - foreach (byte id in Main.PlayerStates.Keys.ToArray()) - { - string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); - var summary = $"{Utils.GetProgressText(id)} {Utils.ColorString(Main.PlayerColors[id], name)}"; - if (Utils.GetProgressText(id).Trim() == string.Empty) continue; - - SummaryText3[id] = summary; - } - - string[] lines1 = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); - StringBuilder sb1 = new(); - - foreach (string eachLine in lines1) - { - string line = eachLine.Trim(); - if ((line.StartsWith("") || line.StartsWith("")) && sb1.Length < 1 && !line.Contains('(')) continue; - - sb1.Append(line + "\r\n"); - } - - if (sb1.Length > 1) - { - string text = sb1.ToString().TrimEnd('\n').TrimEnd('\r'); - if (!Utils.HasTasks(player.Data, false) && sb1.ToString().Count(s => s == '\n') >= 2) text = $"{Utils.ColorString(Utils.GetRoleColor(role).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; - - finalText += $"\r\n{text}\r\n"; - } - - List<(int, byte)> list3 = []; - foreach (byte id in Main.PlayerStates.Keys) list3.Add((MoveAndStop.GetRankFromScore(id), id)); - - list3.Sort(); - list3 = [.. list3.OrderBy(x => !Utils.GetPlayerById(x.Item2).IsAlive())]; - - foreach ((int, byte) id in list3.Where(x => SummaryText3.ContainsKey(x.Item2)).ToArray()) - { - bool alive = Utils.GetPlayerById(id.Item2).IsAlive(); - finalText += $"{(!alive ? "<#777777>" : string.Empty)}\r\n{(alive ? SummaryText3[id.Item2].Replace("", "") : SummaryText3[id.Item2].RemoveHtmlTags())}{(!alive ? $" <#ff0000>{GetString("Dead")}" : string.Empty)}"; - } - - break; - - case CustomGameMode.HotPotato: - - List SummaryText4 = []; - SummaryText4.AddRange(from pc in Main.AllPlayerControls let alive = pc.IsAlive() select $"{(!alive ? "<#777777>" : "")}{HotPotato.GetIndicator(pc.PlayerId)}{pc.PlayerId.ColoredPlayerName()}{(!alive ? $" <#ff0000>{GetString("Dead")}" : "")}"); - - finalText += $"\r\n\r\n{string.Join('\n', SummaryText4)}"; - - break; - - case CustomGameMode.HideAndSeek: - - finalText += $"\r\n\r\n{HnSManager.GetTaskBarText()}"; - - break; - - case CustomGameMode.Speedrun: - - string[] lines2 = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); - StringBuilder sb2 = new(); - - foreach (string eachLine in lines2) - { - string line = eachLine.Trim(); - if ((line.StartsWith("") || line.StartsWith("")) && sb2.Length < 1 && !line.Contains('(')) continue; - - sb2.Append(line + "\r\n"); - } - - if (sb2.Length > 1) - { - string text = sb2.ToString().TrimEnd('\n').TrimEnd('\r'); - if (!Utils.HasTasks(player.Data, false) && sb2.ToString().Count(s => s == '\n') >= 2) text = $"{Utils.ColorString(Utils.GetRoleColor(role).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; - - finalText += $"\r\n{text}\r\n"; - } - - finalText += $"\r\n{SpeedrunManager.GetTaskBarText()}"; - - break; - - case CustomGameMode.NaturalDisasters: - - finalText += Main.AllPlayerControls - .Select(x => (pc: x, alive: x.IsAlive(), time: NaturalDisasters.SurvivalTime(x.PlayerId))) - .OrderByDescending(x => x.alive) - .ThenByDescending(x => x.time) - .Aggregate("", (s, x) => $"{s}\r\n{x.pc.PlayerId.ColoredPlayerName()} - {(x.alive ? $"<#00ff00>{GetString("Alive")}" : $"{GetString("Dead")}: {string.Format(GetString("SurvivalTime"), x.time)}")}"); - - finalText += ""; - break; - - case CustomGameMode.RoomRush: - - finalText += Main.AllPlayerControls - .Select(x => (pc: x, alive: x.IsAlive(), time: RoomRush.GetSurvivalTime(x.PlayerId))) - .OrderByDescending(x => x.alive) - .ThenByDescending(x => x.time) - .Aggregate("", (s, x) => $"{s}\r\n{x.pc.PlayerId.ColoredPlayerName()} - {(x.alive ? $"<#00ff00>{GetString("Alive")}" : $"{GetString("Dead")}: {string.Format(GetString("SurvivalTime"), x.time)}")}"); - - finalText += ""; - break; - } - - __instance.taskText.text = finalText; } - if (RepairSender.Enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) __instance.taskText.text = RepairSender.GetText(); + + if (Input.GetKeyDown(KeyCode.Y) && AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) + { + __instance.ToggleMapVisible(new() + { + Mode = MapOptions.Modes.Sabotage, + AllowMovementWhileMapOpen = true + }); + + if (player.AmOwner) + { + player.MyPhysics.inputHandler.enabled = true; + ConsoleJoystick.SetMode_Task(); + } + } + + if (AmongUsClient.Instance.NetworkMode == NetworkModes.OnlineGame) RepairSender.Enabled = false; + + if (Input.GetKeyDown(KeyCode.RightShift) && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) + { + RepairSender.Enabled = !RepairSender.Enabled; + RepairSender.Reset(); + } + + if (RepairSender.Enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) + { + if (Input.GetKeyDown(KeyCode.Alpha0)) RepairSender.Input(0); + + if (Input.GetKeyDown(KeyCode.Alpha1)) RepairSender.Input(1); + + if (Input.GetKeyDown(KeyCode.Alpha2)) RepairSender.Input(2); + + if (Input.GetKeyDown(KeyCode.Alpha3)) RepairSender.Input(3); + + if (Input.GetKeyDown(KeyCode.Alpha4)) RepairSender.Input(4); + + if (Input.GetKeyDown(KeyCode.Alpha5)) RepairSender.Input(5); + + if (Input.GetKeyDown(KeyCode.Alpha6)) RepairSender.Input(6); + + if (Input.GetKeyDown(KeyCode.Alpha7)) RepairSender.Input(7); + + if (Input.GetKeyDown(KeyCode.Alpha8)) RepairSender.Input(8); + + if (Input.GetKeyDown(KeyCode.Alpha9)) RepairSender.Input(9); + + if (Input.GetKeyDown(KeyCode.Return)) RepairSender.InputEnter(); + } + } + catch (NullReferenceException e) + { + if (LastNullError >= Utils.TimeStamp) return; + + LastNullError = Utils.TimeStamp + 2; + Utils.ThrowException(e); + } + catch (Exception e) + { + Utils.ThrowException(e); } } +} - [HarmonyPatch(typeof(DialogueBox), nameof(DialogueBox.Show))] - internal static class DialogueBoxShowPatch +[HarmonyPatch(typeof(ActionButton), nameof(ActionButton.SetFillUp))] +internal static class ActionButtonSetFillUpPatch +{ + public static void Postfix(ActionButton __instance, [HarmonyArgument(0)] float timer) { - public static bool Prefix(DialogueBox __instance, [HarmonyArgument(0)] string dialogue) + if (__instance.isCoolingDown && timer is <= 90f and > 0f) { - __instance.target.text = dialogue; - if (Minigame.Instance != null) Minigame.Instance.Close(); + RoleTypes roleType = PlayerControl.LocalPlayer.GetRoleTypes(); - if (Minigame.Instance != null) Minigame.Instance.Close(); + bool usingAbility = roleType switch + { + RoleTypes.Engineer => PlayerControl.LocalPlayer.inVent || VentButtonDoClickPatch.Animating, + RoleTypes.Shapeshifter => PlayerControl.LocalPlayer.IsShifted(), + _ => false + }; - __instance.gameObject.SetActive(true); - return false; + Color color = usingAbility ? Color.green : Color.white; + __instance.cooldownTimerText.text = Utils.ColorString(color, Mathf.CeilToInt(timer).ToString()); + __instance.cooldownTimerText.gameObject.SetActive(true); } } +} - [HarmonyPatch(typeof(HudManager), nameof(HudManager.CoShowIntro))] - internal static class CoShowIntroPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ToggleHighlight))] +internal static class ToggleHighlightPatch +{ + private static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); + + public static void Postfix(PlayerControl __instance /*[HarmonyArgument(0)] bool active,*/ /*[HarmonyArgument(1)] RoleTeamTypes team*/) { - public static bool IntroStarted; + if (!GameStates.IsInTask) return; - public static void Prefix() + PlayerControl player = PlayerControl.LocalPlayer; + + if (player.CanUseKillButton()) __instance.cosmetics.currentBodySprite.BodySprite.material.SetColor(OutlineColor, Utils.GetRoleColor(player.GetCustomRole())); + } +} + +[HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))] +internal static class SetVentOutlinePatch +{ + private static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); + private static readonly int AddColor = Shader.PropertyToID("_AddColor"); + + public static void Postfix(Vent __instance, [HarmonyArgument(1)] ref bool mainTarget) + { + Color color = PlayerControl.LocalPlayer.GetRoleColor(); + __instance.myRend.material.SetColor(OutlineColor, color); + __instance.myRend.material.SetColor(AddColor, mainTarget ? color : Color.clear); + } +} + +[HarmonyPatch(typeof(HudManager), nameof(HudManager.SetHudActive), typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool))] +internal static class SetHudActivePatch +{ + public static bool IsActive; + + public static void Prefix( /*HudManager __instance,*/ [HarmonyArgument(2)] ref bool isActive) + { + isActive &= !GameStates.IsMeeting; + } + + public static void Postfix(HudManager __instance, [HarmonyArgument(2)] bool isActive) + { + __instance?.ReportButton?.ToggleVisible(!GameStates.IsLobby && isActive); + + if (__instance == null) { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsModHost) return; + Logger.Fatal("HudManager __instance ended up being null", "SetHudActivePatch.Postfix"); + return; + } - IntroStarted = true; + IsActive = isActive; + if (!isActive) return; - LateTask.New(() => + switch (Options.CurrentGameMode) + { + case CustomGameMode.MoveAndStop: + case CustomGameMode.HotPotato: + case CustomGameMode.Speedrun: + case CustomGameMode.NaturalDisasters: + __instance.ReportButton?.ToggleVisible(false); + __instance.KillButton?.ToggleVisible(false); + __instance.SabotageButton?.ToggleVisible(false); + __instance.ImpostorVentButton?.ToggleVisible(false); + __instance.AbilityButton?.ToggleVisible(false); + return; + case CustomGameMode.FFA: + __instance.ReportButton?.ToggleVisible(false); + __instance.SabotageButton?.ToggleVisible(false); + __instance.AbilityButton?.ToggleVisible(false); + return; + case CustomGameMode.RoomRush: + __instance.ImpostorVentButton?.ToggleVisible(false); + goto case CustomGameMode.CaptureTheFlag; + case CustomGameMode.CaptureTheFlag: + case CustomGameMode.HideAndSeek: + __instance.ReportButton?.ToggleVisible(false); + __instance.SabotageButton?.ToggleVisible(false); + return; + } + + PlayerControl player = PlayerControl.LocalPlayer; + if (player == null) return; + + switch (player.GetCustomRole()) + { + case CustomRoles.Sheriff: + case CustomRoles.Arsonist: + case CustomRoles.SwordsMan: + case CustomRoles.Deputy: + case CustomRoles.Monarch: + case CustomRoles.Pelican: + case CustomRoles.FFF: + case CustomRoles.Medic: + case CustomRoles.Gamer: + case CustomRoles.DarkHide: + case CustomRoles.Farseer: + case CustomRoles.Crusader: + __instance.SabotageButton?.ToggleVisible(false); + __instance.ImpostorVentButton?.ToggleVisible(false); + break; + + case CustomRoles.KB_Normal: + __instance.SabotageButton?.ToggleVisible(false); + __instance.AbilityButton?.ToggleVisible(false); + __instance.ReportButton?.ToggleVisible(false); + break; + case CustomRoles.Parasite: + case CustomRoles.Refugee: + __instance.SabotageButton?.ToggleVisible(true); + break; + case CustomRoles.Magician: + __instance.SabotageButton?.ToggleVisible(true); + break; + } + + if (Main.PlayerStates.TryGetValue(player.PlayerId, out PlayerState ps) && ps.SubRoles.Contains(CustomRoles.Oblivious)) __instance.ReportButton?.ToggleVisible(false); + + __instance.KillButton?.ToggleVisible(player.CanUseKillButton()); + __instance.ImpostorVentButton?.ToggleVisible(player.CanUseImpostorVentButton()); + __instance.SabotageButton?.ToggleVisible(player.CanUseSabotage()); + } +} + +[HarmonyPatch(typeof(VentButton), nameof(VentButton.DoClick))] +internal static class VentButtonDoClickPatch +{ + public static bool Animating; + + public static void Prefix() + { + Animating = true; + LateTask.New(() => { Animating = false; }, 0.6f, log: false); + } +} + +[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.Show))] +internal static class MapBehaviourShowPatch +{ + public static bool Prefix(MapBehaviour __instance, ref MapOptions opts) + { + if (GameStates.IsMeeting) return true; + + PlayerControl player = PlayerControl.LocalPlayer; + + if (player.GetCustomRole() == CustomRoles.NiceHacker && NiceHacker.PlayerIdList.ContainsKey(player.PlayerId)) + { + Logger.Info("Modded Client uses Map", "NiceHacker"); + NiceHacker.MapHandle(player, __instance, opts); + } + else if (opts.Mode is MapOptions.Modes.Normal or MapOptions.Modes.Sabotage) + { + if (player.Is(CustomRoleTypes.Impostor) || player.CanUseSabotage() || player.Is(CustomRoles.Glitch) || player.Is(CustomRoles.WeaponMaster) || player.Is(CustomRoles.Magician) || player.Is(CustomRoles.Parasite) || player.Is(CustomRoles.Refugee) || (player.Is(CustomRoles.Jackal) && Jackal.CanSabotage.GetBool()) || (player.Is(CustomRoles.Traitor) && Traitor.CanSabotage.GetBool())) + opts.Mode = MapOptions.Modes.Sabotage; + else + opts.Mode = MapOptions.Modes.Normal; + } + + if (Main.GodMode.Value) opts.ShowLivePlayerPosition = true; + + return true; + } +} + +[HarmonyPatch(typeof(InfectedOverlay), nameof(InfectedOverlay.Update))] +internal static class SabotageMapPatch +{ + public static Dictionary TimerTexts = []; + + public static void Postfix(InfectedOverlay __instance) + { + float perc = __instance.sabSystem.PercentCool; + int total = __instance.sabSystem.initialCooldown ? 10 : 30; + if (SabotageSystemTypeRepairDamagePatch.IsCooldownModificationEnabled) total = (int)SabotageSystemTypeRepairDamagePatch.ModifiedCooldownSec; + + int remaining = Math.Clamp(total - (int)Math.Ceiling((1f - perc) * total) + 1, 0, total); + + foreach (MapRoom mr in __instance.rooms) + { + if (mr.special == null || mr.special.transform == null) continue; + + SystemTypes room = mr.room; + + if (!TimerTexts.ContainsKey(room)) + { + TimerTexts[room] = Object.Instantiate(HudManager.Instance.KillButton.cooldownTimerText, mr.special.transform, true); + TimerTexts[room].alignment = TextAlignmentOptions.Center; + TimerTexts[room].transform.localPosition = mr.special.transform.localPosition; + TimerTexts[room].transform.localPosition = new(0, -0.4f, 0f); + TimerTexts[room].overflowMode = TextOverflowModes.Overflow; + TimerTexts[room].enableWordWrapping = false; + TimerTexts[room].color = Color.white; + TimerTexts[room].fontSize = TimerTexts[room].fontSizeMax = TimerTexts[room].fontSizeMin = 2.5f; + TimerTexts[room].sortingOrder = 100; + } + + bool isActive = Utils.IsActive(room); + bool isOtherActive = TimerTexts.Keys.Any(Utils.IsActive); + bool doorBlock = __instance.DoorsPreventingSabotage; + TimerTexts[room].text = $"<#ff{(isActive || isOtherActive || doorBlock ? "00" : "ff")}00>{(!isActive && !isOtherActive && !doorBlock ? remaining : GetString(isActive && !doorBlock ? "SabotageActiveIndicator" : "SabotageDisabledIndicator"))}"; + TimerTexts[room].enabled = remaining > 0 || isActive || isOtherActive || doorBlock; + } + } +} + +[HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.SetTaskText))] +internal static class TaskPanelBehaviourPatch +{ + public static void Postfix(TaskPanelBehaviour __instance) + { + PlayerControl player = PlayerControl.LocalPlayer; + + string taskText = __instance.taskText.text; + if (taskText == "None" || GameStates.IsLobby || player == null) return; + + CustomRoles role = player.GetCustomRole(); + + if (!role.IsVanilla()) + { + string roleInfo = player.GetRoleInfo(); + var RoleWithInfo = $"{role.ToColoredString()}:\r\n{roleInfo}"; + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) + { + string[] splitted = roleInfo.Split(' '); + + RoleWithInfo = splitted.Length <= 3 + ? $"{roleInfo}\r\n" + : $"{string.Join(' ', splitted[..3])}\r\n{string.Join(' ', splitted[3..])}\r\n"; + } + else if (roleInfo.RemoveHtmlTags().Length > 35) + { + string[] split = roleInfo.Split(' '); + int half = split.Length / 2; + RoleWithInfo = $"{role.ToColoredString()}:\r\n{string.Join(' ', split[..half])}\r\n{string.Join(' ', split[half..])}"; + } + + string finalText = Utils.ColorString(player.GetRoleColor(), RoleWithInfo); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.Standard: + + List subRoles = player.GetCustomSubRoles(); + + if (subRoles.Count > 0) + { + const int max = 3; + IEnumerable s = subRoles.Take(max).Select(x => Utils.ColorString(Utils.GetRoleColor(x), $"\r\n\r\n{x.ToColoredString()}:\r\n{GetString($"{x}Info")}")); + finalText += s.Aggregate("", (current, next) => current + next) + ""; + int chunk = subRoles.Any(x => GetString(x.ToString()).Contains(' ')) ? 3 : 4; + if (subRoles.Count > max) finalText += $"\r\n....\r\n({subRoles.Skip(max).Chunk(chunk).Select(x => x.Join(r => r.ToColoredString())).Join(delimiter: ",\r\n")})"; + } + + string[] lines = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); + StringBuilder sb = new(); + + foreach (string eachLine in lines) + { + string line = eachLine.Trim(); + if ((line.StartsWith("") || line.StartsWith("")) && sb.Length < 1 && !line.Contains('(') && !line.Contains(DestroyableSingleton.Instance.GetString(TaskTypes.FixComms))) continue; + + sb.Append(line + "\r\n"); + } + + if (sb.Length > 1) + { + string text = sb.ToString().TrimEnd('\n').TrimEnd('\r'); + + if (!Utils.HasTasks(player.Data, false) && sb.ToString().Count(s => s == '\n') >= 2) + text = $"{Utils.ColorString(Utils.GetRoleColor(role).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; + else if (player.myTasks.ToArray().Any(x => x.TaskType == TaskTypes.FixComms)) goto Skip; + + finalText += $"\r\n\r\n{text}"; + } + + Skip: + if (MeetingStates.FirstMeeting) finalText += $"\r\n\r\n{GetString("PressF1ShowMainRoleDes")}"; + + break; + + case CustomGameMode.SoloKombat: + + PlayerControl lpc = PlayerControl.LocalPlayer; + + finalText += "\r\n"; + finalText += $"\r\n{GetString("PVP.ATK")}: {SoloKombatManager.PlayerATK[lpc.PlayerId]:N1}"; + finalText += $"\r\n{GetString("PVP.DF")}: {SoloKombatManager.PlayerDF[lpc.PlayerId]:N1}"; + finalText += $"\r\n{GetString("PVP.RCO")}: {SoloKombatManager.PlayerHPReco[lpc.PlayerId]:N1}"; + finalText += "\r\n"; + + finalText += Main.PlayerStates.Keys.OrderBy(SoloKombatManager.GetRankOfScore).Aggregate("", (s, x) => $"{s}\r\n{SoloKombatManager.GetRankOfScore(x)}. {x.ColoredPlayerName()} - {string.Format(GetString("KillCount").TrimStart(' '), SoloKombatManager.KBScore.GetValueOrDefault(x, 0))}"); + + finalText += ""; + break; + + case CustomGameMode.FFA: + + finalText += Main.PlayerStates.Keys.OrderBy(FFAManager.GetRankFromScore).Aggregate("", (s, x) => $"{s}\r\n{FFAManager.GetRankFromScore(x)}. {x.ColoredPlayerName()} -{string.Format(GetString("KillCount"), FFAManager.KillCount.GetValueOrDefault(x, 0))}"); + + finalText += ""; + break; + + case CustomGameMode.MoveAndStop: + + Dictionary SummaryText3 = []; + + foreach (byte id in Main.PlayerStates.Keys.ToArray()) + { + string name = Main.AllPlayerNames[id].RemoveHtmlTags().Replace("\r\n", string.Empty); + var summary = $"{Utils.GetProgressText(id)} {Utils.ColorString(Main.PlayerColors[id], name)}"; + if (Utils.GetProgressText(id).Trim() == string.Empty) continue; + + SummaryText3[id] = summary; + } + + string[] lines1 = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); + StringBuilder sb1 = new(); + + foreach (string eachLine in lines1) + { + string line = eachLine.Trim(); + if ((line.StartsWith("") || line.StartsWith("")) && sb1.Length < 1 && !line.Contains('(')) continue; + + sb1.Append(line + "\r\n"); + } + + if (sb1.Length > 1) + { + string text = sb1.ToString().TrimEnd('\n').TrimEnd('\r'); + if (!Utils.HasTasks(player.Data, false) && sb1.ToString().Count(s => s == '\n') >= 2) text = $"{Utils.ColorString(Utils.GetRoleColor(role).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; + + finalText += $"\r\n{text}\r\n"; + } + + List<(int, byte)> list3 = []; + foreach (byte id in Main.PlayerStates.Keys) list3.Add((MoveAndStop.GetRankFromScore(id), id)); + + list3.Sort(); + list3 = [.. list3.OrderBy(x => !Utils.GetPlayerById(x.Item2).IsAlive())]; + + foreach ((int, byte) id in list3.Where(x => SummaryText3.ContainsKey(x.Item2)).ToArray()) + { + bool alive = Utils.GetPlayerById(id.Item2).IsAlive(); + finalText += $"{(!alive ? "<#777777>" : string.Empty)}\r\n{(alive ? SummaryText3[id.Item2].Replace("", "") : SummaryText3[id.Item2].RemoveHtmlTags())}{(!alive ? $" <#ff0000>{GetString("Dead")}" : string.Empty)}"; + } + + break; + + case CustomGameMode.HotPotato: + + List SummaryText4 = []; + SummaryText4.AddRange(from pc in Main.AllPlayerControls let alive = pc.IsAlive() select $"{(!alive ? "<#777777>" : "")}{HotPotato.GetIndicator(pc.PlayerId)}{pc.PlayerId.ColoredPlayerName()}{(!alive ? $" <#ff0000>{GetString("Dead")}" : "")}"); + + finalText += $"\r\n\r\n{string.Join('\n', SummaryText4)}"; + + break; + + case CustomGameMode.HideAndSeek: + + finalText += $"\r\n\r\n{HnSManager.GetTaskBarText()}"; + + break; + + case CustomGameMode.Speedrun: + + string[] lines2 = taskText.Split("\r\n\n")[0].Split("\r\n\n")[0].Split("\r\n"); + StringBuilder sb2 = new(); + + foreach (string eachLine in lines2) + { + string line = eachLine.Trim(); + if ((line.StartsWith("") || line.StartsWith("")) && sb2.Length < 1 && !line.Contains('(')) continue; + + sb2.Append(line + "\r\n"); + } + + if (sb2.Length > 1) + { + string text = sb2.ToString().TrimEnd('\n').TrimEnd('\r'); + if (!Utils.HasTasks(player.Data, false) && sb2.ToString().Count(s => s == '\n') >= 2) text = $"{Utils.ColorString(Utils.GetRoleColor(role).ShadeColor(0.2f), GetString("FakeTask"))}\r\n{text}"; + + finalText += $"\r\n{text}\r\n"; + } + + finalText += $"\r\n{SpeedrunManager.GetTaskBarText()}"; + + break; + + case CustomGameMode.NaturalDisasters: + + finalText += Main.AllPlayerControls + .Select(x => (pc: x, alive: x.IsAlive(), time: NaturalDisasters.SurvivalTime(x.PlayerId))) + .OrderByDescending(x => x.alive) + .ThenByDescending(x => x.time) + .Aggregate("", (s, x) => $"{s}\r\n{x.pc.PlayerId.ColoredPlayerName()} - {(x.alive ? $"<#00ff00>{GetString("Alive")}" : $"{GetString("Dead")}: {string.Format(GetString("SurvivalTime"), x.time)}")}"); + + finalText += ""; + break; + + case CustomGameMode.RoomRush: + + finalText += Main.AllPlayerControls + .Select(x => (pc: x, alive: x.IsAlive(), time: RoomRush.GetSurvivalTime(x.PlayerId))) + .OrderByDescending(x => x.alive) + .ThenByDescending(x => x.time) + .Aggregate("", (s, x) => $"{s}\r\n{x.pc.PlayerId.ColoredPlayerName()} - {(x.alive ? $"<#00ff00>{GetString("Alive")}" : $"{GetString("Dead")}: {string.Format(GetString("SurvivalTime"), x.time)}")}"); + + finalText += ""; + break; + } + + __instance.taskText.text = finalText; + } + + if (RepairSender.Enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) __instance.taskText.text = RepairSender.GetText(); + } +} + +[HarmonyPatch(typeof(DialogueBox), nameof(DialogueBox.Show))] +internal static class DialogueBoxShowPatch +{ + public static bool Prefix(DialogueBox __instance, [HarmonyArgument(0)] string dialogue) + { + __instance.target.text = dialogue; + if (Minigame.Instance != null) Minigame.Instance.Close(); + + if (Minigame.Instance != null) Minigame.Instance.Close(); + + __instance.gameObject.SetActive(true); + return false; + } +} + +[HarmonyPatch(typeof(HudManager), nameof(HudManager.CoShowIntro))] +internal static class CoShowIntroPatch +{ + public static bool IntroStarted; + + public static void Prefix() + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsModHost) return; + + IntroStarted = true; + + LateTask.New(() => + { + if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended)) + { + StartGameHostPatch.RpcSetDisconnected(false); + + if (!AmongUsClient.Instance.IsGameOver) DestroyableSingleton.Instance.SetHudActive(true); + } + }, 0.6f, "Set Disconnected"); + + LateTask.New(() => + { + try { if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended)) { - StartGameHostPatch.RpcSetDisconnected(false); + ShipStatusBeginPatch.RolesIsAssigned = true; - if (!AmongUsClient.Instance.IsGameOver) DestroyableSingleton.Instance.SetHudActive(true); + // Assign tasks after assign all roles, as it should be + ShipStatus.Instance.Begin(); + + GameOptionsSender.AllSenders.Clear(); + foreach (PlayerControl pc in Main.AllPlayerControls) GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); + + Utils.SyncAllSettings(); } - }, 0.6f, "Set Disconnected"); - - LateTask.New(() => + } + catch { - try - { - if (!(AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended)) - { - ShipStatusBeginPatch.RolesIsAssigned = true; + Logger.Warn($"Game ended? {AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended}", "ShipStatus.Begin"); + } + }, 4f, "Assign Tasks"); + } +} - // Assign tasks after assign all roles, as it should be - ShipStatus.Instance.Begin(); +internal static class RepairSender +{ + public static bool Enabled; + private static bool TypingAmount; - GameOptionsSender.AllSenders.Clear(); - foreach (PlayerControl pc in Main.AllPlayerControls) GameOptionsSender.AllSenders.Add(new PlayerGameOptionsSender(pc)); + private static int SystemType; + private static int Amount; - Utils.SyncAllSettings(); - } - } - catch - { - Logger.Warn($"Game ended? {AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended}", "ShipStatus.Begin"); - } - }, 4f, "Assign Tasks"); + public static void Input(int num) + { + if (!TypingAmount) + { + SystemType *= 10; + SystemType += num; + } + else + { + Amount *= 10; + Amount += num; } } - internal static class RepairSender + public static void InputEnter() { - public static bool Enabled; - private static bool TypingAmount; + if (!TypingAmount) + TypingAmount = true; + else + Send(); + } - private static int SystemType; - private static int Amount; + private static void Send() + { + ShipStatus.Instance.RpcUpdateSystem((SystemTypes)SystemType, (byte)Amount); + Reset(); + } - public static void Input(int num) - { - if (!TypingAmount) - { - SystemType *= 10; - SystemType += num; - } - else - { - Amount *= 10; - Amount += num; - } - } + public static void Reset() + { + TypingAmount = false; + SystemType = 0; + Amount = 0; + } - public static void InputEnter() - { - if (!TypingAmount) - TypingAmount = true; - else - Send(); - } - - private static void Send() - { - ShipStatus.Instance.RpcUpdateSystem((SystemTypes)SystemType, (byte)Amount); - Reset(); - } - - public static void Reset() - { - TypingAmount = false; - SystemType = 0; - Amount = 0; - } - - public static string GetText() - { - return SystemType + "(" + (SystemTypes)SystemType + ")\r\n" + Amount; - } + public static string GetText() + { + return SystemType + "(" + (SystemTypes)SystemType + ")\r\n" + Amount; } } \ No newline at end of file diff --git a/Patches/HudSpritePatch.cs b/Patches/HudSpritePatch.cs index 62c75ab89..c47a2ca55 100644 --- a/Patches/HudSpritePatch.cs +++ b/Patches/HudSpritePatch.cs @@ -6,399 +6,398 @@ using EHR.Patches; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +public static class CustomButton { - public static class CustomButton + public static Sprite Get(string name) { - public static Sprite Get(string name) - { - return Utils.LoadSprite($"EHR.Resources.Images.Skills.{name}.png", 115f); - } + return Utils.LoadSprite($"EHR.Resources.Images.Skills.{name}.png", 115f); } +} - [HarmonyPriority(520)] - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] - public static class HudSpritePatch +[HarmonyPriority(520)] +[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] +public static class HudSpritePatch +{ + private static Sprite Kill; + private static Sprite Ability; + private static Sprite Vent; + private static Sprite Sabotage; + private static Sprite Pet; + private static Sprite Report; + + private static long LastErrorTime; + + public static void Postfix(HudManager __instance) { - private static Sprite Kill; - private static Sprite Ability; - private static Sprite Vent; - private static Sprite Sabotage; - private static Sprite Pet; - private static Sprite Report; - - private static long LastErrorTime; - - public static void Postfix(HudManager __instance) + try { - try + PlayerControl player = PlayerControl.LocalPlayer; + if (player == null) return; + + if (!Main.EnableCustomButton.Value || !Main.ProcessShapeshifts || Mastermind.ManipulatedPlayers.ContainsKey(player.PlayerId)) return; + if (!SetHudActivePatch.IsActive || !player.IsAlive() || !CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + if (!AmongUsClient.Instance.IsGameStarted || !Main.IntroDestroyed) { - PlayerControl player = PlayerControl.LocalPlayer; - if (player == null) return; + Kill = null; + Ability = null; + Vent = null; + Sabotage = null; + Pet = null; + Report = null; + return; + } - if (!Main.EnableCustomButton.Value || !Main.ProcessShapeshifts || Mastermind.ManipulatedPlayers.ContainsKey(player.PlayerId)) return; - if (!SetHudActivePatch.IsActive || !player.IsAlive() || !CustomGameMode.Standard.IsActiveOrIntegrated()) return; + bool shapeshifting = player.IsShifted(); - if (!AmongUsClient.Instance.IsGameStarted || !Main.IntroDestroyed) - { - Kill = null; - Ability = null; - Vent = null; - Sabotage = null; - Pet = null; - Report = null; - return; - } + if (!Kill) Kill = __instance.KillButton.graphic.sprite; + if (!Ability) Ability = __instance.AbilityButton.graphic.sprite; + if (!Vent) Vent = __instance.ImpostorVentButton.graphic.sprite; + if (!Sabotage) Sabotage = __instance.SabotageButton.graphic.sprite; + if (!Pet) Pet = __instance.PetButton.graphic.sprite; + if (!Report) Report = __instance.ReportButton.graphic.sprite; - bool shapeshifting = player.IsShifted(); + Sprite newKillButton = Kill; + Sprite newAbilityButton = Ability; + Sprite newVentButton = Vent; + Sprite newSabotageButton = Sabotage; + Sprite newPetButton = Pet; + Sprite newReportButton = Report; - if (!Kill) Kill = __instance.KillButton.graphic.sprite; - if (!Ability) Ability = __instance.AbilityButton.graphic.sprite; - if (!Vent) Vent = __instance.ImpostorVentButton.graphic.sprite; - if (!Sabotage) Sabotage = __instance.SabotageButton.graphic.sprite; - if (!Pet) Pet = __instance.PetButton.graphic.sprite; - if (!Report) Report = __instance.ReportButton.graphic.sprite; + switch (player.GetCustomRole()) + { + case CustomRoles.Enderman: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("abscond"); + else + newSabotageButton = CustomButton.Get("abscond"); - Sprite newKillButton = Kill; - Sprite newAbilityButton = Ability; - Sprite newVentButton = Vent; - Sprite newSabotageButton = Sabotage; - Sprite newPetButton = Pet; - Sprite newReportButton = Report; + break; + case CustomRoles.Wizard: + newAbilityButton = CustomButton.Get("Up"); + break; + case CustomRoles.Socialite: + newKillButton = CustomButton.Get("Mark"); + break; + case CustomRoles.Hitman: + case CustomRoles.Augmenter: + newAbilityButton = CustomButton.Get("Mark"); + break; + case CustomRoles.Echo: + newAbilityButton = player.IsShifted() ? Kill : CustomButton.Get("Puttpuer"); + break; + case CustomRoles.Shifter: + newKillButton = CustomButton.Get("Swap"); + break; + case CustomRoles.Changeling: + newAbilityButton = CustomButton.Get("GlitchMimic"); - switch (player.GetCustomRole()) - { - case CustomRoles.Enderman: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("abscond"); - else - newSabotageButton = CustomButton.Get("abscond"); + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Swap"); + else + newVentButton = CustomButton.Get("Swap"); - break; - case CustomRoles.Wizard: - newAbilityButton = CustomButton.Get("Up"); - break; - case CustomRoles.Socialite: + break; + case CustomRoles.Vulture: + newReportButton = CustomButton.Get("Eat"); + break; + case CustomRoles.Sentry: + newPetButton = CustomButton.Get("Sentry"); + break; + case CustomRoles.Commander: + newAbilityButton = CustomButton.Get("Commander"); + break; + case CustomRoles.Amnesiac: + if (Amnesiac.RememberMode.GetValue() == 1) + newKillButton = CustomButton.Get("AmnesiacKill"); + else + newReportButton = CustomButton.Get("AmnesiacReport"); + + break; + case CustomRoles.Assassin: + case CustomRoles.Undertaker: + if (Main.PlayerStates[player.PlayerId].Role is not Assassin assassin) break; + + if (Options.UsePets.GetBool()) + { newKillButton = CustomButton.Get("Mark"); - break; - case CustomRoles.Hitman: - case CustomRoles.Augmenter: - newAbilityButton = CustomButton.Get("Mark"); - break; - case CustomRoles.Echo: - newAbilityButton = player.IsShifted() ? Kill : CustomButton.Get("Puttpuer"); - break; - case CustomRoles.Shifter: - newKillButton = CustomButton.Get("Swap"); - break; - case CustomRoles.Changeling: - newAbilityButton = CustomButton.Get("GlitchMimic"); - - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Swap"); - else - newVentButton = CustomButton.Get("Swap"); - - break; - case CustomRoles.Vulture: - newReportButton = CustomButton.Get("Eat"); - break; - case CustomRoles.Sentry: - newPetButton = CustomButton.Get("Sentry"); - break; - case CustomRoles.Commander: - newAbilityButton = CustomButton.Get("Commander"); - break; - case CustomRoles.Amnesiac: - if (Amnesiac.RememberMode.GetValue() == 1) - newKillButton = CustomButton.Get("AmnesiacKill"); - else - newReportButton = CustomButton.Get("AmnesiacReport"); - - break; - case CustomRoles.Assassin: - case CustomRoles.Undertaker: - if (Main.PlayerStates[player.PlayerId].Role is not Assassin assassin) break; - - if (Options.UsePets.GetBool()) + if (assassin.MarkedPlayer != byte.MaxValue) newPetButton = CustomButton.Get("Assassinate"); + } + else + { + if (!shapeshifting) { newKillButton = CustomButton.Get("Mark"); - if (assassin.MarkedPlayer != byte.MaxValue) newPetButton = CustomButton.Get("Assassinate"); - } - else - { - if (!shapeshifting) - { - newKillButton = CustomButton.Get("Mark"); - if (assassin.MarkedPlayer != byte.MaxValue) newAbilityButton = CustomButton.Get("Assassinate"); - } + if (assassin.MarkedPlayer != byte.MaxValue) newAbilityButton = CustomButton.Get("Assassinate"); } + } - break; - case CustomRoles.Gaulois: - newKillButton = CustomButton.Get("Gaulois"); - break; - case CustomRoles.Consort: - case CustomRoles.Escort: - newKillButton = CustomButton.Get("GlitchHack"); - break; - case CustomRoles.Glitch: - if (Main.PlayerStates[player.PlayerId].Role is not Glitch gc) break; + break; + case CustomRoles.Gaulois: + newKillButton = CustomButton.Get("Gaulois"); + break; + case CustomRoles.Consort: + case CustomRoles.Escort: + newKillButton = CustomButton.Get("GlitchHack"); + break; + case CustomRoles.Glitch: + if (Main.PlayerStates[player.PlayerId].Role is not Glitch gc) break; - if (gc.KCDTimer > 0 && gc.HackCDTimer <= 0) newKillButton = CustomButton.Get("GlitchHack"); + if (gc.KCDTimer > 0 && gc.HackCDTimer <= 0) newKillButton = CustomButton.Get("GlitchHack"); - newSabotageButton = CustomButton.Get("GlitchMimic"); - break; - case CustomRoles.Jester: - newAbilityButton = CustomButton.Get("JesterVent"); - break; - case CustomRoles.Swapster: - newAbilityButton = CustomButton.Get("Transport"); - break; - case CustomRoles.Disperser: + newSabotageButton = CustomButton.Get("GlitchMimic"); + break; + case CustomRoles.Jester: + newAbilityButton = CustomButton.Get("JesterVent"); + break; + case CustomRoles.Swapster: + newAbilityButton = CustomButton.Get("Transport"); + break; + case CustomRoles.Disperser: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Disperse"); + else if (!shapeshifting) newAbilityButton = CustomButton.Get("Disperse"); + + break; + case CustomRoles.ImperiusCurse: + case CustomRoles.Twister: + if (player.Is(CustomRoles.Twister) && Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Transport"); + else if (!shapeshifting) newAbilityButton = CustomButton.Get("Transport"); + + break; + case CustomRoles.Deputy: + newKillButton = CustomButton.Get("Handcuff"); + break; + case CustomRoles.Pursuer: + newKillButton = CustomButton.Get("Pursuer"); + break; + case CustomRoles.Alchemist: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Drink"); + else + newAbilityButton = CustomButton.Get("Drink"); + + break; + case CustomRoles.Jailor: + newKillButton = CustomButton.Get("Jail"); + break; + case CustomRoles.Penguin: + newAbilityButton = CustomButton.Get("Timer"); + break; + case CustomRoles.Revolutionist: + newKillButton = CustomButton.Get("Tag"); + break; + case CustomRoles.DonutDelivery: + newKillButton = CustomButton.Get("Donut"); + break; + case CustomRoles.Sapper: + case CustomRoles.Bomber: + case CustomRoles.Nuker: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Bomb"); + else + newAbilityButton = CustomButton.Get("Bomb"); + + break; + case CustomRoles.Camouflager: + newAbilityButton = CustomButton.Get("Camo"); + break; + case CustomRoles.Agitater: + newKillButton = CustomButton.Get("Pass"); + break; + case CustomRoles.Arsonist: + newKillButton = CustomButton.Get("Douse"); + + if (player.IsDouseDone() || (Options.ArsonistCanIgniteAnytime.GetBool() && Utils.GetDousedPlayerCount(player.PlayerId).Item1 >= Options.ArsonistMinPlayersToIgnite.GetInt())) + { if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Disperse"); - else if (!shapeshifting) newAbilityButton = CustomButton.Get("Disperse"); - - break; - case CustomRoles.ImperiusCurse: - case CustomRoles.Twister: - if (player.Is(CustomRoles.Twister) && Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Transport"); - else if (!shapeshifting) newAbilityButton = CustomButton.Get("Transport"); - - break; - case CustomRoles.Deputy: - newKillButton = CustomButton.Get("Handcuff"); - break; - case CustomRoles.Pursuer: - newKillButton = CustomButton.Get("Pursuer"); - break; - case CustomRoles.Alchemist: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Drink"); + newPetButton = CustomButton.Get("Ignite"); else - newAbilityButton = CustomButton.Get("Drink"); + newVentButton = CustomButton.Get("Ignite"); + } - break; - case CustomRoles.Jailor: - newKillButton = CustomButton.Get("Jail"); - break; - case CustomRoles.Penguin: - newAbilityButton = CustomButton.Get("Timer"); - break; - case CustomRoles.Revolutionist: - newKillButton = CustomButton.Get("Tag"); - break; - case CustomRoles.DonutDelivery: - newKillButton = CustomButton.Get("Donut"); - break; - case CustomRoles.Sapper: - case CustomRoles.Bomber: - case CustomRoles.Nuker: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Bomb"); - else - newAbilityButton = CustomButton.Get("Bomb"); + break; + case CustomRoles.Pyromaniac: + newKillButton = CustomButton.Get("Pyromaniac"); + break; + case CustomRoles.FireWorks: + if (Main.PlayerStates[player.PlayerId].Role is not FireWorks fw) break; - break; - case CustomRoles.Camouflager: - newAbilityButton = CustomButton.Get("Camo"); - break; - case CustomRoles.Agitater: - newKillButton = CustomButton.Get("Pass"); - break; - case CustomRoles.Arsonist: - newKillButton = CustomButton.Get("Douse"); + newAbilityButton = CustomButton.Get(fw.nowFireWorksCount == 0 ? "FireworkD" : "FireworkP"); + break; + case CustomRoles.Hacker: + newAbilityButton = CustomButton.Get("Hack"); + break; + case CustomRoles.Hangman: + if (shapeshifting) newAbilityButton = CustomButton.Get("Hangman"); - if (player.IsDouseDone() || (Options.ArsonistCanIgniteAnytime.GetBool() && Utils.GetDousedPlayerCount(player.PlayerId).Item1 >= Options.ArsonistMinPlayersToIgnite.GetInt())) - { - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Ignite"); - else - newVentButton = CustomButton.Get("Ignite"); - } + break; + case CustomRoles.Paranoia: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Paranoid"); + else + newAbilityButton = CustomButton.Get("Paranoid"); - break; - case CustomRoles.Pyromaniac: - newKillButton = CustomButton.Get("Pyromaniac"); - break; - case CustomRoles.FireWorks: - if (Main.PlayerStates[player.PlayerId].Role is not FireWorks fw) break; + break; + case CustomRoles.Mayor when Mayor.MayorHasPortableButton.GetBool(): + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Button"); + else + newAbilityButton = CustomButton.Get("Button"); - newAbilityButton = CustomButton.Get(fw.nowFireWorksCount == 0 ? "FireworkD" : "FireworkP"); - break; - case CustomRoles.Hacker: - newAbilityButton = CustomButton.Get("Hack"); - break; - case CustomRoles.Hangman: - if (shapeshifting) newAbilityButton = CustomButton.Get("Hangman"); + break; + case CustomRoles.Puppeteer: + newKillButton = CustomButton.Get("Puttpuer"); + break; + case CustomRoles.Aid: + case CustomRoles.Medic: + newKillButton = CustomButton.Get("Shield"); + break; + case CustomRoles.Gangster: + if (Gangster.CanRecruit(player.PlayerId)) newKillButton = CustomButton.Get("Sidekick"); - break; - case CustomRoles.Paranoia: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Paranoid"); - else - newAbilityButton = CustomButton.Get("Paranoid"); + break; + case CustomRoles.Succubus: + newKillButton = CustomButton.Get("Subbus"); + break; + case CustomRoles.Innocent: + newKillButton = CustomButton.Get("Suidce"); + break; + case CustomRoles.EvilTracker: + newAbilityButton = CustomButton.Get("Track"); + break; + case CustomRoles.Vampire: + newKillButton = CustomButton.Get("Bite"); + break; + case CustomRoles.Veteran: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Veteran"); + else + newAbilityButton = CustomButton.Get("Veteran"); - break; - case CustomRoles.Mayor when Mayor.MayorHasPortableButton.GetBool(): - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Button"); - else - newAbilityButton = CustomButton.Get("Button"); + break; + case CustomRoles.Lighter: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Lighter"); + else + newAbilityButton = CustomButton.Get("Lighter"); - break; - case CustomRoles.Puppeteer: - newKillButton = CustomButton.Get("Puttpuer"); - break; - case CustomRoles.Aid: - case CustomRoles.Medic: - newKillButton = CustomButton.Get("Shield"); - break; - case CustomRoles.Gangster: - if (Gangster.CanRecruit(player.PlayerId)) newKillButton = CustomButton.Get("Sidekick"); + break; + case CustomRoles.SecurityGuard: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("BlockSabo"); + else + newAbilityButton = CustomButton.Get("BlockSabo"); - break; - case CustomRoles.Succubus: - newKillButton = CustomButton.Get("Subbus"); - break; - case CustomRoles.Innocent: - newKillButton = CustomButton.Get("Suidce"); - break; - case CustomRoles.EvilTracker: - newAbilityButton = CustomButton.Get("Track"); - break; - case CustomRoles.Vampire: - newKillButton = CustomButton.Get("Bite"); - break; - case CustomRoles.Veteran: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Veteran"); - else - newAbilityButton = CustomButton.Get("Veteran"); + break; + case CustomRoles.Ventguard: + newAbilityButton = CustomButton.Get("Block"); + break; + case CustomRoles.Romantic: + newKillButton = CustomButton.Get(!Romantic.HasPickedPartner ? "Romance" : "RomanticProtect"); + break; + case CustomRoles.VengefulRomantic: + newKillButton = CustomButton.Get("RomanticKill"); + break; + case CustomRoles.Miner: + if (!Options.UsePets.GetBool()) + newAbilityButton = CustomButton.Get("Mine"); + else + newPetButton = CustomButton.Get("Mine"); - break; - case CustomRoles.Lighter: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Lighter"); - else - newAbilityButton = CustomButton.Get("Lighter"); + break; + case CustomRoles.Analyst: + case CustomRoles.Witness: + newKillButton = CustomButton.Get("Examine"); + break; + case CustomRoles.Postman: + newKillButton = CustomButton.Get("Deliver"); + break; + case CustomRoles.Pelican: + newKillButton = CustomButton.Get("Vulture"); + break; + case CustomRoles.TimeMaster: + if (Options.UsePets.GetBool()) + newPetButton = CustomButton.Get("Time Master"); + else + newAbilityButton = CustomButton.Get("Time Master"); - break; - case CustomRoles.SecurityGuard: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("BlockSabo"); - else - newAbilityButton = CustomButton.Get("BlockSabo"); + break; + case CustomRoles.Sheriff: + newKillButton = CustomButton.Get("Kill"); + break; + case CustomRoles.Swiftclaw: + if (Options.UsePets.GetBool() && !Options.UseUnshiftTrigger.GetBool()) + newPetButton = CustomButton.Get("Dash"); + else + newAbilityButton = CustomButton.Get("Dash"); - break; - case CustomRoles.Ventguard: - newAbilityButton = CustomButton.Get("Block"); - break; - case CustomRoles.Romantic: - newKillButton = CustomButton.Get(!Romantic.HasPickedPartner ? "Romance" : "RomanticProtect"); - break; - case CustomRoles.VengefulRomantic: - newKillButton = CustomButton.Get("RomanticKill"); - break; - case CustomRoles.Miner: - if (!Options.UsePets.GetBool()) - newAbilityButton = CustomButton.Get("Mine"); - else - newPetButton = CustomButton.Get("Mine"); - - break; - case CustomRoles.Analyst: - case CustomRoles.Witness: - newKillButton = CustomButton.Get("Examine"); - break; - case CustomRoles.Postman: - newKillButton = CustomButton.Get("Deliver"); - break; - case CustomRoles.Pelican: - newKillButton = CustomButton.Get("Vulture"); - break; - case CustomRoles.TimeMaster: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("Time Master"); - else - newAbilityButton = CustomButton.Get("Time Master"); - - break; - case CustomRoles.Sheriff: - newKillButton = CustomButton.Get("Kill"); - break; - case CustomRoles.Swiftclaw: - if (Options.UsePets.GetBool() && !Options.UseUnshiftTrigger.GetBool()) - newPetButton = CustomButton.Get("Dash"); - else - newAbilityButton = CustomButton.Get("Dash"); - - break; - case CustomRoles.Swooper: - case CustomRoles.Chameleon: - case CustomRoles.Wraith: - newAbilityButton = CustomButton.Get("invisible"); - break; - case CustomRoles.Visionary: - newAbilityButton = CustomButton.Get("prophecies"); - break; - case CustomRoles.Escapee: - if (Options.UsePets.GetBool()) - newPetButton = CustomButton.Get("abscond"); - else - newAbilityButton = CustomButton.Get("abscond"); - - break; - case CustomRoles.Tunneler: + break; + case CustomRoles.Swooper: + case CustomRoles.Chameleon: + case CustomRoles.Wraith: + newAbilityButton = CustomButton.Get("invisible"); + break; + case CustomRoles.Visionary: + newAbilityButton = CustomButton.Get("prophecies"); + break; + case CustomRoles.Escapee: + if (Options.UsePets.GetBool()) newPetButton = CustomButton.Get("abscond"); - break; - case CustomRoles.Farseer: - newKillButton = CustomButton.Get("prophecies"); - break; - case CustomRoles.Warlock: - if (Options.UsePets.GetBool()) - { - newKillButton = CustomButton.Get("Curse"); - if (Warlock.IsCurseAndKill.TryGetValue(player.PlayerId, out bool curse) && curse) newAbilityButton = CustomButton.Get("CurseKill"); - } - else if (!shapeshifting) - { - newKillButton = CustomButton.Get("Curse"); - if (Warlock.IsCurseAndKill.TryGetValue(player.PlayerId, out bool curse) && curse) newAbilityButton = CustomButton.Get("CurseKill"); - } + else + newAbilityButton = CustomButton.Get("abscond"); - break; - } + break; + case CustomRoles.Tunneler: + newPetButton = CustomButton.Get("abscond"); + break; + case CustomRoles.Farseer: + newKillButton = CustomButton.Get("prophecies"); + break; + case CustomRoles.Warlock: + if (Options.UsePets.GetBool()) + { + newKillButton = CustomButton.Get("Curse"); + if (Warlock.IsCurseAndKill.TryGetValue(player.PlayerId, out bool curse) && curse) newAbilityButton = CustomButton.Get("CurseKill"); + } + else if (!shapeshifting) + { + newKillButton = CustomButton.Get("Curse"); + if (Warlock.IsCurseAndKill.TryGetValue(player.PlayerId, out bool curse) && curse) newAbilityButton = CustomButton.Get("CurseKill"); + } - if (player.GetCustomRole().UsesPetInsteadOfKill()) newPetButton = newKillButton; - - - __instance.KillButton.graphic.sprite = newKillButton; - __instance.AbilityButton.graphic.sprite = newAbilityButton; - __instance.ImpostorVentButton.graphic.sprite = newVentButton; - __instance.SabotageButton.graphic.sprite = newSabotageButton; - __instance.PetButton.graphic.sprite = newPetButton; - __instance.ReportButton.graphic.sprite = newReportButton; - - new[] - { - __instance.KillButton.graphic, - __instance.AbilityButton.graphic, - __instance.ImpostorVentButton.graphic, - __instance.SabotageButton.graphic, - __instance.PetButton.graphic, - __instance.ReportButton.graphic - }.Do(x => x.SetCooldownNormalizedUvs()); + break; } - catch (Exception e) + + if (player.GetCustomRole().UsesPetInsteadOfKill()) newPetButton = newKillButton; + + + __instance.KillButton.graphic.sprite = newKillButton; + __instance.AbilityButton.graphic.sprite = newAbilityButton; + __instance.ImpostorVentButton.graphic.sprite = newVentButton; + __instance.SabotageButton.graphic.sprite = newSabotageButton; + __instance.PetButton.graphic.sprite = newPetButton; + __instance.ReportButton.graphic.sprite = newReportButton; + + new[] { - if (Utils.TimeStamp - LastErrorTime > 10) - { - LastErrorTime = Utils.TimeStamp; - Utils.ThrowException(e); - } + __instance.KillButton.graphic, + __instance.AbilityButton.graphic, + __instance.ImpostorVentButton.graphic, + __instance.SabotageButton.graphic, + __instance.PetButton.graphic, + __instance.ReportButton.graphic + }.Do(x => x.SetCooldownNormalizedUvs()); + } + catch (Exception e) + { + if (Utils.TimeStamp - LastErrorTime > 10) + { + LastErrorTime = Utils.TimeStamp; + Utils.ThrowException(e); } } } diff --git a/Patches/IntroPatch.cs b/Patches/IntroPatch.cs index a97fd4c8d..6bc573ad9 100644 --- a/Patches/IntroPatch.cs +++ b/Patches/IntroPatch.cs @@ -8,723 +8,723 @@ using Il2CppSystem.Collections.Generic; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] +internal static class SetUpRoleTextPatch { - [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))] - internal static class SetUpRoleTextPatch + public static bool IsInIntro; + + public static void Postfix(IntroCutscene __instance) { - public static bool IsInIntro; + // After showing team for non-modded clients, update player names. + IsInIntro = false; + Utils.DoNotifyRoles(NoCache: true); - public static void Postfix(IntroCutscene __instance) + PlayerControl lp = PlayerControl.LocalPlayer; + + LateTask.New(() => { - // After showing team for non-modded clients, update player names. - IsInIntro = false; - Utils.DoNotifyRoles(NoCache: true); - - PlayerControl lp = PlayerControl.LocalPlayer; - - LateTask.New(() => - { - switch (Options.CurrentGameMode) - { - case CustomGameMode.SoloKombat: - { - Color color = ColorUtility.TryParseHtmlString("#f55252", out Color c) ? c : new(255, 255, 255, 255); - CustomRoles role = lp.GetCustomRole(); - __instance.YouAreText.color = color; - __instance.RoleText.text = Utils.GetRoleName(role); - __instance.RoleText.color = Utils.GetRoleColor(role); - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = lp.GetRoleInfo(); - break; - } - case CustomGameMode.FFA: - { - Color color = ColorUtility.TryParseHtmlString("#00ffff", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.transform.gameObject.SetActive(false); - __instance.RoleText.text = GetString("Killer"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("KillerInfo"); - break; - } - case CustomGameMode.MoveAndStop: - { - Color color = ColorUtility.TryParseHtmlString("#00ffa5", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.transform.gameObject.SetActive(false); - __instance.RoleText.text = GetString("MoveAndStop"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("TaskerInfo"); - break; - } - case CustomGameMode.HotPotato: - { - Color color = ColorUtility.TryParseHtmlString("#e8cd46", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.transform.gameObject.SetActive(false); - __instance.RoleText.text = GetString("HotPotato"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("PotatoInfo"); - break; - } - case CustomGameMode.Speedrun: - { - Color color = Utils.GetRoleColor(CustomRoles.Speedrunner); - __instance.YouAreText.transform.gameObject.SetActive(false); - __instance.RoleText.text = GetString("Runner"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("RunnerInfo"); - break; - } - case CustomGameMode.CaptureTheFlag: - { - Color color = ColorUtility.TryParseHtmlString("#1313c2", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.transform.gameObject.SetActive(false); - __instance.RoleText.text = GetString("CTFPlayer"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("CTFPlayerInfo"); - break; - } - case CustomGameMode.NaturalDisasters: - { - Color color = ColorUtility.TryParseHtmlString("#03fc4a", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.color = color; - __instance.RoleText.text = GetString("NDPlayer"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("NDPlayerInfo"); - break; - } - case CustomGameMode.RoomRush: - { - Color color = ColorUtility.TryParseHtmlString("#ffab1b", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.color = color; - __instance.RoleText.text = GetString("RRPlayer"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("RRPlayerInfo"); - break; - } - case CustomGameMode.AllInOne: - { - Color color = ColorUtility.TryParseHtmlString("#f542ad", out Color c) ? c : new(255, 255, 255, 255); - __instance.YouAreText.color = color; - __instance.RoleText.text = GetString("AllInOne"); - __instance.RoleText.color = color; - __instance.RoleBlurbText.color = color; - __instance.RoleBlurbText.text = GetString("AllInOneInfo"); - break; - } - default: - { - CustomRoles role = lp.GetCustomRole(); - - if (!role.IsVanilla()) - { - __instance.YouAreText.color = Utils.GetRoleColor(role); - __instance.RoleText.text = Utils.GetRoleName(role); - __instance.RoleText.color = Utils.GetRoleColor(role); - __instance.RoleBlurbText.color = Utils.GetRoleColor(role); - __instance.RoleBlurbText.text = "" + lp.GetRoleInfo() + ""; - } - - foreach (CustomRoles subRole in Main.PlayerStates[lp.PlayerId].SubRoles) - { - if (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor && subRole == CustomRoles.Lovers) continue; - - __instance.RoleBlurbText.text += "\n" + Utils.ColorString(Utils.GetRoleColor(subRole), GetString($"{subRole}Info")); - } - - __instance.RoleText.text += Utils.GetSubRolesText(lp.PlayerId, false, true); - break; - } - } - }, 0.0001f, "Override Role Text"); - - if (!AmongUsClient.Instance.AmHost) - { - LateTask.New(() => - { - if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || lp == null) return; - - lp.SetName(Main.AllPlayerNames[lp.PlayerId]); - }, 1f, "Reset Name For Modded Client"); - } - } - } - - [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] - internal static class CoBeginPatch - { - public static void Prefix() - { - StringBuilder sb = new(); - sb.Append("------------Display Names------------\n"); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text} ({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", string.Empty)})\n"); - pc.cosmetics.nameText.text = pc.name; - } - - sb.Append("------------Roles------------\n"); - foreach (PlayerControl pc in Main.AllPlayerControls) sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); - - sb.Append("------------Platforms------------\n"); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - try - { - string text = pc.AmOwner ? "[*]" : " "; - text += $"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString().Replace("Standalone", string.Empty),-11}"; - - if (Main.PlayerVersion.TryGetValue(pc.PlayerId, out PlayerVersion pv)) - text += $":Mod({pv.forkId}/{pv.version}:{pv.tag})"; - else - text += ":Vanilla"; - - sb.Append(text + "\n"); - } - catch (Exception ex) - { - Logger.Exception(ex, "Platform"); - } - } - - sb.Append("------------Vanilla Settings------------\n"); - System.Collections.Generic.IEnumerable tmp = GameOptionsManager.Instance.CurrentGameOptions.ToHudString(GameData.Instance ? GameData.Instance.PlayerCount : 10).Split("\r\n").Skip(1); - foreach (string t in tmp) sb.Append(t + "\n"); - - sb.Append("------------Modded Settings------------\n"); - - foreach (OptionItem o in OptionItem.AllOptions) - if (!o.IsCurrentlyHidden() && (o.Parent?.GetBool() ?? !o.GetString().Equals("0%"))) - sb.Append($"{(o.Parent == null ? o.GetName(true, true).RemoveHtmlTags().PadRightV2(40) : $"┗ {o.GetName(true, true).RemoveHtmlTags()}".PadRightV2(41))}:{o.GetString().RemoveHtmlTags()}\n"); - - sb.Append("-------------Other Information-------------\n"); - sb.Append($"Number of players: {Main.AllPlayerControls.Length}\n"); - sb.Append($"Game mode: {GetString(Options.CurrentGameMode.ToString())}\n"); - sb.Append($"Map: {Main.CurrentMap}"); - - Logger.Info("\n" + sb, "GameInfo", multiLine: true); - - RPC.RpcVersionCheck(); - - SetupLongRoleDescriptions(); - - Utils.NotifyRoles(NoCache: true); - - GameStates.InGame = true; - } - - private static void SetupLongRoleDescriptions() - { - try - { - Utils.LongRoleDescriptions.Clear(); - - int charsInOneLine = GetUserTrueLang() is SupportedLangs.Russian or SupportedLangs.SChinese or SupportedLangs.TChinese or SupportedLangs.Japanese or SupportedLangs.Korean ? 35 : 50; - - foreach (PlayerControl seer in Main.AllPlayerControls) - { - string longInfo = seer.GetRoleInfo(true).Split("\n\n")[0]; - if (longInfo.Contains("):\n")) longInfo = longInfo.Split("):\n")[1]; - - var tooLong = false; - bool showLongInfo = Options.ShowLongInfo.GetBool(); - - if (showLongInfo) - { - if (longInfo.Length > 296) - { - longInfo = longInfo[..296]; - longInfo += "..."; - tooLong = true; - } - - for (int i = charsInOneLine; i < longInfo.Length; i += charsInOneLine) - { - if (tooLong && i > 296) break; - - int index = longInfo.LastIndexOf(' ', i); - if (index != -1) longInfo = longInfo.Insert(index + 1, "\n"); - } - } - - longInfo = $"<#ffffff>{longInfo}"; - - int lines = longInfo.Count(x => x == '\n'); - int readTime = 20 + (lines * 5); - - Utils.LongRoleDescriptions[seer.PlayerId] = (longInfo, readTime, tooLong); - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - } - - [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] - internal static class BeginCrewmatePatch - { - public static bool Prefix(IntroCutscene __instance, ref List teamToDisplay) - { - CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); - - if (PlayerControl.LocalPlayer.Is(CustomRoleTypes.Neutral) && !role.IsMadmate()) - { - teamToDisplay = new(); - teamToDisplay.Add(PlayerControl.LocalPlayer); - - byte id = PlayerControl.LocalPlayer.PlayerId; - - switch (Main.PlayerStates[id].Role) - { - case Lawyer: - teamToDisplay.Add(Utils.GetPlayerById(Lawyer.Target[id])); - break; - case Executioner: - teamToDisplay.Add(Utils.GetPlayerById(Executioner.Target[id])); - break; - } - } - else if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) - { - teamToDisplay = new(); - teamToDisplay.Add(PlayerControl.LocalPlayer); - __instance.BeginImpostor(teamToDisplay); - __instance.overlayHandle.color = Palette.ImpostorRed; - return false; - } - - if (role == CustomRoles.LovingCrewmate || PlayerControl.LocalPlayer.Is(CustomRoles.Lovers)) - { - teamToDisplay = new(); - teamToDisplay.Add(PlayerControl.LocalPlayer); - teamToDisplay.Add(Main.LoversPlayers.FirstOrDefault(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId)); - } - else if (role == CustomRoles.LovingImpostor) teamToDisplay.Add(Main.LoversPlayers.FirstOrDefault(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId)); - - if (CustomTeamManager.EnabledCustomTeams.Count > 0) - { - CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(PlayerControl.LocalPlayer.PlayerId); - - if (team != null) - { - teamToDisplay = new(); - - foreach (PlayerControl pc in Main.AllPlayerControls) - if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, PlayerControl.LocalPlayer.PlayerId)) - teamToDisplay.Add(pc); - } - } - - if (CustomGameMode.FFA.IsActiveOrIntegrated() && FFAManager.FFATeamMode.GetBool() && FFAManager.PlayerTeams.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out int ffaTeam)) - { - teamToDisplay = new(); - - foreach (PlayerControl pc in Main.AllPlayerControls) - if (FFAManager.PlayerTeams.TryGetValue(pc.PlayerId, out int team) && team == ffaTeam) - teamToDisplay.Add(pc); - } - - return true; - } - - public static void Postfix(IntroCutscene __instance, ref List teamToDisplay) - { - CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); - - __instance.ImpostorText.gameObject.SetActive(false); - - if (PlayerControl.LocalPlayer.Is(CustomRoles.Bloodlust)) - { - __instance.TeamTitle.text = GetString("TeamCrewmate"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Bloodlust); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Bloodlust"); - } - else if (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) - { - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(role.GetRoleTypes()); - byte otherLoverId = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId).PlayerId; - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = string.Format(GetString($"SubText.{role}"), otherLoverId.ColoredPlayerName()); - } - else - { - switch (role.GetCustomRoleTypes()) - { - case CustomRoleTypes.Impostor: - __instance.TeamTitle.text = GetString("TeamImpostor"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Impostor"); - break; - case CustomRoleTypes.Crewmate: - __instance.TeamTitle.text = GetString("TeamCrewmate"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(140, 255, 255, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Crewmate"); - break; - case CustomRoleTypes.Neutral: - - if (Options.UniqueNeutralRevealScreen.GetBool()) - { - __instance.TeamTitle.text = GetString($"{role}"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString($"{role}Info"); - } - else - { - __instance.TeamTitle.text = GetString("TeamNeutral"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 171, 27, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Neutral"); - } - - break; - } - - if (Main.LoversPlayers.Count == 2 && Main.LoversPlayers.Exists(x => x.IsLocalPlayer())) - { - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Lovers); - byte otherLoverId = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId).PlayerId; - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.DestroyTranslator(); - __instance.ImpostorText.text = string.Format(GetString("SubText.LovingCrewmate"), otherLoverId.ColoredPlayerName()); - } - } - - // ReSharper disable Unity.IncorrectMonoBehaviourInstantiation - try - { - PlayerControl.LocalPlayer.Data.Role.IntroSound = role switch - { - CustomRoles.Terrorist or - CustomRoles.Sapper or - CustomRoles.Bomber or - CustomRoles.Nuker - => ShipStatus.Instance.CommonTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixWiring)?.MinigamePrefab.OpenSound, - - CustomRoles.Sheriff or - CustomRoles.Veteran - => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.PutAwayPistols)?.MinigamePrefab.OpenSound, - - CustomRoles.Cleaner or - CustomRoles.Cleanser - => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.PolishRuby)?.MinigamePrefab.OpenSound, - - CustomRoles.Dictator or - CustomRoles.Lawyer or - CustomRoles.Judge or - CustomRoles.Mayor - => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixShower)?.MinigamePrefab.OpenSound, - - CustomRoles.Monitor or - CustomRoles.AntiAdminer - => new AirshipStatus().LongTasks.FirstOrDefault(task => task.TaskType == TaskTypes.ResetBreakers)?.MinigamePrefab.OpenSound, - - CustomRoles.EvilTracker or - CustomRoles.Tracefinder or - CustomRoles.Scout or - CustomRoles.Bloodhound or - CustomRoles.Mortician or - CustomRoles.Lighter - => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.DivertPower)?.MinigamePrefab.OpenSound, - - CustomRoles.Oracle or - CustomRoles.Divinator or - CustomRoles.Mediumshiper or - CustomRoles.DovesOfNeace or - CustomRoles.Spiritualist or - CustomRoles.Spiritcaller or - CustomRoles.Beacon or - CustomRoles.Farseer - => GetIntroSound(RoleTypes.GuardianAngel), - - CustomRoles.Alchemist - => new AirshipStatus().LongTasks.FirstOrDefault(task => task.TaskType == TaskTypes.DevelopPhotos)?.MinigamePrefab.OpenSound, - - CustomRoles.Deputy or - CustomRoles.Jailor - => new AirshipStatus().LongTasks.FirstOrDefault(task => task.TaskType == TaskTypes.UnlockSafe)?.MinigamePrefab.OpenSound, - - CustomRoles.Workaholic or - CustomRoles.Speedrunner or - CustomRoles.Snitch - => DestroyableSingleton.Instance.TaskCompleteSound, - - CustomRoles.TaskManager - => DestroyableSingleton.Instance.TaskUpdateSound, - - CustomRoles.Opportunist or - CustomRoles.FFF or - CustomRoles.Revolutionist - => GetIntroSound(RoleTypes.Crewmate), - - CustomRoles.Nightmare - => GetIntroSound(RoleTypes.Impostor), - - CustomRoles.SabotageMaster or - CustomRoles.Engineer or - CustomRoles.EngineerEHR or - CustomRoles.Inhibitor or - CustomRoles.Saboteur or - CustomRoles.SecurityGuard or - CustomRoles.Provocateur - => ShipStatus.Instance.SabotageSound, - - CustomRoles.Mastermind or - CustomRoles.Shiftguard or - CustomRoles.Randomizer or - CustomRoles.Gambler - => GetIntroSound(RoleTypes.Shapeshifter), - - CustomRoles.Doctor or - CustomRoles.Medic - => GetIntroSound(RoleTypes.Scientist), - - CustomRoles.GM - => DestroyableSingleton.Instance.TaskCompleteSound, - - CustomRoles.SwordsMan or - CustomRoles.Minimalism or - CustomRoles.NiceGuesser - => PlayerControl.LocalPlayer.KillSfx, - - CustomRoles.Drainer - => PlayerControl.LocalPlayer.MyPhysics.ImpostorDiscoveredSound, - - CustomRoles.Addict or - CustomRoles.Ventguard - => ShipStatus.Instance.VentEnterSound, - - CustomRoles.ParityCop or - CustomRoles.NiceEraser or - CustomRoles.TimeManager - => MeetingHud.Instance.VoteLockinSound, - - CustomRoles.Demolitionist or - CustomRoles.TimeMaster or - CustomRoles.Grenadier or - CustomRoles.Miner or - CustomRoles.Disperser - => ShipStatus.Instance.VentMoveSounds.FirstOrDefault(), - - CustomRoles.Tracker - or CustomRoles.EvilTracker - => GetIntroSound(RoleTypes.Tracker), - - CustomRoles.Noisemaker - or CustomRoles.NoisemakerEHR - or CustomRoles.DarkHide - or CustomRoles.Specter - => GetIntroSound(RoleTypes.Noisemaker), - - CustomRoles.Phantom - or CustomRoles.PhantomEHR - or CustomRoles.Swooper - or CustomRoles.Wraith - or CustomRoles.Chameleon - => GetIntroSound(RoleTypes.Phantom), - - CustomRoles.Shapeshifter - or CustomRoles.ShapeshifterEHR - => GetIntroSound(RoleTypes.Shapeshifter), - - _ => GetAudioClipFromCustomRoleType() - }; - } - catch (Exception ex) - { - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetAudioClipFromCustomRoleType(); - Logger.Warn($"Could not set intro sound\n{ex}", "IntroSound"); - } - // ReSharper restore Unity.IncorrectMonoBehaviourInstantiation - - if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) - { - __instance.TeamTitle.text = Utils.GetRoleName(role); - __instance.TeamTitle.color = Utils.GetRoleColor(role); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); - __instance.ImpostorText.gameObject.SetActive(false); - } - - if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || PlayerControl.LocalPlayer.Is(CustomRoles.Parasite) || PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor)) - { - __instance.TeamTitle.text = GetString("TeamMadmate"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.Madmate"); - } - - if (CustomTeamManager.EnabledCustomTeams.Count > 0) - { - CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(PlayerControl.LocalPlayer.PlayerId); - - if (team != null) - { - if (team.RoleRevealScreenTitle != "*") __instance.TeamTitle.text = team.RoleRevealScreenTitle; - - if (team.RoleRevealScreenBackgroundColor != "*" && ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color bgColor)) __instance.TeamTitle.color = __instance.BackgroundBar.material.color = bgColor; - - __instance.ImpostorText.gameObject.SetActive(team.RoleRevealScreenSubtitle != "*"); - __instance.ImpostorText.text = team.RoleRevealScreenSubtitle; - - foreach (PlayerControl pc in Main.AllPlayerControls) - if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, PlayerControl.LocalPlayer.PlayerId)) - teamToDisplay.Add(pc); - } - } - switch (Options.CurrentGameMode) { case CustomGameMode.SoloKombat: { Color color = ColorUtility.TryParseHtmlString("#f55252", out Color c) ? c : new(255, 255, 255, 255); - __instance.TeamTitle.text = Utils.GetRoleName(role); - __instance.TeamTitle.color = Utils.GetRoleColor(role); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("ModeSoloKombat"); - __instance.BackgroundBar.material.color = color; - PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx; + CustomRoles role = lp.GetCustomRole(); + __instance.YouAreText.color = color; + __instance.RoleText.text = Utils.GetRoleName(role); + __instance.RoleText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = lp.GetRoleInfo(); break; } case CustomGameMode.FFA: { - __instance.TeamTitle.text = GetString("Killer"); - Color color = FFAManager.PlayerTeams.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out int team) && ColorUtility.TryParseHtmlString(FFAManager.TeamColors[team], out Color teamColor) ? teamColor : new(0, 255, 255, byte.MaxValue); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = color; - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("KillerInfo"); + Color color = ColorUtility.TryParseHtmlString("#00ffff", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("Killer"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("KillerInfo"); break; } case CustomGameMode.MoveAndStop: { - __instance.TeamTitle.text = GetString("MoveAndStop"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(0, 255, 165, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("TaskerInfo"); + Color color = ColorUtility.TryParseHtmlString("#00ffa5", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("MoveAndStop"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("TaskerInfo"); break; } case CustomGameMode.HotPotato: { - __instance.TeamTitle.text = GetString("HotPotato"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(232, 205, 70, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("PotatoInfo"); + Color color = ColorUtility.TryParseHtmlString("#e8cd46", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("HotPotato"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("PotatoInfo"); break; } case CustomGameMode.Speedrun: { - __instance.TeamTitle.text = GetString("Runner"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Speedrunner); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("RunnerInfo"); - break; - } - case CustomGameMode.HideAndSeek: - { - __instance.TeamTitle.text = GetString("HideAndSeek"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(52, 94, 235, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("SubText.HideAndSeek"); + Color color = Utils.GetRoleColor(CustomRoles.Speedrunner); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("Runner"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("RunnerInfo"); break; } case CustomGameMode.CaptureTheFlag: { - __instance.TeamTitle.text = $"{GetString("CTFPlayer")}"; - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(19, 19, 194, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Engineer); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("CTFPlayerInfo"); + Color color = ColorUtility.TryParseHtmlString("#1313c2", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.transform.gameObject.SetActive(false); + __instance.RoleText.text = GetString("CTFPlayer"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("CTFPlayerInfo"); break; } case CustomGameMode.NaturalDisasters: { - __instance.TeamTitle.text = "" + GetString("NDPlayer") + ""; - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(3, 252, 74, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.GuardianAngel); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("NDPlayerInfo"); + Color color = ColorUtility.TryParseHtmlString("#03fc4a", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.color = color; + __instance.RoleText.text = GetString("NDPlayer"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("NDPlayerInfo"); break; } case CustomGameMode.RoomRush: { - __instance.TeamTitle.text = GetString("RRPlayer"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 171, 27, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("RRPlayerInfo"); + Color color = ColorUtility.TryParseHtmlString("#ffab1b", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.color = color; + __instance.RoleText.text = GetString("RRPlayer"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("RRPlayerInfo"); break; } case CustomGameMode.AllInOne: { - __instance.TeamTitle.text = GetString("AllInOne"); - __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(245, 66, 173, byte.MaxValue); - PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); - __instance.ImpostorText.gameObject.SetActive(true); - __instance.ImpostorText.text = GetString("AllInOneInfo"); + Color color = ColorUtility.TryParseHtmlString("#f542ad", out Color c) ? c : new(255, 255, 255, 255); + __instance.YouAreText.color = color; + __instance.RoleText.text = GetString("AllInOne"); + __instance.RoleText.color = color; + __instance.RoleBlurbText.color = color; + __instance.RoleBlurbText.text = GetString("AllInOneInfo"); + break; + } + default: + { + CustomRoles role = lp.GetCustomRole(); + + if (!role.IsVanilla()) + { + __instance.YouAreText.color = Utils.GetRoleColor(role); + __instance.RoleText.text = Utils.GetRoleName(role); + __instance.RoleText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.color = Utils.GetRoleColor(role); + __instance.RoleBlurbText.text = "" + lp.GetRoleInfo() + ""; + } + + foreach (CustomRoles subRole in Main.PlayerStates[lp.PlayerId].SubRoles) + { + if (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor && subRole == CustomRoles.Lovers) continue; + + __instance.RoleBlurbText.text += "\n" + Utils.ColorString(Utils.GetRoleColor(subRole), GetString($"{subRole}Info")); + } + + __instance.RoleText.text += Utils.GetSubRolesText(lp.PlayerId, false, true); break; } } + }, 0.0001f, "Override Role Text"); - // if (Input.GetKey(KeyCode.RightShift)) - // { - // __instance.TeamTitle.text = "明天就跑路啦"; - // __instance.ImpostorText.gameObject.SetActive(true); - // __instance.ImpostorText.text = "嘿嘿嘿嘿嘿嘿"; - // __instance.TeamTitle.color = Color.cyan; - // StartFadeIntro(__instance, Color.cyan, Color.yellow); - // } - // - // if (Input.GetKey(KeyCode.RightControl)) - // { - // __instance.TeamTitle.text = "警告"; - // __instance.ImpostorText.gameObject.SetActive(true); - // __instance.ImpostorText.text = "请远离无知的玩家"; - // __instance.TeamTitle.color = Color.magenta; - // StartFadeIntro(__instance, Color.magenta, Color.magenta); - // } - - return; - - AudioClip GetAudioClipFromCustomRoleType() + if (!AmongUsClient.Instance.AmHost) + { + LateTask.New(() => { - return PlayerControl.LocalPlayer.GetCustomRoleTypes() switch - { - CustomRoleTypes.Impostor => GetIntroSound(RoleTypes.Impostor), - CustomRoleTypes.Crewmate => GetIntroSound(RoleTypes.Crewmate), - CustomRoleTypes.Neutral => GetIntroSound(RoleTypes.Shapeshifter), - _ => GetIntroSound(RoleTypes.Crewmate) - }; + if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || lp == null) return; + + lp.SetName(Main.AllPlayerNames[lp.PlayerId]); + }, 1f, "Reset Name For Modded Client"); + } + } +} + +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.CoBegin))] +internal static class CoBeginPatch +{ + public static void Prefix() + { + StringBuilder sb = new(); + sb.Append("------------Display Names------------\n"); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.name.PadRightV2(20)}:{pc.cosmetics.nameText.text} ({Palette.ColorNames[pc.Data.DefaultOutfit.ColorId].ToString().Replace("Color", string.Empty)})\n"); + pc.cosmetics.nameText.text = pc.name; + } + + sb.Append("------------Roles------------\n"); + foreach (PlayerControl pc in Main.AllPlayerControls) sb.Append($"{(pc.AmOwner ? "[*]" : string.Empty),-3}{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetAllRoleName().RemoveHtmlTags().Replace("\n", " + ")}\n"); + + sb.Append("------------Platforms------------\n"); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + try + { + string text = pc.AmOwner ? "[*]" : " "; + text += $"{pc.PlayerId,-2}:{pc.Data?.PlayerName?.PadRightV2(20)}:{pc.GetClient()?.PlatformData?.Platform.ToString().Replace("Standalone", string.Empty),-11}"; + + if (Main.PlayerVersion.TryGetValue(pc.PlayerId, out PlayerVersion pv)) + text += $":Mod({pv.forkId}/{pv.version}:{pv.tag})"; + else + text += ":Vanilla"; + + sb.Append(text + "\n"); + } + catch (Exception ex) + { + Logger.Exception(ex, "Platform"); } } - private static AudioClip GetIntroSound(RoleTypes roleType) + sb.Append("------------Vanilla Settings------------\n"); + System.Collections.Generic.IEnumerable tmp = GameOptionsManager.Instance.CurrentGameOptions.ToHudString(GameData.Instance ? GameData.Instance.PlayerCount : 10).Split("\r\n").Skip(1); + foreach (string t in tmp) sb.Append(t + "\n"); + + sb.Append("------------Modded Settings------------\n"); + + foreach (OptionItem o in OptionItem.AllOptions) + if (!o.IsCurrentlyHidden() && (o.Parent?.GetBool() ?? !o.GetString().Equals("0%"))) + sb.Append($"{(o.Parent == null ? o.GetName(true, true).RemoveHtmlTags().PadRightV2(40) : $"┗ {o.GetName(true, true).RemoveHtmlTags()}".PadRightV2(41))}:{o.GetString().RemoveHtmlTags()}\n"); + + sb.Append("-------------Other Information-------------\n"); + sb.Append($"Number of players: {Main.AllPlayerControls.Length}\n"); + sb.Append($"Game mode: {GetString(Options.CurrentGameMode.ToString())}\n"); + sb.Append($"Map: {Main.CurrentMap}"); + + Logger.Info("\n" + sb, "GameInfo", multiLine: true); + + RPC.RpcVersionCheck(); + + SetupLongRoleDescriptions(); + + Utils.NotifyRoles(NoCache: true); + + GameStates.InGame = true; + } + + private static void SetupLongRoleDescriptions() + { + try { - return RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType)?.IntroSound; + Utils.LongRoleDescriptions.Clear(); + + int charsInOneLine = GetUserTrueLang() is SupportedLangs.Russian or SupportedLangs.SChinese or SupportedLangs.TChinese or SupportedLangs.Japanese or SupportedLangs.Korean ? 35 : 50; + + foreach (PlayerControl seer in Main.AllPlayerControls) + { + string longInfo = seer.GetRoleInfo(true).Split("\n\n")[0]; + if (longInfo.Contains("):\n")) longInfo = longInfo.Split("):\n")[1]; + + var tooLong = false; + bool showLongInfo = Options.ShowLongInfo.GetBool(); + + if (showLongInfo) + { + if (longInfo.Length > 296) + { + longInfo = longInfo[..296]; + longInfo += "..."; + tooLong = true; + } + + for (int i = charsInOneLine; i < longInfo.Length; i += charsInOneLine) + { + if (tooLong && i > 296) break; + + int index = longInfo.LastIndexOf(' ', i); + if (index != -1) longInfo = longInfo.Insert(index + 1, "\n"); + } + } + + longInfo = $"<#ffffff>{longInfo}"; + + int lines = longInfo.Count(x => x == '\n'); + int readTime = 20 + (lines * 5); + + Utils.LongRoleDescriptions[seer.PlayerId] = (longInfo, readTime, tooLong); + } } + catch (Exception e) + { + Utils.ThrowException(e); + } + } +} + +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] +internal static class BeginCrewmatePatch +{ + public static bool Prefix(IntroCutscene __instance, ref List teamToDisplay) + { + CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); + + if (PlayerControl.LocalPlayer.Is(CustomRoleTypes.Neutral) && !role.IsMadmate()) + { + teamToDisplay = new(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + + byte id = PlayerControl.LocalPlayer.PlayerId; + + switch (Main.PlayerStates[id].Role) + { + case Lawyer: + teamToDisplay.Add(Utils.GetPlayerById(Lawyer.Target[id])); + break; + case Executioner: + teamToDisplay.Add(Utils.GetPlayerById(Executioner.Target[id])); + break; + } + } + else if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) + { + teamToDisplay = new(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + __instance.BeginImpostor(teamToDisplay); + __instance.overlayHandle.color = Palette.ImpostorRed; + return false; + } + + if (role == CustomRoles.LovingCrewmate || PlayerControl.LocalPlayer.Is(CustomRoles.Lovers)) + { + teamToDisplay = new(); + teamToDisplay.Add(PlayerControl.LocalPlayer); + teamToDisplay.Add(Main.LoversPlayers.FirstOrDefault(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId)); + } + else if (role == CustomRoles.LovingImpostor) teamToDisplay.Add(Main.LoversPlayers.FirstOrDefault(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId)); + + if (CustomTeamManager.EnabledCustomTeams.Count > 0) + { + CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(PlayerControl.LocalPlayer.PlayerId); + + if (team != null) + { + teamToDisplay = new(); + + foreach (PlayerControl pc in Main.AllPlayerControls) + if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, PlayerControl.LocalPlayer.PlayerId)) + teamToDisplay.Add(pc); + } + } + + if (CustomGameMode.FFA.IsActiveOrIntegrated() && FFAManager.FFATeamMode.GetBool() && FFAManager.PlayerTeams.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out int ffaTeam)) + { + teamToDisplay = new(); + + foreach (PlayerControl pc in Main.AllPlayerControls) + if (FFAManager.PlayerTeams.TryGetValue(pc.PlayerId, out int team) && team == ffaTeam) + teamToDisplay.Add(pc); + } + + return true; + } + + public static void Postfix(IntroCutscene __instance, ref List teamToDisplay) + { + CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); + + __instance.ImpostorText.gameObject.SetActive(false); + + if (PlayerControl.LocalPlayer.Is(CustomRoles.Bloodlust)) + { + __instance.TeamTitle.text = GetString("TeamCrewmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Bloodlust); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Bloodlust"); + } + else if (role is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor) + { + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(role.GetRoleTypes()); + byte otherLoverId = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId).PlayerId; + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = string.Format(GetString($"SubText.{role}"), otherLoverId.ColoredPlayerName()); + } + else + { + switch (role.GetCustomRoleTypes()) + { + case CustomRoleTypes.Impostor: + __instance.TeamTitle.text = GetString("TeamImpostor"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Impostor"); + break; + case CustomRoleTypes.Crewmate: + __instance.TeamTitle.text = GetString("TeamCrewmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(140, 255, 255, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Crewmate"); + break; + case CustomRoleTypes.Neutral: + + if (Options.UniqueNeutralRevealScreen.GetBool()) + { + __instance.TeamTitle.text = GetString($"{role}"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString($"{role}Info"); + } + else + { + __instance.TeamTitle.text = GetString("TeamNeutral"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 171, 27, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Neutral"); + } + + break; + } + + if (Main.LoversPlayers.Count == 2 && Main.LoversPlayers.Exists(x => x.IsLocalPlayer())) + { + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Lovers); + byte otherLoverId = Main.LoversPlayers.First(x => x.PlayerId != PlayerControl.LocalPlayer.PlayerId).PlayerId; + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.DestroyTranslator(); + __instance.ImpostorText.text = string.Format(GetString("SubText.LovingCrewmate"), otherLoverId.ColoredPlayerName()); + } + } + + // ReSharper disable Unity.IncorrectMonoBehaviourInstantiation + try + { + PlayerControl.LocalPlayer.Data.Role.IntroSound = role switch + { + CustomRoles.Terrorist or + CustomRoles.Sapper or + CustomRoles.Bomber or + CustomRoles.Nuker + => ShipStatus.Instance.CommonTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixWiring)?.MinigamePrefab.OpenSound, + + CustomRoles.Sheriff or + CustomRoles.Veteran + => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.PutAwayPistols)?.MinigamePrefab.OpenSound, + + CustomRoles.Cleaner or + CustomRoles.Cleanser + => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.PolishRuby)?.MinigamePrefab.OpenSound, + + CustomRoles.Dictator or + CustomRoles.Lawyer or + CustomRoles.Judge or + CustomRoles.Mayor + => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.FixShower)?.MinigamePrefab.OpenSound, + + CustomRoles.Monitor or + CustomRoles.AntiAdminer + => new AirshipStatus().LongTasks.FirstOrDefault(task => task.TaskType == TaskTypes.ResetBreakers)?.MinigamePrefab.OpenSound, + + CustomRoles.EvilTracker or + CustomRoles.Tracefinder or + CustomRoles.Scout or + CustomRoles.Bloodhound or + CustomRoles.Mortician or + CustomRoles.Lighter + => new AirshipStatus().ShortTasks.FirstOrDefault(task => task.TaskType == TaskTypes.DivertPower)?.MinigamePrefab.OpenSound, + + CustomRoles.Oracle or + CustomRoles.Divinator or + CustomRoles.Mediumshiper or + CustomRoles.DovesOfNeace or + CustomRoles.Spiritualist or + CustomRoles.Spiritcaller or + CustomRoles.Beacon or + CustomRoles.Farseer + => GetIntroSound(RoleTypes.GuardianAngel), + + CustomRoles.Alchemist + => new AirshipStatus().LongTasks.FirstOrDefault(task => task.TaskType == TaskTypes.DevelopPhotos)?.MinigamePrefab.OpenSound, + + CustomRoles.Deputy or + CustomRoles.Jailor + => new AirshipStatus().LongTasks.FirstOrDefault(task => task.TaskType == TaskTypes.UnlockSafe)?.MinigamePrefab.OpenSound, + + CustomRoles.Workaholic or + CustomRoles.Speedrunner or + CustomRoles.Snitch + => DestroyableSingleton.Instance.TaskCompleteSound, + + CustomRoles.TaskManager + => DestroyableSingleton.Instance.TaskUpdateSound, + + CustomRoles.Opportunist or + CustomRoles.FFF or + CustomRoles.Revolutionist + => GetIntroSound(RoleTypes.Crewmate), + + CustomRoles.Nightmare + => GetIntroSound(RoleTypes.Impostor), + + CustomRoles.SabotageMaster or + CustomRoles.Engineer or + CustomRoles.EngineerEHR or + CustomRoles.Inhibitor or + CustomRoles.Saboteur or + CustomRoles.SecurityGuard or + CustomRoles.Provocateur + => ShipStatus.Instance.SabotageSound, + + CustomRoles.Mastermind or + CustomRoles.Shiftguard or + CustomRoles.Randomizer or + CustomRoles.Gambler + => GetIntroSound(RoleTypes.Shapeshifter), + + CustomRoles.Doctor or + CustomRoles.Medic + => GetIntroSound(RoleTypes.Scientist), + + CustomRoles.GM + => DestroyableSingleton.Instance.TaskCompleteSound, + + CustomRoles.SwordsMan or + CustomRoles.Minimalism or + CustomRoles.NiceGuesser + => PlayerControl.LocalPlayer.KillSfx, + + CustomRoles.Drainer + => PlayerControl.LocalPlayer.MyPhysics.ImpostorDiscoveredSound, + + CustomRoles.Addict or + CustomRoles.Ventguard + => ShipStatus.Instance.VentEnterSound, + + CustomRoles.ParityCop or + CustomRoles.NiceEraser or + CustomRoles.TimeManager + => MeetingHud.Instance.VoteLockinSound, + + CustomRoles.Demolitionist or + CustomRoles.TimeMaster or + CustomRoles.Grenadier or + CustomRoles.Miner or + CustomRoles.Disperser + => ShipStatus.Instance.VentMoveSounds.FirstOrDefault(), + + CustomRoles.Tracker + or CustomRoles.EvilTracker + => GetIntroSound(RoleTypes.Tracker), + + CustomRoles.Noisemaker + or CustomRoles.NoisemakerEHR + or CustomRoles.DarkHide + or CustomRoles.Specter + => GetIntroSound(RoleTypes.Noisemaker), + + CustomRoles.Phantom + or CustomRoles.PhantomEHR + or CustomRoles.Swooper + or CustomRoles.Wraith + or CustomRoles.Chameleon + => GetIntroSound(RoleTypes.Phantom), + + CustomRoles.Shapeshifter + or CustomRoles.ShapeshifterEHR + => GetIntroSound(RoleTypes.Shapeshifter), + + _ => GetAudioClipFromCustomRoleType() + }; + } + catch (Exception ex) + { + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetAudioClipFromCustomRoleType(); + Logger.Warn($"Could not set intro sound\n{ex}", "IntroSound"); + } + // ReSharper restore Unity.IncorrectMonoBehaviourInstantiation + + if (PlayerControl.LocalPlayer.Is(CustomRoles.GM)) + { + __instance.TeamTitle.text = Utils.GetRoleName(role); + __instance.TeamTitle.color = Utils.GetRoleColor(role); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(role); + __instance.ImpostorText.gameObject.SetActive(false); + } + + if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || PlayerControl.LocalPlayer.Is(CustomRoles.Parasite) || PlayerControl.LocalPlayer.Is(CustomRoles.Crewpostor)) + { + __instance.TeamTitle.text = GetString("TeamMadmate"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 25, 25, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.Madmate"); + } + + if (CustomTeamManager.EnabledCustomTeams.Count > 0) + { + CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(PlayerControl.LocalPlayer.PlayerId); + + if (team != null) + { + if (team.RoleRevealScreenTitle != "*") __instance.TeamTitle.text = team.RoleRevealScreenTitle; + + if (team.RoleRevealScreenBackgroundColor != "*" && ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color bgColor)) __instance.TeamTitle.color = __instance.BackgroundBar.material.color = bgColor; + + __instance.ImpostorText.gameObject.SetActive(team.RoleRevealScreenSubtitle != "*"); + __instance.ImpostorText.text = team.RoleRevealScreenSubtitle; + + foreach (PlayerControl pc in Main.AllPlayerControls) + if (CustomTeamManager.AreInSameCustomTeam(pc.PlayerId, PlayerControl.LocalPlayer.PlayerId)) + teamToDisplay.Add(pc); + } + } + + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: + { + Color color = ColorUtility.TryParseHtmlString("#f55252", out Color c) ? c : new(255, 255, 255, 255); + __instance.TeamTitle.text = Utils.GetRoleName(role); + __instance.TeamTitle.color = Utils.GetRoleColor(role); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("ModeSoloKombat"); + __instance.BackgroundBar.material.color = color; + PlayerControl.LocalPlayer.Data.Role.IntroSound = DestroyableSingleton.Instance.HnSOtherImpostorTransformSfx; + break; + } + case CustomGameMode.FFA: + { + __instance.TeamTitle.text = GetString("Killer"); + Color color = FFAManager.PlayerTeams.TryGetValue(PlayerControl.LocalPlayer.PlayerId, out int team) && ColorUtility.TryParseHtmlString(FFAManager.TeamColors[team], out Color teamColor) ? teamColor : new(0, 255, 255, byte.MaxValue); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = color; + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("KillerInfo"); + break; + } + case CustomGameMode.MoveAndStop: + { + __instance.TeamTitle.text = GetString("MoveAndStop"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(0, 255, 165, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("TaskerInfo"); + break; + } + case CustomGameMode.HotPotato: + { + __instance.TeamTitle.text = GetString("HotPotato"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(232, 205, 70, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("PotatoInfo"); + break; + } + case CustomGameMode.Speedrun: + { + __instance.TeamTitle.text = GetString("Runner"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Speedrunner); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Crewmate); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("RunnerInfo"); + break; + } + case CustomGameMode.HideAndSeek: + { + __instance.TeamTitle.text = GetString("HideAndSeek"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(52, 94, 235, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("SubText.HideAndSeek"); + break; + } + case CustomGameMode.CaptureTheFlag: + { + __instance.TeamTitle.text = $"{GetString("CTFPlayer")}"; + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(19, 19, 194, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Engineer); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("CTFPlayerInfo"); + break; + } + case CustomGameMode.NaturalDisasters: + { + __instance.TeamTitle.text = "" + GetString("NDPlayer") + ""; + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(3, 252, 74, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.GuardianAngel); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("NDPlayerInfo"); + break; + } + case CustomGameMode.RoomRush: + { + __instance.TeamTitle.text = GetString("RRPlayer"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(255, 171, 27, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Impostor); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("RRPlayerInfo"); + break; + } + case CustomGameMode.AllInOne: + { + __instance.TeamTitle.text = GetString("AllInOne"); + __instance.TeamTitle.color = __instance.BackgroundBar.material.color = new Color32(245, 66, 173, byte.MaxValue); + PlayerControl.LocalPlayer.Data.Role.IntroSound = GetIntroSound(RoleTypes.Shapeshifter); + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = GetString("AllInOneInfo"); + break; + } + } + + // if (Input.GetKey(KeyCode.RightShift)) + // { + // __instance.TeamTitle.text = "明天就跑路啦"; + // __instance.ImpostorText.gameObject.SetActive(true); + // __instance.ImpostorText.text = "嘿嘿嘿嘿嘿嘿"; + // __instance.TeamTitle.color = Color.cyan; + // StartFadeIntro(__instance, Color.cyan, Color.yellow); + // } + // + // if (Input.GetKey(KeyCode.RightControl)) + // { + // __instance.TeamTitle.text = "警告"; + // __instance.ImpostorText.gameObject.SetActive(true); + // __instance.ImpostorText.text = "请远离无知的玩家"; + // __instance.TeamTitle.color = Color.magenta; + // StartFadeIntro(__instance, Color.magenta, Color.magenta); + // } + + return; + + AudioClip GetAudioClipFromCustomRoleType() + { + return PlayerControl.LocalPlayer.GetCustomRoleTypes() switch + { + CustomRoleTypes.Impostor => GetIntroSound(RoleTypes.Impostor), + CustomRoleTypes.Crewmate => GetIntroSound(RoleTypes.Crewmate), + CustomRoleTypes.Neutral => GetIntroSound(RoleTypes.Shapeshifter), + _ => GetIntroSound(RoleTypes.Crewmate) + }; + } + } + + private static AudioClip GetIntroSound(RoleTypes roleType) + { + return RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType)?.IntroSound; + } /* private static async void StartFadeIntro(IntroCutscene __instance, Color start, Color end) @@ -747,227 +747,226 @@ namespace EHR } } */ - } +} - [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginImpostor))] - internal static class BeginImpostorPatch +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginImpostor))] +internal static class BeginImpostorPatch +{ + public static bool Prefix(IntroCutscene __instance, ref List yourTeam) { - public static bool Prefix(IntroCutscene __instance, ref List yourTeam) + CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); + + if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) { - CustomRoles role = PlayerControl.LocalPlayer.GetCustomRole(); - - if (PlayerControl.LocalPlayer.Is(CustomRoles.Madmate) || role.IsMadmate()) - { - yourTeam = new(); - yourTeam.Add(PlayerControl.LocalPlayer); - __instance.overlayHandle.color = Palette.ImpostorRed; - return true; - } - - if (PlayerControl.LocalPlayer.IsCrewmate() && role.IsDesyncRole()) - { - yourTeam = new(); - yourTeam.Add(PlayerControl.LocalPlayer); - foreach (PlayerControl pc in Main.AllPlayerControls.Where(x => !x.AmOwner)) yourTeam.Add(pc); - - __instance.BeginCrewmate(yourTeam); - __instance.overlayHandle.color = Palette.CrewmateBlue; - return false; - } - - if (role.IsNeutral() || PlayerControl.LocalPlayer.Is(CustomRoles.Bloodlust)) - { - yourTeam = new(); - yourTeam.Add(PlayerControl.LocalPlayer); - foreach (PlayerControl pc in Main.AllPlayerControls.Where(x => !x.AmOwner)) yourTeam.Add(pc); - - __instance.BeginCrewmate(yourTeam); - __instance.overlayHandle.color = new Color32(255, 171, 27, byte.MaxValue); - return false; - } - - BeginCrewmatePatch.Prefix(__instance, ref yourTeam); + yourTeam = new(); + yourTeam.Add(PlayerControl.LocalPlayer); + __instance.overlayHandle.color = Palette.ImpostorRed; return true; } - public static void Postfix(IntroCutscene __instance, ref List yourTeam) + if (PlayerControl.LocalPlayer.IsCrewmate() && role.IsDesyncRole()) { - BeginCrewmatePatch.Postfix(__instance, ref yourTeam); + yourTeam = new(); + yourTeam.Add(PlayerControl.LocalPlayer); + foreach (PlayerControl pc in Main.AllPlayerControls.Where(x => !x.AmOwner)) yourTeam.Add(pc); + + __instance.BeginCrewmate(yourTeam); + __instance.overlayHandle.color = Palette.CrewmateBlue; + return false; } + + if (role.IsNeutral() || PlayerControl.LocalPlayer.Is(CustomRoles.Bloodlust)) + { + yourTeam = new(); + yourTeam.Add(PlayerControl.LocalPlayer); + foreach (PlayerControl pc in Main.AllPlayerControls.Where(x => !x.AmOwner)) yourTeam.Add(pc); + + __instance.BeginCrewmate(yourTeam); + __instance.overlayHandle.color = new Color32(255, 171, 27, byte.MaxValue); + return false; + } + + BeginCrewmatePatch.Prefix(__instance, ref yourTeam); + return true; } - [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] - internal static class IntroCutsceneDestroyPatch + public static void Postfix(IntroCutscene __instance, ref List yourTeam) { - public static void Postfix( /*IntroCutscene __instance*/) + BeginCrewmatePatch.Postfix(__instance, ref yourTeam); + } +} + +[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] +internal static class IntroCutsceneDestroyPatch +{ + public static void Postfix( /*IntroCutscene __instance*/) + { + if (!GameStates.IsInGame) return; + + Main.IntroDestroyed = true; + + // Set roleAssigned as false for overriding roles for modded players + // for vanilla clients we use "Data.Disconnected" + Main.AllPlayerControls.Do(x => x.roleAssigned = false); + + PlayerControl[] aapc = Main.AllAlivePlayerControls; + + if (AmongUsClient.Instance.AmHost) { - if (!GameStates.IsInGame) return; - - Main.IntroDestroyed = true; - - // Set roleAssigned as false for overriding roles for modded players - // for vanilla clients we use "Data.Disconnected" - Main.AllPlayerControls.Do(x => x.roleAssigned = false); - - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - if (AmongUsClient.Instance.AmHost) + if (Main.NormalOptions.MapId != 4) { - if (Main.NormalOptions.MapId != 4) + foreach (PlayerControl pc in aapc) { - foreach (PlayerControl pc in aapc) - { - pc.SyncSettings(); - pc.RpcResetAbilityCooldown(); + pc.SyncSettings(); + pc.RpcResetAbilityCooldown(); - if (pc.GetCustomRole().UsesPetInsteadOfKill()) - pc.AddAbilityCD(10); - else - pc.AddAbilityCD(false); + if (pc.GetCustomRole().UsesPetInsteadOfKill()) + pc.AddAbilityCD(10); + else + pc.AddAbilityCD(false); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, NoCache: true); - } - - int kcd = Options.StartingKillCooldown.GetInt(); - - if (kcd is not 10 and > 0 && !CustomGameMode.FFA.IsActiveOrIntegrated()) - { - LateTask.New(() => - { - foreach (PlayerControl pc in aapc) - { - pc.ResetKillCooldown(); - pc.SetKillCooldown(kcd - 2); - } - }, 2f, "FixKillCooldownTask"); - } - else if (Options.FixFirstKillCooldown.GetBool() && CustomGameMode.Standard.IsActiveOrIntegrated()) - { - LateTask.New(() => - { - aapc.Do(x => x.ResetKillCooldown()); - aapc.Where(x => Main.AllPlayerKillCooldown[x.PlayerId] - 2f > 0f).Do(pc => pc.SetKillCooldown(Main.AllPlayerKillCooldown[pc.PlayerId] - 2f)); - }, 2f, "FixKillCooldownTask"); - } + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc, NoCache: true); } - switch (Options.CurrentGameMode) + int kcd = Options.StartingKillCooldown.GetInt(); + + if (kcd is not 10 and > 0 && !CustomGameMode.FFA.IsActiveOrIntegrated()) { - case CustomGameMode.FFA when FFAManager.FFAChatDuringGame.GetBool(): - Utils.SetChatVisibleForAll(); - break; - } - - // LateTask.New(() => Main.AllPlayerControls.Do(pc ⇒ pc.RpcSetRoleDesync(RoleTypes.Shapeshifter, -3)), 2f, "SetImpostorForServer"); - - PlayerControl lp = PlayerControl.LocalPlayer; - - if (lp.GetRoleTypes() == RoleTypes.Shapeshifter) lp.RpcChangeRoleBasis(lp.GetCustomRole()); - - if (Options.UsePets.GetBool()) - { - Main.ProcessShapeshifts = false; - LateTask.New(() => { foreach (PlayerControl pc in aapc) { - if (pc.Is(CustomRoles.GM)) continue; - - string petId = PetsPatch.GetPetId(); - pc.RpcSetPetDesync(petId, pc); - Logger.Info($"{pc.GetNameWithRole()} => {GetString(petId)} Pet", "PetAssign"); + pc.ResetKillCooldown(); + pc.SetKillCooldown(kcd - 2); } - - AntiBlackout.SendGameData(); - }, 0.3f, "Grant Pet For Everyone"); - - try - { - LateTask.New(() => - { - try - { - lp.Notify(GetString("GLHF"), 2f); - - foreach (PlayerControl pc in aapc) - { - if (pc.IsHost()) continue; // Skip the host - - try - { - pc.RpcShapeshift(pc, false); - pc.Notify(GetString("GLHF"), 2f); - } - catch (Exception ex) - { - Logger.Fatal(ex.ToString(), "IntroPatch.RpcShapeshift"); - } - } - } - catch (Exception ex) - { - Logger.Fatal(ex.ToString(), "IntroPatch.RpcShapeshift.foreachCycle"); - } - }, 0.4f, "Show Pet For Everyone"); - } - catch { } - - LateTask.New(() => Main.ProcessShapeshifts = true, 1f, "Enable SS Processing"); + }, 2f, "FixKillCooldownTask"); } - - if (Options.UseUnshiftTrigger.GetBool() || Main.PlayerStates.Values.Any(x => x.MainRole.AlwaysUsesUnshift())) LateTask.New(() => aapc.Do(x => x.CheckAndSetUnshiftState()), 2f, "UnshiftTrigger SS"); - - if (Main.GM.Value && lp.Is(CustomRoles.GM)) + else if (Options.FixFirstKillCooldown.GetBool() && CustomGameMode.Standard.IsActiveOrIntegrated()) { LateTask.New(() => { - lp.RpcExile(); - Main.PlayerStates[lp.PlayerId].SetDead(); - }, CustomGameMode.FFA.IsActiveOrIntegrated() && FFAManager.FFAChatDuringGame.GetBool() ? 12.5f : 1f, "Set GM Dead"); + aapc.Do(x => x.ResetKillCooldown()); + aapc.Where(x => Main.AllPlayerKillCooldown[x.PlayerId] - 2f > 0f).Do(pc => pc.SetKillCooldown(Main.AllPlayerKillCooldown[pc.PlayerId] - 2f)); + }, 2f, "FixKillCooldownTask"); } - - if (Options.RandomSpawn.GetBool()) - { - RandomSpawn.SpawnMap map = Main.NormalOptions.MapId switch - { - 0 => new RandomSpawn.SkeldSpawnMap(), - 1 => new RandomSpawn.MiraHQSpawnMap(), - 2 => new RandomSpawn.PolusSpawnMap(), - 3 => new RandomSpawn.DleksSpawnMap(), - 5 => new RandomSpawn.FungleSpawnMap(), - _ => null - }; - - if (map != null && AmongUsClient.Instance.AmHost) aapc.Do(map.RandomTeleport); - } - - if (lp.HasDesyncRole()) - { - lp.Data.Role.AffectedByLightAffectors = false; - - foreach (PlayerControl target in Main.AllPlayerControls) - { - // Set all players as killable players - target.Data.Role.CanBeKilled = true; - - // When target is impostor, set name color as white - target.cosmetics.SetNameColor(Color.white); - target.Data.Role.NameColor = Color.white; - } - } - - Utils.CheckAndSetVentInteractions(); - - if (AFKDetector.ActivateOnStart.GetBool()) LateTask.New(() => aapc.Do(AFKDetector.RecordPosition), 1f, log: false); - - LateTask.New(() => Main.Instance.StartCoroutine(Utils.NotifyEveryoneAsync()), 3f, log: false); } - else - foreach (PlayerControl player in Main.AllPlayerControls) - Main.PlayerStates[player.PlayerId].InitTask(player); - Logger.Info("OnDestroy", "IntroCutscene"); + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA when FFAManager.FFAChatDuringGame.GetBool(): + Utils.SetChatVisibleForAll(); + break; + } + + // LateTask.New(() => Main.AllPlayerControls.Do(pc ⇒ pc.RpcSetRoleDesync(RoleTypes.Shapeshifter, -3)), 2f, "SetImpostorForServer"); + + PlayerControl lp = PlayerControl.LocalPlayer; + + if (lp.GetRoleTypes() == RoleTypes.Shapeshifter) lp.RpcChangeRoleBasis(lp.GetCustomRole()); + + if (Options.UsePets.GetBool()) + { + Main.ProcessShapeshifts = false; + + LateTask.New(() => + { + foreach (PlayerControl pc in aapc) + { + if (pc.Is(CustomRoles.GM)) continue; + + string petId = PetsPatch.GetPetId(); + pc.RpcSetPetDesync(petId, pc); + Logger.Info($"{pc.GetNameWithRole()} => {GetString(petId)} Pet", "PetAssign"); + } + + AntiBlackout.SendGameData(); + }, 0.3f, "Grant Pet For Everyone"); + + try + { + LateTask.New(() => + { + try + { + lp.Notify(GetString("GLHF"), 2f); + + foreach (PlayerControl pc in aapc) + { + if (pc.IsHost()) continue; // Skip the host + + try + { + pc.RpcShapeshift(pc, false); + pc.Notify(GetString("GLHF"), 2f); + } + catch (Exception ex) + { + Logger.Fatal(ex.ToString(), "IntroPatch.RpcShapeshift"); + } + } + } + catch (Exception ex) + { + Logger.Fatal(ex.ToString(), "IntroPatch.RpcShapeshift.foreachCycle"); + } + }, 0.4f, "Show Pet For Everyone"); + } + catch { } + + LateTask.New(() => Main.ProcessShapeshifts = true, 1f, "Enable SS Processing"); + } + + if (Options.UseUnshiftTrigger.GetBool() || Main.PlayerStates.Values.Any(x => x.MainRole.AlwaysUsesUnshift())) LateTask.New(() => aapc.Do(x => x.CheckAndSetUnshiftState()), 2f, "UnshiftTrigger SS"); + + if (Main.GM.Value && lp.Is(CustomRoles.GM)) + { + LateTask.New(() => + { + lp.RpcExile(); + Main.PlayerStates[lp.PlayerId].SetDead(); + }, CustomGameMode.FFA.IsActiveOrIntegrated() && FFAManager.FFAChatDuringGame.GetBool() ? 12.5f : 1f, "Set GM Dead"); + } + + if (Options.RandomSpawn.GetBool()) + { + RandomSpawn.SpawnMap map = Main.NormalOptions.MapId switch + { + 0 => new RandomSpawn.SkeldSpawnMap(), + 1 => new RandomSpawn.MiraHQSpawnMap(), + 2 => new RandomSpawn.PolusSpawnMap(), + 3 => new RandomSpawn.DleksSpawnMap(), + 5 => new RandomSpawn.FungleSpawnMap(), + _ => null + }; + + if (map != null && AmongUsClient.Instance.AmHost) aapc.Do(map.RandomTeleport); + } + + if (lp.HasDesyncRole()) + { + lp.Data.Role.AffectedByLightAffectors = false; + + foreach (PlayerControl target in Main.AllPlayerControls) + { + // Set all players as killable players + target.Data.Role.CanBeKilled = true; + + // When target is impostor, set name color as white + target.cosmetics.SetNameColor(Color.white); + target.Data.Role.NameColor = Color.white; + } + } + + Utils.CheckAndSetVentInteractions(); + + if (AFKDetector.ActivateOnStart.GetBool()) LateTask.New(() => aapc.Do(AFKDetector.RecordPosition), 1f, log: false); + + LateTask.New(() => Main.Instance.StartCoroutine(Utils.NotifyEveryoneAsync()), 3f, log: false); } + else + foreach (PlayerControl player in Main.AllPlayerControls) + Main.PlayerStates[player.PlayerId].InitTask(player); + + Logger.Info("OnDestroy", "IntroCutscene"); } } \ No newline at end of file diff --git a/Patches/JoinGameButtonPatch.cs b/Patches/JoinGameButtonPatch.cs index 4ba9710eb..21519ff41 100644 --- a/Patches/JoinGameButtonPatch.cs +++ b/Patches/JoinGameButtonPatch.cs @@ -2,20 +2,19 @@ using System.Text.RegularExpressions; using HarmonyLib; using UnityEngine; -namespace EHR -{ - [HarmonyPatch(typeof(JoinGameButton), nameof(JoinGameButton.OnClick))] - internal class JoinGameButtonPatch - { - public static void Prefix(JoinGameButton __instance) - { - if (__instance.GameIdText == null) return; +namespace EHR; - if (__instance.GameIdText.text == "" && Regex.IsMatch(GUIUtility.systemCopyBuffer.Trim('\r', '\n'), @"^[A-Z]{6}$")) - { - Logger.Info($"{GUIUtility.systemCopyBuffer}", "ClipBoard"); - __instance.GameIdText.SetText(GUIUtility.systemCopyBuffer.Trim('\r', '\n')); - } +[HarmonyPatch(typeof(JoinGameButton), nameof(JoinGameButton.OnClick))] +internal class JoinGameButtonPatch +{ + public static void Prefix(JoinGameButton __instance) + { + if (__instance.GameIdText == null) return; + + if (__instance.GameIdText.text == "" && Regex.IsMatch(GUIUtility.systemCopyBuffer.Trim('\r', '\n'), @"^[A-Z]{6}$")) + { + Logger.Info($"{GUIUtility.systemCopyBuffer}", "ClipBoard"); + __instance.GameIdText.SetText(GUIUtility.systemCopyBuffer.Trim('\r', '\n')); } } } \ No newline at end of file diff --git a/Patches/LadderPatch.cs b/Patches/LadderPatch.cs index 354acdf38..bcbda3403 100644 --- a/Patches/LadderPatch.cs +++ b/Patches/LadderPatch.cs @@ -2,76 +2,75 @@ using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +public class FallFromLadder { - public class FallFromLadder + public static Dictionary TargetLadderData; + private static int Chance => (Options.LadderDeathChance as StringOptionItem).GetChance(); + + public static void Reset() { - public static Dictionary TargetLadderData; - private static int Chance => (Options.LadderDeathChance as StringOptionItem).GetChance(); + TargetLadderData = []; + } - public static void Reset() + public static void OnClimbLadder(PlayerPhysics player, Ladder source) + { + if (!Options.LadderDeath.GetBool()) return; + + Vector3 sourcePos = source.transform.position; + Vector3 targetPos = source.Destination.transform.position; + + if (sourcePos.y > targetPos.y) { - TargetLadderData = []; - } - - public static void OnClimbLadder(PlayerPhysics player, Ladder source) - { - if (!Options.LadderDeath.GetBool()) return; - - Vector3 sourcePos = source.transform.position; - Vector3 targetPos = source.Destination.transform.position; - - if (sourcePos.y > targetPos.y) - { - int chance = IRandom.Instance.Next(1, 101); - if (chance <= Chance) TargetLadderData[player.myPlayer.PlayerId] = targetPos; - } - } - - public static void FixedUpdate(PlayerControl player) - { - if (player.Data.Disconnected) return; - - if (TargetLadderData.ContainsKey(player.PlayerId) && Vector2.Distance(TargetLadderData[player.PlayerId], player.transform.position) < 0.5f) - { - if (player.Data.IsDead) return; - - // In order to insert LateTask, first enter the death judgment. - player.Data.IsDead = true; - - LateTask.New(() => - { - Vector2 targetPos = (Vector2)TargetLadderData[player.PlayerId] + new Vector2(0.1f, 0f); - var num = (ushort)(NetHelpers.XRange.ReverseLerp(targetPos.x) * 65535f); - var num2 = (ushort)(NetHelpers.YRange.ReverseLerp(targetPos.y) * 65535f); - var sender = CustomRpcSender.Create("LadderFallRpc"); - - sender.AutoStartRpc(player.NetTransform.NetId, (byte)RpcCalls.SnapTo) - .Write(num) - .Write(num2) - .EndRpc(); - - sender.AutoStartRpc(player.NetId, (byte)RpcCalls.MurderPlayer) - .WriteNetObject(player) - .Write((byte)ExtendedPlayerControl.ResultFlags) - .EndRpc(); - - sender.SendMessage(); - player.NetTransform.SnapTo(targetPos); - player.MurderPlayer(player, ExtendedPlayerControl.ResultFlags); - Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Fall; - Main.PlayerStates[player.PlayerId].SetDead(); - }, 0.05f, "LadderFallTask"); - } + int chance = IRandom.Instance.Next(1, 101); + if (chance <= Chance) TargetLadderData[player.myPlayer.PlayerId] = targetPos; } } - [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.ClimbLadder))] - internal class LadderPatch + public static void FixedUpdate(PlayerControl player) { - public static void Postfix(PlayerPhysics __instance, Ladder source /*, byte climbLadderSid*/) + if (player.Data.Disconnected) return; + + if (TargetLadderData.ContainsKey(player.PlayerId) && Vector2.Distance(TargetLadderData[player.PlayerId], player.transform.position) < 0.5f) { - FallFromLadder.OnClimbLadder(__instance, source); + if (player.Data.IsDead) return; + + // In order to insert LateTask, first enter the death judgment. + player.Data.IsDead = true; + + LateTask.New(() => + { + Vector2 targetPos = (Vector2)TargetLadderData[player.PlayerId] + new Vector2(0.1f, 0f); + var num = (ushort)(NetHelpers.XRange.ReverseLerp(targetPos.x) * 65535f); + var num2 = (ushort)(NetHelpers.YRange.ReverseLerp(targetPos.y) * 65535f); + var sender = CustomRpcSender.Create("LadderFallRpc"); + + sender.AutoStartRpc(player.NetTransform.NetId, (byte)RpcCalls.SnapTo) + .Write(num) + .Write(num2) + .EndRpc(); + + sender.AutoStartRpc(player.NetId, (byte)RpcCalls.MurderPlayer) + .WriteNetObject(player) + .Write((byte)ExtendedPlayerControl.ResultFlags) + .EndRpc(); + + sender.SendMessage(); + player.NetTransform.SnapTo(targetPos); + player.MurderPlayer(player, ExtendedPlayerControl.ResultFlags); + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Fall; + Main.PlayerStates[player.PlayerId].SetDead(); + }, 0.05f, "LadderFallTask"); } } +} + +[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.ClimbLadder))] +internal class LadderPatch +{ + public static void Postfix(PlayerPhysics __instance, Ladder source /*, byte climbLadderSid*/) + { + FallFromLadder.OnClimbLadder(__instance, source); + } } \ No newline at end of file diff --git a/Patches/LobbyListPatch.cs b/Patches/LobbyListPatch.cs index e9090150c..ee8269ead 100644 --- a/Patches/LobbyListPatch.cs +++ b/Patches/LobbyListPatch.cs @@ -4,70 +4,69 @@ using HarmonyLib; using InnerNet; using UnityEngine; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(FindAGameManager), nameof(FindAGameManager.Update))] +public static class FindAGameManagerUpdatePatch { - [HarmonyPatch(typeof(FindAGameManager), nameof(FindAGameManager.Update))] - public static class FindAGameManagerUpdatePatch + private static int buffer = 80; + private static GameObject RefreshButton; + private static GameObject InputDisplayGlyph; + + public static void Postfix(FindAGameManager __instance) { - private static int buffer = 80; - private static GameObject RefreshButton; - private static GameObject InputDisplayGlyph; + if ((RefreshButton = GameObject.Find("RefreshButton")) != null) RefreshButton.transform.localPosition = new(100f, 100f, 100f); - public static void Postfix(FindAGameManager __instance) - { - if ((RefreshButton = GameObject.Find("RefreshButton")) != null) RefreshButton.transform.localPosition = new(100f, 100f, 100f); + if ((InputDisplayGlyph = GameObject.Find("InputDisplayGlyph")) != null) InputDisplayGlyph.transform.localPosition = new(100f, 100f, 100f); - if ((InputDisplayGlyph = GameObject.Find("InputDisplayGlyph")) != null) InputDisplayGlyph.transform.localPosition = new(100f, 100f, 100f); + buffer--; + if (buffer > 0) return; - buffer--; - if (buffer > 0) return; - - buffer = 80; - __instance.RefreshList(); - } + buffer = 80; + __instance.RefreshList(); } +} - [HarmonyPatch(typeof(FindAGameManager), nameof(FindAGameManager.HandleList))] - public static class FindAGameManagerHandleListPatch +[HarmonyPatch(typeof(FindAGameManager), nameof(FindAGameManager.HandleList))] +public static class FindAGameManagerHandleListPatch +{ + public static void Prefix( /*FindAGameManager __instance,*/ /*[HarmonyArgument(0)] InnerNetClient.TotalGameData totalGames,*/ [HarmonyArgument(1)] ref List games) { - public static void Prefix( /*FindAGameManager __instance,*/ /*[HarmonyArgument(0)] InnerNetClient.TotalGameData totalGames,*/ [HarmonyArgument(1)] ref List games) + List newList = []; + + List nameList = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? Main.NameSnacksCn : Main.NameSnacksEn; + + foreach (GameListing game in games) { - List newList = []; + if (game.Language.ToString().Length > 9) continue; - List nameList = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? Main.NameSnacksCn : Main.NameSnacksEn; - - foreach (GameListing game in games) + string color = game.Platform switch { - if (game.Language.ToString().Length > 9) continue; + Platforms.StandaloneItch or + Platforms.StandaloneWin10 or + Platforms.StandaloneEpicPC or + Platforms.StandaloneSteamPC => "#00a4ff", - string color = game.Platform switch - { - Platforms.StandaloneItch or - Platforms.StandaloneWin10 or - Platforms.StandaloneEpicPC or - Platforms.StandaloneSteamPC => "#00a4ff", + Platforms.Xbox or + Platforms.Switch or + Platforms.Playstation => "#dd001b", - Platforms.Xbox or - Platforms.Switch or - Platforms.Playstation => "#dd001b", + Platforms.IPhone or + Platforms.Android => "#68bc71", - Platforms.IPhone or - Platforms.Android => "#68bc71", + Platforms.Unknown or + _ => "#ffffff" + }; - Platforms.Unknown or - _ => "#ffffff" - }; + var str = Math.Abs(game.GameId).ToString(); + int id = Math.Min(Math.Max(int.Parse(str.Substring(str.Length - 2, 2)), 1) * nameList.Count / 100, nameList.Count); - var str = Math.Abs(game.GameId).ToString(); - int id = Math.Min(Math.Max(int.Parse(str.Substring(str.Length - 2, 2)), 1) * nameList.Count / 100, nameList.Count); + game.HostName = $"{nameList[id]}"; + game.HostName += $" ({Math.Max(0, 100 - (game.Age / 100))}%)"; - game.HostName = $"{nameList[id]}"; - game.HostName += $" ({Math.Max(0, 100 - (game.Age / 100))}%)"; - - newList.Add(game); - } - - games = newList; + newList.Add(game); } + + games = newList; } } \ No newline at end of file diff --git a/Patches/LobbyPatch.cs b/Patches/LobbyPatch.cs index 4b0363a05..a46eb59fb 100644 --- a/Patches/LobbyPatch.cs +++ b/Patches/LobbyPatch.cs @@ -4,59 +4,58 @@ using InnerNet; using TMPro; using UnityEngine; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(LobbyBehaviour), nameof(LobbyBehaviour.FixedUpdate))] +public static class LobbyFixedUpdatePatch { - [HarmonyPatch(typeof(LobbyBehaviour), nameof(LobbyBehaviour.FixedUpdate))] - public static class LobbyFixedUpdatePatch + private static GameObject Paint; + + public static void Postfix() { - private static GameObject Paint; - - public static void Postfix() + if (Paint == null) { - if (Paint == null) - { - GameObject LeftBox = GameObject.Find("Leftbox"); + GameObject LeftBox = GameObject.Find("Leftbox"); - if (LeftBox != null) - { - Paint = Object.Instantiate(LeftBox, LeftBox.transform.parent.transform); - Paint.name = "Lobby Paint"; - Paint.transform.localPosition = new(0.042f, -2.59f, -10.5f); - var renderer = Paint.GetComponent(); - renderer.sprite = Utils.LoadSprite("EHR.Resources.Images.LobbyPaint.png", 290f); - } + if (LeftBox != null) + { + Paint = Object.Instantiate(LeftBox, LeftBox.transform.parent.transform); + Paint.name = "Lobby Paint"; + Paint.transform.localPosition = new(0.042f, -2.59f, -10.5f); + var renderer = Paint.GetComponent(); + renderer.sprite = Utils.LoadSprite("EHR.Resources.Images.LobbyPaint.png", 290f); } } } +} - [HarmonyPatch(typeof(HostInfoPanel), nameof(HostInfoPanel.SetUp))] - public static class HostInfoPanelSetUpPatch +[HarmonyPatch(typeof(HostInfoPanel), nameof(HostInfoPanel.SetUp))] +public static class HostInfoPanelSetUpPatch +{ + private static TextMeshPro HostText; + + public static void Postfix(HostInfoPanel __instance) { - private static TextMeshPro HostText; + if (HostText == null) HostText = __instance.content.transform.FindChild("Name").GetComponent(); - public static void Postfix(HostInfoPanel __instance) - { - if (HostText == null) HostText = __instance.content.transform.FindChild("Name").GetComponent(); + string icon = Translator.GetString("Icon"); + string name = GameData.Instance?.GetHost()?.PlayerName?.RemoveHtmlTags()?.Split('\n').FirstOrDefault(x => x.Contains(icon))?.Split(icon)[^1] ?? string.Empty; + if (name == string.Empty) return; - string icon = Translator.GetString("Icon"); - string name = GameData.Instance?.GetHost()?.PlayerName?.RemoveHtmlTags()?.Split('\n').FirstOrDefault(x => x.Contains(icon))?.Split(icon)[^1] ?? string.Empty; - if (name == string.Empty) return; + string text = AmongUsClient.Instance.AmHost + ? Translator.GetString("YouAreHostSuffix") + : name; - string text = AmongUsClient.Instance.AmHost - ? Translator.GetString("YouAreHostSuffix") - : name; - - HostText.text = Utils.ColorString(Palette.PlayerColors[__instance.player.ColorId], text); - } + HostText.text = Utils.ColorString(Palette.PlayerColors[__instance.player.ColorId], text); } +} - public static class LobbyPatch +public static class LobbyPatch +{ + public static bool IsGlitchedRoomCode() { - public static bool IsGlitchedRoomCode() - { - string roomCode = GameCode.IntToGameName(AmongUsClient.Instance.GameId).ToUpper(); - string[] badEndings = ["IJPG", "SZAF", "LDQG", "ALGG", "UMPG", "GFXG", "JTFG", "PATG", "WMPG", "FUGG", "YTHG", "UFLG", "FBGG", "ZCQG", "RGGG", "ZHLG", "PJDG", "KJQG", "VDXG", "LCAF"]; - return badEndings.Any(roomCode.EndsWith); - } + string roomCode = GameCode.IntToGameName(AmongUsClient.Instance.GameId).ToUpper(); + string[] badEndings = ["IJPG", "SZAF", "LDQG", "ALGG", "UMPG", "GFXG", "JTFG", "PATG", "WMPG", "FUGG", "YTHG", "UFLG", "FBGG", "ZCQG", "RGGG", "ZHLG", "PJDG", "KJQG", "VDXG", "LCAF"]; + return badEndings.Any(roomCode.EndsWith); } } \ No newline at end of file diff --git a/Patches/LogoAndStampPatch.cs b/Patches/LogoAndStampPatch.cs index 200fd7753..94393c522 100644 --- a/Patches/LogoAndStampPatch.cs +++ b/Patches/LogoAndStampPatch.cs @@ -4,78 +4,78 @@ using HarmonyLib; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +// Credit:https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/LogoAndStampPatch.cs +[HarmonyPatch] +public static class CredentialsPatch { - // Credit:https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Patches/LogoAndStampPatch.cs - [HarmonyPatch] - public static class CredentialsPatch + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + public static class LogoPatch { - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - public static class LogoPatch + private static string BoosterData = string.Empty; + private static string SponsersData = string.Empty; + private static string DevsData = string.Empty; + private static string TransData = string.Empty; + + private static IEnumerator ViewCredentialsCoro(MainMenuManager __instance) { - private static string BoosterData = string.Empty; - private static string SponsersData = string.Empty; - private static string DevsData = string.Empty; - private static string TransData = string.Empty; - - private static IEnumerator ViewCredentialsCoro(MainMenuManager __instance) + while (true) { - while (true) + yield return new WaitForSeconds(1f); + + if (__instance != null) { - yield return new WaitForSeconds(1f); - - if (__instance != null) - { - // ViewBoosterPatch(__instance); - } - - break; + // ViewBoosterPatch(__instance); } + + break; } + } - public static void InitCredentialsData() - { - BoosterData = string.Empty; - SponsersData = string.Empty; - DevsData = string.Empty; - TransData = string.Empty; + public static void InitCredentialsData() + { + BoosterData = string.Empty; + SponsersData = string.Empty; + DevsData = string.Empty; + TransData = string.Empty; - DevsData += $"♥咔皮呆 & Gurge44 - {GetString("MainDev")}"; - DevsData += $"\n♥IRIDESCENT - {GetString("Art")}"; - DevsData += $"\nNCSIMON - {GetString("RoleDev")}"; - DevsData += $"\n天寸梦初 - {GetString("RoleDev")}&{GetString("TechSup")}"; - DevsData += $"\nCommandf1 - {GetString("RoleDev")}&{GetString("FeatureDev")}"; - DevsData += $"\n喜 - {GetString("RoleDev")}"; - DevsData += $"\nSHAAARKY - {GetString("RoleDev")}"; - DevsData += $"\nGurge44 - {GetString("FeatureDev")}"; + DevsData += $"♥咔皮呆 & Gurge44 - {GetString("MainDev")}"; + DevsData += $"\n♥IRIDESCENT - {GetString("Art")}"; + DevsData += $"\nNCSIMON - {GetString("RoleDev")}"; + DevsData += $"\n天寸梦初 - {GetString("RoleDev")}&{GetString("TechSup")}"; + DevsData += $"\nCommandf1 - {GetString("RoleDev")}&{GetString("FeatureDev")}"; + DevsData += $"\n喜 - {GetString("RoleDev")}"; + DevsData += $"\nSHAAARKY - {GetString("RoleDev")}"; + DevsData += $"\nGurge44 - {GetString("FeatureDev")}"; - TransData += $"Tommy-XL - {GetString("TranEN")}&{GetString("TranRU")}"; - TransData += $"\nTem - {GetString("TranEN")}&{GetString("TranRU")}"; - TransData += $"\n阿龍 - {GetString("TranCHT")}"; - TransData += $"\nGurge44 - {GetString("TranEN")}"; - TransData += $"\n法官 - {GetString("TranCHT")}"; - TransData += $"\nSolarFlare - {GetString("TranEN")}"; - TransData += $"\nchill_ultimated - {GetString("TranRU")}"; + TransData += $"Tommy-XL - {GetString("TranEN")}&{GetString("TranRU")}"; + TransData += $"\nTem - {GetString("TranEN")}&{GetString("TranRU")}"; + TransData += $"\n阿龍 - {GetString("TranCHT")}"; + TransData += $"\nGurge44 - {GetString("TranEN")}"; + TransData += $"\n法官 - {GetString("TranCHT")}"; + TransData += $"\nSolarFlare - {GetString("TranEN")}"; + TransData += $"\nchill_ultimated - {GetString("TranRU")}"; - BoosterData += "bunny"; - BoosterData += "\nNamra"; - BoosterData += "\nKNIGHT"; - BoosterData += "\nSolarFlare"; + BoosterData += "bunny"; + BoosterData += "\nNamra"; + BoosterData += "\nKNIGHT"; + BoosterData += "\nSolarFlare"; - SponsersData += "罗寄"; - SponsersData += "\n鬼"; - SponsersData += "\n喜"; - SponsersData += "\n小叨院长"; - SponsersData += "\n波奇酱"; - SponsersData += "\n法师"; - SponsersData += "\n沐煊"; - SponsersData += "\nSolarFlare"; - SponsersData += "\n林林林"; - SponsersData += "\n撒币"; - SponsersData += "\n斯卡蒂Skadi"; - SponsersData += "\nltemten"; - SponsersData += $"\n\n({GetString("OnlyShowPart")})"; - } + SponsersData += "罗寄"; + SponsersData += "\n鬼"; + SponsersData += "\n喜"; + SponsersData += "\n小叨院长"; + SponsersData += "\n波奇酱"; + SponsersData += "\n法师"; + SponsersData += "\n沐煊"; + SponsersData += "\nSolarFlare"; + SponsersData += "\n林林林"; + SponsersData += "\n撒币"; + SponsersData += "\n斯卡蒂Skadi"; + SponsersData += "\nltemten"; + SponsersData += $"\n\n({GetString("OnlyShowPart")})"; + } /* static void ViewBoosterPatch(MainMenuManager __instance) @@ -143,11 +143,10 @@ namespace EHR } */ - public static void Postfix(MainMenuManager __instance) - { - InitCredentialsData(); - AmongUsClient.Instance.StartCoroutine(ViewCredentialsCoro(__instance)); - } + public static void Postfix(MainMenuManager __instance) + { + InitCredentialsData(); + AmongUsClient.Instance.StartCoroutine(ViewCredentialsCoro(__instance)); } } } \ No newline at end of file diff --git a/Patches/MainMenuManagerPatch.cs b/Patches/MainMenuManagerPatch.cs index 3cd6e2878..614cc8bfb 100644 --- a/Patches/MainMenuManagerPatch.cs +++ b/Patches/MainMenuManagerPatch.cs @@ -4,216 +4,215 @@ using TMPro; using UnityEngine; using UnityEngine.Events; -namespace EHR +namespace EHR; + +[HarmonyPatch] +public static class MainMenuManagerPatch { - [HarmonyPatch] - public static class MainMenuManagerPatch + public static PassiveButton Template; + public static PassiveButton UpdateButton; + private static PassiveButton GitHubButton; + private static PassiveButton DiscordButton; + private static PassiveButton WebsiteButton; + + private static bool IsOnline; + public static bool ShowedBak; + private static bool ShowingPanel; + private static SpriteRenderer MgLogo; + private static MainMenuManager Instance { get; set; } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenGameModeMenu))] + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenAccountMenu))] + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenCredits))] + [HarmonyPrefix] + [HarmonyPriority(Priority.Last)] + public static void ShowRightPanel() { - public static PassiveButton Template; - public static PassiveButton UpdateButton; - private static PassiveButton GitHubButton; - private static PassiveButton DiscordButton; - private static PassiveButton WebsiteButton; + ShowingPanel = true; + } - private static bool IsOnline; - public static bool ShowedBak; - private static bool ShowingPanel; - private static SpriteRenderer MgLogo; - private static MainMenuManager Instance { get; set; } + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] + [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Show))] + [HarmonyPrefix] + [HarmonyPriority(Priority.Last)] + public static void HideRightPanel() + { + ShowingPanel = false; + AccountManager.Instance?.transform.FindChild("AccountTab/AccountWindow")?.gameObject.SetActive(false); + } - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenGameModeMenu))] - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenAccountMenu))] - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.OpenCredits))] - [HarmonyPrefix] - [HarmonyPriority(Priority.Last)] - public static void ShowRightPanel() + public static void ShowRightPanelImmediately() + { + ShowingPanel = true; + TitleLogoPatch.RightPanel.transform.localPosition = TitleLogoPatch.RightPanelOp; + Instance.OpenGameModeMenu(); + Instance.playButton.OnClick.AddListener((UnityAction)ShowRightPanelImmediately); + } + + [HarmonyPatch(typeof(SignInStatusComponent), nameof(SignInStatusComponent.SetOnline))] + [HarmonyPostfix] + public static void SetOnline_Postfix() + { + LateTask.New(() => { IsOnline = true; }, 0.1f, "Set Online Status"); + } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPrefix] + public static void Start_Prefix(MainMenuManager __instance) + { + if (Template == null) Template = __instance.quitButton; + + if (Template == null) return; + + if (UpdateButton == null) { - ShowingPanel = true; + UpdateButton = CreateButton( + "updateButton", + new(4.2f, -1.3f, 1f), + new(255, 165, 0, byte.MaxValue), + new(255, 200, 0, byte.MaxValue), + () => ModUpdater.StartUpdate(ModUpdater.DownloadUrl, true), + Translator.GetString("updateButton")); + + UpdateButton.transform.localScale = Vector3.one; } - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Open))] - [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.Show))] - [HarmonyPrefix] - [HarmonyPriority(Priority.Last)] - public static void HideRightPanel() + UpdateButton.gameObject.SetActive(ModUpdater.HasUpdate); + + Application.targetFrameRate = Main.UnlockFps.Value ? 120 : 60; + } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate))] + [HarmonyPostfix] + public static void MainMenuManager_LateUpdate(MainMenuManager __instance) + { + if (GameObject.Find("MainUI") == null) ShowingPanel = false; + + if (TitleLogoPatch.RightPanel != null) { - ShowingPanel = false; - AccountManager.Instance?.transform.FindChild("AccountTab/AccountWindow")?.gameObject.SetActive(false); + Vector3 pos1 = TitleLogoPatch.RightPanel.transform.localPosition; + Vector3 lerp1 = Vector3.Lerp(pos1, TitleLogoPatch.RightPanelOp + new Vector3(ShowingPanel ? 0f : 10f, 0f, 0f), Time.deltaTime * (ShowingPanel ? 3f : 2f)); + + if (ShowingPanel + ? TitleLogoPatch.RightPanel.transform.localPosition.x > TitleLogoPatch.RightPanelOp.x + 0.03f + : TitleLogoPatch.RightPanel.transform.localPosition.x < TitleLogoPatch.RightPanelOp.x + 9f + ) + TitleLogoPatch.RightPanel.transform.localPosition = lerp1; } - public static void ShowRightPanelImmediately() + if (ShowedBak || !IsOnline) return; + + GameObject bak = GameObject.Find("BackgroundTexture"); + if (bak == null || !bak.active) return; + + Vector3 pos2 = bak.transform.position; + Vector3 lerp2 = Vector3.Lerp(pos2, new(pos2.x, 7.1f, pos2.z), Time.deltaTime * 1.4f); + bak.transform.position = lerp2; + if (pos2.y > 7f) ShowedBak = true; + } + + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + [HarmonyPostfix] + [HarmonyPriority(Priority.VeryHigh)] + public static void Start_Postfix(MainMenuManager __instance) + { + Instance = __instance; + + SimpleButton.SetBase(__instance.quitButton); + var logoObject = new GameObject("titleLogo_MG"); + Transform logoTransform = logoObject.transform; + MgLogo = logoObject.AddComponent(); + logoTransform.localPosition = new(2f, -0.5f, 1f); + logoTransform.localScale *= 1.2f; + MgLogo.sprite = Utils.LoadSprite("EHR.Resources.Images.EHR-Icon.png", 400f); + + // GitHub Button + if (GitHubButton == null) { - ShowingPanel = true; - TitleLogoPatch.RightPanel.transform.localPosition = TitleLogoPatch.RightPanelOp; - Instance.OpenGameModeMenu(); - Instance.playButton.OnClick.AddListener((UnityAction)ShowRightPanelImmediately); + GitHubButton = CreateButton( + "GitHubButton", + new(-2.3f, -1.3f, 1f), + new(153, 153, 153, byte.MaxValue), + new(209, 209, 209, byte.MaxValue), + () => Application.OpenURL("https://github.com/Gurge44/EndlessHostRoles"), + Translator.GetString("GitHub")); //"GitHub" } - [HarmonyPatch(typeof(SignInStatusComponent), nameof(SignInStatusComponent.SetOnline))] - [HarmonyPostfix] - public static void SetOnline_Postfix() + GitHubButton.gameObject.SetActive(true); + + // Discord Button + if (DiscordButton == null) { - LateTask.New(() => { IsOnline = true; }, 0.1f, "Set Online Status"); + DiscordButton = CreateButton( + "DiscordButton", + new(-0.5f, -1.3f, 1f), + new(88, 101, 242, byte.MaxValue), + new(148, 161, byte.MaxValue, byte.MaxValue), + () => Application.OpenURL("https://discord.com/invite/m3ayxfumC8"), + Translator.GetString("Discord")); //"Discord" } - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - [HarmonyPrefix] - public static void Start_Prefix(MainMenuManager __instance) + DiscordButton.gameObject.SetActive(true); + + // Website Button + if (WebsiteButton == null) { - if (Template == null) Template = __instance.quitButton; - - if (Template == null) return; - - if (UpdateButton == null) - { - UpdateButton = CreateButton( - "updateButton", - new(4.2f, -1.3f, 1f), - new(255, 165, 0, byte.MaxValue), - new(255, 200, 0, byte.MaxValue), - () => ModUpdater.StartUpdate(ModUpdater.DownloadUrl, true), - Translator.GetString("updateButton")); - - UpdateButton.transform.localScale = Vector3.one; - } - - UpdateButton.gameObject.SetActive(ModUpdater.HasUpdate); - - Application.targetFrameRate = Main.UnlockFps.Value ? 120 : 60; + WebsiteButton = CreateButton( + "WebsiteButton", + new(1.3f, -1.3f, 1f), + new(251, 81, 44, byte.MaxValue), + new(211, 77, 48, byte.MaxValue), + () => Application.OpenURL("https://sites.google.com/view/ehr-au"), + Translator.GetString("Website")); //"Website" } - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.LateUpdate))] - [HarmonyPostfix] - public static void MainMenuManager_LateUpdate(MainMenuManager __instance) + WebsiteButton.gameObject.SetActive(true); + + Application.targetFrameRate = Main.UnlockFps.Value ? 120 : 60; + + foreach (string buttonName in new[] { "SettingsButton", "Inventory Button", "CreditsButton", "ExitGameButton" }) { - if (GameObject.Find("MainUI") == null) ShowingPanel = false; - - if (TitleLogoPatch.RightPanel != null) - { - Vector3 pos1 = TitleLogoPatch.RightPanel.transform.localPosition; - Vector3 lerp1 = Vector3.Lerp(pos1, TitleLogoPatch.RightPanelOp + new Vector3(ShowingPanel ? 0f : 10f, 0f, 0f), Time.deltaTime * (ShowingPanel ? 3f : 2f)); - - if (ShowingPanel - ? TitleLogoPatch.RightPanel.transform.localPosition.x > TitleLogoPatch.RightPanelOp.x + 0.03f - : TitleLogoPatch.RightPanel.transform.localPosition.x < TitleLogoPatch.RightPanelOp.x + 9f - ) - TitleLogoPatch.RightPanel.transform.localPosition = lerp1; - } - - if (ShowedBak || !IsOnline) return; - - GameObject bak = GameObject.Find("BackgroundTexture"); - if (bak == null || !bak.active) return; - - Vector3 pos2 = bak.transform.position; - Vector3 lerp2 = Vector3.Lerp(pos2, new(pos2.x, 7.1f, pos2.z), Time.deltaTime * 1.4f); - bak.transform.position = lerp2; - if (pos2.y > 7f) ShowedBak = true; - } - - [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] - [HarmonyPostfix] - [HarmonyPriority(Priority.VeryHigh)] - public static void Start_Postfix(MainMenuManager __instance) - { - Instance = __instance; - - SimpleButton.SetBase(__instance.quitButton); - var logoObject = new GameObject("titleLogo_MG"); - Transform logoTransform = logoObject.transform; - MgLogo = logoObject.AddComponent(); - logoTransform.localPosition = new(2f, -0.5f, 1f); - logoTransform.localScale *= 1.2f; - MgLogo.sprite = Utils.LoadSprite("EHR.Resources.Images.EHR-Icon.png", 400f); - - // GitHub Button - if (GitHubButton == null) - { - GitHubButton = CreateButton( - "GitHubButton", - new(-2.3f, -1.3f, 1f), - new(153, 153, 153, byte.MaxValue), - new(209, 209, 209, byte.MaxValue), - () => Application.OpenURL("https://github.com/Gurge44/EndlessHostRoles"), - Translator.GetString("GitHub")); //"GitHub" - } - - GitHubButton.gameObject.SetActive(true); - - // Discord Button - if (DiscordButton == null) - { - DiscordButton = CreateButton( - "DiscordButton", - new(-0.5f, -1.3f, 1f), - new(88, 101, 242, byte.MaxValue), - new(148, 161, byte.MaxValue, byte.MaxValue), - () => Application.OpenURL("https://discord.com/invite/m3ayxfumC8"), - Translator.GetString("Discord")); //"Discord" - } - - DiscordButton.gameObject.SetActive(true); - - // Website Button - if (WebsiteButton == null) - { - WebsiteButton = CreateButton( - "WebsiteButton", - new(1.3f, -1.3f, 1f), - new(251, 81, 44, byte.MaxValue), - new(211, 77, 48, byte.MaxValue), - () => Application.OpenURL("https://sites.google.com/view/ehr-au"), - Translator.GetString("Website")); //"Website" - } - - WebsiteButton.gameObject.SetActive(true); - - Application.targetFrameRate = Main.UnlockFps.Value ? 120 : 60; - - foreach (string buttonName in new[] { "SettingsButton", "Inventory Button", "CreditsButton", "ExitGameButton" }) - { - var buttonText = GameObject.Find(buttonName).transform.Find("FontPlacer/Text_TMP").GetComponent(); - buttonText.DestroyTranslator(); - buttonText.text = Translator.GetString($"MainMenu.{buttonName.Replace(" ", "")}"); - } - - __instance.PlayOnlineButton.OnClick.AddListener((UnityAction)(() => GameOptionsManager.Instance.Initialize())); - } - - private static PassiveButton CreateButton(string name, Vector3 localPosition, Color32 normalColor, Color32 hoverColor, Action action, string label, Vector2? scale = null) - { - PassiveButton button = Object.Instantiate(Template, Template.transform.parent); - button.name = name; - Object.Destroy(button.GetComponent()); - button.transform.localPosition = localPosition; - - button.OnClick = new(); - button.OnClick.AddListener(action); - - var buttonText = button.transform.Find("FontPlacer/Text_TMP").GetComponent(); + var buttonText = GameObject.Find(buttonName).transform.Find("FontPlacer/Text_TMP").GetComponent(); buttonText.DestroyTranslator(); - buttonText.fontSize = buttonText.fontSizeMax = buttonText.fontSizeMin = 3.5f; - buttonText.enableWordWrapping = false; - buttonText.text = label; - var normalSprite = button.inactiveSprites.GetComponent(); - var hoverSprite = button.activeSprites.GetComponent(); - normalSprite.color = normalColor; - hoverSprite.color = hoverColor; - - Transform container = buttonText.transform.parent; - Object.Destroy(container.GetComponent()); - Object.Destroy(buttonText.GetComponent()); - container.SetLocalX(0f); - buttonText.transform.SetLocalX(0f); - buttonText.horizontalAlignment = HorizontalAlignmentOptions.Center; - - var buttonCollider = button.GetComponent(); - if (scale.HasValue) normalSprite.size = hoverSprite.size = buttonCollider.size = scale.Value; - - buttonCollider.offset = new(0f, 0f); - - return button; + buttonText.text = Translator.GetString($"MainMenu.{buttonName.Replace(" ", "")}"); } + + __instance.PlayOnlineButton.OnClick.AddListener((UnityAction)(() => GameOptionsManager.Instance.Initialize())); + } + + private static PassiveButton CreateButton(string name, Vector3 localPosition, Color32 normalColor, Color32 hoverColor, Action action, string label, Vector2? scale = null) + { + PassiveButton button = Object.Instantiate(Template, Template.transform.parent); + button.name = name; + Object.Destroy(button.GetComponent()); + button.transform.localPosition = localPosition; + + button.OnClick = new(); + button.OnClick.AddListener(action); + + var buttonText = button.transform.Find("FontPlacer/Text_TMP").GetComponent(); + buttonText.DestroyTranslator(); + buttonText.fontSize = buttonText.fontSizeMax = buttonText.fontSizeMin = 3.5f; + buttonText.enableWordWrapping = false; + buttonText.text = label; + var normalSprite = button.inactiveSprites.GetComponent(); + var hoverSprite = button.activeSprites.GetComponent(); + normalSprite.color = normalColor; + hoverSprite.color = hoverColor; + + Transform container = buttonText.transform.parent; + Object.Destroy(container.GetComponent()); + Object.Destroy(buttonText.GetComponent()); + container.SetLocalX(0f); + buttonText.transform.SetLocalX(0f); + buttonText.horizontalAlignment = HorizontalAlignmentOptions.Center; + + var buttonCollider = button.GetComponent(); + if (scale.HasValue) normalSprite.size = hoverSprite.size = buttonCollider.size = scale.Value; + + buttonCollider.offset = new(0f, 0f); + + return button; } } \ No newline at end of file diff --git a/Patches/MapPickerMenuPatch.cs b/Patches/MapPickerMenuPatch.cs index 0573df5ae..b4a8c8489 100644 --- a/Patches/MapPickerMenuPatch.cs +++ b/Patches/MapPickerMenuPatch.cs @@ -3,159 +3,158 @@ using System.Linq; using HarmonyLib; using UnityEngine; -namespace EHR.Patches +namespace EHR.Patches; + +// Thanks: https://github.com/SubmergedAmongUs/Submerged/blob/4a5a6b47cbed526670ae4b7eae76acd7c42e35de/Submerged/UI/Patches/MapSelectButtonPatches.cs#L49 +internal static class CreateOptionsPickerPatch { - // Thanks: https://github.com/SubmergedAmongUs/Submerged/blob/4a5a6b47cbed526670ae4b7eae76acd7c42e35de/Submerged/UI/Patches/MapSelectButtonPatches.cs#L49 - internal static class CreateOptionsPickerPatch + public static bool SetDleks; + private static MapSelectButton DleksButton; + + [HarmonyPatch(typeof(GameOptionsMapPicker))] + public static class GameOptionsMapPickerPatch { - public static bool SetDleks; - private static MapSelectButton DleksButton; - - [HarmonyPatch(typeof(GameOptionsMapPicker))] - public static class GameOptionsMapPickerPatch + [HarmonyPatch(nameof(GameOptionsMapPicker.Initialize))] + [HarmonyPostfix] + public static void Postfix_Initialize(GameOptionsMapPicker __instance) { - [HarmonyPatch(nameof(GameOptionsMapPicker.Initialize))] - [HarmonyPostfix] - public static void Postfix_Initialize(GameOptionsMapPicker __instance) + const int dleksPos = 3; + + MapSelectButton[] AllMapButton = __instance.transform.GetComponentsInChildren(); + + if (AllMapButton != null) { - const int dleksPos = 3; + GameObject dlekS_ehT = Object.Instantiate(AllMapButton[0].gameObject, __instance.transform); + dlekS_ehT.transform.position = AllMapButton[dleksPos].transform.position; + dlekS_ehT.transform.SetSiblingIndex(dleksPos + 2); + var dlekS_ehT_MapButton = dlekS_ehT.GetComponent(); + DleksButton = dlekS_ehT_MapButton; + dlekS_ehT_MapButton.MapIcon.transform.localScale = new(-1f, 1f, 1f); + dlekS_ehT_MapButton.Button.OnClick.RemoveAllListeners(); - MapSelectButton[] AllMapButton = __instance.transform.GetComponentsInChildren(); - - if (AllMapButton != null) + dlekS_ehT_MapButton.Button.OnClick.AddListener((Action)(() => { - GameObject dlekS_ehT = Object.Instantiate(AllMapButton[0].gameObject, __instance.transform); - dlekS_ehT.transform.position = AllMapButton[dleksPos].transform.position; - dlekS_ehT.transform.SetSiblingIndex(dleksPos + 2); - var dlekS_ehT_MapButton = dlekS_ehT.GetComponent(); - DleksButton = dlekS_ehT_MapButton; - dlekS_ehT_MapButton.MapIcon.transform.localScale = new(-1f, 1f, 1f); - dlekS_ehT_MapButton.Button.OnClick.RemoveAllListeners(); + __instance.SelectMap(__instance.AllMapIcons[0]); - dlekS_ehT_MapButton.Button.OnClick.AddListener((Action)(() => + if (__instance.selectedButton) __instance.selectedButton.Button.SelectButton(false); + + __instance.selectedButton = dlekS_ehT_MapButton; + __instance.selectedButton.Button.SelectButton(true); + __instance.selectedMapId = 3; + + Main.NormalOptions.MapId = 0; + + __instance.MapImage.sprite = Utils.LoadSprite("EHR.Resources.Images.DleksBanner.png", 100f); + __instance.MapName.sprite = Utils.LoadSprite("EHR.Resources.Images.DleksBanner-Wordart.png", 100f); + })); + + for (int i = dleksPos; i < AllMapButton.Length; i++) AllMapButton[i].transform.localPosition += new Vector3(0.625f, 0f, 0f); + + if (DleksButton != null) + { + if (SetDleks) { - __instance.SelectMap(__instance.AllMapIcons[0]); - if (__instance.selectedButton) __instance.selectedButton.Button.SelectButton(false); - __instance.selectedButton = dlekS_ehT_MapButton; - __instance.selectedButton.Button.SelectButton(true); + DleksButton.Button.SelectButton(true); + __instance.selectedButton = DleksButton; __instance.selectedMapId = 3; - Main.NormalOptions.MapId = 0; - __instance.MapImage.sprite = Utils.LoadSprite("EHR.Resources.Images.DleksBanner.png", 100f); __instance.MapName.sprite = Utils.LoadSprite("EHR.Resources.Images.DleksBanner-Wordart.png", 100f); - })); - - for (int i = dleksPos; i < AllMapButton.Length; i++) AllMapButton[i].transform.localPosition += new Vector3(0.625f, 0f, 0f); - - if (DleksButton != null) - { - if (SetDleks) - { - if (__instance.selectedButton) __instance.selectedButton.Button.SelectButton(false); - - DleksButton.Button.SelectButton(true); - __instance.selectedButton = DleksButton; - __instance.selectedMapId = 3; - - __instance.MapImage.sprite = Utils.LoadSprite("EHR.Resources.Images.DleksBanner.png", 100f); - __instance.MapName.sprite = Utils.LoadSprite("EHR.Resources.Images.DleksBanner-Wordart.png", 100f); - } - else - DleksButton.Button.SelectButton(false); } + else + DleksButton.Button.SelectButton(false); } } - - [HarmonyPatch(nameof(GameOptionsMapPicker.FixedUpdate))] - [HarmonyPrefix] - public static bool Prefix_FixedUpdate(GameOptionsMapPicker __instance) - { - if (DleksButton != null) SetDleks = __instance.selectedMapId == 3; - - return __instance.selectedMapId != 3; - } } - [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))] - private class MenuMapPickerPatch + [HarmonyPatch(nameof(GameOptionsMapPicker.FixedUpdate))] + [HarmonyPrefix] + public static bool Prefix_FixedUpdate(GameOptionsMapPicker __instance) { - public static void Postfix(CreateOptionsPicker __instance) + if (DleksButton != null) SetDleks = __instance.selectedMapId == 3; + + return __instance.selectedMapId != 3; + } + } + + [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Awake))] + private class MenuMapPickerPatch + { + public static void Postfix(CreateOptionsPicker __instance) + { + Transform mapPickerTransform = __instance.transform.Find("MapPicker"); + var mapPickerMenu = mapPickerTransform.Find("Map Picker Menu").GetComponent(); + + MapFilterButton airhipIconInMenu = __instance.MapMenu.MapButtons[3]; + MapFilterButton fungleIconInMenu = __instance.MapMenu.MapButtons[4]; + MapFilterButton skeldIconInMenu = __instance.MapMenu.MapButtons[0]; + MapFilterButton dleksIconInMenuCopy = Object.Instantiate(airhipIconInMenu, airhipIconInMenu.transform.parent); + + Transform skeldMenuButton = mapPickerMenu.transform.Find("Skeld"); + Transform polusMenuButton = mapPickerMenu.transform.Find("Polus"); + Transform airshipMenuButton = mapPickerMenu.transform.Find("Airship"); + Transform fungleMenuButton = mapPickerMenu.transform.Find("Fungle"); + Transform dleksMenuButtonCopy = Object.Instantiate(airshipMenuButton, airshipMenuButton.parent); + + // Set mapid for Dleks button + var dleksButton = dleksMenuButtonCopy.GetComponent(); + dleksButton.OnClick.m_PersistentCalls.m_Calls._items[0].arguments.intArgument = (int)MapNames.Dleks; + + var dleksImage = dleksMenuButtonCopy.Find("Image").GetComponent(); + dleksImage.sprite = skeldMenuButton.Find("Image").GetComponent().sprite; + + dleksIconInMenuCopy.name = "Dleks"; + dleksIconInMenuCopy.transform.localPosition = new(0.8f, airhipIconInMenu.transform.localPosition.y, airhipIconInMenu.transform.localPosition.z); + dleksIconInMenuCopy.MapId = MapNames.Dleks; + dleksIconInMenuCopy.Button = dleksButton; + dleksIconInMenuCopy.ButtonCheck = dleksMenuButtonCopy.Find("selectedCheck").GetComponent(); + dleksIconInMenuCopy.ButtonImage = dleksImage; + dleksIconInMenuCopy.ButtonOutline = dleksImage.transform.parent.GetComponent(); + dleksIconInMenuCopy.Icon.sprite = skeldIconInMenu.Icon.sprite; + + dleksMenuButtonCopy.name = "Dleks"; + dleksMenuButtonCopy.position = new(dleksMenuButtonCopy.position.x, (2f * dleksMenuButtonCopy.position.y) - polusMenuButton.transform.position.y, dleksMenuButtonCopy.position.z); + fungleMenuButton.position = new(fungleMenuButton.position.x, dleksMenuButtonCopy.transform.position.y - 0.6f, fungleMenuButton.position.z); + + __instance.MapMenu.MapButtons = __instance.MapMenu.MapButtons.AddItem(dleksIconInMenuCopy).ToArray(); + + float xPos = -1f; + + for (var index = 0; index < 6; ++index) { - Transform mapPickerTransform = __instance.transform.Find("MapPicker"); - var mapPickerMenu = mapPickerTransform.Find("Map Picker Menu").GetComponent(); - - MapFilterButton airhipIconInMenu = __instance.MapMenu.MapButtons[3]; - MapFilterButton fungleIconInMenu = __instance.MapMenu.MapButtons[4]; - MapFilterButton skeldIconInMenu = __instance.MapMenu.MapButtons[0]; - MapFilterButton dleksIconInMenuCopy = Object.Instantiate(airhipIconInMenu, airhipIconInMenu.transform.parent); - - Transform skeldMenuButton = mapPickerMenu.transform.Find("Skeld"); - Transform polusMenuButton = mapPickerMenu.transform.Find("Polus"); - Transform airshipMenuButton = mapPickerMenu.transform.Find("Airship"); - Transform fungleMenuButton = mapPickerMenu.transform.Find("Fungle"); - Transform dleksMenuButtonCopy = Object.Instantiate(airshipMenuButton, airshipMenuButton.parent); - - // Set mapid for Dleks button - var dleksButton = dleksMenuButtonCopy.GetComponent(); - dleksButton.OnClick.m_PersistentCalls.m_Calls._items[0].arguments.intArgument = (int)MapNames.Dleks; - - var dleksImage = dleksMenuButtonCopy.Find("Image").GetComponent(); - dleksImage.sprite = skeldMenuButton.Find("Image").GetComponent().sprite; - - dleksIconInMenuCopy.name = "Dleks"; - dleksIconInMenuCopy.transform.localPosition = new(0.8f, airhipIconInMenu.transform.localPosition.y, airhipIconInMenu.transform.localPosition.z); - dleksIconInMenuCopy.MapId = MapNames.Dleks; - dleksIconInMenuCopy.Button = dleksButton; - dleksIconInMenuCopy.ButtonCheck = dleksMenuButtonCopy.Find("selectedCheck").GetComponent(); - dleksIconInMenuCopy.ButtonImage = dleksImage; - dleksIconInMenuCopy.ButtonOutline = dleksImage.transform.parent.GetComponent(); - dleksIconInMenuCopy.Icon.sprite = skeldIconInMenu.Icon.sprite; - - dleksMenuButtonCopy.name = "Dleks"; - dleksMenuButtonCopy.position = new(dleksMenuButtonCopy.position.x, (2f * dleksMenuButtonCopy.position.y) - polusMenuButton.transform.position.y, dleksMenuButtonCopy.position.z); - fungleMenuButton.position = new(fungleMenuButton.position.x, dleksMenuButtonCopy.transform.position.y - 0.6f, fungleMenuButton.position.z); - - __instance.MapMenu.MapButtons = __instance.MapMenu.MapButtons.AddItem(dleksIconInMenuCopy).ToArray(); - - float xPos = -1f; - - for (var index = 0; index < 6; ++index) - { - __instance.MapMenu.MapButtons[index].transform.SetLocalX(xPos); - xPos += 0.34f; - } - - if (__instance.mode == SettingsMode.Host) - { - mapPickerMenu.transform.localPosition = new(mapPickerMenu.transform.localPosition.x, 0.85f, mapPickerMenu.transform.localPosition.z); - - mapPickerTransform.localScale = new(0.86f, 0.85f, 1f); - mapPickerTransform.transform.localPosition = new(mapPickerTransform.transform.localPosition.x + 0.05f, mapPickerTransform.transform.localPosition.y + 0.03f, mapPickerTransform.transform.localPosition.z); - } - - SwapIconOrButtonPositions(airhipIconInMenu, dleksIconInMenuCopy); - SwapIconOrButtonPositions(fungleIconInMenu, airhipIconInMenu); - - SwapIconOrButtonPositions(airshipMenuButton, dleksMenuButtonCopy); - - // set flipped dleks map Icon/button - __instance.MapMenu.MapButtons[5].SetFlipped(true); - - mapPickerMenu.transform.Find("Backdrop").localScale *= 5; + __instance.MapMenu.MapButtons[index].transform.SetLocalX(xPos); + xPos += 0.34f; } - private static void SwapIconOrButtonPositions(Component one, Component two) + if (__instance.mode == SettingsMode.Host) { - Transform transform1 = one.transform; - Transform transform2 = two.transform; - Vector3 position1 = two.transform.position; - Vector3 position2 = one.transform.position; - transform1.position = position1; - transform2.position = position2; + mapPickerMenu.transform.localPosition = new(mapPickerMenu.transform.localPosition.x, 0.85f, mapPickerMenu.transform.localPosition.z); + + mapPickerTransform.localScale = new(0.86f, 0.85f, 1f); + mapPickerTransform.transform.localPosition = new(mapPickerTransform.transform.localPosition.x + 0.05f, mapPickerTransform.transform.localPosition.y + 0.03f, mapPickerTransform.transform.localPosition.z); } + + SwapIconOrButtonPositions(airhipIconInMenu, dleksIconInMenuCopy); + SwapIconOrButtonPositions(fungleIconInMenu, airhipIconInMenu); + + SwapIconOrButtonPositions(airshipMenuButton, dleksMenuButtonCopy); + + // set flipped dleks map Icon/button + __instance.MapMenu.MapButtons[5].SetFlipped(true); + + mapPickerMenu.transform.Find("Backdrop").localScale *= 5; + } + + private static void SwapIconOrButtonPositions(Component one, Component two) + { + Transform transform1 = one.transform; + Transform transform2 = two.transform; + Vector3 position1 = two.transform.position; + Vector3 position2 = one.transform.position; + transform1.position = position1; + transform2.position = position2; } } } \ No newline at end of file diff --git a/Patches/MeetingHudPatch.cs b/Patches/MeetingHudPatch.cs index 1ef2fb1b7..f515e865d 100644 --- a/Patches/MeetingHudPatch.cs +++ b/Patches/MeetingHudPatch.cs @@ -15,1244 +15,1243 @@ using static EHR.Translator; // ReSharper disable AccessToModifiedClosure -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))] +internal static class CheckForEndVotingPatch { - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))] - internal static class CheckForEndVotingPatch + public static string EjectionText = string.Empty; + public static bool RunRoleCode = true; + + public static bool Prefix(MeetingHud __instance) { - public static string EjectionText = string.Empty; - public static bool RunRoleCode = true; + if (!AmongUsClient.Instance.AmHost) return true; - public static bool Prefix(MeetingHud __instance) + if (Medic.PlayerIdList.Count > 0) Medic.OnCheckMark(); + + //Meeting Skip with vote counting on keystroke (m + delete) + bool shouldSkip = Input.GetKeyDown(KeyCode.F6); + + LogHandler voteLog = Logger.Handler("Vote"); + + try { - if (!AmongUsClient.Instance.AmHost) return true; - - if (Medic.PlayerIdList.Count > 0) Medic.OnCheckMark(); - - //Meeting Skip with vote counting on keystroke (m + delete) - bool shouldSkip = Input.GetKeyDown(KeyCode.F6); - - LogHandler voteLog = Logger.Handler("Vote"); - - try - { - List statesList = []; - MeetingHud.VoterState[] states; - - foreach (PlayerVoteArea pva in __instance.playerStates) - { - if (pva == null) continue; - - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null) continue; - // Dictators who are not dead have already voted. - - // Take the initiative to rebel - if (pva.DidVote && pc.PlayerId == pva.VotedFor && pva.VotedFor < 253 && !pc.Data.IsDead) - { - if (Options.MadmateSpawnMode.GetInt() == 2 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && pc.CanBeMadmate()) - { - Main.MadmateNum++; - pc.RpcSetCustomRole(CustomRoles.Madmate); - ExtendedPlayerControl.RpcSetCustomRole(pc.PlayerId, CustomRoles.Madmate); - Utils.NotifyRoles(true, pc, NoCache: true); - Logger.Info($"Set role: {pc.Data?.PlayerName} => {pc.GetCustomRole()} + {CustomRoles.Madmate}", $"Assign {CustomRoles.Madmate}"); - } - } - - if (pc.Is(CustomRoles.Dictator) && pva.DidVote && pc.PlayerId != pva.VotedFor && pva.VotedFor < 253 && pc.Data?.IsDead == false) - { - PlayerControl voteTarget = Utils.GetPlayerById(pva.VotedFor); - TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, pc.PlayerId); - - statesList.Add(new() - { - VoterId = pva.TargetPlayerId, - VotedForId = pva.VotedFor - }); - - states = [.. statesList]; - __instance.RpcVotingComplete(states.ToArray(), voteTarget.Data, false); - - Statistics.OnVotingComplete(states.ToArray(), voteTarget.Data, false, true); - - Logger.Info($"{voteTarget.GetNameWithRole().RemoveHtmlTags()} expelled by dictator", "Dictator"); - CheckForDeathOnExile(PlayerState.DeathReason.Vote, pva.VotedFor); - Logger.Info("Dictatorship vote, forced end of meeting", "Special Phase"); - voteTarget.SetRealKiller(pc); - Main.LastVotedPlayerInfo = voteTarget.Data; - AntiBlackout.ExilePlayerId = voteTarget.PlayerId; - if (Main.LastVotedPlayerInfo != null) ConfirmEjections(Main.LastVotedPlayerInfo, false); - - return true; - } - - if (pva.DidVote && pva.VotedFor < 253 && pc.IsAlive()) - { - PlayerControl voteTarget = Utils.GetPlayerById(pva.VotedFor); - - if (voteTarget == null || !voteTarget.IsAlive() || voteTarget.Data == null || voteTarget.Data.Disconnected) - { - pva.UnsetVote(); - __instance.RpcClearVote(pc.GetClientId()); - __instance.UpdateButtons(); - pva.VotedFor = byte.MaxValue; - } - else if (voteTarget != null && !pc.GetCustomRole().CancelsVote()) - Main.PlayerStates[pc.PlayerId].Role.OnVote(pc, voteTarget); - else if (pc.Is(CustomRoles.Godfather)) Godfather.GodfatherTarget = byte.MaxValue; - } - } - - if (!shouldSkip && !__instance.playerStates.All(ps => ps == null || !Main.PlayerStates.TryGetValue(ps.TargetPlayerId, out PlayerState st) || st.IsDead || ps.DidVote || Utils.GetPlayerById(ps.TargetPlayerId) == null || Utils.GetPlayerById(ps.TargetPlayerId).Data == null || Utils.GetPlayerById(ps.TargetPlayerId).Data.Disconnected)) return false; - - NetworkedPlayerInfo exiledPlayer = PlayerControl.LocalPlayer.Data; - var tie = false; - EjectionText = string.Empty; - - foreach (PlayerVoteArea ps in __instance.playerStates) - { - if (ps == null) continue; - - voteLog.Info($"{ps.TargetPlayerId,-2}{$"({Utils.GetVoteName(ps.TargetPlayerId)})".PadRightV2(40)}:{ps.VotedFor,-3}({Utils.GetVoteName(ps.VotedFor)})"); - PlayerControl voter = Utils.GetPlayerById(ps.TargetPlayerId); - if (voter == null || voter.Data == null || voter.Data.Disconnected) continue; - - if (Options.VoteMode.GetBool()) - { - if (ps.VotedFor == 253 && !voter.Data.IsDead && - !(Options.WhenSkipVoteIgnoreFirstMeeting.GetBool() && MeetingStates.FirstMeeting) && - !(Options.WhenSkipVoteIgnoreNoDeadBody.GetBool() && !MeetingStates.IsExistDeadBody) && - !(Options.WhenSkipVoteIgnoreEmergency.GetBool() && MeetingStates.IsEmergencyMeeting) - ) - { - switch (Options.GetWhenSkipVote()) - { - case VoteMode.Suicide: - TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, ps.TargetPlayerId); - voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Commit suicide for skipping voting"); - break; - case VoteMode.SelfVote: - ps.VotedFor = ps.TargetPlayerId; - voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Self-voting due to skipping voting"); - break; - } - } - - if (ps.VotedFor == 254 && !voter.Data.IsDead) - { - switch (Options.GetWhenNonVote()) - { - case VoteMode.Suicide: - TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, ps.TargetPlayerId); - voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Committed suicide for not voting"); - break; - case VoteMode.SelfVote: - ps.VotedFor = ps.TargetPlayerId; - voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Self-voting due to not voting"); - break; - case VoteMode.Skip: - ps.VotedFor = 253; - voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Skip for not voting"); - break; - } - } - } - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Divinator) && Divinator.HideVote.GetBool()) continue; - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Eraser) && Eraser.HideVote.GetBool()) continue; - - if (CheckRole(ps.TargetPlayerId, CustomRoles.NiceEraser) && NiceEraser.HideVote.GetBool()) continue; - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Scout) && Scout.HideVote.GetBool()) continue; - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Oracle) && Oracle.HideVote.GetBool()) continue; - - if (ps.TargetPlayerId == ps.VotedFor && Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.IsEnable() && MeetingStates.FirstMeeting) continue; - - AddVote(); - - if (Main.PlayerStates[ps.TargetPlayerId].Role is Adventurer { IsEnable: true } av && av.ActiveWeapons.Contains(Adventurer.Weapon.Proxy)) AddVote(); - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Mayor) && !Mayor.MayorHideVote.GetBool()) Loop.Times(Mayor.MayorAdditionalVote.GetInt(), _ => AddVote()); - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Vindicator) && !Options.VindicatorHideVote.GetBool()) Loop.Times(Options.VindicatorAdditionalVote.GetInt(), _ => AddVote()); - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Knighted)) AddVote(); - - if (CheckRole(ps.TargetPlayerId, CustomRoles.DualPersonality) && Options.DualVotes.GetBool()) - { - int count = statesList.Count(x => x.VoterId == ps.TargetPlayerId && x.VotedForId == ps.VotedFor); - Loop.Times(count, _ => AddVote()); - } - - if (CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) - { - var count = (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Options.TicketsPerKill.GetFloat()); - Loop.Times(count, _ => AddVote()); - } - - if (CheckRole(ps.TargetPlayerId, CustomRoles.Pickpocket)) - { - var count = (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Pickpocket.VotesPerKill.GetFloat()); - Loop.Times(count, _ => AddVote()); - } - - continue; - - void AddVote() - { - statesList.Add(new() - { - VoterId = ps.TargetPlayerId, - VotedForId = ps.VotedFor - }); - } - } - - if ((Blackmailer.On || NiceSwapper.On) && !RunRoleCode) - { - Logger.Warn($"Running role code is disabled, skipping the rest of the process (Blackmailer.On: {Blackmailer.On}, NiceSwapper.On: {NiceSwapper.On}, RunRoleCode: {RunRoleCode})", "Vote"); - LateTask.New(() => RunRoleCode = true, 0.5f, "Enable RunRoleCode"); - return false; - } - - Blackmailer.OnCheckForEndVoting(); - NiceSwapper.OnCheckForEndVoting(); - - states = [.. statesList]; - - Dictionary VotingData = __instance.CustomCalculateVotes(); - Assumer.OnVotingEnd(VotingData); - var exileId = byte.MaxValue; - var max = 0; - voteLog.Info("===Decision to expel player processing begins==="); - - foreach (KeyValuePair data in VotingData) - { - voteLog.Info($"{data.Key} ({Utils.GetVoteName(data.Key)}): {data.Value} votes"); - - if (data.Value > max) - { - voteLog.Info($"{data.Key} has higher votes ({data.Value})"); - if (Dad.OnVotedOut(data.Key)) continue; - - exileId = data.Key; - max = data.Value; - tie = false; - } - else if (data.Value == max) - { - voteLog.Info($"{data.Key} and {exileId} have the same number of votes ({data.Value})"); - exileId = byte.MaxValue; - tie = true; - } - - voteLog.Info($"expelID: {exileId}, maximum: {max} votes"); - } - - voteLog.Info($"Decided to eject: {exileId} ({Utils.GetVoteName(exileId)})"); - - var braked = false; - - if (tie) - { - var target = byte.MaxValue; - - foreach (KeyValuePair data in VotingData.Where(x => x.Key < 15 && x.Value == max)) - { - if (Main.BrakarVoteFor.Contains(data.Key)) - { - if (target != byte.MaxValue) - { - target = byte.MaxValue; - break; - } - - target = data.Key; - } - } - - if (target != byte.MaxValue) - { - Logger.Info("Tiebreaker overrides evicted players", "Brakar Vote"); - exiledPlayer = Utils.GetPlayerInfoById(target); - tie = false; - braked = true; - } - } - - Collector.CollectAmount(VotingData, __instance); - - if (Options.VoteMode.GetBool() && Options.WhenTie.GetBool() && tie) - { - switch ((TieMode)Options.WhenTie.GetValue()) - { - case TieMode.Default: - exiledPlayer = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == exileId); - break; - case TieMode.All: - byte[] exileIds = VotingData.Where(x => x.Key < 15 && x.Value == max).Select(kvp => kvp.Key).ToArray(); - foreach (byte playerId in exileIds) Utils.GetPlayerById(playerId).SetRealKiller(null); - - TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Vote, exileIds); - exiledPlayer = null; - break; - case TieMode.Random: - exiledPlayer = GameData.Instance.AllPlayers.ToArray().OrderBy(_ => Guid.NewGuid()).FirstOrDefault(x => VotingData.TryGetValue(x.PlayerId, out int vote) && vote == max); - tie = false; - break; - } - } - else if (!braked) exiledPlayer = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => !tie && info.PlayerId == exileId); - - exiledPlayer?.Object.SetRealKiller(null); - - __instance.RpcVotingComplete(states.ToArray(), exiledPlayer, tie); - - Statistics.OnVotingComplete(states.ToArray(), exiledPlayer, tie, false); - - CheckForDeathOnExile(PlayerState.DeathReason.Vote, exileId); - Utils.CheckAndSpawnAdditionalRefugee(exiledPlayer); - - Main.LastVotedPlayerInfo = exiledPlayer; - AntiBlackout.ExilePlayerId = exileId; - - if (Main.LastVotedPlayerInfo != null) - { - AntiBlackout.ExilePlayerId = exileId; - ConfirmEjections(Main.LastVotedPlayerInfo, braked); - } - - if (QuizMaster.On) QuizMaster.Data.NumPlayersVotedLastMeeting = __instance.playerStates.Count(x => x.DidVote); - - return false; - } - catch (Exception ex) - { - Utils.ThrowException(ex); - throw; - } - } - - // Reference:https://github.com/music-discussion/TownOfHost-TheOtherRoles - private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool tiebreaker) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (exiledPlayer == null) return; - - byte exileId = exiledPlayer.PlayerId; - if (exileId > 254) return; - - string realName = exiledPlayer.Object.GetRealName(true); - - PlayerControl player = Utils.GetPlayerById(exiledPlayer.PlayerId); - CustomRoles crole = exiledPlayer.GetCustomRole(); - string coloredRole = Utils.GetDisplayRoleName(exileId, true, true); - - if (crole == CustomRoles.LovingImpostor && !Options.ConfirmLoversOnEject.GetBool()) - { - coloredRole = (Lovers.LovingImpostorRoleForOtherImps.GetValue() switch - { - 0 => CustomRoles.ImpostorEHR, - 1 => Lovers.LovingImpostorRole, - _ => CustomRoles.LovingImpostor - }).ToColoredString(); - } - - if (Options.ConfirmEgoistOnEject.GetBool() && player.Is(CustomRoles.Egoist)) coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Egoist), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); - - if (Options.ConfirmLoversOnEject.GetBool() && Main.LoversPlayers.Exists(x => x.PlayerId == player.PlayerId)) coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); - - if (Options.RascalAppearAsMadmate.GetBool() && player.Is(CustomRoles.Rascal)) coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetRoleString("Mad-") + coloredRole.RemoveHtmlTags()); - - var name = string.Empty; - var impnum = 0; - var neutralnum = 0; - - var DecidedWinner = false; - - if (CustomRoles.Bard.RoleExist()) - { - Bard.OnMeetingHudDestroy(ref name); - goto EndOfSession; - } - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc == exiledPlayer.Object) continue; - - if (pc.GetCustomRole().IsImpostor()) - impnum++; - else if (Options.MadmateCountMode.GetValue() == 1 && pc.IsMadmate()) - impnum++; - else if (pc.IsNeutralKiller()) neutralnum++; - } - - string coloredRealName = Utils.ColorString(Main.PlayerColors[player.PlayerId], realName); - - switch (Options.CEMode.GetInt()) - { - case 0: - name = string.Format(GetString("PlayerExiled"), coloredRealName); - break; - case 1: - if (player.IsImpostor() || player.Is(CustomRoles.Parasite) || player.Is(CustomRoles.Crewpostor) || player.Is(CustomRoles.Refugee) || player.Is(CustomRoles.Convict)) - name = string.Format(GetString("BelongTo"), coloredRealName, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("TeamImpostor"))); - else if (player.IsCrewmate()) - name = string.Format(GetString("IsGood"), coloredRealName); - else if (player.GetCustomRole().IsNeutral() && !player.Is(CustomRoles.Parasite) && !player.Is(CustomRoles.Refugee) && !player.Is(CustomRoles.Crewpostor) && !player.Is(CustomRoles.Convict)) name = string.Format(GetString("BelongTo"), coloredRealName, Utils.ColorString(new(255, 171, 27, byte.MaxValue), GetString("TeamNeutral"))); - - break; - case 2: - name = string.Format(GetString("PlayerIsRole"), coloredRealName, coloredRole); - - if (Options.ShowTeamNextToRoleNameOnEject.GetBool()) - { - name += " ("; - CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(player.PlayerId); - - if (team != null) - name += Utils.ColorString(team.RoleRevealScreenBackgroundColor == "*" || !ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color color) ? Color.yellow : color, team.RoleRevealScreenTitle == "*" ? team.TeamName : team.RoleRevealScreenTitle); - else - { - name += player.GetTeam() switch - { - Team.Impostor => Utils.ColorString(new(255, 25, 25, byte.MaxValue), GetString("TeamImpostor")), - Team.Neutral => Utils.ColorString(new(255, 171, 27, byte.MaxValue), GetString("TeamNeutral")), - Team.Crewmate => Utils.ColorString(new(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")), - _ => "----" - }; - } - - name += ")"; - } - - break; - } - - if (crole == CustomRoles.Jester) - { - name = string.Format(GetString("ExiledJester"), realName, coloredRole); - DecidedWinner = true; - } - - if (Executioner.CheckExileTarget(exiledPlayer, true)) - { - name = string.Format(GetString("ExiledExeTarget"), realName, coloredRole); - DecidedWinner = true; - } - - if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exileId)) - { - if (!(!Options.InnocentCanWinByImp.GetBool() && crole.IsImpostor())) - { - if (DecidedWinner) - name += string.Format(GetString("ExiledInnocentTargetAddBelow")); - else - name = string.Format(GetString("ExiledInnocentTargetInOneLine"), realName, coloredRole); - - DecidedWinner = true; - } - } - - if (tiebreaker) name += $" ({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Brakar), GetString("Brakar"))})"; - - if (DecidedWinner) - name += ""; - else if (Options.ShowImpRemainOnEject.GetBool()) - { - name += "\n"; - int actualNeutralnum = neutralnum; - if (!Options.ShowNKRemainOnEject.GetBool()) neutralnum = 0; - - switch (impnum, neutralnum) - { - case (0, 0) when actualNeutralnum == 0 && !Main.AllAlivePlayerControls.Any(x => x.IsConverted()): // Crewmates win - name += GetString("GG"); - break; - case (0, 0) when actualNeutralnum > 0: - name += GetString("IWonderWhatsLeft"); - break; - case (> 0, > 0): // Both imps and neutrals remain - name += impnum switch - { - 1 => $"1 {GetString("RemainingText.ImpSingle")} & ", - 2 => $"2 {GetString("RemainingText.ImpPlural")} & ", - 3 => $"3 {GetString("RemainingText.ImpPlural")} & ", - _ => string.Empty - }; - - if (neutralnum == 1) - name += $"1 {GetString("RemainingText.NKSingle")} {GetString("RemainingText.EjectionSuffix.NKSingle")}"; - else - name += $"{neutralnum} {GetString("RemainingText.NKPlural")} {GetString("RemainingText.EjectionSuffix.NKPlural")}"; - - break; - case (> 0, 0): // Only imps remain - name += impnum switch - { - 1 => GetString("OneImpRemain"), - 2 => GetString("TwoImpRemain"), - 3 => GetString("ThreeImpRemain"), - _ => string.Empty - }; - - break; - case (0, > 0): // Only neutrals remain - if (neutralnum == 1) - name += GetString("OneNeutralRemain"); - else - name += string.Format(GetString("NeutralRemain"), neutralnum); - - break; - } - } - - EndOfSession: - - - name = name.Replace("color=", string.Empty) + ""; - EjectionText = name.Split('\n')[DecidedWinner ? 1 : 0]; - - LateTask.New(() => - { - Main.DoBlockNameChange = true; - - if (GameStates.IsInGame && player != null && !player.Data.Disconnected) - { - exiledPlayer.UpdateName(name, Utils.GetClientById(exiledPlayer.ClientId)); - player.RpcSetName(name); - } - }, 2.5f, "Change Exiled Player Name"); - - LateTask.New(() => - { - if (GameStates.IsInGame && player != null && !player.Data.Disconnected) - { - exiledPlayer.UpdateName(realName, Utils.GetClientById(exiledPlayer.ClientId)); - player.RpcSetName(realName); - Main.DoBlockNameChange = false; - } - }, 11.5f, "Change Exiled Player Name Back"); - } - - public static bool CheckRole(byte id, CustomRoles role) - { - PlayerState s = Main.PlayerStates[id]; - return role.IsAdditionRole() ? s.SubRoles.Contains(role) : s.MainRole == role; - } - - public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathReason, params byte[] playerIds) - { - if (playerIds.Length == 0) return; - Logger.Info($"{playerIds.Join(x => Main.AllPlayerNames[x])} - died with the reason: {deathReason}", "TryAddAfterMeetingDeathPlayers"); - byte[] addedIdList = playerIds.Where(playerId => Main.AfterMeetingDeathPlayers.TryAdd(playerId, deathReason)).ToArray(); - CheckForDeathOnExile(deathReason, addedIdList); - } - - private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, params byte[] playerIds) - { - try - { - if (Witch.PlayerIdList.Count > 0) Witch.OnCheckForEndVoting(deathReason, playerIds); - if (Virus.PlayerIdList.Count > 0) Virus.OnCheckForEndVoting(deathReason, playerIds); - if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds); - if (Wasp.On && deathReason == PlayerState.DeathReason.Vote) Wasp.OnExile(playerIds); - - foreach (byte playerId in playerIds) - { - try - { - byte id = playerId; - - if (CustomRoles.Lovers.IsEnable() && !Main.IsLoversDead && Main.LoversPlayers.Exists(lp => lp.PlayerId == id)) - FixedUpdatePatch.LoversSuicide(playerId, true, true); - - if (Main.PlayerStates.TryGetValue(id, out PlayerState state) && state.SubRoles.Contains(CustomRoles.Avanger)) - RevengeOnExile(playerId /*, deathReason*/); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - private static void RevengeOnExile(byte playerId /*, PlayerState.DeathReason deathReason*/) - { - PlayerControl player = Utils.GetPlayerById(playerId); - if (player == null) return; - - PlayerControl target = PickRevengeTarget(player /*, deathReason*/); - if (target == null) return; - - TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Revenge, target.PlayerId); - target.SetRealKiller(player); - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} revenged: {target.GetNameWithRole().RemoveHtmlTags()}", "RevengeOnExile"); - } - - private static PlayerControl PickRevengeTarget(PlayerControl exiledplayer /*, PlayerState.DeathReason deathReason*/) - { - List TargetList = []; - TargetList.AddRange(Main.AllAlivePlayerControls.Where(candidate => candidate != exiledplayer && !Main.AfterMeetingDeathPlayers.ContainsKey(candidate.PlayerId))); - - if (TargetList.Count == 0) return null; - - PlayerControl target = TargetList.RandomElement(); - return target; - } - } - - internal static class ExtendedMeetingHud - { - public static Dictionary CustomCalculateVotes(this MeetingHud __instance) - { - Logger.Info("===The vote counting process begins===", "Vote"); - Dictionary dic = []; - Main.BrakarVoteFor = []; - Collector.CollectorVoteFor = []; - - foreach (PlayerVoteArea ps in __instance.playerStates) - { - if (ps == null) continue; - - if (ps.VotedFor is not 252 and not byte.MaxValue and not 254) - { - var VoteNum = 1; - - PlayerControl target = Utils.GetPlayerById(ps.VotedFor); - - if (target != null) - { - if (target.Is(CustomRoles.Zombie) || (target.Is(CustomRoles.Shifter) && !Shifter.CanBeVoted.GetBool())) - VoteNum = 0; - - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Brakar)) - if (!Main.BrakarVoteFor.Contains(target.PlayerId)) - Main.BrakarVoteFor.Add(target.PlayerId); - - Collector.CollectorVotes(target, ps); - } - - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Mayor)) VoteNum += Mayor.MayorAdditionalVote.GetInt(); - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Knighted)) VoteNum += 1; - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Glitch) && !Glitch.CanVote.GetBool()) VoteNum = 0; - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Shifter) && !Shifter.CanVote.GetBool()) VoteNum = 0; - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Vindicator)) VoteNum += Options.VindicatorAdditionalVote.GetInt(); - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.DualPersonality) && Options.DualVotes.GetBool()) VoteNum += VoteNum; - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) VoteNum += (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Options.TicketsPerKill.GetFloat()); - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Pickpocket)) VoteNum += (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Pickpocket.VotesPerKill.GetFloat()); - - switch (Main.PlayerStates[ps.TargetPlayerId].Role) - { - case Adventurer { IsEnable: true } av when av.ActiveWeapons.Contains(Adventurer.Weapon.Proxy): - VoteNum++; - break; - case Dad { IsEnable: true } dad when dad.UsingAbilities.Contains(Dad.Ability.GoForMilk): - VoteNum = 0; - break; - } - - if (ps.TargetPlayerId == ps.VotedFor && Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.IsEnable() && MeetingStates.FirstMeeting) VoteNum = 0; - - dic[ps.VotedFor] = !dic.TryGetValue(ps.VotedFor, out int num) ? VoteNum : num + VoteNum; - } - } - - return dic; - } - } - - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - internal static class MeetingHudStartPatch - { - private static void NotifyRoleSkillOnMeetingStart() - { - if (!AmongUsClient.Instance.AmHost) return; - - CheckForEndVotingPatch.RunRoleCode = true; - - List<(string Message, byte TargetID, string Title)> msgToSend = []; - - if (Options.SendRoleDescriptionFirstMeeting.GetBool() && MeetingStates.FirstMeeting) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.IsModClient()) continue; - - CustomRoles role = pc.GetCustomRole(); - StringBuilder sb = new(); - StringBuilder titleSb = new(); - StringBuilder settings = new(); - settings.Append(""); - titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); - sb.Append(""); - sb.Append(pc.GetRoleInfo(true).TrimStart()); - if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, ref settings, disableColor: false); - - settings.Append(""); - if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); - - string searchStr = GetString(role.ToString()); - sb.Replace(searchStr, role.ToColoredString()); - sb.Replace(searchStr.ToLower(), role.ToColoredString()); - sb.Append(""); - - foreach (CustomRoles subRole in Main.PlayerStates[pc.PlayerId].SubRoles) - { - sb.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong")}"); - string searchSubStr = GetString(subRole.ToString()); - sb.Replace(searchSubStr, subRole.ToColoredString()); - sb.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); - } - - if (settings.Length > 0) AddMsg("\n", pc.PlayerId, settings.ToString()); - - AddMsg(sb.Append("").ToString(), pc.PlayerId, titleSb.ToString()); - } - } - - if (msgToSend.Count > 0) LateTask.New(() => { msgToSend.Do(x => Utils.SendMessage(x.Message, x.TargetID, x.Title)); }, 3f, "Skill Description First Meeting"); - - if (Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.IsEnable() && MeetingStates.FirstMeeting) AddMsg(string.Format(GetString("Message.MadmateSelfVoteModeNotify"), GetString("MadmateSpawnMode.SelfVote"))); - - if (CustomRoles.God.RoleExist() && God.NotifyGodAlive.GetBool()) AddMsg(GetString("GodNoticeAlive"), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.God), GetString("GodAliveTitle"))); - - if (MeetingStates.FirstMeeting && CustomRoles.Workaholic.RoleExist() && Workaholic.WorkaholicGiveAdviceAlive.GetBool() && !Workaholic.WorkaholicCannotWinAtDeath.GetBool() /* && !Options.GhostIgnoreTasks.GetBool()*/) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Workaholic)).ToArray()) Workaholic.WorkaholicAlive.Add(pc.PlayerId); - - List workaholicAliveList = []; - workaholicAliveList.AddRange(Workaholic.WorkaholicAlive.Select(whId => Main.AllPlayerNames[whId])); - - string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; - AddMsg(string.Format(GetString("WorkaholicAdviceAlive"), string.Join(separator, workaholicAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Workaholic), GetString("WorkaholicAliveTitle"))); - } - - // Bait Notify - if (MeetingStates.FirstMeeting && CustomRoles.Bait.RoleExist() && Options.BaitNotification.GetBool()) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Bait)).ToArray()) Main.BaitAlive.Add(pc.PlayerId); - - List baitAliveList = []; - baitAliveList.AddRange(Main.BaitAlive.Select(whId => Main.AllPlayerNames[whId])); - - string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; - AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); - } - - var MimicMsg = string.Empty; - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.Is(CustomRoles.Mafia) && !pc.IsAlive()) AddMsg(GetString("MafiaDeadMsg"), pc.PlayerId); - - foreach (byte csId in Main.CyberStarDead) - { - if (!Options.ImpKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsImpostor()) continue; - - if (!Options.NeutralKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; - - AddMsg(string.Format(GetString("CyberStarDead"), Main.AllPlayerNames[csId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.CyberStar), GetString("CyberStarNewsTitle"))); - } - - if (pc != null && Silencer.ForSilencer.Contains(pc.PlayerId)) - { - string playername = pc.GetRealName(); - if (Doppelganger.DoppelVictim.TryGetValue(pc.PlayerId, out string value)) playername = value; - - AddMsg(string.Format(GetString("SilencerDead"), playername, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Silencer), GetString("SilencerKillTitle")))); - } - - if (Detective.DetectiveNotify.TryGetValue(pc.PlayerId, out string value1)) AddMsg(value1, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Detective), GetString("DetectiveNoticeTitle"))); - - if (Main.SleuthMsgs.TryGetValue(pc.PlayerId, out string msg)) AddMsg(msg, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sleuth), GetString("Sleuth"))); - - if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive()) - { - PlayerControl pc1 = pc; - Main.AllAlivePlayerControls.Where(x => x.GetRealKiller()?.PlayerId == pc1.PlayerId).Do(x => MimicMsg += $"\n{x.GetNameWithRole(true)}"); - } - - if (Mortician.MsgToSend.TryGetValue(pc.PlayerId, out string value2)) AddMsg(value2, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mortician), GetString("MorticianCheckTitle"))); - - if (Mediumshiper.ContactPlayer.ContainsValue(pc.PlayerId)) AddMsg(string.Format(GetString("MediumshipNotifySelf"), Main.AllPlayerNames[Mediumshiper.ContactPlayer.FirstOrDefault(x => x.Value == pc.PlayerId).Key], pc.GetAbilityUseLimit()), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); - - if (Mediumshiper.ContactPlayer.ContainsKey(pc.PlayerId) && (!Mediumshiper.OnlyReceiveMsgFromCrew.GetBool() || pc.IsCrewmate())) AddMsg(string.Format(GetString("MediumshipNotifyTarget"), Main.AllPlayerNames[Mediumshiper.ContactPlayer[pc.PlayerId]]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); - - if (Virus.VirusNotify.TryGetValue(pc.PlayerId, out string value3)) AddMsg(value3, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Virus), GetString("VirusNoticeTitle"))); - - if (Enigma.MsgToSend.TryGetValue(pc.PlayerId, out string value4)) AddMsg(value4, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Enigma), Enigma.MsgToSendTitle[pc.PlayerId])); - - if (QuizMaster.On && QuizMaster.MessagesToSend.TryGetValue(pc.PlayerId, out string value5)) - { - AddMsg(value5, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.QuizMaster), GetString("QuizMaster.QuestionSample.Title"))); - QuizMaster.QuizMasters.Do(x => AddMsg(value5, x.QuizMasterId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.QuizMaster), GetString("QuizMaster.QuestionSample.Title.Self")))); - } - } - - if (MimicMsg != string.Empty) - { - MimicMsg = GetString("MimicDeadMsg") + "\n" + MimicMsg; - foreach (PlayerControl ipc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsImpostorTeam()).ToArray()) AddMsg(MimicMsg, ipc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mimic), GetString("MimicMsgTitle"))); - } - - if (msgToSend.Count > 0) LateTask.New(() => msgToSend.Do(x => Utils.SendMessage(x.Message, x.TargetID, x.Title)), 6f, "Meeting Start Notify"); - - Main.CyberStarDead.Clear(); - Express.SpeedNormal.Clear(); - Express.SpeedUp.Clear(); - Detective.DetectiveNotify.Clear(); - Main.SleuthMsgs.Clear(); - Virus.VirusNotify.Clear(); - Mortician.MsgToSend.Clear(); - Enigma.MsgToSend.Clear(); - return; - - void AddMsg(string text, byte sendTo = 255, string title = "") - { - msgToSend.Add((text, sendTo, title)); - } - } - - public static void Prefix( /*MeetingHud __instance*/) - { - Logger.Info("------------Meeting Start------------", "Phase"); - ChatUpdatePatch.DoBlockChat = true; - GameStates.AlreadyDied |= !Utils.IsAllAlive; - Main.AllPlayerControls.Do(x => ReportDeadBodyPatch.WaitReport[x.PlayerId].Clear()); - MeetingStates.MeetingCalled = true; - MeetingStates.MeetingNum++; - } - - public static void Postfix(MeetingHud __instance) - { - SoundManager.Instance.ChangeAmbienceVolume(0f); - - GuessManager.TextTemplate = Object.Instantiate(__instance.playerStates[0].NameText); - GuessManager.TextTemplate.enabled = false; - - PlayerControl seer = PlayerControl.LocalPlayer; - - foreach (PlayerVoteArea pva in __instance.playerStates) - { - PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); - if (target == null) continue; - - bool shouldSeeTargetAddons = seer.PlayerId == target.PlayerId || new[] { seer, target }.All(x => x.Is(Team.Impostor)); - (string, Color) RoleTextData = Utils.GetRoleText(seer.PlayerId, target.PlayerId, seeTargetBetrayalAddons: shouldSeeTargetAddons); - TextMeshPro roleTextMeeting = Object.Instantiate(pva.NameText, pva.NameText.transform, true); - roleTextMeeting.transform.localPosition = new(0f, -0.18f, 0f); - roleTextMeeting.fontSize = 1.4f; - roleTextMeeting.text = RoleTextData.Item1; - if (Main.VisibleTasksCount) roleTextMeeting.text += Utils.GetProgressText(target); - - roleTextMeeting.color = RoleTextData.Item2; - roleTextMeeting.gameObject.name = "RoleTextMeeting"; - roleTextMeeting.enableWordWrapping = false; - - roleTextMeeting.enabled = - target.AmOwner || - (Main.VisibleTasksCount && seer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || - (seer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && target.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) || - (target.Is(CustomRoles.Gravestone) && Main.VisibleTasksCount && target.Data.IsDead) || - (Main.LoversPlayers.TrueForAll(x => x.PlayerId == target.PlayerId || x.PlayerId == seer.PlayerId) && Main.LoversPlayers.Count == 2 && Lovers.LoverKnowRoles.GetBool()) || - (target.Is(CustomRoleTypes.Impostor) && seer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || - (target.Is(CustomRoleTypes.Impostor) && seer.IsMadmate() && Options.MadmateKnowWhosImp.GetBool()) || - (target.IsMadmate() && seer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowWhosMadmate.GetBool()) || - (target.Is(CustomRoleTypes.Impostor) && seer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || - (target.Is(CustomRoles.Crewpostor) && seer.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool()) || - (target.IsMadmate() && seer.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) || - ((target.Is(CustomRoles.Jackal) || target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Recruit)) && (seer.Is(CustomRoles.Sidekick) || seer.Is(CustomRoles.Recruit) || seer.Is(CustomRoles.Jackal))) || - (target.Is(CustomRoles.Workaholic) && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || - (target.Is(CustomRoles.Doctor) && !target.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool()) || - (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) || - (target.Is(CustomRoles.Marshall) && Marshall.CanSeeMarshall(seer) && target.GetTaskState().IsTaskFinished) || - (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) || - (CustomTeamManager.AreInSameCustomTeam(target.PlayerId, seer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(target.PlayerId, CTAOption.KnowRoles)) || - Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)) || - Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == target.PlayerId) || - seer.IsRevealedPlayer(target) || - (seer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) || - seer.Is(CustomRoles.GM) || - Main.GodMode.Value; - - - if (!seer.Data.IsDead && seer.IsRevealedPlayer(target) && target.Is(CustomRoles.Trickster)) - { - roleTextMeeting.text = Farseer.RandomRole[seer.PlayerId]; - roleTextMeeting.text += Farseer.GetTaskState(); - } - - if (EvilTracker.IsTrackTarget(seer, target) && EvilTracker.CanSeeLastRoomInMeeting) - { - roleTextMeeting.text = EvilTracker.GetArrowAndLastRoom(seer, target); - roleTextMeeting.enabled = true; - } - - if (Scout.IsTrackTarget(seer, target) && Scout.CanSeeLastRoomInMeeting) - { - roleTextMeeting.text = Scout.GetArrowAndLastRoom(seer, target); - roleTextMeeting.enabled = true; - } - - string suffix = Main.PlayerStates[seer.PlayerId].Role.GetSuffix(seer, target, meeting: true); - - if (suffix.Length > 0) - { - if (roleTextMeeting.enabled) - roleTextMeeting.text += "\n"; - else - roleTextMeeting.text = string.Empty; - - roleTextMeeting.text += $"<#ffffff>{suffix}"; - roleTextMeeting.enabled = true; - } - - // Thanks BAU (By D1GQ) - are you happy now? - Transform playerLevel = pva.transform.Find("PlayerLevel"); - Transform levelDisplay = Object.Instantiate(playerLevel, pva.transform); - levelDisplay.localPosition = new(-1.21f, -0.15f, playerLevel.transform.localPosition.z); - levelDisplay.transform.SetSiblingIndex(playerLevel.GetSiblingIndex() + 1); - levelDisplay.gameObject.name = "PlayerId"; - levelDisplay.GetComponent().color = Palette.Purple; - Transform idLabel = levelDisplay.transform.Find("LevelLabel"); - Transform idNumber = levelDisplay.transform.Find("LevelNumber"); - Object.Destroy(idLabel.GetComponent()); - idLabel.GetComponent().text = "ID"; - idNumber.GetComponent().text = pva.TargetPlayerId.ToString(); - idLabel.name = "IdLabel"; - idNumber.name = "IdNumber"; - } - - if (Options.SyncButtonMode.GetBool()) - { - Utils.SendMessage(string.Format(GetString("Message.SyncButtonLeft"), Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount)); - Logger.Info("The ship has " + (Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) + " buttons left", "SyncButtonMode"); - } - - TemplateManager.SendTemplate("OnMeeting", noErr: true); - if (MeetingStates.FirstMeeting) TemplateManager.SendTemplate("OnFirstMeeting", noErr: true); - - if (AmongUsClient.Instance.AmHost) NotifyRoleSkillOnMeetingStart(); - - if (AmongUsClient.Instance.AmHost) - { - LateTask.New(() => - { - foreach (PlayerControl pc in Main.AllPlayerControls) pc.RpcSetNameEx(pc.GetRealName(true)); - - ChatUpdatePatch.DoBlockChat = false; - }, 3f, "SetName To Chat"); - } + List statesList = []; + MeetingHud.VoterState[] states; foreach (PlayerVoteArea pva in __instance.playerStates) { if (pva == null) continue; - PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); - if (target == null) continue; + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null) continue; + // Dictators who are not dead have already voted. - if (seer.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) + // Take the initiative to rebel + if (pva.DidVote && pc.PlayerId == pva.VotedFor && pva.VotedFor < 253 && !pc.Data.IsDead) { - target.cosmetics.SetNameColor(Color.white); - pva.NameText.color = Color.white; - } - - StringBuilder sb = new(); - - // Name Color Manager - pva.NameText.text = pva.NameText.text.ApplyNameColorData(seer, target, true); - - CustomRoles seerRole = seer.GetCustomRole(); - - if (seer.KnowDeathReason(target)) sb.Append($"({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})"); - - switch (seer.GetCustomRoleTypes()) - { - case CustomRoleTypes.Impostor: - if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); - - break; - case CustomRoleTypes.Crewmate: - if (target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★")); - - sb.Append(Marshall.GetWarningMark(seer, target)); - break; - } - - if (seer.IsSnitchTarget()) sb.Append(Snitch.GetWarningMark(seer, target)); - - switch (seerRole) - { - case CustomRoles.PlagueBearer when PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId): - sb.Append($"●"); - break; - case CustomRoles.Arsonist: - if (seer.IsDousedPlayer(target)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Arsonist), "▲")); - - break; - case CustomRoles.Executioner: - sb.Append(Executioner.TargetMark(seer, target)); - break; - case CustomRoles.EvilTracker: - sb.Append(EvilTracker.GetTargetMark(seer, target)); - break; - case CustomRoles.Revolutionist: - if (seer.IsDrawPlayer(target)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Revolutionist), "●")); - - break; - case CustomRoles.Psychic: - if (Psychic.IsRedForPsy(target, seer) && !seer.Data.IsDead) pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), pva.NameText.text); - - break; - case CustomRoles.Gamer: - sb.Append(Gamer.TargetMark(seer, target)); - sb.Append(Snitch.GetWarningMark(seer, target)); - break; - case CustomRoles.Scout: - sb.Append(Scout.GetTargetMark(seer, target)); - break; - } - - if (Silencer.ForSilencer.Contains(target.PlayerId)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Silencer), "╳")); - - if (Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId) && (Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId) || seer.Data.IsDead)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), "♥")); - - sb.Append(Witch.GetSpelledMark(target.PlayerId, true)); - sb.Append(Wasp.GetStungMark(target.PlayerId)); - - if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★")); - - if (BallLightning.IsGhost(target)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.BallLightning), "■")); - - if (seer.PlayerId == target.PlayerId && (Medic.InProtect(seer.PlayerId) || Medic.TempMarkProtectedList.Contains(seer.PlayerId)) && Medic.WhoCanSeeProtect.GetInt() is 0 or 2) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); - - if (seer.Is(CustomRoles.Medic) && (Medic.InProtect(target.PlayerId) || Medic.TempMarkProtectedList.Contains(target.PlayerId)) && Medic.WhoCanSeeProtect.GetInt() is 0 or 1) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); - - if (seer.Data.IsDead && Medic.InProtect(target.PlayerId) && !seer.Is(CustomRoles.Medic)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); - - sb.Append(Totocalcio.TargetMark(seer, target)); - sb.Append(Romantic.TargetMark(seer, target)); - sb.Append(Lawyer.LawyerMark(seer, target)); - sb.Append(PlagueDoctor.GetMarkOthers(seer, target)); - sb.Append(Gaslighter.GetMark(seer, target, true)); - - pva.NameText.text += sb.ToString(); - pva.ColorBlindName.transform.localPosition -= new Vector3(1.35f, 0f, 0f); - } - } - } - - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] - internal static class MeetingHudUpdatePatch - { - private static int BufferTime = 10; - - private static void ClearShootButton(MeetingHud __instance, bool forceAll = false) - { - __instance.playerStates.DoIf( - x => (forceAll || !Main.PlayerStates.TryGetValue(x.TargetPlayerId, out PlayerState ps) || ps.IsDead) && x.transform.FindChild("ShootButton") != null, - x => Object.Destroy(x.transform.FindChild("ShootButton").gameObject)); - } - - public static void Postfix(MeetingHud __instance) - { - try - { - // Meeting Skip with vote counting on keystroke (F6) - if (AmongUsClient.Instance.AmHost && Input.GetKeyDown(KeyCode.F6)) __instance.CheckForEndVoting(); - - if (AmongUsClient.Instance.AmHost && Input.GetMouseButtonUp(1) && Input.GetKey(KeyCode.LeftControl)) - { - __instance.playerStates.DoIf(x => x.HighlightedFX.enabled, x => + if (Options.MadmateSpawnMode.GetInt() == 2 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && pc.CanBeMadmate()) { - PlayerControl player = Utils.GetPlayerById(x.TargetPlayerId); + Main.MadmateNum++; + pc.RpcSetCustomRole(CustomRoles.Madmate); + ExtendedPlayerControl.RpcSetCustomRole(pc.PlayerId, CustomRoles.Madmate); + Utils.NotifyRoles(true, pc, NoCache: true); + Logger.Info($"Set role: {pc.Data?.PlayerName} => {pc.GetCustomRole()} + {CustomRoles.Madmate}", $"Assign {CustomRoles.Madmate}"); + } + } - if (player != null && !player.Data.IsDead) - { - Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Execution; - player.RpcExileV2(); - Main.PlayerStates[player.PlayerId].SetDead(); - Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} was executed by the host", "Execution"); - __instance.CheckForEndVoting(); - } + if (pc.Is(CustomRoles.Dictator) && pva.DidVote && pc.PlayerId != pva.VotedFor && pva.VotedFor < 253 && pc.Data?.IsDead == false) + { + PlayerControl voteTarget = Utils.GetPlayerById(pva.VotedFor); + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, pc.PlayerId); + + statesList.Add(new() + { + VoterId = pva.TargetPlayerId, + VotedForId = pva.VotedFor }); + + states = [.. statesList]; + __instance.RpcVotingComplete(states.ToArray(), voteTarget.Data, false); + + Statistics.OnVotingComplete(states.ToArray(), voteTarget.Data, false, true); + + Logger.Info($"{voteTarget.GetNameWithRole().RemoveHtmlTags()} expelled by dictator", "Dictator"); + CheckForDeathOnExile(PlayerState.DeathReason.Vote, pva.VotedFor); + Logger.Info("Dictatorship vote, forced end of meeting", "Special Phase"); + voteTarget.SetRealKiller(pc); + Main.LastVotedPlayerInfo = voteTarget.Data; + AntiBlackout.ExilePlayerId = voteTarget.PlayerId; + if (Main.LastVotedPlayerInfo != null) ConfirmEjections(Main.LastVotedPlayerInfo, false); + + return true; } - if (!GameStates.IsVoting && __instance.lastSecond < 1) + if (pva.DidVote && pva.VotedFor < 253 && pc.IsAlive()) { - if (GameObject.Find("ShootButton") != null) ClearShootButton(__instance, true); + PlayerControl voteTarget = Utils.GetPlayerById(pva.VotedFor); - return; - } - - BufferTime--; - - if (BufferTime < 0 && __instance.discussionTimer > 0) - { - BufferTime = 10; - CustomRoles myRole = PlayerControl.LocalPlayer.GetCustomRole(); - - __instance.playerStates.Where(x => (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out PlayerState ps) || ps.IsDead) && !x.AmDead).Do(x => x.SetDead(x.DidReport, true)); - - switch (myRole) + if (voteTarget == null || !voteTarget.IsAlive() || voteTarget.Data == null || voteTarget.Data.Disconnected) { - case CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Judge or CustomRoles.NiceSwapper or CustomRoles.Councillor or CustomRoles.Guesser when !PlayerControl.LocalPlayer.IsAlive(): - ClearShootButton(__instance, true); - break; - case CustomRoles.Mafia when !PlayerControl.LocalPlayer.IsAlive() && GameObject.Find("ShootButton") == null: - Mafia.CreateJudgeButton(__instance); - break; + pva.UnsetVote(); + __instance.RpcClearVote(pc.GetClientId()); + __instance.UpdateButtons(); + pva.VotedFor = byte.MaxValue; + } + else if (voteTarget != null && !pc.GetCustomRole().CancelsVote()) + Main.PlayerStates[pc.PlayerId].Role.OnVote(pc, voteTarget); + else if (pc.Is(CustomRoles.Godfather)) Godfather.GodfatherTarget = byte.MaxValue; + } + } + + if (!shouldSkip && !__instance.playerStates.All(ps => ps == null || !Main.PlayerStates.TryGetValue(ps.TargetPlayerId, out PlayerState st) || st.IsDead || ps.DidVote || Utils.GetPlayerById(ps.TargetPlayerId) == null || Utils.GetPlayerById(ps.TargetPlayerId).Data == null || Utils.GetPlayerById(ps.TargetPlayerId).Data.Disconnected)) return false; + + NetworkedPlayerInfo exiledPlayer = PlayerControl.LocalPlayer.Data; + var tie = false; + EjectionText = string.Empty; + + foreach (PlayerVoteArea ps in __instance.playerStates) + { + if (ps == null) continue; + + voteLog.Info($"{ps.TargetPlayerId,-2}{$"({Utils.GetVoteName(ps.TargetPlayerId)})".PadRightV2(40)}:{ps.VotedFor,-3}({Utils.GetVoteName(ps.VotedFor)})"); + PlayerControl voter = Utils.GetPlayerById(ps.TargetPlayerId); + if (voter == null || voter.Data == null || voter.Data.Disconnected) continue; + + if (Options.VoteMode.GetBool()) + { + if (ps.VotedFor == 253 && !voter.Data.IsDead && + !(Options.WhenSkipVoteIgnoreFirstMeeting.GetBool() && MeetingStates.FirstMeeting) && + !(Options.WhenSkipVoteIgnoreNoDeadBody.GetBool() && !MeetingStates.IsExistDeadBody) && + !(Options.WhenSkipVoteIgnoreEmergency.GetBool() && MeetingStates.IsEmergencyMeeting) + ) + { + switch (Options.GetWhenSkipVote()) + { + case VoteMode.Suicide: + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, ps.TargetPlayerId); + voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Commit suicide for skipping voting"); + break; + case VoteMode.SelfVote: + ps.VotedFor = ps.TargetPlayerId; + voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Self-voting due to skipping voting"); + break; + } } - ClearShootButton(__instance); + if (ps.VotedFor == 254 && !voter.Data.IsDead) + { + switch (Options.GetWhenNonVote()) + { + case VoteMode.Suicide: + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Suicide, ps.TargetPlayerId); + voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Committed suicide for not voting"); + break; + case VoteMode.SelfVote: + ps.VotedFor = ps.TargetPlayerId; + voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Self-voting due to not voting"); + break; + case VoteMode.Skip: + ps.VotedFor = 253; + voteLog.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} Skip for not voting"); + break; + } + } + } + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Divinator) && Divinator.HideVote.GetBool()) continue; + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Eraser) && Eraser.HideVote.GetBool()) continue; + + if (CheckRole(ps.TargetPlayerId, CustomRoles.NiceEraser) && NiceEraser.HideVote.GetBool()) continue; + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Scout) && Scout.HideVote.GetBool()) continue; + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Oracle) && Oracle.HideVote.GetBool()) continue; + + if (ps.TargetPlayerId == ps.VotedFor && Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.IsEnable() && MeetingStates.FirstMeeting) continue; + + AddVote(); + + if (Main.PlayerStates[ps.TargetPlayerId].Role is Adventurer { IsEnable: true } av && av.ActiveWeapons.Contains(Adventurer.Weapon.Proxy)) AddVote(); + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Mayor) && !Mayor.MayorHideVote.GetBool()) Loop.Times(Mayor.MayorAdditionalVote.GetInt(), _ => AddVote()); + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Vindicator) && !Options.VindicatorHideVote.GetBool()) Loop.Times(Options.VindicatorAdditionalVote.GetInt(), _ => AddVote()); + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Knighted)) AddVote(); + + if (CheckRole(ps.TargetPlayerId, CustomRoles.DualPersonality) && Options.DualVotes.GetBool()) + { + int count = statesList.Count(x => x.VoterId == ps.TargetPlayerId && x.VotedForId == ps.VotedFor); + Loop.Times(count, _ => AddVote()); + } + + if (CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) + { + var count = (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Options.TicketsPerKill.GetFloat()); + Loop.Times(count, _ => AddVote()); + } + + if (CheckRole(ps.TargetPlayerId, CustomRoles.Pickpocket)) + { + var count = (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Pickpocket.VotesPerKill.GetFloat()); + Loop.Times(count, _ => AddVote()); + } + + continue; + + void AddVote() + { + statesList.Add(new() + { + VoterId = ps.TargetPlayerId, + VotedForId = ps.VotedFor + }); } } - catch (Exception ex) + + if ((Blackmailer.On || NiceSwapper.On) && !RunRoleCode) { - Logger.Fatal(ex.ToString(), "MeetingHudUpdatePatch.Postfix"); - Logger.Warn("All Players and their info:", "Debug for Fatal Error"); - foreach (PlayerControl pc in Main.AllPlayerControls) Logger.Info($" {(pc.IsAlive() ? "Alive" : $"Dead ({Main.PlayerStates[pc.PlayerId].deathReason})")}, {Utils.GetProgressText(pc)}, {Utils.GetVitalText(pc.PlayerId)}", $"{pc.GetNameWithRole()} / {pc.PlayerId}"); - - Logger.Warn("-----------------", "Debug for Fatal Error"); - Logger.SendInGame("An error occured with this meeting. Please use /dump and send the log to the developer.\nSorry for the inconvenience."); + Logger.Warn($"Running role code is disabled, skipping the rest of the process (Blackmailer.On: {Blackmailer.On}, NiceSwapper.On: {NiceSwapper.On}, RunRoleCode: {RunRoleCode})", "Vote"); + LateTask.New(() => RunRoleCode = true, 0.5f, "Enable RunRoleCode"); + return false; } - } - } - [HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.SetHighlighted))] - internal static class SetHighlightedPatch - { - public static bool Prefix(PlayerVoteArea __instance, bool value) - { - if (!AmongUsClient.Instance.AmHost) return true; + Blackmailer.OnCheckForEndVoting(); + NiceSwapper.OnCheckForEndVoting(); - if (!__instance.HighlightedFX) return false; + states = [.. statesList]; + + Dictionary VotingData = __instance.CustomCalculateVotes(); + Assumer.OnVotingEnd(VotingData); + var exileId = byte.MaxValue; + var max = 0; + voteLog.Info("===Decision to expel player processing begins==="); + + foreach (KeyValuePair data in VotingData) + { + voteLog.Info($"{data.Key} ({Utils.GetVoteName(data.Key)}): {data.Value} votes"); + + if (data.Value > max) + { + voteLog.Info($"{data.Key} has higher votes ({data.Value})"); + if (Dad.OnVotedOut(data.Key)) continue; + + exileId = data.Key; + max = data.Value; + tie = false; + } + else if (data.Value == max) + { + voteLog.Info($"{data.Key} and {exileId} have the same number of votes ({data.Value})"); + exileId = byte.MaxValue; + tie = true; + } + + voteLog.Info($"expelID: {exileId}, maximum: {max} votes"); + } + + voteLog.Info($"Decided to eject: {exileId} ({Utils.GetVoteName(exileId)})"); + + var braked = false; + + if (tie) + { + var target = byte.MaxValue; + + foreach (KeyValuePair data in VotingData.Where(x => x.Key < 15 && x.Value == max)) + { + if (Main.BrakarVoteFor.Contains(data.Key)) + { + if (target != byte.MaxValue) + { + target = byte.MaxValue; + break; + } + + target = data.Key; + } + } + + if (target != byte.MaxValue) + { + Logger.Info("Tiebreaker overrides evicted players", "Brakar Vote"); + exiledPlayer = Utils.GetPlayerInfoById(target); + tie = false; + braked = true; + } + } + + Collector.CollectAmount(VotingData, __instance); + + if (Options.VoteMode.GetBool() && Options.WhenTie.GetBool() && tie) + { + switch ((TieMode)Options.WhenTie.GetValue()) + { + case TieMode.Default: + exiledPlayer = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => info.PlayerId == exileId); + break; + case TieMode.All: + byte[] exileIds = VotingData.Where(x => x.Key < 15 && x.Value == max).Select(kvp => kvp.Key).ToArray(); + foreach (byte playerId in exileIds) Utils.GetPlayerById(playerId).SetRealKiller(null); + + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Vote, exileIds); + exiledPlayer = null; + break; + case TieMode.Random: + exiledPlayer = GameData.Instance.AllPlayers.ToArray().OrderBy(_ => Guid.NewGuid()).FirstOrDefault(x => VotingData.TryGetValue(x.PlayerId, out int vote) && vote == max); + tie = false; + break; + } + } + else if (!braked) exiledPlayer = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(info => !tie && info.PlayerId == exileId); + + exiledPlayer?.Object.SetRealKiller(null); + + __instance.RpcVotingComplete(states.ToArray(), exiledPlayer, tie); + + Statistics.OnVotingComplete(states.ToArray(), exiledPlayer, tie, false); + + CheckForDeathOnExile(PlayerState.DeathReason.Vote, exileId); + Utils.CheckAndSpawnAdditionalRefugee(exiledPlayer); + + Main.LastVotedPlayerInfo = exiledPlayer; + AntiBlackout.ExilePlayerId = exileId; + + if (Main.LastVotedPlayerInfo != null) + { + AntiBlackout.ExilePlayerId = exileId; + ConfirmEjections(Main.LastVotedPlayerInfo, braked); + } + + if (QuizMaster.On) QuizMaster.Data.NumPlayersVotedLastMeeting = __instance.playerStates.Count(x => x.DidVote); - __instance.HighlightedFX.enabled = value; return false; } - } - - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))] - internal static class MeetingHudOnDestroyPatch - { - public static void Postfix() + catch (Exception ex) { - MeetingStates.FirstMeeting = false; - Logger.Info("------------End of meeting------------", "Phase"); - - if (AmongUsClient.Instance.AmHost) - { - AntiBlackout.SetIsDead(); - RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Clear(); - - Main.LastVotedPlayerInfo = null; - } + Utils.ThrowException(ex); + throw; } } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CastVote))] - internal static class MeetingHudCastVotePatch + // Reference:https://github.com/music-discussion/TownOfHost-TheOtherRoles + private static void ConfirmEjections(NetworkedPlayerInfo exiledPlayer, bool tiebreaker) { - private static readonly Dictionary ShouldCancelVoteList = []; + if (!AmongUsClient.Instance.AmHost) return; - public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPlayerId, [HarmonyArgument(1)] byte suspectPlayerId) + if (exiledPlayer == null) return; + + byte exileId = exiledPlayer.PlayerId; + if (exileId > 254) return; + + string realName = exiledPlayer.Object.GetRealName(true); + + PlayerControl player = Utils.GetPlayerById(exiledPlayer.PlayerId); + CustomRoles crole = exiledPlayer.GetCustomRole(); + string coloredRole = Utils.GetDisplayRoleName(exileId, true, true); + + if (crole == CustomRoles.LovingImpostor && !Options.ConfirmLoversOnEject.GetBool()) { - PlayerVoteArea pva_src = null; - PlayerVoteArea pva_target = null; - var isSkip = false; - - foreach (PlayerVoteArea t in __instance.playerStates) + coloredRole = (Lovers.LovingImpostorRoleForOtherImps.GetValue() switch { - if (t.TargetPlayerId == srcPlayerId) pva_src = t; - - if (t.TargetPlayerId == suspectPlayerId) pva_target = t; - } - - if (pva_src == null) - { - Logger.Error("Src PlayerVoteArea not found", "MeetingHudCastVotePatch.Prefix"); - return true; - } - - if (pva_target == null) - { - //Logger.Warn("Target PlayerVoteArea not found ⇒ Vote treated as a Skip", "MeetingHudCastVotePatch.Prefix"); - isSkip = true; - } - - PlayerControl pc_src = Utils.GetPlayerById(srcPlayerId); - PlayerControl pc_target = Utils.GetPlayerById(suspectPlayerId); - - if (pc_src == null) - { - Logger.Error("Src PlayerControl is null", "MeetingHudCastVotePatch.Prefix"); - return true; - } - - if (pc_target == null) - { - //Logger.Warn("Target PlayerControl is null ⇒ Vote treated as a Skip", "MeetingHudCastVotePatch.Prefix"); - isSkip = true; - } - - var voteCanceled = false; - - if (!Main.DontCancelVoteList.Contains(srcPlayerId) && !isSkip && pc_src.GetCustomRole().CancelsVote() && Main.PlayerStates[srcPlayerId].Role.OnVote(pc_src, pc_target)) - { - ShouldCancelVoteList.TryAdd(srcPlayerId, (__instance, pva_src, pc_src)); - voteCanceled = true; - } - - Logger.Info($"{pc_src.GetNameWithRole().RemoveHtmlTags()} => {(isSkip ? "Skip" : pc_target.GetNameWithRole().RemoveHtmlTags())}{(voteCanceled ? " (Canceled)" : string.Empty)}", "Vote"); - - return isSkip || !voteCanceled; // return false to use the vote as a trigger; skips and invalid votes are never canceled + 0 => CustomRoles.ImpostorEHR, + 1 => Lovers.LovingImpostorRole, + _ => CustomRoles.LovingImpostor + }).ToColoredString(); } - public static void Postfix([HarmonyArgument(0)] byte srcPlayerId) + if (Options.ConfirmEgoistOnEject.GetBool() && player.Is(CustomRoles.Egoist)) coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Egoist), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); + + if (Options.ConfirmLoversOnEject.GetBool() && Main.LoversPlayers.Exists(x => x.PlayerId == player.PlayerId)) coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), GetRoleString("Temp.Blank") + coloredRole.RemoveHtmlTags()); + + if (Options.RascalAppearAsMadmate.GetBool() && player.Is(CustomRoles.Rascal)) coloredRole = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Madmate), GetRoleString("Mad-") + coloredRole.RemoveHtmlTags()); + + var name = string.Empty; + var impnum = 0; + var neutralnum = 0; + + var DecidedWinner = false; + + if (CustomRoles.Bard.RoleExist()) { - if (!ShouldCancelVoteList.TryGetValue(srcPlayerId, out (MeetingHud MeetingHud, PlayerVoteArea SourcePVA, PlayerControl SourcePC) info)) return; + Bard.OnMeetingHudDestroy(ref name); + goto EndOfSession; + } - MeetingHud __instance = info.MeetingHud; - PlayerVoteArea pva_src = info.SourcePVA; - PlayerControl pc_src = info.SourcePC; + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc == exiledPlayer.Object) continue; - try + if (pc.GetCustomRole().IsImpostor()) + impnum++; + else if (Options.MadmateCountMode.GetValue() == 1 && pc.IsMadmate()) + impnum++; + else if (pc.IsNeutralKiller()) neutralnum++; + } + + string coloredRealName = Utils.ColorString(Main.PlayerColors[player.PlayerId], realName); + + switch (Options.CEMode.GetInt()) + { + case 0: + name = string.Format(GetString("PlayerExiled"), coloredRealName); + break; + case 1: + if (player.IsImpostor() || player.Is(CustomRoles.Parasite) || player.Is(CustomRoles.Crewpostor) || player.Is(CustomRoles.Refugee) || player.Is(CustomRoles.Convict)) + name = string.Format(GetString("BelongTo"), coloredRealName, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("TeamImpostor"))); + else if (player.IsCrewmate()) + name = string.Format(GetString("IsGood"), coloredRealName); + else if (player.GetCustomRole().IsNeutral() && !player.Is(CustomRoles.Parasite) && !player.Is(CustomRoles.Refugee) && !player.Is(CustomRoles.Crewpostor) && !player.Is(CustomRoles.Convict)) name = string.Format(GetString("BelongTo"), coloredRealName, Utils.ColorString(new(255, 171, 27, byte.MaxValue), GetString("TeamNeutral"))); + + break; + case 2: + name = string.Format(GetString("PlayerIsRole"), coloredRealName, coloredRole); + + if (Options.ShowTeamNextToRoleNameOnEject.GetBool()) + { + name += " ("; + CustomTeamManager.CustomTeam team = CustomTeamManager.GetCustomTeam(player.PlayerId); + + if (team != null) + name += Utils.ColorString(team.RoleRevealScreenBackgroundColor == "*" || !ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color color) ? Color.yellow : color, team.RoleRevealScreenTitle == "*" ? team.TeamName : team.RoleRevealScreenTitle); + else + { + name += player.GetTeam() switch + { + Team.Impostor => Utils.ColorString(new(255, 25, 25, byte.MaxValue), GetString("TeamImpostor")), + Team.Neutral => Utils.ColorString(new(255, 171, 27, byte.MaxValue), GetString("TeamNeutral")), + Team.Crewmate => Utils.ColorString(new(140, 255, 255, byte.MaxValue), GetString("TeamCrewmate")), + _ => "----" + }; + } + + name += ")"; + } + + break; + } + + if (crole == CustomRoles.Jester) + { + name = string.Format(GetString("ExiledJester"), realName, coloredRole); + DecidedWinner = true; + } + + if (Executioner.CheckExileTarget(exiledPlayer, true)) + { + name = string.Format(GetString("ExiledExeTarget"), realName, coloredRole); + DecidedWinner = true; + } + + if (Main.AllPlayerControls.Any(x => x.Is(CustomRoles.Innocent) && !x.IsAlive() && x.GetRealKiller()?.PlayerId == exileId)) + { + if (!(!Options.InnocentCanWinByImp.GetBool() && crole.IsImpostor())) { - pva_src.UnsetVote(); + if (DecidedWinner) + name += string.Format(GetString("ExiledInnocentTargetAddBelow")); + else + name = string.Format(GetString("ExiledInnocentTargetInOneLine"), realName, coloredRole); + + DecidedWinner = true; } - catch { } + } - __instance.RpcClearVote(pc_src.GetClientId()); + if (tiebreaker) name += $" ({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Brakar), GetString("Brakar"))})"; - pva_src.VotedFor = byte.MaxValue; + if (DecidedWinner) + name += ""; + else if (Options.ShowImpRemainOnEject.GetBool()) + { + name += "\n"; + int actualNeutralnum = neutralnum; + if (!Options.ShowNKRemainOnEject.GetBool()) neutralnum = 0; - ShouldCancelVoteList.Remove(srcPlayerId); + switch (impnum, neutralnum) + { + case (0, 0) when actualNeutralnum == 0 && !Main.AllAlivePlayerControls.Any(x => x.IsConverted()): // Crewmates win + name += GetString("GG"); + break; + case (0, 0) when actualNeutralnum > 0: + name += GetString("IWonderWhatsLeft"); + break; + case (> 0, > 0): // Both imps and neutrals remain + name += impnum switch + { + 1 => $"1 {GetString("RemainingText.ImpSingle")} & ", + 2 => $"2 {GetString("RemainingText.ImpPlural")} & ", + 3 => $"3 {GetString("RemainingText.ImpPlural")} & ", + _ => string.Empty + }; + + if (neutralnum == 1) + name += $"1 {GetString("RemainingText.NKSingle")} {GetString("RemainingText.EjectionSuffix.NKSingle")}"; + else + name += $"{neutralnum} {GetString("RemainingText.NKPlural")} {GetString("RemainingText.EjectionSuffix.NKPlural")}"; + + break; + case (> 0, 0): // Only imps remain + name += impnum switch + { + 1 => GetString("OneImpRemain"), + 2 => GetString("TwoImpRemain"), + 3 => GetString("ThreeImpRemain"), + _ => string.Empty + }; + + break; + case (0, > 0): // Only neutrals remain + if (neutralnum == 1) + name += GetString("OneNeutralRemain"); + else + name += string.Format(GetString("NeutralRemain"), neutralnum); + + break; + } + } + + EndOfSession: + + + name = name.Replace("color=", string.Empty) + ""; + EjectionText = name.Split('\n')[DecidedWinner ? 1 : 0]; + + LateTask.New(() => + { + Main.DoBlockNameChange = true; + + if (GameStates.IsInGame && player != null && !player.Data.Disconnected) + { + exiledPlayer.UpdateName(name, Utils.GetClientById(exiledPlayer.ClientId)); + player.RpcSetName(name); + } + }, 2.5f, "Change Exiled Player Name"); + + LateTask.New(() => + { + if (GameStates.IsInGame && player != null && !player.Data.Disconnected) + { + exiledPlayer.UpdateName(realName, Utils.GetClientById(exiledPlayer.ClientId)); + player.RpcSetName(realName); + Main.DoBlockNameChange = false; + } + }, 11.5f, "Change Exiled Player Name Back"); + } + + public static bool CheckRole(byte id, CustomRoles role) + { + PlayerState s = Main.PlayerStates[id]; + return role.IsAdditionRole() ? s.SubRoles.Contains(role) : s.MainRole == role; + } + + public static void TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason deathReason, params byte[] playerIds) + { + if (playerIds.Length == 0) return; + Logger.Info($"{playerIds.Join(x => Main.AllPlayerNames[x])} - died with the reason: {deathReason}", "TryAddAfterMeetingDeathPlayers"); + byte[] addedIdList = playerIds.Where(playerId => Main.AfterMeetingDeathPlayers.TryAdd(playerId, deathReason)).ToArray(); + CheckForDeathOnExile(deathReason, addedIdList); + } + + private static void CheckForDeathOnExile(PlayerState.DeathReason deathReason, params byte[] playerIds) + { + try + { + if (Witch.PlayerIdList.Count > 0) Witch.OnCheckForEndVoting(deathReason, playerIds); + if (Virus.PlayerIdList.Count > 0) Virus.OnCheckForEndVoting(deathReason, playerIds); + if (deathReason == PlayerState.DeathReason.Vote) Gaslighter.OnExile(playerIds); + if (Wasp.On && deathReason == PlayerState.DeathReason.Vote) Wasp.OnExile(playerIds); + + foreach (byte playerId in playerIds) + { + try + { + byte id = playerId; + + if (CustomRoles.Lovers.IsEnable() && !Main.IsLoversDead && Main.LoversPlayers.Exists(lp => lp.PlayerId == id)) + FixedUpdatePatch.LoversSuicide(playerId, true, true); + + if (Main.PlayerStates.TryGetValue(id, out PlayerState state) && state.SubRoles.Contains(CustomRoles.Avanger)) + RevengeOnExile(playerId /*, deathReason*/); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + } + catch (Exception e) + { + Utils.ThrowException(e); } } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CmdCastVote))] - internal static class MeetingHudCmdCastVotePatch + private static void RevengeOnExile(byte playerId /*, PlayerState.DeathReason deathReason*/) { - public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPlayerId, [HarmonyArgument(1)] byte suspectPlayerId) + PlayerControl player = Utils.GetPlayerById(playerId); + if (player == null) return; + + PlayerControl target = PickRevengeTarget(player /*, deathReason*/); + if (target == null) return; + + TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Revenge, target.PlayerId); + target.SetRealKiller(player); + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} revenged: {target.GetNameWithRole().RemoveHtmlTags()}", "RevengeOnExile"); + } + + private static PlayerControl PickRevengeTarget(PlayerControl exiledplayer /*, PlayerState.DeathReason deathReason*/) + { + List TargetList = []; + TargetList.AddRange(Main.AllAlivePlayerControls.Where(candidate => candidate != exiledplayer && !Main.AfterMeetingDeathPlayers.ContainsKey(candidate.PlayerId))); + + if (TargetList.Count == 0) return null; + + PlayerControl target = TargetList.RandomElement(); + return target; + } +} + +internal static class ExtendedMeetingHud +{ + public static Dictionary CustomCalculateVotes(this MeetingHud __instance) + { + Logger.Info("===The vote counting process begins===", "Vote"); + Dictionary dic = []; + Main.BrakarVoteFor = []; + Collector.CollectorVoteFor = []; + + foreach (PlayerVoteArea ps in __instance.playerStates) { - return MeetingHudCastVotePatch.Prefix(__instance, srcPlayerId, suspectPlayerId); + if (ps == null) continue; + + if (ps.VotedFor is not 252 and not byte.MaxValue and not 254) + { + var VoteNum = 1; + + PlayerControl target = Utils.GetPlayerById(ps.VotedFor); + + if (target != null) + { + if (target.Is(CustomRoles.Zombie) || (target.Is(CustomRoles.Shifter) && !Shifter.CanBeVoted.GetBool())) + VoteNum = 0; + + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Brakar)) + if (!Main.BrakarVoteFor.Contains(target.PlayerId)) + Main.BrakarVoteFor.Add(target.PlayerId); + + Collector.CollectorVotes(target, ps); + } + + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Mayor)) VoteNum += Mayor.MayorAdditionalVote.GetInt(); + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Knighted)) VoteNum += 1; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Glitch) && !Glitch.CanVote.GetBool()) VoteNum = 0; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Shifter) && !Shifter.CanVote.GetBool()) VoteNum = 0; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Vindicator)) VoteNum += Options.VindicatorAdditionalVote.GetInt(); + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.DualPersonality) && Options.DualVotes.GetBool()) VoteNum += VoteNum; + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.TicketsStealer)) VoteNum += (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Options.TicketsPerKill.GetFloat()); + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Pickpocket)) VoteNum += (int)(Main.AllPlayerControls.Count(x => x.GetRealKiller()?.PlayerId == ps.TargetPlayerId) * Pickpocket.VotesPerKill.GetFloat()); + + switch (Main.PlayerStates[ps.TargetPlayerId].Role) + { + case Adventurer { IsEnable: true } av when av.ActiveWeapons.Contains(Adventurer.Weapon.Proxy): + VoteNum++; + break; + case Dad { IsEnable: true } dad when dad.UsingAbilities.Contains(Dad.Ability.GoForMilk): + VoteNum = 0; + break; + } + + if (ps.TargetPlayerId == ps.VotedFor && Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.IsEnable() && MeetingStates.FirstMeeting) VoteNum = 0; + + dic[ps.VotedFor] = !dic.TryGetValue(ps.VotedFor, out int num) ? VoteNum : num + VoteNum; + } } - public static void Postfix([HarmonyArgument(0)] byte srcPlayerId) + return dic; + } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] +internal static class MeetingHudStartPatch +{ + private static void NotifyRoleSkillOnMeetingStart() + { + if (!AmongUsClient.Instance.AmHost) return; + + CheckForEndVotingPatch.RunRoleCode = true; + + List<(string Message, byte TargetID, string Title)> msgToSend = []; + + if (Options.SendRoleDescriptionFirstMeeting.GetBool() && MeetingStates.FirstMeeting) { - MeetingHudCastVotePatch.Postfix(srcPlayerId); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.IsModClient()) continue; + + CustomRoles role = pc.GetCustomRole(); + StringBuilder sb = new(); + StringBuilder titleSb = new(); + StringBuilder settings = new(); + settings.Append(""); + titleSb.Append($"{role.ToColoredString()} {Utils.GetRoleMode(role)}"); + sb.Append(""); + sb.Append(pc.GetRoleInfo(true).TrimStart()); + if (Options.CustomRoleSpawnChances.TryGetValue(role, out StringOptionItem opt)) Utils.ShowChildrenSettings(opt, ref settings, disableColor: false); + + settings.Append(""); + if (role.PetActivatedAbility()) sb.Append($"{GetString("SupportsPetMessage")}"); + + string searchStr = GetString(role.ToString()); + sb.Replace(searchStr, role.ToColoredString()); + sb.Replace(searchStr.ToLower(), role.ToColoredString()); + sb.Append(""); + + foreach (CustomRoles subRole in Main.PlayerStates[pc.PlayerId].SubRoles) + { + sb.Append($"\n\n{subRole.ToColoredString()} {Utils.GetRoleMode(subRole)} {GetString($"{subRole}InfoLong")}"); + string searchSubStr = GetString(subRole.ToString()); + sb.Replace(searchSubStr, subRole.ToColoredString()); + sb.Replace(searchSubStr.ToLower(), subRole.ToColoredString()); + } + + if (settings.Length > 0) AddMsg("\n", pc.PlayerId, settings.ToString()); + + AddMsg(sb.Append("").ToString(), pc.PlayerId, titleSb.ToString()); + } } + + if (msgToSend.Count > 0) LateTask.New(() => { msgToSend.Do(x => Utils.SendMessage(x.Message, x.TargetID, x.Title)); }, 3f, "Skill Description First Meeting"); + + if (Options.MadmateSpawnMode.GetInt() == 2 && CustomRoles.Madmate.IsEnable() && MeetingStates.FirstMeeting) AddMsg(string.Format(GetString("Message.MadmateSelfVoteModeNotify"), GetString("MadmateSpawnMode.SelfVote"))); + + if (CustomRoles.God.RoleExist() && God.NotifyGodAlive.GetBool()) AddMsg(GetString("GodNoticeAlive"), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.God), GetString("GodAliveTitle"))); + + if (MeetingStates.FirstMeeting && CustomRoles.Workaholic.RoleExist() && Workaholic.WorkaholicGiveAdviceAlive.GetBool() && !Workaholic.WorkaholicCannotWinAtDeath.GetBool() /* && !Options.GhostIgnoreTasks.GetBool()*/) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Workaholic)).ToArray()) Workaholic.WorkaholicAlive.Add(pc.PlayerId); + + List workaholicAliveList = []; + workaholicAliveList.AddRange(Workaholic.WorkaholicAlive.Select(whId => Main.AllPlayerNames[whId])); + + string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; + AddMsg(string.Format(GetString("WorkaholicAdviceAlive"), string.Join(separator, workaholicAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Workaholic), GetString("WorkaholicAliveTitle"))); + } + + // Bait Notify + if (MeetingStates.FirstMeeting && CustomRoles.Bait.RoleExist() && Options.BaitNotification.GetBool()) + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Bait)).ToArray()) Main.BaitAlive.Add(pc.PlayerId); + + List baitAliveList = []; + baitAliveList.AddRange(Main.BaitAlive.Select(whId => Main.AllPlayerNames[whId])); + + string separator = TranslationController.Instance.currentLanguage.languageID is SupportedLangs.English or SupportedLangs.Russian ? "], [" : "】, 【"; + AddMsg(string.Format(GetString("BaitAdviceAlive"), string.Join(separator, baitAliveList)), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Bait), GetString("BaitAliveTitle"))); + } + + var MimicMsg = string.Empty; + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (pc.Is(CustomRoles.Mafia) && !pc.IsAlive()) AddMsg(GetString("MafiaDeadMsg"), pc.PlayerId); + + foreach (byte csId in Main.CyberStarDead) + { + if (!Options.ImpKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsImpostor()) continue; + + if (!Options.NeutralKnowCyberStarDead.GetBool() && pc.GetCustomRole().IsNeutral()) continue; + + AddMsg(string.Format(GetString("CyberStarDead"), Main.AllPlayerNames[csId]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.CyberStar), GetString("CyberStarNewsTitle"))); + } + + if (pc != null && Silencer.ForSilencer.Contains(pc.PlayerId)) + { + string playername = pc.GetRealName(); + if (Doppelganger.DoppelVictim.TryGetValue(pc.PlayerId, out string value)) playername = value; + + AddMsg(string.Format(GetString("SilencerDead"), playername, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Silencer), GetString("SilencerKillTitle")))); + } + + if (Detective.DetectiveNotify.TryGetValue(pc.PlayerId, out string value1)) AddMsg(value1, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Detective), GetString("DetectiveNoticeTitle"))); + + if (Main.SleuthMsgs.TryGetValue(pc.PlayerId, out string msg)) AddMsg(msg, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sleuth), GetString("Sleuth"))); + + if (pc.Is(CustomRoles.Mimic) && !pc.IsAlive()) + { + PlayerControl pc1 = pc; + Main.AllAlivePlayerControls.Where(x => x.GetRealKiller()?.PlayerId == pc1.PlayerId).Do(x => MimicMsg += $"\n{x.GetNameWithRole(true)}"); + } + + if (Mortician.MsgToSend.TryGetValue(pc.PlayerId, out string value2)) AddMsg(value2, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mortician), GetString("MorticianCheckTitle"))); + + if (Mediumshiper.ContactPlayer.ContainsValue(pc.PlayerId)) AddMsg(string.Format(GetString("MediumshipNotifySelf"), Main.AllPlayerNames[Mediumshiper.ContactPlayer.FirstOrDefault(x => x.Value == pc.PlayerId).Key], pc.GetAbilityUseLimit()), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); + + if (Mediumshiper.ContactPlayer.ContainsKey(pc.PlayerId) && (!Mediumshiper.OnlyReceiveMsgFromCrew.GetBool() || pc.IsCrewmate())) AddMsg(string.Format(GetString("MediumshipNotifyTarget"), Main.AllPlayerNames[Mediumshiper.ContactPlayer[pc.PlayerId]]), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); + + if (Virus.VirusNotify.TryGetValue(pc.PlayerId, out string value3)) AddMsg(value3, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Virus), GetString("VirusNoticeTitle"))); + + if (Enigma.MsgToSend.TryGetValue(pc.PlayerId, out string value4)) AddMsg(value4, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Enigma), Enigma.MsgToSendTitle[pc.PlayerId])); + + if (QuizMaster.On && QuizMaster.MessagesToSend.TryGetValue(pc.PlayerId, out string value5)) + { + AddMsg(value5, pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.QuizMaster), GetString("QuizMaster.QuestionSample.Title"))); + QuizMaster.QuizMasters.Do(x => AddMsg(value5, x.QuizMasterId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.QuizMaster), GetString("QuizMaster.QuestionSample.Title.Self")))); + } + } + + if (MimicMsg != string.Empty) + { + MimicMsg = GetString("MimicDeadMsg") + "\n" + MimicMsg; + foreach (PlayerControl ipc in Main.AllPlayerControls.Where(x => x.GetCustomRole().IsImpostorTeam()).ToArray()) AddMsg(MimicMsg, ipc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mimic), GetString("MimicMsgTitle"))); + } + + if (msgToSend.Count > 0) LateTask.New(() => msgToSend.Do(x => Utils.SendMessage(x.Message, x.TargetID, x.Title)), 6f, "Meeting Start Notify"); + + Main.CyberStarDead.Clear(); + Express.SpeedNormal.Clear(); + Express.SpeedUp.Clear(); + Detective.DetectiveNotify.Clear(); + Main.SleuthMsgs.Clear(); + Virus.VirusNotify.Clear(); + Mortician.MsgToSend.Clear(); + Enigma.MsgToSend.Clear(); + return; + + void AddMsg(string text, byte sendTo = 255, string title = "") + { + msgToSend.Add((text, sendTo, title)); + } + } + + public static void Prefix( /*MeetingHud __instance*/) + { + Logger.Info("------------Meeting Start------------", "Phase"); + ChatUpdatePatch.DoBlockChat = true; + GameStates.AlreadyDied |= !Utils.IsAllAlive; + Main.AllPlayerControls.Do(x => ReportDeadBodyPatch.WaitReport[x.PlayerId].Clear()); + MeetingStates.MeetingCalled = true; + MeetingStates.MeetingNum++; + } + + public static void Postfix(MeetingHud __instance) + { + SoundManager.Instance.ChangeAmbienceVolume(0f); + + GuessManager.TextTemplate = Object.Instantiate(__instance.playerStates[0].NameText); + GuessManager.TextTemplate.enabled = false; + + PlayerControl seer = PlayerControl.LocalPlayer; + + foreach (PlayerVoteArea pva in __instance.playerStates) + { + PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); + if (target == null) continue; + + bool shouldSeeTargetAddons = seer.PlayerId == target.PlayerId || new[] { seer, target }.All(x => x.Is(Team.Impostor)); + (string, Color) RoleTextData = Utils.GetRoleText(seer.PlayerId, target.PlayerId, seeTargetBetrayalAddons: shouldSeeTargetAddons); + TextMeshPro roleTextMeeting = Object.Instantiate(pva.NameText, pva.NameText.transform, true); + roleTextMeeting.transform.localPosition = new(0f, -0.18f, 0f); + roleTextMeeting.fontSize = 1.4f; + roleTextMeeting.text = RoleTextData.Item1; + if (Main.VisibleTasksCount) roleTextMeeting.text += Utils.GetProgressText(target); + + roleTextMeeting.color = RoleTextData.Item2; + roleTextMeeting.gameObject.name = "RoleTextMeeting"; + roleTextMeeting.enableWordWrapping = false; + + roleTextMeeting.enabled = + target.AmOwner || + (Main.VisibleTasksCount && seer.Data.IsDead && Options.GhostCanSeeOtherRoles.GetBool()) || + (seer.Is(CustomRoles.Mimic) && Main.VisibleTasksCount && target.Data.IsDead && Options.MimicCanSeeDeadRoles.GetBool()) || + (target.Is(CustomRoles.Gravestone) && Main.VisibleTasksCount && target.Data.IsDead) || + (Main.LoversPlayers.TrueForAll(x => x.PlayerId == target.PlayerId || x.PlayerId == seer.PlayerId) && Main.LoversPlayers.Count == 2 && Lovers.LoverKnowRoles.GetBool()) || + (target.Is(CustomRoleTypes.Impostor) && seer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowAlliesRole.GetBool()) || + (target.Is(CustomRoleTypes.Impostor) && seer.IsMadmate() && Options.MadmateKnowWhosImp.GetBool()) || + (target.IsMadmate() && seer.Is(CustomRoleTypes.Impostor) && Options.ImpKnowWhosMadmate.GetBool()) || + (target.Is(CustomRoleTypes.Impostor) && seer.Is(CustomRoles.Crewpostor) && Options.AlliesKnowCrewpostor.GetBool()) || + (target.Is(CustomRoles.Crewpostor) && seer.Is(CustomRoleTypes.Impostor) && Options.CrewpostorKnowsAllies.GetBool()) || + (target.IsMadmate() && seer.IsMadmate() && Options.MadmateKnowWhosMadmate.GetBool()) || + ((target.Is(CustomRoles.Jackal) || target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Recruit)) && (seer.Is(CustomRoles.Sidekick) || seer.Is(CustomRoles.Recruit) || seer.Is(CustomRoles.Jackal))) || + (target.Is(CustomRoles.Workaholic) && Workaholic.WorkaholicVisibleToEveryone.GetBool()) || + (target.Is(CustomRoles.Doctor) && !target.HasEvilAddon() && Options.DoctorVisibleToEveryone.GetBool()) || + (target.Is(CustomRoles.Mayor) && Mayor.MayorRevealWhenDoneTasks.GetBool() && target.GetTaskState().IsTaskFinished) || + (target.Is(CustomRoles.Marshall) && Marshall.CanSeeMarshall(seer) && target.GetTaskState().IsTaskFinished) || + (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.Vote && Options.SeeEjectedRolesInMeeting.GetBool()) || + (CustomTeamManager.AreInSameCustomTeam(target.PlayerId, seer.PlayerId) && CustomTeamManager.IsSettingEnabledForPlayerTeam(target.PlayerId, CTAOption.KnowRoles)) || + Main.PlayerStates.Values.Any(x => x.Role.KnowRole(seer, target)) || + Markseeker.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Markseeker { IsEnable: true, TargetRevealed: true } ms && ms.MarkedId == target.PlayerId) || + seer.IsRevealedPlayer(target) || + (seer.Is(CustomRoles.God) && God.KnowInfo.GetValue() == 2) || + seer.Is(CustomRoles.GM) || + Main.GodMode.Value; + + + if (!seer.Data.IsDead && seer.IsRevealedPlayer(target) && target.Is(CustomRoles.Trickster)) + { + roleTextMeeting.text = Farseer.RandomRole[seer.PlayerId]; + roleTextMeeting.text += Farseer.GetTaskState(); + } + + if (EvilTracker.IsTrackTarget(seer, target) && EvilTracker.CanSeeLastRoomInMeeting) + { + roleTextMeeting.text = EvilTracker.GetArrowAndLastRoom(seer, target); + roleTextMeeting.enabled = true; + } + + if (Scout.IsTrackTarget(seer, target) && Scout.CanSeeLastRoomInMeeting) + { + roleTextMeeting.text = Scout.GetArrowAndLastRoom(seer, target); + roleTextMeeting.enabled = true; + } + + string suffix = Main.PlayerStates[seer.PlayerId].Role.GetSuffix(seer, target, meeting: true); + + if (suffix.Length > 0) + { + if (roleTextMeeting.enabled) + roleTextMeeting.text += "\n"; + else + roleTextMeeting.text = string.Empty; + + roleTextMeeting.text += $"<#ffffff>{suffix}"; + roleTextMeeting.enabled = true; + } + + // Thanks BAU (By D1GQ) - are you happy now? + Transform playerLevel = pva.transform.Find("PlayerLevel"); + Transform levelDisplay = Object.Instantiate(playerLevel, pva.transform); + levelDisplay.localPosition = new(-1.21f, -0.15f, playerLevel.transform.localPosition.z); + levelDisplay.transform.SetSiblingIndex(playerLevel.GetSiblingIndex() + 1); + levelDisplay.gameObject.name = "PlayerId"; + levelDisplay.GetComponent().color = Palette.Purple; + Transform idLabel = levelDisplay.transform.Find("LevelLabel"); + Transform idNumber = levelDisplay.transform.Find("LevelNumber"); + Object.Destroy(idLabel.GetComponent()); + idLabel.GetComponent().text = "ID"; + idNumber.GetComponent().text = pva.TargetPlayerId.ToString(); + idLabel.name = "IdLabel"; + idNumber.name = "IdNumber"; + } + + if (Options.SyncButtonMode.GetBool()) + { + Utils.SendMessage(string.Format(GetString("Message.SyncButtonLeft"), Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount)); + Logger.Info("The ship has " + (Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) + " buttons left", "SyncButtonMode"); + } + + TemplateManager.SendTemplate("OnMeeting", noErr: true); + if (MeetingStates.FirstMeeting) TemplateManager.SendTemplate("OnFirstMeeting", noErr: true); + + if (AmongUsClient.Instance.AmHost) NotifyRoleSkillOnMeetingStart(); + + if (AmongUsClient.Instance.AmHost) + { + LateTask.New(() => + { + foreach (PlayerControl pc in Main.AllPlayerControls) pc.RpcSetNameEx(pc.GetRealName(true)); + + ChatUpdatePatch.DoBlockChat = false; + }, 3f, "SetName To Chat"); + } + + foreach (PlayerVoteArea pva in __instance.playerStates) + { + if (pva == null) continue; + + PlayerControl target = Utils.GetPlayerById(pva.TargetPlayerId); + if (target == null) continue; + + if (seer.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) + { + target.cosmetics.SetNameColor(Color.white); + pva.NameText.color = Color.white; + } + + StringBuilder sb = new(); + + // Name Color Manager + pva.NameText.text = pva.NameText.text.ApplyNameColorData(seer, target, true); + + CustomRoles seerRole = seer.GetCustomRole(); + + if (seer.KnowDeathReason(target)) sb.Append($"({Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), Utils.GetVitalText(target.PlayerId))})"); + + switch (seer.GetCustomRoleTypes()) + { + case CustomRoleTypes.Impostor: + if (target.Is(CustomRoles.Snitch) && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), "★")); + + break; + case CustomRoleTypes.Crewmate: + if (target.Is(CustomRoles.Marshall) && target.GetTaskState().IsTaskFinished) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★")); + + sb.Append(Marshall.GetWarningMark(seer, target)); + break; + } + + if (seer.IsSnitchTarget()) sb.Append(Snitch.GetWarningMark(seer, target)); + + switch (seerRole) + { + case CustomRoles.PlagueBearer when PlagueBearer.IsPlagued(seer.PlayerId, target.PlayerId): + sb.Append($"●"); + break; + case CustomRoles.Arsonist: + if (seer.IsDousedPlayer(target)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Arsonist), "▲")); + + break; + case CustomRoles.Executioner: + sb.Append(Executioner.TargetMark(seer, target)); + break; + case CustomRoles.EvilTracker: + sb.Append(EvilTracker.GetTargetMark(seer, target)); + break; + case CustomRoles.Revolutionist: + if (seer.IsDrawPlayer(target)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Revolutionist), "●")); + + break; + case CustomRoles.Psychic: + if (Psychic.IsRedForPsy(target, seer) && !seer.Data.IsDead) pva.NameText.text = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), pva.NameText.text); + + break; + case CustomRoles.Gamer: + sb.Append(Gamer.TargetMark(seer, target)); + sb.Append(Snitch.GetWarningMark(seer, target)); + break; + case CustomRoles.Scout: + sb.Append(Scout.GetTargetMark(seer, target)); + break; + } + + if (Silencer.ForSilencer.Contains(target.PlayerId)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Silencer), "╳")); + + if (Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId) && (Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId) || seer.Data.IsDead)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lovers), "♥")); + + sb.Append(Witch.GetSpelledMark(target.PlayerId, true)); + sb.Append(Wasp.GetStungMark(target.PlayerId)); + + if (target.Is(CustomRoles.SuperStar) && Options.EveryOneKnowSuperStar.GetBool()) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.SuperStar), "★")); + + if (BallLightning.IsGhost(target)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.BallLightning), "■")); + + if (seer.PlayerId == target.PlayerId && (Medic.InProtect(seer.PlayerId) || Medic.TempMarkProtectedList.Contains(seer.PlayerId)) && Medic.WhoCanSeeProtect.GetInt() is 0 or 2) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); + + if (seer.Is(CustomRoles.Medic) && (Medic.InProtect(target.PlayerId) || Medic.TempMarkProtectedList.Contains(target.PlayerId)) && Medic.WhoCanSeeProtect.GetInt() is 0 or 1) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); + + if (seer.Data.IsDead && Medic.InProtect(target.PlayerId) && !seer.Is(CustomRoles.Medic)) sb.Append(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Medic), " ●")); + + sb.Append(Totocalcio.TargetMark(seer, target)); + sb.Append(Romantic.TargetMark(seer, target)); + sb.Append(Lawyer.LawyerMark(seer, target)); + sb.Append(PlagueDoctor.GetMarkOthers(seer, target)); + sb.Append(Gaslighter.GetMark(seer, target, true)); + + pva.NameText.text += sb.ToString(); + pva.ColorBlindName.transform.localPosition -= new Vector3(1.35f, 0f, 0f); + } + } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))] +internal static class MeetingHudUpdatePatch +{ + private static int BufferTime = 10; + + private static void ClearShootButton(MeetingHud __instance, bool forceAll = false) + { + __instance.playerStates.DoIf( + x => (forceAll || !Main.PlayerStates.TryGetValue(x.TargetPlayerId, out PlayerState ps) || ps.IsDead) && x.transform.FindChild("ShootButton") != null, + x => Object.Destroy(x.transform.FindChild("ShootButton").gameObject)); + } + + public static void Postfix(MeetingHud __instance) + { + try + { + // Meeting Skip with vote counting on keystroke (F6) + if (AmongUsClient.Instance.AmHost && Input.GetKeyDown(KeyCode.F6)) __instance.CheckForEndVoting(); + + if (AmongUsClient.Instance.AmHost && Input.GetMouseButtonUp(1) && Input.GetKey(KeyCode.LeftControl)) + { + __instance.playerStates.DoIf(x => x.HighlightedFX.enabled, x => + { + PlayerControl player = Utils.GetPlayerById(x.TargetPlayerId); + + if (player != null && !player.Data.IsDead) + { + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.Execution; + player.RpcExileV2(); + Main.PlayerStates[player.PlayerId].SetDead(); + Utils.SendMessage(string.Format(GetString("Message.Executed"), player.Data.PlayerName)); + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} was executed by the host", "Execution"); + __instance.CheckForEndVoting(); + } + }); + } + + if (!GameStates.IsVoting && __instance.lastSecond < 1) + { + if (GameObject.Find("ShootButton") != null) ClearShootButton(__instance, true); + + return; + } + + BufferTime--; + + if (BufferTime < 0 && __instance.discussionTimer > 0) + { + BufferTime = 10; + CustomRoles myRole = PlayerControl.LocalPlayer.GetCustomRole(); + + __instance.playerStates.Where(x => (!Main.PlayerStates.TryGetValue(x.TargetPlayerId, out PlayerState ps) || ps.IsDead) && !x.AmDead).Do(x => x.SetDead(x.DidReport, true)); + + switch (myRole) + { + case CustomRoles.NiceGuesser or CustomRoles.EvilGuesser or CustomRoles.Judge or CustomRoles.NiceSwapper or CustomRoles.Councillor or CustomRoles.Guesser when !PlayerControl.LocalPlayer.IsAlive(): + ClearShootButton(__instance, true); + break; + case CustomRoles.Mafia when !PlayerControl.LocalPlayer.IsAlive() && GameObject.Find("ShootButton") == null: + Mafia.CreateJudgeButton(__instance); + break; + } + + ClearShootButton(__instance); + } + } + catch (Exception ex) + { + Logger.Fatal(ex.ToString(), "MeetingHudUpdatePatch.Postfix"); + Logger.Warn("All Players and their info:", "Debug for Fatal Error"); + foreach (PlayerControl pc in Main.AllPlayerControls) Logger.Info($" {(pc.IsAlive() ? "Alive" : $"Dead ({Main.PlayerStates[pc.PlayerId].deathReason})")}, {Utils.GetProgressText(pc)}, {Utils.GetVitalText(pc.PlayerId)}", $"{pc.GetNameWithRole()} / {pc.PlayerId}"); + + Logger.Warn("-----------------", "Debug for Fatal Error"); + Logger.SendInGame("An error occured with this meeting. Please use /dump and send the log to the developer.\nSorry for the inconvenience."); + } + } +} + +[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.SetHighlighted))] +internal static class SetHighlightedPatch +{ + public static bool Prefix(PlayerVoteArea __instance, bool value) + { + if (!AmongUsClient.Instance.AmHost) return true; + + if (!__instance.HighlightedFX) return false; + + __instance.HighlightedFX.enabled = value; + return false; + } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.OnDestroy))] +internal static class MeetingHudOnDestroyPatch +{ + public static void Postfix() + { + MeetingStates.FirstMeeting = false; + Logger.Info("------------End of meeting------------", "Phase"); + + if (AmongUsClient.Instance.AmHost) + { + AntiBlackout.SetIsDead(); + RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Clear(); + + Main.LastVotedPlayerInfo = null; + } + } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CastVote))] +internal static class MeetingHudCastVotePatch +{ + private static readonly Dictionary ShouldCancelVoteList = []; + + public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPlayerId, [HarmonyArgument(1)] byte suspectPlayerId) + { + PlayerVoteArea pva_src = null; + PlayerVoteArea pva_target = null; + var isSkip = false; + + foreach (PlayerVoteArea t in __instance.playerStates) + { + if (t.TargetPlayerId == srcPlayerId) pva_src = t; + + if (t.TargetPlayerId == suspectPlayerId) pva_target = t; + } + + if (pva_src == null) + { + Logger.Error("Src PlayerVoteArea not found", "MeetingHudCastVotePatch.Prefix"); + return true; + } + + if (pva_target == null) + { + //Logger.Warn("Target PlayerVoteArea not found ⇒ Vote treated as a Skip", "MeetingHudCastVotePatch.Prefix"); + isSkip = true; + } + + PlayerControl pc_src = Utils.GetPlayerById(srcPlayerId); + PlayerControl pc_target = Utils.GetPlayerById(suspectPlayerId); + + if (pc_src == null) + { + Logger.Error("Src PlayerControl is null", "MeetingHudCastVotePatch.Prefix"); + return true; + } + + if (pc_target == null) + { + //Logger.Warn("Target PlayerControl is null ⇒ Vote treated as a Skip", "MeetingHudCastVotePatch.Prefix"); + isSkip = true; + } + + var voteCanceled = false; + + if (!Main.DontCancelVoteList.Contains(srcPlayerId) && !isSkip && pc_src.GetCustomRole().CancelsVote() && Main.PlayerStates[srcPlayerId].Role.OnVote(pc_src, pc_target)) + { + ShouldCancelVoteList.TryAdd(srcPlayerId, (__instance, pva_src, pc_src)); + voteCanceled = true; + } + + Logger.Info($"{pc_src.GetNameWithRole().RemoveHtmlTags()} => {(isSkip ? "Skip" : pc_target.GetNameWithRole().RemoveHtmlTags())}{(voteCanceled ? " (Canceled)" : string.Empty)}", "Vote"); + + return isSkip || !voteCanceled; // return false to use the vote as a trigger; skips and invalid votes are never canceled + } + + public static void Postfix([HarmonyArgument(0)] byte srcPlayerId) + { + if (!ShouldCancelVoteList.TryGetValue(srcPlayerId, out (MeetingHud MeetingHud, PlayerVoteArea SourcePVA, PlayerControl SourcePC) info)) return; + + MeetingHud __instance = info.MeetingHud; + PlayerVoteArea pva_src = info.SourcePVA; + PlayerControl pc_src = info.SourcePC; + + try + { + pva_src.UnsetVote(); + } + catch { } + + __instance.RpcClearVote(pc_src.GetClientId()); + + pva_src.VotedFor = byte.MaxValue; + + ShouldCancelVoteList.Remove(srcPlayerId); + } +} + +[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CmdCastVote))] +internal static class MeetingHudCmdCastVotePatch +{ + public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)] byte srcPlayerId, [HarmonyArgument(1)] byte suspectPlayerId) + { + return MeetingHudCastVotePatch.Prefix(__instance, srcPlayerId, suspectPlayerId); + } + + public static void Postfix([HarmonyArgument(0)] byte srcPlayerId) + { + MeetingHudCastVotePatch.Postfix(srcPlayerId); } } \ No newline at end of file diff --git a/Patches/MovingPlatformBehaviourPatch.cs b/Patches/MovingPlatformBehaviourPatch.cs index 6e14ecf36..a16dd8e74 100644 --- a/Patches/MovingPlatformBehaviourPatch.cs +++ b/Patches/MovingPlatformBehaviourPatch.cs @@ -1,52 +1,50 @@ using HarmonyLib; -namespace EHR +namespace EHR; +// https://github.com/tukasa0001/TownOfHost/pull/1274/commits/164d1463e46f0ec453e136c7a2f28a8039cd7fc4 + +[HarmonyPatch(typeof(MovingPlatformBehaviour))] +public static class MovingPlatformBehaviourPatch { - // https://github.com/tukasa0001/TownOfHost/pull/1274/commits/164d1463e46f0ec453e136c7a2f28a8039cd7fc4 + private static bool IsDisabled; - [HarmonyPatch(typeof(MovingPlatformBehaviour))] - public static class MovingPlatformBehaviourPatch + [HarmonyPatch(nameof(MovingPlatformBehaviour.Start))] + [HarmonyPrefix] + public static void StartPrefix(MovingPlatformBehaviour __instance) { - private static bool IsDisabled; + IsDisabled = Options.DisableAirshipMovingPlatform.GetBool(); - [HarmonyPatch(nameof(MovingPlatformBehaviour.Start))] - [HarmonyPrefix] - public static void StartPrefix(MovingPlatformBehaviour __instance) + if (IsDisabled) { - IsDisabled = Options.DisableAirshipMovingPlatform.GetBool(); - - if (IsDisabled) - { - __instance.transform.localPosition = __instance.DisabledPosition; - ShipStatus.Instance.Cast().outOfOrderPlat.SetActive(true); - } - } - - [HarmonyPatch(nameof(MovingPlatformBehaviour.IsDirty), MethodType.Getter)] - [HarmonyPrefix] - public static bool GetIsDirtyPrefix(ref bool __result) - { - if (IsDisabled) - { - __result = false; - return false; - } - - return true; - } - - [HarmonyPatch(nameof(MovingPlatformBehaviour.Use), typeof(PlayerControl))] - [HarmonyPrefix] - public static bool UsePrefix() - { - return !IsDisabled; - } - - [HarmonyPatch(nameof(MovingPlatformBehaviour.SetSide))] - [HarmonyPrefix] - public static bool SetSidePrefix() - { - return !IsDisabled; + __instance.transform.localPosition = __instance.DisabledPosition; + ShipStatus.Instance.Cast().outOfOrderPlat.SetActive(true); } } + + [HarmonyPatch(nameof(MovingPlatformBehaviour.IsDirty), MethodType.Getter)] + [HarmonyPrefix] + public static bool GetIsDirtyPrefix(ref bool __result) + { + if (IsDisabled) + { + __result = false; + return false; + } + + return true; + } + + [HarmonyPatch(nameof(MovingPlatformBehaviour.Use), typeof(PlayerControl))] + [HarmonyPrefix] + public static bool UsePrefix() + { + return !IsDisabled; + } + + [HarmonyPatch(nameof(MovingPlatformBehaviour.SetSide))] + [HarmonyPrefix] + public static bool SetSidePrefix() + { + return !IsDisabled; + } } \ No newline at end of file diff --git a/Patches/NoBlackoutPatch.cs b/Patches/NoBlackoutPatch.cs index b7a41799d..52cf89925 100644 --- a/Patches/NoBlackoutPatch.cs +++ b/Patches/NoBlackoutPatch.cs @@ -1,13 +1,12 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.IsGameOverDueToDeath))] +internal class DontBlackoutPatch { - [HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.IsGameOverDueToDeath))] - internal class DontBlackoutPatch + public static void Postfix(ref bool __result) { - public static void Postfix(ref bool __result) - { - __result = false; - } + __result = false; } } \ No newline at end of file diff --git a/Patches/NotificationPopperPatch.cs b/Patches/NotificationPopperPatch.cs index a4c4f4ad9..24dd5e933 100644 --- a/Patches/NotificationPopperPatch.cs +++ b/Patches/NotificationPopperPatch.cs @@ -3,64 +3,62 @@ using HarmonyLib; using Il2CppSystem; using UnityEngine; -namespace EHR.Patches -{ - // By TommyXL +namespace EHR.Patches; +// By TommyXL - [HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.Awake))] - internal static class NotificationPopperAwakePatch +[HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.Awake))] +internal static class NotificationPopperAwakePatch +{ + public static void Prefix(NotificationPopper __instance) { - public static void Prefix(NotificationPopper __instance) - { - // Don't use ??= because Unity doesn't support null coalescing assignment - NotificationPopperPatch.Instance = __instance; - } + // Don't use ??= because Unity doesn't support null coalescing assignment + NotificationPopperPatch.Instance = __instance; + } +} + +internal static class NotificationPopperPatch +{ + public static NotificationPopper Instance; + + public static void AddSettingsChangeMessage(int index, OptionItem key, bool playSound = false) + { + SendRpc(0, index, playSound: playSound); + + string str = key.Parent != null + ? DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + "") + : DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); + + SettingsChangeMessageLogic(key, str, playSound); } - internal static class NotificationPopperPatch + public static void AddRoleSettingsChangeMessage(int index, OptionItem key, CustomRoles customRole, bool playSound = false) { - public static NotificationPopper Instance; + SendRpc(1, index, customRole, playSound); + string str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); + SettingsChangeMessageLogic(key, str, playSound); + } - public static void AddSettingsChangeMessage(int index, OptionItem key, bool playSound = false) + private static void SettingsChangeMessageLogic(OptionItem key, string item, bool playSound) + { + if (Instance.lastMessageKey == key.Id && Instance.activeMessages.Count > 0) + Instance.activeMessages[^1].UpdateMessage(item); // False error + else { - SendRpc(0, index, playSound: playSound); - - string str = key.Parent != null - ? DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.Parent.GetName() + ": " + key.GetName() + "", "" + key.GetString() + "") - : DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); - - SettingsChangeMessageLogic(key, str, playSound); + Instance.lastMessageKey = key.Id; + LobbyNotificationMessage newMessage = Object.Instantiate(Instance.notificationMessageOrigin, Vector3.zero, Quaternion.identity, Instance.transform); + newMessage.transform.localPosition = new(0f, 0f, -2f); + newMessage.SetUp(item, Instance.settingsChangeSprite, Instance.settingsChangeColor, (Action)(() => Instance.OnMessageDestroy(newMessage))); + Instance.ShiftMessages(); + Instance.AddMessageToQueue(newMessage); } - public static void AddRoleSettingsChangeMessage(int index, OptionItem key, CustomRoles customRole, bool playSound = false) - { - SendRpc(1, index, customRole, playSound); - string str = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotification, "" + key.GetName() + "", "" + key.GetString() + ""); - SettingsChangeMessageLogic(key, str, playSound); - } + if (playSound) SoundManager.Instance.PlaySoundImmediate(Instance.settingsChangeSound, false); + } - private static void SettingsChangeMessageLogic(OptionItem key, string item, bool playSound) - { - if (Instance.lastMessageKey == key.Id && Instance.activeMessages.Count > 0) - Instance.activeMessages[^1].UpdateMessage(item); // False error - else - { - Instance.lastMessageKey = key.Id; - LobbyNotificationMessage newMessage = Object.Instantiate(Instance.notificationMessageOrigin, Vector3.zero, Quaternion.identity, Instance.transform); - newMessage.transform.localPosition = new(0f, 0f, -2f); - newMessage.SetUp(item, Instance.settingsChangeSprite, Instance.settingsChangeColor, (Action)(() => Instance.OnMessageDestroy(newMessage))); - Instance.ShiftMessages(); - Instance.AddMessageToQueue(newMessage); - } + private static void SendRpc(byte typeId, int index, CustomRoles customRole = CustomRoles.NotAssigned, bool playSound = true) + { + if (Options.HideGameSettings.GetBool()) return; - if (playSound) SoundManager.Instance.PlaySoundImmediate(Instance.settingsChangeSound, false); - } - - private static void SendRpc(byte typeId, int index, CustomRoles customRole = CustomRoles.NotAssigned, bool playSound = true) - { - if (Options.HideGameSettings.GetBool()) return; - - Utils.SendRPC(CustomRPC.NotificationPopper, typeId, index, (int)customRole, playSound); - } + Utils.SendRPC(CustomRPC.NotificationPopper, typeId, index, (int)customRole, playSound); } } \ No newline at end of file diff --git a/Patches/OnGameStartedPatch.cs b/Patches/OnGameStartedPatch.cs index a7e41c433..57bed7c5f 100644 --- a/Patches/OnGameStartedPatch.cs +++ b/Patches/OnGameStartedPatch.cs @@ -24,1227 +24,1226 @@ using DateTime = Il2CppSystem.DateTime; using Exception = System.Exception; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGame))] +internal class ChangeRoleSettings { - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGame))] - internal class ChangeRoleSettings + public static bool Prefix(AmongUsClient __instance) { - public static bool Prefix(AmongUsClient __instance) + if (!GameStates.IsLocalGame) return true; + + __instance.StartCoroutine(CoStartGame().WrapToIl2Cpp()); + return false; + + IEnumerator CoStartGame() { - if (!GameStates.IsLocalGame) return true; + AmongUsClient amongUsClient = __instance; + if (DestroyableSingleton.Instance.GameMenu.IsOpen) DestroyableSingleton.Instance.GameMenu.Close(); - __instance.StartCoroutine(CoStartGame().WrapToIl2Cpp()); - return false; + DestroyableSingleton.Instance.Init(); + amongUsClient.logger.Info($"Received game start: {amongUsClient.AmHost}"); + yield return null; + while (!DestroyableSingleton.InstanceExists) yield return null; - IEnumerator CoStartGame() + while (PlayerControl.LocalPlayer == null) yield return null; + + PlayerControl.LocalPlayer.moveable = false; + PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true; + var objectOfType1 = Object.FindObjectOfType(); + if (objectOfType1 != null) objectOfType1.Close(false); + + var objectOfType2 = Object.FindObjectOfType(); + if (objectOfType2 != null) objectOfType2.Close(); + + if (DestroyableSingleton.InstanceExists) { - AmongUsClient amongUsClient = __instance; - if (DestroyableSingleton.Instance.GameMenu.IsOpen) DestroyableSingleton.Instance.GameMenu.Close(); - - DestroyableSingleton.Instance.Init(); - amongUsClient.logger.Info($"Received game start: {amongUsClient.AmHost}"); - yield return null; - while (!DestroyableSingleton.InstanceExists) yield return null; - - while (PlayerControl.LocalPlayer == null) yield return null; - - PlayerControl.LocalPlayer.moveable = false; - PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true; - var objectOfType1 = Object.FindObjectOfType(); - if (objectOfType1 != null) objectOfType1.Close(false); - - var objectOfType2 = Object.FindObjectOfType(); - if (objectOfType2 != null) objectOfType2.Close(); - - if (DestroyableSingleton.InstanceExists) - { - // amongUsClient.DisconnectHandlers.Remove((IDisconnectHandler) DestroyableSingleton.Instance); - Object.Destroy(DestroyableSingleton.Instance.gameObject); - } - - if (DestroyableSingleton.InstanceExists) Object.Destroy(DestroyableSingleton.Instance.gameObject); - - if (DestroyableSingleton.InstanceExists) DestroyableSingleton.Instance.SetPlayingGame(); - - if (!string.IsNullOrEmpty(DataManager.Player.Store.ActiveCosmicube)) - AmongUsClient.Instance.SetActivePodType(DestroyableSingleton.Instance.GetCubeDataByID(DataManager.Player.Store.ActiveCosmicube).podId); - else - { - PlayerStorageManager.CloudPlayerPrefs playerPrefs = DestroyableSingleton.Instance.PlayerPrefs; - AmongUsClient.Instance.SetActivePodType(playerPrefs.ActivePodType); - } - - DestroyableSingleton.Instance.ConfirmationScreen.Cancel(); - DestroyableSingleton.Instance.Ui.Close(true); - DestroyableSingleton.Instance.ReparentUI(); - // CosmeticsCache.ClearUnusedCosmetics(); - yield return DestroyableSingleton.Instance.CoFadeFullScreen(Color.clear, Color.black, showLoader: true); - ++StatsManager.Instance.BanPoints; - StatsManager.Instance.LastGameStarted = DateTime.UtcNow; - - if (amongUsClient.AmHost) - yield return amongUsClient.CoStartGameHost(); - else - { - yield return amongUsClient.CoStartGameClient(); - if (amongUsClient.AmHost) yield return amongUsClient.CoStartGameHost(); - } - - for (var index = 0; index < GameData.Instance.PlayerCount; ++index) - { - PlayerControl player = GameData.Instance.AllPlayers.ToArray()[index].Object; - - if (player != null) - { - player.moveable = true; - player.NetTransform.enabled = true; - player.MyPhysics.enabled = true; - player.MyPhysics.Awake(); - player.MyPhysics.ResetMoveState(); - player.Collider.enabled = true; - ShipStatus.Instance.SpawnPlayer(player, GameData.Instance.PlayerCount, true); - } - } - - DestroyableSingleton.Instance.SetRecentlyPlayed(GameData.Instance.AllPlayers); - GameData.TimeGameStarted = Time.realtimeSinceStartup; - int map = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); - string gameName = GameCode.IntToGameName(AmongUsClient.Instance.GameId); - DestroyableSingleton.Instance.Analytics.StartGame(PlayerControl.LocalPlayer.Data, GameData.Instance.PlayerCount, GameOptionsManager.Instance.CurrentGameOptions.NumImpostors, AmongUsClient.Instance.NetworkMode, (MapNames)map, GameOptionsManager.Instance.CurrentGameOptions.GameMode, gameName, DestroyableSingleton.Instance.CurrentRegion.Name, GameOptionsManager.Instance.CurrentGameOptions, GameData.Instance.AllPlayers); - - try - { - DestroyableSingleton.Instance.StartGame(AmongUsClient.Instance.AmHost, GameData.Instance.PlayerCount, GameOptionsManager.Instance.CurrentGameOptions.NumImpostors, AmongUsClient.Instance.NetworkMode, StatsManager.Instance.GetStat(StringNames.StatsGamesImpostor), StatsManager.Instance.GetStat(StringNames.StatsGamesStarted), StatsManager.Instance.GetStat(StringNames.StatsCrewmateStreak)); - NetworkedPlayerInfo.PlayerOutfit defaultOutfit = PlayerControl.LocalPlayer.Data.DefaultOutfit; - DestroyableSingleton.Instance.StartGameCosmetics(defaultOutfit.ColorId, defaultOutfit.HatId, defaultOutfit.SkinId, defaultOutfit.PetId, defaultOutfit.VisorId, defaultOutfit.NamePlateId); - } - catch { } - - GameDebugCommands.AddCommands(); + // amongUsClient.DisconnectHandlers.Remove((IDisconnectHandler) DestroyableSingleton.Instance); + Object.Destroy(DestroyableSingleton.Instance.gameObject); } - } - public static void Postfix(AmongUsClient __instance) - { + if (DestroyableSingleton.InstanceExists) Object.Destroy(DestroyableSingleton.Instance.gameObject); + + if (DestroyableSingleton.InstanceExists) DestroyableSingleton.Instance.SetPlayingGame(); + + if (!string.IsNullOrEmpty(DataManager.Player.Store.ActiveCosmicube)) + AmongUsClient.Instance.SetActivePodType(DestroyableSingleton.Instance.GetCubeDataByID(DataManager.Player.Store.ActiveCosmicube).podId); + else + { + PlayerStorageManager.CloudPlayerPrefs playerPrefs = DestroyableSingleton.Instance.PlayerPrefs; + AmongUsClient.Instance.SetActivePodType(playerPrefs.ActivePodType); + } + + DestroyableSingleton.Instance.ConfirmationScreen.Cancel(); + DestroyableSingleton.Instance.Ui.Close(true); + DestroyableSingleton.Instance.ReparentUI(); + // CosmeticsCache.ClearUnusedCosmetics(); + yield return DestroyableSingleton.Instance.CoFadeFullScreen(Color.clear, Color.black, showLoader: true); + ++StatsManager.Instance.BanPoints; + StatsManager.Instance.LastGameStarted = DateTime.UtcNow; + + if (amongUsClient.AmHost) + yield return amongUsClient.CoStartGameHost(); + else + { + yield return amongUsClient.CoStartGameClient(); + if (amongUsClient.AmHost) yield return amongUsClient.CoStartGameHost(); + } + + for (var index = 0; index < GameData.Instance.PlayerCount; ++index) + { + PlayerControl player = GameData.Instance.AllPlayers.ToArray()[index].Object; + + if (player != null) + { + player.moveable = true; + player.NetTransform.enabled = true; + player.MyPhysics.enabled = true; + player.MyPhysics.Awake(); + player.MyPhysics.ResetMoveState(); + player.Collider.enabled = true; + ShipStatus.Instance.SpawnPlayer(player, GameData.Instance.PlayerCount, true); + } + } + + DestroyableSingleton.Instance.SetRecentlyPlayed(GameData.Instance.AllPlayers); + GameData.TimeGameStarted = Time.realtimeSinceStartup; + int map = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); + string gameName = GameCode.IntToGameName(AmongUsClient.Instance.GameId); + DestroyableSingleton.Instance.Analytics.StartGame(PlayerControl.LocalPlayer.Data, GameData.Instance.PlayerCount, GameOptionsManager.Instance.CurrentGameOptions.NumImpostors, AmongUsClient.Instance.NetworkMode, (MapNames)map, GameOptionsManager.Instance.CurrentGameOptions.GameMode, gameName, DestroyableSingleton.Instance.CurrentRegion.Name, GameOptionsManager.Instance.CurrentGameOptions, GameData.Instance.AllPlayers); + try { - LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.In_Game); + DestroyableSingleton.Instance.StartGame(AmongUsClient.Instance.AmHost, GameData.Instance.PlayerCount, GameOptionsManager.Instance.CurrentGameOptions.NumImpostors, AmongUsClient.Instance.NetworkMode, StatsManager.Instance.GetStat(StringNames.StatsGamesImpostor), StatsManager.Instance.GetStat(StringNames.StatsGamesStarted), StatsManager.Instance.GetStat(StringNames.StatsCrewmateStreak)); + NetworkedPlayerInfo.PlayerOutfit defaultOutfit = PlayerControl.LocalPlayer.Data.DefaultOutfit; + DestroyableSingleton.Instance.StartGameCosmetics(defaultOutfit.ColorId, defaultOutfit.HatId, defaultOutfit.SkinId, defaultOutfit.PetId, defaultOutfit.VisorId, defaultOutfit.NamePlateId); + } + catch { } + + GameDebugCommands.AddCommands(); + } + } + + public static void Postfix(AmongUsClient __instance) + { + try + { + LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.In_Game); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + + SetUpRoleTextPatch.IsInIntro = true; + + Main.OverrideWelcomeMsg = string.Empty; + + try + { + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); + + if (Options.DisableVanillaRoles.GetBool()) + { + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Noisemaker, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Phantom, 0, 0); + Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Tracker, 0, 0); + } + + if (Main.NormalOptions.MapId > 5) Logger.SendInGame(GetString("UnsupportedMap")); + + Utils.GameStartTimeStamp = Utils.TimeStamp; + + try + { + Main.AllRoleClasses.Do(x => x.Init()); } catch (Exception e) { Utils.ThrowException(e); } - SetUpRoleTextPatch.IsInIntro = true; + Main.PlayerStates = []; - Main.OverrideWelcomeMsg = string.Empty; + Main.AbilityUseLimit = []; + + Main.HasJustStarted = true; + + Main.AllPlayerKillCooldown = []; + Main.AllPlayerSpeed = []; + Main.KillTimers = []; + Main.SleuthMsgs = []; + Main.CyberStarDead = []; + Main.KilledDiseased = []; + Main.KilledAntidote = []; + Main.BaitAlive = []; + Main.DontCancelVoteList = []; + Main.LastEnteredVent = []; + Main.LastEnteredVentLocation = []; + Main.AfterMeetingDeathPlayers = []; + Main.ResetCamPlayerList = []; + Main.ClientIdList = []; + Main.CheckShapeshift = []; + Main.ShapeshiftTarget = []; + Main.LoversPlayers = []; + Main.DiedThisRound = []; + Main.ShieldPlayer = Options.ShieldPersonDiedFirst.GetBool() ? Main.FirstDied : string.Empty; + Main.FirstDied = string.Empty; + Main.MadmateNum = 0; + + Mayor.MayorUsedButtonCount = []; + Paranoia.ParaUsedButtonCount = []; + Mario.MarioVentCount = []; + Cleaner.CleanerBodies = []; + Virus.InfectedBodies = []; + Workaholic.WorkaholicAlive = []; + Virus.VirusNotify = []; + Veteran.VeteranInProtect = []; + Grenadier.GrenadierBlinding = []; + SecurityGuard.BlockSabo = []; + Ventguard.BlockedVents = []; + Grenadier.MadGrenadierBlinding = []; + OverKiller.OverDeadPlayerList = []; + Warlock.WarlockTimer = []; + Arsonist.IsDoused = []; + Revolutionist.IsDraw = []; + Farseer.IsRevealed = []; + Arsonist.ArsonistTimer = []; + Revolutionist.RevolutionistTimer = []; + Revolutionist.RevolutionistStart = []; + Revolutionist.RevolutionistLastTime = []; + Revolutionist.RevolutionistCountdown = []; + TimeMaster.TimeMasterBackTrack = []; + Farseer.FarseerTimer = []; + Warlock.CursedPlayers = []; + Mafia.MafiaRevenged = []; + Warlock.IsCurseAndKill = []; + Warlock.IsCursed = false; + Detective.DetectiveNotify = []; + Provocateur.Provoked = []; + Crusader.ForCrusade = []; + Godfather.GodfatherTarget = byte.MaxValue; + Crewpostor.TasksDone = []; + Express.SpeedNormal = []; + Express.SpeedUp = []; + Messenger.Sent = []; + + ReportDeadBodyPatch.CanReport = []; + SabotageMapPatch.TimerTexts = []; + VentilationSystemDeterioratePatch.LastClosestVent = []; + + Options.UsedButtonCount = 0; + + GameOptionsManager.Instance.currentNormalGameOptions.ConfirmImpostor = false; + Main.RealOptionsData = new(GameOptionsManager.Instance.CurrentGameOptions); + + Main.IntroDestroyed = false; + ShipStatusBeginPatch.RolesIsAssigned = false; + GameEndChecker.Ended = false; + + RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned = []; + + AFKDetector.ShieldedPlayers.Clear(); + + ChatCommands.MutedPlayers.Clear(); + + MeetingTimeManager.Init(); + Main.DefaultCrewmateVision = Main.RealOptionsData.GetFloat(FloatOptionNames.CrewLightMod); + Main.DefaultImpostorVision = Main.RealOptionsData.GetFloat(FloatOptionNames.ImpostorLightMod); + + Main.LastNotifyNames = []; + + CheckForEndVotingPatch.EjectionText = string.Empty; + CoShowIntroPatch.IntroStarted = false; + + Arsonist.CurrentDousingTarget = byte.MaxValue; + Revolutionist.CurrentDrawTarget = byte.MaxValue; + Main.PlayerColors = []; + + RPC.SyncAllPlayerNames(); + RPC.SyncAllClientRealNames(); + + Camouflage.BlockCamouflage = false; + Camouflage.Init(); + + if (AmongUsClient.Instance.AmHost) + { + string[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).Select(p => $"{p.name}").ToArray(); + + if (invalidColor.Length > 0) + { + string msg = GetString("Error.InvalidColor"); + Logger.SendInGame(msg); + msg += "\n" + string.Join(",", invalidColor); + Utils.SendMessage(msg); + Logger.Error(msg, "CoStartGame"); + } + } + + RoleResult = []; + + foreach (PlayerControl target in Main.AllPlayerControls) + { + foreach (PlayerControl seer in Main.AllPlayerControls) + { + (byte, byte) pair = (target.PlayerId, seer.PlayerId); + Main.LastNotifyNames[pair] = target.name; + } + } + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + int colorId = pc.Data.DefaultOutfit.ColorId; + if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); + + Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId); + Main.PlayerColors[pc.PlayerId] = Palette.PlayerColors[colorId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.AllPlayerKillCooldown[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); + ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; + ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; + VentilationSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = 0; + RoleResult[pc.PlayerId] = CustomRoles.NotAssigned; + pc.cosmetics.nameText.text = pc.name; + RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Clear(); + NetworkedPlayerInfo.PlayerOutfit outfit = pc.Data.DefaultOutfit; + Camouflage.PlayerSkins[pc.PlayerId] = new NetworkedPlayerInfo.PlayerOutfit().Set(outfit.PlayerName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId, outfit.NamePlateId); + Main.ClientIdList.Add(pc.GetClientId()); + } + + Main.VisibleTasksCount = true; + + if (__instance.AmHost) + { + RPC.SyncCustomSettingsRPC(); + Main.RefixCooldownDelay = 0; + } + + FallFromLadder.Reset(); try { - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.GuardianAngel, 0, 0); - - if (Options.DisableVanillaRoles.GetBool()) - { - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Scientist, 0, 0); - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Engineer, 0, 0); - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Shapeshifter, 0, 0); - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Noisemaker, 0, 0); - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Phantom, 0, 0); - Main.NormalOptions.roleOptions.SetRoleRate(RoleTypes.Tracker, 0, 0); - } - - if (Main.NormalOptions.MapId > 5) Logger.SendInGame(GetString("UnsupportedMap")); - - Utils.GameStartTimeStamp = Utils.TimeStamp; - - try - { - Main.AllRoleClasses.Do(x => x.Init()); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - - Main.PlayerStates = []; - - Main.AbilityUseLimit = []; - - Main.HasJustStarted = true; - - Main.AllPlayerKillCooldown = []; - Main.AllPlayerSpeed = []; - Main.KillTimers = []; - Main.SleuthMsgs = []; - Main.CyberStarDead = []; - Main.KilledDiseased = []; - Main.KilledAntidote = []; - Main.BaitAlive = []; - Main.DontCancelVoteList = []; - Main.LastEnteredVent = []; - Main.LastEnteredVentLocation = []; - Main.AfterMeetingDeathPlayers = []; - Main.ResetCamPlayerList = []; - Main.ClientIdList = []; - Main.CheckShapeshift = []; - Main.ShapeshiftTarget = []; - Main.LoversPlayers = []; - Main.DiedThisRound = []; - Main.ShieldPlayer = Options.ShieldPersonDiedFirst.GetBool() ? Main.FirstDied : string.Empty; - Main.FirstDied = string.Empty; - Main.MadmateNum = 0; - - Mayor.MayorUsedButtonCount = []; - Paranoia.ParaUsedButtonCount = []; - Mario.MarioVentCount = []; - Cleaner.CleanerBodies = []; - Virus.InfectedBodies = []; - Workaholic.WorkaholicAlive = []; - Virus.VirusNotify = []; - Veteran.VeteranInProtect = []; - Grenadier.GrenadierBlinding = []; - SecurityGuard.BlockSabo = []; - Ventguard.BlockedVents = []; - Grenadier.MadGrenadierBlinding = []; - OverKiller.OverDeadPlayerList = []; - Warlock.WarlockTimer = []; - Arsonist.IsDoused = []; - Revolutionist.IsDraw = []; - Farseer.IsRevealed = []; - Arsonist.ArsonistTimer = []; - Revolutionist.RevolutionistTimer = []; - Revolutionist.RevolutionistStart = []; - Revolutionist.RevolutionistLastTime = []; - Revolutionist.RevolutionistCountdown = []; - TimeMaster.TimeMasterBackTrack = []; - Farseer.FarseerTimer = []; - Warlock.CursedPlayers = []; - Mafia.MafiaRevenged = []; - Warlock.IsCurseAndKill = []; - Warlock.IsCursed = false; - Detective.DetectiveNotify = []; - Provocateur.Provoked = []; - Crusader.ForCrusade = []; - Godfather.GodfatherTarget = byte.MaxValue; - Crewpostor.TasksDone = []; - Express.SpeedNormal = []; - Express.SpeedUp = []; - Messenger.Sent = []; - - ReportDeadBodyPatch.CanReport = []; - SabotageMapPatch.TimerTexts = []; - VentilationSystemDeterioratePatch.LastClosestVent = []; - - Options.UsedButtonCount = 0; - - GameOptionsManager.Instance.currentNormalGameOptions.ConfirmImpostor = false; - Main.RealOptionsData = new(GameOptionsManager.Instance.CurrentGameOptions); - - Main.IntroDestroyed = false; - ShipStatusBeginPatch.RolesIsAssigned = false; - GameEndChecker.Ended = false; - - RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned = []; - - AFKDetector.ShieldedPlayers.Clear(); - - ChatCommands.MutedPlayers.Clear(); - - MeetingTimeManager.Init(); - Main.DefaultCrewmateVision = Main.RealOptionsData.GetFloat(FloatOptionNames.CrewLightMod); - Main.DefaultImpostorVision = Main.RealOptionsData.GetFloat(FloatOptionNames.ImpostorLightMod); - - Main.LastNotifyNames = []; - - CheckForEndVotingPatch.EjectionText = string.Empty; - CoShowIntroPatch.IntroStarted = false; - - Arsonist.CurrentDousingTarget = byte.MaxValue; - Revolutionist.CurrentDrawTarget = byte.MaxValue; - Main.PlayerColors = []; - - RPC.SyncAllPlayerNames(); - RPC.SyncAllClientRealNames(); - - Camouflage.BlockCamouflage = false; - Camouflage.Init(); - - if (AmongUsClient.Instance.AmHost) - { - string[] invalidColor = Main.AllPlayerControls.Where(p => p.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= p.Data.DefaultOutfit.ColorId).Select(p => $"{p.name}").ToArray(); - - if (invalidColor.Length > 0) - { - string msg = GetString("Error.InvalidColor"); - Logger.SendInGame(msg); - msg += "\n" + string.Join(",", invalidColor); - Utils.SendMessage(msg); - Logger.Error(msg, "CoStartGame"); - } - } - - RoleResult = []; - - foreach (PlayerControl target in Main.AllPlayerControls) - { - foreach (PlayerControl seer in Main.AllPlayerControls) - { - (byte, byte) pair = (target.PlayerId, seer.PlayerId); - Main.LastNotifyNames[pair] = target.name; - } - } - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - int colorId = pc.Data.DefaultOutfit.ColorId; - if (AmongUsClient.Instance.AmHost && Options.FormatNameMode.GetInt() == 1) pc.RpcSetName(Palette.GetColorName(colorId)); - - Main.PlayerStates[pc.PlayerId] = new(pc.PlayerId); - Main.PlayerColors[pc.PlayerId] = Palette.PlayerColors[colorId]; - Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - Main.AllPlayerKillCooldown[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); - ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; - ReportDeadBodyPatch.WaitReport[pc.PlayerId] = []; - VentilationSystemDeterioratePatch.LastClosestVent[pc.PlayerId] = 0; - RoleResult[pc.PlayerId] = CustomRoles.NotAssigned; - pc.cosmetics.nameText.text = pc.name; - RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Clear(); - NetworkedPlayerInfo.PlayerOutfit outfit = pc.Data.DefaultOutfit; - Camouflage.PlayerSkins[pc.PlayerId] = new NetworkedPlayerInfo.PlayerOutfit().Set(outfit.PlayerName, outfit.ColorId, outfit.HatId, outfit.SkinId, outfit.VisorId, outfit.PetId, outfit.NamePlateId); - Main.ClientIdList.Add(pc.GetClientId()); - } - - Main.VisibleTasksCount = true; - - if (__instance.AmHost) - { - RPC.SyncCustomSettingsRPC(); - Main.RefixCooldownDelay = 0; - } - - FallFromLadder.Reset(); - - try - { - LastImpostor.Init(); - TargetArrow.Init(); - LocateArrow.Init(); - DoubleTrigger.Init(); - Workhorse.Init(); - Damocles.Initialize(); - Stressed.Init(); - Asthmatic.Init(); - DoubleShot.Init(); - Circumvent.Init(); - } - catch (Exception ex) - { - Logger.Exception(ex, "Init Roles"); - } - - Main.ChangedRole = false; - - try - { - SoloKombatManager.Init(); - FFAManager.Init(); - MoveAndStop.Init(); - HotPotato.Init(); - HnSManager.Init(); - SpeedrunManager.Init(); - AllInOneGameMode.Init(); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - - CustomWinnerHolder.Reset(); - AntiBlackout.Reset(); - NameNotifyManager.Reset(); - SabotageSystemTypeRepairDamagePatch.Initialize(); - DoorsReset.Initialize(); - GhostRolesManager.Initialize(); - RoleBlockManager.Reset(); - ChatManager.ResetHistory(); - CustomNetObject.Reset(); - - IRandom.SetInstanceById(Options.RoleAssigningAlgorithm.GetValue()); - - MeetingStates.MeetingNum = 0; - MeetingStates.MeetingCalled = false; - MeetingStates.FirstMeeting = true; - GameStates.AlreadyDied = false; + LastImpostor.Init(); + TargetArrow.Init(); + LocateArrow.Init(); + DoubleTrigger.Init(); + Workhorse.Init(); + Damocles.Initialize(); + Stressed.Init(); + Asthmatic.Init(); + DoubleShot.Init(); + Circumvent.Init(); } catch (Exception ex) { - Utils.ErrorEnd("Change Role Setting Postfix"); - Utils.ThrowException(ex); + Logger.Exception(ex, "Init Roles"); } + + Main.ChangedRole = false; + + try + { + SoloKombatManager.Init(); + FFAManager.Init(); + MoveAndStop.Init(); + HotPotato.Init(); + HnSManager.Init(); + SpeedrunManager.Init(); + AllInOneGameMode.Init(); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + + CustomWinnerHolder.Reset(); + AntiBlackout.Reset(); + NameNotifyManager.Reset(); + SabotageSystemTypeRepairDamagePatch.Initialize(); + DoorsReset.Initialize(); + GhostRolesManager.Initialize(); + RoleBlockManager.Reset(); + ChatManager.ResetHistory(); + CustomNetObject.Reset(); + + IRandom.SetInstanceById(Options.RoleAssigningAlgorithm.GetValue()); + + MeetingStates.MeetingNum = 0; + MeetingStates.MeetingCalled = false; + MeetingStates.FirstMeeting = true; + GameStates.AlreadyDied = false; + } + catch (Exception ex) + { + Utils.ErrorEnd("Change Role Setting Postfix"); + Utils.ThrowException(ex); } } +} - [HarmonyPatch] - internal static class StartGameHostPatch +[HarmonyPatch] +internal static class StartGameHostPatch +{ + private static AmongUsClient AUClient; + + public static readonly Dictionary> BasisChangingAddons = []; + private static Dictionary RoleTypeNums = []; + + public static readonly Dictionary DataDisconnected = []; + + private static RoleOptionsCollectionV08 RoleOpt => Main.NormalOptions.roleOptions; + + private static void UpdateRoleTypeNums() { - private static AmongUsClient AUClient; - - public static readonly Dictionary> BasisChangingAddons = []; - private static Dictionary RoleTypeNums = []; - - public static readonly Dictionary DataDisconnected = []; - - private static RoleOptionsCollectionV08 RoleOpt => Main.NormalOptions.roleOptions; - - private static void UpdateRoleTypeNums() + RoleTypeNums = new() { - RoleTypeNums = new() - { - { RoleTypes.Scientist, AddScientistNum }, - { RoleTypes.Engineer, AddEngineerNum }, - { RoleTypes.Shapeshifter, AddShapeshifterNum }, - { RoleTypes.Noisemaker, AddNoisemakerNum }, - { RoleTypes.Phantom, AddPhantomNum }, - { RoleTypes.Tracker, AddTrackerNum } - }; + { RoleTypes.Scientist, AddScientistNum }, + { RoleTypes.Engineer, AddEngineerNum }, + { RoleTypes.Shapeshifter, AddShapeshifterNum }, + { RoleTypes.Noisemaker, AddNoisemakerNum }, + { RoleTypes.Phantom, AddPhantomNum }, + { RoleTypes.Tracker, AddTrackerNum } + }; + } + + [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGameHost))] + [HarmonyPrefix] + public static bool CoStartGameHost_Prefix(AmongUsClient __instance, ref IEnumerator __result) + { + AUClient = __instance; + __result = StartGameHost().WrapToIl2Cpp(); + return false; + } + + private static System.Collections.IEnumerator StartGameHost() + { + if (LobbyBehaviour.Instance) LobbyBehaviour.Instance.Despawn(); + + if (!ShipStatus.Instance) + { + int num = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); + AUClient.ShipLoadingAsyncHandle = AUClient.ShipPrefabs.ToArray()[num].InstantiateAsync(); + yield return AUClient.ShipLoadingAsyncHandle; + GameObject result = AUClient.ShipLoadingAsyncHandle.Result; + ShipStatus.Instance = result.GetComponent(); + AUClient.Spawn(ShipStatus.Instance); } - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoStartGameHost))] - [HarmonyPrefix] - public static bool CoStartGameHost_Prefix(AmongUsClient __instance, ref IEnumerator __result) - { - AUClient = __instance; - __result = StartGameHost().WrapToIl2Cpp(); - return false; - } + var timer = 0f; - private static System.Collections.IEnumerator StartGameHost() + while (true) { - if (LobbyBehaviour.Instance) LobbyBehaviour.Instance.Despawn(); + var stopWaiting = true; + int maxTimer = GameOptionsManager.Instance.CurrentGameOptions.MapId is 5 or 4 ? 17 : 12; - if (!ShipStatus.Instance) + lock (AUClient.allClients) { - int num = Mathf.Clamp(GameOptionsManager.Instance.CurrentGameOptions.MapId, 0, Constants.MapNames.Length - 1); - AUClient.ShipLoadingAsyncHandle = AUClient.ShipPrefabs.ToArray()[num].InstantiateAsync(); - yield return AUClient.ShipLoadingAsyncHandle; - GameObject result = AUClient.ShipLoadingAsyncHandle.Result; - ShipStatus.Instance = result.GetComponent(); - AUClient.Spawn(ShipStatus.Instance); + // For loop is necessary, or else when a client times out, a foreach loop will throw: + // System.InvalidOperationException: Collection was modified; enumeration operation may not execute. + for (var i = 0; i < AUClient.allClients.Count; i++) + { + ClientData clientData = AUClient.allClients[i]; // False error + + if (clientData.Id != AUClient.ClientId && !clientData.IsReady) + { + if (timer < maxTimer) + stopWaiting = false; + else + { + AUClient.SendLateRejection(clientData.Id, DisconnectReasons.ClientTimeout); + clientData.IsReady = true; + AUClient.OnPlayerLeft(clientData, DisconnectReasons.ClientTimeout); + } + } + } } - var timer = 0f; + yield return null; + if (stopWaiting) break; - while (true) + timer += Time.deltaTime; + } + + AUClient.SendClientReady(); + yield return new WaitForSeconds(2f); + yield return AssignRoles(); + } + + private static System.Collections.IEnumerator AssignRoles() + { + if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended) yield break; + + RpcSetRoleReplacer.Initialize(); + + SelectCustomRoles(); + SelectAddonRoles(); + CalculateVanillaRoleCount(); + + UpdateRoleTypeNums(); + + foreach (KeyValuePair roleType in RoleTypeNums) + { + int roleNum = Options.DisableVanillaRoles.GetBool() ? 0 : RoleOpt.GetNumPerGame(roleType.Key); + roleNum += roleType.Value; + RoleOpt.SetRoleRate(roleType.Key, roleNum, roleType.Value > 0 ? 100 : RoleOpt.GetChancePerGame(roleType.Key)); + } + + Statistics.OnRoleSelectionComplete(); + + try + { + #region BasisChangingAddonsSetup + + BasisChangingAddons.Clear(); + + var random = IRandom.Instance; + + if (CustomGameMode.Standard.IsActiveOrIntegrated()) { - var stopWaiting = true; - int maxTimer = GameOptionsManager.Instance.CurrentGameOptions.MapId is 5 or 4 ? 17 : 12; + bool bloodlustSpawn = random.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Bloodlust, out IntegerOptionItem option0) ? option0.GetFloat() : 0) && CustomRoles.Bloodlust.IsEnable(); + bool physicistSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Physicist, out IntegerOptionItem option1) ? option1.GetFloat() : 0) && CustomRoles.Physicist.IsEnable(); + bool nimbleSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Nimble, out IntegerOptionItem option2) ? option2.GetFloat() : 0) && CustomRoles.Nimble.IsEnable(); + bool finderSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Finder, out IntegerOptionItem option3) ? option3.GetFloat() : 0) && CustomRoles.Finder.IsEnable(); + bool noisySpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Noisy, out IntegerOptionItem option4) ? option4.GetFloat() : 0) && CustomRoles.Noisy.IsEnable(); - lock (AUClient.allClients) + if (Options.EveryoneCanVent.GetBool()) { - // For loop is necessary, or else when a client times out, a foreach loop will throw: - // System.InvalidOperationException: Collection was modified; enumeration operation may not execute. - for (var i = 0; i < AUClient.allClients.Count; i++) - { - ClientData clientData = AUClient.allClients[i]; // False error + nimbleSpawn = false; + physicistSpawn = false; + finderSpawn = false; + noisySpawn = false; + } - if (clientData.Id != AUClient.ClientId && !clientData.IsReady) + HashSet bloodlustList = [], nimbleList = [], physicistList = [], finderList = [], noisyList = []; + bool hasBanned = Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> banned); + + if (nimbleSpawn || physicistSpawn || finderSpawn || noisySpawn) + { + foreach (PlayerControl player in Main.AllPlayerControls) + { + if (IsBasisChangingPlayer(player.PlayerId, CustomRoles.Bloodlust)) continue; + + KeyValuePair kp = RoleResult.FirstOrDefault(x => x.Key == player.PlayerId); + + bool bloodlustBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Bloodlust)); + bool nimbleBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Nimble)); + bool physicistBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Physicist)); + bool finderBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Finder)); + bool noisyBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Noisy)); + + if (kp.Value.IsCrewmate()) { - if (timer < maxTimer) - stopWaiting = false; - else + if (!bloodlustBanned && !kp.Value.IsTaskBasedCrewmate()) bloodlustList.Add(player.PlayerId); + + if (!nimbleBanned) nimbleList.Add(player.PlayerId); + + if (kp.Value.GetRoleTypes() == RoleTypes.Crewmate) { - AUClient.SendLateRejection(clientData.Id, DisconnectReasons.ClientTimeout); - clientData.IsReady = true; - AUClient.OnPlayerLeft(clientData, DisconnectReasons.ClientTimeout); + if (!physicistBanned) physicistList.Add(player.PlayerId); + + if (!finderBanned) finderList.Add(player.PlayerId); + + if (!noisyBanned) noisyList.Add(player.PlayerId); } } } } - yield return null; - if (stopWaiting) break; - - timer += Time.deltaTime; - } - - AUClient.SendClientReady(); - yield return new WaitForSeconds(2f); - yield return AssignRoles(); - } - - private static System.Collections.IEnumerator AssignRoles() - { - if (AmongUsClient.Instance.IsGameOver || GameStates.IsLobby || GameEndChecker.Ended) yield break; - - RpcSetRoleReplacer.Initialize(); - - SelectCustomRoles(); - SelectAddonRoles(); - CalculateVanillaRoleCount(); - - UpdateRoleTypeNums(); - - foreach (KeyValuePair roleType in RoleTypeNums) - { - int roleNum = Options.DisableVanillaRoles.GetBool() ? 0 : RoleOpt.GetNumPerGame(roleType.Key); - roleNum += roleType.Value; - RoleOpt.SetRoleRate(roleType.Key, roleNum, roleType.Value > 0 ? 100 : RoleOpt.GetChancePerGame(roleType.Key)); - } - - Statistics.OnRoleSelectionComplete(); - - try - { - #region BasisChangingAddonsSetup - - BasisChangingAddons.Clear(); - - var random = IRandom.Instance; - - if (CustomGameMode.Standard.IsActiveOrIntegrated()) + Dictionary RoleList)> roleSpawnMapping = new() { - bool bloodlustSpawn = random.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Bloodlust, out IntegerOptionItem option0) ? option0.GetFloat() : 0) && CustomRoles.Bloodlust.IsEnable(); - bool physicistSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Physicist, out IntegerOptionItem option1) ? option1.GetFloat() : 0) && CustomRoles.Physicist.IsEnable(); - bool nimbleSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Nimble, out IntegerOptionItem option2) ? option2.GetFloat() : 0) && CustomRoles.Nimble.IsEnable(); - bool finderSpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Finder, out IntegerOptionItem option3) ? option3.GetFloat() : 0) && CustomRoles.Finder.IsEnable(); - bool noisySpawn = random.Next(100) < (Options.CustomAdtRoleSpawnRate.TryGetValue(CustomRoles.Noisy, out IntegerOptionItem option4) ? option4.GetFloat() : 0) && CustomRoles.Noisy.IsEnable(); + { CustomRoles.Bloodlust, (bloodlustSpawn, bloodlustList) }, + { CustomRoles.Nimble, (nimbleSpawn, nimbleList) }, + { CustomRoles.Physicist, (physicistSpawn, physicistList) }, + { CustomRoles.Finder, (finderSpawn, finderList) }, + { CustomRoles.Noisy, (noisySpawn, noisyList) } + }; - if (Options.EveryoneCanVent.GetBool()) + for (var i = 0; i < roleSpawnMapping.Count; i++) + { + (CustomRoles addon, (bool SpawnFlag, HashSet RoleList) value) = roleSpawnMapping.ElementAt(i); + if (value.RoleList.Count == 0) value.SpawnFlag = false; + + if (Main.GM.Value) value.RoleList.Remove(0); + + if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> combos) && combos.Values.Any(l => l.Contains(addon))) { - nimbleSpawn = false; - physicistSpawn = false; - finderSpawn = false; - noisySpawn = false; - } + HashSet roles = combos.Where(x => x.Value.Contains(addon)).Select(x => x.Key).ToHashSet(); + HashSet players = RoleResult.Where(x => roles.Contains(x.Value) && x.Value.IsCrewmate() && (addon != CustomRoles.Bloodlust || x.Value.IsTasklessCrewmate()) && (addon == CustomRoles.Nimble || x.Value.GetRoleTypes() == RoleTypes.Crewmate) && !IsBasisChangingPlayer(x.Key, CustomRoles.Bloodlust)).Select(x => x.Key).ToHashSet(); - HashSet bloodlustList = [], nimbleList = [], physicistList = [], finderList = [], noisyList = []; - bool hasBanned = Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> banned); - - if (nimbleSpawn || physicistSpawn || finderSpawn || noisySpawn) - { - foreach (PlayerControl player in Main.AllPlayerControls) - { - if (IsBasisChangingPlayer(player.PlayerId, CustomRoles.Bloodlust)) continue; - - KeyValuePair kp = RoleResult.FirstOrDefault(x => x.Key == player.PlayerId); - - bool bloodlustBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Bloodlust)); - bool nimbleBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Nimble)); - bool physicistBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Physicist)); - bool finderBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Finder)); - bool noisyBanned = hasBanned && banned.Any(x => x.Key == kp.Value && x.Value.Contains(CustomRoles.Noisy)); - - if (kp.Value.IsCrewmate()) - { - if (!bloodlustBanned && !kp.Value.IsTaskBasedCrewmate()) bloodlustList.Add(player.PlayerId); - - if (!nimbleBanned) nimbleList.Add(player.PlayerId); - - if (kp.Value.GetRoleTypes() == RoleTypes.Crewmate) - { - if (!physicistBanned) physicistList.Add(player.PlayerId); - - if (!finderBanned) finderList.Add(player.PlayerId); - - if (!noisyBanned) noisyList.Add(player.PlayerId); - } - } - } - } - - Dictionary RoleList)> roleSpawnMapping = new() - { - { CustomRoles.Bloodlust, (bloodlustSpawn, bloodlustList) }, - { CustomRoles.Nimble, (nimbleSpawn, nimbleList) }, - { CustomRoles.Physicist, (physicistSpawn, physicistList) }, - { CustomRoles.Finder, (finderSpawn, finderList) }, - { CustomRoles.Noisy, (noisySpawn, noisyList) } - }; - - for (var i = 0; i < roleSpawnMapping.Count; i++) - { - (CustomRoles addon, (bool SpawnFlag, HashSet RoleList) value) = roleSpawnMapping.ElementAt(i); - if (value.RoleList.Count == 0) value.SpawnFlag = false; - - if (Main.GM.Value) value.RoleList.Remove(0); - - if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> combos) && combos.Values.Any(l => l.Contains(addon))) - { - HashSet roles = combos.Where(x => x.Value.Contains(addon)).Select(x => x.Key).ToHashSet(); - HashSet players = RoleResult.Where(x => roles.Contains(x.Value) && x.Value.IsCrewmate() && (addon != CustomRoles.Bloodlust || x.Value.IsTasklessCrewmate()) && (addon == CustomRoles.Nimble || x.Value.GetRoleTypes() == RoleTypes.Crewmate) && !IsBasisChangingPlayer(x.Key, CustomRoles.Bloodlust)).Select(x => x.Key).ToHashSet(); - - if (players.Count > 0) - { - value.RoleList = players; - value.SpawnFlag = true; - roleSpawnMapping[addon] = value; - } - } - - if (Main.SetAddOns.Values.Any(x => x.Contains(addon))) + if (players.Count > 0) { + value.RoleList = players; value.SpawnFlag = true; - HashSet newRoleList = Main.SetAddOns.Where(x => x.Value.Contains(addon)).Select(x => x.Key).ToHashSet(); - if (value.RoleList.Count != 1 || value.RoleList.First() != newRoleList.First()) value.RoleList = newRoleList; - roleSpawnMapping[addon] = value; } } - foreach ((CustomRoles addon, (bool spawnFlag, HashSet roleList)) in roleSpawnMapping) + if (Main.SetAddOns.Values.Any(x => x.Contains(addon))) { - if (spawnFlag) - { - foreach ((CustomRoles otherAddon, (bool otherSpawnFlag, _)) in roleSpawnMapping) - if (otherAddon != addon && otherSpawnFlag && BasisChangingAddons.TryGetValue(otherAddon, out List otherList)) - roleList.ExceptWith(otherList); + value.SpawnFlag = true; + HashSet newRoleList = Main.SetAddOns.Where(x => x.Value.Contains(addon)).Select(x => x.Key).ToHashSet(); + if (value.RoleList.Count != 1 || value.RoleList.First() != newRoleList.First()) value.RoleList = newRoleList; - BasisChangingAddons[addon] = roleList.Shuffle().Take(addon.GetCount()).ToList(); - } + roleSpawnMapping[addon] = value; } } - #endregion - - // Start CustomRpcSender - RpcSetRoleReplacer.StartReplace(); - - // Assign roles and create role maps for desync roles - RpcSetRoleReplacer.AssignDesyncRoles(); - RpcSetRoleReplacer.SendRpcForDesync(); - - // Assign roles and create role maps for normal roles - RpcSetRoleReplacer.AssignNormalRoles(); - RpcSetRoleReplacer.SendRpcForNormal(); - - // Send all RPCs - RpcSetRoleReplacer.Release(); - - foreach (PlayerControl pc in Main.AllPlayerControls) + foreach ((CustomRoles addon, (bool spawnFlag, HashSet roleList)) in roleSpawnMapping) { - if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; - - CustomRoles role = Enum.TryParse($"{pc.Data.Role.Role}EHR", out CustomRoles parsedRole) ? parsedRole : CustomRoles.NotAssigned; - if (role == CustomRoles.NotAssigned) Logger.SendInGame(string.Format(GetString("Error.InvalidRoleAssignment"), pc?.Data?.PlayerName)); - - Main.PlayerStates[pc.PlayerId].SetMainRole(role); - } - - foreach (KeyValuePair kv in RoleResult) - { - if (kv.Value.IsDesyncRole() || IsBasisChangingPlayer(kv.Key, CustomRoles.Bloodlust)) continue; - - Main.PlayerStates[kv.Key].SetMainRole(kv.Value); - } - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) - { - foreach (KeyValuePair pair in Main.PlayerStates) ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); - - goto EndOfSelectRolePatch; - } - - BasisChangingAddons.Do(x => x.Value.Do(y => Main.PlayerStates[y].SetSubRole(x.Key))); - - var overrideLovers = false; - - if (Main.SetAddOns.Count(x => x.Value.Contains(CustomRoles.Lovers)) == 2) - { - Main.LoversPlayers.Clear(); - Main.IsLoversDead = false; - overrideLovers = true; - Logger.Warn("Lovers overridden by host's pre-set add-ons", "CustomRoleSelector"); - } - - foreach (KeyValuePair> item in Main.SetAddOns) - { - if (Main.PlayerStates.TryGetValue(item.Key, out PlayerState state)) + if (spawnFlag) { - foreach (CustomRoles role in item.Value) - { - if (role is CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy) continue; + foreach ((CustomRoles otherAddon, (bool otherSpawnFlag, _)) in roleSpawnMapping) + if (otherAddon != addon && otherSpawnFlag && BasisChangingAddons.TryGetValue(otherAddon, out List otherList)) + roleList.ExceptWith(otherList); - state.SetSubRole(role); - if (overrideLovers && role == CustomRoles.Lovers) Main.LoversPlayers.Add(Utils.GetPlayerById(item.Key)); - - if (role.IsGhostRole()) GhostRolesManager.SpecificAssignGhostRole(item.Key, role, true); - } + BasisChangingAddons[addon] = roleList.Shuffle().Take(addon.GetCount()).ToList(); } } - - if (!overrideLovers && CustomRoles.Lovers.IsEnable() && (CustomRoles.FFF.IsEnable() ? -1 : random.Next(1, 100)) <= Lovers.LoverSpawnChances.GetInt()) AssignLoversRolesFromList(); - - // Add-on assignment - PlayerControl[] aapc = Main.AllAlivePlayerControls.Shuffle(); - if (Main.GM.Value) aapc = aapc.Without(PlayerControl.LocalPlayer).ToArray(); - - Dictionary addonNum = aapc.ToDictionary(x => x, _ => 0); - - AddonRolesList - .Except(BasisChangingAddons.Keys) - .Where(x => x.IsEnable() && aapc.Any(p => CustomRolesHelper.CheckAddonConflict(x, p))) - .SelectMany(x => Enumerable.Repeat(x, Math.Clamp(x.GetCount(), 0, aapc.Length))) - .Where(x => IRandom.Instance.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(x, out IntegerOptionItem sc) ? sc.GetFloat() : 0)) - .OrderBy(x => Options.CustomAdtRoleSpawnRate.TryGetValue(x, out IntegerOptionItem sc) && sc.GetInt() == 100 ? IRandom.Instance.Next(100) : IRandom.Instance.Next(100, 1000)) - .Select(x => - { - PlayerControl suitablePlayer = aapc - .OrderBy(p => addonNum[p]) - .FirstOrDefault(p => CustomRolesHelper.CheckAddonConflict(x, p)); - - if (suitablePlayer != null) addonNum[suitablePlayer]++; - - return (Role: x, SuitablePlayer: suitablePlayer); - }) - .DoIf(x => x.SuitablePlayer != null, x => Main.PlayerStates[x.SuitablePlayer.PlayerId].SetSubRole(x.Role)); - - - foreach (PlayerState state in Main.PlayerStates.Values) - { - if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.TryGetValue(state.MainRole, out List bannedAddonList)) - bannedAddonList.ForEach(x => state.RemoveSubRole(x)); - - if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.TryGetValue(state.MainRole, out List addonList)) - addonList.ForEach(x => state.SetSubRole(x)); - - if (!state.MainRole.IsImpostor() && !(state.MainRole == CustomRoles.Traitor && Traitor.CanGetImpostorOnlyAddons.GetBool())) - state.SubRoles.RemoveAll(x => x.IsImpOnlyAddon()); - } - - foreach (KeyValuePair pair in Main.PlayerStates) - { - ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); - - StringBuilder sb = new(); - - foreach (CustomRoles subRole in pair.Value.SubRoles) - { - ExtendedPlayerControl.RpcSetCustomRole(pair.Key, subRole); - sb.Append(subRole).Append(", "); - } - - if (sb.Length > 0) - { - sb.Remove(sb.Length - 2, 2); - Logger.Info($"{Main.AllPlayerNames[pair.Key]} has sub roles: {sb}", "SelectRolesPatch"); - } - } - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - try - { - if (pc.Data.Role.Role == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - - if (pc.AmOwner && pc.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) - { - foreach (PlayerControl target in Main.AllPlayerControls) - { - target.Data.Role.CanBeKilled = true; - - target.cosmetics.SetNameColor(Color.white); - target.Data.Role.NameColor = Color.white; - } - } - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "OnGameStartedPatch Add methods"); - } - } - - try - { - Stressed.Add(); - Asthmatic.Add(); - Circumvent.Add(); - Dynamo.Add(); - Spurt.Add(); - Allergic.Init(); - Lovers.Init(); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - - LateTask.New(CustomTeamManager.InitializeCustomTeamPlayers, 7f, log: false); - - if (overrideLovers) Logger.Msg(Main.LoversPlayers.Join(x => x?.GetRealName()), "Lovers"); - - EndOfSelectRolePatch: - - switch (Options.CurrentGameMode) - { - case CustomGameMode.AllInOne: - case CustomGameMode.HotPotato: - HotPotato.OnGameStart(); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.NaturalDisasters; - break; - case CustomGameMode.HideAndSeek: - HnSManager.StartSeekerBlindTime(); - break; - case CustomGameMode.CaptureTheFlag: - CTFManager.OnGameStart(); - break; - case CustomGameMode.NaturalDisasters: - NaturalDisasters.OnGameStart(); - if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.RoomRush; - break; - case CustomGameMode.RoomRush: - RoomRush.OnGameStart(); - break; - } - - HudManager.Instance.SetHudActive(true); - - foreach (PlayerControl pc in Main.AllPlayerControls) - pc.ResetKillCooldown(); - - - foreach (KeyValuePair roleType in RoleTypeNums) - { - int roleNum = Options.DisableVanillaRoles.GetBool() ? 0 : RoleOpt.GetNumPerGame(roleType.Key); - roleNum -= roleType.Value; - RoleOpt.SetRoleRate(roleType.Key, roleNum, RoleOpt.GetChancePerGame(roleType.Key)); - } - - - switch (Options.CurrentGameMode) - { - case CustomGameMode.Standard: - GameEndChecker.SetPredicateToNormal(); - break; - case CustomGameMode.SoloKombat: - GameEndChecker.SetPredicateToSoloKombat(); - break; - case CustomGameMode.FFA: - GameEndChecker.SetPredicateToFFA(); - break; - case CustomGameMode.MoveAndStop: - GameEndChecker.SetPredicateToMoveAndStop(); - break; - case CustomGameMode.HotPotato: - GameEndChecker.SetPredicateToHotPotato(); - break; - case CustomGameMode.Speedrun: - GameEndChecker.SetPredicateToSpeedrun(); - break; - case CustomGameMode.HideAndSeek: - GameEndChecker.SetPredicateToHideAndSeek(); - break; - case CustomGameMode.CaptureTheFlag: - GameEndChecker.SetPredicateToCaptureTheFlag(); - break; - case CustomGameMode.NaturalDisasters: - GameEndChecker.SetPredicateToNaturalDisasters(); - break; - case CustomGameMode.RoomRush: - GameEndChecker.SetPredicateToRoomRush(); - break; - case CustomGameMode.AllInOne: - GameEndChecker.SetPredicateToAllInOne(); - break; - } - - // Add players with unclassified roles to the list of players who require ResetCam. - Main.ResetCamPlayerList.UnionWith(Main.PlayerStates.Where(p => (p.Value.MainRole.IsDesyncRole() && !p.Value.MainRole.UsesPetInsteadOfKill()) || p.Value.SubRoles.Contains(CustomRoles.Bloodlust)).Select(p => p.Key)); - Utils.CountAlivePlayers(true); - Utils.SyncAllSettings(); - - LateTask.New(() => - { - Main.SetRoles = []; - Main.SetAddOns = []; - ChatCommands.DraftResult = []; - ChatCommands.DraftRoles = []; - }, 7f, log: false); - - if ((MapNames)Main.NormalOptions.MapId == MapNames.Airship && AmongUsClient.Instance.AmHost && Main.GM.Value) LateTask.New(() => PlayerControl.LocalPlayer.NetTransform.SnapTo(new(15.5f, 0.0f), (ushort)(PlayerControl.LocalPlayer.NetTransform.lastSequenceId + 8)), 15f, "GM Auto-TP Failsafe"); // TP to Main Hall - - LateTask.New(() => Main.HasJustStarted = false, 12f, "HasJustStarted to false"); } - catch (Exception ex) + + #endregion + + // Start CustomRpcSender + RpcSetRoleReplacer.StartReplace(); + + // Assign roles and create role maps for desync roles + RpcSetRoleReplacer.AssignDesyncRoles(); + RpcSetRoleReplacer.SendRpcForDesync(); + + // Assign roles and create role maps for normal roles + RpcSetRoleReplacer.AssignNormalRoles(); + RpcSetRoleReplacer.SendRpcForNormal(); + + // Send all RPCs + RpcSetRoleReplacer.Release(); + + foreach (PlayerControl pc in Main.AllPlayerControls) { - Utils.ErrorEnd("Select Role Postfix"); - Utils.ThrowException(ex); - yield break; + if (Main.PlayerStates[pc.PlayerId].MainRole != CustomRoles.NotAssigned) continue; + + CustomRoles role = Enum.TryParse($"{pc.Data.Role.Role}EHR", out CustomRoles parsedRole) ? parsedRole : CustomRoles.NotAssigned; + if (role == CustomRoles.NotAssigned) Logger.SendInGame(string.Format(GetString("Error.InvalidRoleAssignment"), pc?.Data?.PlayerName)); + + Main.PlayerStates[pc.PlayerId].SetMainRole(role); } - Logger.Info("Others assign finished", "AssignRoleTypes"); - yield return new WaitForSeconds(1f); - - Logger.Info("Send rpc disconnected for all", "AssignRoleTypes"); - DataDisconnected.Clear(); - RpcSetDisconnected(true); - - yield return new WaitForSeconds(4f); - - Logger.Info("Assign self", "AssignRoleTypes"); - SetRoleSelf(); - - RpcSetRoleReplacer.EndReplace(); - } - - private static bool IsBasisChangingPlayer(byte id, CustomRoles role) - { - return BasisChangingAddons.TryGetValue(role, out List list) && list.Contains(id); - } - - private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) - { - if (player == null) return; - - byte hostId = PlayerControl.LocalPlayer.PlayerId; - bool isHost = player.PlayerId == hostId; - - Main.PlayerStates[player.PlayerId].SetMainRole(role); - - RoleTypes selfRole = isHost ? BaseRole == RoleTypes.Shapeshifter ? RoleTypes.Shapeshifter : hostBaseRole : BaseRole; - RoleTypes othersRole = isHost ? RoleTypes.Crewmate : RoleTypes.Scientist; - - // Set Desync role for self and for others - foreach (PlayerControl target in Main.AllPlayerControls) + foreach (KeyValuePair kv in RoleResult) { - RoleTypes targetRoleType = othersRole; - CustomRoles targetCustomRole = RoleResult.GetValueOrDefault(target.PlayerId, CustomRoles.CrewmateEHR); + if (kv.Value.IsDesyncRole() || IsBasisChangingPlayer(kv.Key, CustomRoles.Bloodlust)) continue; - if (targetCustomRole.GetVNRole() is CustomRoles.Noisemaker) targetRoleType = RoleTypes.Noisemaker; - - rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? (targetRoleType, targetCustomRole) : (selfRole, role); + Main.PlayerStates[kv.Key].SetMainRole(kv.Value); } - // Set Desync role for others - foreach (PlayerControl seer in Main.AllPlayerControls.Where(x => player.PlayerId != x.PlayerId).ToArray()) rolesMap[(seer.PlayerId, player.PlayerId)] = (othersRole, role); - - - RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); - - // Set role for host, but not self - // canOverride should be false for the host during assign - if (!isHost) player.SetRole(othersRole, false); - - Logger.Info($"Registered Role: {player.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); - } - - private static void MakeDesyncSender(Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap) - { - foreach (PlayerControl seer in Main.AllPlayerControls) + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) { - foreach (PlayerControl target in Main.AllPlayerControls) + foreach (KeyValuePair pair in Main.PlayerStates) ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); + + goto EndOfSelectRolePatch; + } + + BasisChangingAddons.Do(x => x.Value.Do(y => Main.PlayerStates[y].SetSubRole(x.Key))); + + var overrideLovers = false; + + if (Main.SetAddOns.Count(x => x.Value.Contains(CustomRoles.Lovers)) == 2) + { + Main.LoversPlayers.Clear(); + Main.IsLoversDead = false; + overrideLovers = true; + Logger.Warn("Lovers overridden by host's pre-set add-ons", "CustomRoleSelector"); + } + + foreach (KeyValuePair> item in Main.SetAddOns) + { + if (Main.PlayerStates.TryGetValue(item.Key, out PlayerState state)) { - if (seer.PlayerId == target.PlayerId || target.IsLocalPlayer()) continue; - - if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out (RoleTypes, CustomRoles) roleMap)) + foreach (CustomRoles role in item.Value) { - try - { - int targetClientId = target.GetClientId(); - if (targetClientId == -1) continue; + if (role is CustomRoles.Nimble or CustomRoles.Physicist or CustomRoles.Bloodlust or CustomRoles.Finder or CustomRoles.Noisy) continue; - RoleTypes roleType = roleMap.Item1; - CustomRpcSender sender = senders[seer.PlayerId]; - sender.RpcSetRole(seer, roleType, targetClientId); - } - catch { } + state.SetSubRole(role); + if (overrideLovers && role == CustomRoles.Lovers) Main.LoversPlayers.Add(Utils.GetPlayerById(item.Key)); + + if (role.IsGhostRole()) GhostRolesManager.SpecificAssignGhostRole(item.Key, role, true); } } } - } - private static void SetRoleSelf() - { + if (!overrideLovers && CustomRoles.Lovers.IsEnable() && (CustomRoles.FFF.IsEnable() ? -1 : random.Next(1, 100)) <= Lovers.LoverSpawnChances.GetInt()) AssignLoversRolesFromList(); + + // Add-on assignment + PlayerControl[] aapc = Main.AllAlivePlayerControls.Shuffle(); + if (Main.GM.Value) aapc = aapc.Without(PlayerControl.LocalPlayer).ToArray(); + + Dictionary addonNum = aapc.ToDictionary(x => x, _ => 0); + + AddonRolesList + .Except(BasisChangingAddons.Keys) + .Where(x => x.IsEnable() && aapc.Any(p => CustomRolesHelper.CheckAddonConflict(x, p))) + .SelectMany(x => Enumerable.Repeat(x, Math.Clamp(x.GetCount(), 0, aapc.Length))) + .Where(x => IRandom.Instance.Next(1, 100) <= (Options.CustomAdtRoleSpawnRate.TryGetValue(x, out IntegerOptionItem sc) ? sc.GetFloat() : 0)) + .OrderBy(x => Options.CustomAdtRoleSpawnRate.TryGetValue(x, out IntegerOptionItem sc) && sc.GetInt() == 100 ? IRandom.Instance.Next(100) : IRandom.Instance.Next(100, 1000)) + .Select(x => + { + PlayerControl suitablePlayer = aapc + .OrderBy(p => addonNum[p]) + .FirstOrDefault(p => CustomRolesHelper.CheckAddonConflict(x, p)); + + if (suitablePlayer != null) addonNum[suitablePlayer]++; + + return (Role: x, SuitablePlayer: suitablePlayer); + }) + .DoIf(x => x.SuitablePlayer != null, x => Main.PlayerStates[x.SuitablePlayer.PlayerId].SetSubRole(x.Role)); + + + foreach (PlayerState state in Main.PlayerStates.Values) + { + if (Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> neverList) && neverList.TryGetValue(state.MainRole, out List bannedAddonList)) + bannedAddonList.ForEach(x => state.RemoveSubRole(x)); + + if (Main.AlwaysSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> alwaysList) && alwaysList.TryGetValue(state.MainRole, out List addonList)) + addonList.ForEach(x => state.SetSubRole(x)); + + if (!state.MainRole.IsImpostor() && !(state.MainRole == CustomRoles.Traitor && Traitor.CanGetImpostorOnlyAddons.GetBool())) + state.SubRoles.RemoveAll(x => x.IsImpOnlyAddon()); + } + + foreach (KeyValuePair pair in Main.PlayerStates) + { + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, pair.Value.MainRole); + + StringBuilder sb = new(); + + foreach (CustomRoles subRole in pair.Value.SubRoles) + { + ExtendedPlayerControl.RpcSetCustomRole(pair.Key, subRole); + sb.Append(subRole).Append(", "); + } + + if (sb.Length > 0) + { + sb.Remove(sb.Length - 2, 2); + Logger.Info($"{Main.AllPlayerNames[pair.Key]} has sub roles: {sb}", "SelectRolesPatch"); + } + } + foreach (PlayerControl pc in Main.AllPlayerControls) { try { - SetRoleSelf(pc); - } - catch { } - } - } + if (pc.Data.Role.Role == RoleTypes.Shapeshifter) Main.CheckShapeshift.Add(pc.PlayerId, false); - private static void SetRoleSelf(PlayerControl target) - { - if (target == null) return; - - int targetClientId = target.GetClientId(); - if (targetClientId == -1) return; - - RoleTypes roleType = RpcSetRoleReplacer.RoleMap.TryGetValue((target.PlayerId, target.PlayerId), out (RoleTypes RoleType, CustomRoles CustomRole) roleMap) - ? roleMap.RoleType - : RpcSetRoleReplacer.StoragedData[target.PlayerId]; - - target.RpcSetRoleDesync(roleType, targetClientId); - } - - public static void RpcSetDisconnected(bool disconnected) - { - foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) - { - if (disconnected) - { - // if player left the game, remember current data - DataDisconnected[playerInfo.PlayerId] = playerInfo.Disconnected; - - playerInfo.Disconnected = true; - playerInfo.IsDead = false; - } - else - { - bool data = DataDisconnected.GetValueOrDefault(playerInfo.PlayerId, true); - playerInfo.Disconnected = data; - playerInfo.IsDead = data; - } - - MessageWriter stream = MessageWriter.Get(SendOption.Reliable); - stream.StartMessage(5); - stream.Write(AmongUsClient.Instance.GameId); - - { - stream.StartMessage(1); - stream.WritePacked(playerInfo.NetId); - playerInfo.Serialize(stream, false); - stream.EndMessage(); - } - - stream.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(stream); - stream.Recycle(); - } - } - - private static void AssignLoversRolesFromList() - { - if (CustomRoles.Lovers.IsEnable() && !RoleResult.ContainsValue(CustomRoles.Romantic)) - { - Main.LoversPlayers.Clear(); - Main.IsLoversDead = false; - AssignLoversRoles(); - } - } - - private static void AssignLoversRoles(int RawCount = -1) - { - if (Lovers.LegacyLovers.GetBool()) - { - Main.LoversPlayers = Main.AllPlayerControls.Where(x => x.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor).Take(2).ToList(); - return; - } - - List allPlayers = Main.AllPlayerControls.Where(pc => (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> bannedCombos) || bannedCombos.All(x => !pc.Is(x.Key) || !x.Value.Contains(CustomRoles.Lovers))) && !pc.Is(CustomRoles.GM) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && !pc.Is(CustomRoles.Dictator) && !pc.Is(CustomRoles.God) && !pc.Is(CustomRoles.FFF) && !pc.Is(CustomRoles.Bomber) && !pc.Is(CustomRoles.Nuker) && !pc.Is(CustomRoles.Curser) && !pc.Is(CustomRoles.Provocateur) && !pc.Is(CustomRoles.Altruist) && (!pc.IsCrewmate() || Lovers.CrewCanBeInLove.GetBool()) && (!pc.GetCustomRole().IsNeutral() || Lovers.NeutralCanBeInLove.GetBool()) && (!pc.IsImpostor() || Lovers.ImpCanBeInLove.GetBool())).ToList(); - const CustomRoles role = CustomRoles.Lovers; - int count = Math.Clamp(RawCount, 0, allPlayers.Count); - if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); - - if (count <= 0) return; - - for (var i = 0; i < count; i++) - { - PlayerControl player = allPlayers.RandomElement(); - Main.LoversPlayers.Add(player); - allPlayers.Remove(player); - Main.PlayerStates[player.PlayerId].SetSubRole(role); - Logger.Info($"Add-on assigned: {player.Data?.PlayerName} = {player.GetCustomRole()} + {role}", "Assign Lovers"); - } - - RPC.SyncLoversPlayers(); - } - - // https://github.com/0xDrMoe/TownofHost-Enhanced/blob/41566d58e4217c38542df5b91f507045a6394908/Patches/onGameStartedPatch.cs#L667 - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] - [HarmonyPriority(Priority.High)] - public static class RpcSetRoleReplacer - { - public static bool BlockSetRole; - private static Dictionary Senders = []; - public static Dictionary StoragedData = []; - public static Dictionary<(byte SeerID, byte TargetID), (RoleTypes RoleType, CustomRoles CustomRole)> RoleMap = []; - public static List OverriddenSenderList = []; - - public static void Initialize() - { - BlockSetRole = true; - Senders = []; - RoleMap = []; - StoragedData = []; - OverriddenSenderList = []; - } - - // ReSharper disable once MemberHidesStaticFromOuterClass - // ReSharper disable once UnusedMember.Global - public static bool Prefix() - { - return !BlockSetRole; - } - - public static void StartReplace() - { - foreach (PlayerControl pc in Main.AllPlayerControls) - { - Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) - .StartMessage(pc.GetClientId()); - } - } - - public static void AssignDesyncRoles() - { - // Assign desync roles - foreach ((byte playerId, CustomRoles role) in RoleResult.Where(x => x.Value.IsDesyncRole() || IsBasisChangingPlayer(x.Key, CustomRoles.Bloodlust)).ToArray()) - AssignDesyncRole(role, Utils.GetPlayerById(playerId), Senders, RoleMap, ForceImp(playerId) ? RoleTypes.Impostor : role.GetDYRole()); - - return; - - bool ForceImp(byte id) - { - return IsBasisChangingPlayer(id, CustomRoles.Bloodlust) || (CustomGameMode.Speedrun.IsActiveOrIntegrated() && SpeedrunManager.CanKill.Contains(id)); - } - } - - public static void SendRpcForDesync() - { - MakeDesyncSender(Senders, RoleMap); - } - - public static void AssignNormalRoles() - { - foreach ((byte playerId, CustomRoles role) in RoleResult) - { - PlayerControl player = Utils.GetPlayerById(playerId); - if (player == null || role.IsDesyncRole()) continue; - - if (CustomGameMode.Speedrun.IsActiveOrIntegrated() && SpeedrunManager.CanKill.Contains(playerId)) continue; - - RoleTypes roleType = role.GetRoleTypes(); - - if (BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out KeyValuePair> kvp)) + if (pc.AmOwner && pc.GetCustomRole().GetDYRole() is RoleTypes.Shapeshifter or RoleTypes.Phantom) { - if (kvp.Key == CustomRoles.Bloodlust) continue; - - roleType = kvp.Key switch + foreach (PlayerControl target in Main.AllPlayerControls) { - CustomRoles.Nimble => RoleTypes.Engineer, - CustomRoles.Physicist => RoleTypes.Scientist, - CustomRoles.Finder => RoleTypes.Tracker, - CustomRoles.Noisy => RoleTypes.Noisemaker, - _ => roleType - }; - } + target.Data.Role.CanBeKilled = true; - StoragedData.Add(playerId, roleType); - - foreach (PlayerControl target in Main.AllPlayerControls) - { - if (RoleResult.TryGetValue(target.PlayerId, out CustomRoles targetRole) && targetRole.IsDesyncRole() && !target.IsHost()) continue; - - RoleMap[(target.PlayerId, playerId)] = (roleType, role); - } - - if (playerId != PlayerControl.LocalPlayer.PlayerId) - { - // canOverride should be false for the host during assign - player.SetRole(roleType, false); - } - - Logger.Info($"Set original role type => {player.GetRealName()}: {role} => {role.GetRoleTypes()}", "AssignNormalRoles"); - } - } - - public static void SendRpcForNormal() - { - foreach ((byte targetId, CustomRpcSender sender) in Senders) - { - PlayerControl target = Utils.GetPlayerById(targetId); - if (OverriddenSenderList.Contains(sender)) continue; - - if (sender.CurrentState != CustomRpcSender.State.InRootMessage) throw new InvalidOperationException("A CustomRpcSender had Invalid State."); - - foreach ((byte seerId, RoleTypes roleType) in StoragedData) - { - if (targetId == seerId || targetId == PlayerControl.LocalPlayer.PlayerId) continue; - - PlayerControl seer = Utils.GetPlayerById(seerId); - if (seer == null || target == null) continue; - - try - { - int targetClientId = target.GetClientId(); - if (targetClientId == -1) continue; - - // send rpc set role for other clients - sender.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, targetClientId) - .Write((ushort)roleType) - .Write(true) // canOverride - .EndRpc(); + target.cosmetics.SetNameColor(Color.white); + target.Data.Role.NameColor = Color.white; } - catch { } } - - sender.EndMessage(); } - } - - public static void Release() - { - BlockSetRole = false; - Senders.Do(kvp => kvp.Value.SendMessage()); - - if (!CustomRoles.DoubleAgent.IsEnable()) return; - - try + catch (Exception ex) { - RoleResult.DoIf(x => x.Value == CustomRoles.DoubleAgent, k => - { - var da = k.Key.GetPlayer(); - if (da == null) return; - - var ci = da.GetClientId(); - if (ci == -1) return; - - RoleResult.DoIf(x => x.Value.IsImpostor(), x => - { - var imp = x.Key.GetPlayer(); - if (imp == null) return; - - var previousRoleType = RoleMap[(k.Key, x.Key)].RoleType; - - imp.RpcSetRoleDesync(RoleTypes.Crewmate, ci); - - LateTask.New(() => imp.RpcSetRoleDesync(previousRoleType, ci), 7f, log: false); - }); - }); - } - catch (Exception e) - { - Utils.ThrowException(e); + Logger.Error(ex.ToString(), "OnGameStartedPatch Add methods"); } } - public static void EndReplace() + try { - Senders = null; - OverriddenSenderList = null; - StoragedData = null; + Stressed.Add(); + Asthmatic.Add(); + Circumvent.Add(); + Dynamo.Add(); + Spurt.Add(); + Allergic.Init(); + Lovers.Init(); + } + catch (Exception e) + { + Utils.ThrowException(e); } - } - } - [HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] - internal static class FixIntroPatch - { - public static void Postfix() - { + LateTask.New(CustomTeamManager.InitializeCustomTeamPlayers, 7f, log: false); + + if (overrideLovers) Logger.Msg(Main.LoversPlayers.Join(x => x?.GetRealName()), "Lovers"); + + EndOfSelectRolePatch: + + switch (Options.CurrentGameMode) + { + case CustomGameMode.AllInOne: + case CustomGameMode.HotPotato: + HotPotato.OnGameStart(); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.NaturalDisasters; + break; + case CustomGameMode.HideAndSeek: + HnSManager.StartSeekerBlindTime(); + break; + case CustomGameMode.CaptureTheFlag: + CTFManager.OnGameStart(); + break; + case CustomGameMode.NaturalDisasters: + NaturalDisasters.OnGameStart(); + if (Options.CurrentGameMode == CustomGameMode.AllInOne) goto case CustomGameMode.RoomRush; + break; + case CustomGameMode.RoomRush: + RoomRush.OnGameStart(); + break; + } + + HudManager.Instance.SetHudActive(true); + + foreach (PlayerControl pc in Main.AllPlayerControls) + pc.ResetKillCooldown(); + + + foreach (KeyValuePair roleType in RoleTypeNums) + { + int roleNum = Options.DisableVanillaRoles.GetBool() ? 0 : RoleOpt.GetNumPerGame(roleType.Key); + roleNum -= roleType.Value; + RoleOpt.SetRoleRate(roleType.Key, roleNum, RoleOpt.GetChancePerGame(roleType.Key)); + } + + + switch (Options.CurrentGameMode) + { + case CustomGameMode.Standard: + GameEndChecker.SetPredicateToNormal(); + break; + case CustomGameMode.SoloKombat: + GameEndChecker.SetPredicateToSoloKombat(); + break; + case CustomGameMode.FFA: + GameEndChecker.SetPredicateToFFA(); + break; + case CustomGameMode.MoveAndStop: + GameEndChecker.SetPredicateToMoveAndStop(); + break; + case CustomGameMode.HotPotato: + GameEndChecker.SetPredicateToHotPotato(); + break; + case CustomGameMode.Speedrun: + GameEndChecker.SetPredicateToSpeedrun(); + break; + case CustomGameMode.HideAndSeek: + GameEndChecker.SetPredicateToHideAndSeek(); + break; + case CustomGameMode.CaptureTheFlag: + GameEndChecker.SetPredicateToCaptureTheFlag(); + break; + case CustomGameMode.NaturalDisasters: + GameEndChecker.SetPredicateToNaturalDisasters(); + break; + case CustomGameMode.RoomRush: + GameEndChecker.SetPredicateToRoomRush(); + break; + case CustomGameMode.AllInOne: + GameEndChecker.SetPredicateToAllInOne(); + break; + } + + // Add players with unclassified roles to the list of players who require ResetCam. + Main.ResetCamPlayerList.UnionWith(Main.PlayerStates.Where(p => (p.Value.MainRole.IsDesyncRole() && !p.Value.MainRole.UsesPetInsteadOfKill()) || p.Value.SubRoles.Contains(CustomRoles.Bloodlust)).Select(p => p.Key)); + Utils.CountAlivePlayers(true); + Utils.SyncAllSettings(); + LateTask.New(() => { - if (CoShowIntroPatch.IntroStarted) return; + Main.SetRoles = []; + Main.SetAddOns = []; + ChatCommands.DraftResult = []; + ChatCommands.DraftRoles = []; + }, 7f, log: false); - Logger.Warn("Starting intro manually", "StartGameHostPatch"); + if ((MapNames)Main.NormalOptions.MapId == MapNames.Airship && AmongUsClient.Instance.AmHost && Main.GM.Value) LateTask.New(() => PlayerControl.LocalPlayer.NetTransform.SnapTo(new(15.5f, 0.0f), (ushort)(PlayerControl.LocalPlayer.NetTransform.lastSequenceId + 8)), 15f, "GM Auto-TP Failsafe"); // TP to Main Hall - PlayerControl.AllPlayerControls.ForEach((Action)PlayerNameColor.Set); - PlayerControl.LocalPlayer.StopAllCoroutines(); - DestroyableSingleton.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); - DestroyableSingleton.Instance.HideGameLoader(); - }, 1f, log: false); + LateTask.New(() => Main.HasJustStarted = false, 12f, "HasJustStarted to false"); + } + catch (Exception ex) + { + Utils.ErrorEnd("Select Role Postfix"); + Utils.ThrowException(ex); + yield break; + } + + Logger.Info("Others assign finished", "AssignRoleTypes"); + yield return new WaitForSeconds(1f); + + Logger.Info("Send rpc disconnected for all", "AssignRoleTypes"); + DataDisconnected.Clear(); + RpcSetDisconnected(true); + + yield return new WaitForSeconds(4f); + + Logger.Info("Assign self", "AssignRoleTypes"); + SetRoleSelf(); + + RpcSetRoleReplacer.EndReplace(); + } + + private static bool IsBasisChangingPlayer(byte id, CustomRoles role) + { + return BasisChangingAddons.TryGetValue(role, out List list) && list.Contains(id); + } + + private static void AssignDesyncRole(CustomRoles role, PlayerControl player, Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap, RoleTypes BaseRole, RoleTypes hostBaseRole = RoleTypes.Crewmate) + { + if (player == null) return; + + byte hostId = PlayerControl.LocalPlayer.PlayerId; + bool isHost = player.PlayerId == hostId; + + Main.PlayerStates[player.PlayerId].SetMainRole(role); + + RoleTypes selfRole = isHost ? BaseRole == RoleTypes.Shapeshifter ? RoleTypes.Shapeshifter : hostBaseRole : BaseRole; + RoleTypes othersRole = isHost ? RoleTypes.Crewmate : RoleTypes.Scientist; + + // Set Desync role for self and for others + foreach (PlayerControl target in Main.AllPlayerControls) + { + RoleTypes targetRoleType = othersRole; + CustomRoles targetCustomRole = RoleResult.GetValueOrDefault(target.PlayerId, CustomRoles.CrewmateEHR); + + if (targetCustomRole.GetVNRole() is CustomRoles.Noisemaker) targetRoleType = RoleTypes.Noisemaker; + + rolesMap[(player.PlayerId, target.PlayerId)] = player.PlayerId != target.PlayerId ? (targetRoleType, targetCustomRole) : (selfRole, role); + } + + // Set Desync role for others + foreach (PlayerControl seer in Main.AllPlayerControls.Where(x => player.PlayerId != x.PlayerId).ToArray()) rolesMap[(seer.PlayerId, player.PlayerId)] = (othersRole, role); + + + RpcSetRoleReplacer.OverriddenSenderList.Add(senders[player.PlayerId]); + + // Set role for host, but not self + // canOverride should be false for the host during assign + if (!isHost) player.SetRole(othersRole, false); + + Logger.Info($"Registered Role: {player.Data?.PlayerName} => {role} : RoleType for self => {selfRole}, for others => {othersRole}", "AssignDesyncRoles"); + } + + private static void MakeDesyncSender(Dictionary senders, Dictionary<(byte, byte), (RoleTypes, CustomRoles)> rolesMap) + { + foreach (PlayerControl seer in Main.AllPlayerControls) + { + foreach (PlayerControl target in Main.AllPlayerControls) + { + if (seer.PlayerId == target.PlayerId || target.IsLocalPlayer()) continue; + + if (rolesMap.TryGetValue((seer.PlayerId, target.PlayerId), out (RoleTypes, CustomRoles) roleMap)) + { + try + { + int targetClientId = target.GetClientId(); + if (targetClientId == -1) continue; + + RoleTypes roleType = roleMap.Item1; + CustomRpcSender sender = senders[seer.PlayerId]; + sender.RpcSetRole(seer, roleType, targetClientId); + } + catch { } + } + } } } + + private static void SetRoleSelf() + { + foreach (PlayerControl pc in Main.AllPlayerControls) + { + try + { + SetRoleSelf(pc); + } + catch { } + } + } + + private static void SetRoleSelf(PlayerControl target) + { + if (target == null) return; + + int targetClientId = target.GetClientId(); + if (targetClientId == -1) return; + + RoleTypes roleType = RpcSetRoleReplacer.RoleMap.TryGetValue((target.PlayerId, target.PlayerId), out (RoleTypes RoleType, CustomRoles CustomRole) roleMap) + ? roleMap.RoleType + : RpcSetRoleReplacer.StoragedData[target.PlayerId]; + + target.RpcSetRoleDesync(roleType, targetClientId); + } + + public static void RpcSetDisconnected(bool disconnected) + { + foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) + { + if (disconnected) + { + // if player left the game, remember current data + DataDisconnected[playerInfo.PlayerId] = playerInfo.Disconnected; + + playerInfo.Disconnected = true; + playerInfo.IsDead = false; + } + else + { + bool data = DataDisconnected.GetValueOrDefault(playerInfo.PlayerId, true); + playerInfo.Disconnected = data; + playerInfo.IsDead = data; + } + + MessageWriter stream = MessageWriter.Get(SendOption.Reliable); + stream.StartMessage(5); + stream.Write(AmongUsClient.Instance.GameId); + + { + stream.StartMessage(1); + stream.WritePacked(playerInfo.NetId); + playerInfo.Serialize(stream, false); + stream.EndMessage(); + } + + stream.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(stream); + stream.Recycle(); + } + } + + private static void AssignLoversRolesFromList() + { + if (CustomRoles.Lovers.IsEnable() && !RoleResult.ContainsValue(CustomRoles.Romantic)) + { + Main.LoversPlayers.Clear(); + Main.IsLoversDead = false; + AssignLoversRoles(); + } + } + + private static void AssignLoversRoles(int RawCount = -1) + { + if (Lovers.LegacyLovers.GetBool()) + { + Main.LoversPlayers = Main.AllPlayerControls.Where(x => x.GetCustomRole() is CustomRoles.LovingCrewmate or CustomRoles.LovingImpostor).Take(2).ToList(); + return; + } + + List allPlayers = Main.AllPlayerControls.Where(pc => (!Main.NeverSpawnTogetherCombos.TryGetValue(OptionItem.CurrentPreset, out Dictionary> bannedCombos) || bannedCombos.All(x => !pc.Is(x.Key) || !x.Value.Contains(CustomRoles.Lovers))) && !pc.Is(CustomRoles.GM) && (!pc.HasSubRole() || pc.GetCustomSubRoles().Count < Options.NoLimitAddonsNumMax.GetInt()) && !pc.Is(CustomRoles.Dictator) && !pc.Is(CustomRoles.God) && !pc.Is(CustomRoles.FFF) && !pc.Is(CustomRoles.Bomber) && !pc.Is(CustomRoles.Nuker) && !pc.Is(CustomRoles.Curser) && !pc.Is(CustomRoles.Provocateur) && !pc.Is(CustomRoles.Altruist) && (!pc.IsCrewmate() || Lovers.CrewCanBeInLove.GetBool()) && (!pc.GetCustomRole().IsNeutral() || Lovers.NeutralCanBeInLove.GetBool()) && (!pc.IsImpostor() || Lovers.ImpCanBeInLove.GetBool())).ToList(); + const CustomRoles role = CustomRoles.Lovers; + int count = Math.Clamp(RawCount, 0, allPlayers.Count); + if (RawCount == -1) count = Math.Clamp(role.GetCount(), 0, allPlayers.Count); + + if (count <= 0) return; + + for (var i = 0; i < count; i++) + { + PlayerControl player = allPlayers.RandomElement(); + Main.LoversPlayers.Add(player); + allPlayers.Remove(player); + Main.PlayerStates[player.PlayerId].SetSubRole(role); + Logger.Info($"Add-on assigned: {player.Data?.PlayerName} = {player.GetCustomRole()} + {role}", "Assign Lovers"); + } + + RPC.SyncLoversPlayers(); + } + + // https://github.com/0xDrMoe/TownofHost-Enhanced/blob/41566d58e4217c38542df5b91f507045a6394908/Patches/onGameStartedPatch.cs#L667 + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] + [HarmonyPriority(Priority.High)] + public static class RpcSetRoleReplacer + { + public static bool BlockSetRole; + private static Dictionary Senders = []; + public static Dictionary StoragedData = []; + public static Dictionary<(byte SeerID, byte TargetID), (RoleTypes RoleType, CustomRoles CustomRole)> RoleMap = []; + public static List OverriddenSenderList = []; + + public static void Initialize() + { + BlockSetRole = true; + Senders = []; + RoleMap = []; + StoragedData = []; + OverriddenSenderList = []; + } + + // ReSharper disable once MemberHidesStaticFromOuterClass + // ReSharper disable once UnusedMember.Global + public static bool Prefix() + { + return !BlockSetRole; + } + + public static void StartReplace() + { + foreach (PlayerControl pc in Main.AllPlayerControls) + { + Senders[pc.PlayerId] = new CustomRpcSender($"{pc.name}'s SetRole Sender", SendOption.Reliable, false) + .StartMessage(pc.GetClientId()); + } + } + + public static void AssignDesyncRoles() + { + // Assign desync roles + foreach ((byte playerId, CustomRoles role) in RoleResult.Where(x => x.Value.IsDesyncRole() || IsBasisChangingPlayer(x.Key, CustomRoles.Bloodlust)).ToArray()) + AssignDesyncRole(role, Utils.GetPlayerById(playerId), Senders, RoleMap, ForceImp(playerId) ? RoleTypes.Impostor : role.GetDYRole()); + + return; + + bool ForceImp(byte id) + { + return IsBasisChangingPlayer(id, CustomRoles.Bloodlust) || (CustomGameMode.Speedrun.IsActiveOrIntegrated() && SpeedrunManager.CanKill.Contains(id)); + } + } + + public static void SendRpcForDesync() + { + MakeDesyncSender(Senders, RoleMap); + } + + public static void AssignNormalRoles() + { + foreach ((byte playerId, CustomRoles role) in RoleResult) + { + PlayerControl player = Utils.GetPlayerById(playerId); + if (player == null || role.IsDesyncRole()) continue; + + if (CustomGameMode.Speedrun.IsActiveOrIntegrated() && SpeedrunManager.CanKill.Contains(playerId)) continue; + + RoleTypes roleType = role.GetRoleTypes(); + + if (BasisChangingAddons.FindFirst(x => x.Value.Contains(playerId), out KeyValuePair> kvp)) + { + if (kvp.Key == CustomRoles.Bloodlust) continue; + + roleType = kvp.Key switch + { + CustomRoles.Nimble => RoleTypes.Engineer, + CustomRoles.Physicist => RoleTypes.Scientist, + CustomRoles.Finder => RoleTypes.Tracker, + CustomRoles.Noisy => RoleTypes.Noisemaker, + _ => roleType + }; + } + + StoragedData.Add(playerId, roleType); + + foreach (PlayerControl target in Main.AllPlayerControls) + { + if (RoleResult.TryGetValue(target.PlayerId, out CustomRoles targetRole) && targetRole.IsDesyncRole() && !target.IsHost()) continue; + + RoleMap[(target.PlayerId, playerId)] = (roleType, role); + } + + if (playerId != PlayerControl.LocalPlayer.PlayerId) + { + // canOverride should be false for the host during assign + player.SetRole(roleType, false); + } + + Logger.Info($"Set original role type => {player.GetRealName()}: {role} => {role.GetRoleTypes()}", "AssignNormalRoles"); + } + } + + public static void SendRpcForNormal() + { + foreach ((byte targetId, CustomRpcSender sender) in Senders) + { + PlayerControl target = Utils.GetPlayerById(targetId); + if (OverriddenSenderList.Contains(sender)) continue; + + if (sender.CurrentState != CustomRpcSender.State.InRootMessage) throw new InvalidOperationException("A CustomRpcSender had Invalid State."); + + foreach ((byte seerId, RoleTypes roleType) in StoragedData) + { + if (targetId == seerId || targetId == PlayerControl.LocalPlayer.PlayerId) continue; + + PlayerControl seer = Utils.GetPlayerById(seerId); + if (seer == null || target == null) continue; + + try + { + int targetClientId = target.GetClientId(); + if (targetClientId == -1) continue; + + // send rpc set role for other clients + sender.AutoStartRpc(seer.NetId, (byte)RpcCalls.SetRole, targetClientId) + .Write((ushort)roleType) + .Write(true) // canOverride + .EndRpc(); + } + catch { } + } + + sender.EndMessage(); + } + } + + public static void Release() + { + BlockSetRole = false; + Senders.Do(kvp => kvp.Value.SendMessage()); + + if (!CustomRoles.DoubleAgent.IsEnable()) return; + + try + { + RoleResult.DoIf(x => x.Value == CustomRoles.DoubleAgent, k => + { + var da = k.Key.GetPlayer(); + if (da == null) return; + + var ci = da.GetClientId(); + if (ci == -1) return; + + RoleResult.DoIf(x => x.Value.IsImpostor(), x => + { + var imp = x.Key.GetPlayer(); + if (imp == null) return; + + var previousRoleType = RoleMap[(k.Key, x.Key)].RoleType; + + imp.RpcSetRoleDesync(RoleTypes.Crewmate, ci); + + LateTask.New(() => imp.RpcSetRoleDesync(previousRoleType, ci), 7f, log: false); + }); + }); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public static void EndReplace() + { + Senders = null; + OverriddenSenderList = null; + StoragedData = null; + } + } +} + +[HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] +internal static class FixIntroPatch +{ + public static void Postfix() + { + LateTask.New(() => + { + if (CoShowIntroPatch.IntroStarted) return; + + Logger.Warn("Starting intro manually", "StartGameHostPatch"); + + PlayerControl.AllPlayerControls.ForEach((Action)PlayerNameColor.Set); + PlayerControl.LocalPlayer.StopAllCoroutines(); + DestroyableSingleton.Instance.StartCoroutine(DestroyableSingleton.Instance.CoShowIntro()); + DestroyableSingleton.Instance.HideGameLoader(); + }, 1f, log: false); + } } \ No newline at end of file diff --git a/Patches/OneWayShadowsPatch.cs b/Patches/OneWayShadowsPatch.cs index 0015992e3..227d12998 100644 --- a/Patches/OneWayShadowsPatch.cs +++ b/Patches/OneWayShadowsPatch.cs @@ -1,19 +1,18 @@ using HarmonyLib; -namespace EHR -{ - [HarmonyPatch(typeof(OneWayShadows), nameof(OneWayShadows.IsIgnored))] - public static class OneWayShadowsIsIgnoredPatch - { - public static bool Prefix(OneWayShadows __instance, ref bool __result) - { - if (__instance.IgnoreImpostor && Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId)) - { - __result = true; - return false; - } +namespace EHR; - return true; +[HarmonyPatch(typeof(OneWayShadows), nameof(OneWayShadows.IsIgnored))] +public static class OneWayShadowsIsIgnoredPatch +{ + public static bool Prefix(OneWayShadows __instance, ref bool __result) + { + if (__instance.IgnoreImpostor && Main.ResetCamPlayerList.Contains(PlayerControl.LocalPlayer.PlayerId)) + { + __result = true; + return false; } + + return true; } } \ No newline at end of file diff --git a/Patches/OutroPatch.cs b/Patches/OutroPatch.cs index 419edf991..ed051d66d 100644 --- a/Patches/OutroPatch.cs +++ b/Patches/OutroPatch.cs @@ -12,521 +12,520 @@ using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] +internal static class EndGamePatch { - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] - internal static class EndGamePatch + public static Dictionary SummaryText = []; + public static string KillLog = string.Empty; + + public static void Postfix() { - public static Dictionary SummaryText = []; - public static string KillLog = string.Empty; + GameStates.InGame = false; - public static void Postfix() + Logger.Info("-----------Game over-----------", "Phase"); + + ChatCommands.DraftResult = []; + ChatCommands.DraftRoles = []; + Main.SetRoles = []; + Main.SetAddOns = []; + SummaryText = []; + Main.LastAddOns = []; + + Main.ChangedRole = false; + + foreach ((byte id, PlayerState state) in Main.PlayerStates) { - GameStates.InGame = false; - - Logger.Info("-----------Game over-----------", "Phase"); - - ChatCommands.DraftResult = []; - ChatCommands.DraftRoles = []; - Main.SetRoles = []; - Main.SetAddOns = []; - SummaryText = []; - Main.LastAddOns = []; - - Main.ChangedRole = false; - - foreach ((byte id, PlayerState state) in Main.PlayerStates) + if (Doppelganger.PlayerIdList.Count > 0 && Doppelganger.DoppelVictim.ContainsKey(id)) { - if (Doppelganger.PlayerIdList.Count > 0 && Doppelganger.DoppelVictim.ContainsKey(id)) + PlayerControl dpc = Utils.GetPlayerById(id); + + if (dpc != null) { - PlayerControl dpc = Utils.GetPlayerById(id); - - if (dpc != null) - { - dpc.RpcSetName(Doppelganger.DoppelVictim[id]); - Main.AllPlayerNames[id] = Doppelganger.DoppelVictim[id]; - } + dpc.RpcSetName(Doppelganger.DoppelVictim[id]); + Main.AllPlayerNames[id] = Doppelganger.DoppelVictim[id]; } - - SummaryText[id] = Utils.SummaryTexts(id, false); - if (state.SubRoles.Count == 0) continue; - - Main.LastAddOns[id] = $"{id.ColoredPlayerName()}: {state.SubRoles.Join(x => x.ToColoredString())}"; } - if (Options.DumpLogAfterGameEnd.GetBool()) Utils.DumpLog(false); + SummaryText[id] = Utils.SummaryTexts(id, false); + if (state.SubRoles.Count == 0) continue; - StringBuilder sb = new(GetString("KillLog") + ":"); + Main.LastAddOns[id] = $"{id.ColoredPlayerName()}: {state.SubRoles.Join(x => x.ToColoredString())}"; + } - foreach ((byte key, PlayerState value) in Main.PlayerStates.OrderBy(x => x.Value.RealKiller.TimeStamp.Ticks)) + if (Options.DumpLogAfterGameEnd.GetBool()) Utils.DumpLog(false); + + StringBuilder sb = new(GetString("KillLog") + ":"); + + foreach ((byte key, PlayerState value) in Main.PlayerStates.OrderBy(x => x.Value.RealKiller.TimeStamp.Ticks)) + { + DateTime date = value.RealKiller.TimeStamp; + if (date == DateTime.MinValue) continue; + + byte killerId = value.GetRealKiller(); + bool gmIsFM = Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop; + bool gmIsFMHH = gmIsFM || Options.CurrentGameMode is CustomGameMode.HotPotato or CustomGameMode.HideAndSeek or CustomGameMode.Speedrun or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.AllInOne; + sb.Append($"\n{date:T} {Main.AllPlayerNames[key]} ({(gmIsFMHH ? string.Empty : Utils.GetDisplayRoleName(key, true))}{(gmIsFM ? string.Empty : Utils.GetSubRolesText(key, summary: true))}) [{Utils.GetVitalText(key)}]"); + if (killerId != byte.MaxValue && killerId != key) sb.Append($"\n\t⇐ {Main.AllPlayerNames[killerId]} ({(gmIsFMHH ? string.Empty : Utils.GetDisplayRoleName(killerId, true))}{(gmIsFM ? string.Empty : Utils.GetSubRolesText(killerId, summary: true))})"); + } + + KillLog = sb.ToString(); + if (!KillLog.Contains('\n')) KillLog = string.Empty; + + Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; + + EndGameResult.CachedWinners = new(); + + HashSet winner = Main.AllPlayerControls.Where(pc => CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)).ToHashSet(); + + foreach (CustomRoles team in CustomWinnerHolder.WinnerRoles) winner.UnionWith(Main.AllPlayerControls.Where(p => p.Is(team) && !winner.Contains(p))); + + Main.WinnerNameList = []; + Main.WinnerList = []; + + foreach (PlayerControl pc in winner) + { + if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw && pc.Is(CustomRoles.GM)) continue; + + EndGameResult.CachedWinners.Add(new(pc.Data)); + Main.WinnerList.Add(pc.PlayerId); + Main.WinnerNameList.Add(pc.GetRealName()); + } + + Arsonist.IsDoused = []; + Revolutionist.IsDraw = []; + Farseer.IsRevealed = []; + + Main.VisibleTasksCount = false; + + CustomNetObject.Reset(); + Main.LoversPlayers.Clear(); + Bloodmoon.OnMeetingStart(); + AFKDetector.ExemptedPlayers.Clear(); + + foreach (PlayerState state in Main.PlayerStates.Values) state.Role.Init(); + + if (AmongUsClient.Instance.AmHost) + { + Main.RealOptionsData.Restore(GameOptionsManager.Instance.CurrentGameOptions); + GameOptionsSender.AllSenders.Clear(); + GameOptionsSender.AllSenders.Add(new NormalGameOptionsSender()); + + switch (Options.CurrentGameMode) { - DateTime date = value.RealKiller.TimeStamp; - if (date == DateTime.MinValue) continue; - - byte killerId = value.GetRealKiller(); - bool gmIsFM = Options.CurrentGameMode is CustomGameMode.FFA or CustomGameMode.MoveAndStop; - bool gmIsFMHH = gmIsFM || Options.CurrentGameMode is CustomGameMode.HotPotato or CustomGameMode.HideAndSeek or CustomGameMode.Speedrun or CustomGameMode.CaptureTheFlag or CustomGameMode.NaturalDisasters or CustomGameMode.RoomRush or CustomGameMode.AllInOne; - sb.Append($"\n{date:T} {Main.AllPlayerNames[key]} ({(gmIsFMHH ? string.Empty : Utils.GetDisplayRoleName(key, true))}{(gmIsFM ? string.Empty : Utils.GetSubRolesText(key, summary: true))}) [{Utils.GetVitalText(key)}]"); - if (killerId != byte.MaxValue && killerId != key) sb.Append($"\n\t⇐ {Main.AllPlayerNames[killerId]} ({(gmIsFMHH ? string.Empty : Utils.GetDisplayRoleName(killerId, true))}{(gmIsFM ? string.Empty : Utils.GetSubRolesText(killerId, summary: true))})"); - } - - KillLog = sb.ToString(); - if (!KillLog.Contains('\n')) KillLog = string.Empty; - - Main.NormalOptions.KillCooldown = Options.DefaultKillCooldown; - - EndGameResult.CachedWinners = new(); - - HashSet winner = Main.AllPlayerControls.Where(pc => CustomWinnerHolder.WinnerIds.Contains(pc.PlayerId)).ToHashSet(); - - foreach (CustomRoles team in CustomWinnerHolder.WinnerRoles) winner.UnionWith(Main.AllPlayerControls.Where(p => p.Is(team) && !winner.Contains(p))); - - Main.WinnerNameList = []; - Main.WinnerList = []; - - foreach (PlayerControl pc in winner) - { - if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw && pc.Is(CustomRoles.GM)) continue; - - EndGameResult.CachedWinners.Add(new(pc.Data)); - Main.WinnerList.Add(pc.PlayerId); - Main.WinnerNameList.Add(pc.GetRealName()); - } - - Arsonist.IsDoused = []; - Revolutionist.IsDraw = []; - Farseer.IsRevealed = []; - - Main.VisibleTasksCount = false; - - CustomNetObject.Reset(); - Main.LoversPlayers.Clear(); - Bloodmoon.OnMeetingStart(); - AFKDetector.ExemptedPlayers.Clear(); - - foreach (PlayerState state in Main.PlayerStates.Values) state.Role.Init(); - - if (AmongUsClient.Instance.AmHost) - { - Main.RealOptionsData.Restore(GameOptionsManager.Instance.CurrentGameOptions); - GameOptionsSender.AllSenders.Clear(); - GameOptionsSender.AllSenders.Add(new NormalGameOptionsSender()); - - switch (Options.CurrentGameMode) - { - case CustomGameMode.MoveAndStop: - Main.AllPlayerControls.Do(x => MoveAndStop.HasPlayed.Add(x.FriendCode)); - break; - case CustomGameMode.RoomRush: - Main.AllPlayerControls.Do(x => RoomRush.HasPlayedFriendCodes.Add(x.FriendCode)); - break; - } + case CustomGameMode.MoveAndStop: + Main.AllPlayerControls.Do(x => MoveAndStop.HasPlayed.Add(x.FriendCode)); + break; + case CustomGameMode.RoomRush: + Main.AllPlayerControls.Do(x => RoomRush.HasPlayedFriendCodes.Add(x.FriendCode)); + break; } } } +} - [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))] - internal static class SetEverythingUpPatch +[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))] +internal static class SetEverythingUpPatch +{ + public static string LastWinsText = string.Empty; + public static string LastWinsReason = string.Empty; + + public static void Postfix(EndGameManager __instance) { - public static string LastWinsText = string.Empty; - public static string LastWinsReason = string.Empty; + //####################################### + // ==Victory faction display== + //####################################### - public static void Postfix(EndGameManager __instance) + try { - //####################################### - // ==Victory faction display== - //####################################### + // ---------- Code from TOR (The Other Roles)! ---------- + // https://github.com/TheOtherRolesAU/TheOtherRoles/blob/main/TheOtherRoles/Patches/EndGamePatch.cs - try + if (Options.CurrentGameMode is not CustomGameMode.Standard) goto End; + + int num = Mathf.CeilToInt(7.5f); + + Il2CppArrayBase pbs = __instance?.transform.GetComponentsInChildren(); + + if (pbs != null) + foreach (PoolablePlayer pb in pbs) + pb.ToggleName(false); + + List list = EndGameResult.CachedWinners.ToArray().ToList(); + + for (var i = 0; i < list.Count; i++) { - // ---------- Code from TOR (The Other Roles)! ---------- - // https://github.com/TheOtherRolesAU/TheOtherRoles/blob/main/TheOtherRoles/Patches/EndGamePatch.cs + CachedPlayerData data = list[i]; + int num2 = i % 2 == 0 ? -1 : 1; + int num3 = (i + 1) / 2; + float num4 = num3 / (float)num; + float num5 = Mathf.Lerp(1f, 0.75f, num4); + float num6 = i == 0 ? -8 : -1; + PoolablePlayer poolablePlayer = Object.Instantiate(__instance?.PlayerPrefab, __instance?.transform); + poolablePlayer.transform.localPosition = new Vector3(1f * num2 * num3 * num5, FloatRange.SpreadToEdges(-1.125f, 0f, num3, num), num6 + (num3 * 0.01f)) * 0.9f; + float num7 = Mathf.Lerp(1f, 0.65f, num4) * 0.9f; + Vector3 vector = new(num7, num7, 1f); + poolablePlayer.transform.localScale = vector; + poolablePlayer.UpdateFromPlayerOutfit(data.Outfit, PlayerMaterial.MaskType.ComplexUI, data.IsDead, true); - if (Options.CurrentGameMode is not CustomGameMode.Standard) goto End; - - int num = Mathf.CeilToInt(7.5f); - - Il2CppArrayBase pbs = __instance?.transform.GetComponentsInChildren(); - - if (pbs != null) - foreach (PoolablePlayer pb in pbs) - pb.ToggleName(false); - - List list = EndGameResult.CachedWinners.ToArray().ToList(); - - for (var i = 0; i < list.Count; i++) + if (data.IsDead) { - CachedPlayerData data = list[i]; - int num2 = i % 2 == 0 ? -1 : 1; - int num3 = (i + 1) / 2; - float num4 = num3 / (float)num; - float num5 = Mathf.Lerp(1f, 0.75f, num4); - float num6 = i == 0 ? -8 : -1; - PoolablePlayer poolablePlayer = Object.Instantiate(__instance?.PlayerPrefab, __instance?.transform); - poolablePlayer.transform.localPosition = new Vector3(1f * num2 * num3 * num5, FloatRange.SpreadToEdges(-1.125f, 0f, num3, num), num6 + (num3 * 0.01f)) * 0.9f; - float num7 = Mathf.Lerp(1f, 0.65f, num4) * 0.9f; - Vector3 vector = new(num7, num7, 1f); - poolablePlayer.transform.localScale = vector; - poolablePlayer.UpdateFromPlayerOutfit(data.Outfit, PlayerMaterial.MaskType.ComplexUI, data.IsDead, true); + poolablePlayer.cosmetics.currentBodySprite.BodySprite.sprite = poolablePlayer.cosmetics.currentBodySprite.GhostSprite; + poolablePlayer.SetDeadFlipX(i % 2 == 0); + } + else + poolablePlayer.SetFlipX(i % 2 == 0); - if (data.IsDead) - { - poolablePlayer.cosmetics.currentBodySprite.BodySprite.sprite = poolablePlayer.cosmetics.currentBodySprite.GhostSprite; - poolablePlayer.SetDeadFlipX(i % 2 == 0); - } - else - poolablePlayer.SetFlipX(i % 2 == 0); + bool lowered = i is 1 or 2 or 5 or 6 or 9 or 10 or 13 or 14; - bool lowered = i is 1 or 2 or 5 or 6 or 9 or 10 or 13 or 14; + poolablePlayer.cosmetics.nameText.color = Color.white; + poolablePlayer.cosmetics.nameText.transform.localScale = new(1f / vector.x, 1f / vector.y, 1f / vector.z); + poolablePlayer.cosmetics.nameText.text = data.PlayerName; - poolablePlayer.cosmetics.nameText.color = Color.white; - poolablePlayer.cosmetics.nameText.transform.localScale = new(1f / vector.x, 1f / vector.y, 1f / vector.z); - poolablePlayer.cosmetics.nameText.text = data.PlayerName; + Vector3 defaultPos = poolablePlayer.cosmetics.nameText.transform.localPosition; - Vector3 defaultPos = poolablePlayer.cosmetics.nameText.transform.localPosition; + for (var j = 0; j < Main.WinnerList.Count; j++) + { + byte id = Main.WinnerList[j]; + if (Main.WinnerNameList[j].RemoveHtmlTags() != data.PlayerName.RemoveHtmlTags()) continue; - for (var j = 0; j < Main.WinnerList.Count; j++) - { - byte id = Main.WinnerList[j]; - if (Main.WinnerNameList[j].RemoveHtmlTags() != data.PlayerName.RemoveHtmlTags()) continue; + CustomRoles role = Main.PlayerStates[id].MainRole; - CustomRoles role = Main.PlayerStates[id].MainRole; + string color = Main.RoleColors[role]; + string rolename = Utils.GetRoleName(role); - string color = Main.RoleColors[role]; - string rolename = Utils.GetRoleName(role); - - poolablePlayer.cosmetics.nameText.text += $"\n{rolename}"; - poolablePlayer.cosmetics.nameText.transform.localPosition = new(defaultPos.x, !lowered ? defaultPos.y - 0.6f : defaultPos.y - 1.4f, -15f); - } + poolablePlayer.cosmetics.nameText.text += $"\n{rolename}"; + poolablePlayer.cosmetics.nameText.transform.localPosition = new(defaultPos.x, !lowered ? defaultPos.y - 0.6f : defaultPos.y - 1.4f, -15f); } } - catch (Exception e) + } + catch (Exception e) + { + Logger.Error(e.ToString(), "OutroPatch.SetEverythingUpPatch.Postfix"); + } + + End: + + __instance.WinText.alignment = TextAlignmentOptions.Center; + GameObject WinnerTextObject = Object.Instantiate(__instance.WinText.gameObject); + WinnerTextObject.transform.position = new(__instance.WinText.transform.position.x, __instance.WinText.transform.position.y - 0.5f, __instance.WinText.transform.position.z); + WinnerTextObject.transform.localScale = new(0.6f, 0.6f, 0.6f); + var WinnerText = WinnerTextObject.GetComponent(); + WinnerText.fontSizeMin = 3f; + WinnerText.text = string.Empty; + + var CustomWinnerText = string.Empty; + var AdditionalWinnerText = string.Empty; + string CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Crewmate); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: { - Logger.Error(e.ToString(), "OutroPatch.SetEverythingUpPatch.Postfix"); + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.WinText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + __instance.WinText.fontSize -= 5f; + __instance.WinText.color = Main.PlayerColors[winnerId]; + __instance.BackgroundBar.material.color = new Color32(245, 82, 82, 255); + WinnerText.text = $"{GetString("ModeSoloKombat")}"; + WinnerText.color = Color.red; + goto EndOfText; } - - End: - - __instance.WinText.alignment = TextAlignmentOptions.Center; - GameObject WinnerTextObject = Object.Instantiate(__instance.WinText.gameObject); - WinnerTextObject.transform.position = new(__instance.WinText.transform.position.x, __instance.WinText.transform.position.y - 0.5f, __instance.WinText.transform.position.z); - WinnerTextObject.transform.localScale = new(0.6f, 0.6f, 0.6f); - var WinnerText = WinnerTextObject.GetComponent(); - WinnerText.fontSizeMin = 3f; - WinnerText.text = string.Empty; - - var CustomWinnerText = string.Empty; - var AdditionalWinnerText = string.Empty; - string CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Crewmate); - - switch (Options.CurrentGameMode) + case CustomGameMode.FFA: { - case CustomGameMode.SoloKombat: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.WinText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - __instance.WinText.fontSize -= 5f; - __instance.WinText.color = Main.PlayerColors[winnerId]; - __instance.BackgroundBar.material.color = new Color32(245, 82, 82, 255); - WinnerText.text = $"{GetString("ModeSoloKombat")}"; - WinnerText.color = Color.red; - goto EndOfText; - } - case CustomGameMode.FFA: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = new Color32(0, 255, 255, 255); - WinnerText.text = FFAManager.FFATeamMode.GetBool() ? string.Empty : Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } - case CustomGameMode.MoveAndStop: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = new Color32(0, 255, 165, 255); - WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } - case CustomGameMode.HotPotato: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = new Color32(232, 205, 70, 255); - WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } - case CustomGameMode.Speedrun: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Speedrunner); - WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } - case CustomGameMode.CaptureTheFlag: - { - (Color Color, string Team) winnerData = CTFManager.WinnerData; - __instance.BackgroundBar.material.color = winnerData.Color; - WinnerText.text = winnerData.Team; - WinnerText.color = winnerData.Color; - goto EndOfText; - } - case CustomGameMode.NaturalDisasters: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = new Color32(3, 252, 74, 255); - WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } - case CustomGameMode.RoomRush: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = new Color32(255, 171, 27, 255); - WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } - case CustomGameMode.AllInOne: - { - byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); - __instance.BackgroundBar.material.color = new Color32(245, 66, 173, 255); - WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); - WinnerText.color = Main.PlayerColors[winnerId]; - goto EndOfText; - } + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = new Color32(0, 255, 255, 255); + WinnerText.text = FFAManager.FFATeamMode.GetBool() ? string.Empty : Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } - - if (CustomWinnerHolder.WinnerTeam == CustomWinner.CustomTeam) + case CustomGameMode.MoveAndStop: { - CustomTeamManager.CustomTeam team = CustomTeamManager.WinnerTeam; - CustomWinnerText = string.Format(GetString("CustomWinnerText"), team.TeamName); - CustomWinnerColor = team.RoleRevealScreenBackgroundColor == "*" ? Main.NeutralColor : team.RoleRevealScreenBackgroundColor; - __instance.BackgroundBar.material.color = ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color color) ? color : Utils.GetRoleColor(CustomRoles.Sprayer); - AdditionalWinnerText = $"\n{team.TeamMembers.Where(r => Main.PlayerStates.Values.Any(x => x.MainRole == r)).Join(x => x.ToColoredString())}{GetString("Win")}"; - goto Skip; + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = new Color32(0, 255, 165, 255); + WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } - - var winnerRole = (CustomRoles)CustomWinnerHolder.WinnerTeam; - - if (winnerRole >= 0) + case CustomGameMode.HotPotato: { - CustomWinnerText = GetWinnerRoleName(winnerRole); - CustomWinnerColor = Utils.GetRoleColorCode(winnerRole); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = new Color32(232, 205, 70, 255); + WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } - - if (AmongUsClient.Instance.AmHost && Main.PlayerStates[0].MainRole == CustomRoles.GM) + case CustomGameMode.Speedrun: { - __instance.WinText.text = GetString("GameOver"); - __instance.WinText.color = Utils.GetRoleColor(CustomRoles.GM); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Speedrunner); + WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } - - switch (CustomWinnerHolder.WinnerTeam) + case CustomGameMode.CaptureTheFlag: { - case CustomWinner.Crewmate: - CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Crewmate); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Crewmate); - break; - case CustomWinner.Impostor: - CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Impostor); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Impostor); - break; - case CustomWinner.Egoist: - CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Egoist); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Egoist); - break; - case CustomWinner.Terrorist: - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Terrorist); - break; - case CustomWinner.Lovers: - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Lovers); - break; - case CustomWinner.Specter: - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Specter); - break; - case CustomWinner.Draw: - __instance.WinText.text = GetString("ForceEnd"); - __instance.WinText.color = Color.white; - __instance.BackgroundBar.material.color = Color.gray; - WinnerText.text = GetString("ForceEndText"); - WinnerText.color = Color.gray; - break; - case CustomWinner.Neutrals: - __instance.WinText.text = GetString("DefeatText"); - __instance.WinText.color = Utils.GetRoleColor(CustomRoles.Impostor); - __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Executioner); - WinnerText.text = GetString("NeutralsLeftText"); - WinnerText.color = Utils.GetRoleColor(CustomRoles.Executioner); - break; - case CustomWinner.None: - __instance.WinText.text = string.Empty; - __instance.WinText.color = Color.black; - __instance.BackgroundBar.material.color = Color.gray; - WinnerText.text = GetString("EveryoneDied"); - WinnerText.color = Color.gray; - break; - case CustomWinner.Error: - __instance.WinText.text = GetString("ErrorEndText"); - __instance.WinText.color = Color.red; - __instance.BackgroundBar.material.color = Color.red; - WinnerText.text = GetString("ErrorEndTextDescription"); - WinnerText.color = Color.white; - break; + (Color Color, string Team) winnerData = CTFManager.WinnerData; + __instance.BackgroundBar.material.color = winnerData.Color; + WinnerText.text = winnerData.Team; + WinnerText.color = winnerData.Color; + goto EndOfText; } - - foreach (AdditionalWinners additionalWinners in CustomWinnerHolder.AdditionalWinnerTeams) + case CustomGameMode.NaturalDisasters: { - var addWinnerRole = (CustomRoles)additionalWinners; - Logger.Warn(additionalWinners.ToString(), "AdditionalWinner"); - if (addWinnerRole == CustomRoles.Sidekick) continue; - - AdditionalWinnerText += "\n" + Utils.ColorString(Utils.GetRoleColor(addWinnerRole), GetAdditionalWinnerRoleName(addWinnerRole)); + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = new Color32(3, 252, 74, 255); + WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } - - Skip: - - if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw and not CustomWinner.None and not CustomWinner.Error) WinnerText.text = AdditionalWinnerText == string.Empty ? $"{CustomWinnerText}" : $"{CustomWinnerText}{AdditionalWinnerText}"; - - EndOfText: - - LastWinsText = WinnerText.text /*.RemoveHtmlTags()*/; - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - //######################################## - // ==The final result indicates== - //######################################## - - Vector3 Pos = Camera.main.ViewportToWorldPoint(new(0f, 1f, Camera.main.nearClipPlane)); - GameObject RoleSummaryObject = Object.Instantiate(__instance.WinText.gameObject); - RoleSummaryObject.transform.position = new(__instance.Navigation.ExitButton.transform.position.x + 0.1f, Pos.y - 0.1f, -15f); - RoleSummaryObject.transform.localScale = new(1f, 1f, 1f); - - StringBuilder sb = new($"{GetString("RoleSummaryText")}\n"); - List cloneRoles = [.. Main.PlayerStates.Keys]; - - foreach (byte id in Main.WinnerList) + case CustomGameMode.RoomRush: { - if (EndGamePatch.SummaryText[id].Contains("")) continue; - - sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - cloneRoles.Remove(id); + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = new Color32(255, 171, 27, 255); + WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } - - sb.Append("\n"); - - switch (Options.CurrentGameMode) + case CustomGameMode.AllInOne: { - case CustomGameMode.SoloKombat: - { - List<(int, byte)> list = []; - list.AddRange(cloneRoles.Select(id => (SoloKombatManager.GetRankOfScore(id), id))); - - list.Sort(); - foreach ((int, byte) id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) sb.Append('\n').Append(EndGamePatch.SummaryText[id.Item2]); - - break; - } - case CustomGameMode.FFA: - { - List<(int, byte)> list = []; - list.AddRange(cloneRoles.Select(id => (FFAManager.GetRankFromScore(id), id))); - - list.Sort(); - foreach ((int, byte) id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) sb.Append('\n').Append(EndGamePatch.SummaryText[id.Item2]); - - break; - } - case CustomGameMode.MoveAndStop: - { - List<(int, byte)> list = []; - list.AddRange(cloneRoles.Select(id => (MoveAndStop.GetRankFromScore(id), id))); - - list.Sort(); - foreach ((int, byte) id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) sb.Append('\n').Append(EndGamePatch.SummaryText[id.Item2]); - - break; - } - case CustomGameMode.HotPotato: - { - IOrderedEnumerable list = cloneRoles.OrderByDescending(HotPotato.GetSurvivalTime); - foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - - break; - } - case CustomGameMode.Speedrun: - { - IOrderedEnumerable list = cloneRoles.OrderByDescending(id => Main.PlayerStates[id].TaskState.CompletedTasksCount); - foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - - break; - } - case CustomGameMode.CaptureTheFlag: - { - IOrderedEnumerable list = cloneRoles.OrderByDescending(CTFManager.GetFlagTime); - foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - - break; - } - case CustomGameMode.NaturalDisasters: - { - IOrderedEnumerable list = cloneRoles.OrderByDescending(NaturalDisasters.SurvivalTime); - foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - - break; - } - case CustomGameMode.RoomRush: - { - IOrderedEnumerable list = cloneRoles.OrderByDescending(RoomRush.GetSurvivalTime); - foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - - break; - } - default: - { - foreach (byte id in cloneRoles) - { - if (EndGamePatch.SummaryText[id].Contains("")) continue; - - sb.Append('\n').Append(EndGamePatch.SummaryText[id]); - } - - break; - } + byte winnerId = CustomWinnerHolder.WinnerIds.FirstOrDefault(); + __instance.BackgroundBar.material.color = new Color32(245, 66, 173, 255); + WinnerText.text = Main.AllPlayerNames[winnerId] + GetString("Win"); + WinnerText.color = Main.PlayerColors[winnerId]; + goto EndOfText; } + } - sb.Append(""); + if (CustomWinnerHolder.WinnerTeam == CustomWinner.CustomTeam) + { + CustomTeamManager.CustomTeam team = CustomTeamManager.WinnerTeam; + CustomWinnerText = string.Format(GetString("CustomWinnerText"), team.TeamName); + CustomWinnerColor = team.RoleRevealScreenBackgroundColor == "*" ? Main.NeutralColor : team.RoleRevealScreenBackgroundColor; + __instance.BackgroundBar.material.color = ColorUtility.TryParseHtmlString(team.RoleRevealScreenBackgroundColor, out Color color) ? color : Utils.GetRoleColor(CustomRoles.Sprayer); + AdditionalWinnerText = $"\n{team.TeamMembers.Where(r => Main.PlayerStates.Values.Any(x => x.MainRole == r)).Join(x => x.ToColoredString())}{GetString("Win")}"; + goto Skip; + } - var RoleSummary = RoleSummaryObject.GetComponent(); - RoleSummary.alignment = TextAlignmentOptions.TopLeft; - RoleSummary.color = Color.white; - RoleSummary.outlineWidth *= 1.2f; - RoleSummary.fontSizeMin = RoleSummary.fontSizeMax = RoleSummary.fontSize = 1.25f; + var winnerRole = (CustomRoles)CustomWinnerHolder.WinnerTeam; - var RoleSummaryRectTransform = RoleSummary.GetComponent(); - RoleSummaryRectTransform.anchoredPosition = new(Pos.x + 3.5f, Pos.y - 0.1f); - RoleSummary.text = sb.ToString(); + if (winnerRole >= 0) + { + CustomWinnerText = GetWinnerRoleName(winnerRole); + CustomWinnerColor = Utils.GetRoleColorCode(winnerRole); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + } - return; + if (AmongUsClient.Instance.AmHost && Main.PlayerStates[0].MainRole == CustomRoles.GM) + { + __instance.WinText.text = GetString("GameOver"); + __instance.WinText.color = Utils.GetRoleColor(CustomRoles.GM); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(winnerRole); + } - static string GetAdditionalWinnerRoleName(CustomRoles role) + switch (CustomWinnerHolder.WinnerTeam) + { + case CustomWinner.Crewmate: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Crewmate); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Crewmate); + break; + case CustomWinner.Impostor: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Impostor); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Impostor); + break; + case CustomWinner.Egoist: + CustomWinnerColor = Utils.GetRoleColorCode(CustomRoles.Egoist); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Egoist); + break; + case CustomWinner.Terrorist: + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Terrorist); + break; + case CustomWinner.Lovers: + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Lovers); + break; + case CustomWinner.Specter: + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Specter); + break; + case CustomWinner.Draw: + __instance.WinText.text = GetString("ForceEnd"); + __instance.WinText.color = Color.white; + __instance.BackgroundBar.material.color = Color.gray; + WinnerText.text = GetString("ForceEndText"); + WinnerText.color = Color.gray; + break; + case CustomWinner.Neutrals: + __instance.WinText.text = GetString("DefeatText"); + __instance.WinText.color = Utils.GetRoleColor(CustomRoles.Impostor); + __instance.BackgroundBar.material.color = Utils.GetRoleColor(CustomRoles.Executioner); + WinnerText.text = GetString("NeutralsLeftText"); + WinnerText.color = Utils.GetRoleColor(CustomRoles.Executioner); + break; + case CustomWinner.None: + __instance.WinText.text = string.Empty; + __instance.WinText.color = Color.black; + __instance.BackgroundBar.material.color = Color.gray; + WinnerText.text = GetString("EveryoneDied"); + WinnerText.color = Color.gray; + break; + case CustomWinner.Error: + __instance.WinText.text = GetString("ErrorEndText"); + __instance.WinText.color = Color.red; + __instance.BackgroundBar.material.color = Color.red; + WinnerText.text = GetString("ErrorEndTextDescription"); + WinnerText.color = Color.white; + break; + } + + foreach (AdditionalWinners additionalWinners in CustomWinnerHolder.AdditionalWinnerTeams) + { + var addWinnerRole = (CustomRoles)additionalWinners; + Logger.Warn(additionalWinners.ToString(), "AdditionalWinner"); + if (addWinnerRole == CustomRoles.Sidekick) continue; + + AdditionalWinnerText += "\n" + Utils.ColorString(Utils.GetRoleColor(addWinnerRole), GetAdditionalWinnerRoleName(addWinnerRole)); + } + + Skip: + + if (CustomWinnerHolder.WinnerTeam is not CustomWinner.Draw and not CustomWinner.None and not CustomWinner.Error) WinnerText.text = AdditionalWinnerText == string.Empty ? $"{CustomWinnerText}" : $"{CustomWinnerText}{AdditionalWinnerText}"; + + EndOfText: + + LastWinsText = WinnerText.text /*.RemoveHtmlTags()*/; + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + //######################################## + // ==The final result indicates== + //######################################## + + Vector3 Pos = Camera.main.ViewportToWorldPoint(new(0f, 1f, Camera.main.nearClipPlane)); + GameObject RoleSummaryObject = Object.Instantiate(__instance.WinText.gameObject); + RoleSummaryObject.transform.position = new(__instance.Navigation.ExitButton.transform.position.x + 0.1f, Pos.y - 0.1f, -15f); + RoleSummaryObject.transform.localScale = new(1f, 1f, 1f); + + StringBuilder sb = new($"{GetString("RoleSummaryText")}\n"); + List cloneRoles = [.. Main.PlayerStates.Keys]; + + foreach (byte id in Main.WinnerList) + { + if (EndGamePatch.SummaryText[id].Contains("")) continue; + + sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + cloneRoles.Remove(id); + } + + sb.Append("\n"); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: { - string name = GetString($"AdditionalWinnerRoleText.{role}"); - if (name == string.Empty || name.StartsWith("*") || name.StartsWith(" list = []; + list.AddRange(cloneRoles.Select(id => (SoloKombatManager.GetRankOfScore(id), id))); - return name; + list.Sort(); + foreach ((int, byte) id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) sb.Append('\n').Append(EndGamePatch.SummaryText[id.Item2]); + + break; } - - static string GetWinnerRoleName(CustomRoles role) + case CustomGameMode.FFA: { - string name = GetString($"WinnerRoleText.{role}"); - if (name == string.Empty || name.StartsWith("*") || name.StartsWith(" list = []; + list.AddRange(cloneRoles.Select(id => (FFAManager.GetRankFromScore(id), id))); - return name; + list.Sort(); + foreach ((int, byte) id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) sb.Append('\n').Append(EndGamePatch.SummaryText[id.Item2]); + + break; } + case CustomGameMode.MoveAndStop: + { + List<(int, byte)> list = []; + list.AddRange(cloneRoles.Select(id => (MoveAndStop.GetRankFromScore(id), id))); + + list.Sort(); + foreach ((int, byte) id in list.Where(x => EndGamePatch.SummaryText.ContainsKey(x.Item2))) sb.Append('\n').Append(EndGamePatch.SummaryText[id.Item2]); + + break; + } + case CustomGameMode.HotPotato: + { + IOrderedEnumerable list = cloneRoles.OrderByDescending(HotPotato.GetSurvivalTime); + foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + + break; + } + case CustomGameMode.Speedrun: + { + IOrderedEnumerable list = cloneRoles.OrderByDescending(id => Main.PlayerStates[id].TaskState.CompletedTasksCount); + foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + + break; + } + case CustomGameMode.CaptureTheFlag: + { + IOrderedEnumerable list = cloneRoles.OrderByDescending(CTFManager.GetFlagTime); + foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + + break; + } + case CustomGameMode.NaturalDisasters: + { + IOrderedEnumerable list = cloneRoles.OrderByDescending(NaturalDisasters.SurvivalTime); + foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + + break; + } + case CustomGameMode.RoomRush: + { + IOrderedEnumerable list = cloneRoles.OrderByDescending(RoomRush.GetSurvivalTime); + foreach (byte id in list.Where(EndGamePatch.SummaryText.ContainsKey)) sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + + break; + } + default: + { + foreach (byte id in cloneRoles) + { + if (EndGamePatch.SummaryText[id].Contains("")) continue; + + sb.Append('\n').Append(EndGamePatch.SummaryText[id]); + } + + break; + } + } + + sb.Append(""); + + var RoleSummary = RoleSummaryObject.GetComponent(); + RoleSummary.alignment = TextAlignmentOptions.TopLeft; + RoleSummary.color = Color.white; + RoleSummary.outlineWidth *= 1.2f; + RoleSummary.fontSizeMin = RoleSummary.fontSizeMax = RoleSummary.fontSize = 1.25f; + + var RoleSummaryRectTransform = RoleSummary.GetComponent(); + RoleSummaryRectTransform.anchoredPosition = new(Pos.x + 3.5f, Pos.y - 0.1f); + RoleSummary.text = sb.ToString(); + + return; + + static string GetAdditionalWinnerRoleName(CustomRoles role) + { + string name = GetString($"AdditionalWinnerRoleText.{role}"); + if (name == string.Empty || name.StartsWith("*") || name.StartsWith(" LastProcess = []; - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.TryPet))] - internal static class LocalPetPatch + public static bool Prefix(PlayerControl __instance) { - private static readonly Dictionary LastProcess = []; + if (!Options.UsePets.GetBool()) return true; + if (!(AmongUsClient.Instance.AmHost && AmongUsClient.Instance.AmClient)) return true; + if (GameStates.IsLobby || !__instance.IsAlive()) return true; + if (__instance.petting) return true; - public static bool Prefix(PlayerControl __instance) - { - if (!Options.UsePets.GetBool()) return true; - if (!(AmongUsClient.Instance.AmHost && AmongUsClient.Instance.AmClient)) return true; - if (GameStates.IsLobby || !__instance.IsAlive()) return true; - if (__instance.petting) return true; + __instance.petting = true; - __instance.petting = true; + if (!LastProcess.ContainsKey(__instance.PlayerId)) LastProcess.TryAdd(__instance.PlayerId, Utils.TimeStamp - 2); + if (LastProcess[__instance.PlayerId] + 1 >= Utils.TimeStamp) return true; - if (!LastProcess.ContainsKey(__instance.PlayerId)) LastProcess.TryAdd(__instance.PlayerId, Utils.TimeStamp - 2); - if (LastProcess[__instance.PlayerId] + 1 >= Utils.TimeStamp) return true; + ExternalRpcPetPatch.Prefix(__instance.MyPhysics, (byte)RpcCalls.Pet); - ExternalRpcPetPatch.Prefix(__instance.MyPhysics, (byte)RpcCalls.Pet); - - LastProcess[__instance.PlayerId] = Utils.TimeStamp; - return !__instance.GetCustomRole().PetActivatedAbility(); - } - - public static void Postfix(PlayerControl __instance) - { - if (!Options.UsePets.GetBool()) return; - if (!(AmongUsClient.Instance.AmHost && AmongUsClient.Instance.AmClient)) return; - - __instance.petting = false; - } + LastProcess[__instance.PlayerId] = Utils.TimeStamp; + return !__instance.GetCustomRole().PetActivatedAbility(); } - [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleRpc))] - internal static class ExternalRpcPetPatch + public static void Postfix(PlayerControl __instance) { - private static readonly Dictionary LastProcess = []; + if (!Options.UsePets.GetBool()) return; + if (!(AmongUsClient.Instance.AmHost && AmongUsClient.Instance.AmClient)) return; - public static void Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] byte callID) + __instance.petting = false; + } +} + +[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleRpc))] +internal static class ExternalRpcPetPatch +{ + private static readonly Dictionary LastProcess = []; + + public static void Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] byte callID) + { + if (GameStates.IsLobby || !Options.UsePets.GetBool() || !AmongUsClient.Instance.AmHost || (RpcCalls)callID != RpcCalls.Pet) return; + + PlayerControl pc = __instance.myPlayer; + PlayerPhysics physics = __instance; + + if (pc == null || !pc.IsAlive()) return; + + if (!pc.inVent + && !pc.inMovingPlat + && !pc.walkingToVent + && !pc.onLadder + && !physics.Animations.IsPlayingEnterVentAnimation() + && !physics.Animations.IsPlayingClimbAnimation() + && !physics.Animations.IsPlayingAnyLadderAnimation() + && !Pelican.IsEaten(pc.PlayerId) + && GameStates.IsInTask + && pc.GetCustomRole().PetActivatedAbility()) { - if (GameStates.IsLobby || !Options.UsePets.GetBool() || !AmongUsClient.Instance.AmHost || (RpcCalls)callID != RpcCalls.Pet) return; + CancelPet(); + LateTask.New(CancelPet, 0.4f, log: false); - PlayerControl pc = __instance.myPlayer; - PlayerPhysics physics = __instance; - - if (pc == null || !pc.IsAlive()) return; - - if (!pc.inVent - && !pc.inMovingPlat - && !pc.walkingToVent - && !pc.onLadder - && !physics.Animations.IsPlayingEnterVentAnimation() - && !physics.Animations.IsPlayingClimbAnimation() - && !physics.Animations.IsPlayingAnyLadderAnimation() - && !Pelican.IsEaten(pc.PlayerId) - && GameStates.IsInTask - && pc.GetCustomRole().PetActivatedAbility()) + void CancelPet() { - CancelPet(); - LateTask.New(CancelPet, 0.4f, log: false); - - void CancelPet() - { - physics.CancelPet(); - MessageWriter w = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.CancelPet, SendOption.None); - AmongUsClient.Instance.FinishRpcImmediately(w); - } + physics.CancelPet(); + MessageWriter w = AmongUsClient.Instance.StartRpcImmediately(physics.NetId, (byte)RpcCalls.CancelPet, SendOption.None); + AmongUsClient.Instance.FinishRpcImmediately(w); } - - if (!LastProcess.ContainsKey(pc.PlayerId)) LastProcess.TryAdd(pc.PlayerId, Utils.TimeStamp - 2); - if (LastProcess[pc.PlayerId] + 1 >= Utils.TimeStamp) return; - - LastProcess[pc.PlayerId] = Utils.TimeStamp; - - Logger.Info($"Player {pc.GetNameWithRole().RemoveHtmlTags()} petted their pet", "PetActionTrigger"); - - LateTask.New(() => OnPetUse(pc), 0.2f, $"OnPetUse: {pc.GetNameWithRole().RemoveHtmlTags()}", false); } - public static void OnPetUse(PlayerControl pc) + if (!LastProcess.ContainsKey(pc.PlayerId)) LastProcess.TryAdd(pc.PlayerId, Utils.TimeStamp - 2); + if (LastProcess[pc.PlayerId] + 1 >= Utils.TimeStamp) return; + + LastProcess[pc.PlayerId] = Utils.TimeStamp; + + Logger.Info($"Player {pc.GetNameWithRole().RemoveHtmlTags()} petted their pet", "PetActionTrigger"); + + LateTask.New(() => OnPetUse(pc), 0.2f, $"OnPetUse: {pc.GetNameWithRole().RemoveHtmlTags()}", false); + } + + public static void OnPetUse(PlayerControl pc) + { + if (pc == null || + pc.inVent || + pc.inMovingPlat || + pc.onLadder || + pc.walkingToVent || + pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() || + pc.MyPhysics.Animations.IsPlayingClimbAnimation() || + pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || + Pelican.IsEaten(pc.PlayerId) || + Penguin.IsVictim(pc) || + !AmongUsClient.Instance.AmHost || + GameStates.IsLobby + ) + return; + + if (CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated()) { - if (pc == null || - pc.inVent || - pc.inMovingPlat || - pc.onLadder || - pc.walkingToVent || - pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() || - pc.MyPhysics.Animations.IsPlayingClimbAnimation() || - pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || - Pelican.IsEaten(pc.PlayerId) || - Penguin.IsVictim(pc) || - !AmongUsClient.Instance.AmHost || - GameStates.IsLobby - ) - return; - - if (CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated()) - { - CTFManager.TryPickUpFlag(pc); - return; - } - - if (Mastermind.ManipulatedPlayers.ContainsKey(pc.PlayerId)) - { - PlayerControl killTarget = SelectKillButtonTarget(pc); - if (killTarget != null) Mastermind.ForceKillForManipulatedPlayer(pc, killTarget); - - return; - } - - if (pc.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) - { - pc.Notify(Translator.GetString("TraineeNotify")); - return; - } - - if (pc.HasAbilityCD()) - { - if (!pc.IsHost()) pc.Notify(Translator.GetString("AbilityOnCooldown")); - else Main.Instance.StartCoroutine(FlashCooldownTimer()); - - return; - } - - var hasKillTarget = false; - PlayerControl target = SelectKillButtonTarget(pc); - if (target != null) hasKillTarget = true; - - CustomRoles role = pc.GetCustomRole(); - bool alwaysPetRole = role is CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee or CustomRoles.Sidekick; - - if (!pc.CanUseKillButton() && !alwaysPetRole) hasKillTarget = false; - - if (role.UsesPetInsteadOfKill() && hasKillTarget && (pc.Data.RoleType != RoleTypes.Impostor || alwaysPetRole)) - { - if (!CustomGameMode.Speedrun.IsActiveOrIntegrated()) - pc.AddKCDAsAbilityCD(); - - if (Main.PlayerStates[pc.PlayerId].Role.OnCheckMurder(pc, target)) - pc.RpcCheckAndMurder(target); - - if (alwaysPetRole) pc.SetKillCooldown(); - } - else Main.PlayerStates[pc.PlayerId].Role.OnPet(pc); - - if (pc.HasAbilityCD() || Main.PlayerStates[pc.PlayerId].Role is Sniper { IsAim: true }) return; - - pc.AddAbilityCD(); + CTFManager.TryPickUpFlag(pc); + return; } - public static PlayerControl SelectKillButtonTarget(PlayerControl pc) + if (Mastermind.ManipulatedPlayers.ContainsKey(pc.PlayerId)) { - Vector2 pos = pc.Pos(); - List<(PlayerControl pc, float distance)> players = Main.AllAlivePlayerControls.Without(pc).Select(x => (pc: x, distance: Vector2.Distance(pos, x.Pos()))).Where(x => x.distance < 2.5f).OrderBy(x => x.distance).ToList(); - PlayerControl target = players.Count > 0 ? players[0].pc : null; + PlayerControl killTarget = SelectKillButtonTarget(pc); + if (killTarget != null) Mastermind.ForceKillForManipulatedPlayer(pc, killTarget); - if (target != null && target.Is(CustomRoles.Detour)) - { - PlayerControl tempTarget = target; - target = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId && x.PlayerId != pc.PlayerId).MinBy(x => Vector2.Distance(x.Pos(), target.Pos())); - Logger.Info($"Target was {tempTarget.GetNameWithRole()}, new target is {target.GetNameWithRole()}", "Detour"); - } - - return target; + return; } - private static IEnumerator FlashCooldownTimer() + if (pc.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) { - var yellow = false; - - for (var i = 0; i < 8; i++) - { - HudManagerPatch.CooldownTimerFlashColor = yellow ? Color.red : Color.yellow; - yellow = !yellow; - yield return new WaitForSeconds(0.2f); - } - - HudManagerPatch.CooldownTimerFlashColor = null; + pc.Notify(Translator.GetString("TraineeNotify")); + return; } + + if (pc.HasAbilityCD()) + { + if (!pc.IsHost()) pc.Notify(Translator.GetString("AbilityOnCooldown")); + else Main.Instance.StartCoroutine(FlashCooldownTimer()); + + return; + } + + var hasKillTarget = false; + PlayerControl target = SelectKillButtonTarget(pc); + if (target != null) hasKillTarget = true; + + CustomRoles role = pc.GetCustomRole(); + bool alwaysPetRole = role is CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Refugee or CustomRoles.Sidekick; + + if (!pc.CanUseKillButton() && !alwaysPetRole) hasKillTarget = false; + + if (role.UsesPetInsteadOfKill() && hasKillTarget && (pc.Data.RoleType != RoleTypes.Impostor || alwaysPetRole)) + { + if (!CustomGameMode.Speedrun.IsActiveOrIntegrated()) + pc.AddKCDAsAbilityCD(); + + if (Main.PlayerStates[pc.PlayerId].Role.OnCheckMurder(pc, target)) + pc.RpcCheckAndMurder(target); + + if (alwaysPetRole) pc.SetKillCooldown(); + } + else Main.PlayerStates[pc.PlayerId].Role.OnPet(pc); + + if (pc.HasAbilityCD() || Main.PlayerStates[pc.PlayerId].Role is Sniper { IsAim: true }) return; + + pc.AddAbilityCD(); + } + + public static PlayerControl SelectKillButtonTarget(PlayerControl pc) + { + Vector2 pos = pc.Pos(); + List<(PlayerControl pc, float distance)> players = Main.AllAlivePlayerControls.Without(pc).Select(x => (pc: x, distance: Vector2.Distance(pos, x.Pos()))).Where(x => x.distance < 2.5f).OrderBy(x => x.distance).ToList(); + PlayerControl target = players.Count > 0 ? players[0].pc : null; + + if (target != null && target.Is(CustomRoles.Detour)) + { + PlayerControl tempTarget = target; + target = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId && x.PlayerId != pc.PlayerId).MinBy(x => Vector2.Distance(x.Pos(), target.Pos())); + Logger.Info($"Target was {tempTarget.GetNameWithRole()}, new target is {target.GetNameWithRole()}", "Detour"); + } + + return target; + } + + private static IEnumerator FlashCooldownTimer() + { + var yellow = false; + + for (var i = 0; i < 8; i++) + { + HudManagerPatch.CooldownTimerFlashColor = yellow ? Color.red : Color.yellow; + yellow = !yellow; + yield return new WaitForSeconds(0.2f); + } + + HudManagerPatch.CooldownTimerFlashColor = null; } } \ No newline at end of file diff --git a/Patches/PetsPatch.cs b/Patches/PetsPatch.cs index 9c8c186cb..3e6ee3e42 100644 --- a/Patches/PetsPatch.cs +++ b/Patches/PetsPatch.cs @@ -1,36 +1,35 @@ -namespace EHR +namespace EHR; + +public static class PetsPatch { - public static class PetsPatch + public static void RpcRemovePet(PlayerControl pc) { - public static void RpcRemovePet(PlayerControl pc) - { - if (pc == null || !pc.Data.IsDead || pc.IsAlive()) return; + if (pc == null || !pc.Data.IsDead || pc.IsAlive()) return; - if (!GameStates.IsInGame) return; + if (!GameStates.IsInGame) return; - if (!Options.RemovePetsAtDeadPlayers.GetBool()) return; + if (!Options.RemovePetsAtDeadPlayers.GetBool()) return; - if (pc.CurrentOutfit.PetId == "") return; + if (pc.CurrentOutfit.PetId == "") return; - var sender = CustomRpcSender.Create("Remove Pet From Dead Player"); + var sender = CustomRpcSender.Create("Remove Pet From Dead Player"); - pc.SetPet(""); - pc.Data.DefaultOutfit.PetSequenceId += 10; + pc.SetPet(""); + pc.Data.DefaultOutfit.PetSequenceId += 10; - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) - .Write("") - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) + .Write("") + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); - sender.SendMessage(); - } + sender.SendMessage(); + } - public static string GetPetId() - { - string[] pets = Options.PetToAssign; - string pet = pets[Options.PetToAssignToEveryone.GetValue()]; - string petId = pet == "pet_RANDOM_FOR_EVERYONE" ? pets[IRandom.Instance.Next(0, pets.Length - 1)] : pet; - return petId; - } + public static string GetPetId() + { + string[] pets = Options.PetToAssign; + string pet = pets[Options.PetToAssignToEveryone.GetValue()]; + string petId = pet == "pet_RANDOM_FOR_EVERYONE" ? pets[IRandom.Instance.Next(0, pets.Length - 1)] : pet; + return petId; } } \ No newline at end of file diff --git a/Patches/PhantomRolePatch.cs b/Patches/PhantomRolePatch.cs index f4587533d..dc5c72e8a 100644 --- a/Patches/PhantomRolePatch.cs +++ b/Patches/PhantomRolePatch.cs @@ -7,235 +7,233 @@ using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using UnityEngine; -namespace EHR.Patches +namespace EHR.Patches; +// By TommyXL & NikoCat233 + +[HarmonyPatch(typeof(PlayerControl))] +public static class PhantomRolePatch { - // By TommyXL & NikoCat233 + private static readonly List InvisibilityList = new(); + private static readonly System.Collections.Generic.Dictionary PetsList = []; - [HarmonyPatch(typeof(PlayerControl))] - public static class PhantomRolePatch + [HarmonyPatch(nameof(PlayerControl.CmdCheckVanish))] + [HarmonyPrefix] + private static bool CmdCheckVanish_Prefix(PlayerControl __instance, float maxDuration) { - private static readonly List InvisibilityList = new(); - private static readonly System.Collections.Generic.Dictionary PetsList = []; - - [HarmonyPatch(nameof(PlayerControl.CmdCheckVanish))] - [HarmonyPrefix] - private static bool CmdCheckVanish_Prefix(PlayerControl __instance, float maxDuration) + if (AmongUsClient.Instance.AmHost) { - if (AmongUsClient.Instance.AmHost) - { - __instance.CheckVanish(); - return false; - } - - __instance.SetRoleInvisibility(true); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckVanish, SendOption.Reliable, AmongUsClient.Instance.HostId); - messageWriter.Write(maxDuration); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + __instance.CheckVanish(); return false; } - [HarmonyPatch(nameof(PlayerControl.CmdCheckAppear))] - [HarmonyPrefix] - private static bool CmdCheckAppear_Prefix(PlayerControl __instance, bool shouldAnimate) - { - if (AmongUsClient.Instance.AmHost) - { - __instance.CheckAppear(shouldAnimate); - return false; - } + __instance.SetRoleInvisibility(true); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckVanish, SendOption.Reliable, AmongUsClient.Instance.HostId); + messageWriter.Write(maxDuration); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + return false; + } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckAppear, SendOption.Reliable, AmongUsClient.Instance.HostId); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + [HarmonyPatch(nameof(PlayerControl.CmdCheckAppear))] + [HarmonyPrefix] + private static bool CmdCheckAppear_Prefix(PlayerControl __instance, bool shouldAnimate) + { + if (AmongUsClient.Instance.AmHost) + { + __instance.CheckAppear(shouldAnimate); return false; } - // Called when Phantom press vanish button when visible - [HarmonyPatch(nameof(PlayerControl.CheckVanish))] - [HarmonyPrefix] - private static void CheckVanish_Prefix(PlayerControl __instance) + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckAppear, SendOption.Reliable, AmongUsClient.Instance.HostId); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + return false; + } + + // Called when Phantom press vanish button when visible + [HarmonyPatch(nameof(PlayerControl.CheckVanish))] + [HarmonyPrefix] + private static void CheckVanish_Prefix(PlayerControl __instance) + { + if (!AmongUsClient.Instance.AmHost) return; + + PlayerControl phantom = __instance; + Logger.Info($"Player: {phantom.GetRealName()}", "CheckVanish"); + + foreach (PlayerControl target in Main.AllPlayerControls) { - if (!AmongUsClient.Instance.AmHost) return; + if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - PlayerControl phantom = __instance; - Logger.Info($"Player: {phantom.GetRealName()}", "CheckVanish"); + phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); + phantom.RpcCheckVanishDesync(target); - foreach (PlayerControl target in Main.AllPlayerControls) - { - if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - - phantom.RpcSetRoleDesync(RoleTypes.Phantom, target.GetClientId()); - phantom.RpcCheckVanishDesync(target); - - LateTask.New(() => - { - if (GameStates.IsMeeting || phantom == null) return; - - string petId = phantom.Data.DefaultOutfit.PetId; - - if (petId != "") - { - PetsList[phantom.PlayerId] = petId; - phantom.RpcSetPetDesync("", target); - } - - phantom.RpcExileDesync(target); - }, 1.2f, $"Set Phantom invisible {target.PlayerId}"); - } - - InvisibilityList.Add(phantom); - } - - [HarmonyPatch(nameof(PlayerControl.CheckAppear))] - [HarmonyPrefix] - private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnimate) - { - if (!AmongUsClient.Instance.AmHost) return; - - PlayerControl phantom = __instance; - Logger.Info($"Player: {phantom.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); - - if (phantom.inVent) phantom.MyPhysics.RpcBootFromVent(Main.LastEnteredVent[phantom.PlayerId].Id); - - foreach (PlayerControl target in Main.AllPlayerControls) - { - if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; - - int clientId = target.GetClientId(); - - phantom.RpcSetRoleDesync(RoleTypes.Phantom, clientId); - - LateTask.New(() => - { - if (target != null) phantom.RpcCheckAppearDesync(shouldAnimate, target); - }, 0.5f, $"Check Appear when vanish is over {target.PlayerId}"); - - LateTask.New(() => - { - if (GameStates.IsMeeting || phantom == null) return; - - InvisibilityList.Remove(phantom); - phantom.RpcSetRoleDesync(RoleTypes.Scientist, clientId); - - if (PetsList.TryGetValue(phantom.PlayerId, out string petId)) phantom.RpcSetPetDesync(petId, target); - }, 1.8f, $"Set Scientist when vanish is over {target.PlayerId}"); - } - } - - [HarmonyPatch(nameof(PlayerControl.SetRoleInvisibility))] - [HarmonyPrefix] - private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) - { - if (!AmongUsClient.Instance.AmHost) return; - - Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); - } - - public static void OnReportDeadBody(PlayerControl seer, bool force) - { - try - { - if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; - - foreach (PlayerControl phantom in InvisibilityList) + LateTask.New(() => { - if (!phantom.IsAlive()) + if (GameStates.IsMeeting || phantom == null) return; + + string petId = phantom.Data.DefaultOutfit.PetId; + + if (petId != "") { - InvisibilityList.Remove(phantom); - continue; + PetsList[phantom.PlayerId] = petId; + phantom.RpcSetPetDesync("", target); } - Main.Instance.StartCoroutine(CoRevertInvisible(phantom, seer, force)); - } - } - catch (Exception e) + phantom.RpcExileDesync(target); + }, 1.2f, $"Set Phantom invisible {target.PlayerId}"); + } + + InvisibilityList.Add(phantom); + } + + [HarmonyPatch(nameof(PlayerControl.CheckAppear))] + [HarmonyPrefix] + private static void CheckAppear_Prefix(PlayerControl __instance, bool shouldAnimate) + { + if (!AmongUsClient.Instance.AmHost) return; + + PlayerControl phantom = __instance; + Logger.Info($"Player: {phantom.GetRealName()} => shouldAnimate {shouldAnimate}", "CheckAppear"); + + if (phantom.inVent) phantom.MyPhysics.RpcBootFromVent(Main.LastEnteredVent[phantom.PlayerId].Id); + + foreach (PlayerControl target in Main.AllPlayerControls) + { + if (!target.IsAlive() || phantom == target || target.AmOwner || !target.HasDesyncRole()) continue; + + int clientId = target.GetClientId(); + + phantom.RpcSetRoleDesync(RoleTypes.Phantom, clientId); + + LateTask.New(() => + { + if (target != null) phantom.RpcCheckAppearDesync(shouldAnimate, target); + }, 0.5f, $"Check Appear when vanish is over {target.PlayerId}"); + + LateTask.New(() => + { + if (GameStates.IsMeeting || phantom == null) return; + + InvisibilityList.Remove(phantom); + phantom.RpcSetRoleDesync(RoleTypes.Scientist, clientId); + + if (PetsList.TryGetValue(phantom.PlayerId, out string petId)) phantom.RpcSetPetDesync(petId, target); + }, 1.8f, $"Set Scientist when vanish is over {target.PlayerId}"); + } + } + + [HarmonyPatch(nameof(PlayerControl.SetRoleInvisibility))] + [HarmonyPrefix] + private static void SetRoleInvisibility_Prefix(PlayerControl __instance, bool isActive, bool shouldAnimate, bool playFullAnimation) + { + if (!AmongUsClient.Instance.AmHost) return; + + Logger.Info($"Player: {__instance.GetRealName()} => Is Active {isActive}, Animate:{shouldAnimate}, Full Animation:{playFullAnimation}", "SetRoleInvisibility"); + } + + public static void OnReportDeadBody(PlayerControl seer, bool force) + { + try + { + if (InvisibilityList.Count == 0 || !seer.IsAlive() || seer.Data.Role.Role is RoleTypes.Phantom || seer.AmOwner || !seer.HasDesyncRole()) return; + + foreach (PlayerControl phantom in InvisibilityList) { - Utils.ThrowException(e); + if (!phantom.IsAlive()) + { + InvisibilityList.Remove(phantom); + continue; + } + + Main.Instance.StartCoroutine(CoRevertInvisible(phantom, seer, force)); } } - - private static bool InValid(PlayerControl phantom, PlayerControl seer) + catch (Exception e) { - return seer.GetClientId() == -1 || phantom == null; + Utils.ThrowException(e); + } + } + + private static bool InValid(PlayerControl phantom, PlayerControl seer) + { + return seer.GetClientId() == -1 || phantom == null; + } + + private static IEnumerator CoRevertInvisible(PlayerControl phantom, PlayerControl seer, bool force) + { + // Set Scientist for meeting + if (!force) yield return new WaitForSeconds(0.0001f); + + if (InValid(phantom, seer)) yield break; + + phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); + + // Return Phantom in meeting + yield return new WaitForSeconds(1f); + + { + if (InValid(phantom, seer)) yield break; + + phantom?.RpcSetRoleDesync(RoleTypes.Phantom, seer.GetClientId()); } - private static IEnumerator CoRevertInvisible(PlayerControl phantom, PlayerControl seer, bool force) - { - // Set Scientist for meeting - if (!force) yield return new WaitForSeconds(0.0001f); + // Revert invis for phantom + yield return new WaitForSeconds(1f); + { + if (InValid(phantom, seer)) yield break; + + phantom?.RpcStartAppearDesync(false, seer); + } + + // Set Scientist back + yield return new WaitForSeconds(4f); + + { if (InValid(phantom, seer)) yield break; phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); - // Return Phantom in meeting - yield return new WaitForSeconds(1f); - - { - if (InValid(phantom, seer)) yield break; - - phantom?.RpcSetRoleDesync(RoleTypes.Phantom, seer.GetClientId()); - } - - // Revert invis for phantom - yield return new WaitForSeconds(1f); - - { - if (InValid(phantom, seer)) yield break; - - phantom?.RpcStartAppearDesync(false, seer); - } - - // Set Scientist back - yield return new WaitForSeconds(4f); - - { - if (InValid(phantom, seer)) yield break; - - phantom?.RpcSetRoleDesync(RoleTypes.Scientist, seer.GetClientId()); - - if (phantom != null && PetsList.TryGetValue(phantom.PlayerId, out string petId)) phantom.RpcSetPetDesync(petId, seer); - } - } - - public static void AfterMeeting() - { - InvisibilityList.Clear(); - PetsList.Clear(); + if (phantom != null && PetsList.TryGetValue(phantom.PlayerId, out string petId)) phantom.RpcSetPetDesync(petId, seer); } } + public static void AfterMeeting() + { + InvisibilityList.Clear(); + PetsList.Clear(); + } +} + // Fixed vanilla bug for host (from TOH-Y) - [HarmonyPatch(typeof(PhantomRole), nameof(PhantomRole.UseAbility))] - public static class PhantomRoleUseAbilityPatch +[HarmonyPatch(typeof(PhantomRole), nameof(PhantomRole.UseAbility))] +public static class PhantomRoleUseAbilityPatch +{ + public static bool Prefix(PhantomRole __instance) { - public static bool Prefix(PhantomRole __instance) + if (!AmongUsClient.Instance.AmHost) return true; + + if (__instance.Player.AmOwner && !__instance.Player.Data.IsDead && __instance.Player.moveable && !Minigame.Instance && !__instance.IsCoolingDown && !__instance.fading) { - if (!AmongUsClient.Instance.AmHost) return true; - - if (__instance.Player.AmOwner && !__instance.Player.Data.IsDead && __instance.Player.moveable && !Minigame.Instance && !__instance.IsCoolingDown && !__instance.fading) + bool RoleEffectAnimation(RoleEffectAnimation x) { - bool RoleEffectAnimation(RoleEffectAnimation x) - { - return x.effectType == global::RoleEffectAnimation.EffectType.Vanish_Charge; - } - - if (!__instance.Player.currentRoleAnimations.Find((Func)RoleEffectAnimation) && !__instance.Player.walkingToVent && !__instance.Player.inMovingPlat) - { - if (__instance.isInvisible) - { - __instance.MakePlayerVisible(); - return false; - } - - DestroyableSingleton.Instance.AbilityButton.SetSecondImage(__instance.Ability); - DestroyableSingleton.Instance.AbilityButton.OverrideText(DestroyableSingleton.Instance.GetString(StringNames.PhantomAbilityUndo, new Il2CppReferenceArray(0))); - __instance.Player.CmdCheckVanish(GameManager.Instance.LogicOptions.GetPhantomDuration()); - return false; - } + return x.effectType == global::RoleEffectAnimation.EffectType.Vanish_Charge; } - return false; + if (!__instance.Player.currentRoleAnimations.Find((Func)RoleEffectAnimation) && !__instance.Player.walkingToVent && !__instance.Player.inMovingPlat) + { + if (__instance.isInvisible) + { + __instance.MakePlayerVisible(); + return false; + } + + DestroyableSingleton.Instance.AbilityButton.SetSecondImage(__instance.Ability); + DestroyableSingleton.Instance.AbilityButton.OverrideText(DestroyableSingleton.Instance.GetString(StringNames.PhantomAbilityUndo, new Il2CppReferenceArray(0))); + __instance.Player.CmdCheckVanish(GameManager.Instance.LogicOptions.GetPhantomDuration()); + return false; + } } + + return false; } } \ No newline at end of file diff --git a/Patches/PlayerControlPatch.cs b/Patches/PlayerControlPatch.cs index 4d5a7c56f..8c784b917 100644 --- a/Patches/PlayerControlPatch.cs +++ b/Patches/PlayerControlPatch.cs @@ -20,1848 +20,1848 @@ using UnityEngine; using static EHR.Translator; using static EHR.Utils; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckProtect))] +internal static class CheckProtectPatch { - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckProtect))] - internal static class CheckProtectPatch + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + if (!AmongUsClient.Instance.AmHost || target.Data.IsDead) return false; + + Logger.Info($"CheckProtect: {__instance.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "CheckProtect"); + + if (__instance.Is(CustomRoles.EvilSpirit)) { - if (!AmongUsClient.Instance.AmHost || target.Data.IsDead) 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 (GhostRolesManager.AssignedGhostRoles.TryGetValue(__instance.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole)) - { - ghostRole.Instance.OnProtect(__instance, target); - __instance.RpcResetAbilityCooldown(); - return true; - } - - if (__instance.Is(CustomRoles.Sheriff)) - { - if (__instance.Data.IsDead) - { - Logger.Info("Blocked", "CheckProtect"); - return false; - } - } + if (target.Is(CustomRoles.Spiritcaller)) + Spiritcaller.ProtectSpiritcaller(); + else + Spiritcaller.HauntPlayer(target); + __instance.RpcResetAbilityCooldown(); return true; } - } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcMurderPlayer))] - internal static class RpcMurderPlayerPatch - { - public static bool Prefix(PlayerControl __instance, PlayerControl target, bool didSucceed) + if (GhostRolesManager.AssignedGhostRoles.TryGetValue(__instance.PlayerId, out (CustomRoles Role, IGhostRole Instance) ghostRole)) { - if (!AmongUsClient.Instance.AmHost) Logger.Error("Client is calling RpcMurderPlayer, are you hacking?", "RpcMurderPlayerPatch.Prefix"); + ghostRole.Instance.OnProtect(__instance, target); + __instance.RpcResetAbilityCooldown(); + return true; + } - MurderResultFlags murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; - if (AmongUsClient.Instance.AmClient) __instance.MurderPlayer(target, murderResultFlags); + if (__instance.Is(CustomRoles.Sheriff)) + { + if (__instance.Data.IsDead) + { + Logger.Info("Blocked", "CheckProtect"); + return false; + } + } - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable); + return true; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcMurderPlayer))] +internal static class RpcMurderPlayerPatch +{ + public static bool Prefix(PlayerControl __instance, PlayerControl target, bool didSucceed) + { + if (!AmongUsClient.Instance.AmHost) Logger.Error("Client is calling RpcMurderPlayer, are you hacking?", "RpcMurderPlayerPatch.Prefix"); + + MurderResultFlags murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; + if (AmongUsClient.Instance.AmClient) __instance.MurderPlayer(target, murderResultFlags); + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.MurderPlayer, SendOption.Reliable); + messageWriter.WriteNetObject(target); + messageWriter.Write((int)murderResultFlags); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + return false; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckMurder))] // Modded +internal static class CmdCheckMurderPatch +{ + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + if (AmongUsClient.Instance.AmHost) + __instance.CheckMurder(target); + else if (!CustomGameMode.FFA.IsActiveOrIntegrated()) + { + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckMurder, SendOption.Reliable); messageWriter.WriteNetObject(target); - messageWriter.Write((int)murderResultFlags); AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + } + else + { + MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.FFAKill); + writer.WriteNetObject(__instance); + writer.WriteNetObject(target); + writer.EndMessage(); + } - return false; + return false; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckMurder))] // Vanilla +internal static class CheckMurderPatch +{ + public static readonly Dictionary TimeSinceLastKill = []; + + public static void Update() + { + int n = HudManager.InstanceExists ? Math.Max(Main.AllPlayerControls.Length, 15) : 15; + + for (byte i = 0; i < n; i++) + { + if (TimeSinceLastKill.ContainsKey(i)) + { + TimeSinceLastKill[i] += Time.deltaTime; + if (15f < TimeSinceLastKill[i]) TimeSinceLastKill.Remove(i); + } } } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckMurder))] // Modded - internal static class CmdCheckMurderPatch + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + if (!AmongUsClient.Instance.AmHost) return false; + + PlayerControl killer = __instance; + + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "CheckMurder"); + + if (killer.Data.IsDead) { - if (AmongUsClient.Instance.AmHost) - __instance.CheckMurder(target); - else if (!CustomGameMode.FFA.IsActiveOrIntegrated()) - { - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckMurder, SendOption.Reliable); - messageWriter.WriteNetObject(target); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - } - else - { - MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.FFAKill); - writer.WriteNetObject(__instance); - writer.WriteNetObject(target); - writer.EndMessage(); - } - - return false; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckMurder))] // Vanilla - internal static class CheckMurderPatch - { - public static readonly Dictionary TimeSinceLastKill = []; - - public static void Update() - { - int n = HudManager.InstanceExists ? Math.Max(Main.AllPlayerControls.Length, 15) : 15; - - for (byte i = 0; i < n; 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; - - PlayerControl killer = __instance; - - 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 (AntiBlackout.SkipTasks) - { - Logger.Info("CheckMurder while AntiBlackOut protection is in progress, kill canceled", "CheckMurder"); - return false; - } - - if (target.Is(CustomRoles.Detour)) - { - PlayerControl 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 (tempTarget.IsLocalPlayer()) - { - Detour.TotalRedirections++; - if (Detour.TotalRedirections >= 3) Achievements.Type.CantTouchThis.CompleteAfterGameEnd(); - } - } - - 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; - } - - float minTime = CalculatePingDelay(); - - // 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 float time) && time < minTime) - { - Logger.Info($"Last kill was too shortly before, canceled - time: {time}, minTime: {minTime}", "CheckMurder"); - return false; - } - - TimeSinceLastKill[killer.PlayerId] = 0f; - - if (target.Is(CustomRoles.Diseased)) - if (!Main.KilledDiseased.TryAdd(killer.PlayerId, 1)) - Main.KilledDiseased[killer.PlayerId] += 1; - - if (target.Is(CustomRoles.Antidote)) - if (!Main.KilledAntidote.TryAdd(killer.PlayerId, 1)) - Main.KilledAntidote[killer.PlayerId] += 1; - - 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.AllInOne when CustomGameMode.SoloKombat.IsActiveOrIntegrated() && !killer.Is(CustomRoles.Killer): - case CustomGameMode.SoloKombat: - SoloKombatManager.OnPlayerAttack(killer, target); - return false; - case CustomGameMode.AllInOne when CustomGameMode.FFA.IsActiveOrIntegrated() && killer.Is(CustomRoles.Killer): - case CustomGameMode.FFA: - FFAManager.OnPlayerAttack(killer, target); - return false; - case CustomGameMode.MoveAndStop: - case CustomGameMode.HotPotato: - case CustomGameMode.RoomRush: - case CustomGameMode.NaturalDisasters: - case CustomGameMode.Speedrun when !SpeedrunManager.OnCheckMurder(killer, target): - return false; - case CustomGameMode.AllInOne: - if (killer.Is(CustomRoles.Killer)) killer.Kill(target); - return false; - case CustomGameMode.Speedrun: - killer.Kill(target); - return false; - case CustomGameMode.HideAndSeek: - HnSManager.OnCheckMurder(killer, target); - return false; - case CustomGameMode.CaptureTheFlag: - CTFManager.OnCheckMurder(killer, target); - return false; - } - - Deadlined.SetDone(killer); - - if (ToiletMaster.OnAnyoneCheckMurderStart(killer, target)) return false; - - if (Dad.OnAnyoneCheckMurderStart(target)) return false; - - Simon.RemoveTarget(killer, Simon.Instruction.Kill); - - 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 (killer.IsRoleBlocked()) - { - killer.Notify(BlockedAction.Kill.GetBlockNotify()); - return false; - } - - if (Pursuer.OnClientMurder(killer)) return false; - - if (killer.PlayerId != target.PlayerId) - { - switch (killer.Is(CustomRoles.Bloodlust)) - { - case false when !CheckMurder(): - return false; - case true when killer.GetCustomRole().GetDYRole() == RoleTypes.Impostor: - if (killer.CheckDoubleTrigger(target, () => - { - if (CheckMurder()) killer.RpcCheckAndMurder(target); - })) - killer.RpcCheckAndMurder(target); - - return false; - } - - bool CheckMurder() - { - return Main.PlayerStates[killer.PlayerId].Role.OnCheckMurder(killer, target) || target.Is(CustomRoles.Fragile); - } - } - - 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); - LateTask.New(() => killer.RpcCheckAndMurder(target), 0.1f, log: false); - return false; - } - - //==Kill processing== - __instance.Kill(target); - //=================== - + Logger.Info($"Killer {killer.GetNameWithRole().RemoveHtmlTags()} is dead, kill canceled", "CheckMurder"); return false; } - public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, bool check = false) + if (AntiBlackout.SkipTasks) { - if (!AmongUsClient.Instance.AmHost) return false; + Logger.Info("CheckMurder while AntiBlackOut protection is in progress, kill canceled", "CheckMurder"); + return false; + } - if (target == null) target = killer; + if (target.Is(CustomRoles.Detour)) + { + PlayerControl 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 (CustomTeamManager.AreInSameCustomTeam(killer.PlayerId, target.PlayerId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(killer.PlayerId, CTAOption.KillEachOther)) + if (tempTarget.IsLocalPlayer()) { - Notify("SameCTATeam"); + Detour.TotalRedirections++; + if (Detour.TotalRedirections >= 3) Achievements.Type.CantTouchThis.CompleteAfterGameEnd(); + } + } + + 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; + } + + float minTime = CalculatePingDelay(); + + // 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 float time) && time < minTime) + { + Logger.Info($"Last kill was too shortly before, canceled - time: {time}, minTime: {minTime}", "CheckMurder"); + return false; + } + + TimeSinceLastKill[killer.PlayerId] = 0f; + + if (target.Is(CustomRoles.Diseased)) + if (!Main.KilledDiseased.TryAdd(killer.PlayerId, 1)) + Main.KilledDiseased[killer.PlayerId] += 1; + + if (target.Is(CustomRoles.Antidote)) + if (!Main.KilledAntidote.TryAdd(killer.PlayerId, 1)) + Main.KilledAntidote[killer.PlayerId] += 1; + + 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.AllInOne when CustomGameMode.SoloKombat.IsActiveOrIntegrated() && !killer.Is(CustomRoles.Killer): + case CustomGameMode.SoloKombat: + SoloKombatManager.OnPlayerAttack(killer, target); return false; - } - - if (AFKDetector.ShieldedPlayers.Contains(target.PlayerId)) - { - Notify("AFKShielded"); + case CustomGameMode.AllInOne when CustomGameMode.FFA.IsActiveOrIntegrated() && killer.Is(CustomRoles.Killer): + case CustomGameMode.FFA: + FFAManager.OnPlayerAttack(killer, target); return false; - } - - if ((killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick) && !Options.JackalCanKillSidekick.GetBool()) || - (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) || - (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Recruit) && !Options.JackalCanKillSidekick.GetBool()) || - (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) || - (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool()) || - (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool()) || - (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool()) || - (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool())) - { - Notify("JackalSidekick"); + case CustomGameMode.MoveAndStop: + case CustomGameMode.HotPotato: + case CustomGameMode.RoomRush: + case CustomGameMode.NaturalDisasters: + case CustomGameMode.Speedrun when !SpeedrunManager.OnCheckMurder(killer, target): return false; - } - - if (!Virus.ContagiousPlayersCanKillEachOther.GetBool() && target.Is(CustomRoles.Contagious) && killer.Is(CustomRoles.Contagious)) - { - Notify("ContagiousPlayers"); + case CustomGameMode.AllInOne: + if (killer.Is(CustomRoles.Killer)) killer.Kill(target); return false; - } - - if (killer.IsMadmate() && target.Is(CustomRoleTypes.Impostor) && !Options.MadmateCanKillImp.GetBool()) - { - Notify("MadmateKillImpostor"); + case CustomGameMode.Speedrun: + killer.Kill(target); return false; - } - - if (killer.Is(CustomRoleTypes.Impostor) && target.IsMadmate() && !Options.ImpCanKillMadmate.GetBool()) - { - Notify("ImpostorKillMadmate"); + case CustomGameMode.HideAndSeek: + HnSManager.OnCheckMurder(killer, target); return false; - } - - if ((Romantic.PartnerId == target.PlayerId && Romantic.IsPartnerProtected) || - Medic.OnAnyoneCheckMurder(killer, target) || - Randomizer.IsShielded(target) || - Aid.ShieldedPlayers.ContainsKey(target.PlayerId) || - Gaslighter.IsShielded(target) || - !Grappler.OnAnyoneCheckMurder(target) || - !Adventurer.OnAnyoneCheckMurder(target) || - !Sentinel.OnAnyoneCheckMurder(killer) || - !ToiletMaster.OnAnyoneCheckMurder(killer, target)) - { - Notify("SomeSortOfProtection"); + case CustomGameMode.CaptureTheFlag: + CTFManager.OnCheckMurder(killer, target); return false; - } + } - if (!Socialite.OnAnyoneCheckMurder(killer, target)) + Deadlined.SetDone(killer); + + if (ToiletMaster.OnAnyoneCheckMurderStart(killer, target)) return false; + + if (Dad.OnAnyoneCheckMurderStart(target)) return false; + + Simon.RemoveTarget(killer, Simon.Instruction.Kill); + + 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 (killer.IsRoleBlocked()) + { + killer.Notify(BlockedAction.Kill.GetBlockNotify()); + return false; + } + + if (Pursuer.OnClientMurder(killer)) return false; + + if (killer.PlayerId != target.PlayerId) + { + switch (killer.Is(CustomRoles.Bloodlust)) { - Notify("SocialiteTarget"); - return false; - } - - if (Penguin.IsVictim(killer)) - { - Notify("PenguinVictimKill"); - return false; - } - - if (Mathematician.State.ProtectedPlayerId == target.PlayerId) - { - Notify("MathematicianProtected"); - return false; - } - - if (killer.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) - { - Notify("RefugeeKillImpostor"); - return false; - } - - if (!Bodyguard.OnAnyoneCheckMurder(killer, target)) - { - Notify("BodyguardProtected"); - return false; - } - - if (Echo.On) - { - foreach (Echo echo in Echo.Instances) - { - if (echo.EchoPC.shapeshiftTargetPlayerId == target.PlayerId) - { - echo.OnTargetCheckMurder(killer, target); - return false; - } - } - } - - if (GhostRolesManager.AssignedGhostRoles.Values.Any(x => x.Instance is GA ga && ga.ProtectionList.Contains(target.PlayerId))) - { - Notify("GAGuarded"); - - if (killer.IsLocalPlayer()) - Achievements.Type.IForgotThisRoleExists.CompleteAfterGameEnd(); - - return false; - } - - if (SoulHunter.IsSoulHunterTarget(killer.PlayerId) && target.Is(CustomRoles.SoulHunter)) - { - Notify("SoulHunterTargetNotifyNoKill"); - return false; - } - - if (killer.IsRoleBlocked()) - { - killer.Notify(BlockedAction.Kill.GetBlockNotify()); - return false; - } - - if (killer.Is(CustomRoles.Traitor) && target.Is(CustomRoleTypes.Impostor)) - { - Notify("TraitorKillImpostor"); - 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); + case false when !CheckMurder(): return false; - } - } + case true when killer.GetCustomRole().GetDYRole() == RoleTypes.Impostor: + if (killer.CheckDoubleTrigger(target, () => + { + if (CheckMurder()) killer.RpcCheckAndMurder(target); + })) + killer.RpcCheckAndMurder(target); - if (Crusader.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); - Crusader.ForCrusade.Remove(target.PlayerId); - killer.RpcGuardAndKill(target); - return false; - } - - if (player.Is(CustomRoles.Crusader) && player.IsAlive() && killer.Is(CustomRoles.Pestilence)) - { - killer.Kill(player); - Crusader.ForCrusade.Remove(target.PlayerId); - target.RpcGuardAndKill(killer); - Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; - return false; - } - } - } - - switch (target.GetCustomRole()) - { - case CustomRoles.Medic: - Medic.IsDead(target); - break; - case CustomRoles.Gambler when Gambler.IsShielded.ContainsKey(target.PlayerId): - Notify("SomeSortOfProtection"); - killer.SetKillCooldown(5f); return false; - case CustomRoles.Spiritcaller: - if (Spiritcaller.InProtect(target)) - { - killer.RpcGuardAndKill(target); - Notify("SomeSortOfProtection"); - return false; - } - - break; } - if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode)) + bool CheckMurder() { - Main.ShieldPlayer = string.Empty; - killer.SetKillCooldown(15f); - killer.Notify(GetString("TriedToKillLastGameFirstKill"), 10f); + return Main.PlayerStates[killer.PlayerId].Role.OnCheckMurder(killer, target) || target.Is(CustomRoles.Fragile); + } + } + + 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 (Options.MadmateSpawnMode.GetInt() == 1 && Main.MadmateNum < CustomRoles.Madmate.GetCount() && target.CanBeMadmate()) + 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)) { - Main.MadmateNum++; - target.RpcSetCustomRole(CustomRoles.Madmate); - ExtendedPlayerControl.RpcSetCustomRole(target.PlayerId, CustomRoles.Madmate); - target.Notify(ColorString(GetRoleColor(CustomRoles.Madmate), GetString("BecomeMadmateCuzMadmateMode"))); + target.Suicide(PlayerState.DeathReason.Kill, killer); 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 (!Main.PlayerStates[target.PlayerId].Role.OnCheckMurderAsTarget(killer, target)) - { - Notify("SomeSortOfProtection"); - return false; - } - - if (killer.Is(CustomRoles.Rookie) && MeetingStates.FirstMeeting) - { - Notify("RookieKillRoundOne"); - return false; - } - - if (!check) killer.Kill(target); - - if (killer.Is(CustomRoles.Doppelganger)) Doppelganger.OnCheckMurderEnd(killer, target); - - return true; - - void Notify(string message) - { - killer.Notify(ColorString(Color.yellow, GetString("CheckMurderFail") + GetString(message)), 15f); - } + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + return false; } + + if (killer.Is(CustomRoles.Magnet) && !target.Is(CustomRoles.Pestilence)) + { + target.TP(killer); + LateTask.New(() => killer.RpcCheckAndMurder(target), 0.1f, log: false); + return false; + } + + //==Kill processing== + __instance.Kill(target); + //=================== + + return false; } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))] - internal static class MurderPlayerPatch + public static bool RpcCheckAndMurder(PlayerControl killer, PlayerControl target, bool check = false) { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] MurderResultFlags resultFlags*/) + if (!AmongUsClient.Instance.AmHost) return false; + + if (target == null) target = killer; + + if (CustomTeamManager.AreInSameCustomTeam(killer.PlayerId, target.PlayerId) && !CustomTeamManager.IsSettingEnabledForPlayerTeam(killer.PlayerId, CTAOption.KillEachOther)) { - Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}{(target.IsProtected() ? " (Protected)" : string.Empty)}", "MurderPlayer"); - - if (GameStates.IsLobby) - { - Logger.Info("Murder triggered in lobby, so murder canceled", "MurderPlayer Prefix"); - return false; - } - - RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Add(__instance.PlayerId); - - if (!target.IsProtected() && !Doppelganger.DoppelVictim.ContainsKey(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId)) - { - Camouflage.ResetSkinAfterDeathPlayers.Add(target.PlayerId); - Camouflage.RpcSetSkin(target, true); - } - - return true; + Notify("SameCTATeam"); + return false; } - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + if (AFKDetector.ShieldedPlayers.Contains(target.PlayerId)) { - if (__instance == null || target == null || __instance.PlayerId == 255 || target.PlayerId == 255) return; + Notify("AFKShielded"); + return false; + } - if (target.AmOwner) RemoveDisableDevicesPatch.UpdateDisableDevices(); + if ((killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Sidekick) && !Options.JackalCanKillSidekick.GetBool()) || + (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) || + (killer.Is(CustomRoles.Jackal) && target.Is(CustomRoles.Recruit) && !Options.JackalCanKillSidekick.GetBool()) || + (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Jackal) && !Options.SidekickCanKillJackal.GetBool()) || + (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool()) || + (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool()) || + (killer.Is(CustomRoles.Recruit) && target.Is(CustomRoles.Sidekick) && !Options.SidekickCanKillSidekick.GetBool()) || + (killer.Is(CustomRoles.Sidekick) && target.Is(CustomRoles.Recruit) && !Options.SidekickCanKillSidekick.GetBool())) + { + Notify("JackalSidekick"); + return false; + } - if (!target.Data.IsDead || !AmongUsClient.Instance.AmHost) return; + if (!Virus.ContagiousPlayersCanKillEachOther.GetBool() && target.Is(CustomRoles.Contagious) && killer.Is(CustomRoles.Contagious)) + { + Notify("ContagiousPlayers"); + return false; + } - if (OverKiller.OverDeadPlayerList.Contains(target.PlayerId)) return; + if (killer.IsMadmate() && target.Is(CustomRoleTypes.Impostor) && !Options.MadmateCanKillImp.GetBool()) + { + Notify("MadmateKillImpostor"); + return false; + } - PlayerControl killer = __instance; // Alternative variable - if (target.PlayerId == Godfather.GodfatherTarget) killer.RpcSetCustomRole(CustomRoles.Refugee); + if (killer.Is(CustomRoleTypes.Impostor) && target.IsMadmate() && !Options.ImpCanKillMadmate.GetBool()) + { + Notify("ImpostorKillMadmate"); + return false; + } - PlagueDoctor.OnAnyMurder(); + if ((Romantic.PartnerId == target.PlayerId && Romantic.IsPartnerProtected) || + Medic.OnAnyoneCheckMurder(killer, target) || + Randomizer.IsShielded(target) || + Aid.ShieldedPlayers.ContainsKey(target.PlayerId) || + Gaslighter.IsShielded(target) || + !Grappler.OnAnyoneCheckMurder(target) || + !Adventurer.OnAnyoneCheckMurder(target) || + !Sentinel.OnAnyoneCheckMurder(killer) || + !ToiletMaster.OnAnyoneCheckMurder(killer, target)) + { + Notify("SomeSortOfProtection"); + return false; + } - // 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 (!Socialite.OnAnyoneCheckMurder(killer, target)) + { + Notify("SocialiteTarget"); + return false; + } - if (killer.Is(CustomRoles.Sniper)) + if (Penguin.IsVictim(killer)) + { + Notify("PenguinVictimKill"); + return false; + } + + if (Mathematician.State.ProtectedPlayerId == target.PlayerId) + { + Notify("MathematicianProtected"); + return false; + } + + if (killer.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) + { + Notify("RefugeeKillImpostor"); + return false; + } + + if (!Bodyguard.OnAnyoneCheckMurder(killer, target)) + { + Notify("BodyguardProtected"); + return false; + } + + if (Echo.On) + { + foreach (Echo echo in Echo.Instances) { - if (!Options.UsePets.GetBool()) - killer.RpcResetAbilityCooldown(); - else + if (echo.EchoPC.shapeshiftTargetPlayerId == target.PlayerId) { - int cd = Options.DefaultShapeshiftCooldown.GetInt(); - Main.AbilityCD[killer.PlayerId] = (TimeStamp, cd); - SendRPC(CustomRPC.SyncAbilityCD, 1, killer.PlayerId, cd); + echo.OnTargetCheckMurder(killer, target); + return false; } } + } - if (killer != __instance) Logger.Info($"Real Killer = {killer.GetNameWithRole().RemoveHtmlTags()}", "MurderPlayer"); + if (GhostRolesManager.AssignedGhostRoles.Values.Any(x => x.Instance is GA ga && ga.ProtectionList.Contains(target.PlayerId))) + { + Notify("GAGuarded"); - if (Main.PlayerStates[target.PlayerId].deathReason == PlayerState.DeathReason.etc) + if (killer.IsLocalPlayer()) + Achievements.Type.IForgotThisRoleExists.CompleteAfterGameEnd(); + + return false; + } + + if (SoulHunter.IsSoulHunterTarget(killer.PlayerId) && target.Is(CustomRoles.SoulHunter)) + { + Notify("SoulHunterTargetNotifyNoKill"); + return false; + } + + if (killer.IsRoleBlocked()) + { + killer.Notify(BlockedAction.Kill.GetBlockNotify()); + return false; + } + + if (killer.Is(CustomRoles.Traitor) && target.Is(CustomRoleTypes.Impostor)) + { + Notify("TraitorKillImpostor"); + 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()) { - // If the cause of death is not specified, it is determined as a normal kill. - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Kill; + killer.SetKillCooldown(15f); + return false; } + } - // Let’s see if Youtuber was stabbed first - if (Main.FirstDied == string.Empty && target.Is(CustomRoles.Youtuber)) + if (Crusader.ForCrusade.Contains(target.PlayerId)) + { + foreach (PlayerControl player in Main.AllPlayerControls) { - CustomSoundsManager.RPCPlayCustomSoundAll("Congrats"); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Youtuber); - CustomWinnerHolder.WinnerIds.Add(target.PlayerId); - } - - Postman.CheckAndResetTargets(target, true); - - if (target.Is(CustomRoles.Trapper) && killer != target) killer.TrapperKilled(target); - - if (target.Is(CustomRoles.Stained)) Stained.OnDeath(target, killer); - - Witness.AllKillers[killer.PlayerId] = TimeStamp; - - killer.AddKillTimerToDict(); - - switch (target.GetCustomRole()) - { - case CustomRoles.BallLightning: - if (killer != target) BallLightning.MurderPlayer(killer, target); - - break; - case CustomRoles.Bane: - if (killer != target) Bane.OnKilled(killer); - - break; - case CustomRoles.Markseeker: - Markseeker.OnDeath(target); - break; - } - - Main.PlayerStates[killer.PlayerId].Role.OnMurder(killer, target); - - Chef.SpitOutFood(killer); - - if (CustomGameMode.Speedrun.IsActiveOrIntegrated()) SpeedrunManager.ResetTimer(killer); - - 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 (killer.Is(CustomRoles.Deadlined)) Deadlined.SetDone(killer); - - if (target.Is(CustomRoles.Avanger)) - { - PlayerControl[] pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId).ToArray(); - PlayerControl rp = pcList.RandomElement(); - if (!rp.Is(CustomRoles.Pestilence)) rp.Suicide(PlayerState.DeathReason.Revenge, target); - } - - if (target.Is(CustomRoles.Bait) && !killer.Is(CustomRoles.Minimalism) && (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"); - - LateTask.New(() => + if (player.Is(CustomRoles.Crusader) && player.IsAlive() && !killer.Is(CustomRoles.Pestilence) && !killer.Is(CustomRoles.Minimalism)) { - if (GameStates.IsInTask) - { - if (!Options.ReportBaitAtAllCost.GetBool()) - killer.CmdReportDeadBody(target.Data); - else - killer.NoCheckStartMeeting(target.Data, true); - } - }, delay, "Bait Self Report"); + player.Kill(killer); + Crusader.ForCrusade.Remove(target.PlayerId); + killer.RpcGuardAndKill(target); + return false; + } + + if (player.Is(CustomRoles.Crusader) && player.IsAlive() && killer.Is(CustomRoles.Pestilence)) + { + killer.Kill(player); + Crusader.ForCrusade.Remove(target.PlayerId); + target.RpcGuardAndKill(killer); + Main.PlayerStates[player.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; + return false; + } } + } - AfterPlayerDeathTasks(target); + switch (target.GetCustomRole()) + { + case CustomRoles.Medic: + Medic.IsDead(target); + break; + case CustomRoles.Gambler when Gambler.IsShielded.ContainsKey(target.PlayerId): + Notify("SomeSortOfProtection"); + killer.SetKillCooldown(5f); + return false; + case CustomRoles.Spiritcaller: + if (Spiritcaller.InProtect(target)) + { + killer.RpcGuardAndKill(target); + Notify("SomeSortOfProtection"); + return false; + } - Main.PlayerStates[target.PlayerId].SetDead(); - target.SetRealKiller(killer, true); - CountAlivePlayers(true); + break; + } - if (Options.LowLoadMode.GetBool()) - { - __instance.MarkDirtySettings(); - target.MarkDirtySettings(); - NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); - NotifyRoles(SpecifySeer: target); - } - else - { - SyncAllSettings(); - NotifyRoles(ForceLoop: true); - } + if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode)) + { + Main.ShieldPlayer = string.Empty; + killer.SetKillCooldown(15f); + killer.Notify(GetString("TriedToKillLastGameFirstKill"), 10f); + return false; + } - Statistics.OnMurder(killer, target); + 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 (!Main.PlayerStates[target.PlayerId].Role.OnCheckMurderAsTarget(killer, target)) + { + Notify("SomeSortOfProtection"); + return false; + } + + if (killer.Is(CustomRoles.Rookie) && MeetingStates.FirstMeeting) + { + Notify("RookieKillRoundOne"); + return false; + } + + if (!check) killer.Kill(target); + + if (killer.Is(CustomRoles.Doppelganger)) Doppelganger.OnCheckMurderEnd(killer, target); + + return true; + + void Notify(string message) + { + killer.Notify(ColorString(Color.yellow, GetString("CheckMurderFail") + GetString(message)), 15f); } } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))] +internal static class MurderPlayerPatch +{ + public static bool 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 (GameStates.IsLobby) + { + Logger.Info("Murder triggered in lobby, so murder canceled", "MurderPlayer Prefix"); + return false; + } + + RandomSpawn.CustomNetworkTransformHandleRpcPatch.HasSpawned.Add(__instance.PlayerId); + + if (!target.IsProtected() && !Doppelganger.DoppelVictim.ContainsKey(target.PlayerId) && !Camouflage.ResetSkinAfterDeathPlayers.Contains(target.PlayerId)) + { + Camouflage.ResetSkinAfterDeathPlayers.Add(target.PlayerId); + Camouflage.RpcSetSkin(target, true); + } + + return true; + } + + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + if (__instance == null || target == null || __instance.PlayerId == 255 || target.PlayerId == 255) return; + + if (target.AmOwner) RemoveDisableDevicesPatch.UpdateDisableDevices(); + + if (!target.Data.IsDead || !AmongUsClient.Instance.AmHost) return; + + if (OverKiller.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 + { + int cd = Options.DefaultShapeshiftCooldown.GetInt(); + Main.AbilityCD[killer.PlayerId] = (TimeStamp, cd); + SendRPC(CustomRPC.SyncAbilityCD, 1, killer.PlayerId, cd); + } + } + + 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 == string.Empty && target.Is(CustomRoles.Youtuber)) + { + CustomSoundsManager.RPCPlayCustomSoundAll("Congrats"); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Youtuber); + CustomWinnerHolder.WinnerIds.Add(target.PlayerId); + } + + Postman.CheckAndResetTargets(target, true); + + if (target.Is(CustomRoles.Trapper) && killer != target) killer.TrapperKilled(target); + + if (target.Is(CustomRoles.Stained)) Stained.OnDeath(target, killer); + + Witness.AllKillers[killer.PlayerId] = TimeStamp; + + killer.AddKillTimerToDict(); + + switch (target.GetCustomRole()) + { + case CustomRoles.BallLightning: + if (killer != target) BallLightning.MurderPlayer(killer, target); + + break; + case CustomRoles.Bane: + if (killer != target) Bane.OnKilled(killer); + + break; + case CustomRoles.Markseeker: + Markseeker.OnDeath(target); + break; + } + + Main.PlayerStates[killer.PlayerId].Role.OnMurder(killer, target); + + Chef.SpitOutFood(killer); + + if (CustomGameMode.Speedrun.IsActiveOrIntegrated()) SpeedrunManager.ResetTimer(killer); + + 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 (killer.Is(CustomRoles.Deadlined)) Deadlined.SetDone(killer); + + if (target.Is(CustomRoles.Avanger)) + { + PlayerControl[] pcList = Main.AllAlivePlayerControls.Where(x => x.PlayerId != target.PlayerId).ToArray(); + PlayerControl rp = pcList.RandomElement(); + if (!rp.Is(CustomRoles.Pestilence)) rp.Suicide(PlayerState.DeathReason.Revenge, target); + } + + if (target.Is(CustomRoles.Bait) && !killer.Is(CustomRoles.Minimalism) && (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"); + + LateTask.New(() => + { + if (GameStates.IsInTask) + { + if (!Options.ReportBaitAtAllCost.GetBool()) + killer.CmdReportDeadBody(target.Data); + else + killer.NoCheckStartMeeting(target.Data, true); + } + }, delay, "Bait Self Report"); + } + + AfterPlayerDeathTasks(target); + + Main.PlayerStates[target.PlayerId].SetDead(); + target.SetRealKiller(killer, true); + CountAlivePlayers(true); + + if (Options.LowLoadMode.GetBool()) + { + __instance.MarkDirtySettings(); + target.MarkDirtySettings(); + NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); + NotifyRoles(SpecifySeer: target); + } + else + { + SyncAllSettings(); + NotifyRoles(ForceLoop: true); + } + + Statistics.OnMurder(killer, target); + } +} // Triggered when the shapeshifter selects a target - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckShapeshift))] - internal static class CheckShapeshiftPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckShapeshift))] +internal static class CheckShapeshiftPatch +{ + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] bool shouldAnimate*/) { - 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 - } + return ShapeshiftPatch.ProcessShapeshift(__instance, target); // return false to cancel the shapeshift } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckShapeshift))] - internal static class CmdCheckShapeshiftPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckShapeshift))] +internal static class CmdCheckShapeshiftPatch +{ + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] bool shouldAnimate*/) { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target /*, [HarmonyArgument(1)] bool shouldAnimate*/) - { - return CheckShapeshiftPatch.Prefix(__instance, target /*, shouldAnimate*/); - } + return CheckShapeshiftPatch.Prefix(__instance, target /*, shouldAnimate*/); } +} // Triggered when the egg animation starts playing - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Shapeshift))] - internal static class ShapeshiftPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Shapeshift))] +internal static class ShapeshiftPatch +{ + public static bool ProcessShapeshift(PlayerControl shapeshifter, PlayerControl target) { - public static bool ProcessShapeshift(PlayerControl shapeshifter, PlayerControl target) + if (!Main.ProcessShapeshifts) return true; + + if (AntiBlackout.SkipTasks) { - if (!Main.ProcessShapeshifts) return true; - - if (AntiBlackout.SkipTasks) - { - Logger.Info("Shapeshift while AntiBlackOut protection is in progress, shapeshift canceled", "Shapeshift"); - return false; - } - - if (shapeshifter == null || target == null) return true; - - Logger.Info($"{shapeshifter.GetNameWithRole()} => {target.GetNameWithRole()}", "Shapeshift"); - - bool shapeshifting = shapeshifter.PlayerId != target.PlayerId; - - if (AmongUsClient.Instance.AmHost && shapeshifting) - { - if (!Rhapsode.CheckAbilityUse(shapeshifter)) return false; - - if (shapeshifter.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) - { - shapeshifter.Notify(GetString("TraineeNotify")); - return false; - } - } - - Main.CheckShapeshift[shapeshifter.PlayerId] = shapeshifting; - Main.ShapeshiftTarget[shapeshifter.PlayerId] = target.PlayerId; - - if (!AmongUsClient.Instance.AmHost) return true; - - if (!shapeshifting) Camouflage.RpcSetSkin(shapeshifter); - - var 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); - isSSneeded = false; - } - - bool ctf = CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated(); - if (ctf) CTFManager.TryPickUpFlag(shapeshifter); - - CustomRoles role = shapeshifter.GetCustomRole(); - - bool forceCancel = role.ForceCancelShapeshift(); - bool unshiftTrigger = role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()); - unshiftTrigger |= role.AlwaysUsesUnshift(); - - unshiftTrigger |= ctf; - forceCancel |= unshiftTrigger; - isSSneeded &= !ctf; - - if (Changeling.ChangedRole.TryGetValue(shapeshifter.PlayerId, out bool changed) && changed && shapeshifter.GetRoleTypes() != RoleTypes.Shapeshifter) - { - forceCancel = true; - isSSneeded = false; - } - - bool shouldCancel = Options.DisableShapeshiftAnimations.GetBool(); - bool shouldAlwaysCancel = shouldCancel && Options.DisableAllShapeshiftAnimations.GetBool(); - bool doSSwithoutAnim = isSSneeded && shouldAlwaysCancel; - - doSSwithoutAnim |= isSSneeded && role.IsNoAnimationShifter(); - isSSneeded &= !shouldAlwaysCancel; - forceCancel |= shouldAlwaysCancel; - isSSneeded &= !doSSwithoutAnim; - - // 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) - LateTask.New(() => NotifyRoles(NoCache: true), 1.2f, "ShapeShiftNotify"); - - if (!(shapeshifting && doSSwithoutAnim) && !isSSneeded && !Swapster.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId)) - LateTask.New(shapeshifter.RpcResetAbilityCooldown, 0.01f, log: false); - - if (!isSSneeded) - { - Main.CheckShapeshift[shapeshifter.PlayerId] = false; - shapeshifter.RpcRejectShapeshift(); - NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter); - } - - if (doSSwithoutAnim) - { - shapeshifter.RpcShapeshift(target, false); - return false; - } - - if (unshiftTrigger) - { - shapeshifter.CheckAndSetUnshiftState(force: true); - shapeshifter.AddAbilityCD(); - } - - - bool animated = isSSneeded || (!shouldCancel && !forceCancel) || (!shapeshifting && !shouldAlwaysCancel && !unshiftTrigger); - - Statistics.OnShapeshift(shapeshifter, shapeshifting, animated); - - return animated; + Logger.Info("Shapeshift while AntiBlackOut protection is in progress, shapeshift canceled", "Shapeshift"); + return false; } - // Tasks that should run when someone performs a shapeshift (with the egg animation) should be here. - public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + if (shapeshifter == null || target == null) return true; + + Logger.Info($"{shapeshifter.GetNameWithRole()} => {target.GetNameWithRole()}", "Shapeshift"); + + bool shapeshifting = shapeshifter.PlayerId != target.PlayerId; + + if (AmongUsClient.Instance.AmHost && shapeshifting) { - if (!Main.ProcessShapeshifts || !GameStates.IsInTask || __instance == null || target == null) return; + if (!Rhapsode.CheckAbilityUse(shapeshifter)) return false; - bool shapeshifting = __instance.PlayerId != target.PlayerId; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (shapeshifter.Is(CustomRoles.Trainee) && MeetingStates.FirstMeeting) { - if (pc.Is(CustomRoles.Shiftguard)) pc.Notify(shapeshifting ? GetString("ShiftguardNotifySS") : GetString("ShiftguardNotifyUnshift")); + shapeshifter.Notify(GetString("TraineeNotify")); + return false; + } + } - switch (Main.PlayerStates[pc.PlayerId].Role) - { - case Adventurer av: - Adventurer.OnAnyoneShapeshiftLoop(av, __instance); - break; - case Crewmate.Sentry st: - st.OnAnyoneShapeshiftLoop(__instance, target); - break; - } + Main.CheckShapeshift[shapeshifter.PlayerId] = shapeshifting; + Main.ShapeshiftTarget[shapeshifter.PlayerId] = target.PlayerId; + + if (!AmongUsClient.Instance.AmHost) return true; + + if (!shapeshifting) Camouflage.RpcSetSkin(shapeshifter); + + var 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); + isSSneeded = false; + } + + bool ctf = CustomGameMode.CaptureTheFlag.IsActiveOrIntegrated(); + if (ctf) CTFManager.TryPickUpFlag(shapeshifter); + + CustomRoles role = shapeshifter.GetCustomRole(); + + bool forceCancel = role.ForceCancelShapeshift(); + bool unshiftTrigger = role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!role.IsNeutral() || Options.UseUnshiftTriggerForNKs.GetBool()); + unshiftTrigger |= role.AlwaysUsesUnshift(); + + unshiftTrigger |= ctf; + forceCancel |= unshiftTrigger; + isSSneeded &= !ctf; + + if (Changeling.ChangedRole.TryGetValue(shapeshifter.PlayerId, out bool changed) && changed && shapeshifter.GetRoleTypes() != RoleTypes.Shapeshifter) + { + forceCancel = true; + isSSneeded = false; + } + + bool shouldCancel = Options.DisableShapeshiftAnimations.GetBool(); + bool shouldAlwaysCancel = shouldCancel && Options.DisableAllShapeshiftAnimations.GetBool(); + bool doSSwithoutAnim = isSSneeded && shouldAlwaysCancel; + + doSSwithoutAnim |= isSSneeded && role.IsNoAnimationShifter(); + isSSneeded &= !shouldAlwaysCancel; + forceCancel |= shouldAlwaysCancel; + isSSneeded &= !doSSwithoutAnim; + + // 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) + LateTask.New(() => NotifyRoles(NoCache: true), 1.2f, "ShapeShiftNotify"); + + if (!(shapeshifting && doSSwithoutAnim) && !isSSneeded && !Swapster.FirstSwapTarget.ContainsKey(shapeshifter.PlayerId)) + LateTask.New(shapeshifter.RpcResetAbilityCooldown, 0.01f, log: false); + + if (!isSSneeded) + { + Main.CheckShapeshift[shapeshifter.PlayerId] = false; + shapeshifter.RpcRejectShapeshift(); + NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter); + } + + if (doSSwithoutAnim) + { + shapeshifter.RpcShapeshift(target, false); + return false; + } + + if (unshiftTrigger) + { + shapeshifter.CheckAndSetUnshiftState(force: true); + shapeshifter.AddAbilityCD(); + } + + + bool animated = isSSneeded || (!shouldCancel && !forceCancel) || (!shapeshifting && !shouldAlwaysCancel && !unshiftTrigger); + + Statistics.OnShapeshift(shapeshifter, shapeshifting, animated); + + return animated; + } + + // Tasks that should run when someone performs a shapeshift (with the egg animation) should be here. + public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) + { + if (!Main.ProcessShapeshifts || !GameStates.IsInTask || __instance == null || target == null) return; + + bool shapeshifting = __instance.PlayerId != target.PlayerId; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.Is(CustomRoles.Shiftguard)) pc.Notify(shapeshifting ? GetString("ShiftguardNotifySS") : GetString("ShiftguardNotifyUnshift")); + + switch (Main.PlayerStates[pc.PlayerId].Role) + { + case Adventurer av: + Adventurer.OnAnyoneShapeshiftLoop(av, __instance); + break; + case Crewmate.Sentry st: + st.OnAnyoneShapeshiftLoop(__instance, target); + break; } } } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcShapeshift))] - internal static class RpcShapeshiftPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcShapeshift))] +internal static class RpcShapeshiftPatch +{ + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) { - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) - { - Main.CheckShapeshift[__instance.PlayerId] = __instance.PlayerId != target.PlayerId; - } + Main.CheckShapeshift[__instance.PlayerId] = __instance.PlayerId != target.PlayerId; } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] - internal static class ReportDeadBodyPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))] +internal static class ReportDeadBodyPatch +{ + public static Dictionary CanReport; + public static readonly Dictionary> WaitReport = []; + + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] NetworkedPlayerInfo target) { - public static Dictionary CanReport; - public static readonly Dictionary> WaitReport = []; + if (GameStates.IsMeeting) return false; - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] NetworkedPlayerInfo target) + if (Options.DisableMeeting.GetBool()) return false; + + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; + + if (Options.DisableReportWhenCC.GetBool() && Camouflage.IsCamouflage) return false; + + if (!CanReport[__instance.PlayerId]) { - if (GameStates.IsMeeting) return false; - - if (Options.DisableMeeting.GetBool()) return false; - - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; - - if (Options.DisableReportWhenCC.GetBool() && Camouflage.IsCamouflage) 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 (KeyValuePair 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 - //============================================= - - PlayerControl killer = target?.Object?.GetRealKiller(); - CustomRoles? killerRole = killer?.GetCustomRole(); - - if (Camouflage.IsCamouflage && Options.DisableReportWhenCC.GetBool()) return false; - - if (Main.PlayerStates.Values.Any(x => x.Role is Tremor { IsDoom: true })) return false; - - if (target == null) - { - if (__instance.Is(CustomRoles.Jester) && !Jester.JesterCanUseButton.GetBool()) return false; - - if (__instance.Is(CustomRoles.NiceSwapper) && !NiceSwapper.CanStartMeeting.GetBool()) return false; - - if (__instance.Is(CustomRoles.Adrenaline) && !Adrenaline.CanCallMeeting(__instance)) return false; - - if (SoulHunter.IsSoulHunterTarget(__instance.PlayerId)) - { - __instance.Notify(GetString("SoulHunterTargetNotifyNoMeeting")); - 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 (!Hypnotist.OnAnyoneReport()) - { - if (__instance.IsLocalPlayer()) - Achievements.Type.Hypnosis.CompleteAfterGameEnd(); - - return false; - } - - if (!Occultist.OnAnyoneReportDeadBody(target)) return false; - - if (!Altruist.OnAnyoneCheckReportDeadBody(__instance, target)) return false; - - if (!BoobyTrap.OnAnyoneCheckReportDeadBody(__instance, target)) 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 - || Cleaner.CleanerBodies.Contains(target.PlayerId)) - return false; - - PlayerControl 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 (tpc.Is(CustomRoles.Unreportable)) return false; - } - - if (Options.SyncButtonMode.GetBool() && target == null) - { - Logger.Info("Max buttons:" + Options.SyncedButtonCount.GetInt() + ", used:" + Options.UsedButtonCount, "ReportDeadBody"); - - if (Options.SyncedButtonCount.GetFloat() <= Options.UsedButtonCount) - { - Logger.Info("The ship has no more emergency meetings left", "ReportDeadBody"); - return false; - } - - Options.UsedButtonCount++; - if (Math.Abs(Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) < 0.5f) Logger.Info("This was the last allowed emergency meeting", "ReportDeadBody"); - } - - AfterReportTasks(__instance, target); - } - catch (Exception e) - { - ThrowException(e); - } - - return true; + 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; } - public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo target, bool force = false) + Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()} => {target?.Object?.GetNameWithRole() ?? "null"}", "ReportDeadBody"); + + foreach (KeyValuePair kvp in Main.PlayerStates) kvp.Value.LastRoom = GetPlayerById(kvp.Key).GetPlainShipRoom(); + + if (!AmongUsClient.Instance.AmHost) return true; + + try { - //============================================================================================= - // Hereinafter, it is confirmed that the meeting is allowed, and the meeting will start. - //============================================================================================= + // If the caller is dead, this process will cancel the meeting, so stop here. + if (__instance.Data.IsDead) return false; - Asthmatic.RunChecks = false; + //============================================= + // Next, check whether this meeting is allowed + //============================================= - Damocles.CountRepairSabotage = false; - Stressed.CountRepairSabotage = false; + PlayerControl killer = target?.Object?.GetRealKiller(); + CustomRoles? killerRole = killer?.GetCustomRole(); - Main.DiedThisRound = []; + if (Camouflage.IsCamouflage && Options.DisableReportWhenCC.GetBool()) return false; + + if (Main.PlayerStates.Values.Any(x => x.Role is Tremor { IsDoom: true })) return false; if (target == null) { - switch (Main.PlayerStates[player.PlayerId].Role) - { - case Mayor: - Mayor.MayorUsedButtonCount[player.PlayerId]++; - break; - case Rogue rg: - rg.OnButtonPressed(); - break; - } + if (__instance.Is(CustomRoles.Jester) && !Jester.JesterCanUseButton.GetBool()) return false; - if (QuizMaster.On) + if (__instance.Is(CustomRoles.NiceSwapper) && !NiceSwapper.CanStartMeeting.GetBool()) return false; + + if (__instance.Is(CustomRoles.Adrenaline) && !Adrenaline.CanCallMeeting(__instance)) return false; + + if (SoulHunter.IsSoulHunterTarget(__instance.PlayerId)) { - QuizMaster.Data.LastPlayerPressedButtonName = player.GetRealName(); - QuizMaster.Data.NumEmergencyMeetings++; + __instance.Notify(GetString("SoulHunterTargetNotifyNoMeeting")); + return false; } } - else - { - PlayerControl tpc = target.Object; - if (tpc != null && !tpc.IsAlive()) + if (target != null) + { + if (Bloodhound.UnreportablePlayers.Contains(target.PlayerId) + || Vulture.UnreportablePlayers.Contains(target.PlayerId)) + return false; + + if (!Librarian.OnAnyoneReport(__instance)) return false; + + if (!Hypnotist.OnAnyoneReport()) { - if (player.Is(CustomRoles.Detective) && player.PlayerId != target.PlayerId) - Detective.OnReportDeadBody(player, target.Object); - else if (player.Is(CustomRoles.Sleuth) && player.PlayerId != target.PlayerId) + if (__instance.IsLocalPlayer()) + Achievements.Type.Hypnosis.CompleteAfterGameEnd(); + + return false; + } + + if (!Occultist.OnAnyoneReportDeadBody(target)) return false; + + if (!Altruist.OnAnyoneCheckReportDeadBody(__instance, target)) return false; + + if (!BoobyTrap.OnAnyoneCheckReportDeadBody(__instance, target)) 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 + || Cleaner.CleanerBodies.Contains(target.PlayerId)) + return false; + + PlayerControl 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()) { - string msg = string.Format(GetString("SleuthMsg"), tpc.GetRealName(), tpc.GetDisplayRoleName()); - Main.SleuthMsgs[player.PlayerId] = msg; + __instance.Suicide(); + return false; } } - if (Virus.InfectedBodies.Contains(target.PlayerId)) Virus.OnKilledBodyReport(player); + if (tpc.Is(CustomRoles.Unreportable)) return false; + } - if (QuizMaster.On) + if (Options.SyncButtonMode.GetBool() && target == null) + { + Logger.Info("Max buttons:" + Options.SyncedButtonCount.GetInt() + ", used:" + Options.UsedButtonCount, "ReportDeadBody"); + + if (Options.SyncedButtonCount.GetFloat() <= Options.UsedButtonCount) { - QuizMaster.Data.LastReporterName = player.GetRealName(); - QuizMaster.Data.LastReportedPlayer = (Palette.GetColorName(target.DefaultOutfit.ColorId), target.Object); - if (MeetingStates.FirstMeeting) QuizMaster.Data.FirstReportedBodyPlayerName = target.Object.GetRealName(); + Logger.Info("The ship has no more emergency meetings left", "ReportDeadBody"); + return false; } + + Options.UsedButtonCount++; + if (Math.Abs(Options.SyncedButtonCount.GetFloat() - Options.UsedButtonCount) < 0.5f) Logger.Info("This was the last allowed emergency meeting", "ReportDeadBody"); + } + + AfterReportTasks(__instance, target); + } + catch (Exception e) + { + ThrowException(e); + } + + return true; + } + + public static void AfterReportTasks(PlayerControl player, NetworkedPlayerInfo target, bool force = false) + { + //============================================================================================= + // Hereinafter, it is confirmed that the meeting is allowed, and the meeting will start. + //============================================================================================= + + Asthmatic.RunChecks = false; + + Damocles.CountRepairSabotage = false; + Stressed.CountRepairSabotage = false; + + Main.DiedThisRound = []; + + if (target == null) + { + switch (Main.PlayerStates[player.PlayerId].Role) + { + case Mayor: + Mayor.MayorUsedButtonCount[player.PlayerId]++; + break; + case Rogue rg: + rg.OnButtonPressed(); + break; } if (QuizMaster.On) { - if (MeetingStates.FirstMeeting) QuizMaster.Data.NumPlayersDeadFirstRound = Main.AllPlayerControls.Count(x => x.Data.IsDead && !x.Is(CustomRoles.GM)); - - QuizMaster.Data.NumMeetings++; + QuizMaster.Data.LastPlayerPressedButtonName = player.GetRealName(); + QuizMaster.Data.NumEmergencyMeetings++; } + } + else + { + PlayerControl tpc = target.Object; - if (Main.LoversPlayers.Exists(x => x.IsAlive()) && Main.IsLoversDead && Lovers.LoverDieConsequence.GetValue() == 1) + if (tpc != null && !tpc.IsAlive()) { - PlayerControl aliveLover = Main.LoversPlayers.First(x => x.IsAlive()); - - switch (Lovers.LoverSuicideTime.GetValue()) + if (player.Is(CustomRoles.Detective) && player.PlayerId != target.PlayerId) + Detective.OnReportDeadBody(player, target.Object); + else if (player.Is(CustomRoles.Sleuth) && player.PlayerId != target.PlayerId) { - case 1: - aliveLover.Suicide(PlayerState.DeathReason.FollowingSuicide); - break; - case 2: - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, aliveLover.PlayerId); - break; + string msg = string.Format(GetString("SleuthMsg"), tpc.GetRealName(), tpc.GetDisplayRoleName()); + Main.SleuthMsgs[player.PlayerId] = msg; } } - Enigma.OnReportDeadBody(player, target); - Mediumshiper.OnReportDeadBody(target); - Mortician.OnReportDeadBody(player, target); - Spiritualist.OnReportDeadBody(target); + if (Virus.InfectedBodies.Contains(target.PlayerId)) Virus.OnKilledBodyReport(player); - Bloodmoon.OnMeetingStart(); - Deadlined.OnMeetingStart(); - - Main.LastVotedPlayerInfo = null; - Arsonist.ArsonistTimer.Clear(); - Farseer.FarseerTimer.Clear(); - Puppeteer.PuppeteerList.Clear(); - Puppeteer.PuppeteerDelayList.Clear(); - Main.GuesserGuessed.Clear(); - Veteran.VeteranInProtect.Clear(); - Grenadier.GrenadierBlinding.Clear(); - SecurityGuard.BlockSabo.Clear(); - Grenadier.MadGrenadierBlinding.Clear(); - Divinator.DidVote.Clear(); - Oracle.DidVote.Clear(); - Vulture.Clear(); - - foreach (PlayerState state in Main.PlayerStates.Values) - if (state.Role.IsEnable) - state.Role.OnReportDeadBody(); - - Main.AbilityCD.Clear(); - SendRPC(CustomRPC.SyncAbilityCD, 2); - - if (player.Is(CustomRoles.Damocles)) Damocles.OnReport(player.PlayerId); - - Damocles.OnMeetingStart(); - - if (player.Is(CustomRoles.Stressed)) Stressed.OnReport(player); - - Stressed.OnMeetingStart(); - - foreach (PlayerControl pc in Main.AllPlayerControls) + if (QuizMaster.On) { - if (Main.CheckShapeshift.ContainsKey(pc.PlayerId) && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId)) - Camouflage.RpcSetSkin(pc, RevertToDefault: true); + QuizMaster.Data.LastReporterName = player.GetRealName(); + QuizMaster.Data.LastReportedPlayer = (Palette.GetColorName(target.DefaultOutfit.ColorId), target.Object); + if (MeetingStates.FirstMeeting) QuizMaster.Data.FirstReportedBodyPlayerName = target.Object.GetRealName(); + } + } - if (Main.CurrentMap == MapNames.Fungle && (pc.IsMushroomMixupActive() || IsActive(SystemTypes.MushroomMixupSabotage))) - pc.FixMixedUpOutfit(); + if (QuizMaster.On) + { + if (MeetingStates.FirstMeeting) QuizMaster.Data.NumPlayersDeadFirstRound = Main.AllPlayerControls.Count(x => x.Data.IsDead && !x.Is(CustomRoles.GM)); - PhantomRolePatch.OnReportDeadBody(pc, force); + QuizMaster.Data.NumMeetings++; + } + + if (Main.LoversPlayers.Exists(x => x.IsAlive()) && Main.IsLoversDead && Lovers.LoverDieConsequence.GetValue() == 1) + { + PlayerControl aliveLover = Main.LoversPlayers.First(x => x.IsAlive()); + + switch (Lovers.LoverSuicideTime.GetValue()) + { + case 1: + aliveLover.Suicide(PlayerState.DeathReason.FollowingSuicide); + break; + case 2: + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, aliveLover.PlayerId); + break; + } + } + + Enigma.OnReportDeadBody(player, target); + Mediumshiper.OnReportDeadBody(target); + Mortician.OnReportDeadBody(player, target); + Spiritualist.OnReportDeadBody(target); + + Bloodmoon.OnMeetingStart(); + Deadlined.OnMeetingStart(); + + Main.LastVotedPlayerInfo = null; + Arsonist.ArsonistTimer.Clear(); + Farseer.FarseerTimer.Clear(); + Puppeteer.PuppeteerList.Clear(); + Puppeteer.PuppeteerDelayList.Clear(); + Main.GuesserGuessed.Clear(); + Veteran.VeteranInProtect.Clear(); + Grenadier.GrenadierBlinding.Clear(); + SecurityGuard.BlockSabo.Clear(); + Grenadier.MadGrenadierBlinding.Clear(); + Divinator.DidVote.Clear(); + Oracle.DidVote.Clear(); + Vulture.Clear(); + + foreach (PlayerState state in Main.PlayerStates.Values) + if (state.Role.IsEnable) + state.Role.OnReportDeadBody(); + + Main.AbilityCD.Clear(); + SendRPC(CustomRPC.SyncAbilityCD, 2); + + if (player.Is(CustomRoles.Damocles)) Damocles.OnReport(player.PlayerId); + + Damocles.OnMeetingStart(); + + if (player.Is(CustomRoles.Stressed)) Stressed.OnReport(player); + + Stressed.OnMeetingStart(); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + if (Main.CheckShapeshift.ContainsKey(pc.PlayerId) && !Doppelganger.DoppelVictim.ContainsKey(pc.PlayerId)) + Camouflage.RpcSetSkin(pc, RevertToDefault: true); + + if (Main.CurrentMap == MapNames.Fungle && (pc.IsMushroomMixupActive() || IsActive(SystemTypes.MushroomMixupSabotage))) + pc.FixMixedUpOutfit(); + + PhantomRolePatch.OnReportDeadBody(pc, force); + } + + MeetingTimeManager.OnReportDeadBody(); + + NameNotifyManager.Reset(); + NotifyRoles(true, NoCache: true, CamouflageIsForMeeting: true, GuesserIsForMeeting: true); + + LateTask.New(SyncAllSettings, 3f, "SyncAllSettings on meeting start"); + + Main.ProcessShapeshifts = false; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + CustomRoles role = pc.GetCustomRole(); + + if (role.AlwaysUsesUnshift() || (role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool()))) + pc.RpcShapeshift(pc, false); + + if (Camouflage.IsCamouflage) Camouflage.RpcSetSkin(pc, RevertToDefault: true, ForceRevert: true); + } + + Camouflage.CamoTimesThisRound = 0; + + if (HudManagerPatch.AchievementUnlockedText == string.Empty) + HudManagerPatch.ClearLowerInfoText(); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] +internal static class FixedUpdatePatch +{ + private static readonly StringBuilder Mark = new(20); + private static readonly StringBuilder Suffix = new(); + private static int LevelKickBufferTime = 10; + private static readonly Dictionary BufferTime = []; + private static readonly Dictionary DeadBufferTime = []; + private static readonly Dictionary LastUpdate = []; + private static readonly Dictionary LastAddAbilityTime = []; + private static long LastErrorTS; + + public static void Postfix(PlayerControl __instance) + { + if (__instance == null || __instance.PlayerId == 255) return; + + if (AmongUsClient.Instance.AmHost && __instance.AmOwner) + CustomNetObject.FixedUpdate(); + + if (AmongUsClient.Instance.AmHost && __instance.IsAlive()) + VentilationSystemDeterioratePatch.CheckVentInteraction(__instance); + + byte id = __instance.PlayerId; + + if (AmongUsClient.Instance.AmHost && GameStates.IsInTask && ReportDeadBodyPatch.CanReport[id] && ReportDeadBodyPatch.WaitReport[id].Count > 0) + { + if (id.IsPlayerRoleBlocked()) + { + __instance.Notify(BlockedAction.Report.GetBlockNotify()); + Logger.Info("Dead Body Report Blocked (player role blocked)", "FixedUpdate.ReportDeadBody"); + ReportDeadBodyPatch.WaitReport[id].Clear(); + } + else + { + NetworkedPlayerInfo info = ReportDeadBodyPatch.WaitReport[id][0]; + ReportDeadBodyPatch.WaitReport[id].Clear(); + Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}: Now that it is possible to report, we will process the report.", "ReportDeadBody"); + __instance.ReportDeadBody(info); + } + } + + if (AmongUsClient.Instance.AmHost) + { + if (GhostRolesManager.AssignedGhostRoles.TryGetValue(id, out (CustomRoles Role, IGhostRole Instance) ghostRole)) + { + switch (ghostRole.Instance) + { + case Warden warden: + warden.Update(__instance); + break; + case Haunter haunter: + haunter.Update(__instance); + break; + case Bloodmoon bloodmoon: + Bloodmoon.Update(__instance, bloodmoon); + break; + } + } + else if (!Main.HasJustStarted && GameStates.IsInTask && !ExileController.Instance && GhostRolesManager.ShouldHaveGhostRole(__instance)) GhostRolesManager.AssignGhostRole(__instance); + } + + if (GameStates.InGame && Options.DontUpdateDeadPlayers.GetBool() && !__instance.IsAlive() && !__instance.GetCustomRole().NeedsUpdateAfterDeath() && !CustomGameMode.RoomRush.IsActiveOrIntegrated()) + { + int buffer = Options.DeepLowLoad.GetBool() ? 30 : 10; + DeadBufferTime.TryAdd(id, buffer); + DeadBufferTime[id]--; + if (DeadBufferTime[id] > 0) return; + + DeadBufferTime[id] = buffer; + } + + if (Options.LowLoadMode.GetBool()) + { + BufferTime.TryAdd(id, Options.DeepLowLoad.GetBool() ? 30 : 10); + BufferTime[id]--; + } + + try + { + DoPostfix(__instance); + } + catch (Exception ex) + { + long now = TimeStamp; + + if (LastErrorTS != now) + { + Logger.Error($"Error for {__instance.GetNameWithRole()}:", "FixedUpdatePatch"); + ThrowException(ex); + LastErrorTS = now; } - MeetingTimeManager.OnReportDeadBody(); - - NameNotifyManager.Reset(); - NotifyRoles(true, NoCache: true, CamouflageIsForMeeting: true, GuesserIsForMeeting: true); - - LateTask.New(SyncAllSettings, 3f, "SyncAllSettings on meeting start"); - - Main.ProcessShapeshifts = false; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - CustomRoles role = pc.GetCustomRole(); - - if (role.AlwaysUsesUnshift() || (role.SimpleAbilityTrigger() && Options.UseUnshiftTrigger.GetBool() && (!pc.IsNeutralKiller() || Options.UseUnshiftTriggerForNKs.GetBool()))) - pc.RpcShapeshift(pc, false); - - if (Camouflage.IsCamouflage) Camouflage.RpcSetSkin(pc, RevertToDefault: true, ForceRevert: true); - } - - Camouflage.CamoTimesThisRound = 0; - - if (HudManagerPatch.AchievementUnlockedText == string.Empty) - HudManagerPatch.ClearLowerInfoText(); + Logger.Error($"Error for {__instance.GetNameWithRole()}: {ex}", "FixedUpdatePatch"); } } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] - internal static class FixedUpdatePatch + private static void DoPostfix(PlayerControl __instance) { - private static readonly StringBuilder Mark = new(20); - private static readonly StringBuilder Suffix = new(); - private static int LevelKickBufferTime = 10; - private static readonly Dictionary BufferTime = []; - private static readonly Dictionary DeadBufferTime = []; - private static readonly Dictionary LastUpdate = []; - private static readonly Dictionary LastAddAbilityTime = []; - private static long LastErrorTS; + PlayerControl player = __instance; + byte playerId = player.PlayerId; + byte lpId = PlayerControl.LocalPlayer.PlayerId; + bool localPlayer = playerId == lpId; // Updates that are independent of the player are only executed for the local player. - public static void Postfix(PlayerControl __instance) + var lowLoad = false; + + if (Options.LowLoadMode.GetBool()) { - if (__instance == null || __instance.PlayerId == 255) return; + if (BufferTime[playerId] > 0) + lowLoad = true; + else + BufferTime[playerId] = Options.DeepLowLoad.GetBool() ? 30 : 10; + } - if (AmongUsClient.Instance.AmHost && __instance.AmOwner) - CustomNetObject.FixedUpdate(); + bool inTask = GameStates.IsInTask; + bool alive = player.IsAlive(); - if (AmongUsClient.Instance.AmHost && __instance.IsAlive()) - VentilationSystemDeterioratePatch.CheckVentInteraction(__instance); - - byte id = __instance.PlayerId; - - if (AmongUsClient.Instance.AmHost && GameStates.IsInTask && ReportDeadBodyPatch.CanReport[id] && ReportDeadBodyPatch.WaitReport[id].Count > 0) + try + { + if (__instance.AmOwner && inTask && ((Main.ChangedRole && localPlayer && AmongUsClient.Instance.AmHost) || (!__instance.Is(CustomRoleTypes.Impostor) && __instance.CanUseKillButton() && !__instance.Data.IsDead))) { - if (id.IsPlayerRoleBlocked()) - { - __instance.Notify(BlockedAction.Report.GetBlockNotify()); - Logger.Info("Dead Body Report Blocked (player role blocked)", "FixedUpdate.ReportDeadBody"); - ReportDeadBodyPatch.WaitReport[id].Clear(); - } - else - { - NetworkedPlayerInfo info = ReportDeadBodyPatch.WaitReport[id][0]; - ReportDeadBodyPatch.WaitReport[id].Clear(); - Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}: Now that it is possible to report, we will process the report.", "ReportDeadBody"); - __instance.ReportDeadBody(info); - } + List players = __instance.GetPlayersInAbilityRangeSorted(); + PlayerControl closest = players.Count == 0 ? null : players[0]; + HudManager.Instance.KillButton.SetTarget(closest); } + } + catch { } + + if (localPlayer) + { + Zoom.OnFixedUpdate(); + TextBoxTMPSetTextPatch.Update(); + } + + if (!lowLoad) + { + NameNotifyManager.OnFixedUpdate(player); + TargetArrow.OnFixedUpdate(player); + LocateArrow.OnFixedUpdate(player); if (AmongUsClient.Instance.AmHost) { - if (GhostRolesManager.AssignedGhostRoles.TryGetValue(id, out (CustomRoles Role, IGhostRole Instance) ghostRole)) + Camouflage.OnFixedUpdate(player); + AFKDetector.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) { - switch (ghostRole.Instance) + LevelKickBufferTime = 20; + + if (player.GetClient().ProductUserId != "") { - case Warden warden: - warden.Update(__instance); - break; - case Haunter haunter: - haunter.Update(__instance); - break; - case Bloodmoon bloodmoon: - Bloodmoon.Update(__instance, bloodmoon); - break; + string hashedPuid = player.GetClient().GetHashedPuid(); + if (!BanManager.TempBanWhiteList.Contains(hashedPuid)) BanManager.TempBanWhiteList.Add(hashedPuid); + } + + if (!Main.AllPlayerControls.All(x => x.Data.PlayerLevel <= 1) && !LobbyPatch.IsGlitchedRoomCode()) + { + 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"); } } - else if (!Main.HasJustStarted && GameStates.IsInTask && !ExileController.Instance && GhostRolesManager.ShouldHaveGhostRole(__instance)) GhostRolesManager.AssignGhostRole(__instance); } - if (GameStates.InGame && Options.DontUpdateDeadPlayers.GetBool() && !__instance.IsAlive() && !__instance.GetCustomRole().NeedsUpdateAfterDeath() && !CustomGameMode.RoomRush.IsActiveOrIntegrated()) + if (!GameStates.IsLobby) { - int buffer = Options.DeepLowLoad.GetBool() ? 30 : 10; - DeadBufferTime.TryAdd(id, buffer); - DeadBufferTime[id]--; - if (DeadBufferTime[id] > 0) return; - - DeadBufferTime[id] = buffer; - } - - if (Options.LowLoadMode.GetBool()) - { - BufferTime.TryAdd(id, Options.DeepLowLoad.GetBool() ? 30 : 10); - BufferTime[id]--; - } - - try - { - DoPostfix(__instance); - } - catch (Exception ex) - { - long now = TimeStamp; - - if (LastErrorTS != now) + if (player.Is(CustomRoles.Spurt) && !Mathf.Approximately(Main.AllPlayerSpeed[playerId], Spurt.StartingSpeed[playerId]) && !inTask && !GameStates.IsMeeting) // fix ludicrous bug { - Logger.Error($"Error for {__instance.GetNameWithRole()}:", "FixedUpdatePatch"); - ThrowException(ex); - LastErrorTS = now; + Main.AllPlayerSpeed[playerId] = Spurt.StartingSpeed[playerId]; + player.MarkDirtySettings(); } - Logger.Error($"Error for {__instance.GetNameWithRole()}: {ex}", "FixedUpdatePatch"); + 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 (localPlayer) + if (QuizMaster.On && inTask && !lowLoad && QuizMaster.AllSabotages.Any(IsActive)) + QuizMaster.Data.LastSabotage = QuizMaster.AllSabotages.FirstOrDefault(IsActive); + + 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 PlayerState s) && s.Role.IsEnable) s.Role.OnFixedUpdate(player); + + if (inTask && 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); + } + + bool checkPos = inTask && player != null && alive && !Pelican.IsEaten(playerId); + if (checkPos) Asthmatic.OnCheckPlayerPosition(player); + + foreach (PlayerState state in Main.PlayerStates.Values) + { + if (state.Role.IsEnable) + { + if (checkPos) state.Role.OnCheckPlayerPosition(player); + + state.Role.OnGlobalFixedUpdate(player, lowLoad); + } + } + + if (Main.PlayerStates.TryGetValue(playerId, out PlayerState playerState) && inTask && alive) + { + List subRoles = playerState.SubRoles; + if (subRoles.Contains(CustomRoles.Dynamo)) Dynamo.OnFixedUpdate(player); + + if (subRoles.Contains(CustomRoles.Spurt)) Spurt.OnFixedUpdate(player); + + if (!lowLoad) + { + 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); + + if (subRoles.Contains(CustomRoles.Clumsy)) Clumsy.OnFixedUpdate(player); + + if (subRoles.Contains(CustomRoles.Sonar)) Sonar.OnFixedUpdate(player); + + if (subRoles.Contains(CustomRoles.Sleep)) Sleep.CheckGlowNearby(player); + + if (subRoles.Contains(CustomRoles.Introvert)) Introvert.OnFixedUpdate(player); + + if (subRoles.Contains(CustomRoles.Allergic)) Allergic.OnFixedUpdate(player); + } + } + + long now = TimeStamp; + + if (!lowLoad && Options.UsePets.GetBool() && inTask && (!LastUpdate.TryGetValue(playerId, out long lastPetNotify) || lastPetNotify < now)) + { + if (Main.AbilityCD.TryGetValue(playerId, out (long StartTimeStamp, int TotalCooldown) timer)) + { + if (timer.StartTimeStamp + timer.TotalCooldown < now || !alive) player.RemoveAbilityCD(); + + if (!player.IsModClient() && timer.TotalCooldown - (now - timer.StartTimeStamp) <= 60) NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + + LastUpdate[playerId] = now; + } + } + + if (!lowLoad) Randomizer.OnFixedUpdateForPlayers(player); + + RoleBlockManager.OnFixedUpdate(player); } } - private static void DoPostfix(PlayerControl __instance) + if (!lowLoad) { - PlayerControl player = __instance; - byte playerId = player.PlayerId; - byte lpId = PlayerControl.LocalPlayer.PlayerId; - bool localPlayer = playerId == lpId; // Updates that are independent of the player are only executed for the local player. + long now = TimeStamp; - var lowLoad = false; + // Ability Use Gain every 5 seconds - if (Options.LowLoadMode.GetBool()) + if (inTask && alive && Main.PlayerStates.TryGetValue(playerId, out PlayerState state) && state.TaskState.RemainingTasksCount <= 0 && (!LastAddAbilityTime.TryGetValue(playerId, out var ts) || ts + 5 < now)) { - if (BufferTime[playerId] > 0) - lowLoad = true; - else - BufferTime[playerId] = Options.DeepLowLoad.GetBool() ? 30 : 10; + LastAddAbilityTime[playerId] = now; + + AddExtraAbilityUsesOnFinishedTasks(player); } - bool inTask = GameStates.IsInTask; - bool alive = player.IsAlive(); + if (inTask && alive && Options.LadderDeath.GetBool()) FallFromLadder.FixedUpdate(player); - try - { - if (__instance.AmOwner && inTask && ((Main.ChangedRole && localPlayer && AmongUsClient.Instance.AmHost) || (!__instance.Is(CustomRoleTypes.Impostor) && __instance.CanUseKillButton() && !__instance.Data.IsDead))) - { - List players = __instance.GetPlayersInAbilityRangeSorted(); - PlayerControl closest = players.Count == 0 ? null : players[0]; - HudManager.Instance.KillButton.SetTarget(closest); - } - } - catch { } + if (localPlayer && GameStates.IsInGame) LoversSuicide(); - if (localPlayer) + if (inTask && localPlayer && Options.DisableDevices.GetBool()) DisableDevice.FixedUpdate(); + + if (localPlayer && GameStates.IsInGame && Main.RefixCooldownDelay <= 0) { - Zoom.OnFixedUpdate(); - TextBoxTMPSetTextPatch.Update(); + 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 (!lowLoad) + if (!Main.DoBlockNameChange && AmongUsClient.Instance.AmHost) ApplySuffix(__instance); + } + + Transform RoleTextTransform = __instance.cosmetics.nameText.transform.Find("RoleText"); + var RoleText = RoleTextTransform.GetComponent(); + + if (RoleText != null && __instance != null && !lowLoad) + { + if (GameStates.IsLobby) { - NameNotifyManager.OnFixedUpdate(player); - TargetArrow.OnFixedUpdate(player); - LocateArrow.OnFixedUpdate(player); - - if (AmongUsClient.Instance.AmHost) + if (Main.PlayerVersion.TryGetValue(playerId, out PlayerVersion ver)) { - Camouflage.OnFixedUpdate(player); - AFKDetector.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 != "") - { - string hashedPuid = player.GetClient().GetHashedPuid(); - if (!BanManager.TempBanWhiteList.Contains(hashedPuid)) BanManager.TempBanWhiteList.Add(hashedPuid); - } - - if (!Main.AllPlayerControls.All(x => x.Data.PlayerLevel <= 1) && !LobbyPatch.IsGlitchedRoomCode()) - { - 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 (!GameStates.IsLobby) - { - if (player.Is(CustomRoles.Spurt) && !Mathf.Approximately(Main.AllPlayerSpeed[playerId], Spurt.StartingSpeed[playerId]) && !inTask && !GameStates.IsMeeting) // fix ludicrous bug - { - Main.AllPlayerSpeed[playerId] = Spurt.StartingSpeed[playerId]; - player.MarkDirtySettings(); - } - - 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 (localPlayer) - if (QuizMaster.On && inTask && !lowLoad && QuizMaster.AllSabotages.Any(IsActive)) - QuizMaster.Data.LastSabotage = QuizMaster.AllSabotages.FirstOrDefault(IsActive); - - 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 PlayerState s) && s.Role.IsEnable) s.Role.OnFixedUpdate(player); - - if (inTask && 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); - } - - bool checkPos = inTask && player != null && alive && !Pelican.IsEaten(playerId); - if (checkPos) Asthmatic.OnCheckPlayerPosition(player); - - foreach (PlayerState state in Main.PlayerStates.Values) - { - if (state.Role.IsEnable) - { - if (checkPos) state.Role.OnCheckPlayerPosition(player); - - state.Role.OnGlobalFixedUpdate(player, lowLoad); - } - } - - if (Main.PlayerStates.TryGetValue(playerId, out PlayerState playerState) && inTask && alive) - { - List subRoles = playerState.SubRoles; - if (subRoles.Contains(CustomRoles.Dynamo)) Dynamo.OnFixedUpdate(player); - - if (subRoles.Contains(CustomRoles.Spurt)) Spurt.OnFixedUpdate(player); - - if (!lowLoad) - { - 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); - - if (subRoles.Contains(CustomRoles.Clumsy)) Clumsy.OnFixedUpdate(player); - - if (subRoles.Contains(CustomRoles.Sonar)) Sonar.OnFixedUpdate(player); - - if (subRoles.Contains(CustomRoles.Sleep)) Sleep.CheckGlowNearby(player); - - if (subRoles.Contains(CustomRoles.Introvert)) Introvert.OnFixedUpdate(player); - - if (subRoles.Contains(CustomRoles.Allergic)) Allergic.OnFixedUpdate(player); - } - } - - long now = TimeStamp; - - if (!lowLoad && Options.UsePets.GetBool() && inTask && (!LastUpdate.TryGetValue(playerId, out long lastPetNotify) || lastPetNotify < now)) - { - if (Main.AbilityCD.TryGetValue(playerId, out (long StartTimeStamp, int TotalCooldown) timer)) - { - if (timer.StartTimeStamp + timer.TotalCooldown < now || !alive) player.RemoveAbilityCD(); - - if (!player.IsModClient() && timer.TotalCooldown - (now - timer.StartTimeStamp) <= 60) NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - - LastUpdate[playerId] = now; - } - } - - if (!lowLoad) Randomizer.OnFixedUpdateForPlayers(player); - - RoleBlockManager.OnFixedUpdate(player); - } - } - - if (!lowLoad) - { - long now = TimeStamp; - - // Ability Use Gain every 5 seconds - - if (inTask && alive && Main.PlayerStates.TryGetValue(playerId, out PlayerState state) && state.TaskState.RemainingTasksCount <= 0 && (!LastAddAbilityTime.TryGetValue(playerId, out var ts) || ts + 5 < now)) - { - LastAddAbilityTime[playerId] = now; - - AddExtraAbilityUsesOnFinishedTasks(player); - } - - if (inTask && alive && Options.LadderDeath.GetBool()) FallFromLadder.FixedUpdate(player); - - if (localPlayer && GameStates.IsInGame) LoversSuicide(); - - if (inTask && localPlayer && Options.DisableDevices.GetBool()) DisableDevice.FixedUpdate(); - - if (localPlayer && 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); - } - - Transform RoleTextTransform = __instance.cosmetics.nameText.transform.Find("RoleText"); - var RoleText = RoleTextTransform.GetComponent(); - - if (RoleText != null && __instance != null && !lowLoad) - { - if (GameStates.IsLobby) - { - if (Main.PlayerVersion.TryGetValue(playerId, out PlayerVersion ver)) - { - if (Main.ForkId != ver.forkId) - __instance.cosmetics.nameText.text = $"{ver.forkId}\n{__instance?.name}"; - else if (Main.Version.CompareTo(ver.version) == 0) - __instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"{__instance.name}" : $"{ver.tag}\n{__instance?.name}"; - else - __instance.cosmetics.nameText.text = $"v{ver.version}\n{__instance?.name}"; - } + if (Main.ForkId != ver.forkId) + __instance.cosmetics.nameText.text = $"{ver.forkId}\n{__instance?.name}"; + else if (Main.Version.CompareTo(ver.version) == 0) + __instance.cosmetics.nameText.text = ver.tag == $"{ThisAssembly.Git.Commit}({ThisAssembly.Git.Branch})" ? $"{__instance.name}" : $"{ver.tag}\n{__instance?.name}"; else - __instance.cosmetics.nameText.text = Main.ShowPlayerInfoInLobby.Value && !__instance.AmOwner ? $"<#888888>{__instance.GetClient().PlatformData.Platform} | {__instance.FriendCode} | {__instance.GetClient().GetHashedPuid()}\n{__instance?.Data?.PlayerName}" : __instance?.Data?.PlayerName; + __instance.cosmetics.nameText.text = $"v{ver.version}\n{__instance?.name}"; + } + else + __instance.cosmetics.nameText.text = Main.ShowPlayerInfoInLobby.Value && !__instance.AmOwner ? $"<#888888>{__instance.GetClient().PlatformData.Platform} | {__instance.FriendCode} | {__instance.GetClient().GetHashedPuid()}\n{__instance?.Data?.PlayerName}" : __instance?.Data?.PlayerName; + } + + if (GameStates.IsInGame) + { + if (!AmongUsClient.Instance.AmHost && !CustomGameMode.Standard.IsActiveOrIntegrated()) + { + RoleText.text = string.Empty; + RoleText.enabled = false; + return; } - if (GameStates.IsInGame) + bool shouldSeeTargetAddons = playerId == lpId || new[] { PlayerControl.LocalPlayer, player }.All(x => x.Is(Team.Impostor)); + + (string, Color) RoleTextData = GetRoleText(lpId, playerId, seeTargetBetrayalAddons: shouldSeeTargetAddons); + + RoleText.text = RoleTextData.Item1; + RoleText.color = RoleTextData.Item2; + + if (Options.CurrentGameMode is not CustomGameMode.Standard and not CustomGameMode.HideAndSeek) RoleText.text = string.Empty; + + RoleText.enabled = IsRoleTextEnabled(__instance); + + if (!PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) && __instance.Is(CustomRoles.Trickster)) { - if (!AmongUsClient.Instance.AmHost && !CustomGameMode.Standard.IsActiveOrIntegrated()) - { - RoleText.text = string.Empty; - RoleText.enabled = false; - return; - } + RoleText.text = Farseer.RandomRole[lpId]; + RoleText.text += Farseer.GetTaskState(); + } - bool shouldSeeTargetAddons = playerId == lpId || new[] { PlayerControl.LocalPlayer, player }.All(x => x.Is(Team.Impostor)); + if (!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) + { + RoleText.enabled = false; + if (!__instance.AmOwner) __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName; + } - (string, Color) RoleTextData = GetRoleText(lpId, playerId, seeTargetBetrayalAddons: shouldSeeTargetAddons); + var isProgressTextLong = false; + string progressText = GetProgressText(__instance); - RoleText.text = RoleTextData.Item1; - RoleText.color = RoleTextData.Item2; + if (progressText.RemoveHtmlTags().Length > 25 && Main.VisibleTasksCount) + { + isProgressTextLong = true; + progressText = $"\n{progressText}"; + } - if (Options.CurrentGameMode is not CustomGameMode.Standard and not CustomGameMode.HideAndSeek) RoleText.text = string.Empty; + if (Main.VisibleTasksCount) + { + if (CustomGameMode.MoveAndStop.IsActiveOrIntegrated()) RoleText.text = RoleText.text.Insert(0, progressText); + else RoleText.text += progressText; + } - RoleText.enabled = IsRoleTextEnabled(__instance); + PlayerControl seer = PlayerControl.LocalPlayer; + PlayerControl target = __instance; - if (!PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.IsRevealedPlayer(__instance) && __instance.Is(CustomRoles.Trickster)) - { - RoleText.text = Farseer.RandomRole[lpId]; - RoleText.text += Farseer.GetTaskState(); - } + if (target.Is(CustomRoles.Car)) + { + target.cosmetics.nameText.text = Car.Name; + RoleText.enabled = false; + return; + } - if (!AmongUsClient.Instance.IsGameStarted && AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay) - { - RoleText.enabled = false; - if (!__instance.AmOwner) __instance.cosmetics.nameText.text = __instance?.Data?.PlayerName; - } + bool self = seer.PlayerId == target.PlayerId; - var isProgressTextLong = false; - string progressText = GetProgressText(__instance); + Mark.Clear(); + Suffix.Clear(); - if (progressText.RemoveHtmlTags().Length > 25 && Main.VisibleTasksCount) - { - isProgressTextLong = true; - progressText = $"\n{progressText}"; - } + if (AntiBlackout.SkipTasks) Suffix.AppendLine(GetString("AntiBlackoutSkipTasks")); - if (Main.VisibleTasksCount) - { - if (CustomGameMode.MoveAndStop.IsActiveOrIntegrated()) RoleText.text = RoleText.text.Insert(0, progressText); - else RoleText.text += progressText; - } + string RealName = target.GetRealName(); - PlayerControl seer = PlayerControl.LocalPlayer; - PlayerControl target = __instance; + if (target.AmOwner && inTask) + { + 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"), Revolutionist.RevolutionistCountdown.GetValueOrDefault(seer.PlayerId, 10))); - if (target.Is(CustomRoles.Car)) - { - target.cosmetics.nameText.text = Car.Name; - RoleText.enabled = false; - return; - } - - bool self = seer.PlayerId == target.PlayerId; - - Mark.Clear(); - Suffix.Clear(); - - if (AntiBlackout.SkipTasks) Suffix.AppendLine(GetString("AntiBlackoutSkipTasks")); - - string RealName = target.GetRealName(); - - if (target.AmOwner && inTask) - { - 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"), Revolutionist.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; - } - - if (Deathpact.IsInActiveDeathpact(seer)) RealName = Deathpact.GetDeathpactString(seer); - - if (NameNotifyManager.GetNameNotify(target, out string name) && name.Length > 0) RealName = name; - } - - // Name Color Manager - RealName = RealName.ApplyNameColorData(seer, target, false); - - Main.PlayerStates.Values.Do(x => Suffix.Append(x.Role.GetSuffix(seer, target, meeting: GameStates.IsMeeting))); - - if (self) Suffix.Append(CustomTeamManager.GetSuffix(seer)); - - Suffix.Append(AFKDetector.GetSuffix(seer, target)); - - switch (target.GetCustomRole()) - { - case CustomRoles.Snitch when seer.IsImpostor() && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished: - Mark.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); - break; - case CustomRoles.Marshall when Marshall.CanSeeMarshall(seer) && target.GetTaskState().IsTaskFinished: - Mark.Append(ColorString(GetRoleColor(CustomRoles.Marshall), "★")); - break; - case CustomRoles.SuperStar when Options.EveryOneKnowSuperStar.GetBool(): - Mark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); - break; - case CustomRoles.PlagueDoctor: - Mark.Append(PlagueDoctor.GetMarkOthers(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($"●"); - break; - case CustomRoles.Arsonist: - if (seer.IsDousedPlayer(target)) - Mark.Append($"▲"); - else if ( - Arsonist.CurrentDousingTarget != byte.MaxValue && - Arsonist.CurrentDousingTarget == target.PlayerId - ) - Mark.Append($"△"); - - break; - case CustomRoles.Revolutionist: - if (seer.IsDrawPlayer(target)) - Mark.Append($"●"); - else if ( - Revolutionist.CurrentDrawTarget != byte.MaxValue && - Revolutionist.CurrentDrawTarget == target.PlayerId - ) - Mark.Append($"○"); - - break; - case CustomRoles.Farseer: - if (Revolutionist.CurrentDrawTarget != byte.MaxValue && - Revolutionist.CurrentDrawTarget == target.PlayerId) - Mark.Append($"○"); - - break; - case CustomRoles.Analyst: - if ((Main.PlayerStates[seer.PlayerId].Role as Analyst).CurrentTarget.ID == target.PlayerId) Mark.Append($"○"); - - break; - case CustomRoles.Samurai: - if ((Main.PlayerStates[seer.PlayerId].Role as Samurai).Target.Id == target.PlayerId) Mark.Append($"○"); - - break; - case CustomRoles.Puppeteer: - if (Puppeteer.PuppeteerList.ContainsValue(seer.PlayerId) && Puppeteer.PuppeteerList.ContainsKey(target.PlayerId)) Mark.Append($"◆"); - - break; - case CustomRoles.EvilTracker: - Mark.Append(EvilTracker.GetTargetMark(seer, target)); - break; - case CustomRoles.Scout: - Mark.Append(Scout.GetTargetMark(seer, target)); - break; - case CustomRoles.Monitor when inTask && self: - if (AntiAdminer.IsAdminWatch) Suffix.Append($"{GetString("AntiAdminerAD")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Admin)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - if (AntiAdminer.IsVitalWatch) Suffix.Append($"{GetString("AntiAdminerVI")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Vitals)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - if (AntiAdminer.IsDoorLogWatch) Suffix.Append($"{GetString("AntiAdminerDL")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.DoorLog)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - if (AntiAdminer.IsCameraWatch) Suffix.Append($"{GetString("AntiAdminerCA")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Camera)).Select(x => x.Key.ColoredPlayerName()).Join()})"); - break; - case CustomRoles.AntiAdminer when inTask && self: - 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; - } - - 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), "■")); - - Mark.Append(Medic.GetMark(seer, target)); - Mark.Append(Gaslighter.GetMark(seer, target)); - Mark.Append(Snitch.GetWarningArrow(seer, target)); - Mark.Append(Snitch.GetWarningMark(seer, target)); - Mark.Append(Deathpact.GetDeathpactMark(seer, target)); - - Main.LoversPlayers.ToArray().DoIf(x => x == null, x => Main.LoversPlayers.Remove(x)); - if (!Main.HasJustStarted) Main.LoversPlayers.DoIf(x => !x.Is(CustomRoles.Lovers), x => x.RpcSetCustomRole(CustomRoles.Lovers)); - - if (Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId)) - { - if (Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId)) - Mark.Append($" ♥"); - else if (!seer.IsAlive()) Mark.Append($" ♥"); - } - - if (self) - { - Suffix.Append(Bloodmoon.GetSuffix(seer)); - Suffix.Append(Haunter.GetSuffix(seer)); - if (seer.Is(CustomRoles.Asthmatic)) Suffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); - if (seer.Is(CustomRoles.Sonar)) Suffix.Append(Sonar.GetSuffix(seer, GameStates.IsMeeting)); - if (seer.Is(CustomRoles.Deadlined)) Suffix.Append(Deadlined.GetSuffix(seer)); - if (seer.Is(CustomRoles.Allergic)) Suffix.Append(Allergic.GetSelfSuffix(seer)); - } + if (Pelican.IsEaten(seer.PlayerId)) RealName = ColorString(GetRoleColor(CustomRoles.Pelican), GetString("EatenByPelican")); switch (Options.CurrentGameMode) { case CustomGameMode.SoloKombat: - Suffix.Append(SoloKombatManager.GetDisplayHealth(target)); - break; - case CustomGameMode.FFA: - Suffix.Append(FFAManager.GetPlayerArrow(seer, target)); - break; - case CustomGameMode.MoveAndStop when self: - Suffix.Append(MoveAndStop.GetSuffixText(seer)); - break; - case CustomGameMode.Speedrun when self: - Suffix.Append(SpeedrunManager.GetSuffixText(seer)); - break; - case CustomGameMode.HideAndSeek: - Suffix.Append(HnSManager.GetSuffixText(seer, target)); - break; - case CustomGameMode.CaptureTheFlag: - Suffix.Append(CTFManager.GetSuffixText(seer, target)); - break; - case CustomGameMode.RoomRush: - Suffix.Append(RoomRush.GetSuffix(seer)); - break; - case CustomGameMode.AllInOne: - if (alive) Suffix.Append(SoloKombatManager.GetDisplayHealth(target)); - if (self && alive) Suffix.Append("\n" + MoveAndStop.GetSuffixText(seer) + "\n"); - if (self && alive && !seer.Is(CustomRoles.Killer)) Suffix.Append(string.Format(GetString("DamoclesTimeLeft"), SpeedrunManager.Timers[seer.PlayerId]) + "\n"); - if (self) Suffix.Append(RoomRush.GetSuffix(seer).Replace("\n", " - ")); + SoloKombatManager.GetNameNotify(target, ref RealName); break; } - if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && !self && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA) Suffix.Append(GetString("DiedR1Warning")); + if (Deathpact.IsInActiveDeathpact(seer)) RealName = Deathpact.GetDeathpactString(seer); - // 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"); + if (NameNotifyManager.GetNameNotify(target, out string name) && name.Length > 0) RealName = name; + } - // Camouflage - if (Camouflage.IsCamouflage) RealName = $"{RealName} "; + // Name Color Manager + RealName = RealName.ApplyNameColorData(seer, target, false); - string DeathReason = seer.Data.IsDead && seer.KnowDeathReason(target) ? $"\n『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』" : string.Empty; + Main.PlayerStates.Values.Do(x => Suffix.Append(x.Role.GetSuffix(seer, target, meeting: GameStates.IsMeeting))); - string currentText = target.cosmetics.nameText.text; - var changeTo = $"{RealName}{DeathReason}{Mark}\r\n{Suffix}"; - bool needUpdate = currentText != changeTo; + if (self) Suffix.Append(CustomTeamManager.GetSuffix(seer)); - if (needUpdate) + Suffix.Append(AFKDetector.GetSuffix(seer, target)); + + switch (target.GetCustomRole()) + { + case CustomRoles.Snitch when seer.IsImpostor() && target.Is(CustomRoles.Madmate) && target.GetTaskState().IsTaskFinished: + Mark.Append(ColorString(GetRoleColor(CustomRoles.Impostor), "★")); + break; + case CustomRoles.Marshall when Marshall.CanSeeMarshall(seer) && target.GetTaskState().IsTaskFinished: + Mark.Append(ColorString(GetRoleColor(CustomRoles.Marshall), "★")); + break; + case CustomRoles.SuperStar when Options.EveryOneKnowSuperStar.GetBool(): + Mark.Append(ColorString(GetRoleColor(CustomRoles.SuperStar), "★")); + break; + case CustomRoles.PlagueDoctor: + Mark.Append(PlagueDoctor.GetMarkOthers(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($"●"); + break; + case CustomRoles.Arsonist: + if (seer.IsDousedPlayer(target)) + Mark.Append($"▲"); + else if ( + Arsonist.CurrentDousingTarget != byte.MaxValue && + Arsonist.CurrentDousingTarget == target.PlayerId + ) + Mark.Append($"△"); + + break; + case CustomRoles.Revolutionist: + if (seer.IsDrawPlayer(target)) + Mark.Append($"●"); + else if ( + Revolutionist.CurrentDrawTarget != byte.MaxValue && + Revolutionist.CurrentDrawTarget == target.PlayerId + ) + Mark.Append($"○"); + + break; + case CustomRoles.Farseer: + if (Revolutionist.CurrentDrawTarget != byte.MaxValue && + Revolutionist.CurrentDrawTarget == target.PlayerId) + Mark.Append($"○"); + + break; + case CustomRoles.Analyst: + if ((Main.PlayerStates[seer.PlayerId].Role as Analyst).CurrentTarget.ID == target.PlayerId) Mark.Append($"○"); + + break; + case CustomRoles.Samurai: + if ((Main.PlayerStates[seer.PlayerId].Role as Samurai).Target.Id == target.PlayerId) Mark.Append($"○"); + + break; + case CustomRoles.Puppeteer: + if (Puppeteer.PuppeteerList.ContainsValue(seer.PlayerId) && Puppeteer.PuppeteerList.ContainsKey(target.PlayerId)) Mark.Append($"◆"); + + break; + case CustomRoles.EvilTracker: + Mark.Append(EvilTracker.GetTargetMark(seer, target)); + break; + case CustomRoles.Scout: + Mark.Append(Scout.GetTargetMark(seer, target)); + break; + case CustomRoles.Monitor when inTask && self: + if (AntiAdminer.IsAdminWatch) Suffix.Append($"{GetString("AntiAdminerAD")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Admin)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + if (AntiAdminer.IsVitalWatch) Suffix.Append($"{GetString("AntiAdminerVI")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Vitals)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + if (AntiAdminer.IsDoorLogWatch) Suffix.Append($"{GetString("AntiAdminerDL")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.DoorLog)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + if (AntiAdminer.IsCameraWatch) Suffix.Append($"{GetString("AntiAdminerCA")} ({AntiAdminer.PlayersNearDevices.Where(x => x.Value.Contains(AntiAdminer.Device.Camera)).Select(x => x.Key.ColoredPlayerName()).Join()})"); + break; + case CustomRoles.AntiAdminer when inTask && self: + 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; + } + + 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), "■")); + + Mark.Append(Medic.GetMark(seer, target)); + Mark.Append(Gaslighter.GetMark(seer, target)); + Mark.Append(Snitch.GetWarningArrow(seer, target)); + Mark.Append(Snitch.GetWarningMark(seer, target)); + Mark.Append(Deathpact.GetDeathpactMark(seer, target)); + + Main.LoversPlayers.ToArray().DoIf(x => x == null, x => Main.LoversPlayers.Remove(x)); + if (!Main.HasJustStarted) Main.LoversPlayers.DoIf(x => !x.Is(CustomRoles.Lovers), x => x.RpcSetCustomRole(CustomRoles.Lovers)); + + if (Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId)) + { + if (Main.LoversPlayers.Exists(x => x.PlayerId == seer.PlayerId)) + Mark.Append($" ♥"); + else if (!seer.IsAlive()) Mark.Append($" ♥"); + } + + if (self) + { + Suffix.Append(Bloodmoon.GetSuffix(seer)); + Suffix.Append(Haunter.GetSuffix(seer)); + if (seer.Is(CustomRoles.Asthmatic)) Suffix.Append(Asthmatic.GetSuffixText(seer.PlayerId)); + if (seer.Is(CustomRoles.Sonar)) Suffix.Append(Sonar.GetSuffix(seer, GameStates.IsMeeting)); + if (seer.Is(CustomRoles.Deadlined)) Suffix.Append(Deadlined.GetSuffix(seer)); + if (seer.Is(CustomRoles.Allergic)) Suffix.Append(Allergic.GetSelfSuffix(seer)); + } + + switch (Options.CurrentGameMode) + { + case CustomGameMode.SoloKombat: + Suffix.Append(SoloKombatManager.GetDisplayHealth(target)); + break; + case CustomGameMode.FFA: + Suffix.Append(FFAManager.GetPlayerArrow(seer, target)); + break; + case CustomGameMode.MoveAndStop when self: + Suffix.Append(MoveAndStop.GetSuffixText(seer)); + break; + case CustomGameMode.Speedrun when self: + Suffix.Append(SpeedrunManager.GetSuffixText(seer)); + break; + case CustomGameMode.HideAndSeek: + Suffix.Append(HnSManager.GetSuffixText(seer, target)); + break; + case CustomGameMode.CaptureTheFlag: + Suffix.Append(CTFManager.GetSuffixText(seer, target)); + break; + case CustomGameMode.RoomRush: + Suffix.Append(RoomRush.GetSuffix(seer)); + break; + case CustomGameMode.AllInOne: + if (alive) Suffix.Append(SoloKombatManager.GetDisplayHealth(target)); + if (self && alive) Suffix.Append("\n" + MoveAndStop.GetSuffixText(seer) + "\n"); + if (self && alive && !seer.Is(CustomRoles.Killer)) Suffix.Append(string.Format(GetString("DamoclesTimeLeft"), SpeedrunManager.Timers[seer.PlayerId]) + "\n"); + if (self) Suffix.Append(RoomRush.GetSuffix(seer).Replace("\n", " - ")); + break; + } + + if (MeetingStates.FirstMeeting && Main.ShieldPlayer == target.FriendCode && !string.IsNullOrEmpty(target.FriendCode) && !self && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.SoloKombat or CustomGameMode.FFA) Suffix.Append(GetString("DiedR1Warning")); + + // 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 (Camouflage.IsCamouflage) RealName = $"{RealName} "; + + string DeathReason = seer.Data.IsDead && seer.KnowDeathReason(target) ? $"\n『{ColorString(GetRoleColor(CustomRoles.Doctor), GetVitalText(target.PlayerId))}』" : string.Empty; + + string currentText = target.cosmetics.nameText.text; + var changeTo = $"{RealName}{DeathReason}{Mark}\r\n{Suffix}"; + bool needUpdate = currentText != changeTo; + + if (needUpdate) + { + target.cosmetics.nameText.text = changeTo; + + var offset = 0.2f; + + if (NameNotifyManager.GetNameNotify(seer, out string notify) && notify.Contains('\n')) { - target.cosmetics.nameText.text = changeTo; - - var offset = 0.2f; - - if (NameNotifyManager.GetNameNotify(seer, out string notify) && notify.Contains('\n')) - { - int count = notify.Count(x => x == '\n'); - for (var i = 0; i < count; i++) offset += 0.15f; - } - - 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 (CustomGameMode.MoveAndStop.IsActiveOrIntegrated()) offset += 0.2f; - - RoleText.transform.SetLocalY(offset); + int count = notify.Count(x => x == '\n'); + for (var i = 0; i < count; i++) offset += 0.15f; } - } - else - { - // Restoring the position text coordinates to their initial values - RoleText.transform.SetLocalY(0.2f); - } - } - } - public static void AddExtraAbilityUsesOnFinishedTasks(PlayerControl player) - { - if (Main.HasJustStarted || !player.IsAlive()) return; + 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 (Main.PlayerStates[player.PlayerId].Role is SabotageMaster sm) - { - sm.UsedSkillCount -= SabotageMaster.AbilityChargesWhenFinishedTasks.GetFloat(); - sm.SendRPC(); + if (!seer.IsAlive()) offset += 0.1f; + + if (isProgressTextLong) offset += 0.3f; + + if (CustomGameMode.MoveAndStop.IsActiveOrIntegrated()) offset += 0.2f; + + RoleText.transform.SetLocalY(offset); + } } else { - float add = GetSettingNameAndValueForRole(player.GetCustomRole(), "AbilityChargesWhenFinishedTasks"); - - if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0) - { - if (player.Is(CustomRoles.Bloodlust)) add *= 5; - - player.RpcIncreaseAbilityUseLimitBy(add); - } + // Restoring the position text coordinates to their initial values + RoleText.transform.SetLocalY(0.2f); } } - - public static void LoversSuicide(byte deathId = 0x7f, bool exile = false, bool force = false, bool guess = false) - { - if (Lovers.LoverDieConsequence.GetValue() == 0 || Main.IsLoversDead || (!Main.LoversPlayers.Exists(player => player.Data.IsDead && player.PlayerId == deathId) && !force)) return; - - Main.IsLoversDead = true; - PlayerControl partnerPlayer = Main.LoversPlayers.First(player => player.PlayerId != deathId && !player.Data.IsDead); - - if (Lovers.LoverDieConsequence.GetValue() == 2) - { - partnerPlayer.MarkDirtySettings(); - PlayerGameOptionsSender.SetDirty(deathId); - return; - } - - if (Lovers.LoverSuicideTime.GetValue() != 0 && !exile && !guess) return; - - if (exile) - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, partnerPlayer.PlayerId); - else - partnerPlayer.Suicide(PlayerState.DeathReason.FollowingSuicide); - } } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))] - internal static class PlayerStartPatch + public static void AddExtraAbilityUsesOnFinishedTasks(PlayerControl player) { - public static void Postfix(PlayerControl __instance) + if (Main.HasJustStarted || !player.IsAlive()) return; + + if (Main.PlayerStates[player.PlayerId].Role is SabotageMaster sm) { - TextMeshPro roleText = Object.Instantiate(__instance.cosmetics.nameText, __instance.cosmetics.nameText.transform, true); - roleText.transform.localPosition = new(0f, 0.2f, 0f); - roleText.fontSize -= 0.9f; - roleText.text = "RoleText"; - roleText.gameObject.name = "RoleText"; - roleText.enabled = false; + sm.UsedSkillCount -= SabotageMaster.AbilityChargesWhenFinishedTasks.GetFloat(); + sm.SendRPC(); + } + else + { + float add = GetSettingNameAndValueForRole(player.GetCustomRole(), "AbilityChargesWhenFinishedTasks"); + + if (Math.Abs(add - float.MaxValue) > 0.5f && add > 0) + { + if (player.Is(CustomRoles.Bloodlust)) add *= 5; + + player.RpcIncreaseAbilityUseLimitBy(add); + } } } + public static void LoversSuicide(byte deathId = 0x7f, bool exile = false, bool force = false, bool guess = false) + { + if (Lovers.LoverDieConsequence.GetValue() == 0 || Main.IsLoversDead || (!Main.LoversPlayers.Exists(player => player.Data.IsDead && player.PlayerId == deathId) && !force)) return; + + Main.IsLoversDead = true; + PlayerControl partnerPlayer = Main.LoversPlayers.First(player => player.PlayerId != deathId && !player.Data.IsDead); + + if (Lovers.LoverDieConsequence.GetValue() == 2) + { + partnerPlayer.MarkDirtySettings(); + PlayerGameOptionsSender.SetDirty(deathId); + return; + } + + if (Lovers.LoverSuicideTime.GetValue() != 0 && !exile && !guess) return; + + if (exile) + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.FollowingSuicide, partnerPlayer.PlayerId); + else + partnerPlayer.Suicide(PlayerState.DeathReason.FollowingSuicide); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Start))] +internal static class PlayerStartPatch +{ + public static void Postfix(PlayerControl __instance) + { + TextMeshPro roleText = Object.Instantiate(__instance.cosmetics.nameText, __instance.cosmetics.nameText.transform, true); + roleText.transform.localPosition = new(0f, 0.2f, 0f); + roleText.fontSize -= 0.9f; + roleText.text = "RoleText"; + roleText.gameObject.name = "RoleText"; + roleText.enabled = false; + } +} + //[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))] //class SetColorPatch //{ @@ -1873,496 +1873,495 @@ namespace EHR // } //} - [HarmonyPatch(typeof(Vent), nameof(Vent.ExitVent))] - internal static class ExitVentPatch +[HarmonyPatch(typeof(Vent), nameof(Vent.ExitVent))] +internal static class ExitVentPatch +{ + public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc) { - public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc) - { - Logger.Info($" {pc.GetNameWithRole()}, Vent ID: {__instance.Id} ({__instance.name})", "ExitVent"); + Logger.Info($" {pc.GetNameWithRole()}, Vent ID: {__instance.Id} ({__instance.name})", "ExitVent"); - if (pc.IsLocalPlayer()) LateTask.New(() => HudManager.Instance.SetHudActive(pc, pc.Data.Role, true), 0.6f, log: false); + if (pc.IsLocalPlayer()) LateTask.New(() => HudManager.Instance.SetHudActive(pc, pc.Data.Role, true), 0.6f, log: false); - if (!AmongUsClient.Instance.AmHost) return; + if (!AmongUsClient.Instance.AmHost) return; - if (Main.KillTimers.ContainsKey(pc.PlayerId)) Main.KillTimers[pc.PlayerId] += 0.5f; + if (Main.KillTimers.ContainsKey(pc.PlayerId)) Main.KillTimers[pc.PlayerId] += 0.5f; - Drainer.OnAnyoneExitVent(pc); + Drainer.OnAnyoneExitVent(pc); - Main.PlayerStates[pc.PlayerId].Role.OnExitVent(pc, __instance); + Main.PlayerStates[pc.PlayerId].Role.OnExitVent(pc, __instance); - if (Options.WhackAMole.GetBool()) LateTask.New(() => pc.TPToRandomVent(), 0.5f, "Whack-A-Mole TP"); + if (Options.WhackAMole.GetBool()) LateTask.New(() => pc.TPToRandomVent(), 0.5f, "Whack-A-Mole TP"); - if (!pc.IsModClient() && pc.Is(CustomRoles.Haste)) pc.SetKillCooldown(Math.Max(Main.KillTimers[pc.PlayerId], 0.1f)); - } + if (!pc.IsModClient() && pc.Is(CustomRoles.Haste)) pc.SetKillCooldown(Math.Max(Main.KillTimers[pc.PlayerId], 0.1f)); } +} - [HarmonyPatch(typeof(Vent), nameof(Vent.EnterVent))] - internal static class EnterVentPatch +[HarmonyPatch(typeof(Vent), nameof(Vent.EnterVent))] +internal static class EnterVentPatch +{ + public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc) { - public static void Postfix(Vent __instance, [HarmonyArgument(0)] PlayerControl pc) + Logger.Info($" {pc.GetNameWithRole()}, Vent ID: {__instance.Id} ({__instance.name})", "EnterVent"); + + if (AmongUsClient.Instance.AmHost && !pc.CanUseVent() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek && !pc.Is(CustomRoles.Nimble) && !pc.Is(CustomRoles.Bloodlust)) { - Logger.Info($" {pc.GetNameWithRole()}, Vent ID: {__instance.Id} ({__instance.name})", "EnterVent"); - - if (AmongUsClient.Instance.AmHost && !pc.CanUseVent() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek && !pc.Is(CustomRoles.Nimble) && !pc.Is(CustomRoles.Bloodlust)) - { - pc.MyPhysics?.RpcBootFromVent(__instance.Id); - return; - } - - Drainer.OnAnyoneEnterVent(pc, __instance); - Analyst.OnAnyoneEnterVent(pc); - Crewmate.Sentry.OnAnyoneEnterVent(pc); - - switch (pc.GetCustomRole()) - { - case CustomRoles.Mayor when !Options.UsePets.GetBool() && Mayor.MayorUsedButtonCount.TryGetValue(pc.PlayerId, out int count2) && count2 < Mayor.MayorNumOfUseButton.GetInt(): - if (AmongUsClient.Instance.AmHost) pc.MyPhysics?.RpcBootFromVent(__instance.Id); - - pc.ReportDeadBody(null); - break; - } - - if (!AmongUsClient.Instance.AmHost) return; - - Main.LastEnteredVent[pc.PlayerId] = __instance; - Main.LastEnteredVentLocation[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); - - Chef.SpitOutFood(pc); - - Main.PlayerStates[pc.PlayerId].Role.OnEnterVent(pc, __instance); + pc.MyPhysics?.RpcBootFromVent(__instance.Id); + return; } - } - [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.CoEnterVent))] - internal static class CoEnterVentPatch - { - public static bool Prefix(PlayerPhysics __instance, [HarmonyArgument(0)] int id) + Drainer.OnAnyoneEnterVent(pc, __instance); + Analyst.OnAnyoneEnterVent(pc); + Crewmate.Sentry.OnAnyoneEnterVent(pc); + + switch (pc.GetCustomRole()) { - if (!AmongUsClient.Instance.AmHost) return true; + case CustomRoles.Mayor when !Options.UsePets.GetBool() && Mayor.MayorUsedButtonCount.TryGetValue(pc.PlayerId, out int count2) && count2 < Mayor.MayorNumOfUseButton.GetInt(): + if (AmongUsClient.Instance.AmHost) pc.MyPhysics?.RpcBootFromVent(__instance.Id); - Logger.Info($" {__instance.myPlayer.GetNameWithRole()}, Vent ID: {id}", "CoEnterVent"); + pc.ReportDeadBody(null); + break; + } - if (Main.KillTimers.ContainsKey(__instance.myPlayer.PlayerId)) Main.KillTimers[__instance.myPlayer.PlayerId] += 0.5f; + if (!AmongUsClient.Instance.AmHost) return; - CheckInvalidMovementPatch.ExemptedPlayers.Add(__instance.myPlayer.PlayerId); + Main.LastEnteredVent[pc.PlayerId] = __instance; + Main.LastEnteredVentLocation[pc.PlayerId] = pc.Pos(); - switch (Options.CurrentGameMode) - { - case CustomGameMode.FFA when FFAManager.FFADisableVentingWhenTwoPlayersAlive.GetBool() && Main.AllAlivePlayerControls.Length <= 2: - LateTask.New(() => - { - __instance.myPlayer?.Notify(GetString("FFA-NoVentingBecauseTwoPlayers"), 7f); - __instance.RpcBootFromVent(id); - }, 0.5f, "FFA-NoVentingWhenTwoPlayersAlive"); + if (pc.Is(CustomRoles.Unlucky)) + { + var Ue = IRandom.Instance; + if (Ue.Next(0, 100) < Options.UnluckyVentSuicideChance.GetInt()) pc.Suicide(); + } - return true; - case CustomGameMode.FFA when FFAManager.FFADisableVentingWhenKcdIsUp.GetBool() && Main.KillTimers[__instance.myPlayer.PlayerId] <= 0: - LateTask.New(() => - { - __instance.myPlayer?.Notify(GetString("FFA-NoVentingBecauseKCDIsUP"), 7f); - __instance.RpcBootFromVent(id); - }, 0.5f, "FFA-NoVentingWhenKCDIsUP"); + if (pc.Is(CustomRoles.Damocles)) Damocles.OnEnterVent(pc.PlayerId, __instance.Id); - return true; - case CustomGameMode.AllInOne: - case CustomGameMode.MoveAndStop: - case CustomGameMode.HotPotato: - case CustomGameMode.Speedrun: - case CustomGameMode.CaptureTheFlag: - case CustomGameMode.NaturalDisasters: - LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, log: false); - return true; - case CustomGameMode.HideAndSeek: - HnSManager.OnCoEnterVent(__instance, id); - break; - case CustomGameMode.RoomRush: - return true; - } + Chef.SpitOutFood(pc); - if (__instance.myPlayer.IsRoleBlocked()) - { + Main.PlayerStates[pc.PlayerId].Role.OnEnterVent(pc, __instance); + } +} + +[HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.CoEnterVent))] +internal static 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.ContainsKey(__instance.myPlayer.PlayerId)) Main.KillTimers[__instance.myPlayer.PlayerId] += 0.5f; + + CheckInvalidMovementPatch.ExemptedPlayers.Add(__instance.myPlayer.PlayerId); + + switch (Options.CurrentGameMode) + { + case CustomGameMode.FFA when FFAManager.FFADisableVentingWhenTwoPlayersAlive.GetBool() && Main.AllAlivePlayerControls.Length <= 2: LateTask.New(() => { - __instance.myPlayer?.Notify(BlockedAction.Vent.GetBlockNotify()); + __instance.myPlayer?.Notify(GetString("FFA-NoVentingBecauseTwoPlayers"), 7f); __instance.RpcBootFromVent(id); - }, 0.5f, "RoleBlockedBootFromVent"); + }, 0.5f, "FFA-NoVentingWhenTwoPlayersAlive"); return true; - } + case CustomGameMode.FFA when FFAManager.FFADisableVentingWhenKcdIsUp.GetBool() && Main.KillTimers[__instance.myPlayer.PlayerId] <= 0: + LateTask.New(() => + { + __instance.myPlayer?.Notify(GetString("FFA-NoVentingBecauseKCDIsUP"), 7f); + __instance.RpcBootFromVent(id); + }, 0.5f, "FFA-NoVentingWhenKCDIsUP"); - if (!Rhapsode.CheckAbilityUse(__instance.myPlayer)) - { + return true; + case CustomGameMode.AllInOne: + case CustomGameMode.MoveAndStop: + case CustomGameMode.HotPotato: + case CustomGameMode.Speedrun: + case CustomGameMode.CaptureTheFlag: + case CustomGameMode.NaturalDisasters: LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, log: false); return true; - } - - if (Penguin.IsVictim(__instance.myPlayer)) - { - LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "PenguinVictimBootFromVent"); + case CustomGameMode.HideAndSeek: + HnSManager.OnCoEnterVent(__instance, id); + break; + case CustomGameMode.RoomRush: return true; - } + } - if (SoulHunter.IsSoulHunterTarget(__instance.myPlayer.PlayerId)) + if (__instance.myPlayer.IsRoleBlocked()) + { + LateTask.New(() => + { + __instance.myPlayer?.Notify(BlockedAction.Vent.GetBlockNotify()); + __instance.RpcBootFromVent(id); + }, 0.5f, "RoleBlockedBootFromVent"); + + return true; + } + + if (!Rhapsode.CheckAbilityUse(__instance.myPlayer)) + { + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, log: false); + return true; + } + + if (Penguin.IsVictim(__instance.myPlayer)) + { + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "PenguinVictimBootFromVent"); + return true; + } + + if (SoulHunter.IsSoulHunterTarget(__instance.myPlayer.PlayerId)) + { + LateTask.New(() => + { + __instance.myPlayer?.Notify(GetString("SoulHunterTargetNotifyNoVent")); + __instance.RpcBootFromVent(id); + }, 0.5f, "SoulHunterTargetBootFromVent"); + + return true; + } + + if (Ventguard.BlockedVents.Contains(id)) + { + PlayerControl pc = __instance.myPlayer; + + if (!Ventguard.VentguardBlockDoesNotAffectCrew.GetBool() || !pc.IsCrewmate()) { LateTask.New(() => { - __instance.myPlayer?.Notify(GetString("SoulHunterTargetNotifyNoVent")); + pc?.Notify(GetString("EnteredBlockedVent")); __instance.RpcBootFromVent(id); - }, 0.5f, "SoulHunterTargetBootFromVent"); + }, 0.5f, "VentguardBlockedVentBootFromVent"); + + if (Ventguard.VentguardNotifyOnBlockedVentUse.GetBool()) + foreach (PlayerControl ventguard in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Ventguard)).ToArray()) + ventguard.Notify(GetString("VentguardNotify")); return true; } + } - if (Ventguard.BlockedVents.Contains(id)) + if (__instance.myPlayer.Is(CustomRoles.Circumvent)) Circumvent.OnCoEnterVent(__instance, id); + + if (!__instance.myPlayer.CanUseVent() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek && !__instance.myPlayer.Is(CustomRoles.Nimble) && !__instance.myPlayer.Is(CustomRoles.Bloodlust)) LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "CannotUseVentBootFromVent"); + + if (((__instance.myPlayer.Data.Role.Role != RoleTypes.Engineer && !__instance.myPlayer.CanUseImpostorVentButton()) || + (__instance.myPlayer.Is(CustomRoles.Mayor) && Mayor.MayorUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out int count) && count >= Mayor.MayorNumOfUseButton.GetInt()) || + (__instance.myPlayer.Is(CustomRoles.Paranoia) && Paranoia.ParaUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out int count2) && count2 >= Options.ParanoiaNumOfUseButton.GetInt())) + && !__instance.myPlayer.Is(CustomRoles.Nimble) && !__instance.myPlayer.Is(CustomRoles.Bloodlust) && CustomGameMode.Standard.IsActiveOrIntegrated()) + { + try { - PlayerControl pc = __instance.myPlayer; - - if (!Ventguard.VentguardBlockDoesNotAffectCrew.GetBool() || !pc.IsCrewmate()) - { - LateTask.New(() => - { - pc?.Notify(GetString("EnteredBlockedVent")); - __instance.RpcBootFromVent(id); - }, 0.5f, "VentguardBlockedVentBootFromVent"); - - if (Ventguard.VentguardNotifyOnBlockedVentUse.GetBool()) - foreach (PlayerControl ventguard in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Ventguard)).ToArray()) - ventguard.Notify(GetString("VentguardNotify")); - - return true; - } - } - - if (__instance.myPlayer.Is(CustomRoles.Circumvent)) Circumvent.OnCoEnterVent(__instance, id); - - if (!__instance.myPlayer.CanUseVent() && Options.CurrentGameMode is CustomGameMode.Standard or CustomGameMode.HideAndSeek && !__instance.myPlayer.Is(CustomRoles.Nimble) && !__instance.myPlayer.Is(CustomRoles.Bloodlust)) LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "CannotUseVentBootFromVent"); - - if (((__instance.myPlayer.Data.Role.Role != RoleTypes.Engineer && !__instance.myPlayer.CanUseImpostorVentButton()) || - (__instance.myPlayer.Is(CustomRoles.Mayor) && Mayor.MayorUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out int count) && count >= Mayor.MayorNumOfUseButton.GetInt()) || - (__instance.myPlayer.Is(CustomRoles.Paranoia) && Paranoia.ParaUsedButtonCount.TryGetValue(__instance.myPlayer.PlayerId, out int count2) && count2 >= Options.ParanoiaNumOfUseButton.GetInt())) - && !__instance.myPlayer.Is(CustomRoles.Nimble) && !__instance.myPlayer.Is(CustomRoles.Bloodlust) && CustomGameMode.Standard.IsActiveOrIntegrated()) - { - try - { - LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "CannotUseVentBootFromVent2"); - return true; - } - catch { } - + LateTask.New(() => __instance.RpcBootFromVent(id), 0.5f, "CannotUseVentBootFromVent2"); return true; } - - Main.PlayerStates[__instance.myPlayer.PlayerId].Role.OnCoEnterVent(__instance, id); - - if (__instance.myPlayer.IsLocalPlayer()) - Statistics.VentTimes++; + catch { } return true; } + + Main.PlayerStates[__instance.myPlayer.PlayerId].Role.OnCoEnterVent(__instance, id); + + if (__instance.myPlayer.IsLocalPlayer()) + Statistics.VentTimes++; + + return true; + } +} + +[HarmonyPatch(typeof(GameData), nameof(GameData.CompleteTask))] +internal static class GameDataCompleteTaskPatch +{ + public static void Postfix(PlayerControl pc /*, uint taskId*/) + { + if (GameStates.IsMeeting) return; + + Logger.Info($"TaskComplete: {pc.GetNameWithRole().RemoveHtmlTags()}", "CompleteTask"); + Main.PlayerStates[pc.PlayerId].UpdateTask(pc); + NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] +internal static class PlayerControlCompleteTaskPatch +{ + public static bool Prefix(PlayerControl __instance) + { + if (GameStates.IsMeeting) return false; + + return !Workhorse.OnCompleteTask(__instance) && Capitalism.AddTaskForPlayer(__instance); // Cancel task win } - [HarmonyPatch(typeof(GameData), nameof(GameData.CompleteTask))] - internal static class GameDataCompleteTaskPatch + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] uint idx) { - public static void Postfix(PlayerControl pc /*, uint taskId*/) - { - if (GameStates.IsMeeting) return; + if (GameStates.IsMeeting) return; - Logger.Info($"TaskComplete: {pc.GetNameWithRole().RemoveHtmlTags()}", "CompleteTask"); - Main.PlayerStates[pc.PlayerId].UpdateTask(pc); - NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + var task = __instance.myTasks[(Index)Convert.ToInt32(idx)] as PlayerTask; + if (__instance != null && __instance.IsAlive()) Benefactor.OnTaskComplete(__instance, task); + + if (__instance == null) return; + + Snitch.OnCompleteTask(__instance); + + bool isTaskFinish = __instance.GetTaskState().IsTaskFinished; + + if (isTaskFinish && __instance.Is(CustomRoles.Snitch) && __instance.Is(CustomRoles.Madmate)) + { + foreach (PlayerControl impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoleTypes.Impostor)).ToArray()) NameColorManager.Add(impostor.PlayerId, __instance.PlayerId, "#ff1919"); + + NotifyRoles(SpecifySeer: __instance, ForceLoop: true); } + + if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated() && HnSManager.PlayerRoles[__instance.PlayerId].Interface.Team == Team.Crewmate && __instance.IsAlive()) Hider.OnSpecificTaskComplete(__instance, task); } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CompleteTask))] - internal static class PlayerControlCompleteTaskPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Die))] +public static class PlayerControlDiePatch +{ + public static void Postfix(PlayerControl __instance) { - public static bool Prefix(PlayerControl __instance) - { - if (GameStates.IsMeeting) return false; + if (AmongUsClient.Instance.AmHost) PetsPatch.RpcRemovePet(__instance); + } +} - return !Workhorse.OnCompleteTask(__instance) && Capitalism.AddTaskForPlayer(__instance); // Cancel task win +[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.IsImpostor() && Options.DisableZiplineForImps.GetBool()) return false; + + if (__instance.GetCustomRole().IsNeutral() && Options.DisableZiplineForNeutrals.GetBool()) return false; + + if (__instance.IsCrewmate() && Options.DisableZiplineForCrew.GetBool()) return false; } - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] uint idx) + return true; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ProtectPlayer))] +internal static 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))] +internal static class PlayerControlRemoveProtectionPatch +{ + public static void Postfix(PlayerControl __instance) + { + Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}", "RemoveProtection"); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] +internal static class PlayerControlSetRolePatch +{ + public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) + { + canOverrideRole = true; + + // Skip after first assign + if (StartGameHostPatch.RpcSetRoleReplacer.BlockSetRole) return true; + + string targetName = __instance.GetNameWithRole(); + Logger.Info($"{targetName} => {roleType}", "PlayerControl.RpcSetRole"); + if (!ShipStatus.Instance.enabled) return true; + + if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) { - if (GameStates.IsMeeting) return; + bool targetIsKiller = __instance.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(__instance.PlayerId); + Dictionary ghostRoles = new(); - var task = __instance.myTasks[(Index)Convert.ToInt32(idx)] as PlayerTask; - if (__instance != null && __instance.IsAlive()) Benefactor.OnTaskComplete(__instance, task); - - if (__instance == null) return; - - Snitch.OnCompleteTask(__instance); - - bool isTaskFinish = __instance.GetTaskState().IsTaskFinished; - - if (isTaskFinish && __instance.Is(CustomRoles.Snitch) && __instance.Is(CustomRoles.Madmate)) + foreach (PlayerControl seer in Main.AllPlayerControls) { - foreach (PlayerControl impostor in Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoleTypes.Impostor)).ToArray()) NameColorManager.Add(impostor.PlayerId, __instance.PlayerId, "#ff1919"); - - NotifyRoles(SpecifySeer: __instance, ForceLoop: true); - } - - if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated() && HnSManager.PlayerRoles[__instance.PlayerId].Interface.Team == Team.Crewmate && __instance.IsAlive()) Hider.OnSpecificTaskComplete(__instance, task); - } - } - - [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.IsImpostor() && Options.DisableZiplineForImps.GetBool()) return false; - - if (__instance.GetCustomRole().IsNeutral() && Options.DisableZiplineForNeutrals.GetBool()) return false; - - if (__instance.IsCrewmate() && Options.DisableZiplineForCrew.GetBool()) return false; - } - - return true; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ProtectPlayer))] - internal static 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))] - internal static class PlayerControlRemoveProtectionPatch - { - public static void Postfix(PlayerControl __instance) - { - Logger.Info($"{__instance.GetNameWithRole().RemoveHtmlTags()}", "RemoveProtection"); - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetRole))] - internal static class PlayerControlSetRolePatch - { - public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] ref RoleTypes roleType, [HarmonyArgument(1)] ref bool canOverrideRole) - { - canOverrideRole = true; - - // Skip after first assign - if (StartGameHostPatch.RpcSetRoleReplacer.BlockSetRole) return true; - - string targetName = __instance.GetNameWithRole(); - Logger.Info($"{targetName} => {roleType}", "PlayerControl.RpcSetRole"); - if (!ShipStatus.Instance.enabled) return true; - - if (roleType is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost) - { - bool targetIsKiller = __instance.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(__instance.PlayerId); - Dictionary ghostRoles = new(); - - foreach (PlayerControl seer in Main.AllPlayerControls) - { - bool self = seer.PlayerId == __instance.PlayerId; - bool seerIsKiller = seer.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(seer.PlayerId); - - if (__instance.HasGhostRole()) - ghostRoles[seer] = RoleTypes.GuardianAngel; - else if ((self && targetIsKiller) || (!seerIsKiller && __instance.Is(CustomRoleTypes.Impostor))) - ghostRoles[seer] = RoleTypes.ImpostorGhost; - else - ghostRoles[seer] = RoleTypes.CrewmateGhost; - } + bool self = seer.PlayerId == __instance.PlayerId; + bool seerIsKiller = seer.Is(CustomRoleTypes.Impostor) || Main.ResetCamPlayerList.Contains(seer.PlayerId); if (__instance.HasGhostRole()) - 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; + ghostRoles[seer] = RoleTypes.GuardianAngel; + else if ((self && targetIsKiller) || (!seerIsKiller && __instance.Is(CustomRoleTypes.Impostor))) + ghostRoles[seer] = RoleTypes.ImpostorGhost; else - { - foreach ((PlayerControl seer, RoleTypes role) in ghostRoles) - { - Logger.Info($"Desync {targetName} => {role} for {seer.GetNameWithRole().RemoveHtmlTags()}", "PlayerControl.RpcSetRole"); - __instance.RpcSetRoleDesync(role, seer.GetClientId()); - } + ghostRoles[seer] = RoleTypes.CrewmateGhost; + } - return false; + if (__instance.HasGhostRole()) + 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"); + __instance.RpcSetRoleDesync(role, seer.GetClientId()); } - } - return true; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoSetRole))] - internal static class PlayerControlLocalSetRolePatch - { - public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role) - { - if (!AmongUsClient.Instance.AmHost && !GameStates.IsModHost) - { - CustomRoles moddedRole = role switch - { - RoleTypes.Impostor => CustomRoles.ImpostorEHR, - RoleTypes.Phantom => CustomRoles.PhantomEHR, - RoleTypes.Shapeshifter => CustomRoles.ShapeshifterEHR, - RoleTypes.Crewmate => CustomRoles.CrewmateEHR, - RoleTypes.Engineer => CustomRoles.EngineerEHR, - RoleTypes.Noisemaker => CustomRoles.NoisemakerEHR, - RoleTypes.Scientist => CustomRoles.ScientistEHR, - RoleTypes.Tracker => CustomRoles.TrackerEHR, - _ => CustomRoles.NotAssigned - }; - - if (moddedRole != CustomRoles.NotAssigned) Main.PlayerStates[__instance.PlayerId].SetMainRole(moddedRole); - } - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckVanish))] - internal static class CmdCheckVanishPatch - { - public static bool Prefix(PlayerControl __instance, float maxDuration) - { - if (AmongUsClient.Instance.AmHost) - { - __instance.CheckVanish(); return false; } - - __instance.SetRoleInvisibility(true); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckVanish, SendOption.Reliable, AmongUsClient.Instance.HostId); - messageWriter.Write(maxDuration); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - - return false; } - } - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckAppear))] - internal static class CmdCheckAppearPatch + return true; + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoSetRole))] +internal static class PlayerControlLocalSetRolePatch +{ + public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role) { - public static bool Prefix(PlayerControl __instance, bool shouldAnimate) + if (!AmongUsClient.Instance.AmHost && !GameStates.IsModHost) { - if (AmongUsClient.Instance.AmHost) + CustomRoles moddedRole = role switch { - __instance.CheckAppear(shouldAnimate); - return false; - } + RoleTypes.Impostor => CustomRoles.ImpostorEHR, + RoleTypes.Phantom => CustomRoles.PhantomEHR, + RoleTypes.Shapeshifter => CustomRoles.ShapeshifterEHR, + RoleTypes.Crewmate => CustomRoles.CrewmateEHR, + RoleTypes.Engineer => CustomRoles.EngineerEHR, + RoleTypes.Noisemaker => CustomRoles.NoisemakerEHR, + RoleTypes.Scientist => CustomRoles.ScientistEHR, + RoleTypes.Tracker => CustomRoles.TrackerEHR, + _ => CustomRoles.NotAssigned + }; - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckAppear, SendOption.Reliable, AmongUsClient.Instance.HostId); - messageWriter.Write(shouldAnimate); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + if (moddedRole != CustomRoles.NotAssigned) Main.PlayerStates[__instance.PlayerId].SetMainRole(moddedRole); + } + } +} +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckVanish))] +internal static class CmdCheckVanishPatch +{ + public static bool Prefix(PlayerControl __instance, float maxDuration) + { + if (AmongUsClient.Instance.AmHost) + { + __instance.CheckVanish(); return false; } + + __instance.SetRoleInvisibility(true); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckVanish, SendOption.Reliable, AmongUsClient.Instance.HostId); + messageWriter.Write(maxDuration); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + return false; } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckVanish))] - internal static class CheckVanishPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckAppear))] +internal static class CmdCheckAppearPatch +{ + public static bool Prefix(PlayerControl __instance, bool shouldAnimate) { - public static bool Prefix(PlayerControl __instance) - { - Logger.Info($" {__instance.GetNameWithRole()}", "CheckVanish"); - bool allow = Main.PlayerStates[__instance.PlayerId].Role.OnVanish(__instance); - if (!allow) LateTask.New(__instance.RpcResetAbilityCooldown, 0.2f, log: false); - - return true; - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.AssertWithTimeout))] - internal static class AssertWithTimeoutPatch - { - public static bool Prefix() + if (AmongUsClient.Instance.AmHost) { + __instance.CheckAppear(shouldAnimate); return false; } + + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, (byte)RpcCalls.CheckAppear, SendOption.Reliable, AmongUsClient.Instance.HostId); + messageWriter.Write(shouldAnimate); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + + return false; } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckName))] - internal static class CmdCheckNameVersionCheckPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckVanish))] +internal static class CheckVanishPatch +{ + public static bool Prefix(PlayerControl __instance) { - public static void Postfix(PlayerControl __instance) - { - RPC.RpcVersionCheck(); - } + Logger.Info($" {__instance.GetNameWithRole()}", "CheckVanish"); + bool allow = Main.PlayerStates[__instance.PlayerId].Role.OnVanish(__instance); + if (!allow) LateTask.New(__instance.RpcResetAbilityCooldown, 0.2f, log: false); + + return true; } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MixUpOutfit))] - public static class PlayerControlMixupOutfitPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.AssertWithTimeout))] +internal static class AssertWithTimeoutPatch +{ + public static bool Prefix() { - public static void Postfix(PlayerControl __instance) - { - if (!__instance.IsAlive()) return; - - if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && // Has Impostor role behavior - !PlayerControl.LocalPlayer.Is(Team.Impostor) && // Not an actual Impostor - PlayerControl.LocalPlayer.GetCustomRole().GetDYRole() is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) // Has Desynced Impostor role - __instance.cosmetics.ToggleNameVisible(false); - } + return false; } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixMixedUpOutfit))] - public static class PlayerControlFixMixedUpOutfitPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckName))] +internal static class CmdCheckNameVersionCheckPatch +{ + public static void Postfix(PlayerControl __instance) { - public static void Postfix(PlayerControl __instance) - { - if (!__instance.IsAlive()) return; - - __instance.cosmetics.ToggleNameVisible(true); - } + RPC.RpcVersionCheck(); } +} - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ShouldProcessRpc))] - internal static class ShouldProcessRpcPatch +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MixUpOutfit))] +public static class PlayerControlMixupOutfitPatch +{ + public static void Postfix(PlayerControl __instance) { - // Since the stupid AU code added a check for RPC processing for outfit players, we need to patch this - // Always return true because the check is absolutely pointless - public static bool Prefix(PlayerControl __instance, RpcCalls rpc, byte sequenceId, ref bool __result) - { - __result = true; - return false; - } + if (!__instance.IsAlive()) return; + + if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && // Has Impostor role behavior + !PlayerControl.LocalPlayer.Is(Team.Impostor) && // Not an actual Impostor + PlayerControl.LocalPlayer.GetCustomRole().GetDYRole() is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) // Has Desynced Impostor role + __instance.cosmetics.ToggleNameVisible(false); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixMixedUpOutfit))] +public static class PlayerControlFixMixedUpOutfitPatch +{ + public static void Postfix(PlayerControl __instance) + { + if (!__instance.IsAlive()) return; + + __instance.cosmetics.ToggleNameVisible(true); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ShouldProcessRpc))] +internal static class ShouldProcessRpcPatch +{ + // Since the stupid AU code added a check for RPC processing for outfit players, we need to patch this + // Always return true because the check is absolutely pointless + public static bool Prefix(PlayerControl __instance, RpcCalls rpc, byte sequenceId, ref bool __result) + { + __result = true; + return false; } } \ No newline at end of file diff --git a/Patches/PlayerJoinAndLeftPatch.cs b/Patches/PlayerJoinAndLeftPatch.cs index 8ccfba959..ee82f648b 100644 --- a/Patches/PlayerJoinAndLeftPatch.cs +++ b/Patches/PlayerJoinAndLeftPatch.cs @@ -14,553 +14,552 @@ using InnerNet; using UnityEngine; using static EHR.Translator; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] +internal static class OnGameJoinedPatch { - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameJoined))] - internal static class OnGameJoinedPatch + public static bool JoiningGame; + + public static void Postfix(AmongUsClient __instance) { - public static bool JoiningGame; + JoiningGame = true; - public static void Postfix(AmongUsClient __instance) + while (!Options.IsLoaded) Task.Delay(1); + + Logger.Info($"{__instance.GameId} joined lobby", "OnGameJoined"); + + SetUpRoleTextPatch.IsInIntro = false; + + Main.PlayerVersion = []; + RPC.RpcVersionCheck(); + SoundManager.Instance?.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); + + ChatUpdatePatch.DoBlockChat = false; + GameStates.InGame = false; + ErrorText.Instance?.Clear(); + + LateTask.New(Achievements.ShowWaitingAchievements, 5f, log: false); + + if (AmongUsClient.Instance.AmHost) { - JoiningGame = true; + GameStartManagerPatch.GameStartManagerUpdatePatch.ExitTimer = -1; + Main.DoBlockNameChange = false; + Main.NewLobby = true; + EAC.DeNum = new(); + Main.AllPlayerNames = []; + Main.AllClientRealNames = []; - while (!Options.IsLoaded) Task.Delay(1); + if (Main.NormalOptions?.KillCooldown == 0f) Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; - Logger.Info($"{__instance.GameId} joined lobby", "OnGameJoined"); - - SetUpRoleTextPatch.IsInIntro = false; - - Main.PlayerVersion = []; - RPC.RpcVersionCheck(); - SoundManager.Instance?.ChangeAmbienceVolume(DataManager.Settings.Audio.AmbienceVolume); - - ChatUpdatePatch.DoBlockChat = false; - GameStates.InGame = false; - ErrorText.Instance?.Clear(); - - LateTask.New(Achievements.ShowWaitingAchievements, 5f, log: false); - - if (AmongUsClient.Instance.AmHost) - { - GameStartManagerPatch.GameStartManagerUpdatePatch.ExitTimer = -1; - Main.DoBlockNameChange = false; - Main.NewLobby = true; - EAC.DeNum = new(); - Main.AllPlayerNames = []; - Main.AllClientRealNames = []; - - if (Main.NormalOptions?.KillCooldown == 0f) Main.NormalOptions.KillCooldown = Main.LastKillCooldown.Value; - - AURoleOptions.SetOpt(Main.NormalOptions?.Cast()); - if (AURoleOptions.ShapeshifterCooldown == 0f) AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; - - LateTask.New(() => - { - if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid()) && GameStates.IsOnlineGame) - { - AmongUsClient.Instance.ExitGame(DisconnectReasons.Banned); - SceneChanger.ChangeScene("MainMenu"); - } - - ClientData client = PlayerControl.LocalPlayer.GetClient(); - Logger.Info($"{client.PlayerName.RemoveHtmlTags()} (ClientID: {client.Id} / FriendCode: {client.FriendCode} / HashPuid: {client.GetHashedPuid()} / Platform: {client.PlatformData.Platform}) Hosted room", "Session"); - }, 1f, "OnGameJoinedPatch"); - - Main.SetRoles = []; - Main.SetAddOns = []; - ChatCommands.DraftResult = []; - ChatCommands.DraftRoles = []; - - LateTask.New(() => - { - JoiningGame = false; - - if (GameStates.IsOnlineGame && GameStates.IsVanillaServer) - { - try - { - LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.In_Lobby); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - }, 5f, "NotifyLobbyCreated"); - } - } - } - - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))] - internal static class DisconnectInternalPatch - { - public static void Prefix( /*InnerNetClient __instance,*/ DisconnectReasons reason, string stringReason) - { - //ShowDisconnectPopupPatch.Reason = reason; - //ShowDisconnectPopupPatch.StringReason = stringReason; - ErrorText.Instance.CheatDetected = false; - ErrorText.Instance.SBDetected = false; - ErrorText.Instance.Clear(); - Cloud.StopConnect(); - } - } - - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] - internal static class OnPlayerJoinedPatch - { - public static bool IsDisconnected(this ClientData client) - { - var __instance = AmongUsClient.Instance; - - foreach (ClientData clientData in __instance.allClients) - if (clientData.Id == client.Id) - return false; - - return true; - } - - public static void Postfix( /*AmongUsClient __instance,*/ [HarmonyArgument(0)] ClientData client) - { - Logger.Info($"{client.PlayerName} (ClientID: {client.Id} / FriendCode: {client.FriendCode} / Hashed PUID: {client.GetHashedPuid()}) joined the lobby", "Session"); + AURoleOptions.SetOpt(Main.NormalOptions?.Cast()); + if (AURoleOptions.ShapeshifterCooldown == 0f) AURoleOptions.ShapeshifterCooldown = Main.LastShapeshifterCooldown.Value; LateTask.New(() => { - try + if (BanManager.CheckEACList(PlayerControl.LocalPlayer.FriendCode, PlayerControl.LocalPlayer.GetClient().GetHashedPuid()) && GameStates.IsOnlineGame) { - if (AmongUsClient.Instance.AmHost) + AmongUsClient.Instance.ExitGame(DisconnectReasons.Banned); + SceneChanger.ChangeScene("MainMenu"); + } + + ClientData client = PlayerControl.LocalPlayer.GetClient(); + Logger.Info($"{client.PlayerName.RemoveHtmlTags()} (ClientID: {client.Id} / FriendCode: {client.FriendCode} / HashPuid: {client.GetHashedPuid()} / Platform: {client.PlatformData.Platform}) Hosted room", "Session"); + }, 1f, "OnGameJoinedPatch"); + + Main.SetRoles = []; + Main.SetAddOns = []; + ChatCommands.DraftResult = []; + ChatCommands.DraftRoles = []; + + LateTask.New(() => + { + JoiningGame = false; + + if (GameStates.IsOnlineGame && GameStates.IsVanillaServer) + { + try { - if ((!client.IsDisconnected() && client.Character.Data.IsIncomplete) || ((client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length <= 15)) - { - Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}"); - AmongUsClient.Instance.KickPlayer(client.Id, false); - Logger.Info($"Kicked client {client.Id}/{client.PlayerName} since its PlayerControl was not spawned in time.", "OnPlayerJoinedPatchPostfix"); - return; - } - - if (!Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) - { - MessageWriter retry = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.None, client.Id); - AmongUsClient.Instance.FinishRpcImmediately(retry); - } - - if (client.Character != null && client.Character.Data != null && (client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length >= 18) - { - Disco.ChangeColor(client.Character); - } + LobbyNotifierForDiscord.NotifyLobbyStatusChanged(LobbyStatus.In_Lobby); + } + catch (Exception e) + { + Utils.ThrowException(e); } } - catch { } - }, 4.5f, "green bean kick late task", false); - - if (AmongUsClient.Instance.AmHost && client.FriendCode == "" && Options.KickPlayerFriendCodeNotExist.GetBool() && !GameStates.IsLocalGame) - { - if (!BanManager.TempBanWhiteList.Contains(client.GetHashedPuid())) BanManager.TempBanWhiteList.Add(client.GetHashedPuid()); - - AmongUsClient.Instance.KickPlayer(client.Id, true); - Logger.SendInGame(string.Format(GetString("Message.KickedByNoFriendCode"), client.PlayerName)); - Logger.Info($"TempBanned a player {client.PlayerName} without a friend code", "Temp Ban"); - } - - if (AmongUsClient.Instance.AmHost && client.PlatformData.Platform == (Platforms.Android | Platforms.IPhone) && Options.KickAndroidPlayer.GetBool()) - { - AmongUsClient.Instance.KickPlayer(client.Id, false); - string msg = string.Format(GetString("KickAndriodPlayer"), client.PlayerName); - Logger.SendInGame(msg); - Logger.Info(msg, "Android Kick"); - } - - if (AmongUsClient.Instance.AmHost && (client.PlayerName.EndsWith("cm", StringComparison.OrdinalIgnoreCase) || client.PlayerName.EndsWith("sm", StringComparison.OrdinalIgnoreCase)) && (client.PlayerName.Length == 4 || client.PlayerName.Count(x => x is 'i' or 'I') >= 2)) - { - AmongUsClient.Instance.KickPlayer(client.Id, false); - Logger.SendInGame("They were probably hacking tbh"); - } - - if (DestroyableSingleton.Instance.IsPlayerBlockedUsername(client.FriendCode) && AmongUsClient.Instance.AmHost) - { - AmongUsClient.Instance.KickPlayer(client.Id, true); - Logger.Info($"Blocked Player {client.PlayerName}({client.FriendCode}) has been banned.", "BAN"); - } - - BanManager.CheckBanPlayer(client); - RPC.RpcVersionCheck(); - - if (AmongUsClient.Instance.AmHost) - { - Main.SayStartTimes.Remove(client.Id); - Main.SayBanwordsTimes.Remove(client.Id); - } + }, 5f, "NotifyLobbyCreated"); } } +} - [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] - internal static class OnPlayerLeftPatch +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.DisconnectInternal))] +internal static class DisconnectInternalPatch +{ + public static void Prefix( /*InnerNetClient __instance,*/ DisconnectReasons reason, string stringReason) { - public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) + //ShowDisconnectPopupPatch.Reason = reason; + //ShowDisconnectPopupPatch.StringReason = stringReason; + ErrorText.Instance.CheatDetected = false; + ErrorText.Instance.SBDetected = false; + ErrorText.Instance.Clear(); + Cloud.StopConnect(); + } +} + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] +internal static class OnPlayerJoinedPatch +{ + public static bool IsDisconnected(this ClientData client) + { + var __instance = AmongUsClient.Instance; + + foreach (ClientData clientData in __instance.allClients) + if (clientData.Id == client.Id) + return false; + + return true; + } + + public static void Postfix( /*AmongUsClient __instance,*/ [HarmonyArgument(0)] ClientData client) + { + Logger.Info($"{client.PlayerName} (ClientID: {client.Id} / FriendCode: {client.FriendCode} / Hashed PUID: {client.GetHashedPuid()}) joined the lobby", "Session"); + + LateTask.New(() => { try { - if (data != null && data.Character != null) StartGameHostPatch.DataDisconnected[data.Character.PlayerId] = true; - - if (GameStates.IsInGame && data != null && data.Character != null) + if (AmongUsClient.Instance.AmHost) { - if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) HnSManager.PlayerRoles.Remove(data.Character.PlayerId); - - if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) + if ((!client.IsDisconnected() && client.Character.Data.IsIncomplete) || ((client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length <= 15)) { - foreach (PlayerControl lovers in Main.LoversPlayers) - { - Main.IsLoversDead = true; - Main.PlayerStates[lovers.PlayerId].RemoveSubRole(CustomRoles.Lovers); - } - - Main.LoversPlayers.RemoveAll(x => x.PlayerId == data.Character.PlayerId); + Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}"); + AmongUsClient.Instance.KickPlayer(client.Id, false); + Logger.Info($"Kicked client {client.Id}/{client.PlayerName} since its PlayerControl was not spawned in time.", "OnPlayerJoinedPatchPostfix"); + return; } - switch (data.Character.GetCustomRole()) + if (!Main.PlayerVersion.ContainsKey(client.Character.PlayerId)) { - case CustomRoles.Pelican: - Pelican.OnPelicanDied(data.Character.PlayerId); - break; - case CustomRoles.Markseeker: - Markseeker.OnDeath(data.Character); - break; - case CustomRoles.Jackal: - Jackal.Instances.Do(x => x.PromoteSidekick()); - break; + MessageWriter retry = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.None, client.Id); + AmongUsClient.Instance.FinishRpcImmediately(retry); } - if (Executioner.Target.ContainsValue(data.Character.PlayerId)) Executioner.ChangeRoleByTarget(data.Character); + if (client.Character != null && client.Character.Data != null && (client.Character.Data.DefaultOutfit.ColorId < 0 || Palette.PlayerColors.Length <= client.Character.Data.DefaultOutfit.ColorId) && Main.AllPlayerControls.Length >= 18) + { + Disco.ChangeColor(client.Character); + } + } + } + catch { } + }, 4.5f, "green bean kick late task", false); - if (Lawyer.Target.ContainsValue(data.Character.PlayerId)) Lawyer.ChangeRoleByTarget(data.Character); + if (AmongUsClient.Instance.AmHost && client.FriendCode == "" && Options.KickPlayerFriendCodeNotExist.GetBool() && !GameStates.IsLocalGame) + { + if (!BanManager.TempBanWhiteList.Contains(client.GetHashedPuid())) BanManager.TempBanWhiteList.Add(client.GetHashedPuid()); - if (Spiritualist.SpiritualistTarget == data.Character.PlayerId) Spiritualist.RemoveTarget(); + AmongUsClient.Instance.KickPlayer(client.Id, true); + Logger.SendInGame(string.Format(GetString("Message.KickedByNoFriendCode"), client.PlayerName)); + Logger.Info($"TempBanned a player {client.PlayerName} without a friend code", "Temp Ban"); + } - Postman.CheckAndResetTargets(data.Character); - GhostRolesManager.AssignedGhostRoles.Remove(data.Character.PlayerId); + if (AmongUsClient.Instance.AmHost && client.PlatformData.Platform == (Platforms.Android | Platforms.IPhone) && Options.KickAndroidPlayer.GetBool()) + { + AmongUsClient.Instance.KickPlayer(client.Id, false); + string msg = string.Format(GetString("KickAndriodPlayer"), client.PlayerName); + Logger.SendInGame(msg); + Logger.Info(msg, "Android Kick"); + } - PlayerState state = Main.PlayerStates[data.Character.PlayerId]; - if (state.deathReason == PlayerState.DeathReason.etc) state.deathReason = PlayerState.DeathReason.Disconnected; + if (AmongUsClient.Instance.AmHost && (client.PlayerName.EndsWith("cm", StringComparison.OrdinalIgnoreCase) || client.PlayerName.EndsWith("sm", StringComparison.OrdinalIgnoreCase)) && (client.PlayerName.Length == 4 || client.PlayerName.Count(x => x is 'i' or 'I') >= 2)) + { + AmongUsClient.Instance.KickPlayer(client.Id, false); + Logger.SendInGame("They were probably hacking tbh"); + } - if (!state.IsDead) state.SetDead(); + if (DestroyableSingleton.Instance.IsPlayerBlockedUsername(client.FriendCode) && AmongUsClient.Instance.AmHost) + { + AmongUsClient.Instance.KickPlayer(client.Id, true); + Logger.Info($"Blocked Player {client.PlayerName}({client.FriendCode}) has been banned.", "BAN"); + } - Utils.AfterPlayerDeathTasks(data.Character, GameStates.IsMeeting, true); + BanManager.CheckBanPlayer(client); + RPC.RpcVersionCheck(); - NameNotifyManager.Notifies.Remove(data.Character.PlayerId); - data.Character.RpcSetName(data.Character.GetRealName(true)); - AntiBlackout.OnDisconnect(data.Character.Data); - PlayerGameOptionsSender.RemoveSender(data.Character); + if (AmongUsClient.Instance.AmHost) + { + Main.SayStartTimes.Remove(client.Id); + Main.SayBanwordsTimes.Remove(client.Id); + } + } +} + +[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))] +internal static class OnPlayerLeftPatch +{ + public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData data, [HarmonyArgument(1)] DisconnectReasons reason) + { + try + { + if (data != null && data.Character != null) StartGameHostPatch.DataDisconnected[data.Character.PlayerId] = true; + + if (GameStates.IsInGame && data != null && data.Character != null) + { + if (CustomGameMode.HideAndSeek.IsActiveOrIntegrated()) HnSManager.PlayerRoles.Remove(data.Character.PlayerId); + + if (data.Character.Is(CustomRoles.Lovers) && !data.Character.Data.IsDead) + { + foreach (PlayerControl lovers in Main.LoversPlayers) + { + Main.IsLoversDead = true; + Main.PlayerStates[lovers.PlayerId].RemoveSubRole(CustomRoles.Lovers); + } + + Main.LoversPlayers.RemoveAll(x => x.PlayerId == data.Character.PlayerId); } - // if (Main.HostClientId == __instance.ClientId) - // { - // const int clientId = -1; - // var player = PlayerControl.LocalPlayer; - // var title = "" + GetString("DefaultSystemMessageTitle") + ""; - // var name = player?.Data?.PlayerName; - // var msg = string.Empty; - // if (GameStates.IsInGame) - // { - // Utils.ErrorEnd("Host Left the Game"); - // msg = GetString("Message.HostLeftGameInGame"); - // } - // else if (GameStates.IsLobby) - // msg = GetString("Message.HostLeftGameInLobby"); - // - // player?.SetName(title); - // DestroyableSingleton.Instance.Chat.AddChat(player, msg); - // player?.SetName(name); - // - // if (player != null && player.Data != null) - // { - // var writer = CustomRpcSender.Create("MessagesToSend"); - // writer.StartMessage(clientId); - // writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - // .Write(player.Data.NetId) - // .Write(title) - // .EndRpc(); - // writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) - // .Write(msg) - // .EndRpc(); - // writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) - // .Write(player.Data.NetId) - // .Write(player.Data.PlayerName) - // .EndRpc(); - // writer.EndMessage(); - // writer.SendMessage(); - // } - // } - - // Additional description of the reason for disconnection - switch (reason) + switch (data.Character.GetCustomRole()) { - case DisconnectReasons.Hacking: - Logger.SendInGame(string.Format(GetString("PlayerLeftByAU-Anticheat"), data?.PlayerName)); + case CustomRoles.Pelican: + Pelican.OnPelicanDied(data.Character.PlayerId); + break; + case CustomRoles.Markseeker: + Markseeker.OnDeath(data.Character); + break; + case CustomRoles.Jackal: + Jackal.Instances.Do(x => x.PromoteSidekick()); break; } - Logger.Info($"{data?.PlayerName} - (ClientID: {data?.Id} / FriendCode: {data?.FriendCode}) - Disconnected: {reason}, Ping: ({AmongUsClient.Instance.Ping})", "Session"); + if (Executioner.Target.ContainsValue(data.Character.PlayerId)) Executioner.ChangeRoleByTarget(data.Character); - if (AmongUsClient.Instance.AmHost) + if (Lawyer.Target.ContainsValue(data.Character.PlayerId)) Lawyer.ChangeRoleByTarget(data.Character); + + if (Spiritualist.SpiritualistTarget == data.Character.PlayerId) Spiritualist.RemoveTarget(); + + Postman.CheckAndResetTargets(data.Character); + GhostRolesManager.AssignedGhostRoles.Remove(data.Character.PlayerId); + + PlayerState state = Main.PlayerStates[data.Character.PlayerId]; + if (state.deathReason == PlayerState.DeathReason.etc) state.deathReason = PlayerState.DeathReason.Disconnected; + + if (!state.IsDead) state.SetDead(); + + Utils.AfterPlayerDeathTasks(data.Character, GameStates.IsMeeting, true); + + NameNotifyManager.Notifies.Remove(data.Character.PlayerId); + data.Character.RpcSetName(data.Character.GetRealName(true)); + AntiBlackout.OnDisconnect(data.Character.Data); + PlayerGameOptionsSender.RemoveSender(data.Character); + } + + // if (Main.HostClientId == __instance.ClientId) + // { + // const int clientId = -1; + // var player = PlayerControl.LocalPlayer; + // var title = "" + GetString("DefaultSystemMessageTitle") + ""; + // var name = player?.Data?.PlayerName; + // var msg = string.Empty; + // if (GameStates.IsInGame) + // { + // Utils.ErrorEnd("Host Left the Game"); + // msg = GetString("Message.HostLeftGameInGame"); + // } + // else if (GameStates.IsLobby) + // msg = GetString("Message.HostLeftGameInLobby"); + // + // player?.SetName(title); + // DestroyableSingleton.Instance.Chat.AddChat(player, msg); + // player?.SetName(name); + // + // if (player != null && player.Data != null) + // { + // var writer = CustomRpcSender.Create("MessagesToSend"); + // writer.StartMessage(clientId); + // writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + // .Write(player.Data.NetId) + // .Write(title) + // .EndRpc(); + // writer.StartRpc(player.NetId, (byte)RpcCalls.SendChat) + // .Write(msg) + // .EndRpc(); + // writer.StartRpc(player.NetId, (byte)RpcCalls.SetName) + // .Write(player.Data.NetId) + // .Write(player.Data.PlayerName) + // .EndRpc(); + // writer.EndMessage(); + // writer.SendMessage(); + // } + // } + + // Additional description of the reason for disconnection + switch (reason) + { + case DisconnectReasons.Hacking: + Logger.SendInGame(string.Format(GetString("PlayerLeftByAU-Anticheat"), data?.PlayerName)); + break; + } + + Logger.Info($"{data?.PlayerName} - (ClientID: {data?.Id} / FriendCode: {data?.FriendCode}) - Disconnected: {reason}, Ping: ({AmongUsClient.Instance.Ping})", "Session"); + + if (AmongUsClient.Instance.AmHost) + { + Main.SayStartTimes.Remove(__instance.ClientId); + Main.SayBanwordsTimes.Remove(__instance.ClientId); + Main.PlayerVersion.Remove(data?.Character?.PlayerId ?? byte.MaxValue); + Logger.Info($"{Main.MessagesToSend.RemoveAll(x => x.ReceiverID != byte.MaxValue && x.ReceiverID == data?.Character.PlayerId)} sending messages were canceled", "OnPlayerLeftPatchPostfix"); + + if (data != null && data.Character != null) { - Main.SayStartTimes.Remove(__instance.ClientId); - Main.SayBanwordsTimes.Remove(__instance.ClientId); - Main.PlayerVersion.Remove(data?.Character?.PlayerId ?? byte.MaxValue); - Logger.Info($"{Main.MessagesToSend.RemoveAll(x => x.ReceiverID != byte.MaxValue && x.ReceiverID == data?.Character.PlayerId)} sending messages were canceled", "OnPlayerLeftPatchPostfix"); + uint netid = data.Character.NetId; - if (data != null && data.Character != null) + LateTask.New(() => { - uint netid = data.Character.NetId; - - LateTask.New(() => + if (GameStates.IsOnlineGame) { - if (GameStates.IsOnlineGame) - { - MessageWriter messageWriter = AmongUsClient.Instance.Streams[1]; - messageWriter.StartMessage(5); - messageWriter.WritePacked(netid); - messageWriter.EndMessage(); - } - }, 2.5f, "Repeat Despawn", false); - } + MessageWriter messageWriter = AmongUsClient.Instance.Streams[1]; + messageWriter.StartMessage(5); + messageWriter.WritePacked(netid); + messageWriter.EndMessage(); + } + }, 2.5f, "Repeat Despawn", false); } + } - Utils.CountAlivePlayers(true); - } - catch (NullReferenceException) { } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "OnPlayerLeftPatch.Postfix"); - } - finally - { - Utils.NotifyRoles(NoCache: true); - ChatUpdatePatch.DoBlockChat = false; - } + Utils.CountAlivePlayers(true); + } + catch (NullReferenceException) { } + catch (Exception ex) + { + Logger.Error(ex.ToString(), "OnPlayerLeftPatch.Postfix"); + } + finally + { + Utils.NotifyRoles(NoCache: true); + ChatUpdatePatch.DoBlockChat = false; } } +} - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] - internal static class InnerNetClientSpawnPatch +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.Spawn))] +internal static class InnerNetClientSpawnPatch +{ + public static void Postfix([HarmonyArgument(1)] int ownerId, [HarmonyArgument(2)] SpawnFlags flags) { - public static void Postfix([HarmonyArgument(1)] int ownerId, [HarmonyArgument(2)] SpawnFlags flags) + if (!AmongUsClient.Instance.AmHost || flags != SpawnFlags.IsClientCharacter) return; + + ClientData client = Utils.GetClientById(ownerId); + + Logger.Msg($"Spawn player data: ID {client?.Character?.PlayerId}: {client?.PlayerName}", "CreatePlayer"); + + if (client == null || client.Character == null // client is null + || client.ColorId < 0 || Palette.PlayerColors.Length <= client.ColorId) // invalid client color + Logger.Warn("client is null or client have invalid color", "TrySyncAndSendMessage"); + else { - if (!AmongUsClient.Instance.AmHost || flags != SpawnFlags.IsClientCharacter) return; - - ClientData client = Utils.GetClientById(ownerId); - - Logger.Msg($"Spawn player data: ID {client?.Character?.PlayerId}: {client?.PlayerName}", "CreatePlayer"); - - if (client == null || client.Character == null // client is null - || client.ColorId < 0 || Palette.PlayerColors.Length <= client.ColorId) // invalid client color - Logger.Warn("client is null or client have invalid color", "TrySyncAndSendMessage"); - else - { - LateTask.New(() => { OptionItem.SyncAllOptions(client.Id); }, 3f, "Sync All Options For New Player"); - - LateTask.New(() => - { - if (Main.OverrideWelcomeMsg != "") - Utils.SendMessage(Main.OverrideWelcomeMsg, client.Character.PlayerId); - else - TemplateManager.SendTemplate("welcome", client.Character.PlayerId, true); - }, 3f, "Welcome Message"); - - LateTask.New(() => - { - if (client.Character == null) return; - - MessageWriter sender = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, client.Character.OwnerId); - AmongUsClient.Instance.FinishRpcImmediately(sender); - }, 3f, "RPC Request Retry Version Check"); - - if (GameStates.IsOnlineGame && !client.Character.IsHost()) - { - LateTask.New(() => - { - if (GameStates.IsLobby && client.Character != null && LobbyBehaviour.Instance != null && GameStates.IsVanillaServer) - { - // Only for vanilla - if (!client.Character.IsModClient()) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(LobbyBehaviour.Instance.NetId, (byte)RpcCalls.LobbyTimeExpiring, SendOption.None, client.Id); - writer.WritePacked((int)GameStartManagerPatch.Timer); - writer.Write(false); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - // Non-host modded client - else - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncLobbyTimer, SendOption.Reliable, client.Id); - writer.WritePacked((int)GameStartManagerPatch.Timer); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } - }, 3f, "Sync Lobby Timer RPC"); - } - } - - if (client != null && client.Character != null) Main.GuessNumber[client.Character.PlayerId] = [-1, 7]; - - if (Main.OverrideWelcomeMsg == string.Empty && Main.PlayerStates.Count > 0 && client != null && Main.ClientIdList.Contains(client.Id)) - { - if (Options.AutoDisplayKillLog.GetBool()) - { - LateTask.New(() => - { - if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) - { - Main.IsChatCommand = true; - Utils.ShowKillLog(client.Character.PlayerId); - } - }, 1f, "DisplayKillLog"); - } - - if (Options.AutoDisplayLastAddOns.GetBool()) - { - LateTask.New(() => - { - if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) - { - Main.IsChatCommand = true; - Utils.ShowLastAddOns(client.Character.PlayerId); - } - }, 1.1f, "DisplayLastAddOns"); - } - - if (Options.AutoDisplayLastRoles.GetBool()) - { - LateTask.New(() => - { - if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) - { - Main.IsChatCommand = true; - Utils.ShowLastRoles(client.Character.PlayerId); - } - }, 1.2f, "DisplayLastRoles"); - } - - if (Options.AutoDisplayLastResult.GetBool()) - { - LateTask.New(() => - { - if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) - { - Main.IsChatCommand = true; - Utils.ShowLastResult(client.Character.PlayerId); - } - }, 1.3f, "DisplayLastResult"); - } - - // if (PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp && Options.EnableUpMode.GetBool()) - // { - // LateTask.New(() => - // { - // if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) - // { - // Main.IsChatCommand = true; - // Utils.SendMessage($"{GetString("Message.YTPlanNotice")} {PlayerControl.LocalPlayer.FriendCode.GetDevUser().UpName}", client.Character.PlayerId); - // } - // }, 1.4f, "DisplayUpWarnning"); - // } - } - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckName))] - internal static class PlayerControlCheckNamePatch - { - public static void Postfix(PlayerControl __instance, ref string playerName) - { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsLobby) return; - - if (BanManager.CheckDenyNamePlayer(__instance, playerName)) return; - - if (Main.AllClientRealNames.TryAdd(__instance.OwnerId, playerName)) RPC.SyncAllClientRealNames(); - - string name = playerName; - - if (Options.FormatNameMode.GetInt() == 2 && __instance.Data.ClientId != AmongUsClient.Instance.ClientId) - name = Main.Get_TName_Snacks; - else - { - name = name.RemoveHtmlTags().Replace(@"\", string.Empty).Replace("/", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty); - if (name.Length > 10) name = name[..10]; - - if (Options.DisableEmojiName.GetBool()) name = Regex.Replace(name, @"\p{Cs}", string.Empty); - - if (Regex.Replace(Regex.Replace(name, @"\s", string.Empty), @"[\x01-\x1F,\x7F]", string.Empty).Length < 1) name = Main.Get_TName_Snacks; - } - - Main.AllPlayerNames[__instance.PlayerId] = name; - Logger.Info($"PlayerId: {__instance.PlayerId} - playerName: {playerName} - name: {name}", "Name player"); - RPC.SyncAllPlayerNames(); - - if (__instance != null && !name.Equals(playerName)) - { - Logger.Warn($"Standard nickname: {playerName} => {name}", "Name Format"); - playerName = name; - } + LateTask.New(() => { OptionItem.SyncAllOptions(client.Id); }, 3f, "Sync All Options For New Player"); LateTask.New(() => { - if (__instance != null && !__instance.Data.Disconnected && !__instance.IsModClient()) - { - MessageWriter sender = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, __instance.OwnerId); - AmongUsClient.Instance.FinishRpcImmediately(sender); - } - }, 0.6f, "Retry Version Check", false); - } - } + if (Main.OverrideWelcomeMsg != "") + Utils.SendMessage(Main.OverrideWelcomeMsg, client.Character.PlayerId); + else + TemplateManager.SendTemplate("welcome", client.Character.PlayerId, true); + }, 3f, "Welcome Message"); - [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))] - internal static class InnerNetClientFixedUpdatePatch - { - private static float Timer; + LateTask.New(() => + { + if (client.Character == null) return; - public static void Postfix() - { - if (GameStates.IsLocalGame || !Options.KickNotJoinedPlayersRegularly.GetBool() || Main.AllPlayerControls.Length < 7) return; + MessageWriter sender = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, client.Character.OwnerId); + AmongUsClient.Instance.FinishRpcImmediately(sender); + }, 3f, "RPC Request Retry Version Check"); - Timer += Time.fixedDeltaTime; - if (Timer < 25f) return; - - Timer = 0f; - - AmongUsClient.Instance.KickNotJoinedPlayers(); - } - } - - [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))] - internal static class RpcSetColorPatch - { - public static void Postfix(PlayerControl __instance, byte bodyColor) - { - if (Main.IntroDestroyed || __instance == null) return; - - string colorName = Palette.GetColorName(bodyColor); - Logger.Info($"{__instance.GetRealName()}'s color is {colorName}", "RpcSetColor"); - - if (colorName == "???") + if (GameStates.IsOnlineGame && !client.Character.IsHost()) { LateTask.New(() => { - if (__instance != null && !Main.PlayerColors.ContainsKey(__instance.PlayerId)) + if (GameStates.IsLobby && client.Character != null && LobbyBehaviour.Instance != null && GameStates.IsVanillaServer) { - var client = __instance.GetClient(); - - if (client != null) + // Only for vanilla + if (!client.Character.IsModClient()) { - Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}"); - AmongUsClient.Instance.KickPlayer(client.Id, false); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(LobbyBehaviour.Instance.NetId, (byte)RpcCalls.LobbyTimeExpiring, SendOption.None, client.Id); + writer.WritePacked((int)GameStartManagerPatch.Timer); + writer.Write(false); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + // Non-host modded client + else + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncLobbyTimer, SendOption.Reliable, client.Id); + writer.WritePacked((int)GameStartManagerPatch.Timer); + AmongUsClient.Instance.FinishRpcImmediately(writer); } } - }, 5f, "fortegreen bean color kick"); + }, 3f, "Sync Lobby Timer RPC"); + } + } + + if (client != null && client.Character != null) Main.GuessNumber[client.Character.PlayerId] = [-1, 7]; + + if (Main.OverrideWelcomeMsg == string.Empty && Main.PlayerStates.Count > 0 && client != null && Main.ClientIdList.Contains(client.Id)) + { + if (Options.AutoDisplayKillLog.GetBool()) + { + LateTask.New(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.IsChatCommand = true; + Utils.ShowKillLog(client.Character.PlayerId); + } + }, 1f, "DisplayKillLog"); } - if (bodyColor == 255) return; + if (Options.AutoDisplayLastAddOns.GetBool()) + { + LateTask.New(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.IsChatCommand = true; + Utils.ShowLastAddOns(client.Character.PlayerId); + } + }, 1.1f, "DisplayLastAddOns"); + } - Main.PlayerColors[__instance.PlayerId] = Palette.PlayerColors[bodyColor]; + if (Options.AutoDisplayLastRoles.GetBool()) + { + LateTask.New(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.IsChatCommand = true; + Utils.ShowLastRoles(client.Character.PlayerId); + } + }, 1.2f, "DisplayLastRoles"); + } + + if (Options.AutoDisplayLastResult.GetBool()) + { + LateTask.New(() => + { + if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + { + Main.IsChatCommand = true; + Utils.ShowLastResult(client.Character.PlayerId); + } + }, 1.3f, "DisplayLastResult"); + } + + // if (PlayerControl.LocalPlayer.FriendCode.GetDevUser().IsUp && Options.EnableUpMode.GetBool()) + // { + // LateTask.New(() => + // { + // if (!AmongUsClient.Instance.IsGameStarted && client.Character != null) + // { + // Main.IsChatCommand = true; + // Utils.SendMessage($"{GetString("Message.YTPlanNotice")} {PlayerControl.LocalPlayer.FriendCode.GetDevUser().UpName}", client.Character.PlayerId); + // } + // }, 1.4f, "DisplayUpWarnning"); + // } } } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckName))] +internal static class PlayerControlCheckNamePatch +{ + public static void Postfix(PlayerControl __instance, ref string playerName) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsLobby) return; + + if (BanManager.CheckDenyNamePlayer(__instance, playerName)) return; + + if (Main.AllClientRealNames.TryAdd(__instance.OwnerId, playerName)) RPC.SyncAllClientRealNames(); + + string name = playerName; + + if (Options.FormatNameMode.GetInt() == 2 && __instance.Data.ClientId != AmongUsClient.Instance.ClientId) + name = Main.Get_TName_Snacks; + else + { + name = name.RemoveHtmlTags().Replace(@"\", string.Empty).Replace("/", string.Empty).Replace("\n", string.Empty).Replace("\r", string.Empty).Replace("<", string.Empty).Replace(">", string.Empty); + if (name.Length > 10) name = name[..10]; + + if (Options.DisableEmojiName.GetBool()) name = Regex.Replace(name, @"\p{Cs}", string.Empty); + + if (Regex.Replace(Regex.Replace(name, @"\s", string.Empty), @"[\x01-\x1F,\x7F]", string.Empty).Length < 1) name = Main.Get_TName_Snacks; + } + + Main.AllPlayerNames[__instance.PlayerId] = name; + Logger.Info($"PlayerId: {__instance.PlayerId} - playerName: {playerName} - name: {name}", "Name player"); + RPC.SyncAllPlayerNames(); + + if (__instance != null && !name.Equals(playerName)) + { + Logger.Warn($"Standard nickname: {playerName} => {name}", "Name Format"); + playerName = name; + } + + LateTask.New(() => + { + if (__instance != null && !__instance.Data.Disconnected && !__instance.IsModClient()) + { + MessageWriter sender = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RequestRetryVersionCheck, SendOption.Reliable, __instance.OwnerId); + AmongUsClient.Instance.FinishRpcImmediately(sender); + } + }, 0.6f, "Retry Version Check", false); + } +} + +[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.FixedUpdate))] +internal static class InnerNetClientFixedUpdatePatch +{ + private static float Timer; + + public static void Postfix() + { + if (GameStates.IsLocalGame || !Options.KickNotJoinedPlayersRegularly.GetBool() || Main.AllPlayerControls.Length < 7) return; + + Timer += Time.fixedDeltaTime; + if (Timer < 25f) return; + + Timer = 0f; + + AmongUsClient.Instance.KickNotJoinedPlayers(); + } +} + +[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetColor))] +internal static class RpcSetColorPatch +{ + public static void Postfix(PlayerControl __instance, byte bodyColor) + { + if (Main.IntroDestroyed || __instance == null) return; + + string colorName = Palette.GetColorName(bodyColor); + Logger.Info($"{__instance.GetRealName()}'s color is {colorName}", "RpcSetColor"); + + if (colorName == "???") + { + LateTask.New(() => + { + if (__instance != null && !Main.PlayerColors.ContainsKey(__instance.PlayerId)) + { + var client = __instance.GetClient(); + + if (client != null) + { + Logger.SendInGame(GetString("Error.InvalidColor") + $" {client.Id}/{client.PlayerName}"); + AmongUsClient.Instance.KickPlayer(client.Id, false); + } + } + }, 5f, "fortegreen bean color kick"); + } + + if (bodyColor == 255) return; + + Main.PlayerColors[__instance.PlayerId] = Palette.PlayerColors[bodyColor]; + } } \ No newline at end of file diff --git a/Patches/RandomSpawnPatch.cs b/Patches/RandomSpawnPatch.cs index 9b7ae82c9..2e543a5e6 100644 --- a/Patches/RandomSpawnPatch.cs +++ b/Patches/RandomSpawnPatch.cs @@ -6,268 +6,267 @@ using HarmonyLib; using Hazel; using UnityEngine; -namespace EHR +namespace EHR; + +internal abstract class RandomSpawn { - internal abstract class RandomSpawn + // Thanks: https://github.com/tukasa0001/TownOfHost/blob/main/Patches/RandomSpawnPatch.cs + [HarmonyPatch(typeof(CustomNetworkTransform), nameof(CustomNetworkTransform.HandleRpc))] + public class CustomNetworkTransformHandleRpcPatch { - // Thanks: https://github.com/tukasa0001/TownOfHost/blob/main/Patches/RandomSpawnPatch.cs - [HarmonyPatch(typeof(CustomNetworkTransform), nameof(CustomNetworkTransform.HandleRpc))] - public class CustomNetworkTransformHandleRpcPatch + public static HashSet HasSpawned = []; + + private static readonly HashSet<(int x, int y)> DecupleVanillaSpawnPositions = + [ + (-7, 85), + (-7, -10), + (-70, -115), + (335, -15), + (200, 105), + (155, 0) + ]; + + public static bool Prefix(CustomNetworkTransform __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) { - public static HashSet HasSpawned = []; + if (!AmongUsClient.Instance.AmHost) return true; - private static readonly HashSet<(int x, int y)> DecupleVanillaSpawnPositions = - [ - (-7, 85), - (-7, -10), - (-70, -115), - (335, -15), - (200, 105), - (155, 0) - ]; + if (!__instance.isActiveAndEnabled) return false; - public static bool Prefix(CustomNetworkTransform __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader) + if ((RpcCalls)callId == RpcCalls.SnapTo && (MapNames)Main.NormalOptions.MapId == MapNames.Airship) { - if (!AmongUsClient.Instance.AmHost) return true; + PlayerControl player = __instance.myPlayer; - if (!__instance.isActiveAndEnabled) return false; - - if ((RpcCalls)callId == RpcCalls.SnapTo && (MapNames)Main.NormalOptions.MapId == MapNames.Airship) + if (!HasSpawned.Contains(player.PlayerId)) { - PlayerControl player = __instance.myPlayer; + Vector2 position; - if (!HasSpawned.Contains(player.PlayerId)) { - Vector2 position; + MessageReader newReader = MessageReader.Get(reader); + position = NetHelpers.ReadVector2(newReader); + newReader.Recycle(); + } - { - MessageReader newReader = MessageReader.Get(reader); - position = NetHelpers.ReadVector2(newReader); - newReader.Recycle(); - } + Logger.Info($"SnapTo: {player.GetRealName()}, ({position.x}, {position.y})", "RandomSpawn"); - Logger.Info($"SnapTo: {player.GetRealName()}, ({position.x}, {position.y})", "RandomSpawn"); - - if (IsAirshipVanillaSpawnPosition(position)) - { - AirshipSpawn(player); - return !Options.RandomSpawn.GetBool(); - } + if (IsAirshipVanillaSpawnPosition(position)) + { + AirshipSpawn(player); + return !Options.RandomSpawn.GetBool(); } } - - return true; } - private static bool IsAirshipVanillaSpawnPosition(Vector2 position) - { - float decupleXFloat = position.x * 10f; - float decupleYFloat = position.y * 10f; - int decupleXInt = Mathf.RoundToInt(decupleXFloat); - - if (Mathf.Abs(decupleXInt - decupleXFloat) >= 0.09f) return false; - - int decupleYInt = Mathf.RoundToInt(decupleYFloat); - if (Mathf.Abs(decupleYInt - decupleYFloat) >= 0.09f) return false; - - (int decupleXInt, int decupleYInt) decuplePosition = (decupleXInt, decupleYInt); - return DecupleVanillaSpawnPositions.Contains(decuplePosition); - } - - private static void AirshipSpawn(PlayerControl player) - { - Logger.Info($"Spawn: {player.GetRealName()}", "RandomSpawn"); - - if (AmongUsClient.Instance.AmHost) - { - if (player.Is(CustomRoles.Penguin)) Penguin.OnSpawnAirship(); - - player.RpcResetAbilityCooldown(); - if (Options.FixFirstKillCooldown.GetBool() && !MeetingStates.MeetingCalled) player.SetKillCooldown(Main.AllPlayerKillCooldown[player.PlayerId]); - - if (Options.RandomSpawn.GetBool() || player.Is(CustomRoles.GM)) new AirshipSpawnMap().RandomTeleport(player); - } - - HasSpawned.Add(player.PlayerId); - } + return true; } - public abstract class SpawnMap + private static bool IsAirshipVanillaSpawnPosition(Vector2 position) { - public abstract Dictionary Positions { get; } + float decupleXFloat = position.x * 10f; + float decupleYFloat = position.y * 10f; + int decupleXInt = Mathf.RoundToInt(decupleXFloat); - public virtual void RandomTeleport(PlayerControl player) - { - KeyValuePair spawn = GetLocation(); - Logger.Info($"{player.Data.PlayerName} => {Translator.GetString(spawn.Key.ToString())} {spawn.Value}", "RandomSpawn"); - player.TP(spawn.Value, true, false); - } + if (Mathf.Abs(decupleXInt - decupleXFloat) >= 0.09f) return false; - protected abstract KeyValuePair GetLocation(); + int decupleYInt = Mathf.RoundToInt(decupleYFloat); + if (Mathf.Abs(decupleYInt - decupleYFloat) >= 0.09f) return false; + + (int decupleXInt, int decupleYInt) decuplePosition = (decupleXInt, decupleYInt); + return DecupleVanillaSpawnPositions.Contains(decuplePosition); } - public class SkeldSpawnMap : SpawnMap + private static void AirshipSpawn(PlayerControl player) { - public readonly Dictionary positions = new() - { - [SystemTypes.Cafeteria] = new(-1.0f, 3.0f), - [SystemTypes.Weapons] = new(9.3f, 1.0f), - [SystemTypes.LifeSupp] = new(6.5f, -3.8f), - [SystemTypes.Nav] = new(16.5f, -4.8f), - [SystemTypes.Shields] = new(9.3f, -12.3f), - [SystemTypes.Comms] = new(4.0f, -15.5f), - [SystemTypes.Storage] = new(-1.5f, -15.5f), - [SystemTypes.Admin] = new(4.5f, -7.9f), - [SystemTypes.Electrical] = new(-7.5f, -8.8f), - [SystemTypes.LowerEngine] = new(-17.0f, -13.5f), - [SystemTypes.UpperEngine] = new(-17.0f, -1.3f), - [SystemTypes.Security] = new(-13.5f, -5.5f), - [SystemTypes.Reactor] = new(-20.5f, -5.5f), - [SystemTypes.MedBay] = new(-9.0f, -4.0f) - }; + Logger.Info($"Spawn: {player.GetRealName()}", "RandomSpawn"); - public override Dictionary Positions => positions; - - protected override KeyValuePair GetLocation() + if (AmongUsClient.Instance.AmHost) { - return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); + if (player.Is(CustomRoles.Penguin)) Penguin.OnSpawnAirship(); + + player.RpcResetAbilityCooldown(); + if (Options.FixFirstKillCooldown.GetBool() && !MeetingStates.MeetingCalled) player.SetKillCooldown(Main.AllPlayerKillCooldown[player.PlayerId]); + + if (Options.RandomSpawn.GetBool() || player.Is(CustomRoles.GM)) new AirshipSpawnMap().RandomTeleport(player); } + + HasSpawned.Add(player.PlayerId); + } + } + + public abstract class SpawnMap + { + public abstract Dictionary Positions { get; } + + public virtual void RandomTeleport(PlayerControl player) + { + KeyValuePair spawn = GetLocation(); + Logger.Info($"{player.Data.PlayerName} => {Translator.GetString(spawn.Key.ToString())} {spawn.Value}", "RandomSpawn"); + player.TP(spawn.Value, true, false); } - public class MiraHQSpawnMap : SpawnMap + protected abstract KeyValuePair GetLocation(); + } + + public class SkeldSpawnMap : SpawnMap + { + public readonly Dictionary positions = new() { - public readonly Dictionary positions = new() - { - [SystemTypes.Cafeteria] = new(25.5f, 2.0f), - [SystemTypes.Balcony] = new(24.0f, -2.0f), - [SystemTypes.Storage] = new(19.5f, 4.0f), - [SystemTypes.Hallway] = new(17.8f, 11.5f), - [SystemTypes.Comms] = new(15.3f, 3.8f), - [SystemTypes.MedBay] = new(15.5f, -0.5f), - [SystemTypes.LockerRoom] = new(9.0f, 1.0f), - [SystemTypes.Decontamination] = new(6.1f, 6.0f), - [SystemTypes.Laboratory] = new(9.5f, 12.0f), - [SystemTypes.Reactor] = new(2.5f, 10.5f), - [SystemTypes.Launchpad] = new(-4.5f, 2.0f), - [SystemTypes.Admin] = new(21.0f, 17.5f), - [SystemTypes.Office] = new(15.0f, 19.0f), - [SystemTypes.Greenhouse] = new(17.8f, 23.0f) - }; + [SystemTypes.Cafeteria] = new(-1.0f, 3.0f), + [SystemTypes.Weapons] = new(9.3f, 1.0f), + [SystemTypes.LifeSupp] = new(6.5f, -3.8f), + [SystemTypes.Nav] = new(16.5f, -4.8f), + [SystemTypes.Shields] = new(9.3f, -12.3f), + [SystemTypes.Comms] = new(4.0f, -15.5f), + [SystemTypes.Storage] = new(-1.5f, -15.5f), + [SystemTypes.Admin] = new(4.5f, -7.9f), + [SystemTypes.Electrical] = new(-7.5f, -8.8f), + [SystemTypes.LowerEngine] = new(-17.0f, -13.5f), + [SystemTypes.UpperEngine] = new(-17.0f, -1.3f), + [SystemTypes.Security] = new(-13.5f, -5.5f), + [SystemTypes.Reactor] = new(-20.5f, -5.5f), + [SystemTypes.MedBay] = new(-9.0f, -4.0f) + }; - public override Dictionary Positions => positions; + public override Dictionary Positions => positions; - protected override KeyValuePair GetLocation() - { - return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); - } + protected override KeyValuePair GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); } + } - public class PolusSpawnMap : SpawnMap + public class MiraHQSpawnMap : SpawnMap + { + public readonly Dictionary positions = new() { - public readonly Dictionary positions = new() - { - [SystemTypes.MeetingRoom] = new(19.5f, -18.0f), // Office - [SystemTypes.Office] = new(26.0f, -17.0f), - [SystemTypes.Admin] = new(24.0f, -22.5f), - [SystemTypes.Comms] = new(12.5f, -16.0f), - [SystemTypes.Weapons] = new(12.0f, -23.5f), - [SystemTypes.BoilerRoom] = new(2.3f, -24.0f), - [SystemTypes.LifeSupp] = new(2.0f, -17.5f), - [SystemTypes.Electrical] = new(9.5f, -12.5f), - [SystemTypes.Security] = new(3.0f, -12.0f), - [SystemTypes.Dropship] = new(16.7f, -3.0f), - [SystemTypes.Storage] = new(20.5f, -12.0f), - [SystemTypes.MedBay] = new(26.7f, -8.5f), // Drill - [SystemTypes.Laboratory] = new(36.5f, -7.5f), - [SystemTypes.Decontamination2] = new(34.0f, -10.0f), // Toilet (Laboratory) - [SystemTypes.Specimens] = new(36.5f, -22.0f) - }; + [SystemTypes.Cafeteria] = new(25.5f, 2.0f), + [SystemTypes.Balcony] = new(24.0f, -2.0f), + [SystemTypes.Storage] = new(19.5f, 4.0f), + [SystemTypes.Hallway] = new(17.8f, 11.5f), + [SystemTypes.Comms] = new(15.3f, 3.8f), + [SystemTypes.MedBay] = new(15.5f, -0.5f), + [SystemTypes.LockerRoom] = new(9.0f, 1.0f), + [SystemTypes.Decontamination] = new(6.1f, 6.0f), + [SystemTypes.Laboratory] = new(9.5f, 12.0f), + [SystemTypes.Reactor] = new(2.5f, 10.5f), + [SystemTypes.Launchpad] = new(-4.5f, 2.0f), + [SystemTypes.Admin] = new(21.0f, 17.5f), + [SystemTypes.Office] = new(15.0f, 19.0f), + [SystemTypes.Greenhouse] = new(17.8f, 23.0f) + }; - public override Dictionary Positions => positions; + public override Dictionary Positions => positions; - protected override KeyValuePair GetLocation() - { - return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); - } + protected override KeyValuePair GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); } + } - public class DleksSpawnMap : SpawnMap + public class PolusSpawnMap : SpawnMap + { + public readonly Dictionary positions = new() { - public readonly Dictionary positions = new SkeldSpawnMap().positions.ToDictionary(e => e.Key, e => new Vector2(-e.Value.x, e.Value.y)); + [SystemTypes.MeetingRoom] = new(19.5f, -18.0f), // Office + [SystemTypes.Office] = new(26.0f, -17.0f), + [SystemTypes.Admin] = new(24.0f, -22.5f), + [SystemTypes.Comms] = new(12.5f, -16.0f), + [SystemTypes.Weapons] = new(12.0f, -23.5f), + [SystemTypes.BoilerRoom] = new(2.3f, -24.0f), + [SystemTypes.LifeSupp] = new(2.0f, -17.5f), + [SystemTypes.Electrical] = new(9.5f, -12.5f), + [SystemTypes.Security] = new(3.0f, -12.0f), + [SystemTypes.Dropship] = new(16.7f, -3.0f), + [SystemTypes.Storage] = new(20.5f, -12.0f), + [SystemTypes.MedBay] = new(26.7f, -8.5f), // Drill + [SystemTypes.Laboratory] = new(36.5f, -7.5f), + [SystemTypes.Decontamination2] = new(34.0f, -10.0f), // Toilet (Laboratory) + [SystemTypes.Specimens] = new(36.5f, -22.0f) + }; - public override Dictionary Positions => positions; + public override Dictionary Positions => positions; - protected override KeyValuePair GetLocation() - { - return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); - } + protected override KeyValuePair GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); } + } - public class AirshipSpawnMap : SpawnMap + public class DleksSpawnMap : SpawnMap + { + public readonly Dictionary positions = new SkeldSpawnMap().positions.ToDictionary(e => e.Key, e => new Vector2(-e.Value.x, e.Value.y)); + + public override Dictionary Positions => positions; + + protected override KeyValuePair GetLocation() { - public readonly Dictionary positions = new() - { - [SystemTypes.Brig] = new(-0.7f, 8.5f), - [SystemTypes.Engine] = new(-0.7f, -1.0f), - [SystemTypes.Kitchen] = new(-7.0f, -11.5f), - [SystemTypes.CargoBay] = new(33.5f, -1.5f), - [SystemTypes.Records] = new(20.0f, 10.5f), - [SystemTypes.MainHall] = new(15.5f, 0.0f), - [SystemTypes.SleepingQuarters] = new(6.3f, 2.5f), - [SystemTypes.MeetingRoom] = new(17.1f, 14.9f), - [SystemTypes.GapRoom] = new(12.0f, 8.5f), - [SystemTypes.VaultRoom] = new(-8.9f, 12.2f), - [SystemTypes.Comms] = new(-13.3f, 1.3f), - [SystemTypes.Cockpit] = new(-23.5f, -1.6f), - [SystemTypes.Armory] = new(-10.3f, -5.9f), - [SystemTypes.ViewingDeck] = new(-13.7f, -12.6f), - [SystemTypes.Security] = new(5.8f, -10.8f), - [SystemTypes.Electrical] = new(16.3f, -8.8f), - [SystemTypes.Medical] = new(29.0f, -6.2f), - [SystemTypes.Lounge] = new(30.9f, 6.8f), - [SystemTypes.Showers] = new(21.2f, -0.8f) - }; - - public override Dictionary Positions => positions; - - protected override KeyValuePair GetLocation() - { - return Options.AirshipAdditionalSpawn.GetBool() - ? positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault() - : positions.ToArray()[..6].OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); - } + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); } + } - public class FungleSpawnMap : SpawnMap + public class AirshipSpawnMap : SpawnMap + { + public readonly Dictionary positions = new() { - public readonly Dictionary positions = new() - { - [SystemTypes.Outside] = new(-9.8f, 3.4f), // First Spawn - [SystemTypes.Dropship] = new(-7.8f, 10.6f), - [SystemTypes.Cafeteria] = new(-16.4f, 7.3f), - [SystemTypes.RecRoom] = new(-15.6f, -1.8f), // Splash Zone - [SystemTypes.Beach] = new(-22.8f, -0.6f), - [SystemTypes.Kitchen] = new(-15.5f, -7.5f), - [SystemTypes.FishingDock] = new(-23.1f, -7.0f), - [SystemTypes.Storage] = new(1.7f, 4.4f), - [SystemTypes.MeetingRoom] = new(-3.0f, -2.6f), - [SystemTypes.SleepingQuarters] = new(2.6f, -1.3f), // The Dorm - [SystemTypes.Laboratory] = new(-4.3f, -8.6f), - [SystemTypes.Jungle] = new(0.8f, -11.7f), - [SystemTypes.Greenhouse] = new(9.3f, -9.8f), - [SystemTypes.Reactor] = new(22.3f, -7.0f), - [SystemTypes.Lookout] = new(9.5f, 1.2f), - [SystemTypes.MiningPit] = new(12.6f, 9.8f), - [SystemTypes.UpperEngine] = new(22.4f, 3.4f), - [SystemTypes.Comms] = new(22.2f, 13.7f) - }; + [SystemTypes.Brig] = new(-0.7f, 8.5f), + [SystemTypes.Engine] = new(-0.7f, -1.0f), + [SystemTypes.Kitchen] = new(-7.0f, -11.5f), + [SystemTypes.CargoBay] = new(33.5f, -1.5f), + [SystemTypes.Records] = new(20.0f, 10.5f), + [SystemTypes.MainHall] = new(15.5f, 0.0f), + [SystemTypes.SleepingQuarters] = new(6.3f, 2.5f), + [SystemTypes.MeetingRoom] = new(17.1f, 14.9f), + [SystemTypes.GapRoom] = new(12.0f, 8.5f), + [SystemTypes.VaultRoom] = new(-8.9f, 12.2f), + [SystemTypes.Comms] = new(-13.3f, 1.3f), + [SystemTypes.Cockpit] = new(-23.5f, -1.6f), + [SystemTypes.Armory] = new(-10.3f, -5.9f), + [SystemTypes.ViewingDeck] = new(-13.7f, -12.6f), + [SystemTypes.Security] = new(5.8f, -10.8f), + [SystemTypes.Electrical] = new(16.3f, -8.8f), + [SystemTypes.Medical] = new(29.0f, -6.2f), + [SystemTypes.Lounge] = new(30.9f, 6.8f), + [SystemTypes.Showers] = new(21.2f, -0.8f) + }; - public override Dictionary Positions => positions; + public override Dictionary Positions => positions; - protected override KeyValuePair GetLocation() - { - return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); - } + protected override KeyValuePair GetLocation() + { + return Options.AirshipAdditionalSpawn.GetBool() + ? positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault() + : positions.ToArray()[..6].OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); + } + } + + public class FungleSpawnMap : SpawnMap + { + public readonly Dictionary positions = new() + { + [SystemTypes.Outside] = new(-9.8f, 3.4f), // First Spawn + [SystemTypes.Dropship] = new(-7.8f, 10.6f), + [SystemTypes.Cafeteria] = new(-16.4f, 7.3f), + [SystemTypes.RecRoom] = new(-15.6f, -1.8f), // Splash Zone + [SystemTypes.Beach] = new(-22.8f, -0.6f), + [SystemTypes.Kitchen] = new(-15.5f, -7.5f), + [SystemTypes.FishingDock] = new(-23.1f, -7.0f), + [SystemTypes.Storage] = new(1.7f, 4.4f), + [SystemTypes.MeetingRoom] = new(-3.0f, -2.6f), + [SystemTypes.SleepingQuarters] = new(2.6f, -1.3f), // The Dorm + [SystemTypes.Laboratory] = new(-4.3f, -8.6f), + [SystemTypes.Jungle] = new(0.8f, -11.7f), + [SystemTypes.Greenhouse] = new(9.3f, -9.8f), + [SystemTypes.Reactor] = new(22.3f, -7.0f), + [SystemTypes.Lookout] = new(9.5f, 1.2f), + [SystemTypes.MiningPit] = new(12.6f, 9.8f), + [SystemTypes.UpperEngine] = new(22.4f, 3.4f), + [SystemTypes.Comms] = new(22.2f, 13.7f) + }; + + public override Dictionary Positions => positions; + + protected override KeyValuePair GetLocation() + { + return positions.ToArray().OrderBy(_ => Guid.NewGuid()).Take(1).FirstOrDefault(); } } } \ No newline at end of file diff --git a/Patches/RecomputeTaskPatch.cs b/Patches/RecomputeTaskPatch.cs index 3fb2a35e8..0cc7c70df 100644 --- a/Patches/RecomputeTaskPatch.cs +++ b/Patches/RecomputeTaskPatch.cs @@ -1,32 +1,31 @@ using HarmonyLib; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(GameData), nameof(GameData.RecomputeTaskCounts))] +internal class CustomTaskCountsPatch { - [HarmonyPatch(typeof(GameData), nameof(GameData.RecomputeTaskCounts))] - internal class CustomTaskCountsPatch + public static bool Prefix(GameData __instance) { - public static bool Prefix(GameData __instance) + __instance.TotalTasks = 0; + __instance.CompletedTasks = 0; + + foreach (NetworkedPlayerInfo p in __instance.AllPlayers) { - __instance.TotalTasks = 0; - __instance.CompletedTasks = 0; + if (p == null) continue; - foreach (NetworkedPlayerInfo p in __instance.AllPlayers) + bool hasTasks = Utils.HasTasks(p) && Main.PlayerStates[p.PlayerId].TaskState.AllTasksCount > 0; + + if (hasTasks) { - if (p == null) continue; - - bool hasTasks = Utils.HasTasks(p) && Main.PlayerStates[p.PlayerId].TaskState.AllTasksCount > 0; - - if (hasTasks) + foreach (NetworkedPlayerInfo.TaskInfo task in p.Tasks) { - foreach (NetworkedPlayerInfo.TaskInfo task in p.Tasks) - { - __instance.TotalTasks++; - if (task.Complete) __instance.CompletedTasks++; - } + __instance.TotalTasks++; + if (task.Complete) __instance.CompletedTasks++; } } - - return false; } + + return false; } } \ No newline at end of file diff --git a/Patches/RegionMenuPatch.cs b/Patches/RegionMenuPatch.cs index 23daa73c7..234558df1 100644 --- a/Patches/RegionMenuPatch.cs +++ b/Patches/RegionMenuPatch.cs @@ -3,43 +3,41 @@ using System.Linq; using HarmonyLib; using UnityEngine; -namespace EHR.Patches +namespace EHR.Patches; +// https://github.com/0xDrMoe/TownofHost-Enhanced/blob/4ba167ac3009c54b14ee007534dc033227ded2a1/Patches/RegionMenuPatch.cs + +[HarmonyPatch(typeof(RegionMenu))] +public static class RegionMenuPatch { - // https://github.com/0xDrMoe/TownofHost-Enhanced/blob/4ba167ac3009c54b14ee007534dc033227ded2a1/Patches/RegionMenuPatch.cs - - [HarmonyPatch(typeof(RegionMenu))] - public static class RegionMenuPatch + [HarmonyPatch(nameof(RegionMenu.OnEnable))] + [HarmonyPostfix] + public static void AdjustButtonPositions_Postfix(RegionMenu __instance) { - [HarmonyPatch(nameof(RegionMenu.OnEnable))] - [HarmonyPostfix] - public static void AdjustButtonPositions_Postfix(RegionMenu __instance) + const int maxColumns = 4; + var buttonsPerColumn = 6; + const float buttonSpacing = 0.6f; + const float buttonSpacingSide = 2.25f; + + List buttons = __instance.controllerSelectable.ToArray().ToList(); + + int columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; + + while (columnCount > maxColumns) { - const int maxColumns = 4; - var buttonsPerColumn = 6; - const float buttonSpacing = 0.6f; - const float buttonSpacingSide = 2.25f; + buttonsPerColumn++; + columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; + } - List buttons = __instance.controllerSelectable.ToArray().ToList(); + float totalWidth = (columnCount - 1) * buttonSpacingSide; + float totalHeight = (buttonsPerColumn - 1) * buttonSpacing; - int columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; + Vector3 startPosition = new(-totalWidth / 2, totalHeight / 2, 0f); - while (columnCount > maxColumns) - { - buttonsPerColumn++; - columnCount = (buttons.Count + buttonsPerColumn - 1) / buttonsPerColumn; - } - - float totalWidth = (columnCount - 1) * buttonSpacingSide; - float totalHeight = (buttonsPerColumn - 1) * buttonSpacing; - - Vector3 startPosition = new(-totalWidth / 2, totalHeight / 2, 0f); - - for (var i = 0; i < buttons.Count; i++) - { - int col = i / buttonsPerColumn; - int row = i % buttonsPerColumn; - buttons[i].transform.localPosition = startPosition + new Vector3(col * buttonSpacingSide, -row * buttonSpacing, 0f); - } + for (var i = 0; i < buttons.Count; i++) + { + int col = i / buttonsPerColumn; + int row = i % buttonsPerColumn; + buttons[i].transform.localPosition = startPosition + new Vector3(col * buttonSpacingSide, -row * buttonSpacing, 0f); } } } \ No newline at end of file diff --git a/Patches/SabotageButtonPatch.cs b/Patches/SabotageButtonPatch.cs index 710c09674..4776d5b3b 100644 --- a/Patches/SabotageButtonPatch.cs +++ b/Patches/SabotageButtonPatch.cs @@ -1,23 +1,21 @@ using HarmonyLib; -namespace EHR.Patches +namespace EHR.Patches; +// https://github.com/tukasa0001/TownOfHost/blob/main/Patches/ActionButtonPatch.cs + +[HarmonyPatch(typeof(SabotageButton), nameof(SabotageButton.DoClick))] +public static class SabotageButtonDoClickPatch { - // https://github.com/tukasa0001/TownOfHost/blob/main/Patches/ActionButtonPatch.cs - - [HarmonyPatch(typeof(SabotageButton), nameof(SabotageButton.DoClick))] - public static class SabotageButtonDoClickPatch + public static bool Prefix() { - public static bool Prefix() + if (!PlayerControl.LocalPlayer.inVent && GameManager.Instance.SabotagesEnabled()) { - if (!PlayerControl.LocalPlayer.inVent && GameManager.Instance.SabotagesEnabled()) + DestroyableSingleton.Instance.ToggleMapVisible(new() { - DestroyableSingleton.Instance.ToggleMapVisible(new() - { - Mode = MapOptions.Modes.Sabotage - }); - } - - return false; + Mode = MapOptions.Modes.Sabotage + }); } + + return false; } } \ No newline at end of file diff --git a/Patches/SabotageSystemPatch.cs b/Patches/SabotageSystemPatch.cs index 5c64c6565..0059fb4c8 100644 --- a/Patches/SabotageSystemPatch.cs +++ b/Patches/SabotageSystemPatch.cs @@ -4,422 +4,420 @@ using EHR.Neutral; using HarmonyLib; using Hazel; -namespace EHR -{ - // Based on: +namespace EHR; +// Based on: // https://github.com/Koke1024/Town-Of-Moss/blob/main/TownOfMoss/Patches/MeltDownBoost.cs - [HarmonyPatch(typeof(ReactorSystemType), nameof(ReactorSystemType.Deteriorate))] - public static class ReactorSystemTypePatch +[HarmonyPatch(typeof(ReactorSystemType), nameof(ReactorSystemType.Deteriorate))] +public static class ReactorSystemTypePatch +{ + private static bool SetDurationForReactorSabotage = true; + + public static void Prefix(ReactorSystemType __instance) { - private static bool SetDurationForReactorSabotage = true; + if (!Options.SabotageTimeControl.GetBool()) return; - public static void Prefix(ReactorSystemType __instance) + if ((MapNames)Main.NormalOptions.MapId is MapNames.Airship) return; + + // When the sabotage ends + if (!__instance.IsActive || !SetDurationForReactorSabotage) { - if (!Options.SabotageTimeControl.GetBool()) return; + if (!SetDurationForReactorSabotage && !__instance.IsActive) SetDurationForReactorSabotage = true; - if ((MapNames)Main.NormalOptions.MapId is MapNames.Airship) return; + return; + } - // When the sabotage ends - if (!__instance.IsActive || !SetDurationForReactorSabotage) - { - if (!SetDurationForReactorSabotage && !__instance.IsActive) SetDurationForReactorSabotage = true; + Logger.Info($" {ShipStatus.Instance.Type}", "ReactorSystemTypePatch - ShipStatus.Instance.Type"); + Logger.Info($" {SetDurationForReactorSabotage}", "ReactorSystemTypePatch - SetDurationCriticalSabotage"); + SetDurationForReactorSabotage = false; + switch (ShipStatus.Instance.Type) + { + case ShipStatus.MapType.Ship: //The Skeld + __instance.Countdown = Options.SkeldReactorTimeLimit.GetFloat(); + return; + case ShipStatus.MapType.Hq: //Mira HQ + __instance.Countdown = Options.MiraReactorTimeLimit.GetFloat(); + return; + case ShipStatus.MapType.Pb: //Polus + __instance.Countdown = Options.PolusReactorTimeLimit.GetFloat(); + return; + case ShipStatus.MapType.Fungle: //The Fungle + __instance.Countdown = Options.FungleReactorTimeLimit.GetFloat(); + return; + default: return; - } - - Logger.Info($" {ShipStatus.Instance.Type}", "ReactorSystemTypePatch - ShipStatus.Instance.Type"); - Logger.Info($" {SetDurationForReactorSabotage}", "ReactorSystemTypePatch - SetDurationCriticalSabotage"); - SetDurationForReactorSabotage = false; - - switch (ShipStatus.Instance.Type) - { - case ShipStatus.MapType.Ship: //The Skeld - __instance.Countdown = Options.SkeldReactorTimeLimit.GetFloat(); - return; - case ShipStatus.MapType.Hq: //Mira HQ - __instance.Countdown = Options.MiraReactorTimeLimit.GetFloat(); - return; - case ShipStatus.MapType.Pb: //Polus - __instance.Countdown = Options.PolusReactorTimeLimit.GetFloat(); - return; - case ShipStatus.MapType.Fungle: //The Fungle - __instance.Countdown = Options.FungleReactorTimeLimit.GetFloat(); - return; - default: - return; - } } } +} - [HarmonyPatch(typeof(HeliSabotageSystem), nameof(HeliSabotageSystem.Deteriorate))] - public static class HeliSabotageSystemPatch +[HarmonyPatch(typeof(HeliSabotageSystem), nameof(HeliSabotageSystem.Deteriorate))] +public static class HeliSabotageSystemPatch +{ + private static bool SetDurationForReactorSabotage = true; + + public static void Prefix(HeliSabotageSystem __instance) { - private static bool SetDurationForReactorSabotage = true; + if (!Options.SabotageTimeControl.GetBool()) return; - public static void Prefix(HeliSabotageSystem __instance) + if ((MapNames)Main.NormalOptions.MapId is not MapNames.Airship) return; + + // When the sabotage ends + if (!__instance.IsActive || ShipStatus.Instance == null || !SetDurationForReactorSabotage) { - if (!Options.SabotageTimeControl.GetBool()) return; + if (!SetDurationForReactorSabotage && !__instance.IsActive) SetDurationForReactorSabotage = true; - if ((MapNames)Main.NormalOptions.MapId is not MapNames.Airship) return; + return; + } - // When the sabotage ends - if (!__instance.IsActive || ShipStatus.Instance == null || !SetDurationForReactorSabotage) - { - if (!SetDurationForReactorSabotage && !__instance.IsActive) SetDurationForReactorSabotage = true; + Logger.Info($" {ShipStatus.Instance.Type}", "HeliSabotageSystemPatch - ShipStatus.Instance.Type"); + Logger.Info($" {SetDurationForReactorSabotage}", "HeliSabotageSystemPatch - SetDurationCriticalSabotage"); + SetDurationForReactorSabotage = false; + __instance.Countdown = Options.AirshipReactorTimeLimit.GetFloat(); + } +} + +[HarmonyPatch(typeof(LifeSuppSystemType), nameof(LifeSuppSystemType.Deteriorate))] +public static class LifeSuppSystemTypePatch +{ + private static bool SetDurationForO2Sabotage = true; + + public static void Prefix(LifeSuppSystemType __instance) + { + if (!Options.SabotageTimeControl.GetBool()) return; + + if ((MapNames)Main.NormalOptions.MapId is MapNames.Polus or MapNames.Airship or MapNames.Fungle) return; + + // When the sabotage ends + if (!__instance.IsActive || !SetDurationForO2Sabotage) + { + if (!SetDurationForO2Sabotage && !__instance.IsActive) SetDurationForO2Sabotage = true; + + return; + } + + Logger.Info($" {ShipStatus.Instance.Type}", "LifeSuppSystemType - ShipStatus.Instance.Type"); + Logger.Info($" {SetDurationForO2Sabotage}", "LifeSuppSystemType - SetDurationCriticalSabotage"); + SetDurationForO2Sabotage = false; + + switch (ShipStatus.Instance.Type) + { + case ShipStatus.MapType.Ship: // The Skeld + __instance.Countdown = Options.SkeldO2TimeLimit.GetFloat(); + return; + case ShipStatus.MapType.Hq: // Mira HQ + __instance.Countdown = Options.MiraO2TimeLimit.GetFloat(); + return; + default: return; - } - - Logger.Info($" {ShipStatus.Instance.Type}", "HeliSabotageSystemPatch - ShipStatus.Instance.Type"); - Logger.Info($" {SetDurationForReactorSabotage}", "HeliSabotageSystemPatch - SetDurationCriticalSabotage"); - SetDurationForReactorSabotage = false; - - __instance.Countdown = Options.AirshipReactorTimeLimit.GetFloat(); } } +} - [HarmonyPatch(typeof(LifeSuppSystemType), nameof(LifeSuppSystemType.Deteriorate))] - public static class LifeSuppSystemTypePatch +[HarmonyPatch(typeof(MushroomMixupSabotageSystem), nameof(MushroomMixupSabotageSystem.UpdateSystem))] +public static class MushroomMixupSabotageSystemUpdateSystemPatch +{ + public static void Postfix() { - private static bool SetDurationForO2Sabotage = true; + Logger.Info(" IsActive", "MushroomMixupSabotageSystem.UpdateSystem.Postfix"); - public static void Prefix(LifeSuppSystemType __instance) + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (!Options.SabotageTimeControl.GetBool()) return; + // Need for hiding player names if player is desync Impostor + Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixup: true); - if ((MapNames)Main.NormalOptions.MapId is MapNames.Polus or MapNames.Airship or MapNames.Fungle) return; - - // When the sabotage ends - if (!__instance.IsActive || !SetDurationForO2Sabotage) - { - if (!SetDurationForO2Sabotage && !__instance.IsActive) SetDurationForO2Sabotage = true; - - return; - } - - Logger.Info($" {ShipStatus.Instance.Type}", "LifeSuppSystemType - ShipStatus.Instance.Type"); - Logger.Info($" {SetDurationForO2Sabotage}", "LifeSuppSystemType - SetDurationCriticalSabotage"); - SetDurationForO2Sabotage = false; - - switch (ShipStatus.Instance.Type) - { - case ShipStatus.MapType.Ship: // The Skeld - __instance.Countdown = Options.SkeldO2TimeLimit.GetFloat(); - return; - case ShipStatus.MapType.Hq: // Mira HQ - __instance.Countdown = Options.MiraO2TimeLimit.GetFloat(); - return; - default: - return; - } - } - } - - [HarmonyPatch(typeof(MushroomMixupSabotageSystem), nameof(MushroomMixupSabotageSystem.UpdateSystem))] - public static class MushroomMixupSabotageSystemUpdateSystemPatch - { - public static void Postfix() - { - Logger.Info(" IsActive", "MushroomMixupSabotageSystem.UpdateSystem.Postfix"); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (!pc.Is(Team.Impostor) && pc.HasDesyncRole()) { // Need for hiding player names if player is desync Impostor Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixup: true); - - if (!pc.Is(Team.Impostor) && pc.HasDesyncRole()) - { - // Need for hiding player names if player is desync Impostor - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixup: true); - } } } } +} - [HarmonyPatch(typeof(MushroomMixupSabotageSystem), nameof(MushroomMixupSabotageSystem.Deteriorate))] - public static class MushroomMixupSabotageSystemPatch +[HarmonyPatch(typeof(MushroomMixupSabotageSystem), nameof(MushroomMixupSabotageSystem.Deteriorate))] +public static class MushroomMixupSabotageSystemPatch +{ + private static bool SetDurationMushroomMixupSabotage = true; + + public static void Prefix(MushroomMixupSabotageSystem __instance, ref bool __state) { - private static bool SetDurationMushroomMixupSabotage = true; + __state = __instance.IsActive; - public static void Prefix(MushroomMixupSabotageSystem __instance, ref bool __state) + if (Options.UsePets.GetBool()) __instance.petEmptyChance = 0; + + if (!Options.SabotageTimeControl.GetBool()) return; + + if ((MapNames)Main.NormalOptions.MapId is not MapNames.Fungle) return; + + // When the sabotage ends + if (!__instance.IsActive || !SetDurationMushroomMixupSabotage) { - __state = __instance.IsActive; + if (!SetDurationMushroomMixupSabotage && !__instance.IsActive) SetDurationMushroomMixupSabotage = true; - if (Options.UsePets.GetBool()) __instance.petEmptyChance = 0; - - if (!Options.SabotageTimeControl.GetBool()) return; - - if ((MapNames)Main.NormalOptions.MapId is not MapNames.Fungle) return; - - // When the sabotage ends - if (!__instance.IsActive || !SetDurationMushroomMixupSabotage) - { - if (!SetDurationMushroomMixupSabotage && !__instance.IsActive) SetDurationMushroomMixupSabotage = true; - - return; - } - - Logger.Info($" {ShipStatus.Instance.Type}", "MushroomMixupSabotageSystem - ShipStatus.Instance.Type"); - Logger.Info($" {SetDurationMushroomMixupSabotage}", "MushroomMixupSabotageSystem - SetDurationCriticalSabotage"); - SetDurationMushroomMixupSabotage = false; - - __instance.currentSecondsUntilHeal = Options.FungleMushroomMixupDuration.GetFloat(); + return; } - public static void Postfix(MushroomMixupSabotageSystem __instance, bool __state) + Logger.Info($" {ShipStatus.Instance.Type}", "MushroomMixupSabotageSystem - ShipStatus.Instance.Type"); + Logger.Info($" {SetDurationMushroomMixupSabotage}", "MushroomMixupSabotageSystem - SetDurationCriticalSabotage"); + SetDurationMushroomMixupSabotage = false; + + __instance.currentSecondsUntilHeal = Options.FungleMushroomMixupDuration.GetFloat(); + } + + public static void Postfix(MushroomMixupSabotageSystem __instance, bool __state) + { + // When Mushroom Mixup Sabotage ends + if (AmongUsClient.Instance.AmHost && __instance.IsActive != __state && GameStates.IsInTask) { - // When Mushroom Mixup Sabotage ends - if (AmongUsClient.Instance.AmHost && __instance.IsActive != __state && GameStates.IsInTask) + LateTask.New(() => { - LateTask.New(() => - { - // After MushroomMixup sabotage, shapeshift cooldown sets to 0 - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - // Reset Ability Cooldown To Default For Living Players - pc.RpcResetAbilityCooldown(); - - // Redo Unshift Trigger due to mushroom mixup breaking it - pc.CheckAndSetUnshiftState(); - } - }, 1.2f, "Reset Ability Cooldown Arter Mushroom Mixup"); - + // After MushroomMixup sabotage, shapeshift cooldown sets to 0 foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (!pc.Is(CustomRoleTypes.Impostor) && pc.HasDesyncRole()) - Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixup: true); - } + { + // Reset Ability Cooldown To Default For Living Players + pc.RpcResetAbilityCooldown(); + + // Redo Unshift Trigger due to mushroom mixup breaking it + pc.CheckAndSetUnshiftState(); + } + }, 1.2f, "Reset Ability Cooldown Arter Mushroom Mixup"); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (!pc.Is(CustomRoleTypes.Impostor) && pc.HasDesyncRole()) + Utils.NotifyRoles(SpecifySeer: pc, ForceLoop: true, MushroomMixup: true); } } +} // Thanks: https://github.com/tukasa0001/TownOfHost/tree/main/Patches/ISystemType/SwitchSystemPatch.cs - [HarmonyPatch(typeof(SwitchSystem), nameof(SwitchSystem.UpdateSystem))] - internal static class SwitchSystemUpdatePatch +[HarmonyPatch(typeof(SwitchSystem), nameof(SwitchSystem.UpdateSystem))] +internal static class SwitchSystemUpdatePatch +{ + private static bool Prefix(SwitchSystem __instance, [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] MessageReader msgReader) { - private static bool Prefix(SwitchSystem __instance, [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] MessageReader msgReader) + byte amount; + { - byte amount; - - { - MessageReader newReader = MessageReader.Get(msgReader); - amount = newReader.ReadByte(); - newReader.Recycle(); - } - - if (!AmongUsClient.Instance.AmHost) return true; - - // No matter if the blackout sabotage is sounded (beware of misdirection as it flies under the host's name) - if (amount.HasBit(SwitchSystem.DamageSystem)) return true; - - // Cancel if player can't fix a specific outage on Airship - if (Main.CurrentMap == MapNames.Airship) - { - Vector2 pos = player.Pos(); - if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Vector2.Distance(pos, new(-12.93f, -11.28f)) <= 2f) return false; - - if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Vector2.Distance(pos, new(13.92f, 6.43f)) <= 2f) return false; - - if (Options.DisableAirshipCargoLightsPanel.GetBool() && Vector2.Distance(pos, new(30.56f, 2.12f)) <= 2f) return false; - } - - if (player.Is(CustomRoles.Fool)) return false; - - if (Options.BlockDisturbancesToSwitches.GetBool()) - { - // Shift 1 to the left by amount - // Each digit corresponds to each switch - // Far left switch - (amount: 0) 00001 - // Far right switch - (amount: 4) 10000 - // ref: SwitchSystem.RepairDamage, SwitchMinigame.FixedUpdate - var switchedKnob = (byte)(0b_00001 << amount); - - // ExpectedSwitches: Up and down state of switches when all are on - // ActualSwitches: Actual up/down state of switch - // if Expected and Actual are the same for the operated knob, the knob is already fixed - if ((__instance.ActualSwitches & switchedKnob) == (__instance.ExpectedSwitches & switchedKnob)) return false; - } - - return true; + MessageReader newReader = MessageReader.Get(msgReader); + amount = newReader.ReadByte(); + newReader.Recycle(); } - } - [HarmonyPatch(typeof(ElectricTask), nameof(ElectricTask.Initialize))] - public static class ElectricTaskInitializePatch + if (!AmongUsClient.Instance.AmHost) return true; + + // No matter if the blackout sabotage is sounded (beware of misdirection as it flies under the host's name) + if (amount.HasBit(SwitchSystem.DamageSystem)) return true; + + // Cancel if player can't fix a specific outage on Airship + if (Main.CurrentMap == MapNames.Airship) + { + Vector2 pos = player.Pos(); + if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Vector2.Distance(pos, new(-12.93f, -11.28f)) <= 2f) return false; + + if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Vector2.Distance(pos, new(13.92f, 6.43f)) <= 2f) return false; + + if (Options.DisableAirshipCargoLightsPanel.GetBool() && Vector2.Distance(pos, new(30.56f, 2.12f)) <= 2f) return false; + } + + if (player.Is(CustomRoles.Fool)) return false; + + if (Options.BlockDisturbancesToSwitches.GetBool()) + { + // Shift 1 to the left by amount + // Each digit corresponds to each switch + // Far left switch - (amount: 0) 00001 + // Far right switch - (amount: 4) 10000 + // ref: SwitchSystem.RepairDamage, SwitchMinigame.FixedUpdate + var switchedKnob = (byte)(0b_00001 << amount); + + // ExpectedSwitches: Up and down state of switches when all are on + // ActualSwitches: Actual up/down state of switch + // if Expected and Actual are the same for the operated knob, the knob is already fixed + if ((__instance.ActualSwitches & switchedKnob) == (__instance.ExpectedSwitches & switchedKnob)) return false; + } + + return true; + } +} + +[HarmonyPatch(typeof(ElectricTask), nameof(ElectricTask.Initialize))] +public static class ElectricTaskInitializePatch +{ + private static long LastUpdate; + + public static void Postfix() { - private static long LastUpdate; + long now = Utils.TimeStamp; + if (LastUpdate >= now) return; - public static void Postfix() + LastUpdate = now; + + Utils.MarkEveryoneDirtySettingsV2(); + + if (GameStates.IsInTask) { - long now = Utils.TimeStamp; - if (LastUpdate >= now) return; - - LastUpdate = now; - - Utils.MarkEveryoneDirtySettingsV2(); - - if (GameStates.IsInTask) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (pc.GetCustomRole().NeedUpdateOnLights() || pc.Is(CustomRoles.Mare) || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) - Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true); - } - - Logger.Info("Lights sabotage called", "ElectricTask"); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc.GetCustomRole().NeedUpdateOnLights() || pc.Is(CustomRoles.Mare) || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) + Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true); } - } - [HarmonyPatch(typeof(ElectricTask), nameof(ElectricTask.Complete))] - public static class ElectricTaskCompletePatch + Logger.Info("Lights sabotage called", "ElectricTask"); + } +} + +[HarmonyPatch(typeof(ElectricTask), nameof(ElectricTask.Complete))] +public static class ElectricTaskCompletePatch +{ + private static long LastUpdate; + + public static void Postfix() { - private static long LastUpdate; + long now = Utils.TimeStamp; + if (LastUpdate >= now) return; - public static void Postfix() + LastUpdate = now; + + Utils.MarkEveryoneDirtySettingsV2(); + + if (GameStates.IsInTask) { - long now = Utils.TimeStamp; - if (LastUpdate >= now) return; - - LastUpdate = now; - - Utils.MarkEveryoneDirtySettingsV2(); - - if (GameStates.IsInTask) - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (pc.GetCustomRole().NeedUpdateOnLights() || pc.Is(CustomRoles.Mare) || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) - Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true); - } - - Logger.Info("Lights sabotage fixed", "ElectricTask"); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc.GetCustomRole().NeedUpdateOnLights() || pc.Is(CustomRoles.Mare) || pc.Is(CustomRoles.Torch) || pc.Is(CustomRoles.Sleep) || Beacon.IsAffectedPlayer(pc.PlayerId)) + Utils.NotifyRoles(SpecifyTarget: pc, ForceLoop: true); } + + Logger.Info("Lights sabotage fixed", "ElectricTask"); } +} // https://github.com/tukasa0001/TownOfHost/blob/357f7b5523e4bdd0bb58cda1e0ff6cceaa84813d/Patches/SabotageSystemPatch.cs // Method called when sabotage occurs - [HarmonyPatch(typeof(SabotageSystemType), nameof(SabotageSystemType.UpdateSystem))] - public static class SabotageSystemTypeRepairDamagePatch +[HarmonyPatch(typeof(SabotageSystemType), nameof(SabotageSystemType.UpdateSystem))] +public static class SabotageSystemTypeRepairDamagePatch +{ + public static bool IsCooldownModificationEnabled; + public static float ModifiedCooldownSec; + + public static void Initialize() { - public static bool IsCooldownModificationEnabled; - public static float ModifiedCooldownSec; - - public static void Initialize() - { - IsCooldownModificationEnabled = Options.SabotageCooldownControl.GetBool(); - ModifiedCooldownSec = Options.SabotageCooldown.GetFloat(); - } - - public static bool Prefix([HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] MessageReader msgReader) - { - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; - - if (Options.DisableSabotage.GetBool()) - { - SystemTypes systemTypes; - - { - MessageReader newReader = MessageReader.Get(msgReader); - systemTypes = (SystemTypes)newReader.ReadByte(); - newReader.Recycle(); - } - - switch (systemTypes) - { - case SystemTypes.Hallway: - case SystemTypes.Storage: - case SystemTypes.Cafeteria: - case SystemTypes.UpperEngine: - case SystemTypes.Nav: - case SystemTypes.Admin: - break; - case SystemTypes.Reactor when Options.DisableReactorOnSkeldAndMira.GetBool(): - case SystemTypes.Electrical when Options.DisableLights.GetBool(): - case SystemTypes.LifeSupp when Options.DisableO2.GetBool(): - return false; - default: - if ((uint)systemTypes <= 21U) - { - switch (systemTypes) - { - case SystemTypes.Comms when Options.DisableComms.GetBool(): - case SystemTypes.Laboratory when Options.DisableReactorOnPolus.GetBool(): - return false; - } - } - else - { - switch (systemTypes) - { - case SystemTypes.MushroomMixupSabotage when Options.DisableMushroomMixup.GetBool(): - case SystemTypes.HeliSabotage when Options.DisableReactorOnAirship.GetBool(): - return false; - } - } - - break; - } - } - - if (SecurityGuard.BlockSabo.Count > 0) return false; - - if (player.IsRoleBlocked()) - { - player.Notify(BlockedAction.Sabotage.GetBlockNotify()); - return false; - } - - if (player.Is(Team.Impostor) && !player.IsAlive() && Options.DeadImpCantSabotage.GetBool()) return false; - - if (!player.Is(Team.Impostor) && !player.IsAlive()) return false; - - bool allow = player.GetCustomRole() switch - { - CustomRoles.Jackal when Jackal.CanSabotage.GetBool() => true, - CustomRoles.Sidekick when Jackal.CanSabotageSK.GetBool() => true, - CustomRoles.Traitor when Traitor.CanSabotage.GetBool() => true, - CustomRoles.Parasite when player.IsAlive() => true, - CustomRoles.Refugee when player.IsAlive() => true, - _ => Main.PlayerStates[player.PlayerId].Role.CanUseSabotage(player) && Main.PlayerStates[player.PlayerId].Role.OnSabotage(player) - }; - - if (allow && QuizMaster.On) QuizMaster.Data.NumSabotages++; - - return allow; - } - - - public static void Postfix(SabotageSystemType __instance) - { - if (!IsCooldownModificationEnabled || !AmongUsClient.Instance.AmHost) return; - - __instance.Timer = ModifiedCooldownSec; - __instance.IsDirty = true; - } + IsCooldownModificationEnabled = Options.SabotageCooldownControl.GetBool(); + ModifiedCooldownSec = Options.SabotageCooldown.GetFloat(); } - [HarmonyPatch(typeof(SecurityCameraSystemType), nameof(SecurityCameraSystemType.UpdateSystem))] - public static class SecurityCameraPatch + public static bool Prefix([HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] MessageReader msgReader) { - public static bool Prefix([HarmonyArgument(1)] MessageReader msgReader) + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; + + if (Options.DisableSabotage.GetBool()) { - byte amount; + SystemTypes systemTypes; { MessageReader newReader = MessageReader.Get(msgReader); - amount = newReader.ReadByte(); + systemTypes = (SystemTypes)newReader.ReadByte(); newReader.Recycle(); } - if (amount == SecurityCameraSystemType.IncrementOp) + switch (systemTypes) { - return !((MapNames)Main.NormalOptions.MapId switch - { - MapNames.Skeld or MapNames.Dleks => Options.DisableSkeldCamera.GetBool(), - MapNames.Polus => Options.DisablePolusCamera.GetBool(), - MapNames.Airship => Options.DisableAirshipCamera.GetBool(), - _ => false - }); - } + case SystemTypes.Hallway: + case SystemTypes.Storage: + case SystemTypes.Cafeteria: + case SystemTypes.UpperEngine: + case SystemTypes.Nav: + case SystemTypes.Admin: + break; + case SystemTypes.Reactor when Options.DisableReactorOnSkeldAndMira.GetBool(): + case SystemTypes.Electrical when Options.DisableLights.GetBool(): + case SystemTypes.LifeSupp when Options.DisableO2.GetBool(): + return false; + default: + if ((uint)systemTypes <= 21U) + { + switch (systemTypes) + { + case SystemTypes.Comms when Options.DisableComms.GetBool(): + case SystemTypes.Laboratory when Options.DisableReactorOnPolus.GetBool(): + return false; + } + } + else + { + switch (systemTypes) + { + case SystemTypes.MushroomMixupSabotage when Options.DisableMushroomMixup.GetBool(): + case SystemTypes.HeliSabotage when Options.DisableReactorOnAirship.GetBool(): + return false; + } + } - return true; + break; + } } + + if (SecurityGuard.BlockSabo.Count > 0) return false; + + if (player.IsRoleBlocked()) + { + player.Notify(BlockedAction.Sabotage.GetBlockNotify()); + return false; + } + + if (player.Is(Team.Impostor) && !player.IsAlive() && Options.DeadImpCantSabotage.GetBool()) return false; + + if (!player.Is(Team.Impostor) && !player.IsAlive()) return false; + + bool allow = player.GetCustomRole() switch + { + CustomRoles.Jackal when Jackal.CanSabotage.GetBool() => true, + CustomRoles.Sidekick when Jackal.CanSabotageSK.GetBool() => true, + CustomRoles.Traitor when Traitor.CanSabotage.GetBool() => true, + CustomRoles.Parasite when player.IsAlive() => true, + CustomRoles.Refugee when player.IsAlive() => true, + _ => Main.PlayerStates[player.PlayerId].Role.CanUseSabotage(player) && Main.PlayerStates[player.PlayerId].Role.OnSabotage(player) + }; + + if (allow && QuizMaster.On) QuizMaster.Data.NumSabotages++; + + return allow; + } + + + public static void Postfix(SabotageSystemType __instance) + { + if (!IsCooldownModificationEnabled || !AmongUsClient.Instance.AmHost) return; + + __instance.Timer = ModifiedCooldownSec; + __instance.IsDirty = true; + } +} + +[HarmonyPatch(typeof(SecurityCameraSystemType), nameof(SecurityCameraSystemType.UpdateSystem))] +public static class SecurityCameraPatch +{ + public static bool Prefix([HarmonyArgument(1)] MessageReader msgReader) + { + byte amount; + + { + MessageReader newReader = MessageReader.Get(msgReader); + amount = newReader.ReadByte(); + newReader.Recycle(); + } + + if (amount == SecurityCameraSystemType.IncrementOp) + { + return !((MapNames)Main.NormalOptions.MapId switch + { + MapNames.Skeld or MapNames.Dleks => Options.DisableSkeldCamera.GetBool(), + MapNames.Polus => Options.DisablePolusCamera.GetBool(), + MapNames.Airship => Options.DisableAirshipCamera.GetBool(), + _ => false + }); + } + + return true; } } \ No newline at end of file diff --git a/Patches/ServerVersionPatch.cs b/Patches/ServerVersionPatch.cs index 53b971837..3d290ddcc 100644 --- a/Patches/ServerVersionPatch.cs +++ b/Patches/ServerVersionPatch.cs @@ -1,30 +1,29 @@ using HarmonyLib; -namespace EHR.Patches -{ - [HarmonyPatch(typeof(Constants), nameof(Constants.GetBroadcastVersion))] - internal static class ServerUpdatePatch - { - public static void Postfix(ref int __result) - { - if (GameStates.IsLocalGame) Logger.Info($"IsLocalGame: {__result}", "VersionServer"); +namespace EHR.Patches; - if (GameStates.IsOnlineGame) - { - // Changing server version for AU mods - __result += 25; - Logger.Info($"IsOnlineGame: {__result}", "VersionServer"); - } +[HarmonyPatch(typeof(Constants), nameof(Constants.GetBroadcastVersion))] +internal static class ServerUpdatePatch +{ + public static void Postfix(ref int __result) + { + if (GameStates.IsLocalGame) Logger.Info($"IsLocalGame: {__result}", "VersionServer"); + + if (GameStates.IsOnlineGame) + { + // Changing server version for AU mods + __result += 25; + Logger.Info($"IsOnlineGame: {__result}", "VersionServer"); } } +} - [HarmonyPatch(typeof(Constants), nameof(Constants.IsVersionModded))] - public static class IsVersionModdedPatch +[HarmonyPatch(typeof(Constants), nameof(Constants.IsVersionModded))] +public static class IsVersionModdedPatch +{ + public static bool Prefix(ref bool __result) { - public static bool Prefix(ref bool __result) - { - __result = true; - return false; - } + __result = true; + return false; } } \ No newline at end of file diff --git a/Patches/ShipStatusPatch.cs b/Patches/ShipStatusPatch.cs index a9215732f..74a7cdaee 100644 --- a/Patches/ShipStatusPatch.cs +++ b/Patches/ShipStatusPatch.cs @@ -13,666 +13,665 @@ using HarmonyLib; using Hazel; using UnityEngine; -namespace EHR -{ - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.FixedUpdate))] - internal static class ShipFixedUpdatePatch - { - public static void Postfix( /*ShipStatus __instance*/) - { - if (!AmongUsClient.Instance.AmHost) return; +namespace EHR; - if (Main.IsFixedCooldown && Main.RefixCooldownDelay >= 0) - Main.RefixCooldownDelay -= Time.fixedDeltaTime; - else if (!float.IsNaN(Main.RefixCooldownDelay)) - { - Utils.MarkEveryoneDirtySettingsV4(); - Main.RefixCooldownDelay = float.NaN; - Logger.Info("Refix Cooldown", "CoolDown"); - } +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.FixedUpdate))] +internal static class ShipFixedUpdatePatch +{ + public static void Postfix( /*ShipStatus __instance*/) + { + if (!AmongUsClient.Instance.AmHost) return; + + if (Main.IsFixedCooldown && Main.RefixCooldownDelay >= 0) + Main.RefixCooldownDelay -= Time.fixedDeltaTime; + else if (!float.IsNaN(Main.RefixCooldownDelay)) + { + Utils.MarkEveryoneDirtySettingsV4(); + Main.RefixCooldownDelay = float.NaN; + Logger.Info("Refix Cooldown", "CoolDown"); } } +} - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] - public static class MessageReaderUpdateSystemPatch +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(MessageReader))] +public static class MessageReaderUpdateSystemPatch +{ + public static bool Prefix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) { - public static bool Prefix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player, [HarmonyArgument(2)] MessageReader reader) + try { - try + if (systemType is SystemTypes.Ventilation or SystemTypes.Security or SystemTypes.Decontamination or SystemTypes.Decontamination2 or SystemTypes.Decontamination3 or SystemTypes.MedBay) return true; + + byte amount = MessageReader.Get(reader).ReadByte(); + + if (EAC.CheckInvalidSabotage(systemType, player, amount)) { - if (systemType is SystemTypes.Ventilation or SystemTypes.Security or SystemTypes.Decontamination or SystemTypes.Decontamination2 or SystemTypes.Decontamination3 or SystemTypes.MedBay) return true; + Logger.Info("EAC patched Sabotage RPC", "MessageReaderUpdateSystemPatch"); + return false; + } - byte amount = MessageReader.Get(reader).ReadByte(); + return RepairSystemPatch.Prefix(systemType, player, amount); + } + catch { } - if (EAC.CheckInvalidSabotage(systemType, player, amount)) + return true; + } + + public static void Postfix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player) + { + try + { + if (systemType is SystemTypes.Ventilation or SystemTypes.Security or SystemTypes.Decontamination or SystemTypes.Decontamination2 or SystemTypes.Decontamination3 or SystemTypes.MedBay) return; + + RepairSystemPatch.Postfix(systemType, player); + } + catch { } + } +} + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(byte))] +internal static class RepairSystemPatch +{ + public static bool Prefix( /*ShipStatus __instance,*/ + [HarmonyArgument(0)] SystemTypes systemType, + [HarmonyArgument(1)] PlayerControl player, + [HarmonyArgument(2)] byte amount) + { + Logger.Msg($"SystemType: {systemType}, PlayerName: {player.GetNameWithRole().RemoveHtmlTags()}, amount: {amount}", "RepairSystem"); + if (RepairSender.Enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) Logger.SendInGame($"SystemType: {systemType}, PlayerName: {player.GetNameWithRole().RemoveHtmlTags()}, amount: {amount}"); + + if (!AmongUsClient.Instance.AmHost) return true; // Execute the following only on the host + + if ((!CustomGameMode.Standard.IsActiveOrIntegrated() || Options.DisableSabotage.GetBool()) && systemType == SystemTypes.Sabotage) return false; + + if (player.Is(CustomRoles.Fool) && systemType is SystemTypes.Comms or SystemTypes.Electrical) return false; + + switch (player.GetCustomRole()) + { + case CustomRoles.SabotageMaster: + SabotageMaster.RepairSystem(player.PlayerId, systemType, amount); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; + case CustomRoles.Alchemist when Main.PlayerStates[player.PlayerId].Role is Alchemist { IsEnable: true, FixNextSabo: true }: + Alchemist.RepairSystem(player, systemType, amount); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; + case CustomRoles.Technician: + Technician.RepairSystem(player.PlayerId, systemType, amount); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; + } + + switch (systemType) + { + case SystemTypes.Doors when player.Is(CustomRoles.Unlucky) && player.IsAlive(): + var Ue = IRandom.Instance; + + if (Ue.Next(0, 100) < Options.UnluckySabotageSuicideChance.GetInt()) { - Logger.Info("EAC patched Sabotage RPC", "MessageReaderUpdateSystemPatch"); + player.Suicide(); return false; } - return RepairSystemPatch.Prefix(systemType, player, amount); - } - catch { } + break; + case SystemTypes.Electrical when amount <= 4 && Main.NormalOptions.MapId == 4: + if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(-12.93f, -11.28f)) <= 2f) return false; - return true; - } + if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(13.92f, 6.43f)) <= 2f) return false; + + if (Options.DisableAirshipCargoLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(30.56f, 2.12f)) <= 2f) return false; + + goto Next; + case SystemTypes.Electrical when amount <= 4: + Next: - public static void Postfix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player) - { - try { - if (systemType is SystemTypes.Ventilation or SystemTypes.Security or SystemTypes.Decontamination or SystemTypes.Decontamination2 or SystemTypes.Decontamination3 or SystemTypes.MedBay) return; - - RepairSystemPatch.Postfix(systemType, player); - } - catch { } - } - } - - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.UpdateSystem), typeof(SystemTypes), typeof(PlayerControl), typeof(byte))] - internal static class RepairSystemPatch - { - public static bool Prefix( /*ShipStatus __instance,*/ - [HarmonyArgument(0)] SystemTypes systemType, - [HarmonyArgument(1)] PlayerControl player, - [HarmonyArgument(2)] byte amount) - { - Logger.Msg($"SystemType: {systemType}, PlayerName: {player.GetNameWithRole().RemoveHtmlTags()}, amount: {amount}", "RepairSystem"); - if (RepairSender.Enabled && AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) Logger.SendInGame($"SystemType: {systemType}, PlayerName: {player.GetNameWithRole().RemoveHtmlTags()}, amount: {amount}"); - - if (!AmongUsClient.Instance.AmHost) return true; // Execute the following only on the host - - if ((!CustomGameMode.Standard.IsActiveOrIntegrated() || Options.DisableSabotage.GetBool()) && systemType == SystemTypes.Sabotage) return false; - - if (player.Is(CustomRoles.Fool) && systemType is SystemTypes.Comms or SystemTypes.Electrical) return false; - - switch (player.GetCustomRole()) - { - case CustomRoles.SabotageMaster: - SabotageMaster.RepairSystem(player.PlayerId, systemType, amount); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - case CustomRoles.Alchemist when Main.PlayerStates[player.PlayerId].Role is Alchemist { IsEnable: true, FixNextSabo: true }: - Alchemist.RepairSystem(player, systemType, amount); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - case CustomRoles.Technician: - Technician.RepairSystem(player.PlayerId, systemType, amount); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - } - - switch (systemType) - { - case SystemTypes.Doors when player.Is(CustomRoles.Unlucky) && player.IsAlive(): - var Ue = IRandom.Instance; - - if (Ue.Next(0, 100) < Options.UnluckySabotageSuicideChance.GetInt()) - { - player.Suicide(); - return false; - } - - break; - case SystemTypes.Electrical when amount <= 4 && Main.NormalOptions.MapId == 4: - if (Options.DisableAirshipViewingDeckLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(-12.93f, -11.28f)) <= 2f) return false; - - if (Options.DisableAirshipGapRoomLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(13.92f, 6.43f)) <= 2f) return false; - - if (Options.DisableAirshipCargoLightsPanel.GetBool() && Vector2.Distance(player.transform.position, new(30.56f, 2.12f)) <= 2f) return false; - - goto Next; - case SystemTypes.Electrical when amount <= 4: - Next: + var SwitchSystem = ShipStatus.Instance?.Systems?[SystemTypes.Electrical]?.Cast(); + if (SwitchSystem is { IsActive: true }) { - var SwitchSystem = ShipStatus.Instance?.Systems?[SystemTypes.Electrical]?.Cast(); - - if (SwitchSystem is { IsActive: true }) + switch (Main.PlayerStates[player.PlayerId].Role) { - switch (Main.PlayerStates[player.PlayerId].Role) + case SabotageMaster: { - case SabotageMaster: - { - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "SabotageMaster"); - SabotageMaster.SwitchSystemRepair(player.PlayerId, SwitchSystem, amount); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - } - case Alchemist { FixNextSabo: true } am: - { - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "Alchemist"); - if (amount.HasBit(SwitchSystem.DamageSystem)) break; - - SwitchSystem.ActualSwitches = (byte)(SwitchSystem.ExpectedSwitches ^ (1 << amount)); - am.FixNextSabo = false; - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - } - case Adventurer av: - { - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "Adventurer"); - if (amount.HasBit(SwitchSystem.DamageSystem)) break; - - SwitchSystem.ActualSwitches = (byte)(SwitchSystem.ExpectedSwitches ^ (1 << amount)); - av.OnLightsFix(); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - } - case Technician: - { - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "Technician"); - Technician.SwitchSystemRepair(player.PlayerId, SwitchSystem, amount); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - break; - } + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "SabotageMaster"); + SabotageMaster.SwitchSystemRepair(player.PlayerId, SwitchSystem, amount); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; } + case Alchemist { FixNextSabo: true } am: + { + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "Alchemist"); + if (amount.HasBit(SwitchSystem.DamageSystem)) break; - if (player.Is(CustomRoles.Damocles) && Damocles.CountRepairSabotage) Damocles.OnRepairSabotage(player.PlayerId); + SwitchSystem.ActualSwitches = (byte)(SwitchSystem.ExpectedSwitches ^ (1 << amount)); + am.FixNextSabo = false; + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; + } + case Adventurer av: + { + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "Adventurer"); + if (amount.HasBit(SwitchSystem.DamageSystem)) break; - if (player.Is(CustomRoles.Stressed) && Stressed.CountRepairSabotage) Stressed.OnRepairSabotage(player); + SwitchSystem.ActualSwitches = (byte)(SwitchSystem.ExpectedSwitches ^ (1 << amount)); + av.OnLightsFix(); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; + } + case Technician: + { + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} instant-fix-lights", "Technician"); + Technician.SwitchSystemRepair(player.PlayerId, SwitchSystem, amount); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + break; + } } - break; + if (player.Is(CustomRoles.Damocles) && Damocles.CountRepairSabotage) Damocles.OnRepairSabotage(player.PlayerId); + + if (player.Is(CustomRoles.Stressed) && Stressed.CountRepairSabotage) Stressed.OnRepairSabotage(player); } - case SystemTypes.Sabotage when AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay: - if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; - if (SecurityGuard.BlockSabo.Count > 0) return false; - - if (player.IsRoleBlocked()) - { - player.Notify(BlockedAction.Sabotage.GetBlockNotify()); - return false; - } - - if (player.Is(Team.Impostor) && !player.IsAlive() && Options.DeadImpCantSabotage.GetBool()) return false; - - if (!player.Is(Team.Impostor) && !player.IsAlive()) return false; - - return player.GetCustomRole() switch - { - CustomRoles.Jackal when Jackal.CanSabotage.GetBool() => true, - CustomRoles.Sidekick when Jackal.CanSabotageSK.GetBool() => true, - CustomRoles.Traitor when Traitor.CanSabotage.GetBool() => true, - CustomRoles.Parasite when player.IsAlive() => true, - CustomRoles.Refugee when player.IsAlive() => true, - _ => Main.PlayerStates[player.PlayerId].Role.CanUseSabotage(player) - }; - case SystemTypes.Security when amount == 1: - bool camerasDisabled = (MapNames)Main.NormalOptions.MapId switch - { - MapNames.Skeld => Options.DisableSkeldCamera.GetBool(), - MapNames.Polus => Options.DisablePolusCamera.GetBool(), - MapNames.Airship => Options.DisableAirshipCamera.GetBool(), - _ => false - }; - - if (camerasDisabled) player.Notify(Translator.GetString("CamerasDisabledNotify"), 15f); - - return !camerasDisabled; + break; } + case SystemTypes.Sabotage when AmongUsClient.Instance.NetworkMode != NetworkModes.FreePlay: + if (!CustomGameMode.Standard.IsActiveOrIntegrated()) return false; - return true; - } + if (SecurityGuard.BlockSabo.Count > 0) return false; - public static void Postfix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player) - { - switch (systemType) - { - case SystemTypes.Comms: - if (!Camouflage.CheckCamouflage()) Utils.NotifyRoles(); - goto case SystemTypes.Electrical; - case SystemTypes.Reactor: - case SystemTypes.LifeSupp: - case SystemTypes.Laboratory: - case SystemTypes.HeliSabotage: - case SystemTypes.Electrical: + if (player.IsRoleBlocked()) { - if (player.Is(CustomRoles.Damocles) && Damocles.CountRepairSabotage) - Damocles.OnRepairSabotage(player.PlayerId); - - if (player.Is(CustomRoles.Stressed) && Stressed.CountRepairSabotage) - Stressed.OnRepairSabotage(player); - - if (Main.PlayerStates[player.PlayerId].Role is Rogue rg) - rg.OnFixSabotage(); - - break; + player.Notify(BlockedAction.Sabotage.GetBlockNotify()); + return false; } - } - } - public static void CheckAndOpenDoorsRange(ShipStatus __instance, int amount, int min, int max) - { - List Ids = new(); - for (int i = min; i <= max; i++) Ids.Add(i); + if (player.Is(Team.Impostor) && !player.IsAlive() && Options.DeadImpCantSabotage.GetBool()) return false; - CheckAndOpenDoors(__instance, amount, [.. Ids]); - } + if (!player.Is(Team.Impostor) && !player.IsAlive()) return false; - private static void CheckAndOpenDoors(ShipStatus __instance, int amount, params int[] DoorIds) - { - if (!DoorIds.Contains(amount)) return; - - foreach (int id in DoorIds) __instance.RpcUpdateSystem(SystemTypes.Doors, (byte)id); - } - } - - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CloseDoorsOfType))] - internal static class CloseDoorsPatch - { - public static bool Prefix( /*ShipStatus __instance, */ [HarmonyArgument(0)] SystemTypes room) - { - bool allow = !Options.DisableSabotage.GetBool() && Options.CurrentGameMode is not CustomGameMode.SoloKombat and not CustomGameMode.FFA and not CustomGameMode.MoveAndStop and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.AllInOne; - - if (SecurityGuard.BlockSabo.Count > 0) allow = false; - - if (Options.DisableCloseDoor.GetBool()) allow = false; - - Logger.Info($"({room}) => {(allow ? "Allowed" : "Blocked")}", "DoorClose"); - return allow; - } - } - - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] - internal static class StartPatch - { - public static void Postfix() - { - Logger.Info("-----------Game start-----------", "Phase"); - - Utils.CountAlivePlayers(true); - - if (Options.AllowConsole.GetBool()) - { - if (!ConsoleManager.ConsoleActive && ConsoleManager.ConsoleEnabled) ConsoleManager.CreateConsole(); - } - else - { - if (ConsoleManager.ConsoleActive && !DebugModeManager.AmDebugger) + return player.GetCustomRole() switch { - ConsoleManager.DetachConsole(); - Logger.SendInGame("Sorry, console use is prohibited in this room, so your console has been turned off"); - } - } + CustomRoles.Jackal when Jackal.CanSabotage.GetBool() => true, + CustomRoles.Sidekick when Jackal.CanSabotageSK.GetBool() => true, + CustomRoles.Traitor when Traitor.CanSabotage.GetBool() => true, + CustomRoles.Parasite when player.IsAlive() => true, + CustomRoles.Refugee when player.IsAlive() => true, + _ => Main.PlayerStates[player.PlayerId].Role.CanUseSabotage(player) + }; + case SystemTypes.Security when amount == 1: + bool camerasDisabled = (MapNames)Main.NormalOptions.MapId switch + { + MapNames.Skeld => Options.DisableSkeldCamera.GetBool(), + MapNames.Polus => Options.DisablePolusCamera.GetBool(), + MapNames.Airship => Options.DisableAirshipCamera.GetBool(), + _ => false + }; + + if (camerasDisabled) player.Notify(Translator.GetString("CamerasDisabledNotify"), 15f); + + return !camerasDisabled; } + + return true; } - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.StartMeeting))] - internal static class StartMeetingPatch + public static void Postfix([HarmonyArgument(0)] SystemTypes systemType, [HarmonyArgument(1)] PlayerControl player) { - public static void Prefix( /*ShipStatus __instance, PlayerControl reporter,*/ NetworkedPlayerInfo target) + switch (systemType) { - MeetingStates.ReportTarget = target; - MeetingStates.DeadBodies = Object.FindObjectsOfType(); - } - } - - [HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] - internal static class CheckTaskCompletionPatch - { - public static bool Prefix(ref bool __result) - { - if (Options.DisableTaskWin.GetBool() || Options.NoGameEnd.GetBool() || TaskState.InitialTotalTasks == 0 || (Options.DisableTaskWinIfAllCrewsAreDead.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoleTypes.Crewmate))) || (Options.DisableTaskWinIfAllCrewsAreConverted.GetBool() && Main.AllAlivePlayerControls.Where(x => x.Is(Team.Crewmate) && x.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel).All(x => x.IsConverted())) || !CustomGameMode.Standard.IsActiveOrIntegrated()) + case SystemTypes.Comms: + if (!Camouflage.CheckCamouflage()) Utils.NotifyRoles(); + goto case SystemTypes.Electrical; + case SystemTypes.Reactor: + case SystemTypes.LifeSupp: + case SystemTypes.Laboratory: + case SystemTypes.HeliSabotage: + case SystemTypes.Electrical: { - __result = false; - return false; - } + if (player.Is(CustomRoles.Damocles) && Damocles.CountRepairSabotage) + Damocles.OnRepairSabotage(player.PlayerId); - return true; + if (player.Is(CustomRoles.Stressed) && Stressed.CountRepairSabotage) + Stressed.OnRepairSabotage(player); + + if (Main.PlayerStates[player.PlayerId].Role is Rogue rg) + rg.OnFixSabotage(); + + break; + } } } - [HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))] - public static class HauntMenuMinigameSetFilterTextPatch + public static void CheckAndOpenDoorsRange(ShipStatus __instance, int amount, int min, int max) { - public static bool Prefix(HauntMenuMinigame __instance) + List Ids = new(); + for (int i = min; i <= max; i++) Ids.Add(i); + + CheckAndOpenDoors(__instance, amount, [.. Ids]); + } + + private static void CheckAndOpenDoors(ShipStatus __instance, int amount, params int[] DoorIds) + { + if (!DoorIds.Contains(amount)) return; + + foreach (int id in DoorIds) __instance.RpcUpdateSystem(SystemTypes.Doors, (byte)id); + } +} + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CloseDoorsOfType))] +internal static class CloseDoorsPatch +{ + public static bool Prefix( /*ShipStatus __instance, */ [HarmonyArgument(0)] SystemTypes room) + { + bool allow = !Options.DisableSabotage.GetBool() && Options.CurrentGameMode is not CustomGameMode.SoloKombat and not CustomGameMode.FFA and not CustomGameMode.MoveAndStop and not CustomGameMode.HotPotato and not CustomGameMode.Speedrun and not CustomGameMode.CaptureTheFlag and not CustomGameMode.NaturalDisasters and not CustomGameMode.RoomRush and not CustomGameMode.AllInOne; + + if (SecurityGuard.BlockSabo.Count > 0) allow = false; + + if (Options.DisableCloseDoor.GetBool()) allow = false; + + Logger.Info($"({room}) => {(allow ? "Allowed" : "Blocked")}", "DoorClose"); + return allow; + } +} + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Start))] +internal static class StartPatch +{ + public static void Postfix() + { + Logger.Info("-----------Game start-----------", "Phase"); + + Utils.CountAlivePlayers(true); + + if (Options.AllowConsole.GetBool()) { - if (__instance.HauntTarget != null && Options.GhostCanSeeOtherRoles.GetBool()) + if (!ConsoleManager.ConsoleActive && ConsoleManager.ConsoleEnabled) ConsoleManager.CreateConsole(); + } + else + { + if (ConsoleManager.ConsoleActive && !DebugModeManager.AmDebugger) { - byte id = __instance.HauntTarget.PlayerId; - __instance.FilterText.text = Utils.GetDisplayRoleName(id) + Utils.GetProgressText(id); - return false; - } - - return true; - } - } - -// From https://github.com/0xDrMoe/TownofHost-Enhanced - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] - internal static class ShipStatusBeginPatch - { - public static bool RolesIsAssigned = false; - - public static bool Prefix() - { - return RolesIsAssigned; - } - - public static void Postfix() - { - if (RolesIsAssigned && !Main.IntroDestroyed) - { - foreach (PlayerControl player in Main.AllPlayerControls) Main.PlayerStates[player.PlayerId].InitTask(player); - - GameData.Instance.RecomputeTaskCounts(); - TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; - - Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); + ConsoleManager.DetachConsole(); + Logger.SendInGame("Sorry, console use is prohibited in this room, so your console has been turned off"); } } } +} -// From https://github.com/0xDrMoe/TownofHost-Enhanced - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.SpawnPlayer))] - internal static class ShipStatusSpawnPlayerPatch +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.StartMeeting))] +internal static class StartMeetingPatch +{ + public static void Prefix( /*ShipStatus __instance, PlayerControl reporter,*/ NetworkedPlayerInfo target) { - // Since SnapTo is unstable on the server side, - // after a meeting, sometimes not all players appear on the table, - // it's better to manually teleport them - public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) + MeetingStates.ReportTarget = target; + MeetingStates.DeadBodies = Object.FindObjectsOfType(); + } +} + +[HarmonyPatch(typeof(GameManager), nameof(GameManager.CheckTaskCompletion))] +internal static class CheckTaskCompletionPatch +{ + public static bool Prefix(ref bool __result) + { + if (Options.DisableTaskWin.GetBool() || Options.NoGameEnd.GetBool() || TaskState.InitialTotalTasks == 0 || (Options.DisableTaskWinIfAllCrewsAreDead.GetBool() && !Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoleTypes.Crewmate))) || (Options.DisableTaskWinIfAllCrewsAreConverted.GetBool() && Main.AllAlivePlayerControls.Where(x => x.Is(Team.Crewmate) && x.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Scientist or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel).All(x => x.IsConverted())) || !CustomGameMode.Standard.IsActiveOrIntegrated()) { - if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true; - - // Lazy doesn't teleport to the meeting table - if (player.Is(CustomRoles.Lazy)) return false; - - Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); - Vector2 position = __instance.MeetingSpawnCenter + (direction * __instance.SpawnRadius) + new Vector2(0.0f, 0.3636f); - - player.TP(position, true, false); + __result = false; return false; } + + return true; } +} -// From https://github.com/0xDrMoe/TownofHost-Enhanced - [HarmonyPatch(typeof(PolusShipStatus), nameof(PolusShipStatus.SpawnPlayer))] - internal static class PolusShipStatusSpawnPlayerPatch +[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetFilterText))] +public static class HauntMenuMinigameSetFilterTextPatch +{ + public static bool Prefix(HauntMenuMinigame __instance) { - public static bool Prefix(PolusShipStatus __instance, - [HarmonyArgument(0)] PlayerControl player, - [HarmonyArgument(1)] int numPlayers, - [HarmonyArgument(2)] bool initialSpawn) + if (__instance.HauntTarget != null && Options.GhostCanSeeOtherRoles.GetBool()) { - if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true; - - // Lazy doesn't teleport to the meeting table - if (player.Is(CustomRoles.Lazy)) return false; - - int num1 = Mathf.FloorToInt(numPlayers / 2f); - int num2 = player.PlayerId % 15; - - Vector2 position = num2 >= num1 - ? __instance.MeetingSpawnCenter2 + (Vector2.right * (num2 - num1) * 0.6f) - : __instance.MeetingSpawnCenter + (Vector2.right * num2 * 0.6f); - - player.TP(position, true, false); + byte id = __instance.HauntTarget.PlayerId; + __instance.FilterText.text = Utils.GetDisplayRoleName(id) + Utils.GetProgressText(id); return false; } + + return true; } +} + +// From https://github.com/0xDrMoe/TownofHost-Enhanced +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] +internal static class ShipStatusBeginPatch +{ + public static bool RolesIsAssigned = false; + + public static bool Prefix() + { + return RolesIsAssigned; + } + + public static void Postfix() + { + if (RolesIsAssigned && !Main.IntroDestroyed) + { + foreach (PlayerControl player in Main.AllPlayerControls) Main.PlayerStates[player.PlayerId].InitTask(player); + + GameData.Instance.RecomputeTaskCounts(); + TaskState.InitialTotalTasks = GameData.Instance.TotalTasks; + + Utils.DoNotifyRoles(ForceLoop: true, NoCache: true); + } + } +} + +// From https://github.com/0xDrMoe/TownofHost-Enhanced +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.SpawnPlayer))] +internal static class ShipStatusSpawnPlayerPatch +{ + // Since SnapTo is unstable on the server side, + // after a meeting, sometimes not all players appear on the table, + // it's better to manually teleport them + public static bool Prefix(ShipStatus __instance, PlayerControl player, int numPlayers, bool initialSpawn) + { + if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true; + + // Lazy doesn't teleport to the meeting table + if (player.Is(CustomRoles.Lazy)) return false; + + Vector2 direction = Vector2.up.Rotate((player.PlayerId - 1) * (360f / numPlayers)); + Vector2 position = __instance.MeetingSpawnCenter + (direction * __instance.SpawnRadius) + new Vector2(0.0f, 0.3636f); + + player.TP(position, true, false); + return false; + } +} + +// From https://github.com/0xDrMoe/TownofHost-Enhanced +[HarmonyPatch(typeof(PolusShipStatus), nameof(PolusShipStatus.SpawnPlayer))] +internal static class PolusShipStatusSpawnPlayerPatch +{ + public static bool Prefix(PolusShipStatus __instance, + [HarmonyArgument(0)] PlayerControl player, + [HarmonyArgument(1)] int numPlayers, + [HarmonyArgument(2)] bool initialSpawn) + { + if (!AmongUsClient.Instance.AmHost || initialSpawn || !player.IsAlive()) return true; + + // Lazy doesn't teleport to the meeting table + if (player.Is(CustomRoles.Lazy)) return false; + + int num1 = Mathf.FloorToInt(numPlayers / 2f); + int num2 = player.PlayerId % 15; + + Vector2 position = num2 >= num1 + ? __instance.MeetingSpawnCenter2 + (Vector2.right * (num2 - num1) * 0.6f) + : __instance.MeetingSpawnCenter + (Vector2.right * num2 * 0.6f); + + player.TP(position, true, false); + return false; + } +} // All below are from: https://github.com/Rabek009/MoreGamemodes/blob/master/Patches/ShipStatusPatch.cs - [HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.PerformVentOp))] - internal static class PerformVentOpPatch +[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.PerformVentOp))] +internal static class PerformVentOpPatch +{ + public static bool Prefix(VentilationSystem __instance, [HarmonyArgument(0)] byte playerId, [HarmonyArgument(1)] VentilationSystem.Operation op, [HarmonyArgument(2)] byte ventId, [HarmonyArgument(3)] SequenceBuffer seqBuffer) { - public static bool Prefix(VentilationSystem __instance, [HarmonyArgument(0)] byte playerId, [HarmonyArgument(1)] VentilationSystem.Operation op, [HarmonyArgument(2)] byte ventId, [HarmonyArgument(3)] SequenceBuffer seqBuffer) + if (!AmongUsClient.Instance.AmHost) return true; + + if (Utils.GetPlayerById(playerId) == null) return true; + + switch (op) { - if (!AmongUsClient.Instance.AmHost) return true; + case VentilationSystem.Operation.Move: + if (!__instance.PlayersInsideVents.ContainsKey(playerId)) + { + seqBuffer.BumpSid(); + return false; + } - if (Utils.GetPlayerById(playerId) == null) return true; + break; + } - switch (op) - { - case VentilationSystem.Operation.Move: - if (!__instance.PlayersInsideVents.ContainsKey(playerId)) - { - seqBuffer.BumpSid(); - return false; - } + return true; + } +} - break; - } +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Serialize))] +internal static class ShipStatusSerializePatch +{ + public static void Prefix(ShipStatus __instance, [HarmonyArgument(1)] bool initialState) + { + if (!AmongUsClient.Instance.AmHost) return; - return true; + if (initialState) return; + + bool cancel = Main.AllPlayerControls.Any(VentilationSystemDeterioratePatch.BlockVentInteraction); + var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); + + if (cancel && ventilationSystem is { IsDirty: true }) + { + Utils.SetAllVentInteractions(); + ventilationSystem.IsDirty = false; } } +} - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Serialize))] - internal static class ShipStatusSerializePatch +[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.Deteriorate))] +internal static class VentilationSystemDeterioratePatch +{ + public static Dictionary LastClosestVent = []; + private static readonly Dictionary LastCanUseVent = []; + private static readonly Dictionary LastClosestVentForUpdate = []; + private static readonly Dictionary CheckBufferTime = []; + + public static void Postfix(VentilationSystem __instance) { - public static void Prefix(ShipStatus __instance, [HarmonyArgument(1)] bool initialState) + if (!AmongUsClient.Instance.AmHost) return; + + if (!GameStates.InGame) return; + + foreach (PlayerControl pc in Main.AllPlayerControls) { - if (!AmongUsClient.Instance.AmHost) return; - - if (initialState) return; - - bool cancel = Main.AllPlayerControls.Any(VentilationSystemDeterioratePatch.BlockVentInteraction); - var ventilationSystem = __instance.Systems[SystemTypes.Ventilation].Cast(); - - if (cancel && ventilationSystem is { IsDirty: true }) + if (BlockVentInteraction(pc)) { - Utils.SetAllVentInteractions(); - ventilationSystem.IsDirty = false; + var players = 0; + + foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) + if (playerInfo != null && !playerInfo.Disconnected) + ++players; + + int closestVentId = pc.GetClosestVent().Id; + if (closestVentId == LastClosestVent[pc.PlayerId] && players >= 3) continue; + + LastClosestVent[pc.PlayerId] = closestVentId; + MessageWriter writer = MessageWriter.Get(); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = ShipStatus.Instance.AllVents.Count(vent => !pc.CanUseVent(vent.Id)); + List AllPlayers = []; + + foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + + int maxVents = Math.Min(vents, AllPlayers.Count); + var blockedVents = 0; + writer.WritePacked(maxVents); + + foreach (Vent vent in pc.GetVentsFromClosest()) + { + if (!pc.CanUseVent(vent.Id)) + { + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; + } + + if (blockedVents >= maxVents) break; + } + + writer.WritePacked(__instance.PlayersInsideVents.Count); + + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); } } } - [HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.Deteriorate))] - internal static class VentilationSystemDeterioratePatch + public static bool BlockVentInteraction(PlayerControl pc) { - public static Dictionary LastClosestVent = []; - private static readonly Dictionary LastCanUseVent = []; - private static readonly Dictionary LastClosestVentForUpdate = []; - private static readonly Dictionary CheckBufferTime = []; + return !pc.AmOwner && (!pc.IsModClient() || (!pc.IsHost() && CustomGameMode.RoomRush.IsActiveOrIntegrated())) && !pc.Data.IsDead && (pc.IsImpostor() || pc.GetRoleTypes() is RoleTypes.Engineer or RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) && ShipStatus.Instance.AllVents.Any(vent => !pc.CanUseVent(vent.Id)); + } - public static void Postfix(VentilationSystem __instance) + public static void SerializeV2(VentilationSystem __instance, PlayerControl player = null) + { + foreach (PlayerControl pc in Main.AllPlayerControls) { - if (!AmongUsClient.Instance.AmHost) return; + if (pc.AmOwner) continue; - if (!GameStates.InGame) return; + if (player != null && pc != player) continue; - foreach (PlayerControl pc in Main.AllPlayerControls) + if (BlockVentInteraction(pc)) { - if (BlockVentInteraction(pc)) + MessageWriter writer = MessageWriter.Get(); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + int vents = ShipStatus.Instance.AllVents.Count(vent => !pc.CanUseVent(vent.Id)); + List AllPlayers = []; + + foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) + if (playerInfo != null && !playerInfo.Disconnected) + AllPlayers.Add(playerInfo); + + int maxVents = Math.Min(vents, AllPlayers.Count); + var blockedVents = 0; + writer.WritePacked(maxVents); + + foreach (Vent vent in pc.GetVentsFromClosest()) { - var players = 0; - - foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) - if (playerInfo != null && !playerInfo.Disconnected) - ++players; - - int closestVentId = pc.GetClosestVent().Id; - if (closestVentId == LastClosestVent[pc.PlayerId] && players >= 3) continue; - - LastClosestVent[pc.PlayerId] = closestVentId; - MessageWriter writer = MessageWriter.Get(); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.GetClientId()); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = ShipStatus.Instance.AllVents.Count(vent => !pc.CanUseVent(vent.Id)); - List AllPlayers = []; - - foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) - if (playerInfo != null && !playerInfo.Disconnected) - AllPlayers.Add(playerInfo); - - int maxVents = Math.Min(vents, AllPlayers.Count); - var blockedVents = 0; - writer.WritePacked(maxVents); - - foreach (Vent vent in pc.GetVentsFromClosest()) + if (!pc.CanUseVent(vent.Id)) { - if (!pc.CanUseVent(vent.Id)) - { - writer.Write(AllPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; - } - - if (blockedVents >= maxVents) break; + writer.Write(AllPlayers[blockedVents].PlayerId); + writer.Write((byte)vent.Id); + ++blockedVents; } - writer.WritePacked(__instance.PlayersInsideVents.Count); - - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) - { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); - } - - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); + if (blockedVents >= maxVents) break; } + + writer.WritePacked(__instance.PlayersInsideVents.Count); + + foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) + { + writer.Write(keyValuePair2.Key); + writer.Write(keyValuePair2.Value); + } + + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); + } + else + { + MessageWriter writer = MessageWriter.Get(); + writer.StartMessage(6); + writer.Write(AmongUsClient.Instance.GameId); + writer.WritePacked(pc.GetClientId()); + writer.StartMessage(1); + writer.WritePacked(ShipStatus.Instance.NetId); + writer.StartMessage((byte)SystemTypes.Ventilation); + __instance.Serialize(writer, false); + writer.EndMessage(); + writer.EndMessage(); + writer.EndMessage(); + AmongUsClient.Instance.SendOrDisconnect(writer); + writer.Recycle(); } } + } - public static bool BlockVentInteraction(PlayerControl pc) + public static void CheckVentInteraction(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance || !ShipStatus.Instance) return; + + const int bufferTimeWait = 10; + + if (!CheckBufferTime.TryGetValue(pc.PlayerId, out int bufferTime)) { - return !pc.AmOwner && (!pc.IsModClient() || (!pc.IsHost() && CustomGameMode.RoomRush.IsActiveOrIntegrated())) && !pc.Data.IsDead && (pc.IsImpostor() || pc.GetRoleTypes() is RoleTypes.Engineer or RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom) && ShipStatus.Instance.AllVents.Any(vent => !pc.CanUseVent(vent.Id)); - } - - public static void SerializeV2(VentilationSystem __instance, PlayerControl player = null) - { - foreach (PlayerControl pc in Main.AllPlayerControls) - { - if (pc.AmOwner) continue; - - if (player != null && pc != player) continue; - - if (BlockVentInteraction(pc)) - { - MessageWriter writer = MessageWriter.Get(); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.GetClientId()); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - int vents = ShipStatus.Instance.AllVents.Count(vent => !pc.CanUseVent(vent.Id)); - List AllPlayers = []; - - foreach (NetworkedPlayerInfo playerInfo in GameData.Instance.AllPlayers) - if (playerInfo != null && !playerInfo.Disconnected) - AllPlayers.Add(playerInfo); - - int maxVents = Math.Min(vents, AllPlayers.Count); - var blockedVents = 0; - writer.WritePacked(maxVents); - - foreach (Vent vent in pc.GetVentsFromClosest()) - { - if (!pc.CanUseVent(vent.Id)) - { - writer.Write(AllPlayers[blockedVents].PlayerId); - writer.Write((byte)vent.Id); - ++blockedVents; - } - - if (blockedVents >= maxVents) break; - } - - writer.WritePacked(__instance.PlayersInsideVents.Count); - - foreach (Il2CppSystem.Collections.Generic.KeyValuePair keyValuePair2 in __instance.PlayersInsideVents) - { - writer.Write(keyValuePair2.Key); - writer.Write(keyValuePair2.Value); - } - - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); - } - else - { - MessageWriter writer = MessageWriter.Get(); - writer.StartMessage(6); - writer.Write(AmongUsClient.Instance.GameId); - writer.WritePacked(pc.GetClientId()); - writer.StartMessage(1); - writer.WritePacked(ShipStatus.Instance.NetId); - writer.StartMessage((byte)SystemTypes.Ventilation); - __instance.Serialize(writer, false); - writer.EndMessage(); - writer.EndMessage(); - writer.EndMessage(); - AmongUsClient.Instance.SendOrDisconnect(writer); - writer.Recycle(); - } - } - } - - public static void CheckVentInteraction(PlayerControl pc) - { - if (!GameStates.IsInTask || ExileController.Instance || !ShipStatus.Instance) return; - - const int bufferTimeWait = 10; - - if (!CheckBufferTime.TryGetValue(pc.PlayerId, out int bufferTime)) - { - CheckBufferTime[pc.PlayerId] = bufferTimeWait; - return; - } - - if (bufferTime > 0) - { - CheckBufferTime[pc.PlayerId]--; - return; - } - CheckBufferTime[pc.PlayerId] = bufferTimeWait; + return; + } + + if (bufferTime > 0) + { + CheckBufferTime[pc.PlayerId]--; + return; + } + + CheckBufferTime[pc.PlayerId] = bufferTimeWait; - int closestVent = pc.GetClosestVent().Id; + int closestVent = pc.GetClosestVent().Id; - if (!LastClosestVentForUpdate.TryGetValue(pc.PlayerId, out int lastClosestVent)) - { - LastClosestVentForUpdate[pc.PlayerId] = closestVent; - return; - } + if (!LastClosestVentForUpdate.TryGetValue(pc.PlayerId, out int lastClosestVent)) + { + LastClosestVentForUpdate[pc.PlayerId] = closestVent; + return; + } - bool canUse = pc.CanUseVent(closestVent); + bool canUse = pc.CanUseVent(closestVent); - if (!LastCanUseVent.TryGetValue(pc.PlayerId, out bool couldUse)) - { - LastCanUseVent[pc.PlayerId] = canUse; - return; - } + if (!LastCanUseVent.TryGetValue(pc.PlayerId, out bool couldUse)) + { + LastCanUseVent[pc.PlayerId] = canUse; + return; + } - if (couldUse != canUse || lastClosestVent != closestVent) - { - LastCanUseVent[pc.PlayerId] = canUse; - pc.RpcSetVentInteraction(); - } + if (couldUse != canUse || lastClosestVent != closestVent) + { + LastCanUseVent[pc.PlayerId] = canUse; + pc.RpcSetVentInteraction(); } } +} - [HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.IsVentCurrentlyBeingCleaned))] - internal static class VentSystemIsVentCurrentlyBeingCleanedPatch +[HarmonyPatch(typeof(VentilationSystem), nameof(VentilationSystem.IsVentCurrentlyBeingCleaned))] +internal static class VentSystemIsVentCurrentlyBeingCleanedPatch +{ + // Patch block use vent for host becouse host always skips RpcSerializeVent + public static bool Prefix([HarmonyArgument(0)] int id, ref bool __result) { - // Patch block use vent for host becouse host always skips RpcSerializeVent - public static bool Prefix([HarmonyArgument(0)] int id, ref bool __result) + if (!AmongUsClient.Instance.AmHost) return true; + + if (!PlayerControl.LocalPlayer.CanUseVent(id)) { - if (!AmongUsClient.Instance.AmHost) return true; - - if (!PlayerControl.LocalPlayer.CanUseVent(id)) - { - __result = true; - return false; - } - - return true; + __result = true; + return false; } + + return true; } } \ No newline at end of file diff --git a/Patches/TaskAdderPatch.cs b/Patches/TaskAdderPatch.cs index 99928dee0..763014698 100644 --- a/Patches/TaskAdderPatch.cs +++ b/Patches/TaskAdderPatch.cs @@ -3,101 +3,100 @@ using AmongUs.GameOptions; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))] +internal static class ShowFolderPatch { - [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))] - internal static class ShowFolderPatch + private static TaskFolder CustomRolesFolder; + + public static void Prefix(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder) { - private static TaskFolder CustomRolesFolder; - - public static void Prefix(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder) + if (__instance.Root == taskFolder && CustomRolesFolder == null) { - if (__instance.Root == taskFolder && CustomRolesFolder == null) - { - TaskFolder rolesFolder = Object.Instantiate( - __instance.RootFolderPrefab, - __instance.transform - ); + TaskFolder rolesFolder = Object.Instantiate( + __instance.RootFolderPrefab, + __instance.transform + ); - rolesFolder.gameObject.SetActive(false); - rolesFolder.FolderName = Main.ModName; - CustomRolesFolder = rolesFolder; - __instance.Root.SubFolders.Add(rolesFolder); - } - } - - public static void Postfix(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder) - { - Logger.Info("Opened " + taskFolder.FolderName, "TaskFolder"); - var xCursor = 0f; - var yCursor = 0f; - var maxHeight = 0f; - - if (CustomRolesFolder != null && CustomRolesFolder.FolderName == taskFolder.FolderName) - { - CustomRoles[] list = Enum.GetValues(); - - foreach (CustomRoles cRoleID in list) - { - TaskAddButton button = Object.Instantiate(__instance.RoleButton); - button.Text.text = Utils.GetRoleName(cRoleID); - __instance.AddFileAsChild(CustomRolesFolder, button, ref xCursor, ref yCursor, ref maxHeight); - - var roleBehaviour = new RoleBehaviour - { - Role = (RoleTypes)cRoleID + 1000 - }; - - button.Role = roleBehaviour; - - Color roleColor = Utils.GetRoleColor(cRoleID); - - button.FileImage.color = roleColor; - button.RolloverHandler.OutColor = roleColor; - button.RolloverHandler.OverColor = new(roleColor.r * 0.5f, roleColor.g * 0.5f, roleColor.b * 0.5f); - } - } + rolesFolder.gameObject.SetActive(false); + rolesFolder.FolderName = Main.ModName; + CustomRolesFolder = rolesFolder; + __instance.Root.SubFolders.Add(rolesFolder); } } - [HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.Update))] - internal class TaskAddButtonUpdatePatch + public static void Postfix(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder) { - public static bool Prefix(TaskAddButton __instance) - { - try - { - if ((int)__instance.Role.Role >= 1000) - { - CustomRoles PlayerCustomRole = PlayerControl.LocalPlayer.GetCustomRole(); - CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; - __instance.Overlay.enabled = PlayerCustomRole == FileCustomRole; - } - } - catch { } + Logger.Info("Opened " + taskFolder.FolderName, "TaskFolder"); + var xCursor = 0f; + var yCursor = 0f; + var maxHeight = 0f; - return true; + if (CustomRolesFolder != null && CustomRolesFolder.FolderName == taskFolder.FolderName) + { + CustomRoles[] list = Enum.GetValues(); + + foreach (CustomRoles cRoleID in list) + { + TaskAddButton button = Object.Instantiate(__instance.RoleButton); + button.Text.text = Utils.GetRoleName(cRoleID); + __instance.AddFileAsChild(CustomRolesFolder, button, ref xCursor, ref yCursor, ref maxHeight); + + var roleBehaviour = new RoleBehaviour + { + Role = (RoleTypes)cRoleID + 1000 + }; + + button.Role = roleBehaviour; + + Color roleColor = Utils.GetRoleColor(cRoleID); + + button.FileImage.color = roleColor; + button.RolloverHandler.OutColor = roleColor; + button.RolloverHandler.OverColor = new(roleColor.r * 0.5f, roleColor.g * 0.5f, roleColor.b * 0.5f); + } } } +} - [HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.AddTask))] - internal static class AddTaskButtonPatch +[HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.Update))] +internal class TaskAddButtonUpdatePatch +{ + public static bool Prefix(TaskAddButton __instance) { - public static bool Prefix(TaskAddButton __instance) + try { - try + if ((int)__instance.Role.Role >= 1000) { - if ((int)__instance.Role.Role >= 1000) - { - CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; - PlayerControl.LocalPlayer.RpcSetCustomRole(FileCustomRole); - PlayerControl.LocalPlayer.RpcSetRole(FileCustomRole.GetRoleTypes(), true); - return false; - } + CustomRoles PlayerCustomRole = PlayerControl.LocalPlayer.GetCustomRole(); + CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; + __instance.Overlay.enabled = PlayerCustomRole == FileCustomRole; } - catch { } - - return true; } + catch { } + + return true; + } +} + +[HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.AddTask))] +internal static class AddTaskButtonPatch +{ + public static bool Prefix(TaskAddButton __instance) + { + try + { + if ((int)__instance.Role.Role >= 1000) + { + CustomRoles FileCustomRole = (CustomRoles)__instance.Role.Role - 1000; + PlayerControl.LocalPlayer.RpcSetCustomRole(FileCustomRole); + PlayerControl.LocalPlayer.RpcSetRole(FileCustomRole.GetRoleTypes(), true); + return false; + } + } + catch { } + + return true; } } \ No newline at end of file diff --git a/Patches/TaskAssignPatch.cs b/Patches/TaskAssignPatch.cs index f1b0a109b..49ea31318 100644 --- a/Patches/TaskAssignPatch.cs +++ b/Patches/TaskAssignPatch.cs @@ -9,277 +9,276 @@ using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Random = UnityEngine.Random; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.AddTasksFromList))] +internal static class AddTasksFromListPatch { - [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.AddTasksFromList))] - internal static class AddTasksFromListPatch + public static Dictionary DisableTasksSettings = []; + + public static void Prefix([HarmonyArgument(4)] Il2CppSystem.Collections.Generic.List unusedTasks) { - public static Dictionary DisableTasksSettings = []; + if (!AmongUsClient.Instance.AmHost) return; - public static void Prefix([HarmonyArgument(4)] Il2CppSystem.Collections.Generic.List unusedTasks) + if (DisableTasksSettings.Count == 0) { - if (!AmongUsClient.Instance.AmHost) return; - - if (DisableTasksSettings.Count == 0) + DisableTasksSettings = new() { - DisableTasksSettings = new() - { - [TaskTypes.SwipeCard] = Options.DisableSwipeCard, - [TaskTypes.SubmitScan] = Options.DisableSubmitScan, - [TaskTypes.UnlockSafe] = Options.DisableUnlockSafe, - [TaskTypes.UploadData] = Options.DisableUploadData, - [TaskTypes.StartReactor] = Options.DisableStartReactor, - [TaskTypes.ResetBreakers] = Options.DisableResetBreaker, - [TaskTypes.VentCleaning] = Options.DisableCleanVent, - [TaskTypes.CalibrateDistributor] = Options.DisableCalibrateDistributor, - [TaskTypes.ChartCourse] = Options.DisableChartCourse, - [TaskTypes.StabilizeSteering] = Options.DisableStabilizeSteering, - [TaskTypes.CleanO2Filter] = Options.DisableCleanO2Filter, - [TaskTypes.UnlockManifolds] = Options.DisableUnlockManifolds, - [TaskTypes.PrimeShields] = Options.DisablePrimeShields, - [TaskTypes.MeasureWeather] = Options.DisableMeasureWeather, - [TaskTypes.BuyBeverage] = Options.DisableBuyBeverage, - [TaskTypes.AssembleArtifact] = Options.DisableAssembleArtifact, - [TaskTypes.SortSamples] = Options.DisableSortSamples, - [TaskTypes.ProcessData] = Options.DisableProcessData, - [TaskTypes.RunDiagnostics] = Options.DisableRunDiagnostics, - [TaskTypes.RepairDrill] = Options.DisableRepairDrill, - [TaskTypes.AlignTelescope] = Options.DisableAlignTelescope, - [TaskTypes.RecordTemperature] = Options.DisableRecordTemperature, - [TaskTypes.FillCanisters] = Options.DisableFillCanisters, - [TaskTypes.MonitorOxygen] = Options.DisableMonitorTree, - [TaskTypes.StoreArtifacts] = Options.DisableStoreArtifacts, - [TaskTypes.PutAwayPistols] = Options.DisablePutAwayPistols, - [TaskTypes.PutAwayRifles] = Options.DisablePutAwayRifles, - [TaskTypes.MakeBurger] = Options.DisableMakeBurger, - [TaskTypes.CleanToilet] = Options.DisableCleanToilet, - [TaskTypes.Decontaminate] = Options.DisableDecontaminate, - [TaskTypes.SortRecords] = Options.DisableSortRecords, - [TaskTypes.FixShower] = Options.DisableFixShower, - [TaskTypes.PickUpTowels] = Options.DisablePickUpTowels, - [TaskTypes.PolishRuby] = Options.DisablePolishRuby, - [TaskTypes.DressMannequin] = Options.DisableDressMannequin, - [TaskTypes.AlignEngineOutput] = Options.DisableAlignEngineOutput, - [TaskTypes.InspectSample] = Options.DisableInspectSample, - [TaskTypes.EmptyChute] = Options.DisableEmptyChute, - [TaskTypes.ClearAsteroids] = Options.DisableClearAsteroids, - [TaskTypes.WaterPlants] = Options.DisableWaterPlants, - [TaskTypes.OpenWaterways] = Options.DisableOpenWaterways, - [TaskTypes.ReplaceWaterJug] = Options.DisableReplaceWaterJug, - [TaskTypes.RebootWifi] = Options.DisableRebootWifi, - [TaskTypes.DevelopPhotos] = Options.DisableDevelopPhotos, - [TaskTypes.RewindTapes] = Options.DisableRewindTapes, - [TaskTypes.StartFans] = Options.DisableStartFans, - [TaskTypes.FixWiring] = Options.DisableFixWiring, - [TaskTypes.EnterIdCode] = Options.DisableEnterIdCode, - [TaskTypes.InsertKeys] = Options.DisableInsertKeys, - [TaskTypes.ScanBoardingPass] = Options.DisableScanBoardingPass, - [TaskTypes.EmptyGarbage] = Options.DisableEmptyGarbage, - [TaskTypes.FuelEngines] = Options.DisableFuelEngines, - [TaskTypes.DivertPower] = Options.DisableDivertPower, - [TaskTypes.FixWeatherNode] = Options.DisableActivateWeatherNodes, - [TaskTypes.RoastMarshmallow] = Options.DisableRoastMarshmallow, - [TaskTypes.CollectSamples] = Options.DisableCollectSamples, - [TaskTypes.ReplaceParts] = Options.DisableReplaceParts, - [TaskTypes.CollectVegetables] = Options.DisableCollectVegetables, - [TaskTypes.MineOres] = Options.DisableMineOres, - [TaskTypes.ExtractFuel] = Options.DisableExtractFuel, - [TaskTypes.CatchFish] = Options.DisableCatchFish, - [TaskTypes.PolishGem] = Options.DisablePolishGem, - [TaskTypes.HelpCritter] = Options.DisableHelpCritter, - [TaskTypes.HoistSupplies] = Options.DisableHoistSupplies, - [TaskTypes.FixAntenna] = Options.DisableFixAntenna, - [TaskTypes.BuildSandcastle] = Options.DisableBuildSandcastle, - [TaskTypes.CrankGenerator] = Options.DisableCrankGenerator, - [TaskTypes.MonitorMushroom] = Options.DisableMonitorMushroom, - [TaskTypes.PlayVideogame] = Options.DisablePlayVideoGame, - [TaskTypes.TuneRadio] = Options.DisableFindSignal, - [TaskTypes.TestFrisbee] = Options.DisableThrowFisbee, - [TaskTypes.LiftWeights] = Options.DisableLiftWeights, - [TaskTypes.CollectShells] = Options.DisableCollectShells - }; - } - - if (!Options.DisableShortTasks.GetBool() && !Options.DisableCommonTasks.GetBool() && !Options.DisableLongTasks.GetBool() && !Options.DisableOtherTasks.GetBool() && CustomGameMode.Standard.IsActiveOrIntegrated()) return; - - List disabledTasks = []; - - foreach (NormalPlayerTask task in unusedTasks) - if ((DisableTasksSettings.TryGetValue(task.TaskType, out OptionItem setting) && setting.GetBool()) || IsTaskAlwaysDisabled(task.TaskType)) - disabledTasks.Add(task); - - foreach (NormalPlayerTask task in disabledTasks) - { - Logger.Msg($"Deleted assigned task: {task.TaskType}", "AddTask"); - unusedTasks.Remove(task); - } - - return; - - bool IsTaskAlwaysDisabled(TaskTypes type) => type switch - { - TaskTypes.FuelEngines => Options.CurrentGameMode is CustomGameMode.MoveAndStop or CustomGameMode.Speedrun or CustomGameMode.AllInOne, - TaskTypes.VentCleaning => CustomGameMode.RoomRush.IsActiveOrIntegrated(), - _ => false + [TaskTypes.SwipeCard] = Options.DisableSwipeCard, + [TaskTypes.SubmitScan] = Options.DisableSubmitScan, + [TaskTypes.UnlockSafe] = Options.DisableUnlockSafe, + [TaskTypes.UploadData] = Options.DisableUploadData, + [TaskTypes.StartReactor] = Options.DisableStartReactor, + [TaskTypes.ResetBreakers] = Options.DisableResetBreaker, + [TaskTypes.VentCleaning] = Options.DisableCleanVent, + [TaskTypes.CalibrateDistributor] = Options.DisableCalibrateDistributor, + [TaskTypes.ChartCourse] = Options.DisableChartCourse, + [TaskTypes.StabilizeSteering] = Options.DisableStabilizeSteering, + [TaskTypes.CleanO2Filter] = Options.DisableCleanO2Filter, + [TaskTypes.UnlockManifolds] = Options.DisableUnlockManifolds, + [TaskTypes.PrimeShields] = Options.DisablePrimeShields, + [TaskTypes.MeasureWeather] = Options.DisableMeasureWeather, + [TaskTypes.BuyBeverage] = Options.DisableBuyBeverage, + [TaskTypes.AssembleArtifact] = Options.DisableAssembleArtifact, + [TaskTypes.SortSamples] = Options.DisableSortSamples, + [TaskTypes.ProcessData] = Options.DisableProcessData, + [TaskTypes.RunDiagnostics] = Options.DisableRunDiagnostics, + [TaskTypes.RepairDrill] = Options.DisableRepairDrill, + [TaskTypes.AlignTelescope] = Options.DisableAlignTelescope, + [TaskTypes.RecordTemperature] = Options.DisableRecordTemperature, + [TaskTypes.FillCanisters] = Options.DisableFillCanisters, + [TaskTypes.MonitorOxygen] = Options.DisableMonitorTree, + [TaskTypes.StoreArtifacts] = Options.DisableStoreArtifacts, + [TaskTypes.PutAwayPistols] = Options.DisablePutAwayPistols, + [TaskTypes.PutAwayRifles] = Options.DisablePutAwayRifles, + [TaskTypes.MakeBurger] = Options.DisableMakeBurger, + [TaskTypes.CleanToilet] = Options.DisableCleanToilet, + [TaskTypes.Decontaminate] = Options.DisableDecontaminate, + [TaskTypes.SortRecords] = Options.DisableSortRecords, + [TaskTypes.FixShower] = Options.DisableFixShower, + [TaskTypes.PickUpTowels] = Options.DisablePickUpTowels, + [TaskTypes.PolishRuby] = Options.DisablePolishRuby, + [TaskTypes.DressMannequin] = Options.DisableDressMannequin, + [TaskTypes.AlignEngineOutput] = Options.DisableAlignEngineOutput, + [TaskTypes.InspectSample] = Options.DisableInspectSample, + [TaskTypes.EmptyChute] = Options.DisableEmptyChute, + [TaskTypes.ClearAsteroids] = Options.DisableClearAsteroids, + [TaskTypes.WaterPlants] = Options.DisableWaterPlants, + [TaskTypes.OpenWaterways] = Options.DisableOpenWaterways, + [TaskTypes.ReplaceWaterJug] = Options.DisableReplaceWaterJug, + [TaskTypes.RebootWifi] = Options.DisableRebootWifi, + [TaskTypes.DevelopPhotos] = Options.DisableDevelopPhotos, + [TaskTypes.RewindTapes] = Options.DisableRewindTapes, + [TaskTypes.StartFans] = Options.DisableStartFans, + [TaskTypes.FixWiring] = Options.DisableFixWiring, + [TaskTypes.EnterIdCode] = Options.DisableEnterIdCode, + [TaskTypes.InsertKeys] = Options.DisableInsertKeys, + [TaskTypes.ScanBoardingPass] = Options.DisableScanBoardingPass, + [TaskTypes.EmptyGarbage] = Options.DisableEmptyGarbage, + [TaskTypes.FuelEngines] = Options.DisableFuelEngines, + [TaskTypes.DivertPower] = Options.DisableDivertPower, + [TaskTypes.FixWeatherNode] = Options.DisableActivateWeatherNodes, + [TaskTypes.RoastMarshmallow] = Options.DisableRoastMarshmallow, + [TaskTypes.CollectSamples] = Options.DisableCollectSamples, + [TaskTypes.ReplaceParts] = Options.DisableReplaceParts, + [TaskTypes.CollectVegetables] = Options.DisableCollectVegetables, + [TaskTypes.MineOres] = Options.DisableMineOres, + [TaskTypes.ExtractFuel] = Options.DisableExtractFuel, + [TaskTypes.CatchFish] = Options.DisableCatchFish, + [TaskTypes.PolishGem] = Options.DisablePolishGem, + [TaskTypes.HelpCritter] = Options.DisableHelpCritter, + [TaskTypes.HoistSupplies] = Options.DisableHoistSupplies, + [TaskTypes.FixAntenna] = Options.DisableFixAntenna, + [TaskTypes.BuildSandcastle] = Options.DisableBuildSandcastle, + [TaskTypes.CrankGenerator] = Options.DisableCrankGenerator, + [TaskTypes.MonitorMushroom] = Options.DisableMonitorMushroom, + [TaskTypes.PlayVideogame] = Options.DisablePlayVideoGame, + [TaskTypes.TuneRadio] = Options.DisableFindSignal, + [TaskTypes.TestFrisbee] = Options.DisableThrowFisbee, + [TaskTypes.LiftWeights] = Options.DisableLiftWeights, + [TaskTypes.CollectShells] = Options.DisableCollectShells }; } - } - [HarmonyPatch(typeof(NetworkedPlayerInfo), nameof(NetworkedPlayerInfo.RpcSetTasks))] - internal static class RpcSetTasksPatch - { - // Patch that overwrites the task just before assigning the task and sending the RPC - // Do not interfere with vanilla task allocation process itself - public static void Prefix(NetworkedPlayerInfo __instance, [HarmonyArgument(0)] ref Il2CppStructArray taskTypeIds) + if (!Options.DisableShortTasks.GetBool() && !Options.DisableCommonTasks.GetBool() && !Options.DisableLongTasks.GetBool() && !Options.DisableOtherTasks.GetBool() && CustomGameMode.Standard.IsActiveOrIntegrated()) return; + + List disabledTasks = []; + + foreach (NormalPlayerTask task in unusedTasks) + if ((DisableTasksSettings.TryGetValue(task.TaskType, out OptionItem setting) && setting.GetBool()) || IsTaskAlwaysDisabled(task.TaskType)) + disabledTasks.Add(task); + + foreach (NormalPlayerTask task in disabledTasks) { - // Null measures - if (Main.RealOptionsData == null) - { - Logger.Warn("Warning: RealOptionsData is null.", "RpcSetTasksPatch"); - return; - } - - PlayerControl pc = __instance.Object; - if (pc == null) return; - - CustomRoles role = GhostRolesManager.AssignedGhostRoles.TryGetValue(pc.PlayerId, out (CustomRoles Role, IGhostRole Instance) gr) && gr.Instance is Specter or Haunter ? gr.Role : pc.GetCustomRole(); - - // Default number of tasks - var hasCommonTasks = true; - int NumLongTasks = Main.NormalOptions.NumLongTasks; - int NumShortTasks = Main.NormalOptions.NumShortTasks; - - if (Options.OverrideTasksData.AllData.TryGetValue(role, out Options.OverrideTasksData data) && data.DoOverride.GetBool()) - { - hasCommonTasks = data.AssignCommonTasks.GetBool(); // Whether to assign common tasks (regular tasks) - // Even if assigned, it will not be reassigned and will be assigned the same common tasks as other crews. - NumLongTasks = data.NumLongTasks.GetInt(); // Number of long tasks to allocate - NumShortTasks = data.NumShortTasks.GetInt(); // Number of short tasks to allocate - // Longs and shorts are constantly reallocated. - if (role is CustomRoles.Specter or CustomRoles.Haunter) Main.PlayerStates[pc.PlayerId].TaskState.AllTasksCount = NumLongTasks + NumShortTasks; - } - - if (pc.Is(CustomRoles.Busy)) - { - NumLongTasks += Options.BusyLongTasks.GetInt(); - NumShortTasks += Options.BusyShortTasks.GetInt(); - } - - // Mad Snitch mission coverage - if (pc.Is(CustomRoles.Snitch) && pc.Is(CustomRoles.Madmate)) - { - hasCommonTasks = false; - NumLongTasks = 0; - NumShortTasks = Options.MadSnitchTasks.GetInt(); - } - - // GM and Lazy Guy have no tasks - if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.Needy) || Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.HotPotato or CustomGameMode.NaturalDisasters) - { - hasCommonTasks = false; - NumShortTasks = 0; - NumLongTasks = 0; - } - - // Workhorse task assignment - if (pc.Is(CustomRoles.Workhorse)) (hasCommonTasks, NumLongTasks, NumShortTasks) = Workhorse.TaskData; - - // Capitalism is going to harm people~ - if (Capitalism.CapitalismAssignTask.ContainsKey(pc.PlayerId)) - { - NumShortTasks += Capitalism.CapitalismAssignTask[pc.PlayerId]; - Capitalism.CapitalismAssignTask.Remove(pc.PlayerId); - } - - if (taskTypeIds.Length == 0) hasCommonTasks = false; // Set common to 0 when redistributing tasks - - switch (hasCommonTasks) - { - case false when NumLongTasks == 0 && NumShortTasks == 0: - NumShortTasks = 1; // Task 0 measures - break; - case true when NumLongTasks == Main.NormalOptions.NumLongTasks && NumShortTasks == Main.NormalOptions.NumShortTasks: - return; // If there are no changes - } - - // List containing IDs of assignable tasks - // Clone of the second argument of the original RpcSetTasks - Il2CppSystem.Collections.Generic.List TasksList = new(); - foreach (byte num in taskTypeIds) TasksList.Add(num); - - // Reference: ShipStatus.Begin - // Processing to delete unnecessary assigned tasks - // If the setting is to assign common tasks, delete all other than common tasks - // Empty the list if no common tasks are assigned - int defaultCommonTasksNum = Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); - - if (hasCommonTasks) - TasksList.RemoveRange(defaultCommonTasksNum, TasksList.Count - defaultCommonTasksNum); - else - TasksList.Clear(); - - // HashSet where assigned tasks will be placed - // Prevent multiple assignments of the same task - Il2CppSystem.Collections.Generic.HashSet usedTaskTypes = new(); - var start2 = 0; - var start3 = 0; - - // List of assignable long tasks - Il2CppSystem.Collections.Generic.List LongTasks = new(); - foreach (NormalPlayerTask task in ShipStatus.Instance.LongTasks) LongTasks.Add(task); - - Shuffle(LongTasks); - - // List of assignable short tasks - Il2CppSystem.Collections.Generic.List ShortTasks = new(); - foreach (NormalPlayerTask task in ShipStatus.Instance.ShortTasks) ShortTasks.Add(task); - - Shuffle(ShortTasks); - - // Use the task assignment function actually used on the Among Us side. - ShipStatus.Instance.AddTasksFromList( - ref start2, - NumLongTasks, - TasksList, - usedTaskTypes, - LongTasks - ); - - ShipStatus.Instance.AddTasksFromList( - ref start3, - NumShortTasks, - TasksList, - usedTaskTypes, - ShortTasks - ); - - // Convert list of tasks to array (Il2CppStructArray) - taskTypeIds = new(TasksList.Count); - for (var i = 0; i < TasksList.Count; i++) taskTypeIds[i] = TasksList[i]; - - - Il2CppSystem.Text.StringBuilder sb = new(); - - foreach (TaskTypes taskType in usedTaskTypes) - { - GetTaskFromTaskType(taskType).AppendTaskText(sb); - } - - Logger.Info($" Assigned tasks:\n{sb.Replace("\r\n", "\n")}", pc.GetRealName(), multiLine: true); - return; - - PlayerTask GetTaskFromTaskType(TaskTypes type) => ShortTasks.ToArray().Concat(LongTasks.ToArray()).FirstOrDefault(t => t.TaskType == type); + Logger.Msg($"Deleted assigned task: {task.TaskType}", "AddTask"); + unusedTasks.Remove(task); } - private static void Shuffle(Il2CppSystem.Collections.Generic.List list) + return; + + bool IsTaskAlwaysDisabled(TaskTypes type) => type switch { - for (var i = 0; i < list.Count - 1; i++) - { - T obj = list[i]; - int rand = Random.Range(i, list.Count); - list[i] = list[rand]; - list[rand] = obj; - } + TaskTypes.FuelEngines => Options.CurrentGameMode is CustomGameMode.MoveAndStop or CustomGameMode.Speedrun or CustomGameMode.AllInOne, + TaskTypes.VentCleaning => CustomGameMode.RoomRush.IsActiveOrIntegrated(), + _ => false + }; + } +} + +[HarmonyPatch(typeof(NetworkedPlayerInfo), nameof(NetworkedPlayerInfo.RpcSetTasks))] +internal static class RpcSetTasksPatch +{ + // Patch that overwrites the task just before assigning the task and sending the RPC + // Do not interfere with vanilla task allocation process itself + public static void Prefix(NetworkedPlayerInfo __instance, [HarmonyArgument(0)] ref Il2CppStructArray taskTypeIds) + { + // Null measures + if (Main.RealOptionsData == null) + { + Logger.Warn("Warning: RealOptionsData is null.", "RpcSetTasksPatch"); + return; + } + + PlayerControl pc = __instance.Object; + if (pc == null) return; + + CustomRoles role = GhostRolesManager.AssignedGhostRoles.TryGetValue(pc.PlayerId, out (CustomRoles Role, IGhostRole Instance) gr) && gr.Instance is Specter or Haunter ? gr.Role : pc.GetCustomRole(); + + // Default number of tasks + var hasCommonTasks = true; + int NumLongTasks = Main.NormalOptions.NumLongTasks; + int NumShortTasks = Main.NormalOptions.NumShortTasks; + + if (Options.OverrideTasksData.AllData.TryGetValue(role, out Options.OverrideTasksData data) && data.DoOverride.GetBool()) + { + hasCommonTasks = data.AssignCommonTasks.GetBool(); // Whether to assign common tasks (regular tasks) + // Even if assigned, it will not be reassigned and will be assigned the same common tasks as other crews. + NumLongTasks = data.NumLongTasks.GetInt(); // Number of long tasks to allocate + NumShortTasks = data.NumShortTasks.GetInt(); // Number of short tasks to allocate + // Longs and shorts are constantly reallocated. + if (role is CustomRoles.Specter or CustomRoles.Haunter) Main.PlayerStates[pc.PlayerId].TaskState.AllTasksCount = NumLongTasks + NumShortTasks; + } + + if (pc.Is(CustomRoles.Busy)) + { + NumLongTasks += Options.BusyLongTasks.GetInt(); + NumShortTasks += Options.BusyShortTasks.GetInt(); + } + + // Mad Snitch mission coverage + if (pc.Is(CustomRoles.Snitch) && pc.Is(CustomRoles.Madmate)) + { + hasCommonTasks = false; + NumLongTasks = 0; + NumShortTasks = Options.MadSnitchTasks.GetInt(); + } + + // GM and Lazy Guy have no tasks + if (pc.Is(CustomRoles.GM) || pc.Is(CustomRoles.Needy) || Options.CurrentGameMode is CustomGameMode.SoloKombat or CustomGameMode.FFA or CustomGameMode.HotPotato or CustomGameMode.NaturalDisasters) + { + hasCommonTasks = false; + NumShortTasks = 0; + NumLongTasks = 0; + } + + // Workhorse task assignment + if (pc.Is(CustomRoles.Workhorse)) (hasCommonTasks, NumLongTasks, NumShortTasks) = Workhorse.TaskData; + + // Capitalism is going to harm people~ + if (Capitalism.CapitalismAssignTask.ContainsKey(pc.PlayerId)) + { + NumShortTasks += Capitalism.CapitalismAssignTask[pc.PlayerId]; + Capitalism.CapitalismAssignTask.Remove(pc.PlayerId); + } + + if (taskTypeIds.Length == 0) hasCommonTasks = false; // Set common to 0 when redistributing tasks + + switch (hasCommonTasks) + { + case false when NumLongTasks == 0 && NumShortTasks == 0: + NumShortTasks = 1; // Task 0 measures + break; + case true when NumLongTasks == Main.NormalOptions.NumLongTasks && NumShortTasks == Main.NormalOptions.NumShortTasks: + return; // If there are no changes + } + + // List containing IDs of assignable tasks + // Clone of the second argument of the original RpcSetTasks + Il2CppSystem.Collections.Generic.List TasksList = new(); + foreach (byte num in taskTypeIds) TasksList.Add(num); + + // Reference: ShipStatus.Begin + // Processing to delete unnecessary assigned tasks + // If the setting is to assign common tasks, delete all other than common tasks + // Empty the list if no common tasks are assigned + int defaultCommonTasksNum = Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); + + if (hasCommonTasks) + TasksList.RemoveRange(defaultCommonTasksNum, TasksList.Count - defaultCommonTasksNum); + else + TasksList.Clear(); + + // HashSet where assigned tasks will be placed + // Prevent multiple assignments of the same task + Il2CppSystem.Collections.Generic.HashSet usedTaskTypes = new(); + var start2 = 0; + var start3 = 0; + + // List of assignable long tasks + Il2CppSystem.Collections.Generic.List LongTasks = new(); + foreach (NormalPlayerTask task in ShipStatus.Instance.LongTasks) LongTasks.Add(task); + + Shuffle(LongTasks); + + // List of assignable short tasks + Il2CppSystem.Collections.Generic.List ShortTasks = new(); + foreach (NormalPlayerTask task in ShipStatus.Instance.ShortTasks) ShortTasks.Add(task); + + Shuffle(ShortTasks); + + // Use the task assignment function actually used on the Among Us side. + ShipStatus.Instance.AddTasksFromList( + ref start2, + NumLongTasks, + TasksList, + usedTaskTypes, + LongTasks + ); + + ShipStatus.Instance.AddTasksFromList( + ref start3, + NumShortTasks, + TasksList, + usedTaskTypes, + ShortTasks + ); + + // Convert list of tasks to array (Il2CppStructArray) + taskTypeIds = new(TasksList.Count); + for (var i = 0; i < TasksList.Count; i++) taskTypeIds[i] = TasksList[i]; + + + Il2CppSystem.Text.StringBuilder sb = new(); + + foreach (TaskTypes taskType in usedTaskTypes) + { + GetTaskFromTaskType(taskType).AppendTaskText(sb); + } + + Logger.Info($" Assigned tasks:\n{sb.Replace("\r\n", "\n")}", pc.GetRealName(), multiLine: true); + return; + + PlayerTask GetTaskFromTaskType(TaskTypes type) => ShortTasks.ToArray().Concat(LongTasks.ToArray()).FirstOrDefault(t => t.TaskType == type); + } + + private static void Shuffle(Il2CppSystem.Collections.Generic.List list) + { + for (var i = 0; i < list.Count - 1; i++) + { + T obj = list[i]; + int rand = Random.Range(i, list.Count); + list[i] = list[rand]; + list[rand] = obj; } } } \ No newline at end of file diff --git a/Patches/TextBoxPatch.cs b/Patches/TextBoxPatch.cs index 0310c9cd7..a93882626 100644 --- a/Patches/TextBoxPatch.cs +++ b/Patches/TextBoxPatch.cs @@ -4,261 +4,260 @@ using HarmonyLib; using TMPro; using UnityEngine; -namespace EHR.Patches +namespace EHR.Patches; + +[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.IsCharAllowed))] +static class TextBoxTMPIsCharAllowed { - [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.IsCharAllowed))] - static class TextBoxTMPIsCharAllowed + public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] char i, ref bool __result) { - public static bool Prefix(TextBoxTMP __instance, [HarmonyArgument(0)] char i, ref bool __result) - { - if (!__instance.gameObject.HasParentInHierarchy("ChatScreenRoot/ChatScreenContainer")) return true; - __result = i != '\b'; - return false; - } + if (!__instance.gameObject.HasParentInHierarchy("ChatScreenRoot/ChatScreenContainer")) return true; + __result = i != '\b'; + return false; } +} - [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] - internal static class TextBoxTMPSetTextPatch +[HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))] +internal static class TextBoxTMPSetTextPatch +{ + private static TextMeshPro PlaceHolderText; + private static TextMeshPro CommandInfoText; + + public static bool IsInvalidCommand; + + public static void Postfix(TextBoxTMP __instance) { - private static TextMeshPro PlaceHolderText; - private static TextMeshPro CommandInfoText; - - public static bool IsInvalidCommand; - - public static void Postfix(TextBoxTMP __instance) - { - try - { - if (!__instance.gameObject.HasParentInHierarchy("ChatScreenRoot/ChatScreenContainer")) return; - - if (!Main.EnableCommandHelper.Value) - { - Destroy(); - return; - } - - string input = __instance.outputText.text.Trim().Replace("\b", ""); - - if (!input.StartsWith('/') || input.Length < 2) - { - Destroy(); - IsInvalidCommand = false; - return; - } - - Command command = null; - double highestMatchRate = 0; - string inputCheck = input.Split(' ')[0]; - var exactMatch = false; - bool english = TranslationController.Instance.currentLanguage.languageID == SupportedLangs.English; - - foreach (Command cmd in ChatCommands.AllCommands) - { - foreach (string form in cmd.CommandForms) - { - if (english && !form.All(char.IsAscii)) continue; - - string check = "/" + form; - if (check.Length < inputCheck.Length) continue; - - if (check == inputCheck) - { - highestMatchRate = 1; - command = cmd; - exactMatch = true; - break; - } - - var matchNum = 0; - - for (var i = 0; i < inputCheck.Length; i++) - { - if (i >= check.Length) break; - - if (inputCheck[i].Equals(check[i])) - matchNum++; - else - break; - } - - double matchRate = (double)matchNum / inputCheck.Length; - - if (matchRate > highestMatchRate) - { - highestMatchRate = matchRate; - command = cmd; - } - } - - if (exactMatch) break; - } - - if (command == null || highestMatchRate < 1) - { - Destroy(); - IsInvalidCommand = true; - __instance.compoText.Color(Color.red); - __instance.outputText.color = Color.red; - return; - } - - IsInvalidCommand = false; - - if (PlaceHolderText == null) - { - PlaceHolderText = Object.Instantiate(__instance.outputText, __instance.outputText.transform.parent); - PlaceHolderText.name = "PlaceHolderText"; - PlaceHolderText.color = new(0.7f, 0.7f, 0.7f, 0.7f); - PlaceHolderText.transform.localPosition = __instance.outputText.transform.localPosition; - } - - if (CommandInfoText == null) - { - HudManager hud = DestroyableSingleton.Instance; - CommandInfoText = Object.Instantiate(hud.KillButton.cooldownTimerText, hud.transform, true); - CommandInfoText.name = "CommandInfoText"; - CommandInfoText.alignment = TextAlignmentOptions.Left; - CommandInfoText.verticalAlignment = VerticalAlignmentOptions.Top; - CommandInfoText.transform.localPosition = new(-3.2f, -2.35f, 0f); - CommandInfoText.overflowMode = TextOverflowModes.Overflow; - CommandInfoText.enableWordWrapping = false; - CommandInfoText.color = Color.white; - CommandInfoText.fontSize = CommandInfoText.fontSizeMax = CommandInfoText.fontSizeMin = 1.8f; - CommandInfoText.sortingOrder = 1000; - CommandInfoText.transform.SetAsLastSibling(); - } - - string inputForm = input.TrimStart('/'); - string text = "/" + (exactMatch ? inputForm : command.CommandForms.Where(x => x.All(char.IsAscii) && x.StartsWith(inputForm)).MaxBy(x => x.Length)); - var info = $"{command.Description}"; - - if (exactMatch && command.Arguments.Length > 0) - { - bool poll = command.CommandForms.Contains("poll"); - bool say = command.CommandForms.Contains("say"); - int spaces = poll ? input.SkipWhile(x => x != '?').Count(x => x == ' ') + 1 : input.Count(x => x == ' '); - if (say) spaces = Math.Min(spaces, 1); - - var preText = $"{text} {command.Arguments}"; - if (!poll) text += " " + command.Arguments.Split(' ').Skip(spaces).Join(delimiter: " "); - - string[] args = preText.Split(' ')[1..]; - - for (var i = 0; i < args.Length; i++) - { - if (command.ArgsDescriptions.Length <= i) break; - - int skip = poll ? input.TakeWhile(x => x != '?').Count(x => x == ' ') - 1 : 0; - string arg = poll ? i == 0 ? args[..++skip].Join(delimiter: " ") : args[spaces - 1 < i ? skip + i + spaces : skip + i] : args[spaces > i ? i : i + spaces]; - bool current = spaces - 1 == i, invalid = IsInvalidArg(), valid = IsValidArg(); - - info += "\n" + (invalid, current, valid) switch - { - (true, true, false) => "<#ffa500>\u27a1 ", - (true, false, false) => "<#ff0000> ", - (false, true, true) => "<#00ffa5>\u27a1 \u2713 ", - (false, false, true) => "<#00ffa5>\u2713 <#00ffff> ", - (false, true, false) => "<#ffff44>\u27a1 ", - _ => " " - }; - - info += $" - {arg}{GetExtraArgInfo()}: {command.ArgsDescriptions[i]}"; - if (current || invalid || valid) info += ""; - - continue; - - bool IsInvalidArg() - { - return arg != command.Arguments.Split(' ')[i] && command.Arguments.Split(' ')[i] switch - { - "{id}" or "{id1}" or "{id2}" => !byte.TryParse(arg, out byte id) || Main.AllPlayerControls.All(x => x.PlayerId != id), - "{number}" or "{level}" or "{duration}" or "{number1}" or "{number2}" => !int.TryParse(arg, out int num) || num < 0, - "{team}" => arg is not "crew" and not "imp", - "{role}" => !ChatCommands.GetRoleByName(arg, out _), - "{addon}" => !ChatCommands.GetRoleByName(arg, out CustomRoles role) || !role.IsAdditionRole(), - "{letter}" => arg.Length != 1 || !char.IsLetter(arg[0]), - "{chance}" => !int.TryParse(arg, out int chance) || chance < 0 || chance > 100 || chance % 5 != 0, - _ => false - }; - } - - bool IsValidArg() - { - return command.Arguments.Split(' ')[i].Replace('[', '{').Replace(']', '}') switch - { - "{id}" or "{id1}" or "{id2}" => byte.TryParse(arg, out byte id) && Main.AllPlayerControls.Any(x => x.PlayerId == id), - "{team}" => arg is "crew" or "imp", - "{role}" => ChatCommands.GetRoleByName(arg, out _), - "{addon}" => ChatCommands.GetRoleByName(arg, out CustomRoles role) && role.IsAdditionRole(), - "{chance}" => int.TryParse(arg, out int chance) && chance is >= 0 and <= 100 && chance % 5 == 0, - _ => false - }; - } - - string GetExtraArgInfo() - { - return !IsValidArg() - ? string.Empty - : command.Arguments.Split(' ')[i] switch - { - "{id}" or "{id1}" or "{id2}" => $" ({byte.Parse(arg).ColoredPlayerName()})", - "{role}" or "{addon}" when ChatCommands.GetRoleByName(arg, out CustomRoles role) => $" ({role.ToColoredString()})", - _ => string.Empty - }; - } - } - } - - PlaceHolderText.text = text; - CommandInfoText.text = info; - - PlaceHolderText.enabled = true; - CommandInfoText.enabled = true; - } - catch - { - Destroy(); - } - - return; - - void Destroy() - { - if (PlaceHolderText != null) PlaceHolderText.enabled = false; - if (CommandInfoText != null) CommandInfoText.enabled = false; - } - } - - public static void OnTabPress(ChatController __instance) - { - if (PlaceHolderText == null || PlaceHolderText.text == "") return; - - __instance.freeChatField.textArea.SetText(PlaceHolderText.text); - __instance.freeChatField.textArea.compoText = ""; - } - - public static void Update() - { - try - { - bool open = HudManager.Instance?.Chat?.IsOpenOrOpening ?? false; - PlaceHolderText?.gameObject.SetActive(open); - CommandInfoText?.gameObject.SetActive(open); - } - catch { } - } - } - - // Originally by KARPED1EM. Reference: https://github.com/KARPED1EM/TownOfNext/blob/TONX/TONX/Patches/TextBoxPatch.cs - [HarmonyPatch(typeof(TextBoxTMP))] - public class TextBoxPatch - { - [HarmonyPatch(nameof(TextBoxTMP.SetText))] - [HarmonyPrefix] - public static void ModifyCharacterLimit(TextBoxTMP __instance) + try { if (!__instance.gameObject.HasParentInHierarchy("ChatScreenRoot/ChatScreenContainer")) return; - __instance.characterLimit = 1200; + + if (!Main.EnableCommandHelper.Value) + { + Destroy(); + return; + } + + string input = __instance.outputText.text.Trim().Replace("\b", ""); + + if (!input.StartsWith('/') || input.Length < 2) + { + Destroy(); + IsInvalidCommand = false; + return; + } + + Command command = null; + double highestMatchRate = 0; + string inputCheck = input.Split(' ')[0]; + var exactMatch = false; + bool english = TranslationController.Instance.currentLanguage.languageID == SupportedLangs.English; + + foreach (Command cmd in ChatCommands.AllCommands) + { + foreach (string form in cmd.CommandForms) + { + if (english && !form.All(char.IsAscii)) continue; + + string check = "/" + form; + if (check.Length < inputCheck.Length) continue; + + if (check == inputCheck) + { + highestMatchRate = 1; + command = cmd; + exactMatch = true; + break; + } + + var matchNum = 0; + + for (var i = 0; i < inputCheck.Length; i++) + { + if (i >= check.Length) break; + + if (inputCheck[i].Equals(check[i])) + matchNum++; + else + break; + } + + double matchRate = (double)matchNum / inputCheck.Length; + + if (matchRate > highestMatchRate) + { + highestMatchRate = matchRate; + command = cmd; + } + } + + if (exactMatch) break; + } + + if (command == null || highestMatchRate < 1) + { + Destroy(); + IsInvalidCommand = true; + __instance.compoText.Color(Color.red); + __instance.outputText.color = Color.red; + return; + } + + IsInvalidCommand = false; + + if (PlaceHolderText == null) + { + PlaceHolderText = Object.Instantiate(__instance.outputText, __instance.outputText.transform.parent); + PlaceHolderText.name = "PlaceHolderText"; + PlaceHolderText.color = new(0.7f, 0.7f, 0.7f, 0.7f); + PlaceHolderText.transform.localPosition = __instance.outputText.transform.localPosition; + } + + if (CommandInfoText == null) + { + HudManager hud = DestroyableSingleton.Instance; + CommandInfoText = Object.Instantiate(hud.KillButton.cooldownTimerText, hud.transform, true); + CommandInfoText.name = "CommandInfoText"; + CommandInfoText.alignment = TextAlignmentOptions.Left; + CommandInfoText.verticalAlignment = VerticalAlignmentOptions.Top; + CommandInfoText.transform.localPosition = new(-3.2f, -2.35f, 0f); + CommandInfoText.overflowMode = TextOverflowModes.Overflow; + CommandInfoText.enableWordWrapping = false; + CommandInfoText.color = Color.white; + CommandInfoText.fontSize = CommandInfoText.fontSizeMax = CommandInfoText.fontSizeMin = 1.8f; + CommandInfoText.sortingOrder = 1000; + CommandInfoText.transform.SetAsLastSibling(); + } + + string inputForm = input.TrimStart('/'); + string text = "/" + (exactMatch ? inputForm : command.CommandForms.Where(x => x.All(char.IsAscii) && x.StartsWith(inputForm)).MaxBy(x => x.Length)); + var info = $"{command.Description}"; + + if (exactMatch && command.Arguments.Length > 0) + { + bool poll = command.CommandForms.Contains("poll"); + bool say = command.CommandForms.Contains("say"); + int spaces = poll ? input.SkipWhile(x => x != '?').Count(x => x == ' ') + 1 : input.Count(x => x == ' '); + if (say) spaces = Math.Min(spaces, 1); + + var preText = $"{text} {command.Arguments}"; + if (!poll) text += " " + command.Arguments.Split(' ').Skip(spaces).Join(delimiter: " "); + + string[] args = preText.Split(' ')[1..]; + + for (var i = 0; i < args.Length; i++) + { + if (command.ArgsDescriptions.Length <= i) break; + + int skip = poll ? input.TakeWhile(x => x != '?').Count(x => x == ' ') - 1 : 0; + string arg = poll ? i == 0 ? args[..++skip].Join(delimiter: " ") : args[spaces - 1 < i ? skip + i + spaces : skip + i] : args[spaces > i ? i : i + spaces]; + bool current = spaces - 1 == i, invalid = IsInvalidArg(), valid = IsValidArg(); + + info += "\n" + (invalid, current, valid) switch + { + (true, true, false) => "<#ffa500>\u27a1 ", + (true, false, false) => "<#ff0000> ", + (false, true, true) => "<#00ffa5>\u27a1 \u2713 ", + (false, false, true) => "<#00ffa5>\u2713 <#00ffff> ", + (false, true, false) => "<#ffff44>\u27a1 ", + _ => " " + }; + + info += $" - {arg}{GetExtraArgInfo()}: {command.ArgsDescriptions[i]}"; + if (current || invalid || valid) info += ""; + + continue; + + bool IsInvalidArg() + { + return arg != command.Arguments.Split(' ')[i] && command.Arguments.Split(' ')[i] switch + { + "{id}" or "{id1}" or "{id2}" => !byte.TryParse(arg, out byte id) || Main.AllPlayerControls.All(x => x.PlayerId != id), + "{number}" or "{level}" or "{duration}" or "{number1}" or "{number2}" => !int.TryParse(arg, out int num) || num < 0, + "{team}" => arg is not "crew" and not "imp", + "{role}" => !ChatCommands.GetRoleByName(arg, out _), + "{addon}" => !ChatCommands.GetRoleByName(arg, out CustomRoles role) || !role.IsAdditionRole(), + "{letter}" => arg.Length != 1 || !char.IsLetter(arg[0]), + "{chance}" => !int.TryParse(arg, out int chance) || chance < 0 || chance > 100 || chance % 5 != 0, + _ => false + }; + } + + bool IsValidArg() + { + return command.Arguments.Split(' ')[i].Replace('[', '{').Replace(']', '}') switch + { + "{id}" or "{id1}" or "{id2}" => byte.TryParse(arg, out byte id) && Main.AllPlayerControls.Any(x => x.PlayerId == id), + "{team}" => arg is "crew" or "imp", + "{role}" => ChatCommands.GetRoleByName(arg, out _), + "{addon}" => ChatCommands.GetRoleByName(arg, out CustomRoles role) && role.IsAdditionRole(), + "{chance}" => int.TryParse(arg, out int chance) && chance is >= 0 and <= 100 && chance % 5 == 0, + _ => false + }; + } + + string GetExtraArgInfo() + { + return !IsValidArg() + ? string.Empty + : command.Arguments.Split(' ')[i] switch + { + "{id}" or "{id1}" or "{id2}" => $" ({byte.Parse(arg).ColoredPlayerName()})", + "{role}" or "{addon}" when ChatCommands.GetRoleByName(arg, out CustomRoles role) => $" ({role.ToColoredString()})", + _ => string.Empty + }; + } + } + } + + PlaceHolderText.text = text; + CommandInfoText.text = info; + + PlaceHolderText.enabled = true; + CommandInfoText.enabled = true; + } + catch + { + Destroy(); + } + + return; + + void Destroy() + { + if (PlaceHolderText != null) PlaceHolderText.enabled = false; + if (CommandInfoText != null) CommandInfoText.enabled = false; } } + + public static void OnTabPress(ChatController __instance) + { + if (PlaceHolderText == null || PlaceHolderText.text == "") return; + + __instance.freeChatField.textArea.SetText(PlaceHolderText.text); + __instance.freeChatField.textArea.compoText = ""; + } + + public static void Update() + { + try + { + bool open = HudManager.Instance?.Chat?.IsOpenOrOpening ?? false; + PlaceHolderText?.gameObject.SetActive(open); + CommandInfoText?.gameObject.SetActive(open); + } + catch { } + } +} + +// Originally by KARPED1EM. Reference: https://github.com/KARPED1EM/TownOfNext/blob/TONX/TONX/Patches/TextBoxPatch.cs +[HarmonyPatch(typeof(TextBoxTMP))] +public class TextBoxPatch +{ + [HarmonyPatch(nameof(TextBoxTMP.SetText))] + [HarmonyPrefix] + public static void ModifyCharacterLimit(TextBoxTMP __instance) + { + if (!__instance.gameObject.HasParentInHierarchy("ChatScreenRoot/ChatScreenContainer")) return; + __instance.characterLimit = 1200; + } } \ No newline at end of file diff --git a/Patches/UsablesPatch.cs b/Patches/UsablesPatch.cs index ef5e4cfe6..c5b89f13d 100644 --- a/Patches/UsablesPatch.cs +++ b/Patches/UsablesPatch.cs @@ -2,109 +2,108 @@ using AmongUs.GameOptions; using HarmonyLib; using UnityEngine; -namespace EHR +namespace EHR; + +[HarmonyPatch(typeof(Console), nameof(Console.CanUse))] +internal static class CanUsePatch { - [HarmonyPatch(typeof(Console), nameof(Console.CanUse))] - internal static class CanUsePatch + public static bool Prefix( /*ref float __result,*/ Console __instance, /*[HarmonyArgument(0)] NetworkedPlayerInfo pc,*/ [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) { - public static bool Prefix( /*ref float __result,*/ Console __instance, /*[HarmonyArgument(0)] NetworkedPlayerInfo pc,*/ [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) - { - canUse = couldUse = false; - // Even if you return this with false, usable items other than tasks will remain usable (buttons, etc.) - if (Main.GM.Value && AmongUsClient.Instance.AmHost && GameStates.InGame) return false; + canUse = couldUse = false; + // Even if you return this with false, usable items other than tasks will remain usable (buttons, etc.) + if (Main.GM.Value && AmongUsClient.Instance.AmHost && GameStates.InGame) return false; - PlayerControl lp = PlayerControl.LocalPlayer; + PlayerControl lp = PlayerControl.LocalPlayer; - if (Options.CurrentGameMode == CustomGameMode.AllInOne && !AllInOneGameMode.Taskers.Contains(lp.PlayerId)) return false; + if (Options.CurrentGameMode == CustomGameMode.AllInOne && !AllInOneGameMode.Taskers.Contains(lp.PlayerId)) return false; - return __instance.AllowImpostor || (Utils.HasTasks(lp.Data, false) && (!lp.Is(CustomRoles.Wizard) || HasTasksAsWizard())); + return __instance.AllowImpostor || (Utils.HasTasks(lp.Data, false) && (!lp.Is(CustomRoles.Wizard) || HasTasksAsWizard())); - bool HasTasksAsWizard() - { - if (lp.GetTaskState().IsTaskFinished) return false; - if (!lp.IsAlive()) return true; - return lp.GetAbilityUseLimit() < 1f; - } + bool HasTasksAsWizard() + { + if (lp.GetTaskState().IsTaskFinished) return false; + if (!lp.IsAlive()) return true; + return lp.GetAbilityUseLimit() < 1f; } } +} - [HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))] - internal static class EmergencyMinigamePatch +[HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))] +internal static class EmergencyMinigamePatch +{ + public static void Postfix(EmergencyMinigame __instance) { - public static void Postfix(EmergencyMinigame __instance) - { - if (Options.DisableMeeting.GetBool() || !CustomGameMode.Standard.IsActiveOrIntegrated()) __instance.Close(); - } + if (Options.DisableMeeting.GetBool() || !CustomGameMode.Standard.IsActiveOrIntegrated()) __instance.Close(); } +} - [HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))] - internal static class CanUseVentPatch +[HarmonyPatch(typeof(Vent), nameof(Vent.CanUse))] +internal static class CanUseVentPatch +{ + public static bool Prefix(Vent __instance, + [HarmonyArgument(0)] NetworkedPlayerInfo pc, + [HarmonyArgument(1)] ref bool canUse, + [HarmonyArgument(2)] ref bool couldUse, + ref float __result) { - public static bool Prefix(Vent __instance, - [HarmonyArgument(0)] NetworkedPlayerInfo pc, - [HarmonyArgument(1)] ref bool canUse, - [HarmonyArgument(2)] ref bool couldUse, - ref float __result) + PlayerControl playerControl = pc.Object; + + // First half, Mod-specific processing + + // Determine if vent is available based on custom role + // always true for engineer-based roles + couldUse = playerControl.CanUseImpostorVentButton() || (pc.Role.Role == RoleTypes.Engineer && pc.Role.CanUse(__instance.Cast())); + + canUse = couldUse; + // Not available if custom roles are not available + if (!canUse) return false; + + // Mod's own processing up to this point + // Replace vanilla processing from here + + var usableVent = __instance.Cast(); + // Distance between vent and player + var actualDistance = float.MaxValue; + + couldUse = + // true for classic and for vanilla HnS + GameManager.Instance.LogicUsables.CanUse(usableVent, playerControl) && + // CanUse(usableVent) && Ignore because the decision is based on custom role, not vanilla role + // there is no vent task in the target vent, or you are in the target vent now + (!playerControl.MustCleanVent(__instance.Id) || (playerControl.inVent && Vent.currentVent == __instance)) && + playerControl.IsAlive() && + (playerControl.CanMove || playerControl.inVent); + + // Check vent cleaning + if (ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Ventilation, out ISystemType systemType)) { - PlayerControl playerControl = pc.Object; - - // First half, Mod-specific processing - - // Determine if vent is available based on custom role - // always true for engineer-based roles - couldUse = playerControl.CanUseImpostorVentButton() || (pc.Role.Role == RoleTypes.Engineer && pc.Role.CanUse(__instance.Cast())); - - canUse = couldUse; - // Not available if custom roles are not available - if (!canUse) return false; - - // Mod's own processing up to this point - // Replace vanilla processing from here - - var usableVent = __instance.Cast(); - // Distance between vent and player - var actualDistance = float.MaxValue; - - couldUse = - // true for classic and for vanilla HnS - GameManager.Instance.LogicUsables.CanUse(usableVent, playerControl) && - // CanUse(usableVent) && Ignore because the decision is based on custom role, not vanilla role - // there is no vent task in the target vent, or you are in the target vent now - (!playerControl.MustCleanVent(__instance.Id) || (playerControl.inVent && Vent.currentVent == __instance)) && - playerControl.IsAlive() && - (playerControl.CanMove || playerControl.inVent); - - // Check vent cleaning - if (ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Ventilation, out ISystemType systemType)) - { - var ventilationSystem = systemType.TryCast(); - // If someone is cleaning a vent, you can't get into that vent - if (ventilationSystem != null && ventilationSystem.IsVentCurrentlyBeingCleaned(__instance.Id)) couldUse = false; - } - - canUse = couldUse; - - if (canUse) - { - Vector3 center = playerControl.Collider.bounds.center; - Vector3 ventPosition = __instance.transform.position; - actualDistance = Vector2.Distance(center, ventPosition); - canUse &= actualDistance <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(playerControl.Collider, center, ventPosition, Constants.ShipOnlyMask, false); - } - - __result = actualDistance; - return false; + var ventilationSystem = systemType.TryCast(); + // If someone is cleaning a vent, you can't get into that vent + if (ventilationSystem != null && ventilationSystem.IsVentCurrentlyBeingCleaned(__instance.Id)) couldUse = false; } + + canUse = couldUse; + + if (canUse) + { + Vector3 center = playerControl.Collider.bounds.center; + Vector3 ventPosition = __instance.transform.position; + actualDistance = Vector2.Distance(center, ventPosition); + canUse &= actualDistance <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(playerControl.Collider, center, ventPosition, Constants.ShipOnlyMask, false); + } + + __result = actualDistance; + return false; } +} - [HarmonyPatch(typeof(PlayerPurchasesData), nameof(PlayerPurchasesData.GetPurchase))] - public static class PlayerPurchasesDataPatch +[HarmonyPatch(typeof(PlayerPurchasesData), nameof(PlayerPurchasesData.GetPurchase))] +public static class PlayerPurchasesDataPatch +{ + public static bool Prefix(ref bool __result) { - public static bool Prefix(ref bool __result) - { - if (RunLoginPatch.ClickCount < 20) return true; - // __result = true; - // return false; - } + if (RunLoginPatch.ClickCount < 20) return true; + // __result = true; + // return false; } } \ No newline at end of file diff --git a/Patches/VoteBanSystemPatch.cs b/Patches/VoteBanSystemPatch.cs index 263e4e517..4bda0c8c7 100644 --- a/Patches/VoteBanSystemPatch.cs +++ b/Patches/VoteBanSystemPatch.cs @@ -1,22 +1,21 @@ using HarmonyLib; -namespace EHR.Patches -{ - [HarmonyPatch(typeof(VoteBanSystem), nameof(VoteBanSystem.AddVote))] - internal class VoteBanSystemPatch - { - public static bool Prefix() - { - return !AmongUsClient.Instance.AmHost || !Options.DisableVoteBan.GetBool(); - } - } +namespace EHR.Patches; - [HarmonyPatch(typeof(VoteBanSystem), nameof(VoteBanSystem.CmdAddVote))] - internal class VoteBanSystemPatchCmd +[HarmonyPatch(typeof(VoteBanSystem), nameof(VoteBanSystem.AddVote))] +internal class VoteBanSystemPatch +{ + public static bool Prefix() { - public static bool Prefix() - { - return !AmongUsClient.Instance.AmHost || !Options.DisableVoteBan.GetBool(); - } + return !AmongUsClient.Instance.AmHost || !Options.DisableVoteBan.GetBool(); + } +} + +[HarmonyPatch(typeof(VoteBanSystem), nameof(VoteBanSystem.CmdAddVote))] +internal class VoteBanSystemPatchCmd +{ + public static bool Prefix() + { + return !AmongUsClient.Instance.AmHost || !Options.DisableVoteBan.GetBool(); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Allergic.cs b/Roles/AddOns/Common/Allergic.cs index 8d9693087..56fd5de66 100644 --- a/Roles/AddOns/Common/Allergic.cs +++ b/Roles/AddOns/Common/Allergic.cs @@ -3,106 +3,105 @@ using System.Collections.Generic; using EHR.Modules; using Hazel; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Allergic : IAddon { - public class Allergic : IAddon + private static OptionItem Time; + private static OptionItem Range; + + private static Dictionary AllergicPlayers = []; + private static Dictionary AllergyMaxTS = []; + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - private static OptionItem Time; - private static OptionItem Range; + Options.SetupAdtRoleOptions(645941, CustomRoles.Allergic, canSetNum: true, teamSpawnOptions: true); - private static Dictionary AllergicPlayers = []; - private static Dictionary AllergyMaxTS = []; - public AddonTypes Type => AddonTypes.Harmful; + Time = new IntegerOptionItem(645948, "Allergic.Time", new(0, 60, 1), 15, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Allergic]) + .SetValueFormat(OptionFormat.Seconds); - public void SetupCustomOption() + Range = new FloatOptionItem(645949, "Allergic.Range", new(0.1f, 10f, 0.1f), 1.5f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Allergic]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public static void Init() + { + AllergicPlayers = []; + AllergyMaxTS = []; + + LateTask.New(() => { - Options.SetupAdtRoleOptions(645941, CustomRoles.Allergic, canSetNum: true, teamSpawnOptions: true); + PlayerControl[] aapc = Main.AllAlivePlayerControls; - Time = new IntegerOptionItem(645948, "Allergic.Time", new(0, 60, 1), 15, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Allergic]) - .SetValueFormat(OptionFormat.Seconds); - - Range = new FloatOptionItem(645949, "Allergic.Range", new(0.1f, 10f, 0.1f), 1.5f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Allergic]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public static void Init() - { - AllergicPlayers = []; - AllergyMaxTS = []; - - LateTask.New(() => + foreach (PlayerControl pc in aapc) { - PlayerControl[] aapc = Main.AllAlivePlayerControls; - - foreach (PlayerControl pc in aapc) + if (pc.Is(CustomRoles.Allergic)) { - if (pc.Is(CustomRoles.Allergic)) - { - PlayerControl target = aapc.Without(pc).RandomElement(); - AllergicPlayers[pc.PlayerId] = target.PlayerId; - } + PlayerControl target = aapc.Without(pc).RandomElement(); + AllergicPlayers[pc.PlayerId] = target.PlayerId; } - }, 20f, "Allergic.Init"); - } + } + }, 20f, "Allergic.Init"); + } - public static void OnFixedUpdate(PlayerControl pc) + public static void OnFixedUpdate(PlayerControl pc) + { + if (Main.HasJustStarted || ExileController.Instance) return; + + if (!AllergicPlayers.TryGetValue(pc.PlayerId, out byte targetId)) return; + + PlayerControl target = targetId.GetPlayer(); + + if (target == null || !target.IsAlive() || Vector2.Distance(pc.Pos(), target.Pos()) > Range.GetFloat()) { - if (Main.HasJustStarted || ExileController.Instance) return; - - if (!AllergicPlayers.TryGetValue(pc.PlayerId, out byte targetId)) return; - - PlayerControl target = targetId.GetPlayer(); - - if (target == null || !target.IsAlive() || Vector2.Distance(pc.Pos(), target.Pos()) > Range.GetFloat()) + if (AllergyMaxTS.Remove(pc.PlayerId)) { - if (AllergyMaxTS.Remove(pc.PlayerId)) - { - Utils.NotifyRoles(SpecifyTarget: pc, SpecifySeer: pc); - Utils.SendRPC(CustomRPC.SyncAllergic, 1, pc.PlayerId); - } - - return; - } - - if (!AllergyMaxTS.TryGetValue(pc.PlayerId, out long endTS)) - { - endTS = Utils.TimeStamp + Time.GetInt(); - AllergyMaxTS[pc.PlayerId] = endTS; - Utils.SendRPC(CustomRPC.SyncAllergic, 2, pc.PlayerId, endTS); - return; - } - - if (Utils.TimeStamp >= endTS) - { - AllergicPlayers.Remove(pc.PlayerId); - AllergyMaxTS.Remove(pc.PlayerId); - pc.Suicide(PlayerState.DeathReason.Allergy, target); - } - else Utils.NotifyRoles(SpecifyTarget: pc, SpecifySeer: pc); - } - - public static void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - AllergyMaxTS.Remove(reader.ReadByte()); - break; - case 2: - AllergyMaxTS[reader.ReadByte()] = long.Parse(reader.ReadString()); - break; + Utils.SendRPC(CustomRPC.SyncAllergic, 1, pc.PlayerId); } + + return; } - public static string GetSelfSuffix(PlayerControl seer) + if (!AllergyMaxTS.TryGetValue(pc.PlayerId, out long endTS)) { - if (!seer.IsAlive() || !AllergyMaxTS.TryGetValue(seer.PlayerId, out long endTS)) return string.Empty; - long now = Utils.TimeStamp; - float percentage = (float)(endTS - now) / Time.GetInt(); - return string.Format(Translator.GetString("Allergic.Suffix"), 100 - (int)Math.Round(percentage * 100f)); + endTS = Utils.TimeStamp + Time.GetInt(); + AllergyMaxTS[pc.PlayerId] = endTS; + Utils.SendRPC(CustomRPC.SyncAllergic, 2, pc.PlayerId, endTS); + return; + } + + if (Utils.TimeStamp >= endTS) + { + AllergicPlayers.Remove(pc.PlayerId); + AllergyMaxTS.Remove(pc.PlayerId); + pc.Suicide(PlayerState.DeathReason.Allergy, target); + } + else + Utils.NotifyRoles(SpecifyTarget: pc, SpecifySeer: pc); + } + + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + AllergyMaxTS.Remove(reader.ReadByte()); + break; + case 2: + AllergyMaxTS[reader.ReadByte()] = long.Parse(reader.ReadString()); + break; } } + + public static string GetSelfSuffix(PlayerControl seer) + { + if (!seer.IsAlive() || !AllergyMaxTS.TryGetValue(seer.PlayerId, out long endTS)) return string.Empty; + long now = Utils.TimeStamp; + float percentage = (float)(endTS - now) / Time.GetInt(); + return string.Format(Translator.GetString("Allergic.Suffix"), 100 - (int)Math.Round(percentage * 100f)); + } } \ No newline at end of file diff --git a/Roles/AddOns/Common/AntiTP.cs b/Roles/AddOns/Common/AntiTP.cs index 021774448..203e08984 100644 --- a/Roles/AddOns/Common/AntiTP.cs +++ b/Roles/AddOns/Common/AntiTP.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - public class AntiTP : IAddon - { - public AddonTypes Type => AddonTypes.Mixed; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(16893, CustomRoles.AntiTP, canSetNum: true, teamSpawnOptions: true); - } +public class AntiTP : IAddon +{ + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(16893, CustomRoles.AntiTP, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Antidote.cs b/Roles/AddOns/Common/Antidote.cs index e1dd1c72b..63dd4866d 100644 --- a/Roles/AddOns/Common/Antidote.cs +++ b/Roles/AddOns/Common/Antidote.cs @@ -1,22 +1,21 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Antidote : IAddon { - internal class Antidote : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + const int id = 648500; + SetupAdtRoleOptions(id, CustomRoles.Antidote, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - const int id = 648500; - SetupAdtRoleOptions(id, CustomRoles.Antidote, canSetNum: true, teamSpawnOptions: true); + AntidoteCDOpt = new FloatOptionItem(id + 8, "AntidoteCDOpt", new(0f, 180f, 1f), 5f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]) + .SetValueFormat(OptionFormat.Seconds); - AntidoteCDOpt = new FloatOptionItem(id + 8, "AntidoteCDOpt", new(0f, 180f, 1f), 5f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]) - .SetValueFormat(OptionFormat.Seconds); - - AntidoteCDReset = new BooleanOptionItem(id + 7, "AntidoteCDReset", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); - } + AntidoteCDReset = new BooleanOptionItem(id + 7, "AntidoteCDReset", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Antidote]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Asthmatic.cs b/Roles/AddOns/Common/Asthmatic.cs index a78c4ae73..b9141016a 100644 --- a/Roles/AddOns/Common/Asthmatic.cs +++ b/Roles/AddOns/Common/Asthmatic.cs @@ -3,140 +3,139 @@ using System.Linq; using UnityEngine; using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Asthmatic : IAddon { - internal class Asthmatic : IAddon + private static readonly Dictionary Timers = []; + private static readonly Dictionary LastSuffix = []; + private static readonly Dictionary LastPosition = []; + private static int MinRedTime; + private static int MaxRedTime; + private static int MinGreenTime; + private static int MaxGreenTime; + + public static bool RunChecks = true; + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - private static readonly Dictionary Timers = []; - private static readonly Dictionary LastSuffix = []; - private static readonly Dictionary LastPosition = []; - private static int MinRedTime; - private static int MaxRedTime; - private static int MinGreenTime; - private static int MaxGreenTime; + SetupAdtRoleOptions(15419, CustomRoles.Asthmatic, canSetNum: true, teamSpawnOptions: true); - public static bool RunChecks = true; - public AddonTypes Type => AddonTypes.Harmful; + AsthmaticMinRedTime = new IntegerOptionItem(15426, "AsthmaticMinRedTime", new(1, 90, 1), 5, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) + .SetValueFormat(OptionFormat.Seconds); - public void SetupCustomOption() + AsthmaticMaxRedTime = new IntegerOptionItem(15427, "AsthmaticMaxRedTime", new(1, 90, 1), 15, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) + .SetValueFormat(OptionFormat.Seconds); + + AsthmaticMinGreenTime = new IntegerOptionItem(15428, "AsthmaticMinGreenTime", new(1, 90, 1), 5, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) + .SetValueFormat(OptionFormat.Seconds); + + AsthmaticMaxGreenTime = new IntegerOptionItem(15429, "AsthmaticMaxGreenTime", new(1, 90, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) + .SetValueFormat(OptionFormat.Seconds); + } + + private static int RandomRedTime(char _) => IRandom.Instance.Next(MinRedTime, MaxRedTime); + private static int RandomGreenTime(char _) => IRandom.Instance.Next(MinGreenTime, MaxGreenTime); + + public static void Init() + { + Timers.Clear(); + LastSuffix.Clear(); + LastPosition.Clear(); + + MinRedTime = AsthmaticMinRedTime.GetInt(); + MaxRedTime = AsthmaticMaxRedTime.GetInt(); + MinGreenTime = AsthmaticMinGreenTime.GetInt(); + MaxGreenTime = AsthmaticMaxGreenTime.GetInt(); + } + + public static void Add() + { + LateTask.New(() => { - SetupAdtRoleOptions(15419, CustomRoles.Asthmatic, canSetNum: true, teamSpawnOptions: true); + var r = IRandom.Instance; + long now = Utils.TimeStamp; - AsthmaticMinRedTime = new IntegerOptionItem(15426, "AsthmaticMinRedTime", new(1, 90, 1), 5, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) - .SetValueFormat(OptionFormat.Seconds); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc.Is(CustomRoles.Asthmatic)) + Timers[pc.PlayerId] = new(30, r.Next(MinRedTime, MaxRedTime), now, '●', false, RandomRedTime, RandomGreenTime); + }, 8f, "Add Asthmatic Timers"); + } - AsthmaticMaxRedTime = new IntegerOptionItem(15427, "AsthmaticMaxRedTime", new(1, 90, 1), 15, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) - .SetValueFormat(OptionFormat.Seconds); - - AsthmaticMinGreenTime = new IntegerOptionItem(15428, "AsthmaticMinGreenTime", new(1, 90, 1), 5, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) - .SetValueFormat(OptionFormat.Seconds); - - AsthmaticMaxGreenTime = new IntegerOptionItem(15429, "AsthmaticMaxGreenTime", new(1, 90, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Asthmatic]) - .SetValueFormat(OptionFormat.Seconds); - } - - private static int RandomRedTime(char _) => IRandom.Instance.Next(MinRedTime, MaxRedTime); - private static int RandomGreenTime(char _) => IRandom.Instance.Next(MinGreenTime, MaxGreenTime); - - public static void Init() + public static void OnFixedUpdate() + { + foreach (KeyValuePair kvp in Timers.ToArray()) { - Timers.Clear(); - LastSuffix.Clear(); - LastPosition.Clear(); + PlayerState state = Main.PlayerStates[kvp.Key]; - MinRedTime = AsthmaticMinRedTime.GetInt(); - MaxRedTime = AsthmaticMaxRedTime.GetInt(); - MinGreenTime = AsthmaticMinGreenTime.GetInt(); - MaxGreenTime = AsthmaticMaxGreenTime.GetInt(); - } - - public static void Add() - { - LateTask.New(() => + if (state.IsDead || !state.SubRoles.Contains(CustomRoles.Asthmatic)) { - var r = IRandom.Instance; - long now = Utils.TimeStamp; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (pc.Is(CustomRoles.Asthmatic)) - Timers[pc.PlayerId] = new(30, r.Next(MinRedTime, MaxRedTime), now, '●', false, RandomRedTime, RandomGreenTime); - }, 8f, "Add Asthmatic Timers"); - } - - public static void OnFixedUpdate() - { - foreach (KeyValuePair kvp in Timers.ToArray()) - { - PlayerState state = Main.PlayerStates[kvp.Key]; - - if (state.IsDead || !state.SubRoles.Contains(CustomRoles.Asthmatic)) - { - state.RemoveSubRole(CustomRoles.Asthmatic); - Timers.Remove(kvp.Key); - LastSuffix.Remove(kvp.Key); - LastPosition.Remove(kvp.Key); - continue; - } - - kvp.Value.Update(); - } - } - - public static void OnCheckPlayerPosition(PlayerControl pc) - { - if (!pc.Is(CustomRoles.Asthmatic) || ExileController.Instance || !RunChecks || !Timers.TryGetValue(pc.PlayerId, out Counter counter)) return; - - Vector2 currentPosition = pc.transform.position; - - if (!LastPosition.TryGetValue(pc.PlayerId, out Vector2 previousPosition)) - { - LastPosition[pc.PlayerId] = currentPosition; - return; + state.RemoveSubRole(CustomRoles.Asthmatic); + Timers.Remove(kvp.Key); + LastSuffix.Remove(kvp.Key); + LastPosition.Remove(kvp.Key); + continue; } - currentPosition.x += previousPosition.x * Time.deltaTime; - currentPosition.y += previousPosition.y * Time.deltaTime; - - Vector2 direction = currentPosition - previousPosition; - - direction.Normalize(); - - float distanceX = currentPosition.x - previousPosition.x; - float distanceY = currentPosition.y - previousPosition.y; - - const float limit = 2f; - - if (direction.y is > 0 or < 0 || direction.x is > 0 or < 0) - { - switch (counter.IsRed) - { - case true when distanceY > limit || distanceY < -limit || distanceX > limit || distanceX < -limit: - pc.Suicide(PlayerState.DeathReason.Asthma); - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Asthmatic); - Timers.Remove(pc.PlayerId); - LastSuffix.Remove(pc.PlayerId); - LastPosition.Remove(pc.PlayerId); - return; - case false: - LastPosition[pc.PlayerId] = currentPosition; - break; - } - } - - string suffix = GetSuffixText(pc.PlayerId); - - if (!pc.IsModClient() && (!LastSuffix.TryGetValue(pc.PlayerId, out string beforeSuffix) || beforeSuffix != suffix)) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - LastSuffix[pc.PlayerId] = suffix; - } - - public static string GetSuffixText(byte id) - { - return id.GetPlayer().IsAlive() && Timers.TryGetValue(id, out Counter counter) ? $"{counter.ColoredArrow} {counter.ColoredTimerString}" : string.Empty; + kvp.Value.Update(); } } + + public static void OnCheckPlayerPosition(PlayerControl pc) + { + if (!pc.Is(CustomRoles.Asthmatic) || ExileController.Instance || !RunChecks || !Timers.TryGetValue(pc.PlayerId, out Counter counter)) return; + + Vector2 currentPosition = pc.transform.position; + + if (!LastPosition.TryGetValue(pc.PlayerId, out Vector2 previousPosition)) + { + LastPosition[pc.PlayerId] = currentPosition; + return; + } + + currentPosition.x += previousPosition.x * Time.deltaTime; + currentPosition.y += previousPosition.y * Time.deltaTime; + + Vector2 direction = currentPosition - previousPosition; + + direction.Normalize(); + + float distanceX = currentPosition.x - previousPosition.x; + float distanceY = currentPosition.y - previousPosition.y; + + const float limit = 2f; + + if (direction.y is > 0 or < 0 || direction.x is > 0 or < 0) + { + switch (counter.IsRed) + { + case true when distanceY > limit || distanceY < -limit || distanceX > limit || distanceX < -limit: + pc.Suicide(PlayerState.DeathReason.Asthma); + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Asthmatic); + Timers.Remove(pc.PlayerId); + LastSuffix.Remove(pc.PlayerId); + LastPosition.Remove(pc.PlayerId); + return; + case false: + LastPosition[pc.PlayerId] = currentPosition; + break; + } + } + + string suffix = GetSuffixText(pc.PlayerId); + + if (!pc.IsModClient() && (!LastSuffix.TryGetValue(pc.PlayerId, out string beforeSuffix) || beforeSuffix != suffix)) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + LastSuffix[pc.PlayerId] = suffix; + } + + public static string GetSuffixText(byte id) + { + return id.GetPlayer().IsAlive() && Timers.TryGetValue(id, out Counter counter) ? $"{counter.ColoredArrow} {counter.ColoredTimerString}" : string.Empty; + } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Autopsy.cs b/Roles/AddOns/Common/Autopsy.cs index 7e1c83957..6cfbd4a8d 100644 --- a/Roles/AddOns/Common/Autopsy.cs +++ b/Roles/AddOns/Common/Autopsy.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Autopsy : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(13600, CustomRoles.Autopsy, canSetNum: true, teamSpawnOptions: true); - } +internal class Autopsy : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(13600, CustomRoles.Autopsy, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Avanger.cs b/Roles/AddOns/Common/Avanger.cs index 06cefa744..8a620bb6d 100644 --- a/Roles/AddOns/Common/Avanger.cs +++ b/Roles/AddOns/Common/Avanger.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class Avanger : IAddon - { - public AddonTypes Type => AddonTypes.Harmful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(15100, CustomRoles.Avanger, canSetNum: true, teamSpawnOptions: true); - } +internal class Avanger : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(15100, CustomRoles.Avanger, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Bait.cs b/Roles/AddOns/Common/Bait.cs index d1fc212c4..c5ddf5ca4 100644 --- a/Roles/AddOns/Common/Bait.cs +++ b/Roles/AddOns/Common/Bait.cs @@ -1,31 +1,30 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Bait : IAddon { - internal class Bait : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(13700, CustomRoles.Bait, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(13700, CustomRoles.Bait, canSetNum: true, teamSpawnOptions: true); + BaitDelayMin = new FloatOptionItem(13713, "BaitDelayMin", new(0f, 90f, 1f), 0f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]) + .SetValueFormat(OptionFormat.Seconds); - BaitDelayMin = new FloatOptionItem(13713, "BaitDelayMin", new(0f, 90f, 1f), 0f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]) - .SetValueFormat(OptionFormat.Seconds); + BaitDelayMax = new FloatOptionItem(13714, "BaitDelayMax", new(0f, 90f, 1f), 0f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]) + .SetValueFormat(OptionFormat.Seconds); - BaitDelayMax = new FloatOptionItem(13714, "BaitDelayMax", new(0f, 90f, 1f), 0f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]) - .SetValueFormat(OptionFormat.Seconds); + BaitDelayNotify = new BooleanOptionItem(13715, "BaitDelayNotify", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); - BaitDelayNotify = new BooleanOptionItem(13715, "BaitDelayNotify", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); + BaitNotification = new BooleanOptionItem(13716, "BaitNotification", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); - BaitNotification = new BooleanOptionItem(13716, "BaitNotification", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); - - ReportBaitAtAllCost = new BooleanOptionItem(13717, "ReportBaitAtAllCost", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); - } + ReportBaitAtAllCost = new BooleanOptionItem(13717, "ReportBaitAtAllCost", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bait]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Bewilder.cs b/Roles/AddOns/Common/Bewilder.cs index eb6e6f487..b5c47ca98 100644 --- a/Roles/AddOns/Common/Bewilder.cs +++ b/Roles/AddOns/Common/Bewilder.cs @@ -1,18 +1,17 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Bewilder : IAddon { - internal class Bewilder : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(15200, CustomRoles.Bewilder, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15200, CustomRoles.Bewilder, canSetNum: true, teamSpawnOptions: true); - - BewilderVision = new FloatOptionItem(15210, "BewilderVision", new(0f, 5f, 0.05f), 0.6f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]) - .SetValueFormat(OptionFormat.Multiplier); - } + BewilderVision = new FloatOptionItem(15210, "BewilderVision", new(0f, 5f, 0.05f), 0.6f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bewilder]) + .SetValueFormat(OptionFormat.Multiplier); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Brakar.cs b/Roles/AddOns/Common/Brakar.cs index 55602c851..a6c4d22ed 100644 --- a/Roles/AddOns/Common/Brakar.cs +++ b/Roles/AddOns/Common/Brakar.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Brakar : IAddon - { - public AddonTypes Type => AddonTypes.Mixed; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(14900, CustomRoles.Brakar, canSetNum: true, teamSpawnOptions: true); - } +internal class Brakar : IAddon +{ + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(14900, CustomRoles.Brakar, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Busy.cs b/Roles/AddOns/Common/Busy.cs index 6356285ee..542a369aa 100644 --- a/Roles/AddOns/Common/Busy.cs +++ b/Roles/AddOns/Common/Busy.cs @@ -1,20 +1,19 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Busy : IAddon { - internal class Busy : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(15290, CustomRoles.Busy, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15290, CustomRoles.Busy, canSetNum: true, teamSpawnOptions: true); + BusyLongTasks = new IntegerOptionItem(15298, "BusyLongTasks", new(0, 90, 1), 1, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Busy]); - BusyLongTasks = new IntegerOptionItem(15298, "BusyLongTasks", new(0, 90, 1), 1, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Busy]); - - BusyShortTasks = new IntegerOptionItem(15297, "BusyShortTasks", new(0, 90, 1), 1, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Busy]); - } + BusyShortTasks = new IntegerOptionItem(15297, "BusyShortTasks", new(0, 90, 1), 1, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Busy]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Clumsy.cs b/Roles/AddOns/Common/Clumsy.cs index 779fcab8d..3593b7ade 100644 --- a/Roles/AddOns/Common/Clumsy.cs +++ b/Roles/AddOns/Common/Clumsy.cs @@ -1,29 +1,28 @@ -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Clumsy : IAddon { - internal class Clumsy : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + Options.SetupAdtRoleOptions(15170, CustomRoles.Clumsy, canSetNum: true, teamSpawnOptions: true); + } - public void SetupCustomOption() + public static void OnFixedUpdate(PlayerControl pc) + { + if (IRandom.Instance.Next(300) == 0 && !pc.inVent && !pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() && !pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() && !pc.inMovingPlat && pc.MyPhysics.Animations.IsPlayingRunAnimation()) { - Options.SetupAdtRoleOptions(15170, CustomRoles.Clumsy, canSetNum: true, teamSpawnOptions: true); - } + float duration = IRandom.Instance.Next(1, 4); + float speed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.MarkDirtySettings(); - public static void OnFixedUpdate(PlayerControl pc) - { - if (IRandom.Instance.Next(300) == 0 && !pc.inVent && !pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() && !pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() && !pc.inMovingPlat && pc.MyPhysics.Animations.IsPlayingRunAnimation()) + LateTask.New(() => { - float duration = IRandom.Instance.Next(1, 4); - float speed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + Main.AllPlayerSpeed[pc.PlayerId] = speed; pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[pc.PlayerId] = speed; - pc.MarkDirtySettings(); - }, duration, "Clumsy Trip"); - } + }, duration, "Clumsy Trip"); } } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Deadlined.cs b/Roles/AddOns/Common/Deadlined.cs index e22007b1e..93cf29b94 100644 --- a/Roles/AddOns/Common/Deadlined.cs +++ b/Roles/AddOns/Common/Deadlined.cs @@ -1,66 +1,65 @@ using System.Collections.Generic; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Deadlined : IAddon { - public class Deadlined : IAddon + private static HashSet DidTask = []; + private static long MeetingEndTS; + private static OptionItem InactiveTime; + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - private static HashSet DidTask = []; - private static long MeetingEndTS; - private static OptionItem InactiveTime; - public AddonTypes Type => AddonTypes.Harmful; + Options.SetupAdtRoleOptions(649292, CustomRoles.Deadlined, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() + InactiveTime = new IntegerOptionItem(649299, "Deadlined.InactiveTime", new(0, 60, 1), 15, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deadlined]) + .SetValueFormat(OptionFormat.Seconds); + } + + public static void SetDone(PlayerControl pc) + { + DidTask.Add(pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public static void AfterMeetingTasks() + { + DidTask = []; + MeetingEndTS = Utils.TimeStamp; + + foreach (PlayerControl pc in Main.AllPlayerControls) { - Options.SetupAdtRoleOptions(649292, CustomRoles.Deadlined, canSetNum: true, teamSpawnOptions: true); - - InactiveTime = new IntegerOptionItem(649299, "Deadlined.InactiveTime", new(0, 60, 1), 15, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Deadlined]) - .SetValueFormat(OptionFormat.Seconds); - } - - public static void SetDone(PlayerControl pc) - { - DidTask.Add(pc.PlayerId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public static void AfterMeetingTasks() - { - DidTask = []; - MeetingEndTS = Utils.TimeStamp; - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - TaskState ts = pc.GetTaskState(); - if (pc.Is(CustomRoles.Deadlined) && (!pc.IsAlive() || ts.IsTaskFinished || (!ts.HasTasks && !pc.CanUseKillButton()))) Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Deadlined); - } - } - - public static void OnMeetingStart() - { - if (MeetingStates.FirstMeeting) return; - - if (MeetingEndTS + InactiveTime.GetInt() > Utils.TimeStamp) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!pc.Is(CustomRoles.Deadlined)) continue; - - if (!DidTask.Contains(pc.PlayerId)) pc.Suicide(); - } - } - - public static string GetSuffix(PlayerControl seer, bool hud = false) - { - if (!seer.Is(CustomRoles.Deadlined) || (seer.IsModClient() && !hud)) return string.Empty; - - if (DidTask.Contains(seer.PlayerId) || MeetingStates.FirstMeeting) return "<#00ff00>\u2713"; - - long now = Utils.TimeStamp; - - return MeetingEndTS + InactiveTime.GetInt() <= now - ? Translator.GetString("Deadlined.MustDoTask") - : string.Format(Translator.GetString("Deadlined.SafeTime"), InactiveTime.GetInt() - (now - MeetingEndTS)); + TaskState ts = pc.GetTaskState(); + if (pc.Is(CustomRoles.Deadlined) && (!pc.IsAlive() || ts.IsTaskFinished || (!ts.HasTasks && !pc.CanUseKillButton()))) Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Deadlined); } } + + public static void OnMeetingStart() + { + if (MeetingStates.FirstMeeting) return; + + if (MeetingEndTS + InactiveTime.GetInt() > Utils.TimeStamp) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (!pc.Is(CustomRoles.Deadlined)) continue; + + if (!DidTask.Contains(pc.PlayerId)) pc.Suicide(); + } + } + + public static string GetSuffix(PlayerControl seer, bool hud = false) + { + if (!seer.Is(CustomRoles.Deadlined) || (seer.IsModClient() && !hud)) return string.Empty; + + if (DidTask.Contains(seer.PlayerId) || MeetingStates.FirstMeeting) return "<#00ff00>\u2713"; + + long now = Utils.TimeStamp; + + return MeetingEndTS + InactiveTime.GetInt() <= now + ? Translator.GetString("Deadlined.MustDoTask") + : string.Format(Translator.GetString("Deadlined.SafeTime"), InactiveTime.GetInt() - (now - MeetingEndTS)); + } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Disco.cs b/Roles/AddOns/Common/Disco.cs index 4461fb4c2..44a338a7a 100644 --- a/Roles/AddOns/Common/Disco.cs +++ b/Roles/AddOns/Common/Disco.cs @@ -2,54 +2,53 @@ using EHR.Impostor; using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Disco : IAddon { - internal class Disco : IAddon + private static readonly Dictionary LastChange = []; + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - private static readonly Dictionary LastChange = []; - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(15430, CustomRoles.Disco, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() + DiscoChangeInterval = new IntegerOptionItem(15437, "DiscoChangeInterval", new(1, 90, 1), 5, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Disco]) + .SetValueFormat(OptionFormat.Seconds); + } + + public static void ChangeColor(PlayerControl pc) + { + int colorId = IRandom.Instance.Next(0, 18); + + pc.SetColor(colorId); + + try { - SetupAdtRoleOptions(15430, CustomRoles.Disco, canSetNum: true, teamSpawnOptions: true); - - DiscoChangeInterval = new IntegerOptionItem(15437, "DiscoChangeInterval", new(1, 90, 1), 5, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Disco]) - .SetValueFormat(OptionFormat.Seconds); + pc.RpcSetColor((byte)colorId); } - - public static void ChangeColor(PlayerControl pc) + catch { - int colorId = IRandom.Instance.Next(0, 18); + var sender = CustomRpcSender.Create($"Disco.ChangeColor({pc.Data.PlayerName})"); - pc.SetColor(colorId); + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) + .Write(pc.Data.NetId) + .Write((byte)colorId) + .EndRpc(); - try - { - pc.RpcSetColor((byte)colorId); - } - catch - { - var sender = CustomRpcSender.Create($"Disco.ChangeColor({pc.Data.PlayerName})"); - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) - .Write(pc.Data.NetId) - .Write((byte)colorId) - .EndRpc(); - - sender.SendMessage(); - } - } - - public static void OnFixedUpdate(PlayerControl pc) - { - if (!pc.Is(CustomRoles.Disco) || !GameStates.IsInTask || pc.IsShifted() || Camouflage.IsCamouflage || pc.inVent || pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() || pc.walkingToVent || pc.onLadder || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || pc.inMovingPlat) return; - - long now = Utils.TimeStamp; - if (LastChange.TryGetValue(pc.PlayerId, out long change) && change + DiscoChangeInterval.GetInt() > now) return; - - ChangeColor(pc); - LastChange[pc.PlayerId] = now; + sender.SendMessage(); } } + + public static void OnFixedUpdate(PlayerControl pc) + { + if (!pc.Is(CustomRoles.Disco) || !GameStates.IsInTask || pc.IsShifted() || Camouflage.IsCamouflage || pc.inVent || pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() || pc.walkingToVent || pc.onLadder || pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || pc.inMovingPlat) return; + + long now = Utils.TimeStamp; + if (LastChange.TryGetValue(pc.PlayerId, out long change) && change + DiscoChangeInterval.GetInt() > now) return; + + ChangeColor(pc); + LastChange[pc.PlayerId] = now; + } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Diseased.cs b/Roles/AddOns/Common/Diseased.cs index 459bd93ce..1a210c6c1 100644 --- a/Roles/AddOns/Common/Diseased.cs +++ b/Roles/AddOns/Common/Diseased.cs @@ -1,22 +1,21 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Diseased : IAddon { - internal class Diseased : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + const int id = 648600; + SetupAdtRoleOptions(id, CustomRoles.Diseased, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - const int id = 648600; - SetupAdtRoleOptions(id, CustomRoles.Diseased, canSetNum: true, teamSpawnOptions: true); + DiseasedCDOpt = new FloatOptionItem(id + 8, "DiseasedCDOpt", new(0f, 180f, 1f), 25f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]) + .SetValueFormat(OptionFormat.Seconds); - DiseasedCDOpt = new FloatOptionItem(id + 8, "DiseasedCDOpt", new(0f, 180f, 1f), 25f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]) - .SetValueFormat(OptionFormat.Seconds); - - DiseasedCDReset = new BooleanOptionItem(id + 7, "DiseasedCDReset", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); - } + DiseasedCDReset = new BooleanOptionItem(id + 7, "DiseasedCDReset", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Diseased]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/DoubleShot.cs b/Roles/AddOns/Common/DoubleShot.cs index 77c4a4e0c..f38bd1cf2 100644 --- a/Roles/AddOns/Common/DoubleShot.cs +++ b/Roles/AddOns/Common/DoubleShot.cs @@ -2,60 +2,59 @@ using System.Collections.Generic; using EHR.Modules; using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class DoubleShot : IAddon { - public class DoubleShot : IAddon + public static Dictionary Tries = []; + + private static OptionItem MaxTries; + + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public static Dictionary Tries = []; + SetupAdtRoleOptions(13900, CustomRoles.DoubleShot, canSetNum: true, teamSpawnOptions: true); - private static OptionItem MaxTries; + MaxTries = new IntegerOptionItem(13907, "DoubleShot.MaxTries", new(1, 30, 1), 1, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]) + .SetValueFormat(OptionFormat.Times); + } - public AddonTypes Type => AddonTypes.Helpful; + public static void Init() + { + Tries = []; + } - public void SetupCustomOption() + public static bool CheckGuess(PlayerControl guesser, bool isUI) + { + if (!guesser.Is(CustomRoles.DoubleShot)) return false; + + if (!Tries.TryGetValue(guesser.PlayerId, out int tries)) { - SetupAdtRoleOptions(13900, CustomRoles.DoubleShot, canSetNum: true, teamSpawnOptions: true); - - MaxTries = new IntegerOptionItem(13907, "DoubleShot.MaxTries", new(1, 30, 1), 1, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.DoubleShot]) - .SetValueFormat(OptionFormat.Times); + Tries[guesser.PlayerId] = 1; + LogAndNotify(); + return true; } - public static void Init() + if (tries < MaxTries.GetInt()) { - Tries = []; + Tries[guesser.PlayerId] = ++tries; + LogAndNotify(); + return true; } - public static bool CheckGuess(PlayerControl guesser, bool isUI) + Tries.Remove(guesser.PlayerId); + return false; + + void LogAndNotify() { - if (!guesser.Is(CustomRoles.DoubleShot)) return false; + Logger.Msg($"{guesser.PlayerId} : {tries}", "GuesserDoubleShotTries"); - if (!Tries.TryGetValue(guesser.PlayerId, out int tries)) - { - Tries[guesser.PlayerId] = 1; - LogAndNotify(); - return true; - } - - if (tries < MaxTries.GetInt()) - { - Tries[guesser.PlayerId] = ++tries; - LogAndNotify(); - return true; - } - - Tries.Remove(guesser.PlayerId); - return false; - - void LogAndNotify() - { - Logger.Msg($"{guesser.PlayerId} : {tries}", "GuesserDoubleShotTries"); - - if (!isUI) - Utils.SendMessage(Translator.GetString("GuessDoubleShot"), guesser.PlayerId); - else - guesser.ShowPopUp(Translator.GetString("GuessDoubleShot")); - } + if (!isUI) + Utils.SendMessage(Translator.GetString("GuessDoubleShot"), guesser.PlayerId); + else + guesser.ShowPopUp(Translator.GetString("GuessDoubleShot")); } } } \ No newline at end of file diff --git a/Roles/AddOns/Common/DualPersonality.cs b/Roles/AddOns/Common/DualPersonality.cs index 1dfd08a50..5af321361 100644 --- a/Roles/AddOns/Common/DualPersonality.cs +++ b/Roles/AddOns/Common/DualPersonality.cs @@ -1,17 +1,16 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class DualPersonality : IAddon { - internal class DualPersonality : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(14700, CustomRoles.DualPersonality, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(14700, CustomRoles.DualPersonality, canSetNum: true, teamSpawnOptions: true); - - DualVotes = new BooleanOptionItem(14712, "DualVotes", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.DualPersonality]); - } + DualVotes = new BooleanOptionItem(14712, "DualVotes", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.DualPersonality]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Dynamo.cs b/Roles/AddOns/Common/Dynamo.cs index 8977c4201..e0e47f4b4 100644 --- a/Roles/AddOns/Common/Dynamo.cs +++ b/Roles/AddOns/Common/Dynamo.cs @@ -3,80 +3,79 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Dynamo : IAddon { - public class Dynamo : IAddon + private static OptionItem IncreaseSpeedBy; + private static OptionItem IncreaseSpeedFrequency; + private static OptionItem MaxSpeed; + + private static readonly Dictionary LastPosition = []; + private static readonly Dictionary StartingSpeed = []; + private static readonly Dictionary SpeedIncreaseTimer = []; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - private static OptionItem IncreaseSpeedBy; - private static OptionItem IncreaseSpeedFrequency; - private static OptionItem MaxSpeed; + const int id = 19390; + Options.SetupAdtRoleOptions(id, CustomRoles.Dynamo, canSetNum: true, teamSpawnOptions: true); - private static readonly Dictionary LastPosition = []; - private static readonly Dictionary StartingSpeed = []; - private static readonly Dictionary SpeedIncreaseTimer = []; - public AddonTypes Type => AddonTypes.Helpful; + IncreaseSpeedBy = new FloatOptionItem(id + 9, "Dynamo.IncreaseSpeedBy", new(0.1f, 1f, 0.1f), 0.1f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dynamo]) + .SetValueFormat(OptionFormat.Multiplier); - public void SetupCustomOption() + IncreaseSpeedFrequency = new FloatOptionItem(id + 7, "Dynamo.IncreaseSpeedFrequency", new(0.5f, 30f, 0.5f), 5f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dynamo]) + .SetValueFormat(OptionFormat.Seconds); + + MaxSpeed = new FloatOptionItem(id + 8, "Dynamo.MaxSpeed", new(0.1f, 3f, 0.1f), 3f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dynamo]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public static void Add() + { + foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) { - const int id = 19390; - Options.SetupAdtRoleOptions(id, CustomRoles.Dynamo, canSetNum: true, teamSpawnOptions: true); - - IncreaseSpeedBy = new FloatOptionItem(id + 9, "Dynamo.IncreaseSpeedBy", new(0.1f, 1f, 0.1f), 0.1f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dynamo]) - .SetValueFormat(OptionFormat.Multiplier); - - IncreaseSpeedFrequency = new FloatOptionItem(id + 7, "Dynamo.IncreaseSpeedFrequency", new(0.5f, 30f, 0.5f), 5f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dynamo]) - .SetValueFormat(OptionFormat.Seconds); - - MaxSpeed = new FloatOptionItem(id + 8, "Dynamo.MaxSpeed", new(0.1f, 3f, 0.1f), 3f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Dynamo]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public static void Add() - { - foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) + if (pc.Is(CustomRoles.Dynamo)) { - if (pc.Is(CustomRoles.Dynamo)) - { - LastPosition[pc.PlayerId] = pc.Pos(); - SpeedIncreaseTimer[pc.PlayerId] = -8f; - StartingSpeed[pc.PlayerId] = speed; - } + LastPosition[pc.PlayerId] = pc.Pos(); + SpeedIncreaseTimer[pc.PlayerId] = -8f; + StartingSpeed[pc.PlayerId] = speed; } } + } - public static void OnFixedUpdate(PlayerControl pc) + public static void OnFixedUpdate(PlayerControl pc) + { + Vector2 pos = pc.Pos(); + bool moving = Vector2.Distance(pos, LastPosition[pc.PlayerId]) > 0.1f; + LastPosition[pc.PlayerId] = pos; + + if (!moving || !pc.IsAlive() || !GameStates.IsInTask) { - Vector2 pos = pc.Pos(); - bool moving = Vector2.Distance(pos, LastPosition[pc.PlayerId]) > 0.1f; - LastPosition[pc.PlayerId] = pos; + float speed = Main.AllPlayerSpeed[pc.PlayerId]; + float defaultSpeed = StartingSpeed[pc.PlayerId]; - if (!moving || !pc.IsAlive() || !GameStates.IsInTask) + if (Math.Abs(speed - defaultSpeed) > 0.1f) { - float speed = Main.AllPlayerSpeed[pc.PlayerId]; - float defaultSpeed = StartingSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = defaultSpeed; + pc.MarkDirtySettings(); + } - if (Math.Abs(speed - defaultSpeed) > 0.1f) - { - Main.AllPlayerSpeed[pc.PlayerId] = defaultSpeed; - pc.MarkDirtySettings(); - } + return; + } - return; - } - - SpeedIncreaseTimer[pc.PlayerId] += Time.fixedDeltaTime; - if (SpeedIncreaseTimer[pc.PlayerId] < IncreaseSpeedFrequency.GetFloat()) return; + SpeedIncreaseTimer[pc.PlayerId] += Time.fixedDeltaTime; + if (SpeedIncreaseTimer[pc.PlayerId] < IncreaseSpeedFrequency.GetFloat()) return; - SpeedIncreaseTimer[pc.PlayerId] = 0f; + SpeedIncreaseTimer[pc.PlayerId] = 0f; - Main.AllPlayerSpeed[pc.PlayerId] += IncreaseSpeedBy.GetFloat(); - float maxSpeed = MaxSpeed.GetFloat(); - if (Main.AllPlayerSpeed[pc.PlayerId] > maxSpeed) Main.AllPlayerSpeed[pc.PlayerId] = maxSpeed; + Main.AllPlayerSpeed[pc.PlayerId] += IncreaseSpeedBy.GetFloat(); + float maxSpeed = MaxSpeed.GetFloat(); + if (Main.AllPlayerSpeed[pc.PlayerId] > maxSpeed) Main.AllPlayerSpeed[pc.PlayerId] = maxSpeed; - pc.MarkDirtySettings(); - } + pc.MarkDirtySettings(); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Energetic.cs b/Roles/AddOns/Common/Energetic.cs index 7829979f3..1607a4ff3 100644 --- a/Roles/AddOns/Common/Energetic.cs +++ b/Roles/AddOns/Common/Energetic.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - public class Energetic : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(644593, CustomRoles.Energetic, canSetNum: true, teamSpawnOptions: true); - } +public class Energetic : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(644593, CustomRoles.Energetic, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Flash.cs b/Roles/AddOns/Common/Flash.cs index 94b8f3dc6..a40686692 100644 --- a/Roles/AddOns/Common/Flash.cs +++ b/Roles/AddOns/Common/Flash.cs @@ -1,16 +1,15 @@ -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Flashman : IAddon { - internal class Flashman : IAddon + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Helpful; + Options.SetupAdtRoleOptions(18700, CustomRoles.Flashman, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(18700, CustomRoles.Flashman, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - - Options.FlashmanSpeed = new FloatOptionItem(18707, "FlashmanSpeed", new(0.25f, 3f, 0.05f), 2.5f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Flashman]) - .SetValueFormat(OptionFormat.Multiplier); - } + Options.FlashmanSpeed = new FloatOptionItem(18707, "FlashmanSpeed", new(0.25f, 3f, 0.05f), 2.5f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Flashman]) + .SetValueFormat(OptionFormat.Multiplier); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Fool.cs b/Roles/AddOns/Common/Fool.cs index 9bd788996..2779fc1a2 100644 --- a/Roles/AddOns/Common/Fool.cs +++ b/Roles/AddOns/Common/Fool.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Fool : IAddon - { - public AddonTypes Type => AddonTypes.Harmful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(19200, CustomRoles.Fool, canSetNum: true, teamSpawnOptions: true); - } +internal class Fool : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(19200, CustomRoles.Fool, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Fragile.cs b/Roles/AddOns/Common/Fragile.cs index 327834ddc..f3c77214f 100644 --- a/Roles/AddOns/Common/Fragile.cs +++ b/Roles/AddOns/Common/Fragile.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - public class Fragile : IAddon - { - public AddonTypes Type => AddonTypes.Harmful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(645333, CustomRoles.Fragile, canSetNum: true, teamSpawnOptions: true); - } +public class Fragile : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(645333, CustomRoles.Fragile, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Giant.cs b/Roles/AddOns/Common/Giant.cs index 34b535764..e360319d9 100644 --- a/Roles/AddOns/Common/Giant.cs +++ b/Roles/AddOns/Common/Giant.cs @@ -1,16 +1,15 @@ -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Giant : IAddon { - internal class Giant : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + Options.SetupAdtRoleOptions(18750, CustomRoles.Giant, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(18750, CustomRoles.Giant, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - - Options.GiantSpeed = new FloatOptionItem(18757, "GiantSpeed", new(0.25f, 3f, 0.05f), 0.75f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Giant]) - .SetValueFormat(OptionFormat.Multiplier); - } + Options.GiantSpeed = new FloatOptionItem(18757, "GiantSpeed", new(0.25f, 3f, 0.05f), 0.75f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Giant]) + .SetValueFormat(OptionFormat.Multiplier); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Glow.cs b/Roles/AddOns/Common/Glow.cs index f0246426b..1339c9949 100644 --- a/Roles/AddOns/Common/Glow.cs +++ b/Roles/AddOns/Common/Glow.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Glow : IAddon - { - public AddonTypes Type => AddonTypes.Mixed; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(14020, CustomRoles.Glow, canSetNum: true, teamSpawnOptions: true); - } +internal class Glow : IAddon +{ + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(14020, CustomRoles.Glow, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Gravestone.cs b/Roles/AddOns/Common/Gravestone.cs index cdf1b1b26..1cd8f5f5a 100644 --- a/Roles/AddOns/Common/Gravestone.cs +++ b/Roles/AddOns/Common/Gravestone.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Gravestone : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(14000, CustomRoles.Gravestone, canSetNum: true, teamSpawnOptions: true); - } +internal class Gravestone : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(14000, CustomRoles.Gravestone, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Guesser.cs b/Roles/AddOns/Common/Guesser.cs index cc3f2aa56..504d3ab5f 100644 --- a/Roles/AddOns/Common/Guesser.cs +++ b/Roles/AddOns/Common/Guesser.cs @@ -1,24 +1,23 @@ using UnityEngine; using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Guesser : IAddon { - internal class Guesser : IAddon + public static OptionItem GCanGuessAdt; + public static OptionItem GTryHideMsg; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public static OptionItem GCanGuessAdt; - public static OptionItem GTryHideMsg; - public AddonTypes Type => AddonTypes.Helpful; + SetupAdtRoleOptions(19100, CustomRoles.Guesser, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(19100, CustomRoles.Guesser, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); + GCanGuessAdt = new BooleanOptionItem(19110, "GCanGuessAdt", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); - GCanGuessAdt = new BooleanOptionItem(19110, "GCanGuessAdt", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]); - - GTryHideMsg = new BooleanOptionItem(19111, "GuesserTryHideMsg", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]) - .SetColor(Color.green); - } + GTryHideMsg = new BooleanOptionItem(19111, "GuesserTryHideMsg", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Guesser]) + .SetColor(Color.green); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Haste.cs b/Roles/AddOns/Common/Haste.cs index 26dee6af2..5a1f97c88 100644 --- a/Roles/AddOns/Common/Haste.cs +++ b/Roles/AddOns/Common/Haste.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class Haste : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(14550, CustomRoles.Haste, canSetNum: true, teamSpawnOptions: true); - } +internal class Haste : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(14550, CustomRoles.Haste, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Introvert.cs b/Roles/AddOns/Common/Introvert.cs index e685dbea3..45953f847 100644 --- a/Roles/AddOns/Common/Introvert.cs +++ b/Roles/AddOns/Common/Introvert.cs @@ -3,99 +3,98 @@ using System.Linq; using EHR.Modules; using Hazel; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Introvert : IAddon { - public class Introvert : IAddon + private static OptionItem Radius; + private static OptionItem Time; + + private static readonly Dictionary TeleportAwayDelays = []; + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - private static OptionItem Radius; - private static OptionItem Time; + Options.SetupAdtRoleOptions(645741, CustomRoles.Introvert, canSetNum: true, teamSpawnOptions: true); - private static readonly Dictionary TeleportAwayDelays = []; - public AddonTypes Type => AddonTypes.Mixed; + Radius = new FloatOptionItem(645748, "Introvert.Radius", new(0.1f, 10f, 0.1f), 2f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Introvert]) + .SetValueFormat(OptionFormat.Multiplier); - public void SetupCustomOption() + Time = new IntegerOptionItem(645749, "Introvert.Time", new(0, 30, 1), 8, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Introvert]) + .SetValueFormat(OptionFormat.Seconds); + } + + public static void OnFixedUpdate(PlayerControl pc) + { + if (Main.HasJustStarted || ExileController.Instance) return; + + Vector2 pos = pc.Pos(); + float radius = Radius.GetFloat(); + bool anyoneNear = Main.AllAlivePlayerControls.Without(pc).Any(x => Vector2.Distance(pos, x.Pos()) <= radius); + + if (!anyoneNear) { - Options.SetupAdtRoleOptions(645741, CustomRoles.Introvert, canSetNum: true, teamSpawnOptions: true); + if (TeleportAwayDelays.Remove(pc.PlayerId)) + { + Utils.SendRPC(CustomRPC.SyncIntrovert, 1, pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } - Radius = new FloatOptionItem(645748, "Introvert.Radius", new(0.1f, 10f, 0.1f), 2f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Introvert]) - .SetValueFormat(OptionFormat.Multiplier); - - Time = new IntegerOptionItem(645749, "Introvert.Time", new(0, 30, 1), 8, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Introvert]) - .SetValueFormat(OptionFormat.Seconds); + return; } - public static void OnFixedUpdate(PlayerControl pc) + if (!TeleportAwayDelays.TryGetValue(pc.PlayerId, out long endTS)) { - if (Main.HasJustStarted || ExileController.Instance) return; + int time = Time.GetInt(); - Vector2 pos = pc.Pos(); - float radius = Radius.GetFloat(); - bool anyoneNear = Main.AllAlivePlayerControls.Without(pc).Any(x => Vector2.Distance(pos, x.Pos()) <= radius); - - if (!anyoneNear) - { - if (TeleportAwayDelays.Remove(pc.PlayerId)) - { - Utils.SendRPC(CustomRPC.SyncIntrovert, 1, pc.PlayerId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - return; - } - - if (!TeleportAwayDelays.TryGetValue(pc.PlayerId, out long endTS)) - { - int time = Time.GetInt(); - - if (time == 0) - { - pc.TPToRandomVent(); - return; - } - - endTS = Utils.TimeStamp + time; - TeleportAwayDelays[pc.PlayerId] = endTS; - Utils.SendRPC(CustomRPC.SyncIntrovert, 2, pc.PlayerId, endTS); - return; - } - - if (endTS <= Utils.TimeStamp) + if (time == 0) { pc.TPToRandomVent(); - TeleportAwayDelays.Remove(pc.PlayerId); - Utils.SendRPC(CustomRPC.SyncIntrovert, 1, pc.PlayerId); + return; } - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + endTS = Utils.TimeStamp + time; + TeleportAwayDelays[pc.PlayerId] = endTS; + Utils.SendRPC(CustomRPC.SyncIntrovert, 2, pc.PlayerId, endTS); + return; } - public static void ReceiveRPC(MessageReader reader) + if (endTS <= Utils.TimeStamp) { - switch (reader.ReadPackedInt32()) + pc.TPToRandomVent(); + TeleportAwayDelays.Remove(pc.PlayerId); + Utils.SendRPC(CustomRPC.SyncIntrovert, 1, pc.PlayerId); + } + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public static void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: { - case 1: - { - byte id = reader.ReadByte(); - TeleportAwayDelays.Remove(id); - break; - } - case 2: - { - byte id = reader.ReadByte(); - long endTS = long.Parse(reader.ReadString()); - TeleportAwayDelays[id] = endTS; - break; - } + byte id = reader.ReadByte(); + TeleportAwayDelays.Remove(id); + break; + } + case 2: + { + byte id = reader.ReadByte(); + long endTS = long.Parse(reader.ReadString()); + TeleportAwayDelays[id] = endTS; + break; } - } - - public static string GetSelfSuffix(PlayerControl seer) - { - if (!seer.IsAlive() || !TeleportAwayDelays.TryGetValue(seer.PlayerId, out long endTS)) return string.Empty; - - return string.Format(Translator.GetString("Introvert.Suffix"), endTS - Utils.TimeStamp); } } + + public static string GetSelfSuffix(PlayerControl seer) + { + if (!seer.IsAlive() || !TeleportAwayDelays.TryGetValue(seer.PlayerId, out long endTS)) return string.Empty; + + return string.Format(Translator.GetString("Introvert.Suffix"), endTS - Utils.TimeStamp); + } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Lovers.cs b/Roles/AddOns/Common/Lovers.cs index 7ad5af44d..44d73c15b 100644 --- a/Roles/AddOns/Common/Lovers.cs +++ b/Roles/AddOns/Common/Lovers.cs @@ -2,157 +2,156 @@ using System.Linq; using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Lovers : IAddon { - internal class Lovers : IAddon + public static bool IsChatActivated = false; + + public static OptionItem LoverSpawnChances; + public static OptionItem LoverKnowRoles; + public static OptionItem LoverDieConsequence; + public static OptionItem LoverSuicideTime; + public static OptionItem ImpCanBeInLove; + public static OptionItem CrewCanBeInLove; + public static OptionItem NeutralCanBeInLove; + public static OptionItem CrewLoversWinWithCrew; + public static OptionItem LegacyLovers; + public static OptionItem LovingImpostorSpawnChance; + public static OptionItem LovingImpostorRoleForOtherImps; + public static OptionItem PrivateChat; + public static OptionItem PrivateChatForLoversOnly; + public static OptionItem GuessAbility; + + private static readonly string[] GuessModes = + [ + "RoleOff", // 0 + "Untouched", // 1 + "RoleOn" // 2 + ]; + + private static readonly string[] LIRole = + [ + "Impostor", + "RandomONImpRole", + "LovingImpostor" + ]; + + private static readonly string[] Consequences = + [ + "Nothing", + "Suicide", + "HalvedVision" + ]; + + private static readonly string[] SuicideTimes = + [ + "Immediately", + "WhenNextMeetingStarts", + "WhenNextMeetingEnds" + ]; + + public static CustomRoles LovingImpostorRole; + + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public static bool IsChatActivated = false; + const CustomRoles role = CustomRoles.Lovers; + const int id = 16200; + const CustomGameMode customGameMode = CustomGameMode.Standard; - public static OptionItem LoverSpawnChances; - public static OptionItem LoverKnowRoles; - public static OptionItem LoverDieConsequence; - public static OptionItem LoverSuicideTime; - public static OptionItem ImpCanBeInLove; - public static OptionItem CrewCanBeInLove; - public static OptionItem NeutralCanBeInLove; - public static OptionItem CrewLoversWinWithCrew; - public static OptionItem LegacyLovers; - public static OptionItem LovingImpostorSpawnChance; - public static OptionItem LovingImpostorRoleForOtherImps; - public static OptionItem PrivateChat; - public static OptionItem PrivateChatForLoversOnly; - public static OptionItem GuessAbility; + var spawnOption = new StringOptionItem(id, role.ToString(), RatesZeroOne, 0, TabGroup.Addons) + .SetColor(Utils.GetRoleColor(role)) + .SetHeader(true) + .SetGameMode(customGameMode) as StringOptionItem; - private static readonly string[] GuessModes = - [ - "RoleOff", // 0 - "Untouched", // 1 - "RoleOn" // 2 - ]; + var rateOption = new IntegerOptionItem(id + 2, "LoverSpawnChances", new(0, 100, 5), 50, TabGroup.Addons) + .SetParent(spawnOption) + .SetValueFormat(OptionFormat.Percent) + .SetGameMode(customGameMode) as IntegerOptionItem; - private static readonly string[] LIRole = - [ - "Impostor", - "RandomONImpRole", - "LovingImpostor" - ]; + LoverSpawnChances = rateOption; - private static readonly string[] Consequences = - [ - "Nothing", - "Suicide", - "HalvedVision" - ]; + LoverDieConsequence = new StringOptionItem(id + 3, "LoverDieConsequence", Consequences, 0, TabGroup.Addons) + .SetParent(spawnOption) + .SetGameMode(customGameMode); - private static readonly string[] SuicideTimes = - [ - "Immediately", - "WhenNextMeetingStarts", - "WhenNextMeetingEnds" - ]; + LoverSuicideTime = new StringOptionItem(id + 4, "LoverSuicideTime", SuicideTimes, 0, TabGroup.Addons) + .SetParent(LoverDieConsequence) + .SetValueFormat(OptionFormat.Seconds) + .SetGameMode(customGameMode); - public static CustomRoles LovingImpostorRole; + LoverKnowRoles = new BooleanOptionItem(id + 5, "LoverKnowRoles", true, TabGroup.Addons) + .SetParent(spawnOption) + .SetGameMode(customGameMode); - public AddonTypes Type => AddonTypes.Mixed; + PrivateChat = new BooleanOptionItem(id + 6, "PrivateChat", false, TabGroup.Addons) + .SetParent(spawnOption) + .SetGameMode(customGameMode); - public void SetupCustomOption() + PrivateChatForLoversOnly = new BooleanOptionItem(id + 7, "PrivateChatForLoversOnly", false, TabGroup.Addons) + .SetParent(PrivateChat) + .SetGameMode(customGameMode); + + CrewLoversWinWithCrew = new BooleanOptionItem(id + 8, "CrewLoversWinWithCrew", true, TabGroup.Addons) + .SetParent(spawnOption) + .SetGameMode(customGameMode); + + GuessAbility = new StringOptionItem(id + 9, "GuessAbility", GuessModes, 1, TabGroup.Addons) + .SetParent(spawnOption) + .SetGameMode(customGameMode); + + LegacyLovers = new BooleanOptionItem(id + 10, "LegacyLovers", false, TabGroup.Addons) + .SetParent(spawnOption) + .SetGameMode(customGameMode) + .RegisterUpdateValueEvent((_, _) => new[] { ImpCanBeInLove, CrewCanBeInLove, NeutralCanBeInLove }.Do(x => x.SetHidden(LegacyLovers.GetBool()))); + + LovingImpostorSpawnChance = new FloatOptionItem(id + 11, "LovingImpostorSpawnChance", new(0, 100, 5), 25, TabGroup.Addons) + .SetParent(LegacyLovers) + .SetValueFormat(OptionFormat.Percent) + .SetGameMode(customGameMode); + + LovingImpostorRoleForOtherImps = new StringOptionItem(id + 12, "LIRoleForOtherImps", LIRole, 2, TabGroup.Addons) + .SetParent(LovingImpostorSpawnChance) + .SetGameMode(customGameMode); + + ImpCanBeInLove = new BooleanOptionItem(id + 13, "ImpCanBeInLove", true, TabGroup.Addons) + .SetParent(spawnOption) + .SetHidden(LegacyLovers.GetBool()) + .SetGameMode(customGameMode); + + CrewCanBeInLove = new BooleanOptionItem(id + 14, "CrewCanBeInLove", true, TabGroup.Addons) + .SetParent(spawnOption) + .SetHidden(LegacyLovers.GetBool()) + .SetGameMode(customGameMode); + + NeutralCanBeInLove = new BooleanOptionItem(id + 15, "NeutralCanBeInLove", true, TabGroup.Addons) + .SetParent(spawnOption) + .SetHidden(LegacyLovers.GetBool()) + .SetGameMode(customGameMode); + + + OptionItem countOption = new IntegerOptionItem(id + 1, "NumberOfLovers", new(2, 2, 1), 2, TabGroup.Addons) + .SetParent(spawnOption) + .SetHidden(true) + .SetGameMode(customGameMode); + + CustomRoleSpawnChances.Add(role, spawnOption); + CustomRoleCounts.Add(role, countOption); + + CustomAdtRoleSpawnRate.Add(role, rateOption); + } + + public static void Init() + { + try { - const CustomRoles role = CustomRoles.Lovers; - const int id = 16200; - const CustomGameMode customGameMode = CustomGameMode.Standard; - - var spawnOption = new StringOptionItem(id, role.ToString(), RatesZeroOne, 0, TabGroup.Addons) - .SetColor(Utils.GetRoleColor(role)) - .SetHeader(true) - .SetGameMode(customGameMode) as StringOptionItem; - - var rateOption = new IntegerOptionItem(id + 2, "LoverSpawnChances", new(0, 100, 5), 50, TabGroup.Addons) - .SetParent(spawnOption) - .SetValueFormat(OptionFormat.Percent) - .SetGameMode(customGameMode) as IntegerOptionItem; - - LoverSpawnChances = rateOption; - - LoverDieConsequence = new StringOptionItem(id + 3, "LoverDieConsequence", Consequences, 0, TabGroup.Addons) - .SetParent(spawnOption) - .SetGameMode(customGameMode); - - LoverSuicideTime = new StringOptionItem(id + 4, "LoverSuicideTime", SuicideTimes, 0, TabGroup.Addons) - .SetParent(LoverDieConsequence) - .SetValueFormat(OptionFormat.Seconds) - .SetGameMode(customGameMode); - - LoverKnowRoles = new BooleanOptionItem(id + 5, "LoverKnowRoles", true, TabGroup.Addons) - .SetParent(spawnOption) - .SetGameMode(customGameMode); - - PrivateChat = new BooleanOptionItem(id + 6, "PrivateChat", false, TabGroup.Addons) - .SetParent(spawnOption) - .SetGameMode(customGameMode); - - PrivateChatForLoversOnly = new BooleanOptionItem(id + 7, "PrivateChatForLoversOnly", false, TabGroup.Addons) - .SetParent(PrivateChat) - .SetGameMode(customGameMode); - - CrewLoversWinWithCrew = new BooleanOptionItem(id + 8, "CrewLoversWinWithCrew", true, TabGroup.Addons) - .SetParent(spawnOption) - .SetGameMode(customGameMode); - - GuessAbility = new StringOptionItem(id + 9, "GuessAbility", GuessModes, 1, TabGroup.Addons) - .SetParent(spawnOption) - .SetGameMode(customGameMode); - - LegacyLovers = new BooleanOptionItem(id + 10, "LegacyLovers", false, TabGroup.Addons) - .SetParent(spawnOption) - .SetGameMode(customGameMode) - .RegisterUpdateValueEvent((_, _) => new[] { ImpCanBeInLove, CrewCanBeInLove, NeutralCanBeInLove }.Do(x => x.SetHidden(LegacyLovers.GetBool()))); - - LovingImpostorSpawnChance = new FloatOptionItem(id + 11, "LovingImpostorSpawnChance", new(0, 100, 5), 25, TabGroup.Addons) - .SetParent(LegacyLovers) - .SetValueFormat(OptionFormat.Percent) - .SetGameMode(customGameMode); - - LovingImpostorRoleForOtherImps = new StringOptionItem(id + 12, "LIRoleForOtherImps", LIRole, 2, TabGroup.Addons) - .SetParent(LovingImpostorSpawnChance) - .SetGameMode(customGameMode); - - ImpCanBeInLove = new BooleanOptionItem(id + 13, "ImpCanBeInLove", true, TabGroup.Addons) - .SetParent(spawnOption) - .SetHidden(LegacyLovers.GetBool()) - .SetGameMode(customGameMode); - - CrewCanBeInLove = new BooleanOptionItem(id + 14, "CrewCanBeInLove", true, TabGroup.Addons) - .SetParent(spawnOption) - .SetHidden(LegacyLovers.GetBool()) - .SetGameMode(customGameMode); - - NeutralCanBeInLove = new BooleanOptionItem(id + 15, "NeutralCanBeInLove", true, TabGroup.Addons) - .SetParent(spawnOption) - .SetHidden(LegacyLovers.GetBool()) - .SetGameMode(customGameMode); - - - OptionItem countOption = new IntegerOptionItem(id + 1, "NumberOfLovers", new(2, 2, 1), 2, TabGroup.Addons) - .SetParent(spawnOption) - .SetHidden(true) - .SetGameMode(customGameMode); - - CustomRoleSpawnChances.Add(role, spawnOption); - CustomRoleCounts.Add(role, countOption); - - CustomAdtRoleSpawnRate.Add(role, rateOption); + LovingImpostorRole = Enum.GetValues().Where(x => x.IsEnable() && x.IsImpostor() && x != CustomRoles.LovingImpostor && !x.RoleExist(true) && !HnSManager.AllHnSRoles.Contains(x)).Shuffle()[0]; } - - public static void Init() + catch { - try - { - LovingImpostorRole = Enum.GetValues().Where(x => x.IsEnable() && x.IsImpostor() && x != CustomRoles.LovingImpostor && !x.RoleExist(true) && !HnSManager.AllHnSRoles.Contains(x)).Shuffle()[0]; - } - catch - { - LovingImpostorRole = CustomRoles.LovingImpostor; - } + LovingImpostorRole = CustomRoles.LovingImpostor; } } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Loyal.cs b/Roles/AddOns/Common/Loyal.cs index 2511e29bc..fd5c10640 100644 --- a/Roles/AddOns/Common/Loyal.cs +++ b/Roles/AddOns/Common/Loyal.cs @@ -1,20 +1,19 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Loyal : IAddon { - internal class Loyal : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(15500, CustomRoles.Loyal, canSetNum: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15500, CustomRoles.Loyal, canSetNum: true); + ImpCanBeLoyal = new BooleanOptionItem(15510, "ImpCanBeLoyal", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); - ImpCanBeLoyal = new BooleanOptionItem(15510, "ImpCanBeLoyal", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); - - CrewCanBeLoyal = new BooleanOptionItem(15511, "CrewCanBeLoyal", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); - } + CrewCanBeLoyal = new BooleanOptionItem(15511, "CrewCanBeLoyal", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Loyal]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Lucky.cs b/Roles/AddOns/Common/Lucky.cs index 63de109f4..4410aaa5c 100644 --- a/Roles/AddOns/Common/Lucky.cs +++ b/Roles/AddOns/Common/Lucky.cs @@ -1,18 +1,17 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Lucky : IAddon { - internal class Lucky : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(14300, CustomRoles.Lucky, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(14300, CustomRoles.Lucky, canSetNum: true, teamSpawnOptions: true); - - LuckyProbability = new IntegerOptionItem(14310, "LuckyProbability", new(0, 100, 5), 50, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lucky]) - .SetValueFormat(OptionFormat.Percent); - } + LuckyProbability = new IntegerOptionItem(14310, "LuckyProbability", new(0, 100, 5), 50, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lucky]) + .SetValueFormat(OptionFormat.Percent); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Magnet.cs b/Roles/AddOns/Common/Magnet.cs index 19abc5922..03844028d 100644 --- a/Roles/AddOns/Common/Magnet.cs +++ b/Roles/AddOns/Common/Magnet.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class Magnet : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(19693, CustomRoles.Magnet, canSetNum: true, teamSpawnOptions: true); - } +internal class Magnet : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(19693, CustomRoles.Magnet, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Messenger.cs b/Roles/AddOns/Common/Messenger.cs index 0ccc5e87d..4b9547460 100644 --- a/Roles/AddOns/Common/Messenger.cs +++ b/Roles/AddOns/Common/Messenger.cs @@ -1,15 +1,14 @@ using System.Collections.Generic; -namespace EHR.AddOns.Common -{ - public class Messenger : IAddon - { - public static HashSet Sent = []; - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(649393, CustomRoles.Messenger, canSetNum: true, teamSpawnOptions: true); - } +public class Messenger : IAddon +{ + public static HashSet Sent = []; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(649393, CustomRoles.Messenger, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Mischievous.cs b/Roles/AddOns/Common/Mischievous.cs index 192703417..8ecf56f43 100644 --- a/Roles/AddOns/Common/Mischievous.cs +++ b/Roles/AddOns/Common/Mischievous.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class Mischievous : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(15160, CustomRoles.Mischievous, canSetNum: true); - } +internal class Mischievous : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(15160, CustomRoles.Mischievous, canSetNum: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Necroview.cs b/Roles/AddOns/Common/Necroview.cs index 8f50b3f94..6a733f22d 100644 --- a/Roles/AddOns/Common/Necroview.cs +++ b/Roles/AddOns/Common/Necroview.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Necroview : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(14400, CustomRoles.Necroview, canSetNum: true, teamSpawnOptions: true); - } +internal class Necroview : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(14400, CustomRoles.Necroview, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Oblivious.cs b/Roles/AddOns/Common/Oblivious.cs index 59b917d55..ee6a65ad2 100644 --- a/Roles/AddOns/Common/Oblivious.cs +++ b/Roles/AddOns/Common/Oblivious.cs @@ -1,17 +1,16 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Oblivious : IAddon { - internal class Oblivious : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(15400, CustomRoles.Oblivious, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15400, CustomRoles.Oblivious, canSetNum: true, teamSpawnOptions: true); - - ObliviousBaitImmune = new BooleanOptionItem(15413, "ObliviousBaitImmune", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); - } + ObliviousBaitImmune = new BooleanOptionItem(15413, "ObliviousBaitImmune", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Oblivious]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Onbound.cs b/Roles/AddOns/Common/Onbound.cs index f7485aa15..584ce78cf 100644 --- a/Roles/AddOns/Common/Onbound.cs +++ b/Roles/AddOns/Common/Onbound.cs @@ -1,17 +1,16 @@ -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Onbound : IAddon { - public class Onbound : IAddon + public static OptionItem GuesserSuicides; + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public static OptionItem GuesserSuicides; - public AddonTypes Type => AddonTypes.Mixed; + Options.SetupAdtRoleOptions(14500, CustomRoles.Onbound, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(14500, CustomRoles.Onbound, canSetNum: true, teamSpawnOptions: true); - - GuesserSuicides = new BooleanOptionItem(14507, "GuesserSuicides", true, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Onbound]) - .SetGameMode(CustomGameMode.Standard); - } + GuesserSuicides = new BooleanOptionItem(14507, "GuesserSuicides", true, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Onbound]) + .SetGameMode(CustomGameMode.Standard); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Rascal.cs b/Roles/AddOns/Common/Rascal.cs index 0ed705e66..5876de8dd 100644 --- a/Roles/AddOns/Common/Rascal.cs +++ b/Roles/AddOns/Common/Rascal.cs @@ -1,17 +1,16 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Rascal : IAddon { - internal class Rascal : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(15600, CustomRoles.Rascal, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15600, CustomRoles.Rascal, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - - RascalAppearAsMadmate = new BooleanOptionItem(15610, "RascalAppearAsMadmate", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Rascal]); - } + RascalAppearAsMadmate = new BooleanOptionItem(15610, "RascalAppearAsMadmate", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Rascal]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Reach.cs b/Roles/AddOns/Common/Reach.cs index 9f90d3e68..e794ab0f2 100644 --- a/Roles/AddOns/Common/Reach.cs +++ b/Roles/AddOns/Common/Reach.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class Reach : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(14600, CustomRoles.Reach, canSetNum: true, teamSpawnOptions: true); - } +internal class Reach : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(14600, CustomRoles.Reach, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Rookie.cs b/Roles/AddOns/Common/Rookie.cs index 9b107f78d..fc347441c 100644 --- a/Roles/AddOns/Common/Rookie.cs +++ b/Roles/AddOns/Common/Rookie.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - public class Rookie : IAddon - { - public AddonTypes Type => AddonTypes.Harmful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(649593, CustomRoles.Rookie, canSetNum: true, teamSpawnOptions: true); - } +public class Rookie : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(649593, CustomRoles.Rookie, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Seer.cs b/Roles/AddOns/Common/Seer.cs index b3f7a0f20..d7c2e2e07 100644 --- a/Roles/AddOns/Common/Seer.cs +++ b/Roles/AddOns/Common/Seer.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Seer : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(14800, CustomRoles.Seer, canSetNum: true, teamSpawnOptions: true); - } +internal class Seer : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(14800, CustomRoles.Seer, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Sleep.cs b/Roles/AddOns/Common/Sleep.cs index 5793d91a6..3b9562f5b 100644 --- a/Roles/AddOns/Common/Sleep.cs +++ b/Roles/AddOns/Common/Sleep.cs @@ -1,27 +1,26 @@ using System.Linq; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Sleep : IAddon { - public class Sleep : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + Options.SetupAdtRoleOptions(644293, CustomRoles.Sleep, canSetNum: true, teamSpawnOptions: true); + } - public void SetupCustomOption() + public static void CheckGlowNearby(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask) return; + + Vector2 pos = pc.Pos(); + + if (Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoles.Glow) && Vector2.Distance(x.Pos(), pos) <= 1.5f)) { - Options.SetupAdtRoleOptions(644293, CustomRoles.Sleep, canSetNum: true, teamSpawnOptions: true); - } - - public static void CheckGlowNearby(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask) return; - - Vector2 pos = pc.Pos(); - - if (Main.AllAlivePlayerControls.Any(x => x.Is(CustomRoles.Glow) && Vector2.Distance(x.Pos(), pos) <= 1.5f)) - { - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Sleep); - pc.MarkDirtySettings(); - } + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Sleep); + pc.MarkDirtySettings(); } } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Sleuth.cs b/Roles/AddOns/Common/Sleuth.cs index 28a62c3d5..0dd980649 100644 --- a/Roles/AddOns/Common/Sleuth.cs +++ b/Roles/AddOns/Common/Sleuth.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class Sleuth : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(15150, CustomRoles.Sleuth, canSetNum: true, teamSpawnOptions: true); - } +internal class Sleuth : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(15150, CustomRoles.Sleuth, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Sonar.cs b/Roles/AddOns/Common/Sonar.cs index fdf67bfc9..0f92b1e16 100644 --- a/Roles/AddOns/Common/Sonar.cs +++ b/Roles/AddOns/Common/Sonar.cs @@ -1,47 +1,46 @@ using System.Collections.Generic; using System.Linq; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +public class Sonar : IAddon { - public class Sonar : IAddon + private static readonly Dictionary Target = []; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - private static readonly Dictionary Target = []; - public AddonTypes Type => AddonTypes.Helpful; + Options.SetupAdtRoleOptions(13643, CustomRoles.Sonar, canSetNum: true, teamSpawnOptions: true); + } - public void SetupCustomOption() + public static string GetSuffix(PlayerControl seer, bool meeting) + { + if (meeting || !seer.Is(CustomRoles.Sonar) || !Target.TryGetValue(seer.PlayerId, out byte targetId)) return string.Empty; + + return TargetArrow.GetArrows(seer, targetId); + } + + public static void OnFixedUpdate(PlayerControl seer) + { + if (!seer.Is(CustomRoles.Sonar) || !GameStates.IsInTask || seer.inVent || Main.AllAlivePlayerControls.Length == 1) return; + + PlayerControl closest = Main.AllAlivePlayerControls.Where(x => x.PlayerId != seer.PlayerId).MinBy(x => Vector2.Distance(seer.Pos(), x.Pos())); + + if (Target.TryGetValue(seer.PlayerId, out byte targetId)) { - Options.SetupAdtRoleOptions(13643, CustomRoles.Sonar, canSetNum: true, teamSpawnOptions: true); - } - - public static string GetSuffix(PlayerControl seer, bool meeting) - { - if (meeting || !seer.Is(CustomRoles.Sonar) || !Target.TryGetValue(seer.PlayerId, out byte targetId)) return string.Empty; - - return TargetArrow.GetArrows(seer, targetId); - } - - public static void OnFixedUpdate(PlayerControl seer) - { - if (!seer.Is(CustomRoles.Sonar) || !GameStates.IsInTask || seer.inVent || Main.AllAlivePlayerControls.Length == 1) return; - - PlayerControl closest = Main.AllAlivePlayerControls.Where(x => x.PlayerId != seer.PlayerId).MinBy(x => Vector2.Distance(seer.Pos(), x.Pos())); - - if (Target.TryGetValue(seer.PlayerId, out byte targetId)) - { - if (targetId != closest.PlayerId) - { - Target[seer.PlayerId] = closest.PlayerId; - TargetArrow.Remove(seer.PlayerId, targetId); - TargetArrow.Add(seer.PlayerId, closest.PlayerId); - Utils.NotifyRoles(SpecifySeer: seer, SpecifyTarget: closest); - } - } - else + if (targetId != closest.PlayerId) { Target[seer.PlayerId] = closest.PlayerId; + TargetArrow.Remove(seer.PlayerId, targetId); TargetArrow.Add(seer.PlayerId, closest.PlayerId); Utils.NotifyRoles(SpecifySeer: seer, SpecifyTarget: closest); } } + else + { + Target[seer.PlayerId] = closest.PlayerId; + TargetArrow.Add(seer.PlayerId, closest.PlayerId); + Utils.NotifyRoles(SpecifySeer: seer, SpecifyTarget: closest); + } } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Spurt.cs b/Roles/AddOns/Common/Spurt.cs index 18550a582..4b2dd986e 100644 --- a/Roles/AddOns/Common/Spurt.cs +++ b/Roles/AddOns/Common/Spurt.cs @@ -3,120 +3,119 @@ using System.Linq; using UnityEngine; using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Spurt : IAddon { - internal class Spurt : IAddon + private static OptionItem MinSpeed; + private static OptionItem Modulator; + private static OptionItem MaxSpeed; + private static OptionItem DisplaysCharge; + + public static bool LocalPlayerAvoidsZeroAndOneHundredPrecent; + + private static readonly Dictionary LastPos = []; + public static readonly Dictionary StartingSpeed = []; + private static readonly Dictionary LastNum = []; + private static readonly Dictionary LastUpdate = []; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - private static OptionItem MinSpeed; - private static OptionItem Modulator; - private static OptionItem MaxSpeed; - private static OptionItem DisplaysCharge; + const int id = 648950; + SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); - public static bool LocalPlayerAvoidsZeroAndOneHundredPrecent; + MinSpeed = new FloatOptionItem(id + 10, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); - private static readonly Dictionary LastPos = []; - public static readonly Dictionary StartingSpeed = []; - private static readonly Dictionary LastNum = []; - private static readonly Dictionary LastUpdate = []; - public AddonTypes Type => AddonTypes.Helpful; + MaxSpeed = new FloatOptionItem(id + 7, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); - public void SetupCustomOption() + Modulator = new FloatOptionItem(id + 8, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) + .SetValueFormat(OptionFormat.Multiplier); + + DisplaysCharge = new BooleanOptionItem(id + 9, "EnableSpurtCharge", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); + } + + public static void Add() + { + foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) { - const int id = 648950; - SetupAdtRoleOptions(id, CustomRoles.Spurt, canSetNum: true, teamSpawnOptions: true); - - MinSpeed = new FloatOptionItem(id + 10, "SpurtMinSpeed", new(0f, 3f, 0.25f), 0.75f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) - .SetValueFormat(OptionFormat.Multiplier); - - MaxSpeed = new FloatOptionItem(id + 7, "SpurtMaxSpeed", new(1.5f, 3f, 0.25f), 3f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) - .SetValueFormat(OptionFormat.Multiplier); - - Modulator = new FloatOptionItem(id + 8, "SpurtModule", new(0.25f, 3f, 0.25f), 1.25f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]) - .SetValueFormat(OptionFormat.Multiplier); - - DisplaysCharge = new BooleanOptionItem(id + 9, "EnableSpurtCharge", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spurt]); - } - - public static void Add() - { - foreach ((PlayerControl pc, float speed) in Main.AllAlivePlayerControls.Zip(Main.AllPlayerSpeed.Values)) + if (pc.Is(CustomRoles.Spurt)) { - if (pc.Is(CustomRoles.Spurt)) - { - LastPos[pc.PlayerId] = pc.Pos(); - LastNum[pc.PlayerId] = 0; - LastUpdate[pc.PlayerId] = Utils.TimeStamp; - StartingSpeed[pc.PlayerId] = speed; - } + LastPos[pc.PlayerId] = pc.Pos(); + LastNum[pc.PlayerId] = 0; + LastUpdate[pc.PlayerId] = Utils.TimeStamp; + StartingSpeed[pc.PlayerId] = speed; } - - LocalPlayerAvoidsZeroAndOneHundredPrecent = PlayerControl.LocalPlayer.Is(CustomRoles.Spurt); } - public static void DeathTask(PlayerControl player) + LocalPlayerAvoidsZeroAndOneHundredPrecent = PlayerControl.LocalPlayer.Is(CustomRoles.Spurt); + } + + public static void DeathTask(PlayerControl player) + { + Main.AllPlayerSpeed[player.PlayerId] = StartingSpeed[player.PlayerId]; + player.MarkDirtySettings(); + } + + private static int DetermineCharge(PlayerControl player) + { + float minSpeed = MinSpeed.GetFloat(); + float maxSpeed = MaxSpeed.GetFloat(); + + if (Mathf.Approximately(minSpeed, maxSpeed)) return 100; + + return (int)((Main.AllPlayerSpeed[player.PlayerId] - minSpeed) / (maxSpeed - minSpeed) * 100); + } + + public static string GetSuffix(PlayerControl player, bool isforhud = false) + { + if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting) return string.Empty; + + int fontsize = isforhud ? 100 : 65; + + return $"{string.Format(Translator.GetString("SpurtSuffix"), DetermineCharge(player))}"; + } + + public static void OnFixedUpdate(PlayerControl player) + { + Vector2 pos = player.Pos(); + bool moving = Vector2.Distance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); + LastPos[player.PlayerId] = pos; + + float modulator = Modulator.GetFloat(); + float increaseBy = Mathf.Clamp(modulator / 20 * 1.5f, 0.05f, 0.6f); + float decreaseby = Mathf.Clamp(modulator / 20 * 0.5f, 0.01f, 0.3f); + + int charge = DetermineCharge(player); + + if (player.IsLocalPlayer() && charge is <= 0 or >= 100) + LocalPlayerAvoidsZeroAndOneHundredPrecent = false; + + if (DisplaysCharge.GetBool() && !player.IsModClient() && LastNum[player.PlayerId] != charge) { - Main.AllPlayerSpeed[player.PlayerId] = StartingSpeed[player.PlayerId]; - player.MarkDirtySettings(); - } + LastNum[player.PlayerId] = charge; + long now = Utils.TimeStamp; - private static int DetermineCharge(PlayerControl player) - { - float minSpeed = MinSpeed.GetFloat(); - float maxSpeed = MaxSpeed.GetFloat(); - - if (Mathf.Approximately(minSpeed, maxSpeed)) return 100; - - return (int)((Main.AllPlayerSpeed[player.PlayerId] - minSpeed) / (maxSpeed - minSpeed) * 100); - } - - public static string GetSuffix(PlayerControl player, bool isforhud = false) - { - if (!player.Is(CustomRoles.Spurt) || !DisplaysCharge.GetBool() || GameStates.IsMeeting) return string.Empty; - - int fontsize = isforhud ? 100 : 65; - - return $"{string.Format(Translator.GetString("SpurtSuffix"), DetermineCharge(player))}"; - } - - public static void OnFixedUpdate(PlayerControl player) - { - Vector2 pos = player.Pos(); - bool moving = Vector2.Distance(pos, LastPos[player.PlayerId]) > 0f || player.MyPhysics.Animations.IsPlayingRunAnimation(); - LastPos[player.PlayerId] = pos; - - float modulator = Modulator.GetFloat(); - float increaseBy = Mathf.Clamp(modulator / 20 * 1.5f, 0.05f, 0.6f); - float decreaseby = Mathf.Clamp(modulator / 20 * 0.5f, 0.01f, 0.3f); - - int charge = DetermineCharge(player); - - if (player.IsLocalPlayer() && charge is <= 0 or >= 100) - LocalPlayerAvoidsZeroAndOneHundredPrecent = false; - - if (DisplaysCharge.GetBool() && !player.IsModClient() && LastNum[player.PlayerId] != charge) + if (now != LastUpdate[player.PlayerId]) { - LastNum[player.PlayerId] = charge; - long now = Utils.TimeStamp; - - if (now != LastUpdate[player.PlayerId]) - { - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - LastUpdate[player.PlayerId] = now; - } + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + LastUpdate[player.PlayerId] = now; } - - if (!moving) - { - Main.AllPlayerSpeed[player.PlayerId] += Mathf.Clamp(increaseBy, 0f, MaxSpeed.GetFloat() - Main.AllPlayerSpeed[player.PlayerId]); - return; - } - - Main.AllPlayerSpeed[player.PlayerId] -= Mathf.Clamp(decreaseby, 0f, Main.AllPlayerSpeed[player.PlayerId] - MinSpeed.GetFloat()); - player.MarkDirtySettings(); } + + if (!moving) + { + Main.AllPlayerSpeed[player.PlayerId] += Mathf.Clamp(increaseBy, 0f, MaxSpeed.GetFloat() - Main.AllPlayerSpeed[player.PlayerId]); + return; + } + + Main.AllPlayerSpeed[player.PlayerId] -= Mathf.Clamp(decreaseby, 0f, Main.AllPlayerSpeed[player.PlayerId] - MinSpeed.GetFloat()); + player.MarkDirtySettings(); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Stained.cs b/Roles/AddOns/Common/Stained.cs index fb45b1e29..2b886c5b9 100644 --- a/Roles/AddOns/Common/Stained.cs +++ b/Roles/AddOns/Common/Stained.cs @@ -1,29 +1,28 @@ using System.Collections.Generic; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Stained : IAddon { - internal class Stained : IAddon + public static List VioletNameList = []; + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public static List VioletNameList = []; - public AddonTypes Type => AddonTypes.Mixed; + Options.SetupAdtRoleOptions(15180, CustomRoles.Stained, canSetNum: true, teamSpawnOptions: true); + } - public void SetupCustomOption() + public static void OnDeath(PlayerControl pc, PlayerControl killer) + { + if (killer == null || pc == null || pc.PlayerId == killer.PlayerId || !killer.IsAlive() || !GameStates.IsInTask) return; + + VioletNameList.Add(killer.PlayerId); + Utils.NotifyRoles(SpecifyTarget: killer); + + LateTask.New(() => { - Options.SetupAdtRoleOptions(15180, CustomRoles.Stained, canSetNum: true, teamSpawnOptions: true); - } - - public static void OnDeath(PlayerControl pc, PlayerControl killer) - { - if (killer == null || pc == null || pc.PlayerId == killer.PlayerId || !killer.IsAlive() || !GameStates.IsInTask) return; - - VioletNameList.Add(killer.PlayerId); - Utils.NotifyRoles(SpecifyTarget: killer); - - LateTask.New(() => - { - VioletNameList.Remove(killer.PlayerId); - Utils.NotifyRoles(SpecifyTarget: killer, isForMeeting: GameStates.IsMeeting); - }, 3f, "Stained Killer Violet Name"); - } + VioletNameList.Remove(killer.PlayerId); + Utils.NotifyRoles(SpecifyTarget: killer, isForMeeting: GameStates.IsMeeting); + }, 3f, "Stained Killer Violet Name"); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Sunglasses.cs b/Roles/AddOns/Common/Sunglasses.cs index d8015c791..bb23a2d2f 100644 --- a/Roles/AddOns/Common/Sunglasses.cs +++ b/Roles/AddOns/Common/Sunglasses.cs @@ -1,18 +1,17 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Sunglasses : IAddon { - internal class Sunglasses : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(15450, CustomRoles.Sunglasses, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15450, CustomRoles.Sunglasses, canSetNum: true, teamSpawnOptions: true); - - SunglassesVision = new FloatOptionItem(15460, "SunglassesVision", new(0f, 5f, 0.05f), 0.75f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sunglasses]) - .SetValueFormat(OptionFormat.Multiplier); - } + SunglassesVision = new FloatOptionItem(15460, "SunglassesVision", new(0f, 5f, 0.05f), 0.75f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sunglasses]) + .SetValueFormat(OptionFormat.Multiplier); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Torch.cs b/Roles/AddOns/Common/Torch.cs index ea224b473..347d6dd84 100644 --- a/Roles/AddOns/Common/Torch.cs +++ b/Roles/AddOns/Common/Torch.cs @@ -1,21 +1,20 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Torch : IAddon { - internal class Torch : IAddon + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Helpful; + SetupAdtRoleOptions(14200, CustomRoles.Torch, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(14200, CustomRoles.Torch, canSetNum: true, teamSpawnOptions: true); + TorchVision = new FloatOptionItem(14210, "TorchVision", new(0.5f, 5f, 0.25f), 1.25f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Torch]) + .SetValueFormat(OptionFormat.Multiplier); - TorchVision = new FloatOptionItem(14210, "TorchVision", new(0.5f, 5f, 0.25f), 1.25f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Torch]) - .SetValueFormat(OptionFormat.Multiplier); - - TorchAffectedByLights = new BooleanOptionItem(14220, "TorchAffectedByLights", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Torch]); - } + TorchAffectedByLights = new BooleanOptionItem(14220, "TorchAffectedByLights", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Torch]); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Trainee.cs b/Roles/AddOns/Common/Trainee.cs index d2ceb210f..c482b64da 100644 --- a/Roles/AddOns/Common/Trainee.cs +++ b/Roles/AddOns/Common/Trainee.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - public class Trainee : IAddon - { - public AddonTypes Type => AddonTypes.Harmful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(647843, CustomRoles.Trainee, canSetNum: true, teamSpawnOptions: true); - } +public class Trainee : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(647843, CustomRoles.Trainee, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Trapper.cs b/Roles/AddOns/Common/Trapper.cs index 512e13b80..9a5342ae5 100644 --- a/Roles/AddOns/Common/Trapper.cs +++ b/Roles/AddOns/Common/Trapper.cs @@ -1,18 +1,17 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Trapper : IAddon { - internal class Trapper : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(13800, CustomRoles.Trapper, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(13800, CustomRoles.Trapper, canSetNum: true, teamSpawnOptions: true); - - TrapperBlockMoveTime = new FloatOptionItem(13813, "TrapperBlockMoveTime", new(0f, 180f, 1f), 5f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]) - .SetValueFormat(OptionFormat.Seconds); - } + TrapperBlockMoveTime = new FloatOptionItem(13813, "TrapperBlockMoveTime", new(0f, 180f, 1f), 5f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Trapper]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Truant.cs b/Roles/AddOns/Common/Truant.cs index 5f6c443e7..bdb4d1744 100644 --- a/Roles/AddOns/Common/Truant.cs +++ b/Roles/AddOns/Common/Truant.cs @@ -1,18 +1,17 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Truant : IAddon { - internal class Truant : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(15438, CustomRoles.Truant, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15438, CustomRoles.Truant, canSetNum: true, teamSpawnOptions: true); - - TruantWaitingTime = new IntegerOptionItem(15445, "TruantWaitingTime", new(1, 90, 1), 3, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Truant]) - .SetValueFormat(OptionFormat.Seconds); - } + TruantWaitingTime = new IntegerOptionItem(15445, "TruantWaitingTime", new(1, 90, 1), 3, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Truant]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Unlucky.cs b/Roles/AddOns/Common/Unlucky.cs index ee1c934ff..d569befc7 100644 --- a/Roles/AddOns/Common/Unlucky.cs +++ b/Roles/AddOns/Common/Unlucky.cs @@ -1,34 +1,33 @@ using static EHR.Options; -namespace EHR.AddOns.Common +namespace EHR.AddOns.Common; + +internal class Unlucky : IAddon { - internal class Unlucky : IAddon + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Harmful; + SetupAdtRoleOptions(14350, CustomRoles.Unlucky, canSetNum: true, teamSpawnOptions: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(14350, CustomRoles.Unlucky, canSetNum: true, teamSpawnOptions: true); + UnluckyKillSuicideChance = new IntegerOptionItem(14364, "UnluckyKillSuicideChance", new(0, 100, 1), 2, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) + .SetValueFormat(OptionFormat.Percent); - UnluckyKillSuicideChance = new IntegerOptionItem(14364, "UnluckyKillSuicideChance", new(0, 100, 1), 2, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) - .SetValueFormat(OptionFormat.Percent); + UnluckyTaskSuicideChance = new IntegerOptionItem(14365, "UnluckyTaskSuicideChance", new(0, 100, 1), 5, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) + .SetValueFormat(OptionFormat.Percent); - UnluckyTaskSuicideChance = new IntegerOptionItem(14365, "UnluckyTaskSuicideChance", new(0, 100, 1), 5, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) - .SetValueFormat(OptionFormat.Percent); + UnluckyVentSuicideChance = new IntegerOptionItem(14366, "UnluckyVentSuicideChance", new(0, 100, 1), 3, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) + .SetValueFormat(OptionFormat.Percent); - UnluckyVentSuicideChance = new IntegerOptionItem(14366, "UnluckyVentSuicideChance", new(0, 100, 1), 3, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) - .SetValueFormat(OptionFormat.Percent); + UnluckyReportSuicideChance = new IntegerOptionItem(14367, "UnluckyReportSuicideChance", new(0, 100, 1), 1, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) + .SetValueFormat(OptionFormat.Percent); - UnluckyReportSuicideChance = new IntegerOptionItem(14367, "UnluckyReportSuicideChance", new(0, 100, 1), 1, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) - .SetValueFormat(OptionFormat.Percent); - - UnluckySabotageSuicideChance = new IntegerOptionItem(14368, "UnluckySabotageSuicideChance", new(0, 100, 1), 4, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) - .SetValueFormat(OptionFormat.Percent); - } + UnluckySabotageSuicideChance = new IntegerOptionItem(14368, "UnluckySabotageSuicideChance", new(0, 100, 1), 4, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Unlucky]) + .SetValueFormat(OptionFormat.Percent); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Unreportable.cs b/Roles/AddOns/Common/Unreportable.cs index 1a6315e6e..c587692f0 100644 --- a/Roles/AddOns/Common/Unreportable.cs +++ b/Roles/AddOns/Common/Unreportable.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Unreportable : IAddon - { - public AddonTypes Type => AddonTypes.Harmful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(15300, CustomRoles.Unreportable, canSetNum: true, teamSpawnOptions: true); - } +internal class Unreportable : IAddon +{ + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(15300, CustomRoles.Unreportable, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/Watcher.cs b/Roles/AddOns/Common/Watcher.cs index ccab48f1d..35b522ecc 100644 --- a/Roles/AddOns/Common/Watcher.cs +++ b/Roles/AddOns/Common/Watcher.cs @@ -1,14 +1,13 @@ using static EHR.Options; -namespace EHR.AddOns.Common -{ - internal class Watcher : IAddon - { - public AddonTypes Type => AddonTypes.Helpful; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - SetupAdtRoleOptions(15000, CustomRoles.Watcher, canSetNum: true, teamSpawnOptions: true); - } +internal class Watcher : IAddon +{ + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() + { + SetupAdtRoleOptions(15000, CustomRoles.Watcher, canSetNum: true, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Common/YouTuber.cs b/Roles/AddOns/Common/YouTuber.cs index 25288cd90..5de44a00b 100644 --- a/Roles/AddOns/Common/YouTuber.cs +++ b/Roles/AddOns/Common/YouTuber.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Common -{ - internal class YouTuber : IAddon - { - public AddonTypes Type => AddonTypes.Mixed; +namespace EHR.AddOns.Common; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(18800, CustomRoles.Youtuber, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); - } +internal class YouTuber : IAddon +{ + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(18800, CustomRoles.Youtuber, canSetNum: true, tab: TabGroup.Addons, teamSpawnOptions: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Bloodlust.cs b/Roles/AddOns/Crewmate/Bloodlust.cs index 944810e7b..b39b8742d 100644 --- a/Roles/AddOns/Crewmate/Bloodlust.cs +++ b/Roles/AddOns/Crewmate/Bloodlust.cs @@ -1,28 +1,27 @@ using static EHR.Options; -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +internal class Bloodlust : IAddon { - internal class Bloodlust : IAddon + private const int Id = 15790; + public static OptionItem KCD; + public static OptionItem CanVent; + public static OptionItem HasImpVision; + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - private const int Id = 15790; - public static OptionItem KCD; - public static OptionItem CanVent; - public static OptionItem HasImpVision; - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(Id, CustomRoles.Bloodlust); - public void SetupCustomOption() - { - SetupAdtRoleOptions(Id, CustomRoles.Bloodlust); + KCD = new FloatOptionItem(Id + 6, "KillCooldown", new(0f, 60f, 2.5f), 30f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodlust]) + .SetValueFormat(OptionFormat.Seconds); - KCD = new FloatOptionItem(Id + 6, "KillCooldown", new(0f, 60f, 2.5f), 30f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodlust]) - .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(Id + 4, "CanVent", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodlust]); - CanVent = new BooleanOptionItem(Id + 4, "CanVent", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodlust]); - - HasImpVision = new BooleanOptionItem(Id + 5, "ImpostorVision", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodlust]); - } + HasImpVision = new BooleanOptionItem(Id + 5, "ImpostorVision", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodlust]); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Finder.cs b/Roles/AddOns/Crewmate/Finder.cs index 95b7fcaeb..9981b9ff3 100644 --- a/Roles/AddOns/Crewmate/Finder.cs +++ b/Roles/AddOns/Crewmate/Finder.cs @@ -1,27 +1,26 @@ -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +public class Finder : IAddon { - public class Finder : IAddon + public static OptionItem FinderCD; + public static OptionItem FinderDuration; + public static OptionItem FinderDelay; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public static OptionItem FinderCD; - public static OptionItem FinderDuration; - public static OptionItem FinderDelay; - public AddonTypes Type => AddonTypes.Helpful; + Options.SetupAdtRoleOptions(15660, CustomRoles.Finder, canSetNum: true); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(15660, CustomRoles.Finder, canSetNum: true); + FinderCD = new FloatOptionItem(15668, "TrackerCooldown", new(0f, 180f, 1f), 25f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Finder]) + .SetValueFormat(OptionFormat.Seconds); - FinderCD = new FloatOptionItem(15668, "TrackerCooldown", new(0f, 180f, 1f), 25f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Finder]) - .SetValueFormat(OptionFormat.Seconds); + FinderDuration = new FloatOptionItem(15666, "TrackerDuration", new(0f, 180f, 1f), 10f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Finder]) + .SetValueFormat(OptionFormat.Seconds); - FinderDuration = new FloatOptionItem(15666, "TrackerDuration", new(0f, 180f, 1f), 10f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Finder]) - .SetValueFormat(OptionFormat.Seconds); - - FinderDelay = new FloatOptionItem(15667, "TrackerDelay", new(0f, 180f, 1f), 5f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Finder]) - .SetValueFormat(OptionFormat.Seconds); - } + FinderDelay = new FloatOptionItem(15667, "TrackerDelay", new(0f, 180f, 1f), 5f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Finder]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Lazy.cs b/Roles/AddOns/Crewmate/Lazy.cs index 974a82f77..6695129e0 100644 --- a/Roles/AddOns/Crewmate/Lazy.cs +++ b/Roles/AddOns/Crewmate/Lazy.cs @@ -1,20 +1,19 @@ using static EHR.Options; -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +internal class Lazy : IAddon { - internal class Lazy : IAddon + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Helpful; + SetupAdtRoleOptions(14100, CustomRoles.Lazy, canSetNum: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(14100, CustomRoles.Lazy, canSetNum: true); + TasklessCrewCanBeLazy = new BooleanOptionItem(14110, "TasklessCrewCanBeLazy", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lazy]); - TasklessCrewCanBeLazy = new BooleanOptionItem(14110, "TasklessCrewCanBeLazy", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lazy]); - - TaskBasedCrewCanBeLazy = new BooleanOptionItem(14120, "TaskBasedCrewCanBeLazy", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lazy]); - } + TaskBasedCrewCanBeLazy = new BooleanOptionItem(14120, "TaskBasedCrewCanBeLazy", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lazy]); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Nimble.cs b/Roles/AddOns/Crewmate/Nimble.cs index 670158eb2..2670e9021 100644 --- a/Roles/AddOns/Crewmate/Nimble.cs +++ b/Roles/AddOns/Crewmate/Nimble.cs @@ -1,24 +1,23 @@ using static EHR.Options; -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +internal class Nimble : IAddon { - internal class Nimble : IAddon + public static OptionItem NimbleCD; + public static OptionItem NimbleInVentTime; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public static OptionItem NimbleCD; - public static OptionItem NimbleInVentTime; - public AddonTypes Type => AddonTypes.Helpful; + SetupAdtRoleOptions(15640, CustomRoles.Nimble, canSetNum: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15640, CustomRoles.Nimble, canSetNum: true); + NimbleCD = new FloatOptionItem(15647, "VentCooldown", new(0, 180, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Nimble]) + .SetValueFormat(OptionFormat.Seconds); - NimbleCD = new FloatOptionItem(15647, "VentCooldown", new(0, 180, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Nimble]) - .SetValueFormat(OptionFormat.Seconds); - - NimbleInVentTime = new FloatOptionItem(15646, "MaxInVentTime", new(0, 180, 1), 15, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Nimble]) - .SetValueFormat(OptionFormat.Seconds); - } + NimbleInVentTime = new FloatOptionItem(15646, "MaxInVentTime", new(0, 180, 1), 15, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Nimble]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Noisy.cs b/Roles/AddOns/Crewmate/Noisy.cs index aa8a202cc..a4c30a827 100644 --- a/Roles/AddOns/Crewmate/Noisy.cs +++ b/Roles/AddOns/Crewmate/Noisy.cs @@ -1,21 +1,20 @@ -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +public class Noisy : IAddon { - public class Noisy : IAddon + public static OptionItem NoisyImpostorAlert; + public static OptionItem NoisyAlertDuration; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public static OptionItem NoisyImpostorAlert; - public static OptionItem NoisyAlertDuration; - public AddonTypes Type => AddonTypes.Helpful; + Options.SetupAdtRoleOptions(15670, CustomRoles.Noisy, canSetNum: true); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(15670, CustomRoles.Noisy, canSetNum: true); + NoisyImpostorAlert = new BooleanOptionItem(15677, "NoisemakerImpostorAlert", true, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Noisy]); - NoisyImpostorAlert = new BooleanOptionItem(15677, "NoisemakerImpostorAlert", true, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Noisy]); - - NoisyAlertDuration = new FloatOptionItem(15676, "NoisemakerAlertDuration", new(0f, 180f, 1f), 10f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Noisy]) - .SetValueFormat(OptionFormat.Seconds); - } + NoisyAlertDuration = new FloatOptionItem(15676, "NoisemakerAlertDuration", new(0f, 180f, 1f), 10f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Noisy]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Physicist.cs b/Roles/AddOns/Crewmate/Physicist.cs index c5d4d94a4..0f12955f7 100644 --- a/Roles/AddOns/Crewmate/Physicist.cs +++ b/Roles/AddOns/Crewmate/Physicist.cs @@ -1,24 +1,23 @@ using static EHR.Options; -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +internal class Physicist : IAddon { - internal class Physicist : IAddon + public static OptionItem PhysicistCD; + public static OptionItem PhysicistViewDuration; + public AddonTypes Type => AddonTypes.Helpful; + + public void SetupCustomOption() { - public static OptionItem PhysicistCD; - public static OptionItem PhysicistViewDuration; - public AddonTypes Type => AddonTypes.Helpful; + SetupAdtRoleOptions(15650, CustomRoles.Physicist, canSetNum: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15650, CustomRoles.Physicist, canSetNum: true); + PhysicistCD = new FloatOptionItem(15657, "VitalsCooldown", new(0, 180, 1), 20, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Physicist]) + .SetValueFormat(OptionFormat.Seconds); - PhysicistCD = new FloatOptionItem(15657, "VitalsCooldown", new(0, 180, 1), 20, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Physicist]) - .SetValueFormat(OptionFormat.Seconds); - - PhysicistViewDuration = new FloatOptionItem(15656, "VitalsDuration", new(0, 180, 1), 5, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Physicist]) - .SetValueFormat(OptionFormat.Seconds); - } + PhysicistViewDuration = new FloatOptionItem(15656, "VitalsDuration", new(0, 180, 1), 5, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Physicist]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Stressed.cs b/Roles/AddOns/Crewmate/Stressed.cs index db7ad2302..f80688a42 100644 --- a/Roles/AddOns/Crewmate/Stressed.cs +++ b/Roles/AddOns/Crewmate/Stressed.cs @@ -5,252 +5,251 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +public class Stressed : IAddon { - public class Stressed : IAddon + private const int Id = 14686; + + private static OptionItem StressedExtraTimeAfterTaskComplete; + private static OptionItem StressedExtraTimeAfterMeeting; + private static OptionItem StressedExtraTimeAfterImpostorDeath; + private static OptionItem StressedExtraTimeAfterImpostorEjected; + private static OptionItem StressedExtraTimeAfterFixingSabotage; + private static OptionItem StressedExtraTimeAfterReporting; + private static OptionItem StressedTimePenaltyAfterCrewmateEjected; + private static OptionItem StressedStartingTime; + + private static int TimeAfterTaskComplete; + private static int TimeAfterMeeting; + private static int TimeAfterImpDead; + private static int TimeAfterImpEject; + private static int TimeAfterSaboFix; + private static int TimeAfterReport; + private static int TimeMinusAfterCrewEject; + private static int StartingTime; + + private static Dictionary Timers = []; + private static Dictionary LastUpdates = []; + + public static bool CountRepairSabotage; + + private static bool IsEnable => Timers.Count > 0; + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - private const int Id = 14686; + SetupAdtRoleOptions(Id, CustomRoles.Stressed, canSetNum: true); - private static OptionItem StressedExtraTimeAfterTaskComplete; - private static OptionItem StressedExtraTimeAfterMeeting; - private static OptionItem StressedExtraTimeAfterImpostorDeath; - private static OptionItem StressedExtraTimeAfterImpostorEjected; - private static OptionItem StressedExtraTimeAfterFixingSabotage; - private static OptionItem StressedExtraTimeAfterReporting; - private static OptionItem StressedTimePenaltyAfterCrewmateEjected; - private static OptionItem StressedStartingTime; + StressedExtraTimeAfterTaskComplete = new IntegerOptionItem(Id + 11, "StressedExtraTimeAfterTask", new(0, 60, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); - private static int TimeAfterTaskComplete; - private static int TimeAfterMeeting; - private static int TimeAfterImpDead; - private static int TimeAfterImpEject; - private static int TimeAfterSaboFix; - private static int TimeAfterReport; - private static int TimeMinusAfterCrewEject; - private static int StartingTime; + StressedExtraTimeAfterMeeting = new IntegerOptionItem(Id + 4, "DamoclesExtraTimeAfterMeeting", new(0, 60, 1), 15, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); - private static Dictionary Timers = []; - private static Dictionary LastUpdates = []; + StressedExtraTimeAfterImpostorDeath = new IntegerOptionItem(Id + 5, "StressedExtraTimeAfterImpostorDeath", new(0, 60, 1), 10, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); - public static bool CountRepairSabotage; + StressedExtraTimeAfterImpostorEjected = new IntegerOptionItem(Id + 6, "StressedExtraTimeAfterImpostorEjected", new(0, 60, 1), 25, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); - private static bool IsEnable => Timers.Count > 0; - public AddonTypes Type => AddonTypes.Harmful; + StressedExtraTimeAfterFixingSabotage = new IntegerOptionItem(Id + 7, "StressedExtraTimeAfterFixingSabotage", new(0, 60, 1), 20, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); - public void SetupCustomOption() + StressedExtraTimeAfterReporting = new IntegerOptionItem(Id + 8, "StressedExtraTimeAfterReporting", new(0, 60, 1), 10, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); + + StressedTimePenaltyAfterCrewmateEjected = new IntegerOptionItem(Id + 9, "StressedTimePenaltyAfterCrewmateEjected", new(0, 60, 1), 15, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); + + StressedStartingTime = new IntegerOptionItem(Id + 10, "DamoclesStartingTime", new(0, 60, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) + .SetValueFormat(OptionFormat.Seconds); + } + + public static void Init() + { + TimeAfterTaskComplete = StressedExtraTimeAfterTaskComplete.GetInt(); + TimeAfterMeeting = StressedExtraTimeAfterMeeting.GetInt(); + StartingTime = StressedStartingTime.GetInt(); + TimeAfterImpDead = StressedExtraTimeAfterImpostorDeath.GetInt(); + TimeAfterImpEject = StressedExtraTimeAfterImpostorEjected.GetInt(); + TimeAfterSaboFix = StressedExtraTimeAfterFixingSabotage.GetInt(); + TimeAfterReport = StressedExtraTimeAfterReporting.GetInt(); + TimeMinusAfterCrewEject = StressedTimePenaltyAfterCrewmateEjected.GetInt(); + + Timers = []; + LastUpdates = []; + CountRepairSabotage = true; + } + + public static void Add() + { + long now = Utils.TimeStamp; + + LateTask.New(() => { - SetupAdtRoleOptions(Id, CustomRoles.Stressed, canSetNum: true); - - StressedExtraTimeAfterTaskComplete = new IntegerOptionItem(Id + 11, "StressedExtraTimeAfterTask", new(0, 60, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedExtraTimeAfterMeeting = new IntegerOptionItem(Id + 4, "DamoclesExtraTimeAfterMeeting", new(0, 60, 1), 15, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedExtraTimeAfterImpostorDeath = new IntegerOptionItem(Id + 5, "StressedExtraTimeAfterImpostorDeath", new(0, 60, 1), 10, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedExtraTimeAfterImpostorEjected = new IntegerOptionItem(Id + 6, "StressedExtraTimeAfterImpostorEjected", new(0, 60, 1), 25, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedExtraTimeAfterFixingSabotage = new IntegerOptionItem(Id + 7, "StressedExtraTimeAfterFixingSabotage", new(0, 60, 1), 20, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedExtraTimeAfterReporting = new IntegerOptionItem(Id + 8, "StressedExtraTimeAfterReporting", new(0, 60, 1), 10, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedTimePenaltyAfterCrewmateEjected = new IntegerOptionItem(Id + 9, "StressedTimePenaltyAfterCrewmateEjected", new(0, 60, 1), 15, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - - StressedStartingTime = new IntegerOptionItem(Id + 10, "DamoclesStartingTime", new(0, 60, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Stressed]) - .SetValueFormat(OptionFormat.Seconds); - } - - public static void Init() - { - TimeAfterTaskComplete = StressedExtraTimeAfterTaskComplete.GetInt(); - TimeAfterMeeting = StressedExtraTimeAfterMeeting.GetInt(); - StartingTime = StressedStartingTime.GetInt(); - TimeAfterImpDead = StressedExtraTimeAfterImpostorDeath.GetInt(); - TimeAfterImpEject = StressedExtraTimeAfterImpostorEjected.GetInt(); - TimeAfterSaboFix = StressedExtraTimeAfterFixingSabotage.GetInt(); - TimeAfterReport = StressedExtraTimeAfterReporting.GetInt(); - TimeMinusAfterCrewEject = StressedTimePenaltyAfterCrewmateEjected.GetInt(); - - Timers = []; - LastUpdates = []; - CountRepairSabotage = true; - } - - public static void Add() - { - long now = Utils.TimeStamp; - - LateTask.New(() => + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc.Is(CustomRoles.Stressed)) { - if (pc.Is(CustomRoles.Stressed)) + if (!pc.GetTaskState().HasTasks) { - if (!pc.GetTaskState().HasTasks) - { - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Stressed); - continue; - } - - Timers.Add(pc.PlayerId, StartingTime); - LastUpdates.Add(pc.PlayerId, now + 1); + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Stressed); + continue; } + + Timers.Add(pc.PlayerId, StartingTime); + LastUpdates.Add(pc.PlayerId, now + 1); } - - LogTimer(); - }, 8f, "Add Stressed Timers"); - } - - public static void Update(PlayerControl pc) - { - long now = Utils.TimeStamp; - if (pc == null || !LastUpdates.TryGetValue(pc.PlayerId, out long x) || x >= now || !Timers.ContainsKey(pc.PlayerId) || !IsEnable || !GameStates.IsInTask || !pc.Is(CustomRoles.Stressed)) return; - - LastUpdates[pc.PlayerId] = now; - - TaskState ts = pc.GetTaskState(); - - if (ts.IsTaskFinished || !ts.HasTasks || !pc.IsAlive()) - { - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Stressed); - Timers.Remove(pc.PlayerId); - LastUpdates.Remove(pc.PlayerId); - return; } - Timers[pc.PlayerId]--; - - if (Timers[pc.PlayerId] < 0) - { - Timers[pc.PlayerId] = 0; - pc.Suicide(); - - if (pc.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - - if (pc.IsNonHostModClient()) SendRPC(pc.PlayerId, Timers[pc.PlayerId], LastUpdates[pc.PlayerId]); - - if (!pc.IsModClient()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public static void SendRPC(byte id, int time, long lastUpdate) - { - if (!Utils.DoRPC || !IsEnable) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncStressedTimer, SendOption.Reliable); - writer.Write(id); - writer.Write(time); - writer.Write(lastUpdate.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - if (!IsEnable) return; - - byte id = reader.ReadByte(); - int time = reader.ReadInt32(); - long lastUpdate = long.Parse(reader.ReadString()); - Timers[id] = time; - LastUpdates[id] = lastUpdate; - } - - private static void LogTimer(byte id = byte.MaxValue, [CallerMemberName] string action = "") - { - if (Timers.TryGetValue(id, out int time)) - Logger.Info($"{action} - Timer: {time} for {id.ColoredPlayerName()}", "Stressed"); - else - Timers.Do(x => Logger.Info($"{action} - Timer: {x.Value} for {x.Key.ColoredPlayerName()}", "Stressed")); - } - - public static void OnTaskComplete(PlayerControl pc) - { - if (!IsEnable) return; - - Timers[pc.PlayerId] += TimeAfterTaskComplete; - LogTimer(pc.PlayerId); - } - - public static void AfterMeetingTasks() - { - if (!IsEnable) return; - - CountRepairSabotage = true; - } - - public static void OnNonCrewmateDead() - { - if (!IsEnable) return; - - AdjustTime(TimeAfterImpDead); LogTimer(); - } + }, 8f, "Add Stressed Timers"); + } - public static void OnNonCrewmateEjected() + public static void Update(PlayerControl pc) + { + long now = Utils.TimeStamp; + if (pc == null || !LastUpdates.TryGetValue(pc.PlayerId, out long x) || x >= now || !Timers.ContainsKey(pc.PlayerId) || !IsEnable || !GameStates.IsInTask || !pc.Is(CustomRoles.Stressed)) return; + + LastUpdates[pc.PlayerId] = now; + + TaskState ts = pc.GetTaskState(); + + if (ts.IsTaskFinished || !ts.HasTasks || !pc.IsAlive()) { - if (!IsEnable) return; - - AdjustTime(TimeAfterImpEject); - LogTimer(); + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Stressed); + Timers.Remove(pc.PlayerId); + LastUpdates.Remove(pc.PlayerId); + return; } - public static void OnCrewmateEjected() + Timers[pc.PlayerId]--; + + if (Timers[pc.PlayerId] < 0) { - if (!IsEnable) return; + Timers[pc.PlayerId] = 0; + pc.Suicide(); - AdjustTime(TimeMinusAfterCrewEject); - LogTimer(); + if (pc.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); } - public static void OnRepairSabotage(PlayerControl pc) - { - if (!IsEnable) return; + if (pc.IsNonHostModClient()) SendRPC(pc.PlayerId, Timers[pc.PlayerId], LastUpdates[pc.PlayerId]); - Timers[pc.PlayerId] += TimeAfterSaboFix; - LogTimer(pc.PlayerId); - } + if (!pc.IsModClient()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } - public static void OnReport(PlayerControl pc) - { - if (!IsEnable) return; + public static void SendRPC(byte id, int time, long lastUpdate) + { + if (!Utils.DoRPC || !IsEnable) return; - Timers[pc.PlayerId] += TimeAfterReport; - LogTimer(pc.PlayerId); - } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncStressedTimer, SendOption.Reliable); + writer.Write(id); + writer.Write(time); + writer.Write(lastUpdate.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public static void OnMeetingStart() - { - if (!IsEnable) return; + public static void ReceiveRPC(MessageReader reader) + { + if (!IsEnable) return; - AdjustTime(TimeAfterMeeting + 9); - LogTimer(); - } + byte id = reader.ReadByte(); + int time = reader.ReadInt32(); + long lastUpdate = long.Parse(reader.ReadString()); + Timers[id] = time; + LastUpdates[id] = lastUpdate; + } - public static string GetProgressText(byte playerId) - { - return Timers.TryGetValue(playerId, out int x) ? string.Format(GetString("DamoclesTimeLeft"), x) : string.Empty; - } + private static void LogTimer(byte id = byte.MaxValue, [CallerMemberName] string action = "") + { + if (Timers.TryGetValue(id, out int time)) + Logger.Info($"{action} - Timer: {time} for {id.ColoredPlayerName()}", "Stressed"); + else + Timers.Do(x => Logger.Info($"{action} - Timer: {x.Value} for {x.Key.ColoredPlayerName()}", "Stressed")); + } - private static void AdjustTime(int change) - { - if (!IsEnable) return; + public static void OnTaskComplete(PlayerControl pc) + { + if (!IsEnable) return; - Timers.AdjustAllValues(x => x + change); - } + Timers[pc.PlayerId] += TimeAfterTaskComplete; + LogTimer(pc.PlayerId); + } + + public static void AfterMeetingTasks() + { + if (!IsEnable) return; + + CountRepairSabotage = true; + } + + public static void OnNonCrewmateDead() + { + if (!IsEnable) return; + + AdjustTime(TimeAfterImpDead); + LogTimer(); + } + + public static void OnNonCrewmateEjected() + { + if (!IsEnable) return; + + AdjustTime(TimeAfterImpEject); + LogTimer(); + } + + public static void OnCrewmateEjected() + { + if (!IsEnable) return; + + AdjustTime(TimeMinusAfterCrewEject); + LogTimer(); + } + + public static void OnRepairSabotage(PlayerControl pc) + { + if (!IsEnable) return; + + Timers[pc.PlayerId] += TimeAfterSaboFix; + LogTimer(pc.PlayerId); + } + + public static void OnReport(PlayerControl pc) + { + if (!IsEnable) return; + + Timers[pc.PlayerId] += TimeAfterReport; + LogTimer(pc.PlayerId); + } + + public static void OnMeetingStart() + { + if (!IsEnable) return; + + AdjustTime(TimeAfterMeeting + 9); + LogTimer(); + } + + public static string GetProgressText(byte playerId) + { + return Timers.TryGetValue(playerId, out int x) ? string.Format(GetString("DamoclesTimeLeft"), x) : string.Empty; + } + + private static void AdjustTime(int change) + { + if (!IsEnable) return; + + Timers.AdjustAllValues(x => x + change); } } \ No newline at end of file diff --git a/Roles/AddOns/Crewmate/Workhorse.cs b/Roles/AddOns/Crewmate/Workhorse.cs index 41b273e22..94ca6a69f 100644 --- a/Roles/AddOns/Crewmate/Workhorse.cs +++ b/Roles/AddOns/Crewmate/Workhorse.cs @@ -2,111 +2,110 @@ using System.Collections.Generic; using UnityEngine; using static EHR.Options; -namespace EHR.AddOns.Crewmate +namespace EHR.AddOns.Crewmate; + +public class Workhorse : IAddon { - public class Workhorse : IAddon + private const int Id = 15700; + public static Color RoleColor = Utils.GetRoleColor(CustomRoles.Workhorse); + private static List PlayerIdList = []; + + private static OptionItem SpawnChance; + private static OptionItem OptionAssignOnlyToCrewmate; + private static OptionItem OptionNumLongTasks; + private static OptionItem OptionNumShortTasks; + private static OptionItem OptionSnitchCanBeWorkhorse; + + private static bool AssignOnlyToCrewmate; + private static int NumLongTasks; + private static int NumShortTasks; + public static (bool, int, int) TaskData => (false, NumLongTasks, NumShortTasks); + public AddonTypes Type => AddonTypes.Harmful; + + public void SetupCustomOption() { - private const int Id = 15700; - public static Color RoleColor = Utils.GetRoleColor(CustomRoles.Workhorse); - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Workhorse, zeroOne: true); - private static OptionItem SpawnChance; - private static OptionItem OptionAssignOnlyToCrewmate; - private static OptionItem OptionNumLongTasks; - private static OptionItem OptionNumShortTasks; - private static OptionItem OptionSnitchCanBeWorkhorse; + SpawnChance = new IntegerOptionItem(Id + 13, "WorkhorseSpawnChance", new(0, 100, 5), 65, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) + .SetValueFormat(OptionFormat.Percent); - private static bool AssignOnlyToCrewmate; - private static int NumLongTasks; - private static int NumShortTasks; - public static (bool, int, int) TaskData => (false, NumLongTasks, NumShortTasks); - public AddonTypes Type => AddonTypes.Harmful; + OptionAssignOnlyToCrewmate = new BooleanOptionItem(Id + 10, "AssignOnlyToCrewmate", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); - public void SetupCustomOption() + OptionNumLongTasks = new IntegerOptionItem(Id + 11, "WorkhorseNumLongTasks", new(0, 5, 1), 1, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) + .SetValueFormat(OptionFormat.Pieces); + + OptionNumShortTasks = new IntegerOptionItem(Id + 12, "WorkhorseNumShortTasks", new(0, 5, 1), 1, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) + .SetValueFormat(OptionFormat.Pieces); + + OptionSnitchCanBeWorkhorse = new BooleanOptionItem(Id + 14, "SnitchCanBeWorkhorse", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); + } + + public static void Init() + { + PlayerIdList = []; + + AssignOnlyToCrewmate = OptionAssignOnlyToCrewmate.GetBool(); + NumLongTasks = OptionNumLongTasks.GetInt(); + NumShortTasks = OptionNumShortTasks.GetInt(); + } + + private static void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public static bool IsThisRole(byte playerId) + { + return PlayerIdList.Contains(playerId); + } + + private static bool IsAssignTarget(PlayerControl pc) + { + if (!pc.IsAlive() || IsThisRole(pc.PlayerId)) return false; + + if (pc.Is(CustomRoles.Needy) || pc.Is(CustomRoles.Lazy) || pc.Is(CustomRoles.Bloodlust)) return false; + + if (pc.GetCustomRole().GetVNRole(true) is CustomRoles.Impostor or CustomRoles.Shapeshifter) return false; + + TaskState taskState = pc.GetTaskState(); + if (taskState.CompletedTasksCount + 1 < taskState.AllTasksCount) return false; + + bool canBeTarget = Utils.HasTasks(pc.Data) && !OverrideTasksData.AllData.ContainsKey(pc.GetCustomRole()); + if (AssignOnlyToCrewmate) return canBeTarget && pc.Is(CustomRoleTypes.Crewmate); + + return canBeTarget; + } + + public static bool OnCompleteTask(PlayerControl pc) + { + if (!CustomRoles.Workhorse.IsEnable() || PlayerIdList.Count >= CustomRoles.Workhorse.GetCount()) return false; + + if (CurrentGameMode != CustomGameMode.Standard) return false; + + if (pc.Is(CustomRoles.Snitch) && !OptionSnitchCanBeWorkhorse.GetBool()) return false; + + if (!IsAssignTarget(pc)) return false; + + if (IRandom.Instance.Next(100) >= SpawnChance.GetInt()) return false; + + pc.RpcSetCustomRole(CustomRoles.Workhorse); + TaskState taskState = pc.GetTaskState(); + taskState.AllTasksCount += NumLongTasks + NumShortTasks; + taskState.CompletedTasksCount++; + + if (AmongUsClient.Instance.AmHost) { - SetupRoleOptions(Id, TabGroup.Addons, CustomRoles.Workhorse, zeroOne: true); - - SpawnChance = new IntegerOptionItem(Id + 13, "WorkhorseSpawnChance", new(0, 100, 5), 65, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) - .SetValueFormat(OptionFormat.Percent); - - OptionAssignOnlyToCrewmate = new BooleanOptionItem(Id + 10, "AssignOnlyToCrewmate", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); - - OptionNumLongTasks = new IntegerOptionItem(Id + 11, "WorkhorseNumLongTasks", new(0, 5, 1), 1, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) - .SetValueFormat(OptionFormat.Pieces); - - OptionNumShortTasks = new IntegerOptionItem(Id + 12, "WorkhorseNumShortTasks", new(0, 5, 1), 1, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]) - .SetValueFormat(OptionFormat.Pieces); - - OptionSnitchCanBeWorkhorse = new BooleanOptionItem(Id + 14, "SnitchCanBeWorkhorse", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workhorse]); + Add(pc.PlayerId); + pc.Data.RpcSetTasks(new(0)); // Redistribute tasks + pc.SyncSettings(); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } - public static void Init() - { - PlayerIdList = []; - - AssignOnlyToCrewmate = OptionAssignOnlyToCrewmate.GetBool(); - NumLongTasks = OptionNumLongTasks.GetInt(); - NumShortTasks = OptionNumShortTasks.GetInt(); - } - - private static void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public static bool IsThisRole(byte playerId) - { - return PlayerIdList.Contains(playerId); - } - - private static bool IsAssignTarget(PlayerControl pc) - { - if (!pc.IsAlive() || IsThisRole(pc.PlayerId)) return false; - - if (pc.Is(CustomRoles.Needy) || pc.Is(CustomRoles.Lazy) || pc.Is(CustomRoles.Bloodlust)) return false; - - if (pc.GetCustomRole().GetVNRole(true) is CustomRoles.Impostor or CustomRoles.Shapeshifter) return false; - - TaskState taskState = pc.GetTaskState(); - if (taskState.CompletedTasksCount + 1 < taskState.AllTasksCount) return false; - - bool canBeTarget = Utils.HasTasks(pc.Data) && !OverrideTasksData.AllData.ContainsKey(pc.GetCustomRole()); - if (AssignOnlyToCrewmate) return canBeTarget && pc.Is(CustomRoleTypes.Crewmate); - - return canBeTarget; - } - - public static bool OnCompleteTask(PlayerControl pc) - { - if (!CustomRoles.Workhorse.IsEnable() || PlayerIdList.Count >= CustomRoles.Workhorse.GetCount()) return false; - - if (CurrentGameMode != CustomGameMode.Standard) return false; - - if (pc.Is(CustomRoles.Snitch) && !OptionSnitchCanBeWorkhorse.GetBool()) return false; - - if (!IsAssignTarget(pc)) return false; - - if (IRandom.Instance.Next(100) >= SpawnChance.GetInt()) return false; - - pc.RpcSetCustomRole(CustomRoles.Workhorse); - TaskState taskState = pc.GetTaskState(); - taskState.AllTasksCount += NumLongTasks + NumShortTasks; - taskState.CompletedTasksCount++; - - if (AmongUsClient.Instance.AmHost) - { - Add(pc.PlayerId); - pc.Data.RpcSetTasks(new(0)); // Redistribute tasks - pc.SyncSettings(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - return true; - } + return true; } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/Bloodmoon.cs b/Roles/AddOns/GhostRoles/Bloodmoon.cs index bcdb691f0..cf7ca946a 100644 --- a/Roles/AddOns/GhostRoles/Bloodmoon.cs +++ b/Roles/AddOns/GhostRoles/Bloodmoon.cs @@ -1,110 +1,109 @@ using System.Collections.Generic; -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +internal class Bloodmoon : IGhostRole { - internal class Bloodmoon : IGhostRole + private static OptionItem CD; + private static OptionItem Duration; + private static OptionItem Speed; + private static OptionItem DieOnMeetingCall; + + private static readonly Dictionary ScheduledDeaths = []; + + private long LastUpdate; + public Team Team => Team.Impostor | Team.Neutral; + public int Cooldown => Duration.GetInt() + CD.GetInt(); + + public void OnAssign(PlayerControl pc) { - private static OptionItem CD; - private static OptionItem Duration; - private static OptionItem Speed; - private static OptionItem DieOnMeetingCall; + Main.AllPlayerSpeed[pc.PlayerId] = Speed.GetFloat(); + pc.MarkDirtySettings(); + LastUpdate = Utils.TimeStamp; + } - private static readonly Dictionary ScheduledDeaths = []; + public void OnProtect(PlayerControl pc, PlayerControl target) + { + if (!pc.RpcCheckAndMurder(target, true)) return; - private long LastUpdate; - public Team Team => Team.Impostor | Team.Neutral; - public int Cooldown => Duration.GetInt() + CD.GetInt(); + ScheduledDeaths.TryAdd(target.PlayerId, Utils.TimeStamp); + } - public void OnAssign(PlayerControl pc) + public void SetupCustomOption() + { + Options.SetupRoleOptions(649400, TabGroup.OtherRoles, CustomRoles.Bloodmoon); + + CD = new IntegerOptionItem(649402, "AbilityCooldown", new(0, 60, 1), 60, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]) + .SetValueFormat(OptionFormat.Seconds); + + Duration = new IntegerOptionItem(649403, "Bloodmoon.Duration", new(0, 60, 1), 15, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]) + .SetValueFormat(OptionFormat.Seconds); + + Speed = new FloatOptionItem(649404, "Bloodmoon.Speed", new(0.05f, 5f, 0.05f), 1f, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]) + .SetValueFormat(OptionFormat.Multiplier); + + DieOnMeetingCall = new BooleanOptionItem(649405, "Bloodmoon.DieOnMeetingCall", true, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]); + } + + public static void Update(PlayerControl pc, Bloodmoon instance) + { + if (!GameStates.IsInTask || ExileController.Instance) return; + + long now = Utils.TimeStamp; + if (now == instance.LastUpdate) return; + + instance.LastUpdate = now; + + foreach (KeyValuePair death in ScheduledDeaths) { - Main.AllPlayerSpeed[pc.PlayerId] = Speed.GetFloat(); - pc.MarkDirtySettings(); - LastUpdate = Utils.TimeStamp; - } + PlayerControl player = Utils.GetPlayerById(death.Key); + if (player == null || !player.IsAlive()) continue; - public void OnProtect(PlayerControl pc, PlayerControl target) - { - if (!pc.RpcCheckAndMurder(target, true)) return; - - ScheduledDeaths.TryAdd(target.PlayerId, Utils.TimeStamp); - } - - public void SetupCustomOption() - { - Options.SetupRoleOptions(649400, TabGroup.OtherRoles, CustomRoles.Bloodmoon); - - CD = new IntegerOptionItem(649402, "AbilityCooldown", new(0, 60, 1), 60, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]) - .SetValueFormat(OptionFormat.Seconds); - - Duration = new IntegerOptionItem(649403, "Bloodmoon.Duration", new(0, 60, 1), 15, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]) - .SetValueFormat(OptionFormat.Seconds); - - Speed = new FloatOptionItem(649404, "Bloodmoon.Speed", new(0.05f, 5f, 0.05f), 1f, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]) - .SetValueFormat(OptionFormat.Multiplier); - - DieOnMeetingCall = new BooleanOptionItem(649405, "Bloodmoon.DieOnMeetingCall", true, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Bloodmoon]); - } - - public static void Update(PlayerControl pc, Bloodmoon instance) - { - if (!GameStates.IsInTask || ExileController.Instance) return; - - long now = Utils.TimeStamp; - if (now == instance.LastUpdate) return; - - instance.LastUpdate = now; - - foreach (KeyValuePair death in ScheduledDeaths) + if (now - death.Value < Duration.GetInt()) { - PlayerControl player = Utils.GetPlayerById(death.Key); - if (player == null || !player.IsAlive()) continue; + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + continue; + } - if (now - death.Value < Duration.GetInt()) - { - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - continue; - } + if (pc.RpcCheckAndMurder(player, true)) player.Suicide(realKiller: pc); + } + } - if (pc.RpcCheckAndMurder(player, true)) player.Suicide(realKiller: pc); + public static void OnMeetingStart() + { + if (DieOnMeetingCall.GetBool()) + { + foreach (byte id in ScheduledDeaths.Keys) + { + PlayerControl pc = Utils.GetPlayerById(id); + if (pc == null || !pc.IsAlive()) continue; + + pc.Suicide(); } } - public static void OnMeetingStart() + ScheduledDeaths.Clear(); + } + + public static string GetSuffix(PlayerControl seer) + { + if (!ScheduledDeaths.TryGetValue(seer.PlayerId, out long ts)) return string.Empty; + + long timeLeft = Duration.GetInt() - (Utils.TimeStamp - ts) + 1; + (string TextColor, string TimeColor) colors = GetColors(); + return string.Format(Translator.GetString("Bloodmoon.Suffix"), timeLeft, colors.TextColor, colors.TimeColor); + + (string TextColor, string TimeColor) GetColors() { - if (DieOnMeetingCall.GetBool()) + return timeLeft switch { - foreach (byte id in ScheduledDeaths.Keys) - { - PlayerControl pc = Utils.GetPlayerById(id); - if (pc == null || !pc.IsAlive()) continue; - - pc.Suicide(); - } - } - - ScheduledDeaths.Clear(); - } - - public static string GetSuffix(PlayerControl seer) - { - if (!ScheduledDeaths.TryGetValue(seer.PlayerId, out long ts)) return string.Empty; - - long timeLeft = Duration.GetInt() - (Utils.TimeStamp - ts) + 1; - (string TextColor, string TimeColor) colors = GetColors(); - return string.Format(Translator.GetString("Bloodmoon.Suffix"), timeLeft, colors.TextColor, colors.TimeColor); - - (string TextColor, string TimeColor) GetColors() - { - return timeLeft switch - { - > 5 => ("#ffff00", "#ffa500"), - _ => ("#ff0000", "#ffff00") - }; - } + > 5 => ("#ffff00", "#ffa500"), + _ => ("#ff0000", "#ffff00") + }; } } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/GA.cs b/Roles/AddOns/GhostRoles/GA.cs index 45ed0067e..7fb2e2858 100644 --- a/Roles/AddOns/GhostRoles/GA.cs +++ b/Roles/AddOns/GhostRoles/GA.cs @@ -1,38 +1,37 @@ using System.Collections.Generic; -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +internal class GA : IGhostRole { - internal class GA : IGhostRole + private static OptionItem ProtectDuration; + private static OptionItem CD; + + public HashSet ProtectionList = []; + + public Team Team => Team.Crewmate; + public int Cooldown => CD.GetInt(); + + public void OnProtect(PlayerControl pc, PlayerControl target) { - private static OptionItem ProtectDuration; - private static OptionItem CD; + if (ProtectionList.Add(target.PlayerId)) LateTask.New(() => ProtectionList.Remove(target.PlayerId), ProtectDuration.GetFloat()); + } - public HashSet ProtectionList = []; + public void OnAssign(PlayerControl pc) + { + ProtectionList = []; + } - public Team Team => Team.Crewmate; - public int Cooldown => CD.GetInt(); + public void SetupCustomOption() + { + Options.SetupRoleOptions(649600, TabGroup.OtherRoles, CustomRoles.GA); - public void OnProtect(PlayerControl pc, PlayerControl target) - { - if (ProtectionList.Add(target.PlayerId)) LateTask.New(() => ProtectionList.Remove(target.PlayerId), ProtectDuration.GetFloat()); - } + ProtectDuration = new IntegerOptionItem(649602, "BKProtectDuration", new(1, 90, 1), 5, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.GA]) + .SetValueFormat(OptionFormat.Seconds); - public void OnAssign(PlayerControl pc) - { - ProtectionList = []; - } - - public void SetupCustomOption() - { - Options.SetupRoleOptions(649600, TabGroup.OtherRoles, CustomRoles.GA); - - ProtectDuration = new IntegerOptionItem(649602, "BKProtectDuration", new(1, 90, 1), 5, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.GA]) - .SetValueFormat(OptionFormat.Seconds); - - CD = new IntegerOptionItem(649603, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.GA]) - .SetValueFormat(OptionFormat.Seconds); - } + CD = new IntegerOptionItem(649603, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.GA]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/Haunter.cs b/Roles/AddOns/GhostRoles/Haunter.cs index b58ae2f88..05cac3d71 100644 --- a/Roles/AddOns/GhostRoles/Haunter.cs +++ b/Roles/AddOns/GhostRoles/Haunter.cs @@ -3,168 +3,167 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +internal class Haunter : IGhostRole { - internal class Haunter : IGhostRole + public static HashSet AllHauntedPlayers = []; + + public static OptionItem TasksBeforeBeingKnown; + private static OptionItem RevealNeutralKillers; + private static OptionItem RevealMadmates; + private static OptionItem NumberOfReveals; + public static OptionItem CanWinWithCrewmates; + + private static readonly string[] WinWithCrewOpts = + [ + "RoleOff", + "WWCO.IfFinishedTasks", + "RoleOn" + ]; + + private byte HaunterId; + private List WarnedImps = []; + private long WarnTimeStamp; + + public Team Team => Team.Crewmate | Team.Neutral; + public int Cooldown => 900; + + public void OnProtect(PlayerControl pc, PlayerControl target) { } + + public void OnAssign(PlayerControl pc) { - public static HashSet AllHauntedPlayers = []; + HaunterId = pc.PlayerId; - public static OptionItem TasksBeforeBeingKnown; - private static OptionItem RevealNeutralKillers; - private static OptionItem RevealMadmates; - private static OptionItem NumberOfReveals; - public static OptionItem CanWinWithCrewmates; - - private static readonly string[] WinWithCrewOpts = - [ - "RoleOff", - "WWCO.IfFinishedTasks", - "RoleOn" - ]; - - private byte HaunterId; - private List WarnedImps = []; - private long WarnTimeStamp; - - public Team Team => Team.Crewmate | Team.Neutral; - public int Cooldown => 900; - - public void OnProtect(PlayerControl pc, PlayerControl target) { } - - public void OnAssign(PlayerControl pc) + LateTask.New(() => { - HaunterId = pc.PlayerId; + TaskState taskState = pc.GetTaskState(); + if (taskState == null) return; - LateTask.New(() => + taskState.HasTasks = true; + taskState.CompletedTasksCount = 0; + taskState.AllTasksCount = Utils.TotalTaskCount - Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); + + pc.Data.RpcSetTasks(new(0)); + pc.SyncSettings(); + pc.RpcResetAbilityCooldown(); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + }, 1f, "Haunter Assign"); + } + + public void SetupCustomOption() + { + Options.SetupRoleOptions(649300, TabGroup.OtherRoles, CustomRoles.Haunter); + + TasksBeforeBeingKnown = new IntegerOptionItem(649302, "Haunter.TasksBeforeBeingKnown", new(1, 10, 1), 1, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); + + RevealNeutralKillers = new BooleanOptionItem(649303, "Haunter.RevealNeutralKillers", true, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); + + RevealMadmates = new BooleanOptionItem(649304, "Haunter.RevealMadmates", true, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); + + NumberOfReveals = new IntegerOptionItem(649305, "Haunter.NumberOfReveals", new(1, 10, 1), 1, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); + + CanWinWithCrewmates = new StringOptionItem(649306, "Haunter.CanWinWithCrewmates", WinWithCrewOpts, 1, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); + + Options.OverrideTasksData.Create(649307, TabGroup.OtherRoles, CustomRoles.Haunter); + } + + public void OnOneTaskLeft(PlayerControl pc) + { + if (WarnedImps.Count > 0) return; + + WarnedImps = []; + + IEnumerable filtered = Main.AllAlivePlayerControls.Where(x => + { + return x.GetTeam() switch { - TaskState taskState = pc.GetTaskState(); - if (taskState == null) return; + Team.Impostor when x.GetCustomRole().IsMadmate() || x.Is(CustomRoles.Madmate) => RevealMadmates.GetBool(), + Team.Impostor => true, + Team.Neutral when x.IsNeutralKiller() => RevealNeutralKillers.GetBool(), + _ => false + }; + }); - taskState.HasTasks = true; - taskState.CompletedTasksCount = 0; - taskState.AllTasksCount = Utils.TotalTaskCount - Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); - - pc.Data.RpcSetTasks(new(0)); - pc.SyncSettings(); - pc.RpcResetAbilityCooldown(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - }, 1f, "Haunter Assign"); + foreach (PlayerControl imp in filtered) + { + TargetArrow.Add(imp.PlayerId, pc.PlayerId); + WarnedImps.Add(imp.PlayerId); + imp.Notify(Translator.GetString("Haunter1TaskLeft"), 300f); } - public void SetupCustomOption() + WarnTimeStamp = Utils.TimeStamp; + } + + public void OnFinishedTasks(PlayerControl pc) + { + if (WarnedImps.Count == 0) return; + + List targets = []; + int numOfReveals = NumberOfReveals.GetInt(); + + for (var i = 0; i < numOfReveals; i++) { - Options.SetupRoleOptions(649300, TabGroup.OtherRoles, CustomRoles.Haunter); - - TasksBeforeBeingKnown = new IntegerOptionItem(649302, "Haunter.TasksBeforeBeingKnown", new(1, 10, 1), 1, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); - - RevealNeutralKillers = new BooleanOptionItem(649303, "Haunter.RevealNeutralKillers", true, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); - - RevealMadmates = new BooleanOptionItem(649304, "Haunter.RevealMadmates", true, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); - - NumberOfReveals = new IntegerOptionItem(649305, "Haunter.NumberOfReveals", new(1, 10, 1), 1, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); - - CanWinWithCrewmates = new StringOptionItem(649306, "Haunter.CanWinWithCrewmates", WinWithCrewOpts, 1, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Haunter]); - - Options.OverrideTasksData.Create(649307, TabGroup.OtherRoles, CustomRoles.Haunter); + int index = IRandom.Instance.Next(WarnedImps.Count); + byte target = WarnedImps[index]; + targets.Add(target); + WarnedImps.Remove(target); + TargetArrow.Remove(target, pc.PlayerId); } - public void OnOneTaskLeft(PlayerControl pc) + AllHauntedPlayers.UnionWith(targets); + + targets.ForEach(x => Utils.GetPlayerById(x)?.Notify(Translator.GetString("HaunterRevealedYou"), 7f)); + WarnedImps.ForEach(x => Utils.GetPlayerById(x)?.Notify(Translator.GetString("HaunterFinishedTasks"), 7f)); + + Utils.NotifyRoles(ForceLoop: true); + } + + public void Update(PlayerControl pc) + { + if (WarnedImps.Count == 0 || WarnTimeStamp == Utils.TimeStamp) return; + + if (WarnedImps.Any(imp => TargetArrow.GetArrows(Utils.GetPlayerById(imp), pc.PlayerId) == "・")) { - if (WarnedImps.Count > 0) return; + foreach (byte imp in WarnedImps) + { + TargetArrow.Remove(imp, pc.PlayerId); + Utils.GetPlayerById(imp)?.Notify(Translator.GetString("HaunterStopped"), 7f); + } WarnedImps = []; - - IEnumerable filtered = Main.AllAlivePlayerControls.Where(x => - { - return x.GetTeam() switch - { - Team.Impostor when x.GetCustomRole().IsMadmate() || x.Is(CustomRoles.Madmate) => RevealMadmates.GetBool(), - Team.Impostor => true, - Team.Neutral when x.IsNeutralKiller() => RevealNeutralKillers.GetBool(), - _ => false - }; - }); - - foreach (PlayerControl imp in filtered) - { - TargetArrow.Add(imp.PlayerId, pc.PlayerId); - WarnedImps.Add(imp.PlayerId); - imp.Notify(Translator.GetString("Haunter1TaskLeft"), 300f); - } - - WarnTimeStamp = Utils.TimeStamp; - } - - public void OnFinishedTasks(PlayerControl pc) - { - if (WarnedImps.Count == 0) return; - - List targets = []; - int numOfReveals = NumberOfReveals.GetInt(); - - for (var i = 0; i < numOfReveals; i++) - { - int index = IRandom.Instance.Next(WarnedImps.Count); - byte target = WarnedImps[index]; - targets.Add(target); - WarnedImps.Remove(target); - TargetArrow.Remove(target, pc.PlayerId); - } - - AllHauntedPlayers.UnionWith(targets); - - targets.ForEach(x => Utils.GetPlayerById(x)?.Notify(Translator.GetString("HaunterRevealedYou"), 7f)); - WarnedImps.ForEach(x => Utils.GetPlayerById(x)?.Notify(Translator.GetString("HaunterFinishedTasks"), 7f)); - - Utils.NotifyRoles(ForceLoop: true); - } - - public void Update(PlayerControl pc) - { - if (WarnedImps.Count == 0 || WarnTimeStamp == Utils.TimeStamp) return; - - if (WarnedImps.Any(imp => TargetArrow.GetArrows(Utils.GetPlayerById(imp), pc.PlayerId) == "・")) - { - foreach (byte imp in WarnedImps) - { - TargetArrow.Remove(imp, pc.PlayerId); - Utils.GetPlayerById(imp)?.Notify(Translator.GetString("HaunterStopped"), 7f); - } - - WarnedImps = []; - Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Haunter); - GhostRolesManager.AssignedGhostRoles.Remove(pc.PlayerId); - pc.Notify(Translator.GetString("HaunterStoppedSelf"), 7f); - } - } - - public static string GetSuffix(PlayerControl seer) - { - foreach ((CustomRoles Role, IGhostRole Instance) role in GhostRolesManager.AssignedGhostRoles.Values) - { - if (role.Instance is not Haunter haunter) continue; - - if (!haunter.WarnedImps.Contains(seer.PlayerId)) continue; - - return TargetArrow.GetArrows(seer, haunter.HaunterId); - } - - return string.Empty; - } - - public static bool CanWinWithCrew(PlayerControl pc) - { - return CanWinWithCrewmates.GetValue() switch - { - 0 => false, - 1 => pc.GetTaskState().IsTaskFinished, - _ => true - }; + Main.PlayerStates[pc.PlayerId].RemoveSubRole(CustomRoles.Haunter); + GhostRolesManager.AssignedGhostRoles.Remove(pc.PlayerId); + pc.Notify(Translator.GetString("HaunterStoppedSelf"), 7f); } } + + public static string GetSuffix(PlayerControl seer) + { + foreach ((CustomRoles Role, IGhostRole Instance) role in GhostRolesManager.AssignedGhostRoles.Values) + { + if (role.Instance is not Haunter haunter) continue; + + if (!haunter.WarnedImps.Contains(seer.PlayerId)) continue; + + return TargetArrow.GetArrows(seer, haunter.HaunterId); + } + + return string.Empty; + } + + public static bool CanWinWithCrew(PlayerControl pc) + { + return CanWinWithCrewmates.GetValue() switch + { + 0 => false, + 1 => pc.GetTaskState().IsTaskFinished, + _ => true + }; + } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/IGhostRole.cs b/Roles/AddOns/GhostRoles/IGhostRole.cs index 1ac7d542f..a92a86b45 100644 --- a/Roles/AddOns/GhostRoles/IGhostRole.cs +++ b/Roles/AddOns/GhostRoles/IGhostRole.cs @@ -1,11 +1,10 @@ -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +public interface IGhostRole { - public interface IGhostRole - { - public Team Team { get; } - public int Cooldown { get; } - public void OnProtect(PlayerControl pc, PlayerControl target); - public void OnAssign(PlayerControl pc); - public void SetupCustomOption(); - } + public Team Team { get; } + public int Cooldown { get; } + public void OnProtect(PlayerControl pc, PlayerControl target); + public void OnAssign(PlayerControl pc); + public void SetupCustomOption(); } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/Minion.cs b/Roles/AddOns/GhostRoles/Minion.cs index 947035fd9..b499709c9 100644 --- a/Roles/AddOns/GhostRoles/Minion.cs +++ b/Roles/AddOns/GhostRoles/Minion.cs @@ -1,42 +1,41 @@ using System.Collections.Generic; -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +internal class Minion : IGhostRole { - internal class Minion : IGhostRole + public static HashSet BlindPlayers = []; + + private static OptionItem BlindDuration; + private static OptionItem CD; + + public Team Team => Team.Impostor; + public int Cooldown => CD.GetInt(); + + public void OnProtect(PlayerControl pc, PlayerControl target) { - public static HashSet BlindPlayers = []; + if (!BlindPlayers.Add(target.PlayerId)) return; - private static OptionItem BlindDuration; - private static OptionItem CD; + target.MarkDirtySettings(); - public Team Team => Team.Impostor; - public int Cooldown => CD.GetInt(); - - public void OnProtect(PlayerControl pc, PlayerControl target) + LateTask.New(() => { - if (!BlindPlayers.Add(target.PlayerId)) return; + if (BlindPlayers.Remove(target.PlayerId)) target.MarkDirtySettings(); + }, BlindDuration.GetFloat(), "Remove Minion Blindness"); + } - target.MarkDirtySettings(); + public void OnAssign(PlayerControl pc) { } - LateTask.New(() => - { - if (BlindPlayers.Remove(target.PlayerId)) target.MarkDirtySettings(); - }, BlindDuration.GetFloat(), "Remove Minion Blindness"); - } + public void SetupCustomOption() + { + Options.SetupRoleOptions(649000, TabGroup.OtherRoles, CustomRoles.Minion); - public void OnAssign(PlayerControl pc) { } + BlindDuration = new IntegerOptionItem(649002, "MinionBlindDuration", new(1, 90, 1), 5, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minion]) + .SetValueFormat(OptionFormat.Seconds); - public void SetupCustomOption() - { - Options.SetupRoleOptions(649000, TabGroup.OtherRoles, CustomRoles.Minion); - - BlindDuration = new IntegerOptionItem(649002, "MinionBlindDuration", new(1, 90, 1), 5, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minion]) - .SetValueFormat(OptionFormat.Seconds); - - CD = new IntegerOptionItem(649003, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minion]) - .SetValueFormat(OptionFormat.Seconds); - } + CD = new IntegerOptionItem(649003, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minion]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/Specter.cs b/Roles/AddOns/GhostRoles/Specter.cs index bd3704309..318d98539 100644 --- a/Roles/AddOns/GhostRoles/Specter.cs +++ b/Roles/AddOns/GhostRoles/Specter.cs @@ -1,58 +1,57 @@ using AmongUs.GameOptions; -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +// TOU-R Phantom +internal class Specter : IGhostRole { - // TOU-R Phantom - internal class Specter : IGhostRole + private static OptionItem SnatchWin; + + public bool IsWon; + public Team Team => Team.Neutral; + public int Cooldown => 900; + + public void OnAssign(PlayerControl pc) { - private static OptionItem SnatchWin; + IsWon = false; - public bool IsWon; - public Team Team => Team.Neutral; - public int Cooldown => 900; - - public void OnAssign(PlayerControl pc) + LateTask.New(() => { - IsWon = false; + TaskState taskState = pc.GetTaskState(); + if (taskState == null) return; - LateTask.New(() => - { - TaskState taskState = pc.GetTaskState(); - if (taskState == null) return; + taskState.HasTasks = true; + taskState.CompletedTasksCount = 0; + taskState.AllTasksCount = Utils.TotalTaskCount - Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); - taskState.HasTasks = true; - taskState.CompletedTasksCount = 0; - taskState.AllTasksCount = Utils.TotalTaskCount - Main.RealOptionsData.GetInt(Int32OptionNames.NumCommonTasks); + pc.Data.RpcSetTasks(new(0)); + pc.SyncSettings(); + pc.RpcResetAbilityCooldown(); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + }, 1f, "Specter Assign"); + } - pc.Data.RpcSetTasks(new(0)); - pc.SyncSettings(); - pc.RpcResetAbilityCooldown(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - }, 1f, "Specter Assign"); + public void OnProtect(PlayerControl pc, PlayerControl target) { } + + public void SetupCustomOption() + { + Options.SetupRoleOptions(649100, TabGroup.OtherRoles, CustomRoles.Specter); + + SnatchWin = new BooleanOptionItem(649102, "SnatchWin", false, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Specter]); + + Options.OverrideTasksData.Create(649103, TabGroup.OtherRoles, CustomRoles.Specter); + } + + public void OnFinishedTasks(PlayerControl pc) + { + if (!SnatchWin.GetBool()) + { + IsWon = true; + return; } - public void OnProtect(PlayerControl pc, PlayerControl target) { } - - public void SetupCustomOption() - { - Options.SetupRoleOptions(649100, TabGroup.OtherRoles, CustomRoles.Specter); - - SnatchWin = new BooleanOptionItem(649102, "SnatchWin", false, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Specter]); - - Options.OverrideTasksData.Create(649103, TabGroup.OtherRoles, CustomRoles.Specter); - } - - public void OnFinishedTasks(PlayerControl pc) - { - if (!SnatchWin.GetBool()) - { - IsWon = true; - return; - } - - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Specter); - CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); - } + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Specter); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); } } \ No newline at end of file diff --git a/Roles/AddOns/GhostRoles/Warden.cs b/Roles/AddOns/GhostRoles/Warden.cs index 9426eab42..08753a041 100644 --- a/Roles/AddOns/GhostRoles/Warden.cs +++ b/Roles/AddOns/GhostRoles/Warden.cs @@ -1,65 +1,64 @@ using System; using System.Collections.Generic; -namespace EHR.AddOns.GhostRoles +namespace EHR.AddOns.GhostRoles; + +internal class Warden : IGhostRole { - internal class Warden : IGhostRole + private static OptionItem ExtraSpeed; + private static OptionItem ExtraSpeedDuration; + private static OptionItem NotifyTarget; + private static OptionItem CD; + + private readonly Dictionary SpeedList = []; + + public Team Team => Team.Crewmate; + public int Cooldown => CD.GetInt(); + + public void OnProtect(PlayerControl pc, PlayerControl target) { - private static OptionItem ExtraSpeed; - private static OptionItem ExtraSpeedDuration; - private static OptionItem NotifyTarget; - private static OptionItem CD; + if (SpeedList.ContainsKey(target.PlayerId)) return; - private readonly Dictionary SpeedList = []; + float speed = Main.AllPlayerSpeed[target.PlayerId]; + float targetSpeed = speed + ExtraSpeed.GetFloat(); + if (Math.Abs(speed - targetSpeed) < 0.1f || speed > targetSpeed) return; - public Team Team => Team.Crewmate; - public int Cooldown => CD.GetInt(); + Main.AllPlayerSpeed[target.PlayerId] += ExtraSpeed.GetFloat(); + target.MarkDirtySettings(); + if (NotifyTarget.GetBool()) target.Notify(Translator.GetString("WardenNotify")); - public void OnProtect(PlayerControl pc, PlayerControl target) + SpeedList[target.PlayerId] = (Utils.TimeStamp, speed); + } + + public void OnAssign(PlayerControl pc) { } + + public void SetupCustomOption() + { + Options.SetupRoleOptions(649200, TabGroup.OtherRoles, CustomRoles.Warden); + + ExtraSpeedDuration = new IntegerOptionItem(649202, "ExpressSpeedDur", new(1, 90, 1), 5, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]) + .SetValueFormat(OptionFormat.Seconds); + + ExtraSpeed = new FloatOptionItem(649203, "WardenAdditionalSpeed", new(0f, 3f, 0.05f), 0.25f, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]) + .SetValueFormat(OptionFormat.Multiplier); + + NotifyTarget = new BooleanOptionItem(649204, "WardenNotifyTarget", true, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]); + + CD = new IntegerOptionItem(649205, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.OtherRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]) + .SetValueFormat(OptionFormat.Seconds); + } + + public void Update(PlayerControl pc) + { + SpeedList.DoIf(x => x.Value.StartTimeStamp + ExtraSpeedDuration.GetInt() <= Utils.TimeStamp, x => { - if (SpeedList.ContainsKey(target.PlayerId)) return; - - float speed = Main.AllPlayerSpeed[target.PlayerId]; - float targetSpeed = speed + ExtraSpeed.GetFloat(); - if (Math.Abs(speed - targetSpeed) < 0.1f || speed > targetSpeed) return; - - Main.AllPlayerSpeed[target.PlayerId] += ExtraSpeed.GetFloat(); - target.MarkDirtySettings(); - if (NotifyTarget.GetBool()) target.Notify(Translator.GetString("WardenNotify")); - - SpeedList[target.PlayerId] = (Utils.TimeStamp, speed); - } - - public void OnAssign(PlayerControl pc) { } - - public void SetupCustomOption() - { - Options.SetupRoleOptions(649200, TabGroup.OtherRoles, CustomRoles.Warden); - - ExtraSpeedDuration = new IntegerOptionItem(649202, "ExpressSpeedDur", new(1, 90, 1), 5, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]) - .SetValueFormat(OptionFormat.Seconds); - - ExtraSpeed = new FloatOptionItem(649203, "WardenAdditionalSpeed", new(0f, 3f, 0.05f), 0.25f, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]) - .SetValueFormat(OptionFormat.Multiplier); - - NotifyTarget = new BooleanOptionItem(649204, "WardenNotifyTarget", true, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]); - - CD = new IntegerOptionItem(649205, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.OtherRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Warden]) - .SetValueFormat(OptionFormat.Seconds); - } - - public void Update(PlayerControl pc) - { - SpeedList.DoIf(x => x.Value.StartTimeStamp + ExtraSpeedDuration.GetInt() <= Utils.TimeStamp, x => - { - Main.AllPlayerSpeed[x.Key] = x.Value.OriginalSpeed; - pc.MarkDirtySettings(); - SpeedList.Remove(x.Key); - }, false); - } + Main.AllPlayerSpeed[x.Key] = x.Value.OriginalSpeed; + pc.MarkDirtySettings(); + SpeedList.Remove(x.Key); + }, false); } } \ No newline at end of file diff --git a/Roles/AddOns/IAddon.cs b/Roles/AddOns/IAddon.cs index d947aa5e0..97545e35c 100644 --- a/Roles/AddOns/IAddon.cs +++ b/Roles/AddOns/IAddon.cs @@ -1,8 +1,7 @@ -namespace EHR.AddOns +namespace EHR.AddOns; + +internal interface IAddon { - internal interface IAddon - { - public AddonTypes Type { get; } - public void SetupCustomOption(); - } + public AddonTypes Type { get; } + public void SetupCustomOption(); } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Circumvent.cs b/Roles/AddOns/Impostor/Circumvent.cs index 6ef1f4117..65d954aec 100644 --- a/Roles/AddOns/Impostor/Circumvent.cs +++ b/Roles/AddOns/Impostor/Circumvent.cs @@ -1,103 +1,102 @@ using System.Collections.Generic; -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class Circumvent : IAddon { - internal class Circumvent : IAddon + private static Dictionary Limits = []; + + private static OptionItem VentPreventionMode; + private static OptionItem Limit; + + private static readonly string[] VentPreventionModes = + [ + "NoVenting", + "LimitPerGame", + "LimitPerRounds" + ]; + + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - private static Dictionary Limits = []; + const int id = 14680; + Options.SetupAdtRoleOptions(id, CustomRoles.Circumvent, canSetNum: true); - private static OptionItem VentPreventionMode; - private static OptionItem Limit; + VentPreventionMode = new StringOptionItem(id + 5, "VentPreventionMode", VentPreventionModes, 1, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Circumvent]); - private static readonly string[] VentPreventionModes = - [ - "NoVenting", - "LimitPerGame", - "LimitPerRounds" - ]; + Limit = new IntegerOptionItem(id + 4, "VentLimit", new(1, 90, 1), 8, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Circumvent]); + } - public AddonTypes Type => AddonTypes.ImpOnly; + public static void Init() + { + Limits = []; + } - public void SetupCustomOption() + public static void Add() + { + if (VentPreventionMode.GetValue() == 0) return; + + LateTask.New(() => { - const int id = 14680; - Options.SetupAdtRoleOptions(id, CustomRoles.Circumvent, canSetNum: true); + foreach (KeyValuePair state in Main.PlayerStates) + if (state.Value.SubRoles.Contains(CustomRoles.Circumvent)) + Limits[state.Key] = Limit.GetInt(); + }, 3f, "Add Circumvents"); + } - VentPreventionMode = new StringOptionItem(id + 5, "VentPreventionMode", VentPreventionModes, 1, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Circumvent]); - - Limit = new IntegerOptionItem(id + 4, "VentLimit", new(1, 90, 1), 8, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Circumvent]); + public static void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (VentPreventionMode.GetValue() == 0) + { + LateTask.New(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Circumvent Boot From Vent"); + return; } - public static void Init() + if (Limits.ContainsKey(physics.myPlayer.PlayerId)) { - Limits = []; - } - - public static void Add() - { - if (VentPreventionMode.GetValue() == 0) return; - - LateTask.New(() => - { - foreach (KeyValuePair state in Main.PlayerStates) - if (state.Value.SubRoles.Contains(CustomRoles.Circumvent)) - Limits[state.Key] = Limit.GetInt(); - }, 3f, "Add Circumvents"); - } - - public static void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (VentPreventionMode.GetValue() == 0) - { - LateTask.New(() => { physics.RpcBootFromVent(ventId); }, 0.5f, "Circumvent Boot From Vent"); - return; - } - - if (Limits.ContainsKey(physics.myPlayer.PlayerId)) - { - Limits[physics.myPlayer.PlayerId]--; - Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); - } - } - - public static void AfterMeetingTasks() - { - if (VentPreventionMode.GetValue() != 2) return; - - Limits.SetAllValues(Limit.GetInt()); - } - - public static string GetProgressText(byte playerId) - { - if (!Limits.TryGetValue(playerId, out int limit)) return string.Empty; - - int mode = VentPreventionMode.GetValue(); - - string color = limit switch - { - > 11 => "#00ff00", - > 7 when mode == 2 => "#00ff00", - > 7 => "#ffff00", - > 3 when mode == 2 => "#ffff00", - > 3 => "#ffa500", - > 0 when mode == 2 => "#ffa500", - > 0 => "#ff0000", - 0 when mode == 2 => "#ff0000", - 0 => "#8b0000", - - _ => "#000000" - }; - - return $" ({limit})"; - } - - public static bool CanUseImpostorVentButton(PlayerControl pc) - { - if (!pc.Is(CustomRoles.Circumvent)) return true; - - return pc.inVent || (!Limits.TryGetValue(pc.PlayerId, out int limit) && VentPreventionMode.GetValue() != 0) || limit > 0; + Limits[physics.myPlayer.PlayerId]--; + Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); } } + + public static void AfterMeetingTasks() + { + if (VentPreventionMode.GetValue() != 2) return; + + Limits.SetAllValues(Limit.GetInt()); + } + + public static string GetProgressText(byte playerId) + { + if (!Limits.TryGetValue(playerId, out int limit)) return string.Empty; + + int mode = VentPreventionMode.GetValue(); + + string color = limit switch + { + > 11 => "#00ff00", + > 7 when mode == 2 => "#00ff00", + > 7 => "#ffff00", + > 3 when mode == 2 => "#ffff00", + > 3 => "#ffa500", + > 0 when mode == 2 => "#ffa500", + > 0 => "#ff0000", + 0 when mode == 2 => "#ff0000", + 0 => "#8b0000", + + _ => "#000000" + }; + + return $" ({limit})"; + } + + public static bool CanUseImpostorVentButton(PlayerControl pc) + { + if (!pc.Is(CustomRoles.Circumvent)) return true; + + return pc.inVent || (!Limits.TryGetValue(pc.PlayerId, out int limit) && VentPreventionMode.GetValue() != 0) || limit > 0; + } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Damocles.cs b/Roles/AddOns/Impostor/Damocles.cs index 4d9bf8ffa..eec240204 100644 --- a/Roles/AddOns/Impostor/Damocles.cs +++ b/Roles/AddOns/Impostor/Damocles.cs @@ -5,188 +5,187 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +public class Damocles : IAddon { - public class Damocles : IAddon + private const int Id = 14670; + + private static OptionItem DamoclesExtraTimeAfterKill; + private static OptionItem DamoclesExtraTimeAfterMeeting; + private static OptionItem DamoclesStartingTime; + + private static int TimeAfterKill; + private static int TimeAfterMeeting; + private static int StartingTime; + + public static Dictionary Timer; + + public static Dictionary LastUpdate; + public static Dictionary> PreviouslyEnteredVents; + + public static bool CountRepairSabotage; + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - private const int Id = 14670; + SetupAdtRoleOptions(Id, CustomRoles.Damocles, canSetNum: true); - private static OptionItem DamoclesExtraTimeAfterKill; - private static OptionItem DamoclesExtraTimeAfterMeeting; - private static OptionItem DamoclesStartingTime; + DamoclesExtraTimeAfterKill = new IntegerOptionItem(Id + 6, "DamoclesExtraTimeAfterKill", new(0, 60, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Damocles]) + .SetValueFormat(OptionFormat.Seconds); - private static int TimeAfterKill; - private static int TimeAfterMeeting; - private static int StartingTime; + DamoclesExtraTimeAfterMeeting = new IntegerOptionItem(Id + 4, "DamoclesExtraTimeAfterMeeting", new(0, 60, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Damocles]) + .SetValueFormat(OptionFormat.Seconds); - public static Dictionary Timer; + DamoclesStartingTime = new IntegerOptionItem(Id + 5, "DamoclesStartingTime", new(0, 60, 1), 30, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Damocles]) + .SetValueFormat(OptionFormat.Seconds); + } - public static Dictionary LastUpdate; - public static Dictionary> PreviouslyEnteredVents; + public static void Initialize() + { + TimeAfterKill = DamoclesExtraTimeAfterKill.GetInt(); + TimeAfterMeeting = DamoclesExtraTimeAfterMeeting.GetInt(); + StartingTime = DamoclesStartingTime.GetInt(); - public static bool CountRepairSabotage; - public AddonTypes Type => AddonTypes.ImpOnly; + Timer = []; + LastUpdate = []; + PreviouslyEnteredVents = []; + CountRepairSabotage = true; + } - public void SetupCustomOption() + public static void Update(PlayerControl pc) + { + byte id = pc.PlayerId; + long now = Utils.TimeStamp; + if ((LastUpdate.TryGetValue(id, out long ts) && ts >= now) || !GameStates.IsInTask || pc == null) return; + + if (!pc.IsAlive()) { - SetupAdtRoleOptions(Id, CustomRoles.Damocles, canSetNum: true); - - DamoclesExtraTimeAfterKill = new IntegerOptionItem(Id + 6, "DamoclesExtraTimeAfterKill", new(0, 60, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Damocles]) - .SetValueFormat(OptionFormat.Seconds); - - DamoclesExtraTimeAfterMeeting = new IntegerOptionItem(Id + 4, "DamoclesExtraTimeAfterMeeting", new(0, 60, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Damocles]) - .SetValueFormat(OptionFormat.Seconds); - - DamoclesStartingTime = new IntegerOptionItem(Id + 5, "DamoclesStartingTime", new(0, 60, 1), 30, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Damocles]) - .SetValueFormat(OptionFormat.Seconds); + Main.PlayerStates[id].RemoveSubRole(CustomRoles.Damocles); + return; } - public static void Initialize() - { - TimeAfterKill = DamoclesExtraTimeAfterKill.GetInt(); - TimeAfterMeeting = DamoclesExtraTimeAfterMeeting.GetInt(); - StartingTime = DamoclesStartingTime.GetInt(); + LastUpdate[id] = now; + if (!Timer.ContainsKey(id)) Timer[id] = StartingTime + 8; - Timer = []; - LastUpdate = []; - PreviouslyEnteredVents = []; - CountRepairSabotage = true; + Timer[id]--; + + if (Timer[id] < 0) + { + Timer[id] = 0; + pc.Suicide(); + + if (pc.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); } - public static void Update(PlayerControl pc) - { - byte id = pc.PlayerId; - long now = Utils.TimeStamp; - if ((LastUpdate.TryGetValue(id, out long ts) && ts >= now) || !GameStates.IsInTask || pc == null) return; + if (pc.IsNonHostModClient()) SendRPC(id); - if (!pc.IsAlive()) - { - Main.PlayerStates[id].RemoveSubRole(CustomRoles.Damocles); - return; - } + if (!pc.IsModClient()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } - LastUpdate[id] = now; - if (!Timer.ContainsKey(id)) Timer[id] = StartingTime + 8; + public static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; - Timer[id]--; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncDamoclesTimer, SendOption.Reliable); + writer.Write(playerId); + writer.Write(Timer[playerId]); + writer.Write(LastUpdate[playerId].ToString()); + writer.Write(PreviouslyEnteredVents[playerId].Count); - if (Timer[id] < 0) - { - Timer[id] = 0; - pc.Suicide(); + if (PreviouslyEnteredVents[playerId].Count > 0) + foreach (int vent in PreviouslyEnteredVents[playerId].ToArray()) + writer.Write(vent); - if (pc.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - if (pc.IsNonHostModClient()) SendRPC(id); + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + Timer[playerId] = reader.ReadInt32(); + LastUpdate[playerId] = long.Parse(reader.ReadString()); + int elements = reader.ReadInt32(); - if (!pc.IsModClient()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } + if (elements > 0) + for (var i = 0; i < elements; i++) + PreviouslyEnteredVents[playerId].Add(reader.ReadInt32()); + } - public static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; + public static void OnMurder(byte id) + { + Timer[id] += TimeAfterKill; + } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncDamoclesTimer, SendOption.Reliable); - writer.Write(playerId); - writer.Write(Timer[playerId]); - writer.Write(LastUpdate[playerId].ToString()); - writer.Write(PreviouslyEnteredVents[playerId].Count); + public static void OnOtherImpostorMurder() + { + AdjustTime(10); + } - if (PreviouslyEnteredVents[playerId].Count > 0) - foreach (int vent in PreviouslyEnteredVents[playerId].ToArray()) - writer.Write(vent); + public static void OnEnterVent(byte id, int ventId) + { + if (!PreviouslyEnteredVents.ContainsKey(id)) PreviouslyEnteredVents[id] = []; - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + if (PreviouslyEnteredVents[id].Contains(ventId)) return; - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - Timer[playerId] = reader.ReadInt32(); - LastUpdate[playerId] = long.Parse(reader.ReadString()); - int elements = reader.ReadInt32(); + PreviouslyEnteredVents[id].Add(ventId); + Timer[id] += 10; + } - if (elements > 0) - for (var i = 0; i < elements; i++) - PreviouslyEnteredVents[playerId].Add(reader.ReadInt32()); - } + public static void AfterMeetingTasks() + { + PreviouslyEnteredVents.Clear(); - public static void OnMurder(byte id) - { - Timer[id] += TimeAfterKill; - } + AdjustTime(TimeAfterMeeting); + CountRepairSabotage = true; + } - public static void OnOtherImpostorMurder() - { - AdjustTime(10); - } + public static void OnMeetingStart() + { + AdjustTime(9); + } - public static void OnEnterVent(byte id, int ventId) - { - if (!PreviouslyEnteredVents.ContainsKey(id)) PreviouslyEnteredVents[id] = []; + public static void OnCrewmateEjected() + { + AdjustTimeByPercent(1.3); + } - if (PreviouslyEnteredVents[id].Contains(ventId)) return; + public static void OnRepairSabotage(byte id) + { + Timer[id] -= 15; + } - PreviouslyEnteredVents[id].Add(ventId); - Timer[id] += 10; - } + public static void OnImpostorDeath() + { + AdjustTime(-20); + } - public static void AfterMeetingTasks() - { - PreviouslyEnteredVents.Clear(); + public static void OnReport(byte id) + { + Timer[id] = (int)Math.Round(Timer[id] * 0.9); + } - AdjustTime(TimeAfterMeeting); - CountRepairSabotage = true; - } + public static void OnImpostorEjected() + { + AdjustTimeByPercent(0.8); + } - public static void OnMeetingStart() - { - AdjustTime(9); - } + private static void AdjustTime(int change) + { + Timer.AdjustAllValues(x => x + change); + } - public static void OnCrewmateEjected() - { - AdjustTimeByPercent(1.3); - } + private static void AdjustTimeByPercent(double percent) + { + Timer.AdjustAllValues(x => (int)Math.Round(x * percent)); + } - public static void OnRepairSabotage(byte id) - { - Timer[id] -= 15; - } - - public static void OnImpostorDeath() - { - AdjustTime(-20); - } - - public static void OnReport(byte id) - { - Timer[id] = (int)Math.Round(Timer[id] * 0.9); - } - - public static void OnImpostorEjected() - { - AdjustTimeByPercent(0.8); - } - - private static void AdjustTime(int change) - { - Timer.AdjustAllValues(x => x + change); - } - - private static void AdjustTimeByPercent(double percent) - { - Timer.AdjustAllValues(x => (int)Math.Round(x * percent)); - } - - public static string GetProgressText(byte id) - { - return string.Format(GetString("DamoclesTimeLeft"), Timer.GetValueOrDefault(id, StartingTime)); - } + public static string GetProgressText(byte id) + { + return string.Format(GetString("DamoclesTimeLeft"), Timer.GetValueOrDefault(id, StartingTime)); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/DeadlyQuota.cs b/Roles/AddOns/Impostor/DeadlyQuota.cs index 95ff67245..aa1dfbe77 100644 --- a/Roles/AddOns/Impostor/DeadlyQuota.cs +++ b/Roles/AddOns/Impostor/DeadlyQuota.cs @@ -1,15 +1,14 @@ -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class DeadlyQuota : IAddon { - internal class DeadlyQuota : IAddon + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.ImpOnly; + Options.SetupAdtRoleOptions(14650, CustomRoles.DeadlyQuota, canSetNum: true); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(14650, CustomRoles.DeadlyQuota, canSetNum: true); - - Options.DQNumOfKillsNeeded = new IntegerOptionItem(14660, "DQNumOfKillsNeeded", new(1, 14, 1), 3, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DeadlyQuota]); - } + Options.DQNumOfKillsNeeded = new IntegerOptionItem(14660, "DQNumOfKillsNeeded", new(1, 14, 1), 3, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DeadlyQuota]); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Egoist.cs b/Roles/AddOns/Impostor/Egoist.cs index 3c2c59e3a..f6b065032 100644 --- a/Roles/AddOns/Impostor/Egoist.cs +++ b/Roles/AddOns/Impostor/Egoist.cs @@ -1,15 +1,14 @@ -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class Egoist : IAddon { - internal class Egoist : IAddon + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.ImpOnly; + Options.SetupAdtRoleOptions(18900, CustomRoles.Egoist, canSetNum: true, tab: TabGroup.Addons); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(18900, CustomRoles.Egoist, canSetNum: true, tab: TabGroup.Addons); - - Options.ImpEgoistVisibalToAllies = new BooleanOptionItem(18912, "ImpEgoistVisibalToAllies", true, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Egoist]); - } + Options.ImpEgoistVisibalToAllies = new BooleanOptionItem(18912, "ImpEgoistVisibalToAllies", true, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Egoist]); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/LastImpostor.cs b/Roles/AddOns/Impostor/LastImpostor.cs index c0c540f27..2f8f13f5c 100644 --- a/Roles/AddOns/Impostor/LastImpostor.cs +++ b/Roles/AddOns/Impostor/LastImpostor.cs @@ -1,71 +1,70 @@ -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +public class LastImpostor : IAddon { - public class LastImpostor : IAddon + private const int Id = 15900; + public static byte CurrentId = byte.MaxValue; + + private static OptionItem Reduction; + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - private const int Id = 15900; - public static byte CurrentId = byte.MaxValue; + Options.SetupSingleRoleOptions(Id, TabGroup.Addons, CustomRoles.LastImpostor); - private static OptionItem Reduction; - public AddonTypes Type => AddonTypes.ImpOnly; + Reduction = new FloatOptionItem(Id + 15, "SansReduceKillCooldown", new(5f, 95f, 5f), 20f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.LastImpostor]) + .SetValueFormat(OptionFormat.Percent); + } - public void SetupCustomOption() + public static void Init() + { + CurrentId = byte.MaxValue; + } + + private static void Add(byte id) + { + CurrentId = id; + } + + public static void SetKillCooldown() + { + if (CurrentId == byte.MaxValue) return; + + if (!Main.AllPlayerKillCooldown.TryGetValue(CurrentId, out float cd)) return; + + float minus = cd * (Reduction.GetFloat() / 100f); + Main.AllPlayerKillCooldown[CurrentId] -= minus; + Logger.Info($"{CurrentId.ColoredPlayerName().RemoveHtmlTags()}'s cooldown is {Main.AllPlayerKillCooldown[CurrentId]}s", "LastImpostor"); + } + + private static bool CanBeLastImpostor(PlayerControl pc) + { + return pc.IsAlive() && !pc.Is(CustomRoles.LastImpostor) && pc.Is(CustomRoleTypes.Impostor); + } + + public static void SetSubRole() + { + if (CurrentId != byte.MaxValue || !AmongUsClient.Instance.AmHost) return; + + if (!CustomGameMode.Standard.IsActiveOrIntegrated() || !CustomRoles.LastImpostor.IsEnable() || Main.AliveImpostorCount != 1) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - Options.SetupSingleRoleOptions(Id, TabGroup.Addons, CustomRoles.LastImpostor); - - Reduction = new FloatOptionItem(Id + 15, "SansReduceKillCooldown", new(5f, 95f, 5f), 20f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.LastImpostor]) - .SetValueFormat(OptionFormat.Percent); - } - - public static void Init() - { - CurrentId = byte.MaxValue; - } - - private static void Add(byte id) - { - CurrentId = id; - } - - public static void SetKillCooldown() - { - if (CurrentId == byte.MaxValue) return; - - if (!Main.AllPlayerKillCooldown.TryGetValue(CurrentId, out float cd)) return; - - float minus = cd * (Reduction.GetFloat() / 100f); - Main.AllPlayerKillCooldown[CurrentId] -= minus; - Logger.Info($"{CurrentId.ColoredPlayerName().RemoveHtmlTags()}'s cooldown is {Main.AllPlayerKillCooldown[CurrentId]}s", "LastImpostor"); - } - - private static bool CanBeLastImpostor(PlayerControl pc) - { - return pc.IsAlive() && !pc.Is(CustomRoles.LastImpostor) && pc.Is(CustomRoleTypes.Impostor); - } - - public static void SetSubRole() - { - if (CurrentId != byte.MaxValue || !AmongUsClient.Instance.AmHost) return; - - if (!CustomGameMode.Standard.IsActiveOrIntegrated() || !CustomRoles.LastImpostor.IsEnable() || Main.AliveImpostorCount != 1) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (CanBeLastImpostor(pc)) { - if (CanBeLastImpostor(pc)) - { - pc.RpcSetCustomRole(CustomRoles.LastImpostor); - Add(pc.PlayerId); - SetKillCooldown(); - pc.SyncSettings(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + pc.RpcSetCustomRole(CustomRoles.LastImpostor); + Add(pc.PlayerId); + SetKillCooldown(); + pc.SyncSettings(); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - if (Main.KillTimers.TryGetValue(pc.PlayerId, out float timer) && - Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out float cd) && - timer > cd) - pc.SetKillCooldown(); + if (Main.KillTimers.TryGetValue(pc.PlayerId, out float timer) && + Main.AllPlayerKillCooldown.TryGetValue(pc.PlayerId, out float cd) && + timer > cd) + pc.SetKillCooldown(); - break; - } + break; } } } diff --git a/Roles/AddOns/Impostor/Madmate.cs b/Roles/AddOns/Impostor/Madmate.cs index 78f6e8c37..c6e61474c 100644 --- a/Roles/AddOns/Impostor/Madmate.cs +++ b/Roles/AddOns/Impostor/Madmate.cs @@ -1,48 +1,47 @@ using static EHR.Options; -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class Madmate : IAddon { - internal class Madmate : IAddon + public AddonTypes Type => AddonTypes.Mixed; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.Mixed; + SetupAdtRoleOptions(15800, CustomRoles.Madmate, canSetNum: true, canSetChance: false, allowZeroCount: true); - public void SetupCustomOption() - { - SetupAdtRoleOptions(15800, CustomRoles.Madmate, canSetNum: true, canSetChance: false, allowZeroCount: true); + MadmateSpawnMode = new StringOptionItem(15810, "MadmateSpawnMode", MadmateSpawnModeStrings, 0, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - MadmateSpawnMode = new StringOptionItem(15810, "MadmateSpawnMode", MadmateSpawnModeStrings, 0, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + MadmateCountMode = new StringOptionItem(15811, "MadmateCountMode", MadmateCountModeStrings, 0, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - MadmateCountMode = new StringOptionItem(15811, "MadmateCountMode", MadmateCountModeStrings, 0, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + SheriffCanBeMadmate = new BooleanOptionItem(15812, "SheriffCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - SheriffCanBeMadmate = new BooleanOptionItem(15812, "SheriffCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + MayorCanBeMadmate = new BooleanOptionItem(15813, "MayorCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - MayorCanBeMadmate = new BooleanOptionItem(15813, "MayorCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + NGuesserCanBeMadmate = new BooleanOptionItem(15814, "NGuesserCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - NGuesserCanBeMadmate = new BooleanOptionItem(15814, "NGuesserCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + MarshallCanBeMadmate = new BooleanOptionItem(15815, "MarshallCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - MarshallCanBeMadmate = new BooleanOptionItem(15815, "MarshallCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + FarseerCanBeMadmate = new BooleanOptionItem(15816, "FarseerCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - FarseerCanBeMadmate = new BooleanOptionItem(15816, "FarseerCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + PresidentCanBeMadmate = new BooleanOptionItem(15817, "PresidentCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - PresidentCanBeMadmate = new BooleanOptionItem(15817, "PresidentCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + SnitchCanBeMadmate = new BooleanOptionItem(15818, "SnitchCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - SnitchCanBeMadmate = new BooleanOptionItem(15818, "SnitchCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); + MadSnitchTasks = new IntegerOptionItem(15819, "MadSnitchTasks", new(0, 90, 1), 3, TabGroup.Addons) + .SetParent(SnitchCanBeMadmate) + .SetValueFormat(OptionFormat.Pieces); - MadSnitchTasks = new IntegerOptionItem(15819, "MadSnitchTasks", new(0, 90, 1), 3, TabGroup.Addons) - .SetParent(SnitchCanBeMadmate) - .SetValueFormat(OptionFormat.Pieces); - - JudgeCanBeMadmate = new BooleanOptionItem(15820, "JudgeCanBeMadmate", false, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); - } + JudgeCanBeMadmate = new BooleanOptionItem(15820, "JudgeCanBeMadmate", false, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Madmate]); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Mare.cs b/Roles/AddOns/Impostor/Mare.cs index 7b035b1e1..b7f1526e9 100644 --- a/Roles/AddOns/Impostor/Mare.cs +++ b/Roles/AddOns/Impostor/Mare.cs @@ -1,29 +1,28 @@ using static EHR.Options; -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class Mare : IAddon { - internal class Mare : IAddon + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.ImpOnly; + SetupAdtRoleOptions(1600, CustomRoles.Mare, canSetNum: true, tab: TabGroup.Addons); - public void SetupCustomOption() - { - SetupAdtRoleOptions(1600, CustomRoles.Mare, canSetNum: true, tab: TabGroup.Addons); + MareKillCD = new FloatOptionItem(1609, "KillCooldown", new(0f, 60f, 1f), 15f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) + .SetValueFormat(OptionFormat.Seconds); - MareKillCD = new FloatOptionItem(1609, "KillCooldown", new(0f, 60f, 1f), 15f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) - .SetValueFormat(OptionFormat.Seconds); + MareKillCDNormally = new FloatOptionItem(1606, "KillCooldownNormally", new(0f, 90f, 1f), 40f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) + .SetValueFormat(OptionFormat.Seconds); - MareKillCDNormally = new FloatOptionItem(1606, "KillCooldownNormally", new(0f, 90f, 1f), 40f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) - .SetValueFormat(OptionFormat.Seconds); + MareHasIncreasedSpeed = new BooleanOptionItem(1607, "MareHasIncreasedSpeed", true, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]); - MareHasIncreasedSpeed = new BooleanOptionItem(1607, "MareHasIncreasedSpeed", true, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]); - - MareSpeedDuringLightsOut = new FloatOptionItem(1608, "MareSpeedDuringLightsOut", new(0.5f, 3f, 0.05f), 1.75f, TabGroup.Addons) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) - .SetValueFormat(OptionFormat.Multiplier); - } + MareSpeedDuringLightsOut = new FloatOptionItem(1608, "MareSpeedDuringLightsOut", new(0.5f, 3f, 0.05f), 1.75f, TabGroup.Addons) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mare]) + .SetValueFormat(OptionFormat.Multiplier); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Mimic.cs b/Roles/AddOns/Impostor/Mimic.cs index da6b24f69..978c445ff 100644 --- a/Roles/AddOns/Impostor/Mimic.cs +++ b/Roles/AddOns/Impostor/Mimic.cs @@ -1,15 +1,14 @@ -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class Mimic : IAddon { - internal class Mimic : IAddon + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.ImpOnly; + Options.SetupAdtRoleOptions(16000, CustomRoles.Mimic, canSetNum: true, tab: TabGroup.Addons); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(16000, CustomRoles.Mimic, canSetNum: true, tab: TabGroup.Addons); - - Options.MimicCanSeeDeadRoles = new BooleanOptionItem(16010, "MimicCanSeeDeadRoles", true, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mimic]); - } + Options.MimicCanSeeDeadRoles = new BooleanOptionItem(16010, "MimicCanSeeDeadRoles", true, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mimic]); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Swift.cs b/Roles/AddOns/Impostor/Swift.cs index c91b7dfcd..9f1fbee56 100644 --- a/Roles/AddOns/Impostor/Swift.cs +++ b/Roles/AddOns/Impostor/Swift.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Impostor -{ - internal class Swift : IAddon - { - public AddonTypes Type => AddonTypes.ImpOnly; +namespace EHR.AddOns.Impostor; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(16050, CustomRoles.Swift, canSetNum: true, tab: TabGroup.Addons); - } +internal class Swift : IAddon +{ + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(16050, CustomRoles.Swift, canSetNum: true, tab: TabGroup.Addons); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/Taskcounter.cs b/Roles/AddOns/Impostor/Taskcounter.cs index d9e107c2b..299151b19 100644 --- a/Roles/AddOns/Impostor/Taskcounter.cs +++ b/Roles/AddOns/Impostor/Taskcounter.cs @@ -1,12 +1,11 @@ -namespace EHR.AddOns.Impostor -{ - internal class Taskcounter : IAddon - { - public AddonTypes Type => AddonTypes.ImpOnly; +namespace EHR.AddOns.Impostor; - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(14370, CustomRoles.Taskcounter, canSetNum: true); - } +internal class Taskcounter : IAddon +{ + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() + { + Options.SetupAdtRoleOptions(14370, CustomRoles.Taskcounter, canSetNum: true); } } \ No newline at end of file diff --git a/Roles/AddOns/Impostor/TicketsStealer.cs b/Roles/AddOns/Impostor/TicketsStealer.cs index b7a3bd452..b32b7e054 100644 --- a/Roles/AddOns/Impostor/TicketsStealer.cs +++ b/Roles/AddOns/Impostor/TicketsStealer.cs @@ -1,15 +1,14 @@ -namespace EHR.AddOns.Impostor +namespace EHR.AddOns.Impostor; + +internal class TicketsStealer : IAddon { - internal class TicketsStealer : IAddon + public AddonTypes Type => AddonTypes.ImpOnly; + + public void SetupCustomOption() { - public AddonTypes Type => AddonTypes.ImpOnly; + Options.SetupAdtRoleOptions(16100, CustomRoles.TicketsStealer, canSetNum: true, tab: TabGroup.Addons); - public void SetupCustomOption() - { - Options.SetupAdtRoleOptions(16100, CustomRoles.TicketsStealer, canSetNum: true, tab: TabGroup.Addons); - - Options.TicketsPerKill = new FloatOptionItem(16110, "TicketsPerKill", new(0.1f, 90f, 0.1f), 0.5f, TabGroup.Addons) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TicketsStealer]); - } + Options.TicketsPerKill = new FloatOptionItem(16110, "TicketsPerKill", new(0.1f, 90f, 0.1f), 0.5f, TabGroup.Addons) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TicketsStealer]); } } \ No newline at end of file diff --git a/Roles/Crewmate/Addict.cs b/Roles/Crewmate/Addict.cs index a87b8812d..18479b94b 100644 --- a/Roles/Crewmate/Addict.cs +++ b/Roles/Crewmate/Addict.cs @@ -4,153 +4,152 @@ using AmongUs.GameOptions; using EHR.Modules; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; + +public class Addict : RoleBase { - using static Options; + private const int Id = 5200; + private static List PlayerIdList = []; - public class Addict : RoleBase + public static OptionItem VentCooldown; + public static OptionItem TimeLimit; + + public static OptionItem ImmortalTimeAfterVent; + + public static OptionItem SpeedWhileImmortal; + public static OptionItem FreezeTimeAfterImmortal; + + private static float DefaultSpeed; + private float ImmortalTimer = 420f; + + private float SuicideTimer = -10f; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 5200; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Addict); - public static OptionItem VentCooldown; - public static OptionItem TimeLimit; + VentCooldown = new FloatOptionItem(Id + 11, "VentCooldown", new(5f, 70f, 1f), 40f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) + .SetValueFormat(OptionFormat.Seconds); - public static OptionItem ImmortalTimeAfterVent; + TimeLimit = new FloatOptionItem(Id + 12, "SerialKillerLimit", new(5f, 75f, 1f), 45f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) + .SetValueFormat(OptionFormat.Seconds); - public static OptionItem SpeedWhileImmortal; - public static OptionItem FreezeTimeAfterImmortal; + ImmortalTimeAfterVent = new FloatOptionItem(Id + 13, "AddictInvulnerbilityTimeAfterVent", new(0f, 30f, 1f), 10f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) + .SetValueFormat(OptionFormat.Seconds); - private static float DefaultSpeed; - private float ImmortalTimer = 420f; + SpeedWhileImmortal = new FloatOptionItem(Id + 14, "AddictSpeedWhileInvulnerble", new(0.25f, 5f, 0.25f), 1.75f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) + .SetValueFormat(OptionFormat.Multiplier); - private float SuicideTimer = -10f; + FreezeTimeAfterImmortal = new FloatOptionItem(Id + 15, "AddictFreezeTimeAfterInvulnerbility", new(0f, 10f, 1f), 3f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) + .SetValueFormat(OptionFormat.Seconds); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + SuicideTimer = -10f; + ImmortalTimer = 420f; + DefaultSpeed = new(); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + SuicideTimer = -10f; + ImmortalTimer = 420f; + DefaultSpeed = Main.AllPlayerSpeed[playerId]; + } + + private bool IsImmortal(PlayerControl player) + { + return player.Is(CustomRoles.Addict) && ImmortalTimer <= ImmortalTimeAfterVent.GetFloat(); + } + + public override void OnReportDeadBody() + { + foreach (byte player in PlayerIdList.ToArray()) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Addict); - - VentCooldown = new FloatOptionItem(Id + 11, "VentCooldown", new(5f, 70f, 1f), 40f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) - .SetValueFormat(OptionFormat.Seconds); - - TimeLimit = new FloatOptionItem(Id + 12, "SerialKillerLimit", new(5f, 75f, 1f), 45f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) - .SetValueFormat(OptionFormat.Seconds); - - ImmortalTimeAfterVent = new FloatOptionItem(Id + 13, "AddictInvulnerbilityTimeAfterVent", new(0f, 30f, 1f), 10f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) - .SetValueFormat(OptionFormat.Seconds); - - SpeedWhileImmortal = new FloatOptionItem(Id + 14, "AddictSpeedWhileInvulnerble", new(0.25f, 5f, 0.25f), 1.75f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) - .SetValueFormat(OptionFormat.Multiplier); - - FreezeTimeAfterImmortal = new FloatOptionItem(Id + 15, "AddictFreezeTimeAfterInvulnerbility", new(0f, 10f, 1f), 3f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Addict]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; SuicideTimer = -10f; ImmortalTimer = 420f; - DefaultSpeed = new(); - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - SuicideTimer = -10f; - ImmortalTimer = 420f; - DefaultSpeed = Main.AllPlayerSpeed[playerId]; - } - - private bool IsImmortal(PlayerControl player) - { - return player.Is(CustomRoles.Addict) && ImmortalTimer <= ImmortalTimeAfterVent.GetFloat(); - } - - public override void OnReportDeadBody() - { - foreach (byte player in PlayerIdList.ToArray()) - { - SuicideTimer = -10f; - ImmortalTimer = 420f; - Main.AllPlayerSpeed[player] = DefaultSpeed; - } - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !IsImmortal(target); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask || !IsEnable || Math.Abs(SuicideTimer - -10f) < 0.5f || !player.IsAlive()) return; - - if (SuicideTimer >= TimeLimit.GetFloat()) - { - player.Suicide(); - SuicideTimer = -10f; - - if (player.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - else if (Mathf.Approximately(SuicideTimer + 8, TimeLimit.GetFloat())) - player.Notify(Translator.GetString("AddictWarning"), 8f); - else - { - SuicideTimer += Time.fixedDeltaTime; - - if (IsImmortal(player)) - ImmortalTimer += Time.fixedDeltaTime; - else if (Math.Abs(ImmortalTimer - 420f) > 0.5f && FreezeTimeAfterImmortal.GetFloat() > 0) - { - AddictGetDown(player); - ImmortalTimer = 420f; - } - } - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (!pc.Is(CustomRoles.Addict)) return; - - SuicideTimer = 0f; - ImmortalTimer = 0f; - - Main.AllPlayerSpeed[pc.PlayerId] = SpeedWhileImmortal.GetFloat(); - pc.MarkDirtySettings(); - } - - private static void AddictGetDown(PlayerControl addict) - { - Main.AllPlayerSpeed[addict.PlayerId] = Main.MinSpeed; - ReportDeadBodyPatch.CanReport[addict.PlayerId] = false; - addict.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[addict.PlayerId] = DefaultSpeed; - ReportDeadBodyPatch.CanReport[addict.PlayerId] = true; - addict.MarkDirtySettings(); - }, FreezeTimeAfterImmortal.GetFloat(), "AddictGetDown"); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton?.OverrideText(Translator.GetString("AddictVentButtonText")); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + Main.AllPlayerSpeed[player] = DefaultSpeed; } } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !IsImmortal(target); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask || !IsEnable || Math.Abs(SuicideTimer - -10f) < 0.5f || !player.IsAlive()) return; + + if (SuicideTimer >= TimeLimit.GetFloat()) + { + player.Suicide(); + SuicideTimer = -10f; + + if (player.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + else if (Mathf.Approximately(SuicideTimer + 8, TimeLimit.GetFloat())) + player.Notify(Translator.GetString("AddictWarning"), 8f); + else + { + SuicideTimer += Time.fixedDeltaTime; + + if (IsImmortal(player)) + ImmortalTimer += Time.fixedDeltaTime; + else if (Math.Abs(ImmortalTimer - 420f) > 0.5f && FreezeTimeAfterImmortal.GetFloat() > 0) + { + AddictGetDown(player); + ImmortalTimer = 420f; + } + } + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (!pc.Is(CustomRoles.Addict)) return; + + SuicideTimer = 0f; + ImmortalTimer = 0f; + + Main.AllPlayerSpeed[pc.PlayerId] = SpeedWhileImmortal.GetFloat(); + pc.MarkDirtySettings(); + } + + private static void AddictGetDown(PlayerControl addict) + { + Main.AllPlayerSpeed[addict.PlayerId] = Main.MinSpeed; + ReportDeadBodyPatch.CanReport[addict.PlayerId] = false; + addict.MarkDirtySettings(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[addict.PlayerId] = DefaultSpeed; + ReportDeadBodyPatch.CanReport[addict.PlayerId] = true; + addict.MarkDirtySettings(); + }, FreezeTimeAfterImmortal.GetFloat(), "AddictGetDown"); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(Translator.GetString("AddictVentButtonText")); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Adrenaline.cs b/Roles/Crewmate/Adrenaline.cs index 29f864522..ba8360d87 100644 --- a/Roles/Crewmate/Adrenaline.cs +++ b/Roles/Crewmate/Adrenaline.cs @@ -1,122 +1,121 @@ using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Adrenaline : RoleBase { - public class Adrenaline : RoleBase + public static bool On; + + private static OptionItem Time; + private static OptionItem MaxSurvives; + private static OptionItem MinTasksRequired; + private static OptionItem CanCallMeetingDuringTimer; + private static OptionItem SpeedIncreaseDuringTimer; + + private byte AdrenalineId; + private float DefaultSpeed; + private long LastUpdate; + private int Timer; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 648300; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Adrenaline); - private static OptionItem Time; - private static OptionItem MaxSurvives; - private static OptionItem MinTasksRequired; - private static OptionItem CanCallMeetingDuringTimer; - private static OptionItem SpeedIncreaseDuringTimer; + Time = new IntegerOptionItem(++id, "Adrenaline.Time", new(1, 120, 1), 15, TabGroup.CrewmateRoles) + .SetValueFormat(OptionFormat.Seconds) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - private byte AdrenalineId; - private float DefaultSpeed; - private long LastUpdate; - private int Timer; - public override bool IsEnable => On; + MaxSurvives = new IntegerOptionItem(++id, "Adrenaline.MaxSurvives", new(1, 5, 1), 1, TabGroup.CrewmateRoles) + .SetValueFormat(OptionFormat.Times) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - public override void SetupCustomOption() + MinTasksRequired = new IntegerOptionItem(++id, "Adrenaline.MinTasksRequired", new(1, 5, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); + + CanCallMeetingDuringTimer = new BooleanOptionItem(++id, "Adrenaline.CanCallMeetingDuringTimer", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); + + SpeedIncreaseDuringTimer = new FloatOptionItem(++id, "Adrenaline.SpeedDuringTimer", new(0f, 3f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + AdrenalineId = playerId; + Timer = 0; + Utils.SendRPC(CustomRPC.SyncRoleData, playerId, Timer); + DefaultSpeed = Main.AllPlayerSpeed[playerId]; + playerId.SetAbilityUseLimit(MaxSurvives.GetInt()); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurderAsTarget(killer, target) || target.GetAbilityUseLimit() < 1 || target.GetTaskState().CompletedTasksCount < MinTasksRequired.GetInt()) return true; + + target.RpcRemoveAbilityUse(); + Timer = Time.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, Timer); + Main.AllPlayerSpeed[target.PlayerId] += SpeedIncreaseDuringTimer.GetFloat(); + target.MarkDirtySettings(); + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (Timer == 0 || !pc.IsAlive()) return; + + if (GameStates.IsMeeting) { - var id = 648300; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Adrenaline); - - Time = new IntegerOptionItem(++id, "Adrenaline.Time", new(1, 120, 1), 15, TabGroup.CrewmateRoles) - .SetValueFormat(OptionFormat.Seconds) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - - MaxSurvives = new IntegerOptionItem(++id, "Adrenaline.MaxSurvives", new(1, 5, 1), 1, TabGroup.CrewmateRoles) - .SetValueFormat(OptionFormat.Times) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - - MinTasksRequired = new IntegerOptionItem(++id, "Adrenaline.MinTasksRequired", new(1, 5, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - - CanCallMeetingDuringTimer = new BooleanOptionItem(++id, "Adrenaline.CanCallMeetingDuringTimer", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - - SpeedIncreaseDuringTimer = new FloatOptionItem(++id, "Adrenaline.SpeedDuringTimer", new(0f, 3f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetValueFormat(OptionFormat.Multiplier) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adrenaline]); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - AdrenalineId = playerId; Timer = 0; - Utils.SendRPC(CustomRPC.SyncRoleData, playerId, Timer); - DefaultSpeed = Main.AllPlayerSpeed[playerId]; - playerId.SetAbilityUseLimit(MaxSurvives.GetInt()); + Main.AllPlayerSpeed[pc.PlayerId] = DefaultSpeed; + pc.MarkDirtySettings(); + return; } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + + LastUpdate = now; + + Timer--; + + if (Timer <= 0) { - if (!base.OnCheckMurderAsTarget(killer, target) || target.GetAbilityUseLimit() < 1 || target.GetTaskState().CompletedTasksCount < MinTasksRequired.GetInt()) return true; + Timer = 0; + pc.Suicide(); - target.RpcRemoveAbilityUse(); - Timer = Time.GetInt(); - Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, Timer); - Main.AllPlayerSpeed[target.PlayerId] += SpeedIncreaseDuringTimer.GetFloat(); - target.MarkDirtySettings(); - return false; + if (pc.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); } - public override void OnFixedUpdate(PlayerControl pc) - { - if (Timer == 0 || !pc.IsAlive()) return; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, Timer); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } - if (GameStates.IsMeeting) - { - Timer = 0; - Main.AllPlayerSpeed[pc.PlayerId] = DefaultSpeed; - pc.MarkDirtySettings(); - return; - } + public static bool CanCallMeeting(PlayerControl pc) + { + return Main.PlayerStates[pc.PlayerId].Role is Adrenaline an && (an.Timer == 0 || CanCallMeetingDuringTimer.GetBool()); + } - long now = Utils.TimeStamp; - if (now == LastUpdate) return; + public void ReceiveRPC(MessageReader reader) + { + Timer = reader.ReadPackedInt32(); + } - LastUpdate = now; + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != AdrenalineId || meeting || (seer.IsModClient() && !hud) || Timer == 0) return string.Empty; - Timer--; - - if (Timer <= 0) - { - Timer = 0; - pc.Suicide(); - - if (pc.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, Timer); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public static bool CanCallMeeting(PlayerControl pc) - { - return Main.PlayerStates[pc.PlayerId].Role is Adrenaline an && (an.Timer == 0 || CanCallMeetingDuringTimer.GetBool()); - } - - public void ReceiveRPC(MessageReader reader) - { - Timer = reader.ReadPackedInt32(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != AdrenalineId || meeting || (seer.IsModClient() && !hud) || Timer == 0) return string.Empty; - - return string.Format(Translator.GetString("Adrenaline.Suffix"), Timer); - } + return string.Format(Translator.GetString("Adrenaline.Suffix"), Timer); } } \ No newline at end of file diff --git a/Roles/Crewmate/Adventurer.cs b/Roles/Crewmate/Adventurer.cs index f7a1ac924..cda15ef6f 100644 --- a/Roles/Crewmate/Adventurer.cs +++ b/Roles/Crewmate/Adventurer.cs @@ -7,440 +7,439 @@ using EHR.Patches; using Hazel; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Adventurer : RoleBase { - internal class Adventurer : RoleBase + public enum Resource { - public enum Resource + TaskCompletion, + Random, + DeadBody, + ShapeshiftSkin, + LightsFix, + Grouping + } + + public enum Weapon + { + Gun, + Shield, + Lantern, + Portal, + Wrench, + Proxy, + Prediction, + RNG + } + + public static bool On; + + public static readonly Dictionary ResourceDisplayData = new() + { + { Resource.TaskCompletion, ('\u2756', Color.blue) }, + { Resource.Random, ('Θ', Color.cyan) }, + { Resource.DeadBody, ('\u2673', Color.magenta) }, + { Resource.ShapeshiftSkin, ('⁂', Color.gray) }, + { Resource.LightsFix, ('\u2600', Color.yellow) }, + { Resource.Grouping, ('\u25cb', Color.green) } + }; + + private static readonly Dictionary> Ingredients = new() + { + { Weapon.Gun, [(Resource.DeadBody, 1), (Resource.TaskCompletion, 1)] }, + { Weapon.Shield, [(Resource.TaskCompletion, 3)] }, + { Weapon.Lantern, [(Resource.LightsFix, 1), (Resource.TaskCompletion, 1)] }, + { Weapon.Portal, [(Resource.Random, 2)] }, + { Weapon.Wrench, [(Resource.LightsFix, 1), (Resource.TaskCompletion, 2)] }, + { Weapon.Proxy, [(Resource.Grouping, 2), (Resource.TaskCompletion, 1)] }, + { Weapon.Prediction, [(Resource.DeadBody, 1), (Resource.ShapeshiftSkin, 2), (Resource.TaskCompletion, 1)] }, + { Weapon.RNG, [(Resource.Random, 2), (Resource.TaskCompletion, 2)] } + }; + + private static OptionItem IncreasedVisionDuration; + private static readonly Dictionary WeaponEnabledSettings = []; + + private static List EnabledWeapons = []; + + private readonly Dictionary ResourceCounts = []; + public List ActiveWeapons; + + private PlayerControl AdventurerPC; + + private int Count; + private bool InCraftingMode; + private long LastGroupingResourceTimeStamp; + + private long LastRandomResourceTimeStamp; + + private List OrderedWeapons; + private Dictionary ResourceLocations; + private HashSet RevealedPlayers; + private Weapon SelectedWeaponToCraft; + private HashSet ShieldedPlayers; + public override bool IsEnable => On; + + private static void HideObject(Resource resource) + { + CustomNetObject.AllObjects.FirstOrDefault(x => x is AdventurerItem a && a.Resource == resource)?.Despawn(); + } + + private static OptionItem CreateWeaponEnabledSetting(int id, Weapon weapon) + { + return new BooleanOptionItem(id, $"AdventurerWeaponEnabled.{weapon}", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adventurer]); + } + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(11330, TabGroup.CrewmateRoles, CustomRoles.Adventurer); + + IncreasedVisionDuration = new IntegerOptionItem(11332, "AdventurerIncreasedVisionDuration", new(1, 60, 1), 30, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adventurer]) + .SetValueFormat(OptionFormat.Seconds); + + foreach (Weapon weapon in Enum.GetValues()) WeaponEnabledSettings[weapon] = CreateWeaponEnabledSetting(11333 + (int)weapon, weapon); + } + + public override void Add(byte playerId) + { + On = true; + AdventurerPC = Utils.GetPlayerById(playerId); + + InCraftingMode = false; + SelectedWeaponToCraft = new(); + + OrderedWeapons = []; + ActiveWeapons = []; + ShieldedPlayers = []; + RevealedPlayers = []; + + LastRandomResourceTimeStamp = Utils.TimeStamp + 8; + LastGroupingResourceTimeStamp = Utils.TimeStamp + 20; + ResourceLocations = []; + + foreach (Resource resource in Enum.GetValues()) ResourceCounts[resource] = 0; + } + + public override void Init() + { + On = false; + + EnabledWeapons = WeaponEnabledSettings + .Where(x => x.Value.GetBool()) + .Select(x => x.Key) + .ToList(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnExitVent(PlayerControl pc, Vent vent) + { + InCraftingMode = !InCraftingMode; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, InCraftingMode); + + switch (InCraftingMode) { - TaskCompletion, - Random, - DeadBody, - ShapeshiftSkin, - LightsFix, - Grouping - } - - public enum Weapon - { - Gun, - Shield, - Lantern, - Portal, - Wrench, - Proxy, - Prediction, - RNG - } - - public static bool On; - - public static readonly Dictionary ResourceDisplayData = new() - { - { Resource.TaskCompletion, ('\u2756', Color.blue) }, - { Resource.Random, ('Θ', Color.cyan) }, - { Resource.DeadBody, ('\u2673', Color.magenta) }, - { Resource.ShapeshiftSkin, ('⁂', Color.gray) }, - { Resource.LightsFix, ('\u2600', Color.yellow) }, - { Resource.Grouping, ('\u25cb', Color.green) } - }; - - private static readonly Dictionary> Ingredients = new() - { - { Weapon.Gun, [(Resource.DeadBody, 1), (Resource.TaskCompletion, 1)] }, - { Weapon.Shield, [(Resource.TaskCompletion, 3)] }, - { Weapon.Lantern, [(Resource.LightsFix, 1), (Resource.TaskCompletion, 1)] }, - { Weapon.Portal, [(Resource.Random, 2)] }, - { Weapon.Wrench, [(Resource.LightsFix, 1), (Resource.TaskCompletion, 2)] }, - { Weapon.Proxy, [(Resource.Grouping, 2), (Resource.TaskCompletion, 1)] }, - { Weapon.Prediction, [(Resource.DeadBody, 1), (Resource.ShapeshiftSkin, 2), (Resource.TaskCompletion, 1)] }, - { Weapon.RNG, [(Resource.Random, 2), (Resource.TaskCompletion, 2)] } - }; - - private static OptionItem IncreasedVisionDuration; - private static readonly Dictionary WeaponEnabledSettings = []; - - private static List EnabledWeapons = []; - - private readonly Dictionary ResourceCounts = []; - public List ActiveWeapons; - - private PlayerControl AdventurerPC; - - private int Count; - private bool InCraftingMode; - private long LastGroupingResourceTimeStamp; - - private long LastRandomResourceTimeStamp; - - private List OrderedWeapons; - private Dictionary ResourceLocations; - private HashSet RevealedPlayers; - private Weapon SelectedWeaponToCraft; - private HashSet ShieldedPlayers; - public override bool IsEnable => On; - - private static void HideObject(Resource resource) - { - CustomNetObject.AllObjects.FirstOrDefault(x => x is AdventurerItem a && a.Resource == resource)?.Despawn(); - } - - private static OptionItem CreateWeaponEnabledSetting(int id, Weapon weapon) - { - return new BooleanOptionItem(id, $"AdventurerWeaponEnabled.{weapon}", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adventurer]); - } - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(11330, TabGroup.CrewmateRoles, CustomRoles.Adventurer); - - IncreasedVisionDuration = new IntegerOptionItem(11332, "AdventurerIncreasedVisionDuration", new(1, 60, 1), 30, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Adventurer]) - .SetValueFormat(OptionFormat.Seconds); - - foreach (Weapon weapon in Enum.GetValues()) WeaponEnabledSettings[weapon] = CreateWeaponEnabledSetting(11333 + (int)weapon, weapon); - } - - public override void Add(byte playerId) - { - On = true; - AdventurerPC = Utils.GetPlayerById(playerId); - - InCraftingMode = false; - SelectedWeaponToCraft = new(); - - OrderedWeapons = []; - ActiveWeapons = []; - ShieldedPlayers = []; - RevealedPlayers = []; - - LastRandomResourceTimeStamp = Utils.TimeStamp + 8; - LastGroupingResourceTimeStamp = Utils.TimeStamp + 20; - ResourceLocations = []; - - foreach (Resource resource in Enum.GetValues()) ResourceCounts[resource] = 0; - } - - public override void Init() - { - On = false; - - EnabledWeapons = WeaponEnabledSettings - .Where(x => x.Value.GetBool()) - .Select(x => x.Key) - .ToList(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = 1f; - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - InCraftingMode = !InCraftingMode; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, InCraftingMode); - - switch (InCraftingMode) + case true: { - case true: - { - OrderedWeapons = [.. EnabledWeapons.OrderBy(x => !Ingredients[x].All(r => r.Count <= ResourceCounts[r.Resource]))]; - SelectedWeaponToCraft = OrderedWeapons.FirstOrDefault(); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 4, (int)SelectedWeaponToCraft); - break; - } - case false when Ingredients[SelectedWeaponToCraft].All(x => x.Count <= ResourceCounts[x.Resource]): - { - Weapon weapon = SelectedWeaponToCraft == Weapon.RNG ? EnabledWeapons.RandomElement() : SelectedWeaponToCraft; - ActiveWeapons.Add(weapon); - pc.Notify(string.Format(Translator.GetString("AdventurerWeaponCrafted"), Translator.GetString($"AdventurerGun.{weapon}"))); - - foreach ((Resource resource, int count) in Ingredients[weapon]) - { - ResourceCounts[resource] -= count; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, (int)resource, count); - } - - if (pc.IsLocalPlayer()) - Achievements.Type.HowDoICraftThisAgain.Complete(); - break; - } + OrderedWeapons = [.. EnabledWeapons.OrderBy(x => !Ingredients[x].All(r => r.Count <= ResourceCounts[r.Resource]))]; + SelectedWeaponToCraft = OrderedWeapons.FirstOrDefault(); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 4, (int)SelectedWeaponToCraft); + break; } + case false when Ingredients[SelectedWeaponToCraft].All(x => x.Count <= ResourceCounts[x.Resource]): + { + Weapon weapon = SelectedWeaponToCraft == Weapon.RNG ? EnabledWeapons.RandomElement() : SelectedWeaponToCraft; + ActiveWeapons.Add(weapon); + pc.Notify(string.Format(Translator.GetString("AdventurerWeaponCrafted"), Translator.GetString($"AdventurerGun.{weapon}"))); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + foreach ((Resource resource, int count) in Ingredients[weapon]) + { + ResourceCounts[resource] -= count; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, (int)resource, count); + } + + if (pc.IsLocalPlayer()) + Achievements.Type.HowDoICraftThisAgain.Complete(); + break; + } } - public override void OnPet(PlayerControl pc) + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnPet(PlayerControl pc) + { + switch (InCraftingMode) { - switch (InCraftingMode) - { - case true: - SelectedWeaponToCraft = OrderedWeapons[(OrderedWeapons.IndexOf(SelectedWeaponToCraft) + 1) % OrderedWeapons.Count]; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 4, (int)SelectedWeaponToCraft); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - break; - case false when ActiveWeapons.Count > 0: - PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); + case true: + SelectedWeaponToCraft = OrderedWeapons[(OrderedWeapons.IndexOf(SelectedWeaponToCraft) + 1) % OrderedWeapons.Count]; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 4, (int)SelectedWeaponToCraft); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + break; + case false when ActiveWeapons.Count > 0: + PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); - switch (ActiveWeapons[0]) - { - case Weapon.Gun when target != null: - pc.RpcCheckAndMurder(target); - RemoveAndNotify(); - break; - case Weapon.Shield when target != null: - ShieldedPlayers.Add(target.PlayerId); - RemoveAndNotify(); - break; - case Weapon.Portal: - IEnumerable e = Main.AllAlivePlayerControls.Where(x => x.PlayerId != pc.PlayerId && !x.inVent && !x.inMovingPlat && !x.onLadder); - PlayerControl[] filtered = e as PlayerControl[] ?? e.ToArray(); - if (filtered.Length == 0) return; + switch (ActiveWeapons[0]) + { + case Weapon.Gun when target != null: + pc.RpcCheckAndMurder(target); + RemoveAndNotify(); + break; + case Weapon.Shield when target != null: + ShieldedPlayers.Add(target.PlayerId); + RemoveAndNotify(); + break; + case Weapon.Portal: + IEnumerable e = Main.AllAlivePlayerControls.Where(x => x.PlayerId != pc.PlayerId && !x.inVent && !x.inMovingPlat && !x.onLadder); + PlayerControl[] filtered = e as PlayerControl[] ?? e.ToArray(); + if (filtered.Length == 0) return; - PlayerControl other = filtered.RandomElement(); - Vector2 pos = other.Pos(); - other.TP(pc); - pc.TP(pos); - RemoveAndNotify(); - break; - case Weapon.Lantern: - Utils.MarkEveryoneDirtySettings(); - LateTask.New(() => ActiveWeapons.Remove(Weapon.Lantern), IncreasedVisionDuration.GetInt(), log: false); - break; - case Weapon.Wrench: - if (Utils.IsActive(SystemTypes.Electrical)) + PlayerControl other = filtered.RandomElement(); + Vector2 pos = other.Pos(); + other.TP(pc); + pc.TP(pos); + RemoveAndNotify(); + break; + case Weapon.Lantern: + Utils.MarkEveryoneDirtySettings(); + LateTask.New(() => ActiveWeapons.Remove(Weapon.Lantern), IncreasedVisionDuration.GetInt(), log: false); + break; + case Weapon.Wrench: + if (Utils.IsActive(SystemTypes.Electrical)) + { + var SwitchSystem = ShipStatus.Instance?.Systems?[SystemTypes.Electrical]?.TryCast(); + + if (SwitchSystem != null) { - var SwitchSystem = ShipStatus.Instance?.Systems?[SystemTypes.Electrical]?.TryCast(); - - if (SwitchSystem != null) - { - SwitchSystem.ActualSwitches = 0; - SwitchSystem.ExpectedSwitches = 0; - } - } - else if (Utils.IsActive(SystemTypes.Reactor)) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 16); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 17); - } - else if (Utils.IsActive(SystemTypes.Laboratory)) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 67); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 66); - } - else if (Utils.IsActive(SystemTypes.LifeSupp)) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66); - } - else if (Utils.IsActive(SystemTypes.Comms)) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 16); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 17); + SwitchSystem.ActualSwitches = 0; + SwitchSystem.ExpectedSwitches = 0; } + } + else if (Utils.IsActive(SystemTypes.Reactor)) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 16); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 17); + } + else if (Utils.IsActive(SystemTypes.Laboratory)) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 67); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 66); + } + else if (Utils.IsActive(SystemTypes.LifeSupp)) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66); + } + else if (Utils.IsActive(SystemTypes.Comms)) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 16); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 17); + } - RemoveAndNotify(); - break; - case Weapon.Prediction: - PlayerControl closest = Main.AllAlivePlayerControls.Where(x => x.PlayerId != pc.PlayerId).MinBy(x => Vector2.Distance(pc.Pos(), x.Pos())); - RevealedPlayers.Add(closest.PlayerId); - RemoveAndNotify(closest); - break; - } - - break; - - void RemoveAndNotify(PlayerControl notifyTarget = null) - { - ActiveWeapons.RemoveAt(0); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: notifyTarget ?? pc); - } - } - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - ResourceCounts[Resource.TaskCompletion]++; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)Resource.TaskCompletion); - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad) return; - - long now = Utils.TimeStamp; - - if (!GameStates.IsInTask) - { - LastGroupingResourceTimeStamp = now; - LastRandomResourceTimeStamp = now; - return; - } - - if (LastRandomResourceTimeStamp + 20 <= now && pc.PlayerId != AdventurerPC.PlayerId && IRandom.Instance.Next(50) == 0) - { - if (ResourceLocations.TryGetValue(Resource.Random, out Vector2 location)) - { - LocateArrow.Remove(AdventurerPC.PlayerId, location); - HideObject(Resource.Random); + RemoveAndNotify(); + break; + case Weapon.Prediction: + PlayerControl closest = Main.AllAlivePlayerControls.Where(x => x.PlayerId != pc.PlayerId).MinBy(x => Vector2.Distance(pc.Pos(), x.Pos())); + RevealedPlayers.Add(closest.PlayerId); + RemoveAndNotify(closest); + break; } - Vector2 pos = pc.Pos(); - LocateArrow.Add(AdventurerPC.PlayerId, pos); - ResourceLocations[Resource.Random] = pos; - _ = new AdventurerItem(pos, Resource.Random, [AdventurerPC.PlayerId]); - LastRandomResourceTimeStamp = now; - Utils.NotifyRoles(SpecifySeer: AdventurerPC, SpecifyTarget: AdventurerPC); - } + break; - if (LastGroupingResourceTimeStamp + 20 <= now && Main.AllAlivePlayerControls.Count(x => x.PlayerId != pc.PlayerId && Vector2.Distance(x.Pos(), pc.Pos()) < 2f) >= 2) - { - if (ResourceLocations.TryGetValue(Resource.Grouping, out Vector2 location)) + void RemoveAndNotify(PlayerControl notifyTarget = null) { - LocateArrow.Remove(AdventurerPC.PlayerId, location); - HideObject(Resource.Grouping); + ActiveWeapons.RemoveAt(0); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: notifyTarget ?? pc); } - - Vector2 pos = pc.Pos(); - LocateArrow.Add(AdventurerPC.PlayerId, pos); - ResourceLocations[Resource.Grouping] = pos; - _ = new AdventurerItem(pos, Resource.Grouping, [AdventurerPC.PlayerId]); - LastGroupingResourceTimeStamp = now; - Utils.NotifyRoles(SpecifySeer: AdventurerPC, SpecifyTarget: AdventurerPC); - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (Count++ < 10) return; - - Count = 0; - - foreach ((Resource resource, Vector2 location) in ResourceLocations) - { - if (Vector2.Distance(pc.Pos(), location) < 2f) - { - ResourceCounts[resource]++; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)resource); - ResourceLocations.Remove(resource); - HideObject(resource); - LocateArrow.Remove(pc.PlayerId, location); - - (char Icon, Color Color) displayData = ResourceDisplayData[resource]; - pc.Notify(string.Format(Translator.GetString("AdventurerFound"), Utils.ColorString(displayData.Color, $"{displayData.Icon}"))); - break; - } - } - } - - public override void AfterMeetingTasks() - { - ActiveWeapons.Remove(Weapon.Proxy); - } - - public void OnLightsFix() - { - ResourceCounts[Resource.LightsFix]++; - Utils.SendRPC(CustomRPC.SyncRoleData, AdventurerPC.PlayerId, 3, (int)Resource.LightsFix); - } - - public static void OnAnyoneShapeshiftLoop(Adventurer av, PlayerControl shapeshifter) - { - Vector2 pos = shapeshifter.Pos(); - av.ResourceLocations[Resource.ShapeshiftSkin] = pos; - HideObject(Resource.ShapeshiftSkin); - _ = new AdventurerItem(pos, Resource.ShapeshiftSkin, [av.AdventurerPC.PlayerId]); - } - - public static void OnAnyoneDead(PlayerControl target) - { - foreach (PlayerState state in Main.PlayerStates.Values) - { - if (state.Role is Adventurer { IsEnable: true } av) - { - Vector2 pos = target.Pos(); - av.ResourceLocations[Resource.DeadBody] = pos; - HideObject(Resource.DeadBody); - _ = new AdventurerItem(pos, Resource.DeadBody, [av.AdventurerPC.PlayerId]); - } - } - } - - public static bool OnAnyoneCheckMurder(PlayerControl target) - { - var any = false; - - foreach (PlayerState s in Main.PlayerStates.Values) - { - if (s.Role is Adventurer { IsEnable: true } av && av.ShieldedPlayers.Contains(target.PlayerId)) - { - any = true; - av.ShieldedPlayers.Remove(target.PlayerId); - break; - } - } - - return !any; - } - - public override bool KnowRole(PlayerControl seer, PlayerControl target) - { - if (base.KnowRole(seer, target)) return true; - - return RevealedPlayers.Contains(target.PlayerId); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - InCraftingMode = reader.ReadBoolean(); - break; - case 2: - ResourceCounts[(Resource)reader.ReadPackedInt32()] -= reader.ReadInt32(); - break; - case 3: - ResourceCounts[(Resource)reader.ReadPackedInt32()]++; - break; - case 4: - SelectedWeaponToCraft = (Weapon)reader.ReadPackedInt32(); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.IsModClient() && !hud) return string.Empty; - - if (seer.PlayerId != target.PlayerId || seer.PlayerId != AdventurerPC.PlayerId) return string.Empty; - - IEnumerable resources = - from resource in Enum.GetValues() - let displayData = ResourceDisplayData[resource] - select $"{Utils.ColorString(displayData.Color, $"{displayData.Icon}")}{ResourceCounts[resource]}"; - - string finalText = string.Join(' ', resources); - if (meeting) return finalText; - - finalText += $"\n{LocateArrow.GetArrows(seer)}\n"; - - finalText += ""; - - finalText += InCraftingMode - ? string.Format( - Translator.GetString("AdventurerIngredientsDisplay"), - Translator.GetString($"AdventurerGun.{SelectedWeaponToCraft}"), - string.Join(' ', Ingredients[SelectedWeaponToCraft] - .Select(x => $"{Utils.ColorString(ResourceDisplayData[x.Resource].Color, $"{ResourceDisplayData[x.Resource].Icon}")}" + - $"{Utils.ColorString(x.Count > ResourceCounts[x.Resource] ? Color.red : Color.white, $"{x.Count}")}"))) - : Translator.GetString("AdventurerVentToEnterCrafting"); - - finalText += ""; - - return finalText; } } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + ResourceCounts[Resource.TaskCompletion]++; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)Resource.TaskCompletion); + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad) return; + + long now = Utils.TimeStamp; + + if (!GameStates.IsInTask) + { + LastGroupingResourceTimeStamp = now; + LastRandomResourceTimeStamp = now; + return; + } + + if (LastRandomResourceTimeStamp + 20 <= now && pc.PlayerId != AdventurerPC.PlayerId && IRandom.Instance.Next(50) == 0) + { + if (ResourceLocations.TryGetValue(Resource.Random, out Vector2 location)) + { + LocateArrow.Remove(AdventurerPC.PlayerId, location); + HideObject(Resource.Random); + } + + Vector2 pos = pc.Pos(); + LocateArrow.Add(AdventurerPC.PlayerId, pos); + ResourceLocations[Resource.Random] = pos; + _ = new AdventurerItem(pos, Resource.Random, [AdventurerPC.PlayerId]); + LastRandomResourceTimeStamp = now; + Utils.NotifyRoles(SpecifySeer: AdventurerPC, SpecifyTarget: AdventurerPC); + } + + if (LastGroupingResourceTimeStamp + 20 <= now && Main.AllAlivePlayerControls.Count(x => x.PlayerId != pc.PlayerId && Vector2.Distance(x.Pos(), pc.Pos()) < 2f) >= 2) + { + if (ResourceLocations.TryGetValue(Resource.Grouping, out Vector2 location)) + { + LocateArrow.Remove(AdventurerPC.PlayerId, location); + HideObject(Resource.Grouping); + } + + Vector2 pos = pc.Pos(); + LocateArrow.Add(AdventurerPC.PlayerId, pos); + ResourceLocations[Resource.Grouping] = pos; + _ = new AdventurerItem(pos, Resource.Grouping, [AdventurerPC.PlayerId]); + LastGroupingResourceTimeStamp = now; + Utils.NotifyRoles(SpecifySeer: AdventurerPC, SpecifyTarget: AdventurerPC); + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (Count++ < 10) return; + + Count = 0; + + foreach ((Resource resource, Vector2 location) in ResourceLocations) + { + if (Vector2.Distance(pc.Pos(), location) < 2f) + { + ResourceCounts[resource]++; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)resource); + ResourceLocations.Remove(resource); + HideObject(resource); + LocateArrow.Remove(pc.PlayerId, location); + + (char Icon, Color Color) displayData = ResourceDisplayData[resource]; + pc.Notify(string.Format(Translator.GetString("AdventurerFound"), Utils.ColorString(displayData.Color, $"{displayData.Icon}"))); + break; + } + } + } + + public override void AfterMeetingTasks() + { + ActiveWeapons.Remove(Weapon.Proxy); + } + + public void OnLightsFix() + { + ResourceCounts[Resource.LightsFix]++; + Utils.SendRPC(CustomRPC.SyncRoleData, AdventurerPC.PlayerId, 3, (int)Resource.LightsFix); + } + + public static void OnAnyoneShapeshiftLoop(Adventurer av, PlayerControl shapeshifter) + { + Vector2 pos = shapeshifter.Pos(); + av.ResourceLocations[Resource.ShapeshiftSkin] = pos; + HideObject(Resource.ShapeshiftSkin); + _ = new AdventurerItem(pos, Resource.ShapeshiftSkin, [av.AdventurerPC.PlayerId]); + } + + public static void OnAnyoneDead(PlayerControl target) + { + foreach (PlayerState state in Main.PlayerStates.Values) + { + if (state.Role is Adventurer { IsEnable: true } av) + { + Vector2 pos = target.Pos(); + av.ResourceLocations[Resource.DeadBody] = pos; + HideObject(Resource.DeadBody); + _ = new AdventurerItem(pos, Resource.DeadBody, [av.AdventurerPC.PlayerId]); + } + } + } + + public static bool OnAnyoneCheckMurder(PlayerControl target) + { + var any = false; + + foreach (PlayerState s in Main.PlayerStates.Values) + { + if (s.Role is Adventurer { IsEnable: true } av && av.ShieldedPlayers.Contains(target.PlayerId)) + { + any = true; + av.ShieldedPlayers.Remove(target.PlayerId); + break; + } + } + + return !any; + } + + public override bool KnowRole(PlayerControl seer, PlayerControl target) + { + if (base.KnowRole(seer, target)) return true; + + return RevealedPlayers.Contains(target.PlayerId); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + InCraftingMode = reader.ReadBoolean(); + break; + case 2: + ResourceCounts[(Resource)reader.ReadPackedInt32()] -= reader.ReadInt32(); + break; + case 3: + ResourceCounts[(Resource)reader.ReadPackedInt32()]++; + break; + case 4: + SelectedWeaponToCraft = (Weapon)reader.ReadPackedInt32(); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.IsModClient() && !hud) return string.Empty; + + if (seer.PlayerId != target.PlayerId || seer.PlayerId != AdventurerPC.PlayerId) return string.Empty; + + IEnumerable resources = + from resource in Enum.GetValues() + let displayData = ResourceDisplayData[resource] + select $"{Utils.ColorString(displayData.Color, $"{displayData.Icon}")}{ResourceCounts[resource]}"; + + string finalText = string.Join(' ', resources); + if (meeting) return finalText; + + finalText += $"\n{LocateArrow.GetArrows(seer)}\n"; + + finalText += ""; + + finalText += InCraftingMode + ? string.Format( + Translator.GetString("AdventurerIngredientsDisplay"), + Translator.GetString($"AdventurerGun.{SelectedWeaponToCraft}"), + string.Join(' ', Ingredients[SelectedWeaponToCraft] + .Select(x => $"{Utils.ColorString(ResourceDisplayData[x.Resource].Color, $"{ResourceDisplayData[x.Resource].Icon}")}" + + $"{Utils.ColorString(x.Count > ResourceCounts[x.Resource] ? Color.red : Color.white, $"{x.Count}")}"))) + : Translator.GetString("AdventurerVentToEnterCrafting"); + + finalText += ""; + + return finalText; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Aid.cs b/Roles/Crewmate/Aid.cs index ec85ee175..42e3eb723 100644 --- a/Roles/Crewmate/Aid.cs +++ b/Roles/Crewmate/Aid.cs @@ -4,165 +4,164 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Aid : RoleBase { - public class Aid : RoleBase + private const int Id = 640200; + public static Dictionary ShieldedPlayers = []; + + public static OptionItem AidDur; + public static OptionItem AidCD; + public static OptionItem TargetKnowsShield; + public static OptionItem UseLimitOpt; + public static OptionItem UsePet; + private static bool On; + private byte AidId; + + public byte TargetId; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 640200; - public static Dictionary ShieldedPlayers = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Aid); - public static OptionItem AidDur; - public static OptionItem AidCD; - public static OptionItem TargetKnowsShield; - public static OptionItem UseLimitOpt; - public static OptionItem UsePet; - private static bool On; - private byte AidId; + AidCD = new FloatOptionItem(Id + 10, "AidCD", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]) + .SetValueFormat(OptionFormat.Seconds); - public byte TargetId; - public override bool IsEnable => On; + AidDur = new FloatOptionItem(Id + 11, "AidDur", new(0f, 60f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + TargetKnowsShield = new BooleanOptionItem(Id + 14, "AidTargetKnowsAboutShield", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]); + + UseLimitOpt = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(1, 20, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]) + .SetValueFormat(OptionFormat.Times); + + UsePet = Options.CreatePetUseSetting(Id + 13, CustomRoles.Aid); + } + + public override void Init() + { + On = false; + ShieldedPlayers = []; + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + TargetId = byte.MaxValue; + AidId = playerId; + } + + public override void SetKillCooldown(byte playerId) + { + Main.AllPlayerKillCooldown[playerId] = AidCD.GetInt(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() >= 1; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() >= 1 && TargetId != byte.MaxValue; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + TargetId = target.PlayerId; + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc == null || !pc.Is(CustomRoles.Aid) || ShieldedPlayers.Count == 0) return; + + var change = false; + + foreach (KeyValuePair x in ShieldedPlayers.ToArray()) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Aid); - - AidCD = new FloatOptionItem(Id + 10, "AidCD", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]) - .SetValueFormat(OptionFormat.Seconds); - - AidDur = new FloatOptionItem(Id + 11, "AidDur", new(0f, 60f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]) - .SetValueFormat(OptionFormat.Seconds); - - TargetKnowsShield = new BooleanOptionItem(Id + 14, "AidTargetKnowsAboutShield", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]); - - UseLimitOpt = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(1, 20, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Aid]) - .SetValueFormat(OptionFormat.Times); - - UsePet = Options.CreatePetUseSetting(Id + 13, CustomRoles.Aid); + if (x.Value + AidDur.GetInt() <= Utils.TimeStamp || !GameStates.IsInTask) + { + ShieldedPlayers.Remove(x.Key); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, x.Key); + change = true; + } } - public override void Init() - { - On = false; - ShieldedPlayers = []; - } + if (change && GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: pc); + } - public override void Add(byte playerId) + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + PlayerControl pc = physics.myPlayer; + + if (pc.GetAbilityUseLimit() >= 1 && TargetId != byte.MaxValue) { - On = true; - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + pc.RpcRemoveAbilityUse(); + ShieldedPlayers[TargetId] = Utils.TimeStamp; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 0, TargetId); + PlayerControl target = Utils.GetPlayerById(TargetId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); TargetId = byte.MaxValue; - AidId = playerId; } - public override void SetKillCooldown(byte playerId) + LateTask.New(() => physics.RpcBootFromVent(ventId), 0.5f, log: false); + } + + public override void OnReportDeadBody() + { + ShieldedPlayers.Clear(); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - Main.AllPlayerKillCooldown[playerId] = AidCD.GetInt(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() >= 1; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() >= 1 && TargetId != byte.MaxValue; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return false; - - if (target == null) return false; - - TargetId = target.PlayerId; - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc == null || !pc.Is(CustomRoles.Aid) || ShieldedPlayers.Count == 0) return; - - var change = false; - - foreach (KeyValuePair x in ShieldedPlayers.ToArray()) - { - if (x.Value + AidDur.GetInt() <= Utils.TimeStamp || !GameStates.IsInTask) - { - ShieldedPlayers.Remove(x.Key); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, x.Key); - change = true; - } - } - - if (change && GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: pc); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - PlayerControl pc = physics.myPlayer; - - if (pc.GetAbilityUseLimit() >= 1 && TargetId != byte.MaxValue) - { - pc.RpcRemoveAbilityUse(); - ShieldedPlayers[TargetId] = Utils.TimeStamp; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 0, TargetId); - PlayerControl target = Utils.GetPlayerById(TargetId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); - TargetId = byte.MaxValue; - } - - LateTask.New(() => physics.RpcBootFromVent(ventId), 0.5f, log: false); - } - - public override void OnReportDeadBody() - { - ShieldedPlayers.Clear(); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 0: - ShieldedPlayers[reader.ReadByte()] = Utils.TimeStamp; - break; - case 1: - ShieldedPlayers.Remove(reader.ReadByte()); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; - - if (TargetKnowsShield.GetBool() && ShieldedPlayers.TryGetValue(seer.PlayerId, out long ts)) - { - long timeLeft = AidDur.GetInt() - (Utils.TimeStamp - ts); - return string.Format(Translator.GetString("AidCounterSelf"), timeLeft); - } - - if (seer.PlayerId == AidId) - { - int duration = AidDur.GetInt(); - long now = Utils.TimeStamp; - IEnumerable formatted = ShieldedPlayers.Select(x => string.Format(Translator.GetString("AidCounterTarget"), x.Key.ColoredPlayerName(), duration - (now - x.Value))); - return string.Join("\n", formatted); - } - - return string.Empty; + case 0: + ShieldedPlayers[reader.ReadByte()] = Utils.TimeStamp; + break; + case 1: + ShieldedPlayers.Remove(reader.ReadByte()); + break; } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; + + if (TargetKnowsShield.GetBool() && ShieldedPlayers.TryGetValue(seer.PlayerId, out long ts)) + { + long timeLeft = AidDur.GetInt() - (Utils.TimeStamp - ts); + return string.Format(Translator.GetString("AidCounterSelf"), timeLeft); + } + + if (seer.PlayerId == AidId) + { + int duration = AidDur.GetInt(); + long now = Utils.TimeStamp; + IEnumerable formatted = ShieldedPlayers.Select(x => string.Format(Translator.GetString("AidCounterTarget"), x.Key.ColoredPlayerName(), duration - (now - x.Value))); + return string.Join("\n", formatted); + } + + return string.Empty; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Alchemist.cs b/Roles/Crewmate/Alchemist.cs index 5ceb2ca2c..0db1ecca7 100644 --- a/Roles/Crewmate/Alchemist.cs +++ b/Roles/Crewmate/Alchemist.cs @@ -5,464 +5,463 @@ using EHR.Modules; using EHR.Neutral; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; +using static Translator; + +public class Alchemist : RoleBase { - using static Options; - using static Translator; + private const int Id = 5250; + private static List PlayerIdList = []; - public class Alchemist : RoleBase + public static OptionItem VentCooldown; + public static OptionItem ShieldDuration; + public static OptionItem Speed; + public static OptionItem Vision; + public static OptionItem VisionOnLightsOut; + public static OptionItem SpeedDuration; + public static OptionItem VisionDuration; + public static OptionItem InvisDuration; + + private byte AlchemistId; + public bool FixNextSabo; + private long InvisTime = -10; + + public bool IsProtected; + + private long lastFixedTime; + public string PlayerName = string.Empty; + public byte PotionID = 10; + private int ventedId = -10; + public bool VisionPotionActive; + + public override bool IsEnable => PlayerIdList.Count > 0; + private bool IsInvis => InvisTime != -10; + + public override void SetupCustomOption() { - private const int Id = 5250; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Alchemist); - public static OptionItem VentCooldown; - public static OptionItem ShieldDuration; - public static OptionItem Speed; - public static OptionItem Vision; - public static OptionItem VisionOnLightsOut; - public static OptionItem SpeedDuration; - public static OptionItem VisionDuration; - public static OptionItem InvisDuration; + VentCooldown = new FloatOptionItem(Id + 11, "AlchemistAbilityCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Seconds); - private byte AlchemistId; - public bool FixNextSabo; - private long InvisTime = -10; + ShieldDuration = new FloatOptionItem(Id + 12, "AlchemistShieldDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Seconds); - public bool IsProtected; + InvisDuration = new FloatOptionItem(Id + 13, "AlchemistInvisDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Seconds); - private long lastFixedTime; - public string PlayerName = string.Empty; - public byte PotionID = 10; - private int ventedId = -10; - public bool VisionPotionActive; + Speed = new FloatOptionItem(Id + 14, "AlchemistSpeed", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Multiplier); - public override bool IsEnable => PlayerIdList.Count > 0; - private bool IsInvis => InvisTime != -10; + SpeedDuration = new FloatOptionItem(Id + 15, "AlchemistSpeedDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + Vision = new FloatOptionItem(Id + 16, "AlchemistVision", new(0f, 1f, 0.05f), 0.85f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Multiplier); + + VisionOnLightsOut = new FloatOptionItem(Id + 17, "AlchemistVisionOnLightsOut", new(0f, 1f, 0.05f), 0.4f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Multiplier); + + VisionDuration = new FloatOptionItem(Id + 18, "AlchemistVisionDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) + .SetValueFormat(OptionFormat.Seconds); + + OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Alchemist); + } + + public override void Init() + { + PlayerIdList = []; + PotionID = 10; + PlayerName = string.Empty; + ventedId = -10; + InvisTime = -10; + FixNextSabo = false; + VisionPotionActive = false; + AlchemistId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + PlayerName = Utils.GetPlayerById(playerId).GetRealName(); + AlchemistId = playerId; + PotionID = 10; + ventedId = -10; + InvisTime = -10; + FixNextSabo = false; + VisionPotionActive = false; + } + + private void SendRPCData() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAlchemistPotion, SendOption.Reliable); + writer.Write(AlchemistId); + writer.Write(IsProtected); + writer.Write(PotionID); + writer.Write(PlayerName); + writer.Write(VisionPotionActive); + writer.Write(FixNextSabo); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCData(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not Alchemist am) return; + + am.IsProtected = reader.ReadBoolean(); + am.PotionID = reader.ReadByte(); + am.PlayerName = reader.ReadString(); + am.VisionPotionActive = reader.ReadBoolean(); + am.FixNextSabo = reader.ReadBoolean(); + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + PotionID = (byte)HashRandom.Next(1, 8); + + switch (PotionID) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Alchemist); - - VentCooldown = new FloatOptionItem(Id + 11, "AlchemistAbilityCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Seconds); - - ShieldDuration = new FloatOptionItem(Id + 12, "AlchemistShieldDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Seconds); - - InvisDuration = new FloatOptionItem(Id + 13, "AlchemistInvisDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Seconds); - - Speed = new FloatOptionItem(Id + 14, "AlchemistSpeed", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Multiplier); - - SpeedDuration = new FloatOptionItem(Id + 15, "AlchemistSpeedDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Seconds); - - Vision = new FloatOptionItem(Id + 16, "AlchemistVision", new(0f, 1f, 0.05f), 0.85f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Multiplier); - - VisionOnLightsOut = new FloatOptionItem(Id + 17, "AlchemistVisionOnLightsOut", new(0f, 1f, 0.05f), 0.4f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Multiplier); - - VisionDuration = new FloatOptionItem(Id + 18, "AlchemistVisionDur", new(5f, 70f, 1f), 20f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Alchemist]) - .SetValueFormat(OptionFormat.Seconds); - - OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Alchemist); - } - - public override void Init() - { - PlayerIdList = []; - PotionID = 10; - PlayerName = string.Empty; - ventedId = -10; - InvisTime = -10; - FixNextSabo = false; - VisionPotionActive = false; - AlchemistId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - PlayerName = Utils.GetPlayerById(playerId).GetRealName(); - AlchemistId = playerId; - PotionID = 10; - ventedId = -10; - InvisTime = -10; - FixNextSabo = false; - VisionPotionActive = false; - } - - private void SendRPCData() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAlchemistPotion, SendOption.Reliable); - writer.Write(AlchemistId); - writer.Write(IsProtected); - writer.Write(PotionID); - writer.Write(PlayerName); - writer.Write(VisionPotionActive); - writer.Write(FixNextSabo); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCData(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not Alchemist am) return; - - am.IsProtected = reader.ReadBoolean(); - am.PotionID = reader.ReadByte(); - am.PlayerName = reader.ReadString(); - am.VisionPotionActive = reader.ReadBoolean(); - am.FixNextSabo = reader.ReadBoolean(); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - PotionID = (byte)HashRandom.Next(1, 8); - - switch (PotionID) - { - case 1: // Shield - pc.Notify(GetString("AlchemistGotShieldPotion"), 15f); - break; - case 2: // Suicide - pc.Notify(GetString("AlchemistGotSuicidePotion"), 15f); - break; - case 3: // TP to random player - pc.Notify(GetString("AlchemistGotTPPotion"), 15f); - break; - case 4: // Increased speed - pc.Notify(GetString("AlchemistGotSpeedPotion"), 15f); - break; - case 5: // Quick fix next sabo - FixNextSabo = true; - PotionID = 10; - pc.Notify(GetString("AlchemistGotQFPotion"), 15f); - break; - case 6: // Invisibility - pc.Notify(GetString("AlchemistGotInvisPotion"), 15f); - break; - case 7: // Increased vision - pc.Notify(GetString("AlchemistGotSightPotion"), 15f); - break; - } - } - - public override void OnPet(PlayerControl pc) - { - DrinkPotion(pc, 0, true); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - DrinkPotion(pc, vent.Id); - } - - public static void DrinkPotion(PlayerControl player, int ventId, bool isPet = false) - { - if (Main.PlayerStates[player.PlayerId].Role is not Alchemist am) return; - - switch (am.PotionID) - { - case 1: // Shield - am.IsProtected = true; - player.Notify(GetString("AlchemistShielded"), ShieldDuration.GetInt()); - - LateTask.New(() => - { - am.IsProtected = false; - player.Notify(GetString("AlchemistShieldOut")); - }, ShieldDuration.GetInt(), "Alchemist Shield"); - - break; - case 2: // Suicide - if (!isPet) player.MyPhysics.RpcBootFromVent(ventId); - - LateTask.New(() => { player.Suicide(PlayerState.DeathReason.Poison); }, !isPet ? 1f : 0.1f, "Alchemist Suicide"); - break; - case 3: // TP to random player - LateTask.New(() => - { - List allAlivePlayer = [.. Main.AllAlivePlayerControls.Where(x => !Pelican.IsEaten(x.PlayerId) && !x.inVent && !x.onLadder).ToArray()]; - PlayerControl tar1 = allAlivePlayer[player.PlayerId]; - allAlivePlayer.Remove(tar1); - PlayerControl tar2 = allAlivePlayer.RandomElement(); - tar1.TP(tar2); - tar1.RPCPlayCustomSound("Teleport"); - }, !isPet ? 2f : 0.1f, "AlchemistTPToRandomPlayer"); - - break; - case 4: // Increased speed - player.Notify(GetString("AlchemistHasSpeed")); - player.MarkDirtySettings(); - float tmpSpeed = Main.AllPlayerSpeed[player.PlayerId]; - Main.AllPlayerSpeed[player.PlayerId] = Speed.GetFloat(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId] - Speed.GetFloat() + tmpSpeed; - player.MarkDirtySettings(); - player.Notify(GetString("AlchemistSpeedOut")); - }, SpeedDuration.GetInt(), "Alchemist Speed"); - - break; - case 5: // Quick fix next sabo - // Done when making the potion - break; - case 6: // Invisibility - // Handled by OnCoEnterVent - break; - case 7: // Increased vision - am.VisionPotionActive = true; - player.MarkDirtySettings(); - player.Notify(GetString("AlchemistHasVision"), VisionDuration.GetFloat()); - - LateTask.New(() => - { - am.VisionPotionActive = false; - player.MarkDirtySettings(); - player.Notify(GetString("AlchemistVisionOut")); - }, VisionDuration.GetFloat(), "Alchemist Vision"); - - break; - case 10 when !player.Is(CustomRoles.Nimble): - if (!isPet) player.MyPhysics.RpcBootFromVent(ventId); - - player.Notify(GetString("AlchemistNoPotion")); - break; - } - - am.SendRPCData(); - - am.PotionID = 10; - } - - private void SendRPC() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAlchemistTimer, SendOption.Reliable); - writer.Write(AlchemistId); - writer.Write(InvisTime.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not Alchemist am) return; - - am.InvisTime = long.Parse(reader.ReadString()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnCoEnterVent(PlayerPhysics instance, int ventId) - { - if (PotionID != 6) return; - - PotionID = 10; - PlayerControl pc = instance.myPlayer; - if (!AmongUsClient.Instance.AmHost) return; - - LateTask.New(() => - { - ventedId = ventId; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(instance.NetId, 34, SendOption.Reliable, pc.GetClientId()); - writer.WritePacked(ventId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - InvisTime = Utils.TimeStamp; - SendRPC(); - pc.Notify(GetString("ChameleonInvisState"), InvisDuration.GetFloat()); - }, 0.5f, "Alchemist Invis"); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask || !IsEnable) return; - - long now = Utils.TimeStamp; - - if (lastFixedTime != now && InvisTime != -10) - { - lastFixedTime = now; - var refresh = false; - long remainTime = InvisTime + (long)InvisDuration.GetFloat() - now; - - switch (remainTime) - { - case < 0: - player.MyPhysics?.RpcBootFromVent(ventedId == -10 ? Main.LastEnteredVent.TryGetValue(player.PlayerId, out Vent vent) ? vent.Id : player.PlayerId : ventedId); - player.Notify(GetString("SwooperInvisStateOut")); - SendRPC(); - refresh = true; - InvisTime = -10; - break; - case <= 10 when !player.IsModClient(): - player.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1), overrideAll: true); - break; - } - - if (refresh) SendRPC(); - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || seer == null || seer.PlayerId != target.PlayerId || !GameStates.IsInTask || seer.PlayerId != AlchemistId) return string.Empty; - - var str = new StringBuilder(); - - if (IsInvis) - { - long remainTime = InvisTime + (long)InvisDuration.GetFloat() - Utils.TimeStamp; - str.Append(string.Format(GetString("ChameleonInvisStateCountdown"), remainTime + 1)); - } - else - { - var preText = $"{GetString("PotionInStore")}:"; - - switch (PotionID) - { - case 1: // Shield - str.Append($"{preText} {GetString("ShieldPotion")}"); - break; - case 2: // Suicide - str.Append($"{preText} {GetString("AwkwardPotion")}"); - break; - case 3: // TP to random player - str.Append($"{preText} {GetString("TeleportPotion")}"); - break; - case 4: // Increased speed - str.Append($"{preText} {GetString("SpeedPotion")}"); - break; - case 5: // Quick fix next sabo - str.Append($"{preText} {GetString("QuickFixPotion")}"); - break; - case 6: // Invisibility - str.Append($"{preText} {GetString("InvisibilityPotion")}"); - break; - case 7: // Increased vision - str.Append($"{preText} {GetString("SightPotion")}"); - break; - case 10: - str.Append($"{preText} {GetString("None")}"); - break; - } - - if (FixNextSabo) str.Append($"\n{GetString("QuickFixPotionWaitForUse")}"); - } - - return str.ToString(); - } - - public override string GetProgressText(byte playerId, bool comms) - { - if (Utils.GetPlayerById(playerId) == null || !GameStates.IsInTask || Utils.GetPlayerById(playerId).IsModClient()) return string.Empty; - - var str = new StringBuilder(); - - switch (PotionID) - { - case 1: // Shield - str.Append($" {GetString("Stored")}: {GetString("ShieldPotion")}"); - break; - case 2: // Suicide - str.Append($" {GetString("Stored")}: {GetString("AwkwardPotion")}"); - break; - case 3: // TP to random player - str.Append($" {GetString("Stored")}: {GetString("TeleportPotion")}"); - break; - case 4: // Increased speed - str.Append($" {GetString("Stored")}: {GetString("SpeedPotion")}"); - break; - case 5: // Quick fix next sabo - str.Append($" {GetString("Stored")}: {GetString("QuickFixPotion")}"); - break; - case 6: // Invisibility - str.Append($" {GetString("Stored")}: {GetString("InvisibilityPotion")}"); - break; - case 7: // Increased vision - str.Append($" {GetString("Stored")}: {GetString("SightPotion")}"); - break; - } - - if (FixNextSabo) str.Append($" ({GetString("QuickFix")})"); - - return str.ToString(); - } - - public static void RepairSystem(PlayerControl pc, SystemTypes systemType, byte amount) - { - if (Main.PlayerStates[pc.PlayerId].Role is not Alchemist { IsEnable: true } am) return; - - am.FixNextSabo = false; - - switch (systemType) - { - case SystemTypes.Reactor: - if (amount is 64 or 65) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 16); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 17); - } - - break; - case SystemTypes.Laboratory: - if (amount is 64 or 65) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 67); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 66); - } - - break; - case SystemTypes.LifeSupp: - if (amount is 64 or 65) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66); - } - - break; - case SystemTypes.Comms: - if (amount is 64 or 65) - { - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 16); - ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 17); - } - - break; - } - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !IsProtected; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton?.OverrideText(GetString("AlchemistVentButtonText")); - else - hud.AbilityButton?.OverrideText(GetString("AlchemistVentButtonText")); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + case 1: // Shield + pc.Notify(GetString("AlchemistGotShieldPotion"), 15f); + break; + case 2: // Suicide + pc.Notify(GetString("AlchemistGotSuicidePotion"), 15f); + break; + case 3: // TP to random player + pc.Notify(GetString("AlchemistGotTPPotion"), 15f); + break; + case 4: // Increased speed + pc.Notify(GetString("AlchemistGotSpeedPotion"), 15f); + break; + case 5: // Quick fix next sabo + FixNextSabo = true; + PotionID = 10; + pc.Notify(GetString("AlchemistGotQFPotion"), 15f); + break; + case 6: // Invisibility + pc.Notify(GetString("AlchemistGotInvisPotion"), 15f); + break; + case 7: // Increased vision + pc.Notify(GetString("AlchemistGotSightPotion"), 15f); + break; } } + + public override void OnPet(PlayerControl pc) + { + DrinkPotion(pc, 0, true); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + DrinkPotion(pc, vent.Id); + } + + public static void DrinkPotion(PlayerControl player, int ventId, bool isPet = false) + { + if (Main.PlayerStates[player.PlayerId].Role is not Alchemist am) return; + + switch (am.PotionID) + { + case 1: // Shield + am.IsProtected = true; + player.Notify(GetString("AlchemistShielded"), ShieldDuration.GetInt()); + + LateTask.New(() => + { + am.IsProtected = false; + player.Notify(GetString("AlchemistShieldOut")); + }, ShieldDuration.GetInt(), "Alchemist Shield"); + + break; + case 2: // Suicide + if (!isPet) player.MyPhysics.RpcBootFromVent(ventId); + + LateTask.New(() => { player.Suicide(PlayerState.DeathReason.Poison); }, !isPet ? 1f : 0.1f, "Alchemist Suicide"); + break; + case 3: // TP to random player + LateTask.New(() => + { + List allAlivePlayer = [.. Main.AllAlivePlayerControls.Where(x => !Pelican.IsEaten(x.PlayerId) && !x.inVent && !x.onLadder).ToArray()]; + PlayerControl tar1 = allAlivePlayer[player.PlayerId]; + allAlivePlayer.Remove(tar1); + PlayerControl tar2 = allAlivePlayer.RandomElement(); + tar1.TP(tar2); + tar1.RPCPlayCustomSound("Teleport"); + }, !isPet ? 2f : 0.1f, "AlchemistTPToRandomPlayer"); + + break; + case 4: // Increased speed + player.Notify(GetString("AlchemistHasSpeed")); + player.MarkDirtySettings(); + float tmpSpeed = Main.AllPlayerSpeed[player.PlayerId]; + Main.AllPlayerSpeed[player.PlayerId] = Speed.GetFloat(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId] - Speed.GetFloat() + tmpSpeed; + player.MarkDirtySettings(); + player.Notify(GetString("AlchemistSpeedOut")); + }, SpeedDuration.GetInt(), "Alchemist Speed"); + + break; + case 5: // Quick fix next sabo + // Done when making the potion + break; + case 6: // Invisibility + // Handled by OnCoEnterVent + break; + case 7: // Increased vision + am.VisionPotionActive = true; + player.MarkDirtySettings(); + player.Notify(GetString("AlchemistHasVision"), VisionDuration.GetFloat()); + + LateTask.New(() => + { + am.VisionPotionActive = false; + player.MarkDirtySettings(); + player.Notify(GetString("AlchemistVisionOut")); + }, VisionDuration.GetFloat(), "Alchemist Vision"); + + break; + case 10 when !player.Is(CustomRoles.Nimble): + if (!isPet) player.MyPhysics.RpcBootFromVent(ventId); + + player.Notify(GetString("AlchemistNoPotion")); + break; + } + + am.SendRPCData(); + + am.PotionID = 10; + } + + private void SendRPC() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetAlchemistTimer, SendOption.Reliable); + writer.Write(AlchemistId); + writer.Write(InvisTime.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not Alchemist am) return; + + am.InvisTime = long.Parse(reader.ReadString()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnCoEnterVent(PlayerPhysics instance, int ventId) + { + if (PotionID != 6) return; + + PotionID = 10; + PlayerControl pc = instance.myPlayer; + if (!AmongUsClient.Instance.AmHost) return; + + LateTask.New(() => + { + ventedId = ventId; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(instance.NetId, 34, SendOption.Reliable, pc.GetClientId()); + writer.WritePacked(ventId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + + InvisTime = Utils.TimeStamp; + SendRPC(); + pc.Notify(GetString("ChameleonInvisState"), InvisDuration.GetFloat()); + }, 0.5f, "Alchemist Invis"); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask || !IsEnable) return; + + long now = Utils.TimeStamp; + + if (lastFixedTime != now && InvisTime != -10) + { + lastFixedTime = now; + var refresh = false; + long remainTime = InvisTime + (long)InvisDuration.GetFloat() - now; + + switch (remainTime) + { + case < 0: + player.MyPhysics?.RpcBootFromVent(ventedId == -10 ? Main.LastEnteredVent.TryGetValue(player.PlayerId, out Vent vent) ? vent.Id : player.PlayerId : ventedId); + player.Notify(GetString("SwooperInvisStateOut")); + SendRPC(); + refresh = true; + InvisTime = -10; + break; + case <= 10 when !player.IsModClient(): + player.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1), overrideAll: true); + break; + } + + if (refresh) SendRPC(); + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || seer == null || seer.PlayerId != target.PlayerId || !GameStates.IsInTask || seer.PlayerId != AlchemistId) return string.Empty; + + var str = new StringBuilder(); + + if (IsInvis) + { + long remainTime = InvisTime + (long)InvisDuration.GetFloat() - Utils.TimeStamp; + str.Append(string.Format(GetString("ChameleonInvisStateCountdown"), remainTime + 1)); + } + else + { + var preText = $"{GetString("PotionInStore")}:"; + + switch (PotionID) + { + case 1: // Shield + str.Append($"{preText} {GetString("ShieldPotion")}"); + break; + case 2: // Suicide + str.Append($"{preText} {GetString("AwkwardPotion")}"); + break; + case 3: // TP to random player + str.Append($"{preText} {GetString("TeleportPotion")}"); + break; + case 4: // Increased speed + str.Append($"{preText} {GetString("SpeedPotion")}"); + break; + case 5: // Quick fix next sabo + str.Append($"{preText} {GetString("QuickFixPotion")}"); + break; + case 6: // Invisibility + str.Append($"{preText} {GetString("InvisibilityPotion")}"); + break; + case 7: // Increased vision + str.Append($"{preText} {GetString("SightPotion")}"); + break; + case 10: + str.Append($"{preText} {GetString("None")}"); + break; + } + + if (FixNextSabo) str.Append($"\n{GetString("QuickFixPotionWaitForUse")}"); + } + + return str.ToString(); + } + + public override string GetProgressText(byte playerId, bool comms) + { + if (Utils.GetPlayerById(playerId) == null || !GameStates.IsInTask || Utils.GetPlayerById(playerId).IsModClient()) return string.Empty; + + var str = new StringBuilder(); + + switch (PotionID) + { + case 1: // Shield + str.Append($" {GetString("Stored")}: {GetString("ShieldPotion")}"); + break; + case 2: // Suicide + str.Append($" {GetString("Stored")}: {GetString("AwkwardPotion")}"); + break; + case 3: // TP to random player + str.Append($" {GetString("Stored")}: {GetString("TeleportPotion")}"); + break; + case 4: // Increased speed + str.Append($" {GetString("Stored")}: {GetString("SpeedPotion")}"); + break; + case 5: // Quick fix next sabo + str.Append($" {GetString("Stored")}: {GetString("QuickFixPotion")}"); + break; + case 6: // Invisibility + str.Append($" {GetString("Stored")}: {GetString("InvisibilityPotion")}"); + break; + case 7: // Increased vision + str.Append($" {GetString("Stored")}: {GetString("SightPotion")}"); + break; + } + + if (FixNextSabo) str.Append($" ({GetString("QuickFix")})"); + + return str.ToString(); + } + + public static void RepairSystem(PlayerControl pc, SystemTypes systemType, byte amount) + { + if (Main.PlayerStates[pc.PlayerId].Role is not Alchemist { IsEnable: true } am) return; + + am.FixNextSabo = false; + + switch (systemType) + { + case SystemTypes.Reactor: + if (amount is 64 or 65) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 16); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Reactor, 17); + } + + break; + case SystemTypes.Laboratory: + if (amount is 64 or 65) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 67); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Laboratory, 66); + } + + break; + case SystemTypes.LifeSupp: + if (amount is 64 or 65) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 67); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.LifeSupp, 66); + } + + break; + case SystemTypes.Comms: + if (amount is 64 or 65) + { + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 16); + ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Comms, 17); + } + + break; + } + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !IsProtected; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton?.OverrideText(GetString("AlchemistVentButtonText")); + else + hud.AbilityButton?.OverrideText(GetString("AlchemistVentButtonText")); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Altruist.cs b/Roles/Crewmate/Altruist.cs index 5728a934d..2d731eac3 100644 --- a/Roles/Crewmate/Altruist.cs +++ b/Roles/Crewmate/Altruist.cs @@ -5,165 +5,164 @@ using EHR.Modules; // ReSharper disable ConvertIfStatementToReturnStatement -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Altruist : RoleBase { - public class Altruist : RoleBase + public static bool On; + + private static OptionItem ReviveTime; + private static OptionItem ReviveTargetCanReportTheirOwnBody; + private static OptionItem ReviveTargetsKillerGetsAlert; + private static OptionItem ReviveTargetsKillerGetsArrow; + + private static HashSet RevivedPlayers = []; + + private byte AlturistId; + + public long ReviveStartTS; + private byte ReviveTarget; + private Vector2 ReviveTargetPos; + + private bool RevivingMode; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(645850) + .AutoSetupOption(ref ReviveTime, 5, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref ReviveTargetCanReportTheirOwnBody, false) + .AutoSetupOption(ref ReviveTargetsKillerGetsAlert, true) + .AutoSetupOption(ref ReviveTargetsKillerGetsArrow, true, overrideParent: ReviveTargetsKillerGetsAlert); + } - private static OptionItem ReviveTime; - private static OptionItem ReviveTargetCanReportTheirOwnBody; - private static OptionItem ReviveTargetsKillerGetsAlert; - private static OptionItem ReviveTargetsKillerGetsArrow; + public override void Init() + { + On = false; + RevivedPlayers = []; + } - private static HashSet RevivedPlayers = []; + public override void Add(byte playerId) + { + On = true; + RevivingMode = true; + ReviveTarget = byte.MaxValue; + ReviveStartTS = 0; + AlturistId = playerId; + ReviveTargetPos = Vector2.zero; + RevivedPlayers = []; + } - private byte AlturistId; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } - public long ReviveStartTS; - private byte ReviveTarget; - private Vector2 ReviveTargetPos; + public static bool OnAnyoneCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + if (ReviveTargetCanReportTheirOwnBody.GetBool()) return true; + return !RevivedPlayers.Contains(reporter.PlayerId) || target.PlayerId != reporter.PlayerId; + } - private bool RevivingMode; + public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) + { + if (!RevivingMode || target.Disconnected || target.Object.IsAlive() || target.Object.Is(CustomRoles.Unreportable)) return true; - public override bool IsEnable => On; + PlayerState state = Main.PlayerStates[reporter.PlayerId]; + state.deathReason = PlayerState.DeathReason.Sacrifice; + state.RealKiller = (DateTime.Now, target.PlayerId); + state.SetDead(); + reporter.RpcExileV2(); + FixedUpdatePatch.LoversSuicide(reporter.PlayerId); - public override void SetupCustomOption() + RevivingMode = false; + ReviveTarget = target.PlayerId; + ReviveStartTS = Utils.TimeStamp; + ReviveTargetPos = reporter.Pos(); + + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc.IsAlive() || !GameStates.IsInTask || ReviveStartTS == 0 || ReviveTarget == byte.MaxValue) return; + + if (Utils.TimeStamp - ReviveStartTS < ReviveTime.GetInt()) { - StartSetup(645850) - .AutoSetupOption(ref ReviveTime, 5, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) - .AutoSetupOption(ref ReviveTargetCanReportTheirOwnBody, false) - .AutoSetupOption(ref ReviveTargetsKillerGetsAlert, true) - .AutoSetupOption(ref ReviveTargetsKillerGetsArrow, true, overrideParent: ReviveTargetsKillerGetsAlert); - } - - public override void Init() - { - On = false; - RevivedPlayers = []; - } - - public override void Add(byte playerId) - { - On = true; - RevivingMode = true; - ReviveTarget = byte.MaxValue; - ReviveStartTS = 0; - AlturistId = playerId; - ReviveTargetPos = Vector2.zero; - RevivedPlayers = []; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = 1f; - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public static bool OnAnyoneCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) - { - if (ReviveTargetCanReportTheirOwnBody.GetBool()) return true; - return !RevivedPlayers.Contains(reporter.PlayerId) || target.PlayerId != reporter.PlayerId; - } - - public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) - { - if (!RevivingMode || target.Disconnected || target.Object.IsAlive() || target.Object.Is(CustomRoles.Unreportable)) return true; - - PlayerState state = Main.PlayerStates[reporter.PlayerId]; - state.deathReason = PlayerState.DeathReason.Sacrifice; - state.RealKiller = (DateTime.Now, target.PlayerId); - state.SetDead(); - reporter.RpcExileV2(); - FixedUpdatePatch.LoversSuicide(reporter.PlayerId); - - RevivingMode = false; - ReviveTarget = target.PlayerId; - ReviveStartTS = Utils.TimeStamp; - ReviveTargetPos = reporter.Pos(); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc.IsAlive() || !GameStates.IsInTask || ReviveStartTS == 0 || ReviveTarget == byte.MaxValue) return; - - if (Utils.TimeStamp - ReviveStartTS < ReviveTime.GetInt()) - { - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - return; - } - - PlayerControl rtg = ReviveTarget.GetPlayer(); - rtg?.RpcRevive(); - rtg?.TP(ReviveTargetPos); - rtg?.Notify(Translator.GetString("RevivedByAltruist"), 15f); - - RevivedPlayers.Add(ReviveTarget); - - PlayerControl killer = rtg == null ? null : rtg.GetRealKiller(); - - if (killer != null && ReviveTargetsKillerGetsAlert.GetBool()) - { - if (ReviveTargetsKillerGetsArrow.GetBool()) TargetArrow.Add(killer.PlayerId, ReviveTarget); - - killer.KillFlash(); - killer.Notify(string.Format(Translator.GetString("AltruistKillerAlert"), ReviveTarget.ColoredPlayerName()), 10f); - } - - ReviveTarget = byte.MaxValue; - ReviveStartTS = 0; - ReviveTargetPos = Vector2.zero; - - if (pc.IsLocalPlayer() && rtg != null && (rtg.IsImpostor() || rtg.IsNeutralKiller() || rtg.IsConverted())) - Achievements.Type.IWishIReported.Complete(); - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !ReviveTargetsKillerGetsArrow.GetBool() || GameStates.IsMeeting || ExileController.Instance) return; - - if (RevivedPlayers.FindFirst(x => x.GetPlayer()?.GetRealKiller()?.PlayerId == pc.PlayerId, out byte revived)) - { - PlayerControl revivedPlayer = revived.GetPlayer(); - - if (revivedPlayer == null || !revivedPlayer.IsAlive()) - { - TargetArrow.Remove(pc.PlayerId, revived); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - } - } - - public override void OnPet(PlayerControl pc) - { - RevivingMode = !RevivingMode; Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + return; } - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + PlayerControl rtg = ReviveTarget.GetPlayer(); + rtg?.RpcRevive(); + rtg?.TP(ReviveTargetPos); + rtg?.Notify(Translator.GetString("RevivedByAltruist"), 15f); + + RevivedPlayers.Add(ReviveTarget); + + PlayerControl killer = rtg == null ? null : rtg.GetRealKiller(); + + if (killer != null && ReviveTargetsKillerGetsAlert.GetBool()) { - OnPet(physics.myPlayer); + if (ReviveTargetsKillerGetsArrow.GetBool()) TargetArrow.Add(killer.PlayerId, ReviveTarget); + + killer.KillFlash(); + killer.Notify(string.Format(Translator.GetString("AltruistKillerAlert"), ReviveTarget.ColoredPlayerName()), 10f); } - public override void OnReportDeadBody() - { - ReviveTarget = byte.MaxValue; - ReviveStartTS = 0; - ReviveTargetPos = Vector2.zero; - } + ReviveTarget = byte.MaxValue; + ReviveStartTS = 0; + ReviveTargetPos = Vector2.zero; - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != AlturistId || meeting || hud) return string.Empty; - if (ReviveStartTS != 0) return string.Format(Translator.GetString("AltruistSuffixRevive"), ReviveTime.GetInt() - (Utils.TimeStamp - ReviveStartTS)); - return string.Format(Translator.GetString("AltruistSuffix"), Translator.GetString(RevivingMode ? "AltruistReviveMode" : "AltruistReportMode")); - } + if (pc.IsLocalPlayer() && rtg != null && (rtg.IsImpostor() || rtg.IsNeutralKiller() || rtg.IsConverted())) + Achievements.Type.IWishIReported.Complete(); + } - public override bool CanUseVent(PlayerControl pc, int ventId) + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !ReviveTargetsKillerGetsArrow.GetBool() || GameStates.IsMeeting || ExileController.Instance) return; + + if (RevivedPlayers.FindFirst(x => x.GetPlayer()?.GetRealKiller()?.PlayerId == pc.PlayerId, out byte revived)) { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + PlayerControl revivedPlayer = revived.GetPlayer(); + + if (revivedPlayer == null || !revivedPlayer.IsAlive()) + { + TargetArrow.Remove(pc.PlayerId, revived); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } } } + + public override void OnPet(PlayerControl pc) + { + RevivingMode = !RevivingMode; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + OnPet(physics.myPlayer); + } + + public override void OnReportDeadBody() + { + ReviveTarget = byte.MaxValue; + ReviveStartTS = 0; + ReviveTargetPos = Vector2.zero; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != AlturistId || meeting || hud) return string.Empty; + if (ReviveStartTS != 0) return string.Format(Translator.GetString("AltruistSuffixRevive"), ReviveTime.GetInt() - (Utils.TimeStamp - ReviveStartTS)); + return string.Format(Translator.GetString("AltruistSuffix"), Translator.GetString(RevivingMode ? "AltruistReviveMode" : "AltruistReportMode")); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Analyst.cs b/Roles/Crewmate/Analyst.cs index 9eda9c61f..10026ee09 100644 --- a/Roles/Crewmate/Analyst.cs +++ b/Roles/Crewmate/Analyst.cs @@ -4,175 +4,174 @@ using AmongUs.GameOptions; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Analyst : RoleBase { - internal class Analyst : RoleBase + private const int Id = 643100; + private static byte PlayerId = byte.MaxValue; + + private static OptionItem UseLimitOpt; + private static OptionItem CD; + private static OptionItem Duration; + private static OptionItem SeeKillCount; + private static OptionItem SeeVentCount; + private static OptionItem SeeRoleBasis; + public static OptionItem UsePet; + + private static readonly Dictionary ReplacementDict = new() { { "Analyze", Utils.ColorString(Utils.GetRoleColor(CustomRoles.Analyst), "Analyze") } }; + + public static Dictionary VentCount = []; + public (byte ID, long TIME) CurrentTarget = (byte.MaxValue, Utils.TimeStamp); + + public override bool IsEnable => PlayerId != byte.MaxValue; + + public override void SetupCustomOption() { - private const int Id = 643100; - private static byte PlayerId = byte.MaxValue; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Analyst); - private static OptionItem UseLimitOpt; - private static OptionItem CD; - private static OptionItem Duration; - private static OptionItem SeeKillCount; - private static OptionItem SeeVentCount; - private static OptionItem SeeRoleBasis; - public static OptionItem UsePet; + UseLimitOpt = new IntegerOptionItem(Id + 10, "AbilityUseLimit", new(0, 30, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]) + .SetValueFormat(OptionFormat.Times); - private static readonly Dictionary ReplacementDict = new() { { "Analyze", Utils.ColorString(Utils.GetRoleColor(CustomRoles.Analyst), "Analyze") } }; + CD = new FloatOptionItem(Id + 11, "AnalyzeCD", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]) + .SetValueFormat(OptionFormat.Seconds); - public static Dictionary VentCount = []; - public (byte ID, long TIME) CurrentTarget = (byte.MaxValue, Utils.TimeStamp); + CD.ReplacementDictionary = ReplacementDict; - public override bool IsEnable => PlayerId != byte.MaxValue; + Duration = new IntegerOptionItem(Id + 12, "AnalyzeDur", new(1, 30, 1), 5, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Analyst); + Duration.ReplacementDictionary = ReplacementDict; - UseLimitOpt = new IntegerOptionItem(Id + 10, "AbilityUseLimit", new(0, 30, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]) - .SetValueFormat(OptionFormat.Times); + SeeKillCount = new BooleanOptionItem(Id + 13, "AnalyzerSeeKillCount", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]); - CD = new FloatOptionItem(Id + 11, "AnalyzeCD", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]) - .SetValueFormat(OptionFormat.Seconds); + SeeVentCount = new BooleanOptionItem(Id + 14, "AnalyzerSeeVentCount", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]); - CD.ReplacementDictionary = ReplacementDict; + SeeRoleBasis = new BooleanOptionItem(Id + 15, "AnalyzerSeeRoleBasis", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]); - Duration = new IntegerOptionItem(Id + 12, "AnalyzeDur", new(1, 30, 1), 5, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]) - .SetValueFormat(OptionFormat.Seconds); + UsePet = CreatePetUseSetting(Id + 16, CustomRoles.Analyst); + } - Duration.ReplacementDictionary = ReplacementDict; + public override bool CanUseKillButton(PlayerControl pc) + { + return CurrentTarget.ID == byte.MaxValue && pc.GetAbilityUseLimit() > 0; + } - SeeKillCount = new BooleanOptionItem(Id + 13, "AnalyzerSeeKillCount", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]); + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(false); + } - SeeVentCount = new BooleanOptionItem(Id + 14, "AnalyzerSeeVentCount", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]); - - SeeRoleBasis = new BooleanOptionItem(Id + 15, "AnalyzerSeeRoleBasis", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Analyst]); - - UsePet = CreatePetUseSetting(Id + 16, CustomRoles.Analyst); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return CurrentTarget.ID == byte.MaxValue && pc.GetAbilityUseLimit() > 0; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(false); - } - - private static string GetRoleBasis(CustomRoles role) - { - return SeeRoleBasis.GetBool() - ? role.GetVNRole(true) switch - { - CustomRoles.Impostor => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("Impostor")), - CustomRoles.Shapeshifter => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Speedrunner), GetString("Shapeshifter")), - CustomRoles.Crewmate => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Crewmate), GetString("Crewmate")), - CustomRoles.Engineer => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Autocrat), GetString("Engineer")), - CustomRoles.Scientist => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), GetString("Scientist")), - _ => string.Empty - } - : string.Empty; - } - - private static int GetKillCount(byte id) - { - return SeeKillCount.GetBool() ? Main.PlayerStates.Count(x => x.Value.GetRealKiller() == id) : 0; - } - - private static int GetVentCount(byte id) - { - return SeeVentCount.GetBool() ? VentCount.GetValueOrDefault(id, 0) : 0; - } - - private static string GetAnalyzeResult(PlayerControl pc) - { - return string.Format(GetString("AnalyzerResult"), pc.GetRealName().RemoveHtmlTags(), GetKillCount(pc.PlayerId), GetVentCount(pc.PlayerId), GetRoleBasis(pc.GetCustomRole())); - } - - public override void Init() - { - PlayerId = byte.MaxValue; - VentCount = []; - CurrentTarget = (byte.MaxValue, Utils.TimeStamp); - } - - public override void Add(byte id) - { - PlayerId = id; - id.SetAbilityUseLimit(UseLimitOpt.GetInt()); - CurrentTarget = (byte.MaxValue, Utils.TimeStamp); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = id.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; - } - - public static void OnAnyoneEnterVent(PlayerControl pc) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (!VentCount.TryAdd(pc.PlayerId, 1)) VentCount[pc.PlayerId]++; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return false; - - if (killer == null || target == null) return false; - - if (killer.GetAbilityUseLimit() <= 0) return false; - - if (CurrentTarget.ID != byte.MaxValue) return false; - - CurrentTarget = (target.PlayerId, Utils.TimeStamp); - killer.SetKillCooldown(Duration.GetFloat()); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable) return; - - if (pc == null) return; - - if (CurrentTarget.ID == byte.MaxValue) return; - - PlayerControl target = Utils.GetPlayerById(CurrentTarget.ID); - if (target == null) return; - - if (Vector2.Distance(target.Pos(), pc.Pos()) > (pc.Is(CustomRoles.Reach) ? 2.5f : 1.5f)) + private static string GetRoleBasis(CustomRoles role) + { + return SeeRoleBasis.GetBool() + ? role.GetVNRole(true) switch { - CurrentTarget.ID = byte.MaxValue; - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); - return; + CustomRoles.Impostor => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), GetString("Impostor")), + CustomRoles.Shapeshifter => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Speedrunner), GetString("Shapeshifter")), + CustomRoles.Crewmate => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Crewmate), GetString("Crewmate")), + CustomRoles.Engineer => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Autocrat), GetString("Engineer")), + CustomRoles.Scientist => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Doctor), GetString("Scientist")), + _ => string.Empty } + : string.Empty; + } - if (CurrentTarget.TIME + Duration.GetInt() <= Utils.TimeStamp) - { - CurrentTarget.ID = byte.MaxValue; - pc.RpcRemoveAbilityUse(); - pc.Notify(GetAnalyzeResult(target), 10f); - pc.SetKillCooldown(); - } - } + private static int GetKillCount(byte id) + { + return SeeKillCount.GetBool() ? Main.PlayerStates.Count(x => x.Value.GetRealKiller() == id) : 0; + } - public override void OnReportDeadBody() + private static int GetVentCount(byte id) + { + return SeeVentCount.GetBool() ? VentCount.GetValueOrDefault(id, 0) : 0; + } + + private static string GetAnalyzeResult(PlayerControl pc) + { + return string.Format(GetString("AnalyzerResult"), pc.GetRealName().RemoveHtmlTags(), GetKillCount(pc.PlayerId), GetVentCount(pc.PlayerId), GetRoleBasis(pc.GetCustomRole())); + } + + public override void Init() + { + PlayerId = byte.MaxValue; + VentCount = []; + CurrentTarget = (byte.MaxValue, Utils.TimeStamp); + } + + public override void Add(byte id) + { + PlayerId = id; + id.SetAbilityUseLimit(UseLimitOpt.GetInt()); + CurrentTarget = (byte.MaxValue, Utils.TimeStamp); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = id.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; + } + + public static void OnAnyoneEnterVent(PlayerControl pc) + { + if (!AmongUsClient.Instance.AmHost) return; + + if (!VentCount.TryAdd(pc.PlayerId, 1)) VentCount[pc.PlayerId]++; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return false; + + if (killer == null || target == null) return false; + + if (killer.GetAbilityUseLimit() <= 0) return false; + + if (CurrentTarget.ID != byte.MaxValue) return false; + + CurrentTarget = (target.PlayerId, Utils.TimeStamp); + killer.SetKillCooldown(Duration.GetFloat()); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable) return; + + if (pc == null) return; + + if (CurrentTarget.ID == byte.MaxValue) return; + + PlayerControl target = Utils.GetPlayerById(CurrentTarget.ID); + if (target == null) return; + + if (Vector2.Distance(target.Pos(), pc.Pos()) > (pc.Is(CustomRoles.Reach) ? 2.5f : 1.5f)) { - if (!IsEnable) return; - CurrentTarget.ID = byte.MaxValue; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); + return; + } + + if (CurrentTarget.TIME + Duration.GetInt() <= Utils.TimeStamp) + { + CurrentTarget.ID = byte.MaxValue; + pc.RpcRemoveAbilityUse(); + pc.Notify(GetAnalyzeResult(target), 10f); + pc.SetKillCooldown(); } } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + CurrentTarget.ID = byte.MaxValue; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Ankylosaurus.cs b/Roles/Crewmate/Ankylosaurus.cs index 180fc4369..be98a8116 100644 --- a/Roles/Crewmate/Ankylosaurus.cs +++ b/Roles/Crewmate/Ankylosaurus.cs @@ -2,62 +2,61 @@ using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Ankylosaurus : RoleBase { - public class Ankylosaurus : RoleBase + public static bool On; + + public override bool IsEnable => On; + + private static OptionItem Speed; + private static OptionItem Vision; + private static OptionItem Lives; + + private int LivesLeft; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(647100) + .AutoSetupOption(ref Speed, 0.8f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref Vision, 0.25f, new FloatValueRule(0f, 1.3f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref Lives, 3, new IntegerValueRule(1, 30, 1), OptionFormat.Health); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - private static OptionItem Speed; - private static OptionItem Vision; - private static OptionItem Lives; + public override void Add(byte playerId) + { + On = true; + LivesLeft = Lives.GetInt(); + } - private int LivesLeft; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + Main.AllPlayerSpeed[playerId] = Speed.GetFloat(); - public override void SetupCustomOption() - { - StartSetup(647100) - .AutoSetupOption(ref Speed, 0.8f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref Vision, 0.25f, new FloatValueRule(0f, 1.3f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref Lives, 3, new IntegerValueRule(1, 30, 1), OptionFormat.Health); - } + var vision = Vision.GetFloat(); + opt.SetFloat(FloatOptionNames.CrewLightMod, vision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); + } - public override void Init() - { - On = false; - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, LivesLeft - 1); + return LivesLeft-- <= 1; + } - public override void Add(byte playerId) - { - On = true; - LivesLeft = Lives.GetInt(); - } + public override string GetProgressText(byte playerId, bool comms) + { + return base.GetProgressText(playerId, comms) + $" ({LivesLeft} \u2665)"; + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - Main.AllPlayerSpeed[playerId] = Speed.GetFloat(); - - var vision = Vision.GetFloat(); - opt.SetFloat(FloatOptionNames.CrewLightMod, vision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - Utils.SendRPC(CustomRPC.SyncRoleData, target.PlayerId, LivesLeft - 1); - return LivesLeft-- <= 1; - } - - public override string GetProgressText(byte playerId, bool comms) - { - return base.GetProgressText(playerId, comms) + $" ({LivesLeft} \u2665)"; - } - - public void ReceiveRPC(MessageReader reader) - { - LivesLeft = reader.ReadPackedInt32(); - } + public void ReceiveRPC(MessageReader reader) + { + LivesLeft = reader.ReadPackedInt32(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Autocrat.cs b/Roles/Crewmate/Autocrat.cs index 2ef01a3c4..838a1e454 100644 --- a/Roles/Crewmate/Autocrat.cs +++ b/Roles/Crewmate/Autocrat.cs @@ -1,32 +1,31 @@ using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Autocrat : RoleBase { - internal class Autocrat : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override void SetupCustomOption() + { + Options.SetupRoleOptions(642620, TabGroup.CrewmateRoles, CustomRoles.Autocrat); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(642620, TabGroup.CrewmateRoles, CustomRoles.Autocrat); - } + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (!pc.IsAlive()) return; - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (!pc.IsAlive()) return; - - Main.AllAlivePlayerControls.OrderBy(x => Vector2.Distance(x.Pos(), pc.Pos())).FirstOrDefault(x => x.PlayerId != pc.PlayerId)?.TP(pc); - } + Main.AllAlivePlayerControls.OrderBy(x => Vector2.Distance(x.Pos(), pc.Pos())).FirstOrDefault(x => x.PlayerId != pc.PlayerId)?.TP(pc); } } \ No newline at end of file diff --git a/Roles/Crewmate/Bane.cs b/Roles/Crewmate/Bane.cs index 7f340df26..42270cd08 100644 --- a/Roles/Crewmate/Bane.cs +++ b/Roles/Crewmate/Bane.cs @@ -1,32 +1,31 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Bane : RoleBase { - public class Bane : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void Init() { - public static bool On; - public override bool IsEnable => On; + On = false; + } - public override void Init() - { - On = false; - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void SetupCustomOption() + { + Options.SetupRoleOptions(642600, TabGroup.CrewmateRoles, CustomRoles.Bane); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(642600, TabGroup.CrewmateRoles, CustomRoles.Bane); - } + public static void OnKilled(PlayerControl killer) + { + if (killer == null || killer.Is(CustomRoles.Bloodlust)) return; - public static void OnKilled(PlayerControl killer) - { - if (killer == null || killer.Is(CustomRoles.Bloodlust)) return; - - CustomRoles erasedRole = killer.IsImpostor() ? CustomRoles.ImpostorEHR : killer.IsCrewmate() ? CustomRoles.CrewmateEHR : CustomRoles.Amnesiac; - killer.RpcSetCustomRole(erasedRole); - killer.RpcChangeRoleBasis(erasedRole); - } + CustomRoles erasedRole = killer.IsImpostor() ? CustomRoles.ImpostorEHR : killer.IsCrewmate() ? CustomRoles.CrewmateEHR : CustomRoles.Amnesiac; + killer.RpcSetCustomRole(erasedRole); + killer.RpcChangeRoleBasis(erasedRole); } } \ No newline at end of file diff --git a/Roles/Crewmate/Beacon.cs b/Roles/Crewmate/Beacon.cs index be7e069bf..3b0ed3cde 100644 --- a/Roles/Crewmate/Beacon.cs +++ b/Roles/Crewmate/Beacon.cs @@ -2,95 +2,94 @@ using System.Linq; using EHR.Modules; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Beacon : RoleBase { - internal class Beacon : RoleBase + private static List Beacons = []; + + private static OptionItem VisionIncrease; + private static OptionItem Radius; + private static List AffectedPlayers = []; + private static Dictionary LastChange = []; + + public static bool On; + private static int Id => 643480; + public override bool IsEnable => On; + public static float IncreasedVision => VisionIncrease.GetFloat() * 5f; + + public override void SetupCustomOption() { - private static List Beacons = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Beacon); - private static OptionItem VisionIncrease; - private static OptionItem Radius; - private static List AffectedPlayers = []; - private static Dictionary LastChange = []; + VisionIncrease = new FloatOptionItem(Id + 2, "BeaconVisionIncrease", new(0.05f, 1.25f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beacon]) + .SetValueFormat(OptionFormat.Multiplier); - public static bool On; - private static int Id => 643480; - public override bool IsEnable => On; - public static float IncreasedVision => VisionIncrease.GetFloat() * 5f; + Radius = new FloatOptionItem(Id + 3, "PerceiverRadius", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beacon]) + .SetValueFormat(OptionFormat.Multiplier); + } - public override void SetupCustomOption() + public override void Init() + { + Beacons = []; + AffectedPlayers = []; + LastChange = []; + On = false; + } + + public override void Add(byte playerId) + { + On = true; + Beacons.Add(playerId.GetPlayer()); + } + + public static bool IsAffectedPlayer(byte id) + { + return Utils.IsActive(SystemTypes.Electrical) && AffectedPlayers.Contains(id); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (!GameStates.IsInTask || pc == null) return; + + long now = Utils.TimeStamp; + if (LastChange.TryGetValue(pc.PlayerId, out long ts) && ts == now) return; + + Vector2 pos = pc.Pos(); + float radius = Radius.GetFloat(); + bool beaconNearby = Beacons.Any(x => Vector2.Distance(x.Pos(), pos) <= radius); + bool affectedPlayer = AffectedPlayers.Contains(pc.PlayerId); + + switch (affectedPlayer) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Beacon); - - VisionIncrease = new FloatOptionItem(Id + 2, "BeaconVisionIncrease", new(0.05f, 1.25f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beacon]) - .SetValueFormat(OptionFormat.Multiplier); - - Radius = new FloatOptionItem(Id + 3, "PerceiverRadius", new(0.1f, 5f, 0.1f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beacon]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Init() - { - Beacons = []; - AffectedPlayers = []; - LastChange = []; - On = false; - } - - public override void Add(byte playerId) - { - On = true; - Beacons.Add(playerId.GetPlayer()); - } - - public static bool IsAffectedPlayer(byte id) - { - return Utils.IsActive(SystemTypes.Electrical) && AffectedPlayers.Contains(id); - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (!GameStates.IsInTask || pc == null) return; - - long now = Utils.TimeStamp; - if (LastChange.TryGetValue(pc.PlayerId, out long ts) && ts == now) return; - - Vector2 pos = pc.Pos(); - float radius = Radius.GetFloat(); - bool beaconNearby = Beacons.Any(x => Vector2.Distance(x.Pos(), pos) <= radius); - bool affectedPlayer = AffectedPlayers.Contains(pc.PlayerId); - - switch (affectedPlayer) + case true when !beaconNearby: { - case true when !beaconNearby: - { - AffectedPlayers.Remove(pc.PlayerId); - if (Utils.IsActive(SystemTypes.Electrical)) pc.MarkDirtySettings(); + AffectedPlayers.Remove(pc.PlayerId); + if (Utils.IsActive(SystemTypes.Electrical)) pc.MarkDirtySettings(); - LastChange[pc.PlayerId] = now; - break; - } - case false when beaconNearby: - { - AffectedPlayers.Add(pc.PlayerId); - if (Utils.IsActive(SystemTypes.Electrical)) pc.MarkDirtySettings(); + LastChange[pc.PlayerId] = now; + break; + } + case false when beaconNearby: + { + AffectedPlayers.Add(pc.PlayerId); + if (Utils.IsActive(SystemTypes.Electrical)) pc.MarkDirtySettings(); - LastChange[pc.PlayerId] = now; + LastChange[pc.PlayerId] = now; - if (pc.IsLocalPlayer()) - Achievements.Type.ALightInTheShadows.CompleteAfterGameEnd(); + if (pc.IsLocalPlayer()) + Achievements.Type.ALightInTheShadows.CompleteAfterGameEnd(); - break; - } + break; } } + } - public override void OnReportDeadBody() - { - AffectedPlayers.Clear(); - LastChange.Clear(); - } + public override void OnReportDeadBody() + { + AffectedPlayers.Clear(); + LastChange.Clear(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Benefactor.cs b/Roles/Crewmate/Benefactor.cs index d536bd5a0..905ac9d37 100644 --- a/Roles/Crewmate/Benefactor.cs +++ b/Roles/Crewmate/Benefactor.cs @@ -5,180 +5,179 @@ using EHR.Modules; using Hazel; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Benefactor : RoleBase { - internal class Benefactor : RoleBase + private const int Id = 8670; + private static List PlayerIdList = []; + + private static Dictionary> TaskIndex = []; + private static Dictionary TaskMarkPerRound = []; + private static Dictionary ShieldedPlayers = []; + private static int MaxTasksMarkedPerRound; + + private static OptionItem TaskMarkPerRoundOpt; + private static OptionItem ShieldDuration; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 8670; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Benefactor); - private static Dictionary> TaskIndex = []; - private static Dictionary TaskMarkPerRound = []; - private static Dictionary ShieldedPlayers = []; - private static int MaxTasksMarkedPerRound; + TaskMarkPerRoundOpt = new IntegerOptionItem(Id + 10, "TaskMarkPerRound", new(1, 14, 1), 3, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Benefactor]) + .SetValueFormat(OptionFormat.Votes); - private static OptionItem TaskMarkPerRoundOpt; - private static OptionItem ShieldDuration; + ShieldDuration = new IntegerOptionItem(Id + 11, "AidDur", new(1, 30, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Benefactor]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + Options.OverrideTasksData.Create(Id + 12, TabGroup.CrewmateRoles, CustomRoles.Benefactor); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + TaskIndex = []; + TaskMarkPerRound = []; + ShieldedPlayers = []; + MaxTasksMarkedPerRound = TaskMarkPerRoundOpt.GetInt(); + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + TaskMarkPerRound[playerId] = 0; + } + + private static void SendRPC(byte benefactorId, int taskIndex = -1, bool isShield = false, bool clearAll = false, bool shieldExpire = false, byte shieldedId = byte.MaxValue) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncBenefactorMarkedTask, SendOption.Reliable); + writer.Write(benefactorId); + writer.Write(taskIndex); + writer.Write(isShield); + writer.Write(clearAll); + writer.Write(shieldExpire); + writer.Write(shieldedId); + if (!isShield) writer.Write(TaskMarkPerRound[benefactorId]); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte benefactorID = reader.ReadByte(); + if (Main.PlayerStates[benefactorID].Role is not Benefactor { IsEnable: true }) return; + + int taskInd = reader.ReadInt32(); + bool IsShield = reader.ReadBoolean(); + bool clearAll = reader.ReadBoolean(); + bool shieldExpire = reader.ReadBoolean(); + byte shieldedId = reader.ReadByte(); + + if (!IsShield) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Benefactor); + int uses = reader.ReadInt32(); + TaskMarkPerRound[benefactorID] = uses; - TaskMarkPerRoundOpt = new IntegerOptionItem(Id + 10, "TaskMarkPerRound", new(1, 14, 1), 3, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Benefactor]) - .SetValueFormat(OptionFormat.Votes); + if (!clearAll && !shieldExpire) + { + if (!TaskIndex.ContainsKey(benefactorID)) TaskIndex[benefactorID] = []; - ShieldDuration = new IntegerOptionItem(Id + 11, "AidDur", new(1, 30, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Benefactor]) - .SetValueFormat(OptionFormat.Seconds); - - Options.OverrideTasksData.Create(Id + 12, TabGroup.CrewmateRoles, CustomRoles.Benefactor); + TaskIndex[benefactorID].Add(taskInd); + } + } + else + { + if (TaskIndex.ContainsKey(benefactorID)) TaskIndex[benefactorID].Remove(taskInd); } - public override void Init() - { - PlayerIdList = []; - TaskIndex = []; - TaskMarkPerRound = []; - ShieldedPlayers = []; - MaxTasksMarkedPerRound = TaskMarkPerRoundOpt.GetInt(); - } + if (clearAll && TaskIndex.ContainsKey(benefactorID)) TaskIndex[benefactorID].Clear(); - public override void Add(byte playerId) + if (IsShield) ShieldedPlayers.TryAdd(shieldedId, Utils.TimeStamp); + + if (shieldExpire) ShieldedPlayers.Remove(shieldedId); + + if (clearAll && ShieldedPlayers.Count > 0) ShieldedPlayers.Clear(); + } + + public override string GetProgressText(byte playerId, bool comms) + { + if (!IsEnable) return string.Empty; + + TaskMarkPerRound.TryAdd(playerId, 0); + int markedTasks = TaskMarkPerRound[playerId]; + int x = Math.Max(MaxTasksMarkedPerRound - markedTasks, 0); + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Benefactor).ShadeColor(0.25f), $"({x})"); + } + + public override void AfterMeetingTasks() + { + if (!IsEnable) return; + + ShieldedPlayers.Clear(); + + foreach (byte playerId in TaskMarkPerRound.Keys.ToArray()) { - PlayerIdList.Add(playerId); TaskMarkPerRound[playerId] = 0; + if (TaskIndex.ContainsKey(playerId)) TaskIndex[playerId].Clear(); + + SendRPC(playerId, clearAll: true); } + } - private static void SendRPC(byte benefactorId, int taskIndex = -1, bool isShield = false, bool clearAll = false, bool shieldExpire = false, byte shieldedId = byte.MaxValue) + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable) return; + + foreach (KeyValuePair x in ShieldedPlayers.Where(x => x.Value + ShieldDuration.GetInt() < Utils.TimeStamp)) { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncBenefactorMarkedTask, SendOption.Reliable); - writer.Write(benefactorId); - writer.Write(taskIndex); - writer.Write(isShield); - writer.Write(clearAll); - writer.Write(shieldExpire); - writer.Write(shieldedId); - if (!isShield) writer.Write(TaskMarkPerRound[benefactorId]); - - AmongUsClient.Instance.FinishRpcImmediately(writer); + ShieldedPlayers.Remove(x.Key); + SendRPC(pc.PlayerId, shieldExpire: true, shieldedId: Utils.GetPlayerById(x.Key).PlayerId); } + } - public static void ReceiveRPC(MessageReader reader) + public static void OnTaskComplete(PlayerControl player, PlayerTask task) // Special case for Benefactor + { + if (player == null) return; + + byte playerId = player.PlayerId; + + if (player.Is(CustomRoles.Benefactor)) { - byte benefactorID = reader.ReadByte(); - if (Main.PlayerStates[benefactorID].Role is not Benefactor { IsEnable: true }) return; - - int taskInd = reader.ReadInt32(); - bool IsShield = reader.ReadBoolean(); - bool clearAll = reader.ReadBoolean(); - bool shieldExpire = reader.ReadBoolean(); - byte shieldedId = reader.ReadByte(); - - if (!IsShield) - { - int uses = reader.ReadInt32(); - TaskMarkPerRound[benefactorID] = uses; - - if (!clearAll && !shieldExpire) - { - if (!TaskIndex.ContainsKey(benefactorID)) TaskIndex[benefactorID] = []; - - TaskIndex[benefactorID].Add(taskInd); - } - } - else - { - if (TaskIndex.ContainsKey(benefactorID)) TaskIndex[benefactorID].Remove(taskInd); - } - - if (clearAll && TaskIndex.ContainsKey(benefactorID)) TaskIndex[benefactorID].Clear(); - - if (IsShield) ShieldedPlayers.TryAdd(shieldedId, Utils.TimeStamp); - - if (shieldExpire) ShieldedPlayers.Remove(shieldedId); - - if (clearAll && ShieldedPlayers.Count > 0) ShieldedPlayers.Clear(); - } - - public override string GetProgressText(byte playerId, bool comms) - { - if (!IsEnable) return string.Empty; - TaskMarkPerRound.TryAdd(playerId, 0); - int markedTasks = TaskMarkPerRound[playerId]; - int x = Math.Max(MaxTasksMarkedPerRound - markedTasks, 0); - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Benefactor).ShadeColor(0.25f), $"({x})"); - } - public override void AfterMeetingTasks() - { - if (!IsEnable) return; - - ShieldedPlayers.Clear(); - - foreach (byte playerId in TaskMarkPerRound.Keys.ToArray()) + if (TaskMarkPerRound[playerId] >= MaxTasksMarkedPerRound) { - TaskMarkPerRound[playerId] = 0; - if (TaskIndex.ContainsKey(playerId)) TaskIndex[playerId].Clear(); - - SendRPC(playerId, clearAll: true); + TaskMarkPerRound[playerId] = MaxTasksMarkedPerRound; + Logger.Info($"Max task per round ({TaskMarkPerRound[playerId]}) reached for {player.GetNameWithRole()}", "Benefactor"); + return; } + + TaskMarkPerRound[playerId]++; + if (!TaskIndex.ContainsKey(playerId)) TaskIndex[playerId] = []; + + TaskIndex[playerId].Add(task.Index); + SendRPC(playerId, task.Index); + player.Notify(GetString("BenefactorTaskMarked")); } - - public override void OnFixedUpdate(PlayerControl pc) + else { - if (!IsEnable) return; - - foreach (KeyValuePair x in ShieldedPlayers.Where(x => x.Value + ShieldDuration.GetInt() < Utils.TimeStamp)) + foreach (byte benefactorId in TaskIndex.Keys.ToArray()) { - ShieldedPlayers.Remove(x.Key); - SendRPC(pc.PlayerId, shieldExpire: true, shieldedId: Utils.GetPlayerById(x.Key).PlayerId); - } - } - - public static void OnTaskComplete(PlayerControl player, PlayerTask task) // Special case for Benefactor - { - if (player == null) return; - - byte playerId = player.PlayerId; - - if (player.Is(CustomRoles.Benefactor)) - { - TaskMarkPerRound.TryAdd(playerId, 0); - - if (TaskMarkPerRound[playerId] >= MaxTasksMarkedPerRound) + if (TaskIndex[benefactorId].Contains(task.Index)) { - TaskMarkPerRound[playerId] = MaxTasksMarkedPerRound; - Logger.Info($"Max task per round ({TaskMarkPerRound[playerId]}) reached for {player.GetNameWithRole()}", "Benefactor"); - return; - } + PlayerControl benefactorPC = Utils.GetPlayerById(benefactorId); + if (benefactorPC == null) continue; - TaskMarkPerRound[playerId]++; - if (!TaskIndex.ContainsKey(playerId)) TaskIndex[playerId] = []; - - TaskIndex[playerId].Add(task.Index); - SendRPC(playerId, task.Index); - player.Notify(GetString("BenefactorTaskMarked")); - } - else - { - foreach (byte benefactorId in TaskIndex.Keys.ToArray()) - { - if (TaskIndex[benefactorId].Contains(task.Index)) - { - PlayerControl benefactorPC = Utils.GetPlayerById(benefactorId); - if (benefactorPC == null) continue; - - player.Notify(GetString("BenefactorTargetGotShieldNotify")); - TaskIndex[benefactorId].Remove(task.Index); - SendRPC(benefactorId, task.Index, true, shieldedId: player.PlayerId); - Logger.Info($"{player.GetAllRoleName()} got a shield because the task was marked by {benefactorPC.GetNameWithRole()}", "Benefactor"); - } + player.Notify(GetString("BenefactorTargetGotShieldNotify")); + TaskIndex[benefactorId].Remove(task.Index); + SendRPC(benefactorId, task.Index, true, shieldedId: player.PlayerId); + Logger.Info($"{player.GetAllRoleName()} got a shield because the task was marked by {benefactorPC.GetNameWithRole()}", "Benefactor"); } } } diff --git a/Roles/Crewmate/Bloodhound.cs b/Roles/Crewmate/Bloodhound.cs index 640ea62dc..9708834e9 100644 --- a/Roles/Crewmate/Bloodhound.cs +++ b/Roles/Crewmate/Bloodhound.cs @@ -2,141 +2,140 @@ using System.Linq; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; +using static Translator; + +public class Bloodhound : RoleBase { - using static Options; - using static Translator; + private const int Id = 6400; + private static List PlayerIdList = []; - public class Bloodhound : RoleBase + public static List UnreportablePlayers = []; + + public static OptionItem ArrowsPointingToDeadBody; + public static OptionItem UseLimitOpt; + public static OptionItem LeaveDeadBodyUnreportable; + public static OptionItem NotifyKiller; + public static OptionItem BloodhoundAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private byte BloodhoundId; + private List BloodhoundTargets = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 6400; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Bloodhound); - public static List UnreportablePlayers = []; + ArrowsPointingToDeadBody = new BooleanOptionItem(Id + 10, "BloodhoundArrowsPointingToDeadBody", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]); - public static OptionItem ArrowsPointingToDeadBody; - public static OptionItem UseLimitOpt; - public static OptionItem LeaveDeadBodyUnreportable; - public static OptionItem NotifyKiller; - public static OptionItem BloodhoundAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + LeaveDeadBodyUnreportable = new BooleanOptionItem(Id + 11, "BloodhoundLeaveDeadBodyUnreportable", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]); - private byte BloodhoundId; - private List BloodhoundTargets = []; + NotifyKiller = new BooleanOptionItem(Id + 14, "BloodhoundNotifyKiller", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]); - public override bool IsEnable => PlayerIdList.Count > 0; + UseLimitOpt = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + BloodhoundAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 15, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + UnreportablePlayers = []; + BloodhoundTargets = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + BloodhoundTargets = []; + BloodhoundId = playerId; + } + + public override void OnReportDeadBody() + { + foreach (byte id in PlayerIdList) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Bloodhound); - - ArrowsPointingToDeadBody = new BooleanOptionItem(Id + 10, "BloodhoundArrowsPointingToDeadBody", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]); - - LeaveDeadBodyUnreportable = new BooleanOptionItem(Id + 11, "BloodhoundLeaveDeadBodyUnreportable", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]); - - NotifyKiller = new BooleanOptionItem(Id + 14, "BloodhoundNotifyKiller", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]); - - UseLimitOpt = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]) - .SetValueFormat(OptionFormat.Times); - - BloodhoundAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 15, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bloodhound]) - .SetValueFormat(OptionFormat.Times); + TargetArrow.RemoveAllTarget(id); + LocateArrow.RemoveAllTarget(id); } - public override void Init() + BloodhoundTargets.Clear(); + } + + public override void AfterMeetingTasks() + { + TargetArrow.RemoveAllTarget(BloodhoundId); + LocateArrow.RemoveAllTarget(BloodhoundId); + } + + public static void OnPlayerDead(PlayerControl target) + { + if (!ArrowsPointingToDeadBody.GetBool()) return; + + foreach (byte id in PlayerIdList) { - PlayerIdList = []; - UnreportablePlayers = []; - BloodhoundTargets = []; - } + PlayerControl player = Utils.GetPlayerById(id); + if (player == null || !player.IsAlive()) continue; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - BloodhoundTargets = []; - BloodhoundId = playerId; - } - - public override void OnReportDeadBody() - { - foreach (byte id in PlayerIdList) - { - TargetArrow.RemoveAllTarget(id); - LocateArrow.RemoveAllTarget(id); - } - - BloodhoundTargets.Clear(); - } - - public override void AfterMeetingTasks() - { - TargetArrow.RemoveAllTarget(BloodhoundId); - LocateArrow.RemoveAllTarget(BloodhoundId); - } - - public static void OnPlayerDead(PlayerControl target) - { - if (!ArrowsPointingToDeadBody.GetBool()) return; - - foreach (byte id in PlayerIdList) - { - PlayerControl player = Utils.GetPlayerById(id); - if (player == null || !player.IsAlive()) continue; - - Vector2 pos = target.Pos(); - LocateArrow.Add(id, pos); - } - } - - public override bool CheckReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target, PlayerControl killer) - { - if (killer != null && !target.Object.Is(CustomRoles.Unreportable)) - { - if (BloodhoundTargets.Contains(killer.PlayerId)) return false; - - Vector2 pos = target.Object.Pos(); - LocateArrow.Remove(pc.PlayerId, pos); - - if (pc.GetAbilityUseLimit() >= 1) - { - BloodhoundTargets.Add(killer.PlayerId); - TargetArrow.Add(pc.PlayerId, killer.PlayerId); - - pc.Notify(GetString("BloodhoundTrackRecorded")); - pc.RpcRemoveAbilityUse(); - - if (LeaveDeadBodyUnreportable.GetBool()) UnreportablePlayers.Add(target.PlayerId); - - if (NotifyKiller.GetBool()) killer.Notify(GetString("BloodhoundKillerNotify")); - } - else - pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); - } - else - pc.Notify(GetString("BloodhoundNoTrack")); - - return false; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (GameStates.IsMeeting || seer.PlayerId != BloodhoundId || hud) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Bloodhound bh) return string.Empty; - - return bh.BloodhoundTargets.Count > 0 ? bh.BloodhoundTargets.Select(targetId => TargetArrow.GetArrows(seer, targetId)).Aggregate(string.Empty, (current, arrow) => current + Utils.ColorString(seer.GetRoleColor(), arrow)) : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); + Vector2 pos = target.Pos(); + LocateArrow.Add(id, pos); } } + + public override bool CheckReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target, PlayerControl killer) + { + if (killer != null && !target.Object.Is(CustomRoles.Unreportable)) + { + if (BloodhoundTargets.Contains(killer.PlayerId)) return false; + + Vector2 pos = target.Object.Pos(); + LocateArrow.Remove(pc.PlayerId, pos); + + if (pc.GetAbilityUseLimit() >= 1) + { + BloodhoundTargets.Add(killer.PlayerId); + TargetArrow.Add(pc.PlayerId, killer.PlayerId); + + pc.Notify(GetString("BloodhoundTrackRecorded")); + pc.RpcRemoveAbilityUse(); + + if (LeaveDeadBodyUnreportable.GetBool()) UnreportablePlayers.Add(target.PlayerId); + + if (NotifyKiller.GetBool()) killer.Notify(GetString("BloodhoundKillerNotify")); + } + else + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + } + else + pc.Notify(GetString("BloodhoundNoTrack")); + + return false; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + if (GameStates.IsMeeting || seer.PlayerId != BloodhoundId || hud) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Bloodhound bh) return string.Empty; + + return bh.BloodhoundTargets.Count > 0 ? bh.BloodhoundTargets.Select(targetId => TargetArrow.GetArrows(seer, targetId)).Aggregate(string.Empty, (current, arrow) => current + Utils.ColorString(seer.GetRoleColor(), arrow)) : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Bodyguard.cs b/Roles/Crewmate/Bodyguard.cs index c5a687084..42e707197 100644 --- a/Roles/Crewmate/Bodyguard.cs +++ b/Roles/Crewmate/Bodyguard.cs @@ -2,75 +2,74 @@ using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Bodyguard : RoleBase { - internal class Bodyguard : RoleBase + public static bool On; + private static List Instances = []; + private PlayerControl BodyguardPC; + public override bool IsEnable => On; + + public override void Init() { - public static bool On; - private static List Instances = []; - private PlayerControl BodyguardPC; - public override bool IsEnable => On; + On = false; + Instances = []; + } - public override void Init() + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + BodyguardPC = Utils.GetPlayerById(playerId); + } + + public override void SetupCustomOption() + { + SetupRoleOptions(8400, TabGroup.CrewmateRoles, CustomRoles.Bodyguard); + + BodyguardProtectRadius = new FloatOptionItem(8410, "BodyguardProtectRadius", new(0.5f, 5f, 0.5f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bodyguard]) + .SetValueFormat(OptionFormat.Multiplier); + + BodyguardKillsKiller = new BooleanOptionItem(8411, "BodyguardKillsKiller", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bodyguard]); + } + + public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.IsCrewmate()) return true; + + if (killer.PlayerId != target.PlayerId) { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - BodyguardPC = Utils.GetPlayerById(playerId); - } - - public override void SetupCustomOption() - { - SetupRoleOptions(8400, TabGroup.CrewmateRoles, CustomRoles.Bodyguard); - - BodyguardProtectRadius = new FloatOptionItem(8410, "BodyguardProtectRadius", new(0.5f, 5f, 0.5f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bodyguard]) - .SetValueFormat(OptionFormat.Multiplier); - - BodyguardKillsKiller = new BooleanOptionItem(8411, "BodyguardKillsKiller", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bodyguard]); - } - - public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.IsCrewmate()) return true; - - if (killer.PlayerId != target.PlayerId) + foreach (Bodyguard bodyguard in Instances) { - foreach (Bodyguard bodyguard in Instances) + if (bodyguard.BodyguardPC.PlayerId == target.PlayerId) continue; + + float dis = Vector2.Distance(bodyguard.BodyguardPC.Pos(), target.Pos()); + if (dis > BodyguardProtectRadius.GetFloat()) return true; + + if (bodyguard.BodyguardPC.IsMadmate() && killer.Is(Team.Impostor)) { - if (bodyguard.BodyguardPC.PlayerId == target.PlayerId) continue; - - float dis = Vector2.Distance(bodyguard.BodyguardPC.Pos(), target.Pos()); - if (dis > BodyguardProtectRadius.GetFloat()) return true; - - if (bodyguard.BodyguardPC.IsMadmate() && killer.Is(Team.Impostor)) - { - Logger.Info($"{bodyguard.BodyguardPC.GetRealName()} is a madmate, so they chose to ignore the murder scene", "Bodyguard"); - continue; - } - - if (BodyguardKillsKiller.GetBool() && bodyguard.BodyguardPC.RpcCheckAndMurder(killer, true)) - bodyguard.BodyguardPC.Kill(killer); - else - killer.SetKillCooldown(); - - bodyguard.BodyguardPC.Suicide(PlayerState.DeathReason.Sacrifice, killer); - Logger.Info($"{bodyguard.BodyguardPC.GetRealName()} stood up and died for {target.GetRealName()}", "Bodyguard"); - - if (bodyguard.BodyguardPC.IsLocalPlayer() && target.Is(CustomRoles.President)) - Achievements.Type.GetDownMrPresident.Complete(); - - return false; + Logger.Info($"{bodyguard.BodyguardPC.GetRealName()} is a madmate, so they chose to ignore the murder scene", "Bodyguard"); + continue; } - } - return true; + if (BodyguardKillsKiller.GetBool() && bodyguard.BodyguardPC.RpcCheckAndMurder(killer, true)) + bodyguard.BodyguardPC.Kill(killer); + else + killer.SetKillCooldown(); + + bodyguard.BodyguardPC.Suicide(PlayerState.DeathReason.Sacrifice, killer); + Logger.Info($"{bodyguard.BodyguardPC.GetRealName()} stood up and died for {target.GetRealName()}", "Bodyguard"); + + if (bodyguard.BodyguardPC.IsLocalPlayer() && target.Is(CustomRoles.President)) + Achievements.Type.GetDownMrPresident.Complete(); + + return false; + } } + + return true; } } \ No newline at end of file diff --git a/Roles/Crewmate/CameraMan.cs b/Roles/Crewmate/CameraMan.cs index fe901fd3f..c861872c2 100644 --- a/Roles/Crewmate/CameraMan.cs +++ b/Roles/Crewmate/CameraMan.cs @@ -2,129 +2,128 @@ using System; using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; + +public class CameraMan : RoleBase { - using static Options; + private const int Id = 641600; + private static List PlayerIdList = []; - public class CameraMan : RoleBase + public static OptionItem VentCooldown; + public static OptionItem UseLimitOpt; + public static OptionItem TPBackWhenMoveAway; + public static OptionItem CameraManAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private static Vector2 CameraPosition; + private Vector2 BasePos; + + private bool IsTeleported; + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 641600; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.CameraMan); - public static OptionItem VentCooldown; - public static OptionItem UseLimitOpt; - public static OptionItem TPBackWhenMoveAway; - public static OptionItem CameraManAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + VentCooldown = new FloatOptionItem(Id + 10, "VentCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) + .SetValueFormat(OptionFormat.Seconds); - private static Vector2 CameraPosition; - private Vector2 BasePos; + UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) + .SetValueFormat(OptionFormat.Times); - private bool IsTeleported; - public override bool IsEnable => PlayerIdList.Count > 0; + TPBackWhenMoveAway = new BooleanOptionItem(Id + 14, "TPBackWhenMoveAway", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]); - public override void SetupCustomOption() + CameraManAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + + CameraPosition = Main.CurrentMap switch { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.CameraMan); + MapNames.Skeld => new(-13.5f, -5.5f), + MapNames.Mira => new(15.3f, 3.8f), + MapNames.Polus => new(3.0f, -12.0f), + MapNames.Dleks => new(-13.5f, -5.5f), + MapNames.Airship => new(5.8f, -10.8f), + MapNames.Fungle => new(9.5f, 1.2f), + _ => throw new NotImplementedException() + }; + } - VentCooldown = new FloatOptionItem(Id + 10, "VentCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + IsTeleported = false; + } - UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) - .SetValueFormat(OptionFormat.Times); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } - TPBackWhenMoveAway = new BooleanOptionItem(Id + 14, "TPBackWhenMoveAway", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]); + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (pc == null) return; - CameraManAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CameraMan]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() + if (pc.GetAbilityUseLimit() >= 1) { - PlayerIdList = []; + pc.RpcRemoveAbilityUse(); - CameraPosition = Main.CurrentMap switch + LateTask.New(() => { - MapNames.Skeld => new(-13.5f, -5.5f), - MapNames.Mira => new(15.3f, 3.8f), - MapNames.Polus => new(3.0f, -12.0f), - MapNames.Dleks => new(-13.5f, -5.5f), - MapNames.Airship => new(5.8f, -10.8f), - MapNames.Fungle => new(9.5f, 1.2f), - _ => throw new NotImplementedException() - }; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - IsTeleported = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (pc == null) return; - - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - - LateTask.New(() => - { - BasePos = pc.Pos(); - if (pc.TP(CameraPosition)) IsTeleported = true; - }, 2f, "CameraMan Teleport"); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override void OnPet(PlayerControl pc) - { - if (pc == null) return; - - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - BasePos = pc.Pos(); if (pc.TP(CameraPosition)) IsTeleported = true; - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + }, 2f, "CameraMan Teleport"); } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override void OnFixedUpdate(PlayerControl pc) + public override void OnPet(PlayerControl pc) + { + if (pc == null) return; + + if (pc.GetAbilityUseLimit() >= 1) { - if (!IsTeleported || !TPBackWhenMoveAway.GetBool() || !pc.IsAlive() || !GameStates.IsInTask || Vector2.Distance(pc.Pos(), CameraPosition) <= DisableDevice.UsableDistance) return; + pc.RpcRemoveAbilityUse(); - IsTeleported = false; - LateTask.New(() => pc.TP(BasePos), 2f, "CameraMan Teleport Back"); + BasePos = pc.Pos(); + if (pc.TP(CameraPosition)) IsTeleported = true; } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override void OnReportDeadBody() - { - IsTeleported = false; - } + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsTeleported || !TPBackWhenMoveAway.GetBool() || !pc.IsAlive() || !GameStates.IsInTask || Vector2.Distance(pc.Pos(), CameraPosition) <= DisableDevice.UsableDistance) return; - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + IsTeleported = false; + LateTask.New(() => pc.TP(BasePos), 2f, "CameraMan Teleport Back"); + } + + public override void OnReportDeadBody() + { + IsTeleported = false; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/CandleLighter.cs b/Roles/Crewmate/CandleLighter.cs index 02611afee..fa57b75f9 100644 --- a/Roles/Crewmate/CandleLighter.cs +++ b/Roles/Crewmate/CandleLighter.cs @@ -3,114 +3,113 @@ using System.Collections.Generic; using AmongUs.GameOptions; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; + +public class Ignitor : RoleBase // Candle Lighter from TOHY { - using static Options; + private const int Id = 5280; + private static List PlayerIdList = []; - public class Ignitor : RoleBase // Candle Lighter from TOHY + private static OptionItem OptionTaskStartVision; + private static OptionItem OptionCountStartTime; + private static OptionItem OptionTaskEndVisionTime; + private static OptionItem OptionTaskEndVision; + private static OptionItem OptionTaskTimeMoveMeeting; + private static OptionItem OptionTasksFinishedVision; + private bool Active = true; + private float ElapsedTime; + + private float UpdateTime; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 5280; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ignitor); - private static OptionItem OptionTaskStartVision; - private static OptionItem OptionCountStartTime; - private static OptionItem OptionTaskEndVisionTime; - private static OptionItem OptionTaskEndVision; - private static OptionItem OptionTaskTimeMoveMeeting; - private static OptionItem OptionTasksFinishedVision; - private bool Active = true; - private float ElapsedTime; + OptionTaskStartVision = new FloatOptionItem(Id + 2, "CandleLighterStartVision", new(0.5f, 5f, 0.1f), 0.8f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) + .SetValueFormat(OptionFormat.Multiplier); - private float UpdateTime; + OptionCountStartTime = new IntegerOptionItem(Id + 3, "CandleLighterCountStartTime", new(0, 50, 5), 0, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + OptionTaskEndVisionTime = new IntegerOptionItem(Id + 4, "CandleLighterEndVisionTime", new(20, 200, 10), 50, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + OptionTaskEndVision = new FloatOptionItem(Id + 5, "CandleLighterEndVision", new(0f, 0.5f, 0.05f), 0.1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) + .SetValueFormat(OptionFormat.Multiplier); + + OptionTaskTimeMoveMeeting = new BooleanOptionItem(Id + 6, "CandleLighterTimeMoveMeeting", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]); + + OptionTasksFinishedVision = new FloatOptionItem(Id + 7, "CandleLighterTasksFinishedVision", new(0.5f, 5f, 0.1f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) + .SetValueFormat(OptionFormat.Multiplier); + + OverrideTasksData.Create(Id + 8, TabGroup.CrewmateRoles, CustomRoles.Ignitor); + } + + public override void Init() + { + PlayerIdList = []; + Active = true; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + Active = true; + UpdateTime = 1.0f; + ElapsedTime = OptionTaskEndVisionTime.GetInt() + OptionCountStartTime.GetInt(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + float Vision; + + if (!Active) + Vision = OptionTasksFinishedVision.GetFloat(); + else if (ElapsedTime > OptionTaskEndVisionTime.GetInt()) + Vision = OptionTaskStartVision.GetFloat(); + else + Vision = OptionTaskStartVision.GetFloat() * (ElapsedTime / OptionTaskEndVisionTime.GetInt()); + + if (Vision <= OptionTaskEndVision.GetFloat()) Vision = OptionTaskEndVision.GetFloat(); + + opt.SetFloat(FloatOptionNames.CrewLightMod, Vision); + if (Utils.IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, Vision * 5); + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (completedTaskCount + 1 >= totalTaskCount) + Active = false; + else + ElapsedTime = OptionTaskEndVisionTime.GetInt(); + + pc.MarkDirtySettings(); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask && !OptionTaskTimeMoveMeeting.GetBool()) return; + + if (!Active) return; + + UpdateTime -= Time.fixedDeltaTime; + if (UpdateTime < 0) UpdateTime = 1.0f; + + if (ElapsedTime > 0f) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ignitor); + ElapsedTime -= Time.fixedDeltaTime; - OptionTaskStartVision = new FloatOptionItem(Id + 2, "CandleLighterStartVision", new(0.5f, 5f, 0.1f), 0.8f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) - .SetValueFormat(OptionFormat.Multiplier); - - OptionCountStartTime = new IntegerOptionItem(Id + 3, "CandleLighterCountStartTime", new(0, 50, 5), 0, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) - .SetValueFormat(OptionFormat.Seconds); - - OptionTaskEndVisionTime = new IntegerOptionItem(Id + 4, "CandleLighterEndVisionTime", new(20, 200, 10), 50, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) - .SetValueFormat(OptionFormat.Seconds); - - OptionTaskEndVision = new FloatOptionItem(Id + 5, "CandleLighterEndVision", new(0f, 0.5f, 0.05f), 0.1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) - .SetValueFormat(OptionFormat.Multiplier); - - OptionTaskTimeMoveMeeting = new BooleanOptionItem(Id + 6, "CandleLighterTimeMoveMeeting", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]); - - OptionTasksFinishedVision = new FloatOptionItem(Id + 7, "CandleLighterTasksFinishedVision", new(0.5f, 5f, 0.1f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ignitor]) - .SetValueFormat(OptionFormat.Multiplier); - - OverrideTasksData.Create(Id + 8, TabGroup.CrewmateRoles, CustomRoles.Ignitor); - } - - public override void Init() - { - PlayerIdList = []; - Active = true; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - Active = true; - UpdateTime = 1.0f; - ElapsedTime = OptionTaskEndVisionTime.GetInt() + OptionCountStartTime.GetInt(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - float Vision; - - if (!Active) - Vision = OptionTasksFinishedVision.GetFloat(); - else if (ElapsedTime > OptionTaskEndVisionTime.GetInt()) - Vision = OptionTaskStartVision.GetFloat(); - else - Vision = OptionTaskStartVision.GetFloat() * (ElapsedTime / OptionTaskEndVisionTime.GetInt()); - - if (Vision <= OptionTaskEndVision.GetFloat()) Vision = OptionTaskEndVision.GetFloat(); - - opt.SetFloat(FloatOptionNames.CrewLightMod, Vision); - if (Utils.IsActive(SystemTypes.Electrical)) opt.SetFloat(FloatOptionNames.CrewLightMod, Vision * 5); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (completedTaskCount + 1 >= totalTaskCount) - Active = false; - else - ElapsedTime = OptionTaskEndVisionTime.GetInt(); - - pc.MarkDirtySettings(); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask && !OptionTaskTimeMoveMeeting.GetBool()) return; - - if (!Active) return; - - UpdateTime -= Time.fixedDeltaTime; - if (UpdateTime < 0) UpdateTime = 1.0f; - - if (ElapsedTime > 0f) - { - ElapsedTime -= Time.fixedDeltaTime; - - if (Math.Abs(UpdateTime - 1.0f) < 0.01f) player.MarkDirtySettings(); - } + if (Math.Abs(UpdateTime - 1.0f) < 0.01f) player.MarkDirtySettings(); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Car.cs b/Roles/Crewmate/Car.cs index a45f11970..b564d64c0 100644 --- a/Roles/Crewmate/Car.cs +++ b/Roles/Crewmate/Car.cs @@ -2,125 +2,124 @@ using System.Collections.Generic; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Car : RoleBase { - public class Car : RoleBase + public static bool On; + + private static OptionItem PropelDistance; + + private int Count; + private HashSet CurrentlyPropelling; + private Vector2 LastPosition; + + public static string Name => ".
\u2588<#628d85>\u2588<#628d85>\u2588<#628d85>\u2588<#628d85>\u2588<#628d85>\u2588\u2588\u2588
<#586874>\u2588<#586874>\u2588<#547a96>\u2588<#547a96>\u2588<#6894b6>\u2588<#6894b6>\u2588<#586874>\u2588\u2588
<#586874>\u2588<#586874>\u2588<#586874>\u2588<#547a96>\u2588<#547a96>\u2588<#586874>\u2588<#586874>\u2588<#f5ee2e>\u2588
<#000000>\u2588<#0d233f>\u2588<#586874>\u2588<#586874>\u2588<#586874>\u2588<#f5ee2e>\u2588<#586874>\u2588<#517a9a>\u2588
<#000000>\u2588<#000000>\u2588\u2588<#000000>\u2588<#0d233f>\u2588<#586874>\u2588<#517a9a>\u2588\u2588
\u2588\u2588\u2588<#000000>\u2588<#000000>\u2588\u2588\u2588\u2588
"; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(5647, TabGroup.CrewmateRoles, CustomRoles.Car); - private static OptionItem PropelDistance; + PropelDistance = new FloatOptionItem(5649, "Car.PropelDistance", new(1f, 20f, 0.5f), 5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Car]); + } - private int Count; - private HashSet CurrentlyPropelling; - private Vector2 LastPosition; + public override void Init() + { + On = false; + } - public static string Name => ".
\u2588<#628d85>\u2588<#628d85>\u2588<#628d85>\u2588<#628d85>\u2588<#628d85>\u2588\u2588\u2588
<#586874>\u2588<#586874>\u2588<#547a96>\u2588<#547a96>\u2588<#6894b6>\u2588<#6894b6>\u2588<#586874>\u2588\u2588
<#586874>\u2588<#586874>\u2588<#586874>\u2588<#547a96>\u2588<#547a96>\u2588<#586874>\u2588<#586874>\u2588<#f5ee2e>\u2588
<#000000>\u2588<#0d233f>\u2588<#586874>\u2588<#586874>\u2588<#586874>\u2588<#f5ee2e>\u2588<#586874>\u2588<#517a9a>\u2588
<#000000>\u2588<#000000>\u2588\u2588<#000000>\u2588<#0d233f>\u2588<#586874>\u2588<#517a9a>\u2588\u2588
\u2588\u2588\u2588<#000000>\u2588<#000000>\u2588\u2588\u2588\u2588
"; + public override void Add(byte playerId) + { + On = true; + LastPosition = Utils.GetPlayerById(playerId).Pos(); + Count = 0; + CurrentlyPropelling = []; + } - public override bool IsEnable => On; + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - public override void SetupCustomOption() - { - Options.SetupRoleOptions(5647, TabGroup.CrewmateRoles, CustomRoles.Car); + if (Count++ < 5) return; - PropelDistance = new FloatOptionItem(5649, "Car.PropelDistance", new(1f, 20f, 0.5f), 5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Car]); - } + Count = 0; - public override void Init() - { - On = false; - } + Vector2 pos = pc.Pos(); + if (Vector2.Distance(pos, LastPosition) < 0.1f) return; - public override void Add(byte playerId) - { - On = true; - LastPosition = Utils.GetPlayerById(playerId).Pos(); - Count = 0; - CurrentlyPropelling = []; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - - if (Count++ < 5) return; - - Count = 0; - - Vector2 pos = pc.Pos(); - if (Vector2.Distance(pos, LastPosition) < 0.1f) return; - - Direction direction = pos.x < LastPosition.x + Direction direction = pos.x < LastPosition.x + ? pos.y < LastPosition.y + ? Direction.DownLeft + : pos.y > LastPosition.y + ? Direction.UpLeft + : Direction.Left + : pos.x > LastPosition.x ? pos.y < LastPosition.y - ? Direction.DownLeft + ? Direction.DownRight : pos.y > LastPosition.y - ? Direction.UpLeft - : Direction.Left - : pos.x > LastPosition.x - ? pos.y < LastPosition.y - ? Direction.DownRight - : pos.y > LastPosition.y - ? Direction.UpRight - : Direction.Right - : pos.y < LastPosition.y - ? Direction.Down - : pos.y > LastPosition.y - ? Direction.Up - : Direction.Left; + ? Direction.UpRight + : Direction.Right + : pos.y < LastPosition.y + ? Direction.Down + : pos.y > LastPosition.y + ? Direction.Up + : Direction.Left; - LastPosition = pos; + LastPosition = pos; - if (Main.AllAlivePlayerControls.Without(pc).FindFirst(x => Vector2.Distance(pos, x.Pos()) < 1.2f, out PlayerControl target) && CurrentlyPropelling.Add(target.PlayerId)) Main.Instance.StartCoroutine(Propel(pc, target, direction)); - } + if (Main.AllAlivePlayerControls.Without(pc).FindFirst(x => Vector2.Distance(pos, x.Pos()) < 1.2f, out PlayerControl target) && CurrentlyPropelling.Add(target.PlayerId)) Main.Instance.StartCoroutine(Propel(pc, target, direction)); + } - private IEnumerator Propel(PlayerControl car, PlayerControl target, Direction direction) + private IEnumerator Propel(PlayerControl car, PlayerControl target, Direction direction) + { + float oldSpeed = Main.AllPlayerSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); + + Vector2 pos = car.Pos(); + + Vector2 addVector = direction switch { - float oldSpeed = Main.AllPlayerSpeed[target.PlayerId]; - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; - target.MarkDirtySettings(); + Direction.Left => new(-0.25f, 0), + Direction.UpLeft => new(-0.25f, 0.25f), + Direction.Up => new(0, 0.25f), + Direction.UpRight => new(0.25f, 0.25f), + Direction.Right => new(0.25f, 0), + Direction.DownRight => new(0.25f, -0.25f), + Direction.Down => new(0, -0.25f), + Direction.DownLeft => new(-0.25f, -0.25f), + _ => Vector2.zero + }; - Vector2 pos = car.Pos(); + float distance = PropelDistance.GetFloat(); + Collider2D collider = target.Collider; - Vector2 addVector = direction switch - { - Direction.Left => new(-0.25f, 0), - Direction.UpLeft => new(-0.25f, 0.25f), - Direction.Up => new(0, 0.25f), - Direction.UpRight => new(0.25f, 0.25f), - Direction.Right => new(0.25f, 0), - Direction.DownRight => new(0.25f, -0.25f), - Direction.Down => new(0, -0.25f), - Direction.DownLeft => new(-0.25f, -0.25f), - _ => Vector2.zero - }; - - float distance = PropelDistance.GetFloat(); - Collider2D collider = target.Collider; - - for (Vector2 newPos = target.Pos(); Vector2.Distance(pos, newPos) < distance && GameStates.IsInTask; newPos += addVector) - { - if (PhysicsHelpers.AnythingBetween(collider, collider.bounds.center, newPos, Constants.ShipOnlyMask, false)) break; - - target.TP(newPos, log: false); - yield return new WaitForSeconds(0.05f); - } - - Main.AllPlayerSpeed[target.PlayerId] = oldSpeed; - target.MarkDirtySettings(); - - CurrentlyPropelling.Remove(target.PlayerId); - } - - private enum Direction + for (Vector2 newPos = target.Pos(); Vector2.Distance(pos, newPos) < distance && GameStates.IsInTask; newPos += addVector) { - Left, - UpLeft, - Up, - UpRight, - Right, - DownRight, - Down, - DownLeft + if (PhysicsHelpers.AnythingBetween(collider, collider.bounds.center, newPos, Constants.ShipOnlyMask, false)) break; + + target.TP(newPos, log: false); + yield return new WaitForSeconds(0.05f); } + + Main.AllPlayerSpeed[target.PlayerId] = oldSpeed; + target.MarkDirtySettings(); + + CurrentlyPropelling.Remove(target.PlayerId); + } + + private enum Direction + { + Left, + UpLeft, + Up, + UpRight, + Right, + DownRight, + Down, + DownLeft } } \ No newline at end of file diff --git a/Roles/Crewmate/Catcher.cs b/Roles/Crewmate/Catcher.cs index 262e3d421..1c8f6ef88 100644 --- a/Roles/Crewmate/Catcher.cs +++ b/Roles/Crewmate/Catcher.cs @@ -4,186 +4,185 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Catcher : RoleBase { - public class Catcher : RoleBase + public static bool On; + + public static OptionItem AbilityCooldown; + public static OptionItem TrapPlaceDelay; + public static OptionItem CatchRange; + public static OptionItem MinPlayersTrappedToShowInfo; + public static OptionItem AbilityUseLimit; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private byte CatcherId; + private Dictionary CaughtRoles; + private long DelayStartTS; + private long LastUpdate; + private Dictionary Traps; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 648650; + const TabGroup tab = TabGroup.CrewmateRoles; + const CustomRoles role = CustomRoles.Catcher; - public static OptionItem AbilityCooldown; - public static OptionItem TrapPlaceDelay; - public static OptionItem CatchRange; - public static OptionItem MinPlayersTrappedToShowInfo; - public static OptionItem AbilityUseLimit; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - private byte CatcherId; - private Dictionary CaughtRoles; - private long DelayStartTS; - private long LastUpdate; - private Dictionary Traps; + Options.SetupRoleOptions(id++, tab, role); - public override bool IsEnable => On; + StringOptionItem parent = Options.CustomRoleSpawnChances[role]; - public override void SetupCustomOption() - { - var id = 648650; - const TabGroup tab = TabGroup.CrewmateRoles; - const CustomRoles role = CustomRoles.Catcher; + AbilityCooldown = new FloatOptionItem(++id, "AbilityCooldown", new(0f, 180f, 0.5f), 10f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Seconds); - Options.SetupRoleOptions(id++, tab, role); + TrapPlaceDelay = new FloatOptionItem(++id, "Catcher.TrapPlaceDelay", new(0f, 180f, 1f), 5f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Seconds); - StringOptionItem parent = Options.CustomRoleSpawnChances[role]; + CatchRange = new FloatOptionItem(++id, "Catcher.CatchRange", new(0f, 10f, 0.25f), 1.5f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Multiplier); - AbilityCooldown = new FloatOptionItem(++id, "AbilityCooldown", new(0f, 180f, 0.5f), 10f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Seconds); + MinPlayersTrappedToShowInfo = new IntegerOptionItem(++id, "Catcher.MinPlayersTrappedToShowInfo", new(1, 10, 1), 2, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Players); - TrapPlaceDelay = new FloatOptionItem(++id, "Catcher.TrapPlaceDelay", new(0f, 180f, 1f), 5f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Seconds); + AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 3, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Times); - CatchRange = new FloatOptionItem(++id, "Catcher.CatchRange", new(0f, 10f, 0.25f), 1.5f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Multiplier); + AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Times); - MinPlayersTrappedToShowInfo = new IntegerOptionItem(++id, "Catcher.MinPlayersTrappedToShowInfo", new(1, 10, 1), 2, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Players); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Times); + } - AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 3, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Times); + public override void Init() + { + On = false; + } - AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Times); + public override void Add(byte playerId) + { + On = true; + CatcherId = playerId; + Traps = []; + DelayStartTS = 0; + LastUpdate = Utils.TimeStamp; + CaughtRoles = []; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Times); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; - public override void Init() - { - On = false; - } + AURoleOptions.EngineerCooldown = AbilityCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } - public override void Add(byte playerId) - { - On = true; - CatcherId = playerId; - Traps = []; - DelayStartTS = 0; - LastUpdate = Utils.TimeStamp; - CaughtRoles = []; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } + private void PlaceTrap(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1) return; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePets.GetBool()) return; + pc.RpcRemoveAbilityUse(); - AURoleOptions.EngineerCooldown = AbilityCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } + Vector2 pos = pc.Pos(); + Traps[pos] = new(pos, pc); - private void PlaceTrap(PlayerControl pc) - { - if (pc.GetAbilityUseLimit() < 1) return; + pc.Notify(Translator.GetString("Catcher.TrapPlaced")); + } - pc.RpcRemoveAbilityUse(); + public override void OnPet(PlayerControl pc) + { + PlaceTrap(pc); + } - Vector2 pos = pc.Pos(); - Traps[pos] = new(pos, pc); + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + DelayStartTS = Utils.TimeStamp + 1; + SendRPC(); + } - pc.Notify(Translator.GetString("Catcher.TrapPlaced")); - } + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance || Options.UsePets.GetBool() || DelayStartTS == 0) return; - public override void OnPet(PlayerControl pc) + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + + LastUpdate = now; + + if (DelayStartTS + TrapPlaceDelay.GetInt() <= now) { PlaceTrap(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - DelayStartTS = Utils.TimeStamp + 1; + DelayStartTS = 0; SendRPC(); } - public override void OnFixedUpdate(PlayerControl pc) + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + pc.RpcResetAbilityCooldown(); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (CaughtRoles.ContainsKey(pc.PlayerId) || pc.PlayerId == CatcherId) return; + + Vector2 pos = pc.Pos(); + float range = CatchRange.GetFloat(); + if (Traps.Keys.Any(x => Vector2.Distance(x, pos) <= range)) CaughtRoles[pc.PlayerId] = pc.GetCustomRole(); + } + + public override void OnReportDeadBody() + { + if (Traps.Count == 0) return; + + Traps.Values.Do(x => x.Despawn()); + Traps = []; + + PlayerControl catcher = CatcherId.GetPlayer(); + if (catcher == null || !catcher.IsAlive()) return; + + LateTask.New(() => { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance || Options.UsePets.GetBool() || DelayStartTS == 0) return; - - long now = Utils.TimeStamp; - if (now == LastUpdate) return; - - LastUpdate = now; - - if (DelayStartTS + TrapPlaceDelay.GetInt() <= now) + if (CaughtRoles.Count >= MinPlayersTrappedToShowInfo.GetInt()) { - PlaceTrap(pc); - DelayStartTS = 0; - SendRPC(); + string roles = string.Join(", ", CaughtRoles.Values.Select(x => x.ToColoredString())); + Utils.SendMessage("\n", CatcherId, Translator.GetString("Catcher.CaughtRoles") + roles); } + else + Utils.SendMessage("\n", CatcherId, Translator.GetString("Catcher.NotEnoughCaughtRoles")); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - pc.RpcResetAbilityCooldown(); - } + CaughtRoles = []; + }, 10f, "Send Catcher Caught Roles"); + } - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (CaughtRoles.ContainsKey(pc.PlayerId) || pc.PlayerId == CatcherId) return; + private void SendRPC() + { + Utils.SendRPC(CustomRPC.SyncRoleData, CatcherId, DelayStartTS); + } - Vector2 pos = pc.Pos(); - float range = CatchRange.GetFloat(); - if (Traps.Keys.Any(x => Vector2.Distance(x, pos) <= range)) CaughtRoles[pc.PlayerId] = pc.GetCustomRole(); - } + public void ReceiveRPC(MessageReader reader) + { + DelayStartTS = long.Parse(reader.ReadString()); + } - public override void OnReportDeadBody() - { - if (Traps.Count == 0) return; + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != CatcherId || meeting || (seer.IsModClient() && !hud) || DelayStartTS == 0 || Options.UsePets.GetBool()) return string.Empty; - Traps.Values.Do(x => x.Despawn()); - Traps = []; + return string.Format(Translator.GetString("Catcher.Suffix"), TrapPlaceDelay.GetInt() - (Utils.TimeStamp - DelayStartTS)); + } - PlayerControl catcher = CatcherId.GetPlayer(); - if (catcher == null || !catcher.IsAlive()) return; - - LateTask.New(() => - { - if (CaughtRoles.Count >= MinPlayersTrappedToShowInfo.GetInt()) - { - string roles = string.Join(", ", CaughtRoles.Values.Select(x => x.ToColoredString())); - Utils.SendMessage("\n", CatcherId, Translator.GetString("Catcher.CaughtRoles") + roles); - } - else - Utils.SendMessage("\n", CatcherId, Translator.GetString("Catcher.NotEnoughCaughtRoles")); - - CaughtRoles = []; - }, 10f, "Send Catcher Caught Roles"); - } - - private void SendRPC() - { - Utils.SendRPC(CustomRPC.SyncRoleData, CatcherId, DelayStartTS); - } - - public void ReceiveRPC(MessageReader reader) - { - DelayStartTS = long.Parse(reader.ReadString()); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != CatcherId || meeting || (seer.IsModClient() && !hud) || DelayStartTS == 0 || Options.UsePets.GetBool()) return string.Empty; - - return string.Format(Translator.GetString("Catcher.Suffix"), TrapPlaceDelay.GetInt() - (Utils.TimeStamp - DelayStartTS)); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Chameleon.cs b/Roles/Crewmate/Chameleon.cs index 1b11965fc..ac855761a 100644 --- a/Roles/Crewmate/Chameleon.cs +++ b/Roles/Crewmate/Chameleon.cs @@ -1,46 +1,45 @@ using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Chameleon : RoleBase { - public class Chameleon : RoleBase + private const int Id = 6300; + + public static OptionItem ChameleonCooldown; + public static OptionItem ChameleonDuration; + public static OptionItem UseLimitOpt; + public static OptionItem ChameleonAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 6300; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Chameleon); - public static OptionItem ChameleonCooldown; - public static OptionItem ChameleonDuration; - public static OptionItem UseLimitOpt; - public static OptionItem ChameleonAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + ChameleonCooldown = new FloatOptionItem(Id + 2, "ChameleonCooldown", new(1f, 60f, 1f), 20f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + ChameleonDuration = new FloatOptionItem(Id + 3, "ChameleonDuration", new(1f, 30f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Chameleon); + UseLimitOpt = new IntegerOptionItem(Id + 4, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) + .SetValueFormat(OptionFormat.Times); - ChameleonCooldown = new FloatOptionItem(Id + 2, "ChameleonCooldown", new(1f, 60f, 1f), 20f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) - .SetValueFormat(OptionFormat.Seconds); + ChameleonAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 5, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) + .SetValueFormat(OptionFormat.Times); - ChameleonDuration = new FloatOptionItem(Id + 3, "ChameleonDuration", new(1f, 30f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimitOpt = new IntegerOptionItem(Id + 4, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) - .SetValueFormat(OptionFormat.Times); - - ChameleonAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 5, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 6, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 6, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chameleon]) + .SetValueFormat(OptionFormat.Times); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Chef.cs b/Roles/Crewmate/Chef.cs index eeba1c523..7c96fda32 100644 --- a/Roles/Crewmate/Chef.cs +++ b/Roles/Crewmate/Chef.cs @@ -4,384 +4,383 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Chef : RoleBase { - public class Chef : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem EventTarget; + private static OptionItem RottenTime; + private static OptionItem SpitOutTime; + private static OptionItem IncreasedSpeed; + private static OptionItem IncreasedSpeedDuration; + private static OptionItem IncreasedVision; + private static OptionItem IncreasedVisionDuration; + private static OptionItem ShieldDuration; + + private static readonly string[] EventTargets = + [ + "Chef.ET.RandomPlayer", + "Chef.ET.NearestPlayer" + ]; + + private Dictionary> ActiveEvents = []; + private byte ChefId; + private Dictionary RottenFood = []; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + var id = 648750; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Chef); - private static OptionItem EventTarget; - private static OptionItem RottenTime; - private static OptionItem SpitOutTime; - private static OptionItem IncreasedSpeed; - private static OptionItem IncreasedSpeedDuration; - private static OptionItem IncreasedVision; - private static OptionItem IncreasedVisionDuration; - private static OptionItem ShieldDuration; + EventTarget = new StringOptionItem(++id, "Chef.EventTarget", EventTargets, 0, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]); - private static readonly string[] EventTargets = - [ - "Chef.ET.RandomPlayer", - "Chef.ET.NearestPlayer" - ]; + RottenTime = new IntegerOptionItem(++id, "Chef.RottenTime", new(1, 60, 1), 15, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Seconds); - private Dictionary> ActiveEvents = []; - private byte ChefId; - private Dictionary RottenFood = []; + SpitOutTime = new IntegerOptionItem(++id, "Chef.SpitOutTime", new(1, 60, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + IncreasedSpeed = new FloatOptionItem(++id, "IncreasedSpeed", new(0f, 3f, 0.05f), 1.75f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() + IncreasedSpeedDuration = new IntegerOptionItem(++id, "IncreasedSpeedDuration", new(1, 60, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Seconds); + + IncreasedVision = new FloatOptionItem(++id, "IncreasedVision", new(0f, 3f, 0.05f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Multiplier); + + IncreasedVisionDuration = new IntegerOptionItem(++id, "IncreasedVisionDuration", new(1, 60, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Seconds); + + ShieldDuration = new IntegerOptionItem(++id, "ShieldDuration", new(1, 60, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + ChefId = playerId; + Instances.Add(this); + ActiveEvents = []; + RottenFood = []; + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (!pc.IsAlive()) return; + + Vector2 pos = pc.Pos(); + PlayerControl[] aapc = Main.AllAlivePlayerControls.Without(pc).ToArray(); + PlayerControl target = EventTarget.GetValue() == 0 ? aapc.RandomElement() : aapc.MinBy(x => Vector2.Distance(x.Pos(), pos)); + + if (target.Is(Team.Impostor) || target.IsNeutralKiller()) { - var id = 648750; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Chef); - - EventTarget = new StringOptionItem(++id, "Chef.EventTarget", EventTargets, 0, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]); - - RottenTime = new IntegerOptionItem(++id, "Chef.RottenTime", new(1, 60, 1), 15, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Seconds); - - SpitOutTime = new IntegerOptionItem(++id, "Chef.SpitOutTime", new(1, 60, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Seconds); - - IncreasedSpeed = new FloatOptionItem(++id, "IncreasedSpeed", new(0f, 3f, 0.05f), 1.75f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Multiplier); - - IncreasedSpeedDuration = new IntegerOptionItem(++id, "IncreasedSpeedDuration", new(1, 60, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Seconds); - - IncreasedVision = new FloatOptionItem(++id, "IncreasedVision", new(0f, 3f, 0.05f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Multiplier); - - IncreasedVisionDuration = new IntegerOptionItem(++id, "IncreasedVisionDuration", new(1, 60, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Seconds); - - ShieldDuration = new IntegerOptionItem(++id, "ShieldDuration", new(1, 60, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Chef]) - .SetValueFormat(OptionFormat.Seconds); + RottenFood[target.PlayerId] = Utils.TimeStamp; + NotifyAboutRandomFood(target, "ChefRotten"); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 4, target.PlayerId); } - - public override void Init() + else { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - ChefId = playerId; - Instances.Add(this); - ActiveEvents = []; - RottenFood = []; - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (!pc.IsAlive()) return; - - Vector2 pos = pc.Pos(); - PlayerControl[] aapc = Main.AllAlivePlayerControls.Without(pc).ToArray(); - PlayerControl target = EventTarget.GetValue() == 0 ? aapc.RandomElement() : aapc.MinBy(x => Vector2.Distance(x.Pos(), pos)); - - if (target.Is(Team.Impostor) || target.IsNeutralKiller()) + switch (IRandom.Instance.Next(4)) { - RottenFood[target.PlayerId] = Utils.TimeStamp; - NotifyAboutRandomFood(target, "ChefRotten"); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 4, target.PlayerId); - } - else - { - switch (IRandom.Instance.Next(4)) - { - case 0: - switch (IRandom.Instance.Next(2)) - { - case 0: - target.TP(aapc.Without(target).RandomElement()); - NotifyAboutRandomFood(target, "ChefBoost.TP"); - break; - case 1: - target.TPToRandomVent(); - NotifyAboutRandomFood(target, "ChefBoost.TPToVent"); - break; - } - - break; - case 1: - new IncreasedSpeedEvent(target, this).Update(); - NotifyAboutRandomFood(target, "ChefBoost.Speed"); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, target.PlayerId); - break; - case 2: - new IncreasedVisionEvent(target, this).Update(); - NotifyAboutRandomFood(target, "ChefBoost.Vision"); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, target.PlayerId); - break; - case 3: - new ShieldEvent(target, this).Update(); - NotifyAboutRandomFood(target, "ChefBoost.Shield"); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, target.PlayerId); - break; - } - } - - if (target.IsLocalPlayer()) - Achievements.Type.Delicious.Complete(); - } - - private static void NotifyAboutRandomFood(PlayerControl pc, string cause) - { - string food = Translator.GetString($"ChefFood.{IRandom.Instance.Next(35)}"); - string str = string.Format(Translator.GetString("ChefNotify"), food, Translator.GetString(cause)); - pc.Notify(str, 10f); - } - - public static void ApplyGameOptionsForOthers(IGameOptions opt, byte id) - { - foreach (Chef chef in Instances) - { - if (chef.ActiveEvents.TryGetValue(id, out List events)) - { - foreach (PlayerEvent playerEvent in events) + case 0: + switch (IRandom.Instance.Next(2)) { - if (playerEvent is IncreasedVisionEvent) - { - opt.SetFloat(FloatOptionNames.CrewLightMod, IncreasedVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, IncreasedVision.GetFloat()); - return; - } + case 0: + target.TP(aapc.Without(target).RandomElement()); + NotifyAboutRandomFood(target, "ChefBoost.TP"); + break; + case 1: + target.TPToRandomVent(); + NotifyAboutRandomFood(target, "ChefBoost.TPToVent"); + break; + } + + break; + case 1: + new IncreasedSpeedEvent(target, this).Update(); + NotifyAboutRandomFood(target, "ChefBoost.Speed"); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, target.PlayerId); + break; + case 2: + new IncreasedVisionEvent(target, this).Update(); + NotifyAboutRandomFood(target, "ChefBoost.Vision"); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, target.PlayerId); + break; + case 3: + new ShieldEvent(target, this).Update(); + NotifyAboutRandomFood(target, "ChefBoost.Shield"); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, target.PlayerId); + break; + } + } + + if (target.IsLocalPlayer()) + Achievements.Type.Delicious.Complete(); + } + + private static void NotifyAboutRandomFood(PlayerControl pc, string cause) + { + string food = Translator.GetString($"ChefFood.{IRandom.Instance.Next(35)}"); + string str = string.Format(Translator.GetString("ChefNotify"), food, Translator.GetString(cause)); + pc.Notify(str, 10f); + } + + public static void ApplyGameOptionsForOthers(IGameOptions opt, byte id) + { + foreach (Chef chef in Instances) + { + if (chef.ActiveEvents.TryGetValue(id, out List events)) + { + foreach (PlayerEvent playerEvent in events) + { + if (playerEvent is IncreasedVisionEvent) + { + opt.SetFloat(FloatOptionNames.CrewLightMod, IncreasedVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, IncreasedVision.GetFloat()); + return; } } } } + } - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + if (ActiveEvents.TryGetValue(pc.PlayerId, out List events)) events.ToArray().Do(x => x.Update()); + + if (RottenFood.TryGetValue(pc.PlayerId, out long ts)) { - if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - - if (ActiveEvents.TryGetValue(pc.PlayerId, out List events)) events.ToArray().Do(x => x.Update()); - - if (RottenFood.TryGetValue(pc.PlayerId, out long ts)) + if (Utils.TimeStamp - ts > RottenTime.GetInt()) { - if (Utils.TimeStamp - ts > RottenTime.GetInt()) + RottenFood.Remove(pc.PlayerId); + Utils.SendRPC(CustomRPC.SyncRoleData, ChefId, 8, pc.PlayerId); + + float oldSpeed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.PlayerStates[pc.PlayerId].IsBlackOut = true; + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + ReportDeadBodyPatch.CanReport[pc.PlayerId] = false; + pc.MarkDirtySettings(); + + LateTask.New(() => { - RottenFood.Remove(pc.PlayerId); - Utils.SendRPC(CustomRPC.SyncRoleData, ChefId, 8, pc.PlayerId); - - float oldSpeed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.PlayerStates[pc.PlayerId].IsBlackOut = true; - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; - ReportDeadBodyPatch.CanReport[pc.PlayerId] = false; + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = oldSpeed; + ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.PlayerStates[pc.PlayerId].IsBlackOut = false; - Main.AllPlayerSpeed[pc.PlayerId] = oldSpeed; - ReportDeadBodyPatch.CanReport[pc.PlayerId] = true; - pc.MarkDirtySettings(); - }, SpitOutTime.GetInt(), "ChefSpitOut"); - } - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + }, SpitOutTime.GetInt(), "ChefSpitOut"); } - } - public override void OnReportDeadBody() - { - ActiveEvents.Clear(); - RottenFood.Keys.Do(x => Utils.GetPlayerById(x).Suicide()); - RottenFood.Clear(); - } - - public static void SpitOutFood(PlayerControl pc) - { - Instances.Do(x => x.RottenFood.Remove(pc.PlayerId)); - NameNotifyManager.Notifies.Remove(pc.PlayerId); Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } + } - public void ReceiveRPC(MessageReader reader) + public override void OnReportDeadBody() + { + ActiveEvents.Clear(); + RottenFood.Keys.Do(x => Utils.GetPlayerById(x).Suicide()); + RottenFood.Clear(); + } + + public static void SpitOutFood(PlayerControl pc) + { + Instances.Do(x => x.RottenFood.Remove(pc.PlayerId)); + NameNotifyManager.Notifies.Remove(pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - switch (reader.ReadPackedInt32()) + case 1: + new IncreasedSpeedEvent(Utils.GetPlayerById(reader.ReadByte()), this).Update(); + break; + case 2: + new IncreasedVisionEvent(Utils.GetPlayerById(reader.ReadByte()), this).Update(); + break; + case 3: + new ShieldEvent(Utils.GetPlayerById(reader.ReadByte()), this).Update(); + break; + case 4: + RottenFood[reader.ReadByte()] = Utils.TimeStamp; + break; + case 5: + ActiveEvents[reader.ReadByte()].RemoveAt(reader.ReadPackedInt32()); + break; + case 6: + ActiveEvents[reader.ReadByte()].RemoveAt(reader.ReadPackedInt32()); + break; + case 7: + ActiveEvents[reader.ReadByte()].RemoveAt(reader.ReadPackedInt32()); + break; + case 8: + RottenFood.Remove(reader.ReadByte()); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (meeting) return string.Empty; + + long now = Utils.TimeStamp; + if (ActiveEvents.TryGetValue(target.PlayerId, out List events) && (seer.PlayerId == target.PlayerId || seer.PlayerId == ChefId)) return $"{string.Join('\n', events.Select(e => string.Format(Translator.GetString("ChefBoostSuffix"), GetEventString(e), e.Duration - (now - e.StartTimeStamp))))}"; + + if (RottenFood.TryGetValue(seer.PlayerId, out long ts) && seer.PlayerId == target.PlayerId) return string.Format(Translator.GetString("ChefRottenSuffix"), RottenTime.GetInt() - (now - ts)); + + return string.Empty; + + string GetEventString(PlayerEvent e) + { + return e switch { - case 1: - new IncreasedSpeedEvent(Utils.GetPlayerById(reader.ReadByte()), this).Update(); - break; - case 2: - new IncreasedVisionEvent(Utils.GetPlayerById(reader.ReadByte()), this).Update(); - break; - case 3: - new ShieldEvent(Utils.GetPlayerById(reader.ReadByte()), this).Update(); - break; - case 4: - RottenFood[reader.ReadByte()] = Utils.TimeStamp; - break; - case 5: - ActiveEvents[reader.ReadByte()].RemoveAt(reader.ReadPackedInt32()); - break; - case 6: - ActiveEvents[reader.ReadByte()].RemoveAt(reader.ReadPackedInt32()); - break; - case 7: - ActiveEvents[reader.ReadByte()].RemoveAt(reader.ReadPackedInt32()); - break; - case 8: - RottenFood.Remove(reader.ReadByte()); - break; - } + IncreasedSpeedEvent => Translator.GetString("ChefBoost.Speed"), + IncreasedVisionEvent => Translator.GetString("ChefBoost.Vision"), + ShieldEvent => Translator.GetString("ChefBoost.Shield"), + _ => string.Empty + }; + } + } + + private abstract class PlayerEvent + { + protected abstract PlayerControl Player { get; } + protected abstract Chef Instance { get; } + internal abstract long StartTimeStamp { get; } + internal abstract int Duration { get; } + internal abstract void Update(); + + protected void AddEventToDictionary() + { + if (!Instance.ActiveEvents.ContainsKey(Player.PlayerId)) + Instance.ActiveEvents[Player.PlayerId] = [this]; + else + Instance.ActiveEvents[Player.PlayerId].Add(this); + } + } + + private sealed class IncreasedSpeedEvent : PlayerEvent + { + public IncreasedSpeedEvent(PlayerControl player, Chef instance) + { + Player = player; + StartTimeStamp = Utils.TimeStamp; + Duration = IncreasedSpeedDuration.GetInt(); + Instance = instance; + + AddEventToDictionary(); + + OldSpeed = Main.AllPlayerSpeed[player.PlayerId]; + Main.AllPlayerSpeed[player.PlayerId] = IncreasedSpeed.GetFloat(); + player.MarkDirtySettings(); } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + protected override PlayerControl Player { get; } + internal override long StartTimeStamp { get; } + internal override int Duration { get; } + protected override Chef Instance { get; } + private float OldSpeed { get; } + + internal override void Update() { - if (meeting) return string.Empty; - - long now = Utils.TimeStamp; - if (ActiveEvents.TryGetValue(target.PlayerId, out List events) && (seer.PlayerId == target.PlayerId || seer.PlayerId == ChefId)) return $"{string.Join('\n', events.Select(e => string.Format(Translator.GetString("ChefBoostSuffix"), GetEventString(e), e.Duration - (now - e.StartTimeStamp))))}"; - - if (RottenFood.TryGetValue(seer.PlayerId, out long ts) && seer.PlayerId == target.PlayerId) return string.Format(Translator.GetString("ChefRottenSuffix"), RottenTime.GetInt() - (now - ts)); - - return string.Empty; - - string GetEventString(PlayerEvent e) + if (Utils.TimeStamp - StartTimeStamp > Duration) { - return e switch - { - IncreasedSpeedEvent => Translator.GetString("ChefBoost.Speed"), - IncreasedVisionEvent => Translator.GetString("ChefBoost.Vision"), - ShieldEvent => Translator.GetString("ChefBoost.Shield"), - _ => string.Empty - }; + Main.AllPlayerSpeed[Player.PlayerId] = OldSpeed; + Player.MarkDirtySettings(); + int index = Instance.ActiveEvents[Player.PlayerId].IndexOf(this); + Instance.ActiveEvents[Player.PlayerId].Remove(this); + Utils.SendRPC(CustomRPC.SyncRoleData, Instance.ChefId, 5, Player.PlayerId, index); } + + Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(Instance.ChefId), SpecifyTarget: Player); + } + } + + private sealed class IncreasedVisionEvent : PlayerEvent + { + public IncreasedVisionEvent(PlayerControl player, Chef instance) + { + Player = player; + StartTimeStamp = Utils.TimeStamp; + Duration = IncreasedVisionDuration.GetInt(); + Instance = instance; + + AddEventToDictionary(); + + player.MarkDirtySettings(); } - private abstract class PlayerEvent - { - protected abstract PlayerControl Player { get; } - protected abstract Chef Instance { get; } - internal abstract long StartTimeStamp { get; } - internal abstract int Duration { get; } - internal abstract void Update(); + protected override PlayerControl Player { get; } + internal override long StartTimeStamp { get; } + internal override int Duration { get; } + protected override Chef Instance { get; } - protected void AddEventToDictionary() + internal override void Update() + { + if (Utils.TimeStamp - StartTimeStamp > Duration) { - if (!Instance.ActiveEvents.ContainsKey(Player.PlayerId)) - Instance.ActiveEvents[Player.PlayerId] = [this]; - else - Instance.ActiveEvents[Player.PlayerId].Add(this); + Player.MarkDirtySettings(); + int index = Instance.ActiveEvents[Player.PlayerId].IndexOf(this); + Instance.ActiveEvents[Player.PlayerId].Remove(this); + Utils.SendRPC(CustomRPC.SyncRoleData, Instance.ChefId, 6, Player.PlayerId, index); } + + Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(Instance.ChefId), SpecifyTarget: Player); + } + } + + private sealed class ShieldEvent : PlayerEvent + { + public ShieldEvent(PlayerControl player, Chef instance) + { + Player = player; + StartTimeStamp = Utils.TimeStamp; + Duration = ShieldDuration.GetInt(); + Instance = instance; + + AddEventToDictionary(); } - private sealed class IncreasedSpeedEvent : PlayerEvent + protected override PlayerControl Player { get; } + internal override long StartTimeStamp { get; } + internal override int Duration { get; } + protected override Chef Instance { get; } + + internal override void Update() { - public IncreasedSpeedEvent(PlayerControl player, Chef instance) + if (Utils.TimeStamp - StartTimeStamp > Duration) { - Player = player; - StartTimeStamp = Utils.TimeStamp; - Duration = IncreasedSpeedDuration.GetInt(); - Instance = instance; - - AddEventToDictionary(); - - OldSpeed = Main.AllPlayerSpeed[player.PlayerId]; - Main.AllPlayerSpeed[player.PlayerId] = IncreasedSpeed.GetFloat(); - player.MarkDirtySettings(); + int index = Instance.ActiveEvents[Player.PlayerId].IndexOf(this); + Instance.ActiveEvents[Player.PlayerId].Remove(this); + Utils.SendRPC(CustomRPC.SyncRoleData, Instance.ChefId, 7, Player.PlayerId, index); } - protected override PlayerControl Player { get; } - internal override long StartTimeStamp { get; } - internal override int Duration { get; } - protected override Chef Instance { get; } - private float OldSpeed { get; } - - internal override void Update() - { - if (Utils.TimeStamp - StartTimeStamp > Duration) - { - Main.AllPlayerSpeed[Player.PlayerId] = OldSpeed; - Player.MarkDirtySettings(); - int index = Instance.ActiveEvents[Player.PlayerId].IndexOf(this); - Instance.ActiveEvents[Player.PlayerId].Remove(this); - Utils.SendRPC(CustomRPC.SyncRoleData, Instance.ChefId, 5, Player.PlayerId, index); - } - - Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(Instance.ChefId), SpecifyTarget: Player); - } - } - - private sealed class IncreasedVisionEvent : PlayerEvent - { - public IncreasedVisionEvent(PlayerControl player, Chef instance) - { - Player = player; - StartTimeStamp = Utils.TimeStamp; - Duration = IncreasedVisionDuration.GetInt(); - Instance = instance; - - AddEventToDictionary(); - - player.MarkDirtySettings(); - } - - protected override PlayerControl Player { get; } - internal override long StartTimeStamp { get; } - internal override int Duration { get; } - protected override Chef Instance { get; } - - internal override void Update() - { - if (Utils.TimeStamp - StartTimeStamp > Duration) - { - Player.MarkDirtySettings(); - int index = Instance.ActiveEvents[Player.PlayerId].IndexOf(this); - Instance.ActiveEvents[Player.PlayerId].Remove(this); - Utils.SendRPC(CustomRPC.SyncRoleData, Instance.ChefId, 6, Player.PlayerId, index); - } - - Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(Instance.ChefId), SpecifyTarget: Player); - } - } - - private sealed class ShieldEvent : PlayerEvent - { - public ShieldEvent(PlayerControl player, Chef instance) - { - Player = player; - StartTimeStamp = Utils.TimeStamp; - Duration = ShieldDuration.GetInt(); - Instance = instance; - - AddEventToDictionary(); - } - - protected override PlayerControl Player { get; } - internal override long StartTimeStamp { get; } - internal override int Duration { get; } - protected override Chef Instance { get; } - - internal override void Update() - { - if (Utils.TimeStamp - StartTimeStamp > Duration) - { - int index = Instance.ActiveEvents[Player.PlayerId].IndexOf(this); - Instance.ActiveEvents[Player.PlayerId].Remove(this); - Utils.SendRPC(CustomRPC.SyncRoleData, Instance.ChefId, 7, Player.PlayerId, index); - } - - Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(Instance.ChefId), SpecifyTarget: Player); - } + Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(Instance.ChefId), SpecifyTarget: Player); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Clairvoyant.cs b/Roles/Crewmate/Clairvoyant.cs index 55acb3636..cb40051bf 100644 --- a/Roles/Crewmate/Clairvoyant.cs +++ b/Roles/Crewmate/Clairvoyant.cs @@ -2,66 +2,65 @@ using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Clairvoyant : RoleBase { - public class Clairvoyant : RoleBase + public static bool On; + + public static readonly Dictionary Settings = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(644970, TabGroup.CrewmateRoles, CustomRoles.Clairvoyant); - public static readonly Dictionary Settings = []; - public override bool IsEnable => On; + var i = 2; - public override void SetupCustomOption() + foreach (Options.GameStateInfo s in Enum.GetValues()) { - Options.SetupRoleOptions(644970, TabGroup.CrewmateRoles, CustomRoles.Clairvoyant); + Settings[s] = new BooleanOptionItem(644970 + i, $"GameStateCommand.Show{s}", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Clairvoyant]); - var i = 2; - - foreach (Options.GameStateInfo s in Enum.GetValues()) - { - Settings[s] = new BooleanOptionItem(644970 + i, $"GameStateCommand.Show{s}", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Clairvoyant]); - - i++; - } - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = 1f; - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - private static void UseAbility(PlayerControl pc) - { - pc.Notify(Utils.GetGameStateData(true)); - } - - public override void OnPet(PlayerControl pc) - { - UseAbility(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - UseAbility(pc); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + i++; } } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + private static void UseAbility(PlayerControl pc) + { + pc.Notify(Utils.GetGameStateData(true)); + } + + public override void OnPet(PlayerControl pc) + { + UseAbility(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + UseAbility(pc); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Cleanser.cs b/Roles/Crewmate/Cleanser.cs index e82ca3377..66b78882e 100644 --- a/Roles/Crewmate/Cleanser.cs +++ b/Roles/Crewmate/Cleanser.cs @@ -5,123 +5,122 @@ using UnityEngine; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Cleanser : RoleBase { - public class Cleanser : RoleBase + private const int Id = 23420; + public static List PlayerIdList = []; + public static List CleansedPlayers = []; + public static Dictionary DidVote = []; + + public static OptionItem CleanserUsesOpt; + public static OptionItem CleansedCanGetAddon; + public static OptionItem CancelVote; + + private byte CleanserId; + public byte CleanserTarget = byte.MaxValue; + public int CleanserUses; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 23420; - public static List PlayerIdList = []; - public static List CleansedPlayers = []; - public static Dictionary DidVote = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Cleanser); - public static OptionItem CleanserUsesOpt; - public static OptionItem CleansedCanGetAddon; - public static OptionItem CancelVote; + CleanserUsesOpt = new IntegerOptionItem(Id + 10, "MaxCleanserUses", new(1, 14, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]) + .SetValueFormat(OptionFormat.Times); - private byte CleanserId; - public byte CleanserTarget = byte.MaxValue; - public int CleanserUses; + CleansedCanGetAddon = new BooleanOptionItem(Id + 11, "CleansedCanGetAddon", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]); - public override bool IsEnable => PlayerIdList.Count > 0; + CancelVote = CreateVoteCancellingUseSetting(Id + 12, CustomRoles.Cleanser, TabGroup.CrewmateRoles); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + CleanserTarget = byte.MaxValue; + CleanserUses = 0; + CleansedPlayers = []; + DidVote = []; + CleanserId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + CleanserTarget = byte.MaxValue; + CleanserUses = 0; + DidVote.Add(playerId, false); + CleanserId = playerId; + } + + //public static string GetProgressText(byte playerId) => Utils.ColorString(CleanserUsesOpt.GetInt() - CleanserUses[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.Cleanser).ShadeColor(0.25f) : Color.gray, CleanserUses.TryGetValue(playerId, out var x) ? $"({CleanserUsesOpt.GetInt() - x})" : "Invalid"); + public override string GetProgressText(byte playerId, bool comms) + { + Color x = CleanserUsesOpt.GetInt() - CleanserUses > 0 ? Utils.GetRoleColor(CustomRoles.Cleanser) : Color.gray; + return Utils.ColorString(x, $"({CleanserUsesOpt.GetInt() - CleanserUses})"); + } + + public void SendRPC(byte playerId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCleanserCleanLimit, SendOption.Reliable); + writer.Write(playerId); + writer.Write(CleanserUses); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte CleanserId = reader.ReadByte(); + if (Main.PlayerStates[CleanserId].Role is not Cleanser cs) return; + + int Limit = reader.ReadInt32(); + cs.CleanserUses = Limit; + } + + public override bool OnVote(PlayerControl voter, PlayerControl target) + { + if (DidVote[voter.PlayerId] || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; + + DidVote[voter.PlayerId] = true; + if (CleanserUses >= CleanserUsesOpt.GetInt()) return false; + + if (target.PlayerId == voter.PlayerId) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Cleanser); - - CleanserUsesOpt = new IntegerOptionItem(Id + 10, "MaxCleanserUses", new(1, 14, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]) - .SetValueFormat(OptionFormat.Times); - - CleansedCanGetAddon = new BooleanOptionItem(Id + 11, "CleansedCanGetAddon", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cleanser]); - - CancelVote = CreateVoteCancellingUseSetting(Id + 12, CustomRoles.Cleanser, TabGroup.CrewmateRoles); + Utils.SendMessage(GetString("CleanserRemoveSelf"), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); + return false; } - public override void Init() - { - PlayerIdList = []; - CleanserTarget = byte.MaxValue; - CleanserUses = 0; - CleansedPlayers = []; - DidVote = []; - CleanserId = byte.MaxValue; - } + if (CleanserTarget != byte.MaxValue) return false; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - CleanserTarget = byte.MaxValue; - CleanserUses = 0; - DidVote.Add(playerId, false); - CleanserId = playerId; - } + CleanserUses++; + CleanserTarget = target.PlayerId; + Logger.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} cleansed {target.GetNameWithRole().RemoveHtmlTags()}", "Cleansed"); + CleansedPlayers.Add(target.PlayerId); + Utils.SendMessage(string.Format(GetString("CleanserRemovedRole"), target.GetRealName()), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); + SendRPC(voter.PlayerId); - //public static string GetProgressText(byte playerId) => Utils.ColorString(CleanserUsesOpt.GetInt() - CleanserUses[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.Cleanser).ShadeColor(0.25f) : Color.gray, CleanserUses.TryGetValue(playerId, out var x) ? $"({CleanserUsesOpt.GetInt() - x})" : "Invalid"); - public override string GetProgressText(byte playerId, bool comms) - { - Color x = CleanserUsesOpt.GetInt() - CleanserUses > 0 ? Utils.GetRoleColor(CustomRoles.Cleanser) : Color.gray; - return Utils.ColorString(x, $"({CleanserUsesOpt.GetInt() - CleanserUses})"); - } + Main.DontCancelVoteList.Add(voter.PlayerId); + return true; + } - public void SendRPC(byte playerId) - { - if (!IsEnable || !Utils.DoRPC) return; + public override void AfterMeetingTasks() + { + if (CleanserTarget == byte.MaxValue || CleanserId == byte.MaxValue) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCleanserCleanLimit, SendOption.Reliable); - writer.Write(playerId); - writer.Write(CleanserUses); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + DidVote[CleanserId] = false; + PlayerControl targetPc = Utils.GetPlayerById(CleanserTarget); + if (targetPc == null) return; - public static void ReceiveRPC(MessageReader reader) - { - byte CleanserId = reader.ReadByte(); - if (Main.PlayerStates[CleanserId].Role is not Cleanser cs) return; - - int Limit = reader.ReadInt32(); - cs.CleanserUses = Limit; - } - - public override bool OnVote(PlayerControl voter, PlayerControl target) - { - if (DidVote[voter.PlayerId] || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; - - DidVote[voter.PlayerId] = true; - if (CleanserUses >= CleanserUsesOpt.GetInt()) return false; - - if (target.PlayerId == voter.PlayerId) - { - Utils.SendMessage(GetString("CleanserRemoveSelf"), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); - return false; - } - - if (CleanserTarget != byte.MaxValue) return false; - - CleanserUses++; - CleanserTarget = target.PlayerId; - Logger.Info($"{voter.GetNameWithRole().RemoveHtmlTags()} cleansed {target.GetNameWithRole().RemoveHtmlTags()}", "Cleansed"); - CleansedPlayers.Add(target.PlayerId); - Utils.SendMessage(string.Format(GetString("CleanserRemovedRole"), target.GetRealName()), voter.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Cleanser), GetString("CleanserTitle"))); - SendRPC(voter.PlayerId); - - Main.DontCancelVoteList.Add(voter.PlayerId); - return true; - } - - public override void AfterMeetingTasks() - { - if (CleanserTarget == byte.MaxValue || CleanserId == byte.MaxValue) return; - - DidVote[CleanserId] = false; - PlayerControl targetPc = Utils.GetPlayerById(CleanserTarget); - if (targetPc == null) return; - - targetPc.RpcSetCustomRole(CustomRoles.Cleansed); - Logger.Info($"Removed all the add ons of {targetPc.GetNameWithRole().RemoveHtmlTags()}", "Cleanser"); - CleanserTarget = byte.MaxValue; - targetPc.MarkDirtySettings(); - targetPc.Notify(GetString("LostAddonByCleanser")); - } + targetPc.RpcSetCustomRole(CustomRoles.Cleansed); + Logger.Info($"Removed all the add ons of {targetPc.GetNameWithRole().RemoveHtmlTags()}", "Cleanser"); + CleanserTarget = byte.MaxValue; + targetPc.MarkDirtySettings(); + targetPc.Notify(GetString("LostAddonByCleanser")); } } \ No newline at end of file diff --git a/Roles/Crewmate/Convener.cs b/Roles/Crewmate/Convener.cs index b96d59abb..3ecae4423 100644 --- a/Roles/Crewmate/Convener.cs +++ b/Roles/Crewmate/Convener.cs @@ -1,83 +1,82 @@ using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Convener : RoleBase { - internal class Convener : RoleBase + public static OptionItem CD; + public static OptionItem Limit; + public static OptionItem ConvenerAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public static bool On; + private static int Id => 643350; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static OptionItem CD; - public static OptionItem Limit; - public static OptionItem ConvenerAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Convener); + CD = Options.CreateCDSetting(Id + 2, TabGroup.CrewmateRoles, CustomRoles.Convener); - public static bool On; - private static int Id => 643350; - public override bool IsEnable => On; + Limit = new IntegerOptionItem(Id + 3, "AbilityUseLimit", new(0, 20, 1), 0, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Convener]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + ConvenerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 4, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Convener]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 5, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Convener]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(Limit.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = CD.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnPet(PlayerControl pc) + { + PullEveryone(pc, isPet: true); + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + PullEveryone(physics.myPlayer, ventId); + } + + private static void PullEveryone(PlayerControl pc, int ventId = 0, bool isPet = false) + { + if (pc == null || pc.GetAbilityUseLimit() < 1f) return; + + if (isPet) + Utils.TPAll(pc.Pos()); + else { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Convener); - CD = Options.CreateCDSetting(Id + 2, TabGroup.CrewmateRoles, CustomRoles.Convener); - - Limit = new IntegerOptionItem(Id + 3, "AbilityUseLimit", new(0, 20, 1), 0, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Convener]) - .SetValueFormat(OptionFormat.Times); - - ConvenerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 4, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Convener]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 5, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Convener]) - .SetValueFormat(OptionFormat.Times); + LateTask.New(() => pc.MyPhysics.RpcBootFromVent(ventId), 0.5f, "Convener RpcBootFromVent"); + LateTask.New(() => Utils.TPAll(pc.Pos()), 1f, "Convener TP"); } - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(Limit.GetInt()); - } + pc.RpcRemoveAbilityUse(); + } - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = CD.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnPet(PlayerControl pc) - { - PullEveryone(pc, isPet: true); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - PullEveryone(physics.myPlayer, ventId); - } - - private static void PullEveryone(PlayerControl pc, int ventId = 0, bool isPet = false) - { - if (pc == null || pc.GetAbilityUseLimit() < 1f) return; - - if (isPet) - Utils.TPAll(pc.Pos()); - else - { - LateTask.New(() => pc.MyPhysics.RpcBootFromVent(ventId), 0.5f, "Convener RpcBootFromVent"); - LateTask.New(() => Utils.TPAll(pc.Pos()), 1f, "Convener TP"); - } - - pc.RpcRemoveAbilityUse(); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/CopyCat.cs b/Roles/Crewmate/CopyCat.cs index 21db31b23..b9fa99123 100644 --- a/Roles/Crewmate/CopyCat.cs +++ b/Roles/Crewmate/CopyCat.cs @@ -2,172 +2,171 @@ using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class CopyCat : RoleBase { - public class CopyCat : RoleBase + private const int Id = 666420; + public static List Instances = []; + + private static OptionItem KillCooldown; + private static OptionItem CanKill; + private static OptionItem CopyCrewVar; + private static OptionItem MiscopyLimitOpt; + public static OptionItem UsePet; + + public PlayerControl CopyCatPC; + private float CurrentKillCooldown = DefaultKillCooldown; + private float TempLimit; + + public override bool IsEnable => Instances.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 666420; - public static List Instances = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.CopyCat); - private static OptionItem KillCooldown; - private static OptionItem CanKill; - private static OptionItem CopyCrewVar; - private static OptionItem MiscopyLimitOpt; - public static OptionItem UsePet; + KillCooldown = new FloatOptionItem(Id + 10, "CopyCatCopyCooldown", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CopyCat]) + .SetValueFormat(OptionFormat.Seconds); - public PlayerControl CopyCatPC; - private float CurrentKillCooldown = DefaultKillCooldown; - private float TempLimit; + CanKill = new BooleanOptionItem(Id + 11, "CopyCatCanKill", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CopyCat]); - public override bool IsEnable => Instances.Count > 0; + CopyCrewVar = new BooleanOptionItem(Id + 13, "CopyCrewVar", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CopyCat]); - public override void SetupCustomOption() + MiscopyLimitOpt = new IntegerOptionItem(Id + 12, "CopyCatMiscopyLimit", new(0, 14, 1), 2, TabGroup.CrewmateRoles) + .SetParent(CanKill) + .SetValueFormat(OptionFormat.Times); + + UsePet = CreatePetUseSetting(Id + 14, CustomRoles.CopyCat); + } + + public override void Init() + { + Instances = []; + CurrentKillCooldown = DefaultKillCooldown; + } + + public override void Add(byte playerId) + { + Instances.Add(this); + CopyCatPC = Utils.GetPlayerById(playerId); + CurrentKillCooldown = KillCooldown.GetFloat(); + int limit = MiscopyLimitOpt.GetInt(); + playerId.SetAbilityUseLimit(limit); + TempLimit = limit; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CurrentKillCooldown; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + private void ResetRole() + { + CustomRoles role = CopyCatPC.GetCustomRole(); + + // Remove settings for current role + switch (role) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.CopyCat); - - KillCooldown = new FloatOptionItem(Id + 10, "CopyCatCopyCooldown", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CopyCat]) - .SetValueFormat(OptionFormat.Seconds); - - CanKill = new BooleanOptionItem(Id + 11, "CopyCatCanKill", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CopyCat]); - - CopyCrewVar = new BooleanOptionItem(Id + 13, "CopyCrewVar", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CopyCat]); - - MiscopyLimitOpt = new IntegerOptionItem(Id + 12, "CopyCatMiscopyLimit", new(0, 14, 1), 2, TabGroup.CrewmateRoles) - .SetParent(CanKill) - .SetValueFormat(OptionFormat.Times); - - UsePet = CreatePetUseSetting(Id + 14, CustomRoles.CopyCat); + case CustomRoles.Cleanser: + Cleanser.DidVote.Remove(CopyCatPC.PlayerId); + break; + case CustomRoles.Merchant: + Merchant.AddonsSold.Remove(CopyCatPC.PlayerId); + Merchant.BribedKiller.Remove(CopyCatPC.PlayerId); + break; + case CustomRoles.Paranoia: + Paranoia.ParaUsedButtonCount.Remove(CopyCatPC.PlayerId); + break; + case CustomRoles.Snitch: + Snitch.IsExposed.Remove(CopyCatPC.PlayerId); + Snitch.IsComplete.Remove(CopyCatPC.PlayerId); + break; + case CustomRoles.Mayor: + Mayor.MayorUsedButtonCount.Remove(CopyCatPC.PlayerId); + break; } - public override void Init() - { - Instances = []; - CurrentKillCooldown = DefaultKillCooldown; - } + Main.PlayerStates[CopyCatPC.PlayerId].MainRole = CustomRoles.CopyCat; + Main.PlayerStates[CopyCatPC.PlayerId].Role = this; + SetKillCooldown(CopyCatPC.PlayerId); + CopyCatPC.SetAbilityUseLimit(TempLimit); + CopyCatPC.RpcChangeRoleBasis(CustomRoles.CopyCat); + CopyCatPC.SyncSettings(); + } - public override void Add(byte playerId) - { - Instances.Add(this); - CopyCatPC = Utils.GetPlayerById(playerId); - CurrentKillCooldown = KillCooldown.GetFloat(); - int limit = MiscopyLimitOpt.GetInt(); - playerId.SetAbilityUseLimit(limit); - TempLimit = limit; - } + public static void ResetRoles() + { + Instances.Do(x => x.ResetRole()); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CurrentKillCooldown; - } + public override bool OnCheckMurder(PlayerControl pc, PlayerControl tpc) + { + CustomRoles role = tpc.GetCustomRole(); - public override bool CanUseKillButton(PlayerControl pc) + if (CopyCrewVar.GetBool()) { - return pc.IsAlive(); - } - - private void ResetRole() - { - CustomRoles role = CopyCatPC.GetCustomRole(); - - // Remove settings for current role - switch (role) + role = role switch { - case CustomRoles.Cleanser: - Cleanser.DidVote.Remove(CopyCatPC.PlayerId); - break; - case CustomRoles.Merchant: - Merchant.AddonsSold.Remove(CopyCatPC.PlayerId); - Merchant.BribedKiller.Remove(CopyCatPC.PlayerId); - break; - case CustomRoles.Paranoia: - Paranoia.ParaUsedButtonCount.Remove(CopyCatPC.PlayerId); - break; - case CustomRoles.Snitch: - Snitch.IsExposed.Remove(CopyCatPC.PlayerId); - Snitch.IsComplete.Remove(CopyCatPC.PlayerId); - break; - case CustomRoles.Mayor: - Mayor.MayorUsedButtonCount.Remove(CopyCatPC.PlayerId); - break; - } - - Main.PlayerStates[CopyCatPC.PlayerId].MainRole = CustomRoles.CopyCat; - Main.PlayerStates[CopyCatPC.PlayerId].Role = this; - SetKillCooldown(CopyCatPC.PlayerId); - CopyCatPC.SetAbilityUseLimit(TempLimit); - CopyCatPC.RpcChangeRoleBasis(CustomRoles.CopyCat); - CopyCatPC.SyncSettings(); + CustomRoles.Swooper or CustomRoles.Wraith => CustomRoles.Chameleon, + CustomRoles.Stealth or CustomRoles.Nonplus => CustomRoles.Grenadier, + CustomRoles.TimeThief => CustomRoles.TimeManager, + CustomRoles.EvilDiviner or CustomRoles.Ritualist => CustomRoles.Farseer, + CustomRoles.AntiAdminer => CustomRoles.Monitor, + CustomRoles.CursedWolf or CustomRoles.Jinx => CustomRoles.Veteran, + CustomRoles.EvilTracker => CustomRoles.TrackerEHR, + CustomRoles.SerialKiller => CustomRoles.Addict, + CustomRoles.Miner => CustomRoles.Mole, + CustomRoles.Escapee => CustomRoles.Tunneler, + CustomRoles.Twister => CustomRoles.TimeMaster, + CustomRoles.Disperser => CustomRoles.Transporter, + CustomRoles.Eraser => CustomRoles.Cleanser, + CustomRoles.Visionary => CustomRoles.Oracle, + CustomRoles.Workaholic => CustomRoles.Snitch, + CustomRoles.Sunnyboy => CustomRoles.Doctor, + CustomRoles.Vindicator or CustomRoles.Pickpocket => CustomRoles.Mayor, + CustomRoles.Councillor => CustomRoles.Judge, + CustomRoles.EvilGuesser or CustomRoles.Doomsayer => CustomRoles.NiceGuesser, + _ => role + }; } - public static void ResetRoles() + if (tpc.IsCrewmate() && !tpc.GetCustomSubRoles().Contains(CustomRoles.Rascal) && !tpc.IsConverted()) { - Instances.Do(x => x.ResetRole()); - } + TempLimit = pc.GetAbilityUseLimit(); - public override bool OnCheckMurder(PlayerControl pc, PlayerControl tpc) - { - CustomRoles role = tpc.GetCustomRole(); + pc.RpcSetCustomRole(role); + pc.RpcChangeRoleBasis(role); + pc.SetAbilityUseLimit(tpc.GetAbilityUseLimit()); - if (CopyCrewVar.GetBool()) - { - role = role switch - { - CustomRoles.Swooper or CustomRoles.Wraith => CustomRoles.Chameleon, - CustomRoles.Stealth or CustomRoles.Nonplus => CustomRoles.Grenadier, - CustomRoles.TimeThief => CustomRoles.TimeManager, - CustomRoles.EvilDiviner or CustomRoles.Ritualist => CustomRoles.Farseer, - CustomRoles.AntiAdminer => CustomRoles.Monitor, - CustomRoles.CursedWolf or CustomRoles.Jinx => CustomRoles.Veteran, - CustomRoles.EvilTracker => CustomRoles.TrackerEHR, - CustomRoles.SerialKiller => CustomRoles.Addict, - CustomRoles.Miner => CustomRoles.Mole, - CustomRoles.Escapee => CustomRoles.Tunneler, - CustomRoles.Twister => CustomRoles.TimeMaster, - CustomRoles.Disperser => CustomRoles.Transporter, - CustomRoles.Eraser => CustomRoles.Cleanser, - CustomRoles.Visionary => CustomRoles.Oracle, - CustomRoles.Workaholic => CustomRoles.Snitch, - CustomRoles.Sunnyboy => CustomRoles.Doctor, - CustomRoles.Vindicator or CustomRoles.Pickpocket => CustomRoles.Mayor, - CustomRoles.Councillor => CustomRoles.Judge, - CustomRoles.EvilGuesser or CustomRoles.Doomsayer => CustomRoles.NiceGuesser, - _ => role - }; - } - - if (tpc.IsCrewmate() && !tpc.GetCustomSubRoles().Contains(CustomRoles.Rascal) && !tpc.IsConverted()) - { - TempLimit = pc.GetAbilityUseLimit(); - - pc.RpcSetCustomRole(role); - pc.RpcChangeRoleBasis(role); - pc.SetAbilityUseLimit(tpc.GetAbilityUseLimit()); - - pc.SyncSettings(); - pc.SetKillCooldown(); - pc.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(role))); - return false; - } - - if (CanKill.GetBool()) - { - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - SetKillCooldown(pc.PlayerId); - return true; - } - - pc.Suicide(); - return false; - } - - pc.Notify(GetString("CopyCatCanNotCopy")); - SetKillCooldown(pc.PlayerId); + pc.SyncSettings(); + pc.SetKillCooldown(); + pc.Notify(string.Format(GetString("CopyCatRoleChange"), Utils.GetRoleName(role))); return false; } + + if (CanKill.GetBool()) + { + if (pc.GetAbilityUseLimit() >= 1) + { + pc.RpcRemoveAbilityUse(); + SetKillCooldown(pc.PlayerId); + return true; + } + + pc.Suicide(); + return false; + } + + pc.Notify(GetString("CopyCatCanNotCopy")); + SetKillCooldown(pc.PlayerId); + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/CrewmateVanillaRoles.cs b/Roles/Crewmate/CrewmateVanillaRoles.cs index d6adfdaea..2aeec10e5 100644 --- a/Roles/Crewmate/CrewmateVanillaRoles.cs +++ b/Roles/Crewmate/CrewmateVanillaRoles.cs @@ -1,70 +1,69 @@ using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class CrewmateVanillaRoles : IVanillaSettingHolder { - internal class CrewmateVanillaRoles : IVanillaSettingHolder + public static OptionItem VanillaCrewmateCannotBeGuessed; + public static OptionItem EngineerCD; + public static OptionItem EngineerDur; + public static OptionItem NoiseMakerImpostorAlert; + public static OptionItem NoisemakerAlertDuration; + public static OptionItem ScientistDur; + public static OptionItem ScientistCD; + public static OptionItem TrackerCooldown; + public static OptionItem TrackerDuration; + public static OptionItem TrackerDelay; + public TabGroup Tab => TabGroup.CrewmateRoles; + + public void SetupCustomOption() { - public static OptionItem VanillaCrewmateCannotBeGuessed; - public static OptionItem EngineerCD; - public static OptionItem EngineerDur; - public static OptionItem NoiseMakerImpostorAlert; - public static OptionItem NoisemakerAlertDuration; - public static OptionItem ScientistDur; - public static OptionItem ScientistCD; - public static OptionItem TrackerCooldown; - public static OptionItem TrackerDuration; - public static OptionItem TrackerDelay; - public TabGroup Tab => TabGroup.CrewmateRoles; + SetupRoleOptions(5020, Tab, CustomRoles.CrewmateEHR); - public void SetupCustomOption() - { - SetupRoleOptions(5020, Tab, CustomRoles.CrewmateEHR); + VanillaCrewmateCannotBeGuessed = new BooleanOptionItem(5022, "VanillaCrewmateCannotBeGuessed", false, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.CrewmateEHR]); - VanillaCrewmateCannotBeGuessed = new BooleanOptionItem(5022, "VanillaCrewmateCannotBeGuessed", false, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.CrewmateEHR]); + SetupRoleOptions(5000, Tab, CustomRoles.EngineerEHR); - SetupRoleOptions(5000, Tab, CustomRoles.EngineerEHR); + EngineerCD = new FloatOptionItem(5002, "VentCooldown", new(1f, 250f, 1f), 30f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.EngineerEHR]) + .SetValueFormat(OptionFormat.Seconds); - EngineerCD = new FloatOptionItem(5002, "VentCooldown", new(1f, 250f, 1f), 30f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.EngineerEHR]) - .SetValueFormat(OptionFormat.Seconds); + EngineerDur = new FloatOptionItem(5003, "MaxInVentTime", new(1f, 250f, 1f), 15f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.EngineerEHR]) + .SetValueFormat(OptionFormat.Seconds); - EngineerDur = new FloatOptionItem(5003, "MaxInVentTime", new(1f, 250f, 1f), 15f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.EngineerEHR]) - .SetValueFormat(OptionFormat.Seconds); + SetupRoleOptions(5040, Tab, CustomRoles.NoisemakerEHR); - SetupRoleOptions(5040, Tab, CustomRoles.NoisemakerEHR); + NoiseMakerImpostorAlert = new BooleanOptionItem(5042, "NoisemakerImpostorAlert", false, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.NoisemakerEHR]); - NoiseMakerImpostorAlert = new BooleanOptionItem(5042, "NoisemakerImpostorAlert", false, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.NoisemakerEHR]); + NoisemakerAlertDuration = new FloatOptionItem(5043, "NoisemakerAlertDuration", new(1f, 250f, 1f), 5f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.NoisemakerEHR]) + .SetValueFormat(OptionFormat.Seconds); - NoisemakerAlertDuration = new FloatOptionItem(5043, "NoisemakerAlertDuration", new(1f, 250f, 1f), 5f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.NoisemakerEHR]) - .SetValueFormat(OptionFormat.Seconds); + SetupRoleOptions(5100, Tab, CustomRoles.ScientistEHR); - SetupRoleOptions(5100, Tab, CustomRoles.ScientistEHR); + ScientistCD = new FloatOptionItem(5102, "VitalsCooldown", new(1f, 250f, 1f), 3f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ScientistEHR]) + .SetValueFormat(OptionFormat.Seconds); - ScientistCD = new FloatOptionItem(5102, "VitalsCooldown", new(1f, 250f, 1f), 3f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ScientistEHR]) - .SetValueFormat(OptionFormat.Seconds); + ScientistDur = new FloatOptionItem(5103, "VitalsDuration", new(1f, 250f, 1f), 15f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ScientistEHR]) + .SetValueFormat(OptionFormat.Seconds); - ScientistDur = new FloatOptionItem(5103, "VitalsDuration", new(1f, 250f, 1f), 15f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ScientistEHR]) - .SetValueFormat(OptionFormat.Seconds); + SetupRoleOptions(5060, Tab, CustomRoles.TrackerEHR); - SetupRoleOptions(5060, Tab, CustomRoles.TrackerEHR); + TrackerCooldown = new FloatOptionItem(5062, "TrackerCooldown", new(1f, 250f, 1f), 25f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR]) + .SetValueFormat(OptionFormat.Seconds); - TrackerCooldown = new FloatOptionItem(5062, "TrackerCooldown", new(1f, 250f, 1f), 25f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR]) - .SetValueFormat(OptionFormat.Seconds); + TrackerDuration = new FloatOptionItem(5063, "TrackerDuration", new(1f, 250f, 1f), 20f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR]) + .SetValueFormat(OptionFormat.Seconds); - TrackerDuration = new FloatOptionItem(5063, "TrackerDuration", new(1f, 250f, 1f), 20f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR]) - .SetValueFormat(OptionFormat.Seconds); - - TrackerDelay = new FloatOptionItem(5064, "TrackerDelay", new(1f, 250f, 1f), 5f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR]) - .SetValueFormat(OptionFormat.Seconds); - } + TrackerDelay = new FloatOptionItem(5064, "TrackerDelay", new(1f, 250f, 1f), 5f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.TrackerEHR]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/Crewmate/Crusader.cs b/Roles/Crewmate/Crusader.cs index 77c030068..cf8628f90 100644 --- a/Roles/Crewmate/Crusader.cs +++ b/Roles/Crewmate/Crusader.cs @@ -1,83 +1,82 @@ using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Crusader : RoleBase { - public class Crusader : RoleBase + private const int Id = 20050; + private static List PlayerIdList = []; + + public static List ForCrusade = []; + + private static OptionItem SkillLimitOpt; + private static OptionItem SkillCooldown; + public static OptionItem UsePet; + + private float CurrentKillCooldown; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 20050; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Crusader); - public static List ForCrusade = []; + SkillCooldown = new FloatOptionItem(Id + 10, "CrusaderSkillCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Crusader]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem SkillLimitOpt; - private static OptionItem SkillCooldown; - public static OptionItem UsePet; + SkillLimitOpt = new IntegerOptionItem(Id + 11, "CrusaderSkillLimit", new(1, 10, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Crusader]) + .SetValueFormat(OptionFormat.Times); - private float CurrentKillCooldown; + UsePet = Options.CreatePetUseSetting(Id + 12, CustomRoles.Crusader); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + CurrentKillCooldown = SkillCooldown.GetFloat(); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Crusader); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(SkillLimitOpt.GetInt()); + CurrentKillCooldown = SkillCooldown.GetFloat(); + } - SkillCooldown = new FloatOptionItem(Id + 10, "CrusaderSkillCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Crusader]) - .SetValueFormat(OptionFormat.Seconds); + public override bool CanUseKillButton(PlayerControl pc) + { + return !Main.PlayerStates[pc.PlayerId].IsDead + && pc.GetAbilityUseLimit() >= 1; + } - SkillLimitOpt = new IntegerOptionItem(Id + 11, "CrusaderSkillLimit", new(1, 10, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Crusader]) - .SetValueFormat(OptionFormat.Times); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? CurrentKillCooldown : 15f; + } - UsePet = Options.CreatePetUseSetting(Id + 12, CustomRoles.Crusader); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } - public override void Init() - { - PlayerIdList = []; - CurrentKillCooldown = SkillCooldown.GetFloat(); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(SkillLimitOpt.GetInt()); - CurrentKillCooldown = SkillCooldown.GetFloat(); - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() <= 0) return false; - public override bool CanUseKillButton(PlayerControl pc) - { - return !Main.PlayerStates[pc.PlayerId].IsDead - && pc.GetAbilityUseLimit() >= 1; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? CurrentKillCooldown : 15f; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() <= 0) return false; - - ForCrusade.Remove(target.PlayerId); - ForCrusade.Add(target.PlayerId); - killer.RpcRemoveAbilityUse(); - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - target.RpcGuardAndKill(killer); - return false; - } + ForCrusade.Remove(target.PlayerId); + ForCrusade.Add(target.PlayerId); + killer.RpcRemoveAbilityUse(); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + target.RpcGuardAndKill(killer); + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/CyberStar.cs b/Roles/Crewmate/CyberStar.cs index 8e2ce6523..8b9f75029 100644 --- a/Roles/Crewmate/CyberStar.cs +++ b/Roles/Crewmate/CyberStar.cs @@ -1,24 +1,23 @@ using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class CyberStar : RoleBase { - internal class CyberStar : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(5300, TabGroup.CrewmateRoles, CustomRoles.CyberStar); - public override void SetupCustomOption() - { - SetupRoleOptions(5300, TabGroup.CrewmateRoles, CustomRoles.CyberStar); + ImpKnowCyberStarDead = new BooleanOptionItem(5400, "ImpKnowCyberStarDead", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CyberStar]); - ImpKnowCyberStarDead = new BooleanOptionItem(5400, "ImpKnowCyberStarDead", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CyberStar]); - - NeutralKnowCyberStarDead = new BooleanOptionItem(5500, "NeutralKnowCyberStarDead", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.CyberStar]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + NeutralKnowCyberStarDead = new BooleanOptionItem(5500, "NeutralKnowCyberStarDead", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.CyberStar]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Dad.cs b/Roles/Crewmate/Dad.cs index ea96389f1..f8af5288e 100644 --- a/Roles/Crewmate/Dad.cs +++ b/Roles/Crewmate/Dad.cs @@ -6,463 +6,462 @@ using EHR.Modules; using EHR.Neutral; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Dad : RoleBase { - public class Dad : RoleBase + public enum Ability { - public enum Ability + GoForMilk, + SuperVision, + Sniffing, + Sleep, + Rage, + GiveDrink, + BecomeGodOfAlcohol + } + + public static bool On; + private static List Instances = []; + private static List AllDeadBodies = []; + + private static OptionItem AlcoholDecreaseOnKilled; + private static OptionItem AlcoholDecreaseOnVotedOut; + private static OptionItem NormalAlcoholDecreaseFrequency; + private static OptionItem NormalAlcoholDecreaseValue; + private static OptionItem AlcoholIncreaseOnBeerPurchase; + private static OptionItem ShowWarningWhenAlcoholIsBelow; + private static OptionItem StartingAlcohol; + private static OptionItem StartingMoney; + private static OptionItem MoneyGainOnTaskComplete; + private static OptionItem AlcoholCost; + private static OptionItem SuperVisionDuration; + private static OptionItem GivingDrinkRange; + private static OptionItem DrunkRoleIncorrectChance; + private static readonly Dictionary AbilityAlcoholDecreaseOptions = []; + private static readonly Dictionary AbilityAlcoholRequirement = []; + + private int Alcohol; + private string Arrows; + private int Count; + private byte DadId; + public bool DoneTasks; + public List DrunkPlayers; + private long LastUpdate; + private long MilkTimer; + private Ability SelectedAbility; + private Vent Shop; + private float StartingSpeed; + private long SuperVisionTS; + public HashSet UsingAbilities; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + var id = 648450; + const TabGroup tab = TabGroup.CrewmateRoles; + const CustomRoles role = CustomRoles.Dad; + + Options.SetupRoleOptions(id++, tab, role); + + StringOptionItem parent = Options.CustomRoleSpawnChances[role]; + + AlcoholDecreaseOnKilled = new IntegerOptionItem(++id, "Dad.AlcoholDecreaseOnKilled", new(0, 100, 1), 10, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + AlcoholDecreaseOnVotedOut = new IntegerOptionItem(++id, "Dad.AlcoholDecreaseOnVotedOut", new(0, 100, 1), 5, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + NormalAlcoholDecreaseFrequency = new IntegerOptionItem(++id, "Dad.NormalAlcoholDecreaseFrequency", new(1, 60, 1), 10, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Seconds); + + NormalAlcoholDecreaseValue = new IntegerOptionItem(++id, "Dad.NormalAlcoholDecreaseValue", new(0, 100, 1), 1, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + AlcoholIncreaseOnBeerPurchase = new IntegerOptionItem(++id, "Dad.AlcoholIncreaseOnBeerPurchase", new(0, 100, 1), 10, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + ShowWarningWhenAlcoholIsBelow = new IntegerOptionItem(++id, "Dad.ShowWarningWhenAlcoholIsBelow", new(0, 100, 1), 20, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + StartingAlcohol = new IntegerOptionItem(++id, "Dad.StartingAlcohol", new(0, 100, 1), 20, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + StartingMoney = new IntegerOptionItem(++id, "Dad.StartingMoney", new(0, 100, 1), 0, tab) + .SetParent(parent); + + MoneyGainOnTaskComplete = new IntegerOptionItem(++id, "Dad.MoneyGainOnTaskComplete", new(0, 100, 1), 15, tab) + .SetParent(parent); + + AlcoholCost = new IntegerOptionItem(++id, "Dad.AlcoholCost", new(0, 100, 1), 10, tab) + .SetParent(parent); + + SuperVisionDuration = new IntegerOptionItem(++id, "Dad.SuperVisionDuration", new(1, 90, 1), 20, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Seconds); + + GivingDrinkRange = new FloatOptionItem(++id, "Dad.GivingDrinkRange", new(0.5f, 10f, 0.5f), 4f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Multiplier); + + DrunkRoleIncorrectChance = new IntegerOptionItem(++id, "Dad.DrunkRoleIncorrectChance", new(0, 100, 1), 50, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Percent); + + Ability[] abilities = Enum.GetValues(); + + foreach (Ability ability in abilities) { - GoForMilk, - SuperVision, - Sniffing, - Sleep, - Rage, - GiveDrink, - BecomeGodOfAlcohol - } - - public static bool On; - private static List Instances = []; - private static List AllDeadBodies = []; - - private static OptionItem AlcoholDecreaseOnKilled; - private static OptionItem AlcoholDecreaseOnVotedOut; - private static OptionItem NormalAlcoholDecreaseFrequency; - private static OptionItem NormalAlcoholDecreaseValue; - private static OptionItem AlcoholIncreaseOnBeerPurchase; - private static OptionItem ShowWarningWhenAlcoholIsBelow; - private static OptionItem StartingAlcohol; - private static OptionItem StartingMoney; - private static OptionItem MoneyGainOnTaskComplete; - private static OptionItem AlcoholCost; - private static OptionItem SuperVisionDuration; - private static OptionItem GivingDrinkRange; - private static OptionItem DrunkRoleIncorrectChance; - private static readonly Dictionary AbilityAlcoholDecreaseOptions = []; - private static readonly Dictionary AbilityAlcoholRequirement = []; - - private int Alcohol; - private string Arrows; - private int Count; - private byte DadId; - public bool DoneTasks; - public List DrunkPlayers; - private long LastUpdate; - private long MilkTimer; - private Ability SelectedAbility; - private Vent Shop; - private float StartingSpeed; - private long SuperVisionTS; - public HashSet UsingAbilities; - - public override bool IsEnable => On; - - public override void SetupCustomOption() - { - var id = 648450; - const TabGroup tab = TabGroup.CrewmateRoles; - const CustomRoles role = CustomRoles.Dad; - - Options.SetupRoleOptions(id++, tab, role); - - StringOptionItem parent = Options.CustomRoleSpawnChances[role]; - - AlcoholDecreaseOnKilled = new IntegerOptionItem(++id, "Dad.AlcoholDecreaseOnKilled", new(0, 100, 1), 10, tab) + AbilityAlcoholDecreaseOptions[ability] = new IntegerOptionItem(++id, $"Dad.{ability}.AlcoholDecrease", new(0, 100, 1), 10, tab) .SetParent(parent) .SetValueFormat(OptionFormat.Percent); + } - AlcoholDecreaseOnVotedOut = new IntegerOptionItem(++id, "Dad.AlcoholDecreaseOnVotedOut", new(0, 100, 1), 5, tab) + foreach (Ability ability in abilities) + { + AbilityAlcoholRequirement[ability] = new IntegerOptionItem(++id, $"Dad.{ability}.AlcoholRequirement", new(0, 100, 1), ability == Ability.BecomeGodOfAlcohol ? 40 : (int)ability * 5, tab) .SetParent(parent) .SetValueFormat(OptionFormat.Percent); - - NormalAlcoholDecreaseFrequency = new IntegerOptionItem(++id, "Dad.NormalAlcoholDecreaseFrequency", new(1, 60, 1), 10, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Seconds); - - NormalAlcoholDecreaseValue = new IntegerOptionItem(++id, "Dad.NormalAlcoholDecreaseValue", new(0, 100, 1), 1, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - - AlcoholIncreaseOnBeerPurchase = new IntegerOptionItem(++id, "Dad.AlcoholIncreaseOnBeerPurchase", new(0, 100, 1), 10, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - - ShowWarningWhenAlcoholIsBelow = new IntegerOptionItem(++id, "Dad.ShowWarningWhenAlcoholIsBelow", new(0, 100, 1), 20, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - - StartingAlcohol = new IntegerOptionItem(++id, "Dad.StartingAlcohol", new(0, 100, 1), 20, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - - StartingMoney = new IntegerOptionItem(++id, "Dad.StartingMoney", new(0, 100, 1), 0, tab) - .SetParent(parent); - - MoneyGainOnTaskComplete = new IntegerOptionItem(++id, "Dad.MoneyGainOnTaskComplete", new(0, 100, 1), 15, tab) - .SetParent(parent); - - AlcoholCost = new IntegerOptionItem(++id, "Dad.AlcoholCost", new(0, 100, 1), 10, tab) - .SetParent(parent); - - SuperVisionDuration = new IntegerOptionItem(++id, "Dad.SuperVisionDuration", new(1, 90, 1), 20, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Seconds); - - GivingDrinkRange = new FloatOptionItem(++id, "Dad.GivingDrinkRange", new(0.5f, 10f, 0.5f), 4f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Multiplier); - - DrunkRoleIncorrectChance = new IntegerOptionItem(++id, "Dad.DrunkRoleIncorrectChance", new(0, 100, 1), 50, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - - Ability[] abilities = Enum.GetValues(); - - foreach (Ability ability in abilities) - { - AbilityAlcoholDecreaseOptions[ability] = new IntegerOptionItem(++id, $"Dad.{ability}.AlcoholDecrease", new(0, 100, 1), 10, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - } - - foreach (Ability ability in abilities) - { - AbilityAlcoholRequirement[ability] = new IntegerOptionItem(++id, $"Dad.{ability}.AlcoholRequirement", new(0, 100, 1), ability == Ability.BecomeGodOfAlcohol ? 40 : (int)ability * 5, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Percent); - } - } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - //Main.AllPlayerSpeed[playerId] *= -1; - DadId = playerId; - Alcohol = StartingAlcohol.GetInt(); - LastUpdate = Utils.TimeStamp; - Count = 0; - Shop = ShipStatus.Instance.AllVents.RandomElement(); - DoneTasks = false; - SelectedAbility = default; - MilkTimer = 315569520; - UsingAbilities = []; - SuperVisionTS = 0; - Arrows = string.Empty; - StartingSpeed = Main.AllPlayerSpeed[playerId]; - DrunkPlayers = []; - playerId.SetAbilityUseLimit(StartingMoney.GetInt()); - Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 1, Shop.Id); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsingAbilities.Contains(Ability.Sleep)) - { - opt.SetVision(false); - opt.BlackOut(true); - return; - } - - if (UsingAbilities.Contains(Ability.SuperVision)) - { - opt.SetVision(true); - opt.SetFloat(FloatOptionNames.CrewLightMod, 1.5f); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.5f); - } - } - - public override void OnReportDeadBody() - { - AllDeadBodies = []; - Arrows = string.Empty; - Main.AllPlayerSpeed[DadId] = StartingSpeed; - - if (DrunkPlayers.Count > 0) - { - int chance = DrunkRoleIncorrectChance.GetInt(); - List allRoles = Enum.GetValues().Where(x => x.IsEnable() && !x.IsAdditionRole() && !HnSManager.AllHnSRoles.Contains(x) && !x.IsForOtherGameMode()).ToList(); - - foreach (byte id in DrunkPlayers) - { - PlayerControl pc = Utils.GetPlayerById(id); - if (pc == null) continue; - - CustomRoles selfRole = Main.PlayerStates[id].MainRole; - CustomRoles rndRole = allRoles.Without(selfRole).RandomElement(); - CustomRoles role = IRandom.Instance.Next(100) < chance ? rndRole : selfRole; - string msg = string.Format(Translator.GetString("Dad.DrunkRoleNotify"), Translator.GetString(role.ToString()).ToLower()); - int delay = 10 + IRandom.Instance.Next(12); - - LateTask.New(() => pc.RpcSendChat(msg), delay, $"Dad - DrunkRoleNotify - {pc.GetNameWithRole()}"); - } - - DrunkPlayers = []; - } - - Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 2, Arrows); - } - - public override void AfterMeetingTasks() - { - if (UsingAbilities.Contains(Ability.GoForMilk)) LateTask.New(() => Utils.GetPlayerById(DadId)?.TP(Pelican.GetBlackRoomPS()), 1f, log: false); - } - - public override void OnPet(PlayerControl pc) - { - SelectedAbility = (Ability)(((int)SelectedAbility + 1) % Enum.GetValues().Length); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (ventId == Shop.Id || UsingAbilities.Contains(Ability.Sleep)) return; - - if (AbilityAlcoholRequirement[SelectedAbility].GetInt() > Alcohol) - { - physics.myPlayer.Notify(Translator.GetString("Dad.AbilityNotAvailable")); - return; - } - - switch (SelectedAbility) - { - case Ability.GoForMilk when Alcohol >= 0: - LateTask.New(() => physics.RpcBootFromVent(ventId), 0.5f, log: false); - LateTask.New(() => physics.myPlayer.TP(Pelican.GetBlackRoomPS()), 1f, log: false); - Main.AllAlivePlayerControls.Do(x => x.Notify(Translator.GetString("Dad.GoForMilkNotify"), 10f)); - UsingAbilities.Add(SelectedAbility); - break; - case Ability.SuperVision when Alcohol >= 5: - SuperVisionTS = Utils.TimeStamp; - UsingAbilities.Add(SelectedAbility); - physics.myPlayer.MarkDirtySettings(); - break; - case Ability.Sniffing when Alcohol >= 10: - AllDeadBodies.Do(x => LocateArrow.Add(DadId, x)); - Arrows = LocateArrow.GetArrows(physics.myPlayer); - LocateArrow.RemoveAllTarget(DadId); - Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); - Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 2, Arrows); - break; - case Ability.Sleep when Alcohol >= 15: - Main.AllPlayerSpeed[DadId] = Main.MinSpeed; - UsingAbilities.Add(SelectedAbility); - physics.myPlayer.MarkDirtySettings(); - break; - case Ability.Rage when Alcohol >= 20: - UsingAbilities.Add(SelectedAbility); - break; - case Ability.GiveDrink when Alcohol >= 25: - Vector2 pos = physics.myPlayer.Pos(); - DrunkPlayers = Main.AllAlivePlayerControls.Without(physics.myPlayer).Where(x => Vector2.Distance(x.Pos(), pos) <= GivingDrinkRange.GetFloat()).Select(x => x.PlayerId).ToList(); - Utils.NotifyRoles(SpecifySeer: physics.myPlayer); - break; - case Ability.BecomeGodOfAlcohol when Alcohol >= 40: - Alcohol = 1; - UsingAbilities.Add(SelectedAbility); - break; - } - - Alcohol -= AbilityAlcoholDecreaseOptions[SelectedAbility].GetInt(); - NotifyIfNecessary(physics.myPlayer); - } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - if (vent.Id != Shop.Id) return; - - int cost = AlcoholCost.GetInt(); - int get = AlcoholIncreaseOnBeerPurchase.GetInt(); - - for (float money = pc.GetAbilityUseLimit(); money >= cost; money -= cost) - { - pc.SetAbilityUseLimit(money - cost); - Alcohol += get; - } - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 3, Alcohol); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - pc.RpcIncreaseAbilityUseLimitBy(MoneyGainOnTaskComplete.GetInt()); - - if (completedTaskCount + 1 >= totalTaskCount) - { - pc.Data.RpcSetTasks(new(0)); - DoneTasks = true; - GameData.Instance.RecomputeTaskCounts(); - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - - if (Count++ < 15) return; - - Count = 0; - - Vector2 pos = pc.Pos(); - if (UsingAbilities.Contains(Ability.Rage) && Main.AllAlivePlayerControls.FindFirst(x => Vector2.Distance(pos, x.Pos()) < 1.3f, out PlayerControl target) && pc.RpcCheckAndMurder(target)) UsingAbilities.Remove(Ability.Rage); - - bool notify = Vector2.Distance(pc.Pos(), Shop.transform.position) < 2f; - - long now = Utils.TimeStamp; - - if (now - LastUpdate < NormalAlcoholDecreaseFrequency.GetInt()) - { - if (notify) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - return; - } - - LastUpdate = now; - - if (UsingAbilities.Contains(Ability.GoForMilk)) - { - MilkTimer--; - - if (MilkTimer <= 0) - { - pc.TPToRandomVent(); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Crewmate); - CustomWinnerHolder.WinnerIds.UnionWith(Main.AllPlayerControls.Where(x => x.Is(Team.Crewmate)).Select(x => x.PlayerId)); - } - } - - if (UsingAbilities.Contains(Ability.SuperVision) && SuperVisionTS + SuperVisionDuration.GetInt() <= now) - { - UsingAbilities.Remove(Ability.SuperVision); - SuperVisionTS = 0; - pc.MarkDirtySettings(); - } - - if (UsingAbilities.Contains(Ability.Sleep)) return; - - if (UsingAbilities.Contains(Ability.BecomeGodOfAlcohol)) - Alcohol += NormalAlcoholDecreaseValue.GetInt(); - else - Alcohol -= NormalAlcoholDecreaseValue.GetInt(); - - if (Alcohol <= 0) - { - pc.Suicide(); - return; - } - - NotifyIfNecessary(pc, notify); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - LateTask.New(() => - { - Alcohol -= AlcoholDecreaseOnKilled.GetInt(); - NotifyIfNecessary(target); - }, 5f, log: false); - - return false; - } - - public static bool OnVotedOut(byte id) - { - Dad dad = Instances.FirstOrDefault(d => d.DadId == id); - if (dad == null) return false; - - dad.Alcohol -= AlcoholDecreaseOnVotedOut.GetInt(); - dad.NotifyIfNecessary(Utils.GetPlayerById(dad.DadId)); - Logger.Info("Ejection prohibited", "Dad"); - return true; - } - - public static void OnAnyoneDeath(PlayerControl target) - { - AllDeadBodies.Add(target.Pos()); - } - - public static bool OnAnyoneCheckMurderStart(PlayerControl target) - { - Dad dad = Instances.FirstOrDefault(d => d.DadId == target.PlayerId); - if (dad == null) return false; - - return dad.UsingAbilities.Contains(Ability.Sleep); - } - - private void NotifyIfNecessary(PlayerControl pc, bool force = false) - { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - - if (force || Alcohol <= ShowWarningWhenAlcoholIsBelow.GetInt()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 3, Alcohol); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - int id = reader.ReadPackedInt32(); - Shop = ShipStatus.Instance.AllVents.FirstOrDefault(x => x.Id == id); - break; - case 2: - Arrows = reader.ReadString(); - break; - case 3: - Alcohol = reader.ReadPackedInt32(); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != DadId || meeting || (seer.IsModClient() && !hud)) return string.Empty; - - var sb = new StringBuilder(); - - if (Vector2.Distance(seer.Pos(), Shop.transform.position) <= 2f) - { - float canBuyAmount = seer.GetAbilityUseLimit() / AlcoholCost.GetInt(); - sb.Append(string.Format(Translator.GetString("Dad.ShopSuffix"), canBuyAmount)); - } - - if (Alcohol <= ShowWarningWhenAlcoholIsBelow.GetInt()) - { - if (sb.Length > 0) sb.Append('\n'); - - sb.Append(string.Format(Translator.GetString("Dad.LowAlcoholSuffix"), Alcohol)); - } - - if (Arrows.Length > 0) - { - if (sb.Length > 0) sb.Append('\n'); - - sb.Append(Arrows); - } - - if (sb.Length > 0) sb.Append("\n\n"); - - sb.Append(string.Format(Translator.GetString("Dad.SelectedAbilitySuffix"), Translator.GetString($"Dad.Ability.{SelectedAbility}"))); - sb.Append('\n'); - sb.Append(Translator.GetString($"Dad.{SelectedAbility}.Description")); - sb.Append('\n'); - sb.Append(string.Format(Translator.GetString("Dad.ShopLocation"), Shop.name)); - sb.Append(""); - - return sb.ToString(); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + //Main.AllPlayerSpeed[playerId] *= -1; + DadId = playerId; + Alcohol = StartingAlcohol.GetInt(); + LastUpdate = Utils.TimeStamp; + Count = 0; + Shop = ShipStatus.Instance.AllVents.RandomElement(); + DoneTasks = false; + SelectedAbility = default; + MilkTimer = 315569520; + UsingAbilities = []; + SuperVisionTS = 0; + Arrows = string.Empty; + StartingSpeed = Main.AllPlayerSpeed[playerId]; + DrunkPlayers = []; + playerId.SetAbilityUseLimit(StartingMoney.GetInt()); + Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 1, Shop.Id); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsingAbilities.Contains(Ability.Sleep)) + { + opt.SetVision(false); + opt.BlackOut(true); + return; + } + + if (UsingAbilities.Contains(Ability.SuperVision)) + { + opt.SetVision(true); + opt.SetFloat(FloatOptionNames.CrewLightMod, 1.5f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.5f); + } + } + + public override void OnReportDeadBody() + { + AllDeadBodies = []; + Arrows = string.Empty; + Main.AllPlayerSpeed[DadId] = StartingSpeed; + + if (DrunkPlayers.Count > 0) + { + int chance = DrunkRoleIncorrectChance.GetInt(); + List allRoles = Enum.GetValues().Where(x => x.IsEnable() && !x.IsAdditionRole() && !HnSManager.AllHnSRoles.Contains(x) && !x.IsForOtherGameMode()).ToList(); + + foreach (byte id in DrunkPlayers) + { + PlayerControl pc = Utils.GetPlayerById(id); + if (pc == null) continue; + + CustomRoles selfRole = Main.PlayerStates[id].MainRole; + CustomRoles rndRole = allRoles.Without(selfRole).RandomElement(); + CustomRoles role = IRandom.Instance.Next(100) < chance ? rndRole : selfRole; + string msg = string.Format(Translator.GetString("Dad.DrunkRoleNotify"), Translator.GetString(role.ToString()).ToLower()); + int delay = 10 + IRandom.Instance.Next(12); + + LateTask.New(() => pc.RpcSendChat(msg), delay, $"Dad - DrunkRoleNotify - {pc.GetNameWithRole()}"); + } + + DrunkPlayers = []; + } + + Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 2, Arrows); + } + + public override void AfterMeetingTasks() + { + if (UsingAbilities.Contains(Ability.GoForMilk)) LateTask.New(() => Utils.GetPlayerById(DadId)?.TP(Pelican.GetBlackRoomPS()), 1f, log: false); + } + + public override void OnPet(PlayerControl pc) + { + SelectedAbility = (Ability)(((int)SelectedAbility + 1) % Enum.GetValues().Length); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (ventId == Shop.Id || UsingAbilities.Contains(Ability.Sleep)) return; + + if (AbilityAlcoholRequirement[SelectedAbility].GetInt() > Alcohol) + { + physics.myPlayer.Notify(Translator.GetString("Dad.AbilityNotAvailable")); + return; + } + + switch (SelectedAbility) + { + case Ability.GoForMilk when Alcohol >= 0: + LateTask.New(() => physics.RpcBootFromVent(ventId), 0.5f, log: false); + LateTask.New(() => physics.myPlayer.TP(Pelican.GetBlackRoomPS()), 1f, log: false); + Main.AllAlivePlayerControls.Do(x => x.Notify(Translator.GetString("Dad.GoForMilkNotify"), 10f)); + UsingAbilities.Add(SelectedAbility); + break; + case Ability.SuperVision when Alcohol >= 5: + SuperVisionTS = Utils.TimeStamp; + UsingAbilities.Add(SelectedAbility); + physics.myPlayer.MarkDirtySettings(); + break; + case Ability.Sniffing when Alcohol >= 10: + AllDeadBodies.Do(x => LocateArrow.Add(DadId, x)); + Arrows = LocateArrow.GetArrows(physics.myPlayer); + LocateArrow.RemoveAllTarget(DadId); + Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); + Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 2, Arrows); + break; + case Ability.Sleep when Alcohol >= 15: + Main.AllPlayerSpeed[DadId] = Main.MinSpeed; + UsingAbilities.Add(SelectedAbility); + physics.myPlayer.MarkDirtySettings(); + break; + case Ability.Rage when Alcohol >= 20: + UsingAbilities.Add(SelectedAbility); + break; + case Ability.GiveDrink when Alcohol >= 25: + Vector2 pos = physics.myPlayer.Pos(); + DrunkPlayers = Main.AllAlivePlayerControls.Without(physics.myPlayer).Where(x => Vector2.Distance(x.Pos(), pos) <= GivingDrinkRange.GetFloat()).Select(x => x.PlayerId).ToList(); + Utils.NotifyRoles(SpecifySeer: physics.myPlayer); + break; + case Ability.BecomeGodOfAlcohol when Alcohol >= 40: + Alcohol = 1; + UsingAbilities.Add(SelectedAbility); + break; + } + + Alcohol -= AbilityAlcoholDecreaseOptions[SelectedAbility].GetInt(); + NotifyIfNecessary(physics.myPlayer); + } + + public override void OnExitVent(PlayerControl pc, Vent vent) + { + if (vent.Id != Shop.Id) return; + + int cost = AlcoholCost.GetInt(); + int get = AlcoholIncreaseOnBeerPurchase.GetInt(); + + for (float money = pc.GetAbilityUseLimit(); money >= cost; money -= cost) + { + pc.SetAbilityUseLimit(money - cost); + Alcohol += get; + } + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 3, Alcohol); + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + pc.RpcIncreaseAbilityUseLimitBy(MoneyGainOnTaskComplete.GetInt()); + + if (completedTaskCount + 1 >= totalTaskCount) + { + pc.Data.RpcSetTasks(new(0)); + DoneTasks = true; + GameData.Instance.RecomputeTaskCounts(); + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + if (Count++ < 15) return; + + Count = 0; + + Vector2 pos = pc.Pos(); + if (UsingAbilities.Contains(Ability.Rage) && Main.AllAlivePlayerControls.FindFirst(x => Vector2.Distance(pos, x.Pos()) < 1.3f, out PlayerControl target) && pc.RpcCheckAndMurder(target)) UsingAbilities.Remove(Ability.Rage); + + bool notify = Vector2.Distance(pc.Pos(), Shop.transform.position) < 2f; + + long now = Utils.TimeStamp; + + if (now - LastUpdate < NormalAlcoholDecreaseFrequency.GetInt()) + { + if (notify) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + return; + } + + LastUpdate = now; + + if (UsingAbilities.Contains(Ability.GoForMilk)) + { + MilkTimer--; + + if (MilkTimer <= 0) + { + pc.TPToRandomVent(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Crewmate); + CustomWinnerHolder.WinnerIds.UnionWith(Main.AllPlayerControls.Where(x => x.Is(Team.Crewmate)).Select(x => x.PlayerId)); + } + } + + if (UsingAbilities.Contains(Ability.SuperVision) && SuperVisionTS + SuperVisionDuration.GetInt() <= now) + { + UsingAbilities.Remove(Ability.SuperVision); + SuperVisionTS = 0; + pc.MarkDirtySettings(); + } + + if (UsingAbilities.Contains(Ability.Sleep)) return; + + if (UsingAbilities.Contains(Ability.BecomeGodOfAlcohol)) + Alcohol += NormalAlcoholDecreaseValue.GetInt(); + else + Alcohol -= NormalAlcoholDecreaseValue.GetInt(); + + if (Alcohol <= 0) + { + pc.Suicide(); + return; + } + + NotifyIfNecessary(pc, notify); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + LateTask.New(() => + { + Alcohol -= AlcoholDecreaseOnKilled.GetInt(); + NotifyIfNecessary(target); + }, 5f, log: false); + + return false; + } + + public static bool OnVotedOut(byte id) + { + Dad dad = Instances.FirstOrDefault(d => d.DadId == id); + if (dad == null) return false; + + dad.Alcohol -= AlcoholDecreaseOnVotedOut.GetInt(); + dad.NotifyIfNecessary(Utils.GetPlayerById(dad.DadId)); + Logger.Info("Ejection prohibited", "Dad"); + return true; + } + + public static void OnAnyoneDeath(PlayerControl target) + { + AllDeadBodies.Add(target.Pos()); + } + + public static bool OnAnyoneCheckMurderStart(PlayerControl target) + { + Dad dad = Instances.FirstOrDefault(d => d.DadId == target.PlayerId); + if (dad == null) return false; + + return dad.UsingAbilities.Contains(Ability.Sleep); + } + + private void NotifyIfNecessary(PlayerControl pc, bool force = false) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + if (force || Alcohol <= ShowWarningWhenAlcoholIsBelow.GetInt()) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 3, Alcohol); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + int id = reader.ReadPackedInt32(); + Shop = ShipStatus.Instance.AllVents.FirstOrDefault(x => x.Id == id); + break; + case 2: + Arrows = reader.ReadString(); + break; + case 3: + Alcohol = reader.ReadPackedInt32(); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != DadId || meeting || (seer.IsModClient() && !hud)) return string.Empty; + + var sb = new StringBuilder(); + + if (Vector2.Distance(seer.Pos(), Shop.transform.position) <= 2f) + { + float canBuyAmount = seer.GetAbilityUseLimit() / AlcoholCost.GetInt(); + sb.Append(string.Format(Translator.GetString("Dad.ShopSuffix"), canBuyAmount)); + } + + if (Alcohol <= ShowWarningWhenAlcoholIsBelow.GetInt()) + { + if (sb.Length > 0) sb.Append('\n'); + + sb.Append(string.Format(Translator.GetString("Dad.LowAlcoholSuffix"), Alcohol)); + } + + if (Arrows.Length > 0) + { + if (sb.Length > 0) sb.Append('\n'); + + sb.Append(Arrows); + } + + if (sb.Length > 0) sb.Append("\n\n"); + + sb.Append(string.Format(Translator.GetString("Dad.SelectedAbilitySuffix"), Translator.GetString($"Dad.Ability.{SelectedAbility}"))); + sb.Append('\n'); + sb.Append(Translator.GetString($"Dad.{SelectedAbility}.Description")); + sb.Append('\n'); + sb.Append(string.Format(Translator.GetString("Dad.ShopLocation"), Shop.name)); + sb.Append(""); + + return sb.ToString(); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Demolitionist.cs b/Roles/Crewmate/Demolitionist.cs index 8fccb3ab7..51f824a3f 100644 --- a/Roles/Crewmate/Demolitionist.cs +++ b/Roles/Crewmate/Demolitionist.cs @@ -1,53 +1,52 @@ using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Demolitionist : RoleBase { - internal class Demolitionist : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(5550, TabGroup.CrewmateRoles, CustomRoles.Demolitionist); - public override void SetupCustomOption() + DemolitionistVentTime = new FloatOptionItem(5552, "DemolitionistVentTime", new(0f, 90f, 1f), 5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Demolitionist]) + .SetValueFormat(OptionFormat.Seconds); + + DemolitionistKillerDiesOnMeetingCall = new BooleanOptionItem(5553, "DemolitionistKillerDiesOnMeetingCall", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Demolitionist]); + } + + public override void Init() { } + + public override void Add(byte playerId) { } + + public static void OnDeath(PlayerControl killer, PlayerControl target) + { + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Demolitionist), Translator.GetString("OnDemolitionistDead"))); + killer.KillFlash(); + + LateTask.New(() => { - SetupRoleOptions(5550, TabGroup.CrewmateRoles, CustomRoles.Demolitionist); - - DemolitionistVentTime = new FloatOptionItem(5552, "DemolitionistVentTime", new(0f, 90f, 1f), 5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Demolitionist]) - .SetValueFormat(OptionFormat.Seconds); - - DemolitionistKillerDiesOnMeetingCall = new BooleanOptionItem(5553, "DemolitionistKillerDiesOnMeetingCall", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Demolitionist]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } - - public static void OnDeath(PlayerControl killer, PlayerControl target) - { - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Demolitionist), Translator.GetString("OnDemolitionistDead"))); - killer.KillFlash(); - - LateTask.New(() => + if (!killer.inVent && killer.PlayerId != target.PlayerId) { - if (!killer.inVent && killer.PlayerId != target.PlayerId) + if ((DemolitionistKillerDiesOnMeetingCall.GetBool() || GameStates.IsInTask) && killer.IsAlive()) { - if ((DemolitionistKillerDiesOnMeetingCall.GetBool() || GameStates.IsInTask) && killer.IsAlive()) - { - killer.Suicide(PlayerState.DeathReason.Demolished, target); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); - } + killer.Suicide(PlayerState.DeathReason.Demolished, target); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); } + } + else + { + if (killer.IsModClient()) + RPC.PlaySoundRPC(killer.PlayerId, Sounds.TaskComplete); else - { - if (killer.IsModClient()) - RPC.PlaySoundRPC(killer.PlayerId, Sounds.TaskComplete); - else - killer.RpcGuardAndKill(killer); + killer.RpcGuardAndKill(killer); - killer.SetKillCooldown(Main.AllPlayerKillCooldown[killer.PlayerId] - (DemolitionistVentTime.GetFloat() + 0.5f)); - } - }, DemolitionistVentTime.GetFloat() + 0.5f, "DemolitionistCheck"); - } + killer.SetKillCooldown(Main.AllPlayerKillCooldown[killer.PlayerId] - (DemolitionistVentTime.GetFloat() + 0.5f)); + } + }, DemolitionistVentTime.GetFloat() + 0.5f, "DemolitionistCheck"); } } \ No newline at end of file diff --git a/Roles/Crewmate/Deputy.cs b/Roles/Crewmate/Deputy.cs index 7d08dc339..df8bbdc7e 100644 --- a/Roles/Crewmate/Deputy.cs +++ b/Roles/Crewmate/Deputy.cs @@ -3,100 +3,99 @@ using AmongUs.GameOptions; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Deputy : RoleBase { - public class Deputy : RoleBase + private const int Id = 6500; + private static List PlayerIdList = []; + + public static OptionItem HandcuffCooldown; + public static OptionItem HandcuffMax; + public static OptionItem DeputyHandcuffCDForTarget; + private static OptionItem DeputyHandcuffDelay; + public static OptionItem UsePet; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 6500; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deputy); - public static OptionItem HandcuffCooldown; - public static OptionItem HandcuffMax; - public static OptionItem DeputyHandcuffCDForTarget; - private static OptionItem DeputyHandcuffDelay; - public static OptionItem UsePet; + HandcuffCooldown = new FloatOptionItem(Id + 10, "DeputyHandcuffCooldown", new(0f, 60f, 2.5f), 17.5f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + DeputyHandcuffCDForTarget = new FloatOptionItem(Id + 14, "DeputyHandcuffCDForTarget", new(0f, 180f, 2.5f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + HandcuffMax = new IntegerOptionItem(Id + 12, "DeputyHandcuffMax", new(1, 20, 1), 4, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) + .SetValueFormat(OptionFormat.Times); + + DeputyHandcuffDelay = new IntegerOptionItem(Id + 11, "DeputyHandcuffDelay", new(0, 20, 1), 5, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) + .SetValueFormat(OptionFormat.Seconds); + + UsePet = CreatePetUseSetting(Id + 13, CustomRoles.Deputy); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(HandcuffMax.GetInt()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = HandcuffCooldown.GetFloat(); + } + + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead && player.GetAbilityUseLimit() >= 1; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return false; + + if (target != null && !target.Is(CustomRoles.Deputy)) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Deputy); + killer.RpcRemoveAbilityUse(); - HandcuffCooldown = new FloatOptionItem(Id + 10, "DeputyHandcuffCooldown", new(0f, 60f, 2.5f), 17.5f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) - .SetValueFormat(OptionFormat.Seconds); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("DeputyHandcuffedPlayer"))); - DeputyHandcuffCDForTarget = new FloatOptionItem(Id + 14, "DeputyHandcuffCDForTarget", new(0f, 180f, 2.5f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) - .SetValueFormat(OptionFormat.Seconds); - - HandcuffMax = new IntegerOptionItem(Id + 12, "DeputyHandcuffMax", new(1, 20, 1), 4, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) - .SetValueFormat(OptionFormat.Times); - - DeputyHandcuffDelay = new IntegerOptionItem(Id + 11, "DeputyHandcuffDelay", new(0, 20, 1), 5, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Deputy]) - .SetValueFormat(OptionFormat.Seconds); - - UsePet = CreatePetUseSetting(Id + 13, CustomRoles.Deputy); - } - - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(HandcuffMax.GetInt()); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = HandcuffCooldown.GetFloat(); - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead && player.GetAbilityUseLimit() >= 1; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return false; - - if (target != null && !target.Is(CustomRoles.Deputy)) + LateTask.New(() => { - killer.RpcRemoveAbilityUse(); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("DeputyHandcuffedPlayer"))); - - LateTask.New(() => + if (GameStates.IsInTask) { - if (GameStates.IsInTask) - { - target.SetKillCooldown(DeputyHandcuffCDForTarget.GetFloat()); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("HandcuffedByDeputy"))); - if (target.IsModClient()) target.RpcResetAbilityCooldown(); + target.SetKillCooldown(DeputyHandcuffCDForTarget.GetFloat()); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("HandcuffedByDeputy"))); + if (target.IsModClient()) target.RpcResetAbilityCooldown(); - if (!target.IsModClient()) target.RpcGuardAndKill(target); - } - }, DeputyHandcuffDelay.GetInt(), "DeputyHandcuffDelay"); + if (!target.IsModClient()) target.RpcGuardAndKill(target); + } + }, DeputyHandcuffDelay.GetInt(), "DeputyHandcuffDelay"); - killer.SetKillCooldown(); + killer.SetKillCooldown(); - return false; - } - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("DeputyInvalidTarget"))); return false; } + + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Deputy), GetString("DeputyInvalidTarget"))); + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/Detective.cs b/Roles/Crewmate/Detective.cs index 394ca401d..9b7ad0818 100644 --- a/Roles/Crewmate/Detective.cs +++ b/Roles/Crewmate/Detective.cs @@ -2,67 +2,66 @@ using System.Collections.Generic; using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Detective : RoleBase { - internal class Detective : RoleBase + public static OptionItem DetectiveCanknowKiller; + public static OptionItem DetectiveCanknowDeathReason; + public static OptionItem DetectiveCanknowAddons; + public static OptionItem DetectiveCanknowKillTime; + + public static Dictionary DetectiveNotify = []; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public static OptionItem DetectiveCanknowKiller; - public static OptionItem DetectiveCanknowDeathReason; - public static OptionItem DetectiveCanknowAddons; - public static OptionItem DetectiveCanknowKillTime; + Options.SetupRoleOptions(6600, TabGroup.CrewmateRoles, CustomRoles.Detective); - public static Dictionary DetectiveNotify = []; + DetectiveCanknowKiller = new BooleanOptionItem(6610, "DetectiveCanknowKiller", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); - public override bool IsEnable => false; + DetectiveCanknowDeathReason = new BooleanOptionItem(6611, "DetectiveCanknowDeathReason", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); - public override void SetupCustomOption() + DetectiveCanknowAddons = new BooleanOptionItem(6612, "DetectiveCanknowAddons", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); + + DetectiveCanknowKillTime = new BooleanOptionItem(6613, "DetectiveCanknowKillTime", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); + } + + public override void Init() { } + + public override void Add(byte playerId) { } + + public static void OnReportDeadBody(PlayerControl player, PlayerControl tpc) + { + string msg = string.Format(Translator.GetString("DetectiveNoticeVictim"), tpc.GetRealName(), tpc.GetCustomRole().ToColoredString()); + + if (DetectiveCanknowKiller.GetBool()) { - Options.SetupRoleOptions(6600, TabGroup.CrewmateRoles, CustomRoles.Detective); + PlayerControl realKiller = tpc.GetRealKiller(); - DetectiveCanknowKiller = new BooleanOptionItem(6610, "DetectiveCanknowKiller", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); - - DetectiveCanknowDeathReason = new BooleanOptionItem(6611, "DetectiveCanknowDeathReason", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); - - DetectiveCanknowAddons = new BooleanOptionItem(6612, "DetectiveCanknowAddons", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); - - DetectiveCanknowKillTime = new BooleanOptionItem(6613, "DetectiveCanknowKillTime", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Detective]); + if (realKiller == null) + msg += ";" + Translator.GetString("DetectiveNoticeKillerNotFound"); + else + msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKiller"), realKiller.GetCustomRole().ToColoredString()); } - public override void Init() { } + if (DetectiveCanknowDeathReason.GetBool()) msg += ";" + string.Format(Translator.GetString("DetectiveNoticeDeathReason"), Translator.GetString($"DeathReason.{Main.PlayerStates[tpc.PlayerId].deathReason}")); - public override void Add(byte playerId) { } + if (DetectiveCanknowAddons.GetBool()) msg += ";" + string.Format(Translator.GetString("DetectiveNoticeAddons"), string.Join(", ", tpc.GetCustomSubRoles().Select(x => x.ToColoredString()))); - public static void OnReportDeadBody(PlayerControl player, PlayerControl tpc) + if (DetectiveCanknowKillTime.GetBool()) { - string msg = string.Format(Translator.GetString("DetectiveNoticeVictim"), tpc.GetRealName(), tpc.GetCustomRole().ToColoredString()); - - if (DetectiveCanknowKiller.GetBool()) - { - PlayerControl realKiller = tpc.GetRealKiller(); - - if (realKiller == null) - msg += ";" + Translator.GetString("DetectiveNoticeKillerNotFound"); - else - msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKiller"), realKiller.GetCustomRole().ToColoredString()); - } - - if (DetectiveCanknowDeathReason.GetBool()) msg += ";" + string.Format(Translator.GetString("DetectiveNoticeDeathReason"), Translator.GetString($"DeathReason.{Main.PlayerStates[tpc.PlayerId].deathReason}")); - - if (DetectiveCanknowAddons.GetBool()) msg += ";" + string.Format(Translator.GetString("DetectiveNoticeAddons"), string.Join(", ", tpc.GetCustomSubRoles().Select(x => x.ToColoredString()))); - - if (DetectiveCanknowKillTime.GetBool()) - { - DateTime deathTimeStamp = Main.PlayerStates[tpc.PlayerId].RealKiller.TimeStamp; - DateTime now = DateTime.Now; - double timeSpanSeconds = (now - deathTimeStamp).TotalSeconds; - msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKillTime"), (int)timeSpanSeconds); - } - - DetectiveNotify.Add(player.PlayerId, msg); + DateTime deathTimeStamp = Main.PlayerStates[tpc.PlayerId].RealKiller.TimeStamp; + DateTime now = DateTime.Now; + double timeSpanSeconds = (now - deathTimeStamp).TotalSeconds; + msg += ";" + string.Format(Translator.GetString("DetectiveNoticeKillTime"), (int)timeSpanSeconds); } + + DetectiveNotify.Add(player.PlayerId, msg); } } \ No newline at end of file diff --git a/Roles/Crewmate/Detour.cs b/Roles/Crewmate/Detour.cs index bb1e2f5d9..7d03b1395 100644 --- a/Roles/Crewmate/Detour.cs +++ b/Roles/Crewmate/Detour.cs @@ -1,20 +1,19 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Detour : RoleBase { - internal class Detour : RoleBase + public static int TotalRedirections; + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public static int TotalRedirections; - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(5590, TabGroup.CrewmateRoles, CustomRoles.Detour); - } - - public override void Init() - { - TotalRedirections = 0; - } - - public override void Add(byte playerId) { } + Options.SetupRoleOptions(5590, TabGroup.CrewmateRoles, CustomRoles.Detour); } + + public override void Init() + { + TotalRedirections = 0; + } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Dictator.cs b/Roles/Crewmate/Dictator.cs index f7ab5a076..5f2edf46c 100644 --- a/Roles/Crewmate/Dictator.cs +++ b/Roles/Crewmate/Dictator.cs @@ -1,16 +1,15 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Dictator : RoleBase { - internal class Dictator : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(9100, TabGroup.CrewmateRoles, CustomRoles.Dictator); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + Options.SetupRoleOptions(9100, TabGroup.CrewmateRoles, CustomRoles.Dictator); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Divinator.cs b/Roles/Crewmate/Divinator.cs index 914f4111a..b897ff33d 100644 --- a/Roles/Crewmate/Divinator.cs +++ b/Roles/Crewmate/Divinator.cs @@ -5,124 +5,123 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Divinator : RoleBase { - public class Divinator : RoleBase + private const int Id = 6700; + private const int RolesPerCategory = 5; + private static List PlayerIdList = []; + + public static OptionItem CheckLimitOpt; + public static OptionItem AccurateCheckMode; + public static OptionItem HideVote; + public static OptionItem ShowSpecificRole; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem CancelVote; + + public static readonly List DidVote = []; + + private static Dictionary> AllPlayerRoleList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 6700; - private const int RolesPerCategory = 5; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Divinator); - public static OptionItem CheckLimitOpt; - public static OptionItem AccurateCheckMode; - public static OptionItem HideVote; - public static OptionItem ShowSpecificRole; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem CancelVote; + CheckLimitOpt = new IntegerOptionItem(Id + 10, "DivinatorSkillLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]) + .SetValueFormat(OptionFormat.Times); - public static readonly List DidVote = []; + AccurateCheckMode = new BooleanOptionItem(Id + 12, "AccurateCheckMode", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]); - private static Dictionary> AllPlayerRoleList = []; + ShowSpecificRole = new BooleanOptionItem(Id + 13, "ShowSpecificRole", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]); - public override bool IsEnable => PlayerIdList.Count > 0; + HideVote = new BooleanOptionItem(Id + 14, "DivinatorHideVote", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]); - public override void SetupCustomOption() + AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 15, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 16, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]) + .SetValueFormat(OptionFormat.Times); + + CancelVote = CreateVoteCancellingUseSetting(Id + 11, CustomRoles.Divinator, TabGroup.CrewmateRoles); + OverrideTasksData.Create(Id + 21, TabGroup.CrewmateRoles, CustomRoles.Divinator); + } + + public override void Init() + { + PlayerIdList = []; + AllPlayerRoleList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(CheckLimitOpt.GetInt()); + + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Divinator); + PlayerControl[] players = Main.AllPlayerControls; + int rolesNeeded = players.Length * (RolesPerCategory - 1); - CheckLimitOpt = new IntegerOptionItem(Id + 10, "DivinatorSkillLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]) - .SetValueFormat(OptionFormat.Times); + (List RoleList, PlayerControl Player)[] roleList = Enum.GetValues() + .Where(x => !x.IsVanilla() && !x.IsAdditionRole() && x is not CustomRoles.GM and not CustomRoles.Convict and not CustomRoles.NotAssigned && !x.IsForOtherGameMode() && !CustomRoleSelector.RoleResult.ContainsValue(x)) + .OrderBy(x => x.IsEnable() ? IRandom.Instance.Next(10) : IRandom.Instance.Next(10, 100)) + .Take(rolesNeeded) + .Chunk(RolesPerCategory - 1) + .Zip(players, (Array, Player) => (RoleList: Array.ToList(), Player)) + .ToArray(); - AccurateCheckMode = new BooleanOptionItem(Id + 12, "AccurateCheckMode", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]); + roleList.Do(x => x.RoleList.Insert(IRandom.Instance.Next(x.RoleList.Count), x.Player.GetCustomRole())); + AllPlayerRoleList = roleList.ToDictionary(x => x.Player.PlayerId, x => x.RoleList.ToHashSet()); - ShowSpecificRole = new BooleanOptionItem(Id + 13, "ShowSpecificRole", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]); + Logger.Info(string.Join(" ---- ", AllPlayerRoleList.Select(x => $"ID {x.Key} ({x.Key.GetPlayer().GetNameWithRole()}): {string.Join(", ", x.Value)}")), "Divinator Roles"); + }, 8f, log: false); + } - HideVote = new BooleanOptionItem(Id + 14, "DivinatorHideVote", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]); + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null) return false; - AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 15, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]) - .SetValueFormat(OptionFormat.Times); + if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 16, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Divinator]) - .SetValueFormat(OptionFormat.Times); + DidVote.Add(player.PlayerId); - CancelVote = CreateVoteCancellingUseSetting(Id + 11, CustomRoles.Divinator, TabGroup.CrewmateRoles); - OverrideTasksData.Create(Id + 21, TabGroup.CrewmateRoles, CustomRoles.Divinator); + if (player.GetAbilityUseLimit() < 1) + { + Utils.SendMessage(GetString("DivinatorCheckReachLimit"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Divinator), GetString("DivinatorCheckMsgTitle"))); + return false; } - public override void Init() + player.RpcRemoveAbilityUse(); + + if (player.PlayerId == target.PlayerId) { - PlayerIdList = []; - AllPlayerRoleList = []; + Utils.SendMessage(GetString("DivinatorCheckSelfMsg") + "\n\n" + string.Format(GetString("DivinatorCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Divinator), GetString("DivinatorCheckMsgTitle"))); + return false; } - public override void Add(byte playerId) + string msg; + + if ((player.AllTasksCompleted() || AccurateCheckMode.GetBool()) && ShowSpecificRole.GetBool()) + msg = string.Format(GetString("DivinatorCheck.TaskDone"), target.GetRealName(), target.GetCustomRole().ToColoredString()); + else { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(CheckLimitOpt.GetInt()); - - LateTask.New(() => - { - PlayerControl[] players = Main.AllPlayerControls; - int rolesNeeded = players.Length * (RolesPerCategory - 1); - - (List RoleList, PlayerControl Player)[] roleList = Enum.GetValues() - .Where(x => !x.IsVanilla() && !x.IsAdditionRole() && x is not CustomRoles.GM and not CustomRoles.Convict and not CustomRoles.NotAssigned && !x.IsForOtherGameMode() && !CustomRoleSelector.RoleResult.ContainsValue(x)) - .OrderBy(x => x.IsEnable() ? IRandom.Instance.Next(10) : IRandom.Instance.Next(10, 100)) - .Take(rolesNeeded) - .Chunk(RolesPerCategory - 1) - .Zip(players, (Array, Player) => (RoleList: Array.ToList(), Player)) - .ToArray(); - - roleList.Do(x => x.RoleList.Insert(IRandom.Instance.Next(x.RoleList.Count), x.Player.GetCustomRole())); - AllPlayerRoleList = roleList.ToDictionary(x => x.Player.PlayerId, x => x.RoleList.ToHashSet()); - - Logger.Info(string.Join(" ---- ", AllPlayerRoleList.Select(x => $"ID {x.Key} ({x.Key.GetPlayer().GetNameWithRole()}): {string.Join(", ", x.Value)}")), "Divinator Roles"); - }, 8f, log: false); + string roles = string.Join(", ", AllPlayerRoleList[target.PlayerId].Select(x => x.ToColoredString())); + msg = string.Format(GetString("DivinatorCheckResult"), target.GetRealName(), roles); } - public override bool OnVote(PlayerControl player, PlayerControl target) - { - if (player == null || target == null) return false; + Utils.SendMessage(GetString("DivinatorCheck") + "\n" + msg + "\n\n" + string.Format(GetString("DivinatorCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Divinator), GetString("DivinatorCheckMsgTitle"))); - if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - - DidVote.Add(player.PlayerId); - - if (player.GetAbilityUseLimit() < 1) - { - Utils.SendMessage(GetString("DivinatorCheckReachLimit"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Divinator), GetString("DivinatorCheckMsgTitle"))); - return false; - } - - player.RpcRemoveAbilityUse(); - - if (player.PlayerId == target.PlayerId) - { - Utils.SendMessage(GetString("DivinatorCheckSelfMsg") + "\n\n" + string.Format(GetString("DivinatorCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Divinator), GetString("DivinatorCheckMsgTitle"))); - return false; - } - - string msg; - - if ((player.AllTasksCompleted() || AccurateCheckMode.GetBool()) && ShowSpecificRole.GetBool()) - msg = string.Format(GetString("DivinatorCheck.TaskDone"), target.GetRealName(), target.GetCustomRole().ToColoredString()); - else - { - string roles = string.Join(", ", AllPlayerRoleList[target.PlayerId].Select(x => x.ToColoredString())); - msg = string.Format(GetString("DivinatorCheckResult"), target.GetRealName(), roles); - } - - Utils.SendMessage(GetString("DivinatorCheck") + "\n" + msg + "\n\n" + string.Format(GetString("DivinatorCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Divinator), GetString("DivinatorCheckMsgTitle"))); - - Main.DontCancelVoteList.Add(player.PlayerId); - return true; - } + Main.DontCancelVoteList.Add(player.PlayerId); + return true; } } \ No newline at end of file diff --git a/Roles/Crewmate/Doctor.cs b/Roles/Crewmate/Doctor.cs index 6753d5ab5..1dbbd736d 100644 --- a/Roles/Crewmate/Doctor.cs +++ b/Roles/Crewmate/Doctor.cs @@ -1,38 +1,37 @@ using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Doctor : RoleBase { - internal class Doctor : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(5600, TabGroup.CrewmateRoles, CustomRoles.Doctor); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(5600, TabGroup.CrewmateRoles, CustomRoles.Doctor); + Options.DoctorTaskCompletedBatteryCharge = new FloatOptionItem(5610, "DoctorTaskCompletedBatteryCharge", new(0f, 300f, 1f), 90f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doctor]) + .SetValueFormat(OptionFormat.Seconds); - Options.DoctorTaskCompletedBatteryCharge = new FloatOptionItem(5610, "DoctorTaskCompletedBatteryCharge", new(0f, 300f, 1f), 90f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doctor]) - .SetValueFormat(OptionFormat.Seconds); + Options.DoctorVisibleToEveryone = new BooleanOptionItem(5611, "DoctorVisibleToEveryone", false, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doctor]); + } - Options.DoctorVisibleToEveryone = new BooleanOptionItem(5611, "DoctorVisibleToEveryone", false, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doctor]); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ScientistCooldown = 0f; - AURoleOptions.ScientistBatteryCharge = Options.DoctorTaskCompletedBatteryCharge.GetFloat(); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ScientistCooldown = 0f; + AURoleOptions.ScientistBatteryCharge = Options.DoctorTaskCompletedBatteryCharge.GetFloat(); } } \ No newline at end of file diff --git a/Roles/Crewmate/DonutDelivery.cs b/Roles/Crewmate/DonutDelivery.cs index 4503a1fb7..1f4f31ee7 100644 --- a/Roles/Crewmate/DonutDelivery.cs +++ b/Roles/Crewmate/DonutDelivery.cs @@ -5,144 +5,143 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class DonutDelivery : RoleBase { - public class DonutDelivery : RoleBase + private const int Id = 642700; + private static List Instances = []; + private static Dictionary StartingSpeed = []; + + private static OptionItem CD; + private static OptionItem UseLimit; + private static OptionItem SpeedEffect; + private static OptionItem SEDelay; + private static OptionItem SEAmount; + private static OptionItem SEDuration; + private static OptionItem SEEvilsGetDecreased; + private static OptionItem SEEvilsDecreaseAmount; + public static OptionItem UsePet; + + private byte DonutDeliveryId; + private HashSet Players = []; + + public override bool IsEnable => Instances.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642700; - private static List Instances = []; - private static Dictionary StartingSpeed = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.DonutDelivery); - private static OptionItem CD; - private static OptionItem UseLimit; - private static OptionItem SpeedEffect; - private static OptionItem SEDelay; - private static OptionItem SEAmount; - private static OptionItem SEDuration; - private static OptionItem SEEvilsGetDecreased; - private static OptionItem SEEvilsDecreaseAmount; - public static OptionItem UsePet; + CD = new FloatOptionItem(Id + 10, "DonutDeliverCD", new(2.5f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]) + .SetValueFormat(OptionFormat.Seconds); - private byte DonutDeliveryId; - private HashSet Players = []; + UseLimit = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(1, 20, 1), 5, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => Instances.Count > 0; + SpeedEffect = new BooleanOptionItem(Id + 14, "DonutDeliverSpeedEffect", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]); - public override void SetupCustomOption() + SEDelay = new FloatOptionItem(Id + 15, "DonutDeliverSEDelay", new(0f, 30f, 0.5f), 3f, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect) + .SetValueFormat(OptionFormat.Seconds); + + SEAmount = new FloatOptionItem(Id + 16, "DonutDeliverSEAmount", new(0.05f, 3f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect); + + SEDuration = new FloatOptionItem(Id + 17, "DonutDeliverSEDuration", new(0.5f, 60f, 0.5f), 5f, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect) + .SetValueFormat(OptionFormat.Seconds); + + SEEvilsGetDecreased = new BooleanOptionItem(Id + 18, "DonutDeliverSEEvilsGetDecreased", true, TabGroup.CrewmateRoles) + .SetParent(SpeedEffect); + + SEEvilsDecreaseAmount = new FloatOptionItem(Id + 19, "DonutDeliverSEEvilsDecreaseAmount", new(0.1f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles) + .SetParent(SEEvilsGetDecreased); + + UsePet = CreatePetUseSetting(Id + 13, CustomRoles.DonutDelivery); + } + + public override void Init() + { + Instances = []; + StartingSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); + } + + public override void Add(byte playerId) + { + Instances.Add(this); + Players = []; + DonutDeliveryId = playerId; + playerId.SetAbilityUseLimit(UseLimit.GetInt()); + } + + public override void SetKillCooldown(byte playerId) + { + Main.AllPlayerKillCooldown[playerId] = playerId.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() >= 1; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; + + killer.RpcRemoveAbilityUse(); + + int num1 = IRandom.Instance.Next(0, 19); + killer.Notify(GetString($"DonutDelivered-{num1}")); + RandomNotifyTarget(target); + Players.Add(target.PlayerId); + + killer.SetKillCooldown(); + + if (SpeedEffect.GetBool()) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.DonutDelivery); - - CD = new FloatOptionItem(Id + 10, "DonutDeliverCD", new(2.5f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimit = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(1, 20, 1), 5, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]) - .SetValueFormat(OptionFormat.Times); - - SpeedEffect = new BooleanOptionItem(Id + 14, "DonutDeliverSpeedEffect", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DonutDelivery]); - - SEDelay = new FloatOptionItem(Id + 15, "DonutDeliverSEDelay", new(0f, 30f, 0.5f), 3f, TabGroup.CrewmateRoles) - .SetParent(SpeedEffect) - .SetValueFormat(OptionFormat.Seconds); - - SEAmount = new FloatOptionItem(Id + 16, "DonutDeliverSEAmount", new(0.05f, 3f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(SpeedEffect); - - SEDuration = new FloatOptionItem(Id + 17, "DonutDeliverSEDuration", new(0.5f, 60f, 0.5f), 5f, TabGroup.CrewmateRoles) - .SetParent(SpeedEffect) - .SetValueFormat(OptionFormat.Seconds); - - SEEvilsGetDecreased = new BooleanOptionItem(Id + 18, "DonutDeliverSEEvilsGetDecreased", true, TabGroup.CrewmateRoles) - .SetParent(SpeedEffect); - - SEEvilsDecreaseAmount = new FloatOptionItem(Id + 19, "DonutDeliverSEEvilsDecreaseAmount", new(0.1f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles) - .SetParent(SEEvilsGetDecreased); - - UsePet = CreatePetUseSetting(Id + 13, CustomRoles.DonutDelivery); - } - - public override void Init() - { - Instances = []; - StartingSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); - } - - public override void Add(byte playerId) - { - Instances.Add(this); - Players = []; - DonutDeliveryId = playerId; - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } - - public override void SetKillCooldown(byte playerId) - { - Main.AllPlayerKillCooldown[playerId] = playerId.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() >= 1; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; - - killer.RpcRemoveAbilityUse(); - - int num1 = IRandom.Instance.Next(0, 19); - killer.Notify(GetString($"DonutDelivered-{num1}")); - RandomNotifyTarget(target); - Players.Add(target.PlayerId); - - killer.SetKillCooldown(); - - if (SpeedEffect.GetBool()) + LateTask.New(() => { + if (target.IsCrewmate() || target.GetCustomRole().IsNonNK() || !SEEvilsGetDecreased.GetBool()) + Main.AllPlayerSpeed[target.PlayerId] += SEAmount.GetFloat(); + else + Main.AllPlayerSpeed[target.PlayerId] -= SEEvilsDecreaseAmount.GetFloat(); + + target.MarkDirtySettings(); + LateTask.New(() => { - if (target.IsCrewmate() || target.GetCustomRole().IsNonNK() || !SEEvilsGetDecreased.GetBool()) - Main.AllPlayerSpeed[target.PlayerId] += SEAmount.GetFloat(); - else - Main.AllPlayerSpeed[target.PlayerId] -= SEEvilsDecreaseAmount.GetFloat(); - + Main.AllPlayerSpeed[target.PlayerId] = StartingSpeed[target.PlayerId]; target.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[target.PlayerId] = StartingSpeed[target.PlayerId]; - target.MarkDirtySettings(); - }, SEDuration.GetFloat(), log: false); - }, SEDelay.GetFloat(), log: false); - } - - if (target.IsLocalPlayer()) - Achievements.Type.Delicious.Complete(); - - return false; + }, SEDuration.GetFloat(), log: false); + }, SEDelay.GetFloat(), log: false); } - public static void RandomNotifyTarget(PlayerControl target) - { - int num2 = IRandom.Instance.Next(0, 6); - target.Notify(GetString($"DonutGot-{num2}")); - } + if (target.IsLocalPlayer()) + Achievements.Type.Delicious.Complete(); - public static bool IsUnguessable(PlayerControl guesser, PlayerControl target) - { - foreach (DonutDelivery instance in Instances) - if (instance.DonutDeliveryId == target.PlayerId && instance.Players.Contains(guesser.PlayerId)) - return true; + return false; + } - return false; - } + public static void RandomNotifyTarget(PlayerControl target) + { + int num2 = IRandom.Instance.Next(0, 6); + target.Notify(GetString($"DonutGot-{num2}")); + } + + public static bool IsUnguessable(PlayerControl guesser, PlayerControl target) + { + foreach (DonutDelivery instance in Instances) + if (instance.DonutDeliveryId == target.PlayerId && instance.Players.Contains(guesser.PlayerId)) + return true; + + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/Doormaster.cs b/Roles/Crewmate/Doormaster.cs index bdd8be3d4..97539f8b8 100644 --- a/Roles/Crewmate/Doormaster.cs +++ b/Roles/Crewmate/Doormaster.cs @@ -2,88 +2,87 @@ using System.Collections.Generic; using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; + +public class Doormaster : RoleBase { - using static Options; + private const int Id = 640000; + private static List PlayerIdList = []; - public class Doormaster : RoleBase + public static OptionItem VentCooldown; + public static OptionItem UseLimitOpt; + public static OptionItem DoormasterAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640000; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Doormaster); - public static OptionItem VentCooldown; - public static OptionItem UseLimitOpt; - public static OptionItem DoormasterAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + VentCooldown = new FloatOptionItem(Id + 10, "VentCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + DoormasterAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnPet(PlayerControl pc) + { + OpenDoors(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + OpenDoors(pc); + } + + private static void OpenDoors(PlayerControl pc) + { + if (pc == null) return; + + if (!pc.Is(CustomRoles.Doormaster)) return; + + if (pc.GetAbilityUseLimit() >= 1) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Doormaster); - - VentCooldown = new FloatOptionItem(Id + 10, "VentCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) - .SetValueFormat(OptionFormat.Times); - - DoormasterAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doormaster]) - .SetValueFormat(OptionFormat.Times); + pc.RpcRemoveAbilityUse(); + DoorsReset.OpenAllDoors(); } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnPet(PlayerControl pc) - { - OpenDoors(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - OpenDoors(pc); - } - - private static void OpenDoors(PlayerControl pc) - { - if (pc == null) return; - - if (!pc.Is(CustomRoles.Doormaster)) return; - - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - DoorsReset.OpenAllDoors(); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/DoubleAgent.cs b/Roles/Crewmate/DoubleAgent.cs index 6620f8450..985b54f81 100644 --- a/Roles/Crewmate/DoubleAgent.cs +++ b/Roles/Crewmate/DoubleAgent.cs @@ -2,31 +2,30 @@ using System.Collections.Generic; using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class DoubleAgent : RoleBase { - public class DoubleAgent : RoleBase + public static bool On; + + public static Dictionary ShownRoles = []; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(645175); + } - public static Dictionary ShownRoles = []; + public override void Init() + { + On = false; + ShownRoles = []; + } - public override bool IsEnable => On; - - public override void SetupCustomOption() - { - StartSetup(645175); - } - - public override void Init() - { - On = false; - ShownRoles = []; - } - - public override void Add(byte playerId) - { - On = true; - ShownRoles[playerId] = Enum.GetValues().Where(x => x is not CustomRoles.DoubleAgent and not CustomRoles.LovingImpostor && x.IsImpostor() && !x.IsVanilla() && !x.IsAdditionRole() && x.GetMode() != 0).RandomElement(); - } + public override void Add(byte playerId) + { + On = true; + ShownRoles[playerId] = Enum.GetValues().Where(x => x is not CustomRoles.DoubleAgent and not CustomRoles.LovingImpostor && x.IsImpostor() && !x.IsVanilla() && !x.IsAdditionRole() && x.GetMode() != 0).RandomElement(); } } \ No newline at end of file diff --git a/Roles/Crewmate/DovesOfNeace.cs b/Roles/Crewmate/DovesOfNeace.cs index 5a01717b0..b7f6ad39f 100644 --- a/Roles/Crewmate/DovesOfNeace.cs +++ b/Roles/Crewmate/DovesOfNeace.cs @@ -4,102 +4,101 @@ using EHR.Impostor; using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class DovesOfNeace : RoleBase { - internal class DovesOfNeace : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + SetupRoleOptions(7700, TabGroup.CrewmateRoles, CustomRoles.DovesOfNeace); - public override void SetupCustomOption() + DovesOfNeaceCooldown = new FloatOptionItem(7710, "DovesOfNeaceCooldown", new(0f, 180f, 1f), 7f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) + .SetValueFormat(OptionFormat.Seconds); + + DovesOfNeaceMaxOfUseage = new IntegerOptionItem(7711, "DovesOfNeaceMaxOfUseage", new(0, 180, 1), 0, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) + .SetValueFormat(OptionFormat.Times); + + DovesOfNeaceAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(7712, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) + .SetValueFormat(OptionFormat.Times); + + DovesOfNeaceAbilityChargesWhenFinishedTasks = new FloatOptionItem(7713, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) + .SetValueFormat(OptionFormat.Times); + } + + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(DovesOfNeaceMaxOfUseage.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = DovesOfNeaceCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("DovesOfNeaceVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("DovesOfNeaceVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + ResetCooldowns(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + ResetCooldowns(pc); + } + + private static void ResetCooldowns(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1) { - SetupRoleOptions(7700, TabGroup.CrewmateRoles, CustomRoles.DovesOfNeace); - - DovesOfNeaceCooldown = new FloatOptionItem(7710, "DovesOfNeaceCooldown", new(0f, 180f, 1f), 7f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) - .SetValueFormat(OptionFormat.Seconds); - - DovesOfNeaceMaxOfUseage = new IntegerOptionItem(7711, "DovesOfNeaceMaxOfUseage", new(0, 180, 1), 0, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) - .SetValueFormat(OptionFormat.Times); - - DovesOfNeaceAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(7712, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) - .SetValueFormat(OptionFormat.Times); - - DovesOfNeaceAbilityChargesWhenFinishedTasks = new FloatOptionItem(7713, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.DovesOfNeace]) - .SetValueFormat(OptionFormat.Times); + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + return; } + pc.RpcRemoveAbilityUse(); + bool isMadMate = pc.Is(CustomRoles.Madmate); - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(DovesOfNeaceMaxOfUseage.GetInt()); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = DovesOfNeaceCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("DovesOfNeaceVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("DovesOfNeaceVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - ResetCooldowns(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - ResetCooldowns(pc); - } - - private static void ResetCooldowns(PlayerControl pc) - { - if (pc.GetAbilityUseLimit() < 1) + Main.AllAlivePlayerControls + .Where(x => isMadMate ? x.CanUseKillButton() && x.IsCrewmate() : x.CanUseKillButton()) + .Do(x => { - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - return; - } + x.RPCPlayCustomSound("Dove"); + x.ResetKillCooldown(); + x.SetKillCooldown(); + if (Main.PlayerStates[x.PlayerId].Role is SerialKiller sk) sk.OnReportDeadBody(); - pc.RpcRemoveAbilityUse(); - bool isMadMate = pc.Is(CustomRoles.Madmate); + x.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DovesOfNeace), Translator.GetString("DovesOfNeaceSkillNotify"))); + }); - Main.AllAlivePlayerControls - .Where(x => isMadMate ? x.CanUseKillButton() && x.IsCrewmate() : x.CanUseKillButton()) - .Do(x => - { - x.RPCPlayCustomSound("Dove"); - x.ResetKillCooldown(); - x.SetKillCooldown(); - if (Main.PlayerStates[x.PlayerId].Role is SerialKiller sk) sk.OnReportDeadBody(); + pc.RPCPlayCustomSound("Dove"); + pc.Notify(string.Format(Translator.GetString("DovesOfNeaceOnGuard"), pc.GetAbilityUseLimit())); + } - x.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.DovesOfNeace), Translator.GetString("DovesOfNeaceSkillNotify"))); - }); - - pc.RPCPlayCustomSound("Dove"); - pc.Notify(string.Format(Translator.GetString("DovesOfNeaceOnGuard"), pc.GetAbilityUseLimit())); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Drainer.cs b/Roles/Crewmate/Drainer.cs index 33461a16d..8fca42a89 100644 --- a/Roles/Crewmate/Drainer.cs +++ b/Roles/Crewmate/Drainer.cs @@ -3,120 +3,119 @@ using System.Linq; using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Drainer : RoleBase { - public class Drainer : RoleBase + private const int Id = 642500; + private static List PlayerIdList = []; + + private static OptionItem VentCD; + private static OptionItem UseLimit; + public static OptionItem DrainerAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public static Dictionary PlayersInVents = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642500; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Drainer); - private static OptionItem VentCD; - private static OptionItem UseLimit; - public static OptionItem DrainerAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + VentCD = new IntegerOptionItem(Id + 10, "VentCooldown", new(1, 60, 1), 30, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) + .SetValueFormat(OptionFormat.Seconds); - public static Dictionary PlayersInVents = []; + UseLimit = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + DrainerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + PlayersInVents = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimit.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.EngineerCooldown = VentCD.GetFloat(); + } + + public static void OnAnyoneExitVent(PlayerControl pc) + { + if (!AmongUsClient.Instance.AmHost) return; + + if (pc != null) PlayersInVents.Remove(pc.PlayerId); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (pc.GetAbilityUseLimit() <= 0) return; + + pc.RpcRemoveAbilityUse(); + + Vent[] vents = vent.NearbyVents.Where(v => v != null).Append(vent).ToArray(); + foreach (Vent ventToDrain in vents) KillPlayersInVent(pc, ventToDrain); + } + + public static void OnAnyoneEnterVent(PlayerControl pc, Vent vent) + { + if (!AmongUsClient.Instance.AmHost || pc == null || vent == null || pc.Is(CustomRoles.Drainer)) return; + + PlayersInVents[pc.PlayerId] = vent.Id; + } + + private void KillPlayersInVent(PlayerControl pc, Vent vent) + { + if (!IsEnable) return; + + int ventId = vent.Id; + + if (!PlayersInVents.ContainsValue(ventId)) return; + + foreach (KeyValuePair venterId in PlayersInVents.Where(x => x.Value == ventId).ToArray()) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Drainer); + PlayerControl venter = Utils.GetPlayerById(venterId.Key); + if (venter == null) continue; - VentCD = new IntegerOptionItem(Id + 10, "VentCooldown", new(1, 60, 1), 30, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimit = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) - .SetValueFormat(OptionFormat.Times); - - DrainerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Drainer]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - PlayerIdList = []; - PlayersInVents = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.EngineerCooldown = VentCD.GetFloat(); - } - - public static void OnAnyoneExitVent(PlayerControl pc) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (pc != null) PlayersInVents.Remove(pc.PlayerId); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (pc.GetAbilityUseLimit() <= 0) return; - - pc.RpcRemoveAbilityUse(); - - Vent[] vents = vent.NearbyVents.Where(v => v != null).Append(vent).ToArray(); - foreach (Vent ventToDrain in vents) KillPlayersInVent(pc, ventToDrain); - } - - public static void OnAnyoneEnterVent(PlayerControl pc, Vent vent) - { - if (!AmongUsClient.Instance.AmHost || pc == null || vent == null || pc.Is(CustomRoles.Drainer)) return; - - PlayersInVents[pc.PlayerId] = vent.Id; - } - - private void KillPlayersInVent(PlayerControl pc, Vent vent) - { - if (!IsEnable) return; - - int ventId = vent.Id; - - if (!PlayersInVents.ContainsValue(ventId)) return; - - foreach (KeyValuePair venterId in PlayersInVents.Where(x => x.Value == ventId).ToArray()) + if (pc != null && pc.RpcCheckAndMurder(venter, true)) { - PlayerControl venter = Utils.GetPlayerById(venterId.Key); - if (venter == null) continue; + venter.MyPhysics.RpcBootFromVent(ventId); - if (pc != null && pc.RpcCheckAndMurder(venter, true)) + LateTask.New(() => { - venter.MyPhysics.RpcBootFromVent(ventId); - - LateTask.New(() => - { - venter.Suicide(PlayerState.DeathReason.Demolished, pc); - Logger.Info($"Killed venter {venter.GetNameWithRole()} (was inside {vent.name}, ID {ventId})", "Drainer"); - }, 0.55f, "Drainer-KillPlayerInVent"); - } + venter.Suicide(PlayerState.DeathReason.Demolished, pc); + Logger.Info($"Killed venter {venter.GetNameWithRole()} (was inside {vent.name}, ID {ventId})", "Drainer"); + }, 0.55f, "Drainer-KillPlayerInVent"); } } + } - public override void OnReportDeadBody() - { - if (!IsEnable) return; + public override void OnReportDeadBody() + { + if (!IsEnable) return; - PlayersInVents.Clear(); - } + PlayersInVents.Clear(); + } - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Druid.cs b/Roles/Crewmate/Druid.cs index d2a332939..6ae3000d4 100644 --- a/Roles/Crewmate/Druid.cs +++ b/Roles/Crewmate/Druid.cs @@ -7,216 +7,215 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Druid : RoleBase { - public class Druid : RoleBase + private const int Id = 642800; + private static List PlayerIdList = []; + + public static OptionItem VentCooldown; + private static OptionItem TriggerPlaceDelay; + private static OptionItem UseLimitOpt; + public static OptionItem DruidAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private PlayerControl DruidPC; + private long lastUpdate; + private long TriggerDelay; + private Dictionary TriggerIds = []; + private Dictionary Triggers = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642800; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Druid); - public static OptionItem VentCooldown; - private static OptionItem TriggerPlaceDelay; - private static OptionItem UseLimitOpt; - public static OptionItem DruidAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + VentCooldown = new IntegerOptionItem(Id + 10, "VentCooldown", new(0, 60, 1), 15, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) + .SetValueFormat(OptionFormat.Seconds); - private PlayerControl DruidPC; - private long lastUpdate; - private long TriggerDelay; - private Dictionary TriggerIds = []; - private Dictionary Triggers = []; + TriggerPlaceDelay = new IntegerOptionItem(Id + 11, "DruidTriggerPlaceDelayOpt", new(1, 20, 1), 7, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + UseLimitOpt = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(0, 20, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + DruidAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 14, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + TriggerDelay = 0; + Triggers = []; + lastUpdate = TimeStamp; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + DruidPC = GetPlayerById(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + lastUpdate = TimeStamp; + TriggerIds = []; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetInt(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public static void SendRPCAddTrigger(bool add, byte playerId, Vector2 position, string roomName = "") + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DruidAddTrigger, SendOption.Reliable); + writer.Write(add); + writer.Write(playerId); + writer.Write(position.x); + writer.Write(position.y); + if (add) writer.Write(roomName); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCAddTrigger(MessageReader reader) + { + bool add = reader.ReadBoolean(); + byte playerId = reader.ReadByte(); + + if (Main.PlayerStates[playerId].Role is not Druid { IsEnable: true } di) return; + + float x = reader.ReadSingle(); + float y = reader.ReadSingle(); + + Vector2 position = new(x, y); + + if (add) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Druid); + string roomName = reader.ReadString(); - VentCooldown = new IntegerOptionItem(Id + 10, "VentCooldown", new(0, 60, 1), 15, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) - .SetValueFormat(OptionFormat.Seconds); - - TriggerPlaceDelay = new IntegerOptionItem(Id + 11, "DruidTriggerPlaceDelayOpt", new(1, 20, 1), 7, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimitOpt = new IntegerOptionItem(Id + 12, "AbilityUseLimit", new(0, 20, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) - .SetValueFormat(OptionFormat.Times); - - DruidAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 13, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 14, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Druid]) - .SetValueFormat(OptionFormat.Times); + di.TriggerDelay = 0; + di.Triggers.TryAdd(position, roomName); } + else + di.Triggers.Remove(position); + } - public override void Init() + public override void OnPet(PlayerControl pc) + { + PlaceTrigger(pc, true); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + PlaceTrigger(pc); + } + + private void PlaceTrigger(PlayerControl pc, bool isPet = false) + { + if (!IsEnable) return; + + if (pc == null || !GameStates.IsInTask) return; + + if (pc.GetAbilityUseLimit() < 1) return; + + long now = TimeStamp; + + if (isPet) { - PlayerIdList = []; - TriggerDelay = 0; - Triggers = []; - lastUpdate = TimeStamp; + (Vector2 location, string roomName) = pc.GetPositionInfo(); + Triggers.TryAdd(location, roomName); + SendRPCAddTrigger(true, pc.PlayerId, location, roomName); + _ = new PlayerDetector(location, [pc.PlayerId], out int id); + TriggerIds.TryAdd(location, id); } + else + TriggerDelay = now; - public override void Add(byte playerId) + pc.RpcRemoveAbilityUse(); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (!GameStates.IsInTask || Triggers.Count <= 0 || PlayerIdList.Contains(pc.PlayerId)) return; + + foreach (KeyValuePair trigger in Triggers.Where(trigger => Vector2.Distance(trigger.Key, pc.Pos()) <= 1.5f)) { - PlayerIdList.Add(playerId); - DruidPC = GetPlayerById(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - lastUpdate = TimeStamp; - TriggerIds = []; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetInt(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public static void SendRPCAddTrigger(bool add, byte playerId, Vector2 position, string roomName = "") - { - if (!DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DruidAddTrigger, SendOption.Reliable); - writer.Write(add); - writer.Write(playerId); - writer.Write(position.x); - writer.Write(position.y); - if (add) writer.Write(roomName); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCAddTrigger(MessageReader reader) - { - bool add = reader.ReadBoolean(); - byte playerId = reader.ReadByte(); - - if (Main.PlayerStates[playerId].Role is not Druid { IsEnable: true } di) return; - - float x = reader.ReadSingle(); - float y = reader.ReadSingle(); - - Vector2 position = new(x, y); - - if (add) - { - string roomName = reader.ReadString(); - - di.TriggerDelay = 0; - di.Triggers.TryAdd(position, roomName); - } - else - di.Triggers.Remove(position); - } - - public override void OnPet(PlayerControl pc) - { - PlaceTrigger(pc, true); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - PlaceTrigger(pc); - } - - private void PlaceTrigger(PlayerControl pc, bool isPet = false) - { - if (!IsEnable) return; - - if (pc == null || !GameStates.IsInTask) return; - - if (pc.GetAbilityUseLimit() < 1) return; - - long now = TimeStamp; - - if (isPet) - { - (Vector2 location, string roomName) = pc.GetPositionInfo(); - Triggers.TryAdd(location, roomName); - SendRPCAddTrigger(true, pc.PlayerId, location, roomName); - _ = new PlayerDetector(location, [pc.PlayerId], out int id); - TriggerIds.TryAdd(location, id); - } - else - TriggerDelay = now; - - pc.RpcRemoveAbilityUse(); - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (!GameStates.IsInTask || Triggers.Count <= 0 || PlayerIdList.Contains(pc.PlayerId)) return; - - foreach (KeyValuePair trigger in Triggers.Where(trigger => Vector2.Distance(trigger.Key, pc.Pos()) <= 1.5f)) - { - DruidPC.Notify(string.Format(GetString("DruidTriggerTriggered"), GetFormattedRoomName(trigger.Value), GetFormattedVectorText(trigger.Key))); - Triggers.Remove(trigger.Key); - SendRPCAddTrigger(false, DruidPC.PlayerId, trigger.Key); - CustomNetObject.Get(TriggerIds[trigger.Key])?.Despawn(); - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable || !GameStates.IsInTask || TriggerDelay == 0 || pc == null) return; - - long now = TimeStamp; - - if (TriggerDelay + TriggerPlaceDelay.GetInt() < now) - { - byte id = pc.PlayerId; - TriggerDelay = 0; - (Vector2 location, string roomName) = pc.GetPositionInfo(); - Triggers.TryAdd(location, roomName); - SendRPCAddTrigger(true, id, location, roomName); - _ = new PlayerDetector(location, [pc.PlayerId], out int oid); - TriggerIds.TryAdd(location, oid); - return; - } - - long timeLeft = TriggerPlaceDelay.GetInt() - (now - TriggerDelay); - if (lastUpdate < now) pc.Notify(string.Format(GetString("DruidTimeLeft"), timeLeft, 2f)); - - lastUpdate = now; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (hud) return GetHUDText(seer); - - if (seer == null || seer.IsModClient() || seer.PlayerId != target.PlayerId || seer.PlayerId != DruidPC.PlayerId) return string.Empty; - - if (Triggers.Count == 0) return string.Empty; - - var sb = new StringBuilder(); - sb.Append("\n"); - - sb.AppendLine($"<#00ffa5>{Triggers.Count} trigger{(Triggers.Count == 1 ? string.Empty : 's')} active"); - sb.Append(string.Join(", ", Triggers.Select(trigger => $"Trigger {GetFormattedRoomName(trigger.Value)} {GetFormattedVectorText(trigger.Key)}"))); - - sb.Append(""); - return sb.ToString(); - } - - private string GetHUDText(PlayerControl pc) - { - if (pc == null || Triggers.Count == 0) return string.Empty; - - var sb = new StringBuilder(); - - sb.AppendLine($"<#00ffa5>{Triggers.Count} trigger{(Triggers.Count == 1 ? string.Empty : 's')} active"); - sb.Append(string.Join('\n', Triggers.Select(trigger => $"Trigger {GetFormattedRoomName(trigger.Value)} {GetFormattedVectorText(trigger.Key)}"))); - - return sb.ToString(); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + DruidPC.Notify(string.Format(GetString("DruidTriggerTriggered"), GetFormattedRoomName(trigger.Value), GetFormattedVectorText(trigger.Key))); + Triggers.Remove(trigger.Key); + SendRPCAddTrigger(false, DruidPC.PlayerId, trigger.Key); + CustomNetObject.Get(TriggerIds[trigger.Key])?.Despawn(); } } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable || !GameStates.IsInTask || TriggerDelay == 0 || pc == null) return; + + long now = TimeStamp; + + if (TriggerDelay + TriggerPlaceDelay.GetInt() < now) + { + byte id = pc.PlayerId; + TriggerDelay = 0; + (Vector2 location, string roomName) = pc.GetPositionInfo(); + Triggers.TryAdd(location, roomName); + SendRPCAddTrigger(true, id, location, roomName); + _ = new PlayerDetector(location, [pc.PlayerId], out int oid); + TriggerIds.TryAdd(location, oid); + return; + } + + long timeLeft = TriggerPlaceDelay.GetInt() - (now - TriggerDelay); + if (lastUpdate < now) pc.Notify(string.Format(GetString("DruidTimeLeft"), timeLeft, 2f)); + + lastUpdate = now; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (hud) return GetHUDText(seer); + + if (seer == null || seer.IsModClient() || seer.PlayerId != target.PlayerId || seer.PlayerId != DruidPC.PlayerId) return string.Empty; + + if (Triggers.Count == 0) return string.Empty; + + var sb = new StringBuilder(); + sb.Append("\n"); + + sb.AppendLine($"<#00ffa5>{Triggers.Count} trigger{(Triggers.Count == 1 ? string.Empty : 's')} active"); + sb.Append(string.Join(", ", Triggers.Select(trigger => $"Trigger {GetFormattedRoomName(trigger.Value)} {GetFormattedVectorText(trigger.Key)}"))); + + sb.Append(""); + return sb.ToString(); + } + + private string GetHUDText(PlayerControl pc) + { + if (pc == null || Triggers.Count == 0) return string.Empty; + + var sb = new StringBuilder(); + + sb.AppendLine($"<#00ffa5>{Triggers.Count} trigger{(Triggers.Count == 1 ? string.Empty : 's')} active"); + sb.Append(string.Join('\n', Triggers.Select(trigger => $"Trigger {GetFormattedRoomName(trigger.Value)} {GetFormattedVectorText(trigger.Key)}"))); + + return sb.ToString(); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Electric.cs b/Roles/Crewmate/Electric.cs index a5d72854c..7de5015c5 100644 --- a/Roles/Crewmate/Electric.cs +++ b/Roles/Crewmate/Electric.cs @@ -3,56 +3,55 @@ using System.Linq; using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Electric : RoleBase { - internal class Electric : RoleBase + public static bool On; + private static OptionItem FreezeDuration; + public override bool IsEnable => On; + + private static int Id => 64410; + + public override void Add(byte playerId) { - public static bool On; - private static OptionItem FreezeDuration; - public override bool IsEnable => On; + On = true; + } - private static int Id => 64410; + public override void Init() + { + On = false; + } - public override void Add(byte playerId) + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Electric); + + FreezeDuration = new FloatOptionItem(Id + 2, "GamblerFreezeDur", new(0.5f, 90f, 0.5f), 3f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Electric]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (pc == null) return; + + List targetList = Main.AllAlivePlayerControls.Where(x => !x.Is(Team.Crewmate)).ToList(); + if (targetList.Count == 0) return; + + PlayerControl target = targetList.RandomElement(); + + float beforeSpeed = Main.AllPlayerSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); + + LateTask.New(() => { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Electric); - - FreezeDuration = new FloatOptionItem(Id + 2, "GamblerFreezeDur", new(0.5f, 90f, 0.5f), 3f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Electric]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (pc == null) return; - - List targetList = Main.AllAlivePlayerControls.Where(x => !x.Is(Team.Crewmate)).ToList(); - if (targetList.Count == 0) return; - - PlayerControl target = targetList.RandomElement(); - - float beforeSpeed = Main.AllPlayerSpeed[target.PlayerId]; - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + Main.AllPlayerSpeed[target.PlayerId] = beforeSpeed; target.MarkDirtySettings(); + }, FreezeDuration.GetFloat(), "Electric Freeze Reset"); - LateTask.New(() => - { - Main.AllPlayerSpeed[target.PlayerId] = beforeSpeed; - target.MarkDirtySettings(); - }, FreezeDuration.GetFloat(), "Electric Freeze Reset"); - - if (target.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - } + if (target.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Enigma.cs b/Roles/Crewmate/Enigma.cs index 11eb62146..6c44dc2ac 100644 --- a/Roles/Crewmate/Enigma.cs +++ b/Roles/Crewmate/Enigma.cs @@ -3,490 +3,489 @@ using System.Linq; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Enigma : RoleBase { - public class Enigma : RoleBase + private const int Id = 8460; + public static List PlayerIdList = []; + private static Dictionary> ShownClues = []; + + private static OptionItem EnigmaClueStage1Tasks; + private static OptionItem EnigmaClueStage2Tasks; + private static OptionItem EnigmaClueStage3Tasks; + private static OptionItem EnigmaClueStage2Probability; + private static OptionItem EnigmaClueStage3Probability; + private static OptionItem EnigmaGetCluesWithoutReporting; + + public static Dictionary MsgToSend = []; + public static Dictionary MsgToSendTitle = []; + + private static readonly List EnigmaClues = + [ + new EnigmaHatClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.HatClue }, + new EnigmaHatClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.HatClue }, + new EnigmaVisorClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.VisorClue }, + new EnigmaVisorClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.VisorClue }, + new EnigmaSkinClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.SkinClue }, + new EnigmaSkinClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.SkinClue }, + new EnigmaPetClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.PetClue }, + new EnigmaPetClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.PetClue }, + new EnigmaNameClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.NameClue }, + new EnigmaNameClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.NameClue }, + new EnigmaNameClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.NameClue }, + new EnigmaNameLengthClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.NameLengthClue }, + new EnigmaNameLengthClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.NameLengthClue }, + new EnigmaNameLengthClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.NameLengthClue }, + new EnigmaColorClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.ColorClue }, + new EnigmaColorClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.ColorClue }, + new EnigmaLocationClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.LocationClue }, + new EnigmaKillerStatusClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.KillerStatusClue }, + new EnigmaKillerRoleClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.KillerRoleClue }, + new EnigmaKillerRoleClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.KillerRoleClue }, + new EnigmaKillerLevelClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.KillerLevelClue }, + new EnigmaKillerLevelClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.KillerLevelClue }, + new EnigmaKillerLevelClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.KillerLevelClue }, + new EnigmaFriendCodeClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.FriendCodeClue } + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 8460; - public static List PlayerIdList = []; - private static Dictionary> ShownClues = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Enigma); - private static OptionItem EnigmaClueStage1Tasks; - private static OptionItem EnigmaClueStage2Tasks; - private static OptionItem EnigmaClueStage3Tasks; - private static OptionItem EnigmaClueStage2Probability; - private static OptionItem EnigmaClueStage3Probability; - private static OptionItem EnigmaGetCluesWithoutReporting; + EnigmaClueStage1Tasks = new FloatOptionItem(Id + 11, "EnigmaClueStage1Tasks", new(0f, 10f, 1f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) + .SetValueFormat(OptionFormat.Times); - public static Dictionary MsgToSend = []; - public static Dictionary MsgToSendTitle = []; + EnigmaClueStage2Tasks = new FloatOptionItem(Id + 12, "EnigmaClueStage2Tasks", new(0f, 10f, 1f), 3f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) + .SetValueFormat(OptionFormat.Times); - private static readonly List EnigmaClues = - [ - new EnigmaHatClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.HatClue }, - new EnigmaHatClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.HatClue }, - new EnigmaVisorClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.VisorClue }, - new EnigmaVisorClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.VisorClue }, - new EnigmaSkinClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.SkinClue }, - new EnigmaSkinClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.SkinClue }, - new EnigmaPetClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.PetClue }, - new EnigmaPetClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.PetClue }, - new EnigmaNameClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.NameClue }, - new EnigmaNameClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.NameClue }, - new EnigmaNameClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.NameClue }, - new EnigmaNameLengthClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.NameLengthClue }, - new EnigmaNameLengthClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.NameLengthClue }, - new EnigmaNameLengthClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.NameLengthClue }, - new EnigmaColorClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.ColorClue }, - new EnigmaColorClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.ColorClue }, - new EnigmaLocationClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.LocationClue }, - new EnigmaKillerStatusClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.KillerStatusClue }, - new EnigmaKillerRoleClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.KillerRoleClue }, - new EnigmaKillerRoleClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.KillerRoleClue }, - new EnigmaKillerLevelClue { ClueStage = 1, EnigmaClueType = EnigmaClueType.KillerLevelClue }, - new EnigmaKillerLevelClue { ClueStage = 2, EnigmaClueType = EnigmaClueType.KillerLevelClue }, - new EnigmaKillerLevelClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.KillerLevelClue }, - new EnigmaFriendCodeClue { ClueStage = 3, EnigmaClueType = EnigmaClueType.FriendCodeClue } - ]; + EnigmaClueStage3Tasks = new FloatOptionItem(Id + 13, "EnigmaClueStage3Tasks", new(0f, 10f, 1f), 7f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + EnigmaClueStage2Probability = new IntegerOptionItem(Id + 14, "EnigmaClueStage2Probability", new(0, 100, 5), 75, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) + .SetValueFormat(OptionFormat.Percent); - public override void SetupCustomOption() + EnigmaClueStage3Probability = new IntegerOptionItem(Id + 15, "EnigmaClueStage3Probability", new(0, 100, 5), 60, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) + .SetValueFormat(OptionFormat.Percent); + + EnigmaGetCluesWithoutReporting = new BooleanOptionItem(Id + 16, "EnigmaClueGetCluesWithoutReporting", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]); + + OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Enigma); + } + + public override void Init() + { + PlayerIdList = []; + ShownClues = []; + MsgToSend = []; + MsgToSendTitle = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + ShownClues.Add(playerId, []); + } + + public static void OnReportDeadBody(PlayerControl player, NetworkedPlayerInfo targetInfo) + { + if (targetInfo == null) return; + + PlayerControl target = Utils.GetPlayerById(targetInfo.PlayerId); + if (target == null) return; + + PlayerControl killer = target.GetRealKiller(); + if (killer == null) return; + + var rd = IRandom.Instance; + + foreach (byte playerId in PlayerIdList) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Enigma); + if (!EnigmaGetCluesWithoutReporting.GetBool() && playerId != player.PlayerId) continue; - EnigmaClueStage1Tasks = new FloatOptionItem(Id + 11, "EnigmaClueStage1Tasks", new(0f, 10f, 1f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) - .SetValueFormat(OptionFormat.Times); + PlayerControl enigmaPlayer = Utils.GetPlayerById(playerId); + if (enigmaPlayer == null) continue; - EnigmaClueStage2Tasks = new FloatOptionItem(Id + 12, "EnigmaClueStage2Tasks", new(0f, 10f, 1f), 3f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) - .SetValueFormat(OptionFormat.Times); + int tasksCompleted = enigmaPlayer.GetTaskState().CompletedTasksCount; + var stage = 0; + var showStageClue = false; - EnigmaClueStage3Tasks = new FloatOptionItem(Id + 13, "EnigmaClueStage3Tasks", new(0f, 10f, 1f), 7f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) - .SetValueFormat(OptionFormat.Times); - - EnigmaClueStage2Probability = new IntegerOptionItem(Id + 14, "EnigmaClueStage2Probability", new(0, 100, 5), 75, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) - .SetValueFormat(OptionFormat.Percent); - - EnigmaClueStage3Probability = new IntegerOptionItem(Id + 15, "EnigmaClueStage3Probability", new(0, 100, 5), 60, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]) - .SetValueFormat(OptionFormat.Percent); - - EnigmaGetCluesWithoutReporting = new BooleanOptionItem(Id + 16, "EnigmaClueGetCluesWithoutReporting", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enigma]); - - OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Enigma); - } - - public override void Init() - { - PlayerIdList = []; - ShownClues = []; - MsgToSend = []; - MsgToSendTitle = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - ShownClues.Add(playerId, []); - } - - public static void OnReportDeadBody(PlayerControl player, NetworkedPlayerInfo targetInfo) - { - if (targetInfo == null) return; - - PlayerControl target = Utils.GetPlayerById(targetInfo.PlayerId); - if (target == null) return; - - PlayerControl killer = target.GetRealKiller(); - if (killer == null) return; - - var rd = IRandom.Instance; - - foreach (byte playerId in PlayerIdList) + if (tasksCompleted >= EnigmaClueStage3Tasks.GetInt()) { - if (!EnigmaGetCluesWithoutReporting.GetBool() && playerId != player.PlayerId) continue; - - PlayerControl enigmaPlayer = Utils.GetPlayerById(playerId); - if (enigmaPlayer == null) continue; - - int tasksCompleted = enigmaPlayer.GetTaskState().CompletedTasksCount; - var stage = 0; - var showStageClue = false; - - if (tasksCompleted >= EnigmaClueStage3Tasks.GetInt()) - { - stage = 3; - showStageClue = rd.Next(0, 100) < EnigmaClueStage3Probability.GetInt(); - } - else if (tasksCompleted >= EnigmaClueStage2Tasks.GetInt()) - { - stage = 2; - showStageClue = rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt(); - } - else if (tasksCompleted >= EnigmaClueStage1Tasks.GetInt()) stage = 1; - - List clues = EnigmaClues.Where(a => a.ClueStage <= stage && - !ShownClues[playerId].Any(b => b.EnigmaClueType == a.EnigmaClueType && b.ClueStage == a.ClueStage)) - .ToList(); - - if (clues.Count == 0) continue; - - if (showStageClue && clues.Any(a => a.ClueStage == stage)) clues = clues.Where(a => a.ClueStage == stage).ToList(); - - EnigmaClue clue = clues[rd.Next(0, clues.Count - 1)]; - string title = clue.Title; - string msg = clue.GetMessage(killer, showStageClue); - - ShownClues[playerId].Add(clue); - - MsgToSend[playerId] = msg; - - MsgToSendTitle[playerId] = title; + stage = 3; + showStageClue = rd.Next(0, 100) < EnigmaClueStage3Probability.GetInt(); } - } - - private abstract class EnigmaClue - { - public int ClueStage { get; init; } - public EnigmaClueType EnigmaClueType { get; init; } - - public abstract string Title { get; } - public abstract string GetMessage(PlayerControl killer, bool showStageClue); - } - - private class EnigmaHatClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueHatTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) + else if (tasksCompleted >= EnigmaClueStage2Tasks.GetInt()) { - NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; - if (killerOutfit.HatId == "hat_EmptyHat") return GetString("EnigmaClueHat2"); - - return ClueStage switch - { - 1 => GetString("EnigmaClueHat1"), - 2 => GetString("EnigmaClueHat1"), - 3 => showStageClue ? string.Format(GetString("EnigmaClueHat3"), killerOutfit.HatId) : GetString("EnigmaClueHat1"), - _ => null - }; + stage = 2; + showStageClue = rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt(); } - } + else if (tasksCompleted >= EnigmaClueStage1Tasks.GetInt()) stage = 1; - private class EnigmaVisorClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueVisorTitle"); + List clues = EnigmaClues.Where(a => a.ClueStage <= stage && + !ShownClues[playerId].Any(b => b.EnigmaClueType == a.EnigmaClueType && b.ClueStage == a.ClueStage)) + .ToList(); - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; - if (killerOutfit.VisorId == "visor_EmptyVisor") return GetString("EnigmaClueVisor2"); + if (clues.Count == 0) continue; - return ClueStage switch - { - 1 => GetString("EnigmaClueVisor1"), - 2 => GetString("EnigmaClueVisor1"), - 3 => showStageClue ? string.Format(GetString("EnigmaClueVisor3"), killerOutfit.VisorId) : GetString("EnigmaClueVisor1"), - _ => null - }; - } - } + if (showStageClue && clues.Any(a => a.ClueStage == stage)) clues = clues.Where(a => a.ClueStage == stage).ToList(); - private class EnigmaSkinClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueSkinTitle"); + EnigmaClue clue = clues[rd.Next(0, clues.Count - 1)]; + string title = clue.Title; + string msg = clue.GetMessage(killer, showStageClue); - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; - if (killerOutfit.SkinId == "skin_EmptySkin") return GetString("EnigmaClueSkin2"); + ShownClues[playerId].Add(clue); - return ClueStage switch - { - 1 => GetString("EnigmaClueSkin1"), - 2 => GetString("EnigmaClueSkin1"), - 3 => showStageClue ? string.Format(GetString("EnigmaClueSkin3"), killerOutfit.SkinId) : GetString("EnigmaClueSkin1"), - _ => null - }; - } - } + MsgToSend[playerId] = msg; - private class EnigmaPetClue : EnigmaClue - { - public override string Title => GetString("EnigmaCluePetTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; - if (killerOutfit.PetId == "pet_EmptyPet") return GetString("EnigmaCluePet2"); - - return ClueStage switch - { - 1 => GetString("EnigmaCluePet1"), - 2 => GetString("EnigmaCluePet1"), - 3 => showStageClue ? string.Format(GetString("EnigmaCluePet3"), killerOutfit.PetId) : GetString("EnigmaCluePet1"), - _ => null - }; - } - } - - private class EnigmaNameClue : EnigmaClue - { - private readonly IRandom rd = IRandom.Instance; - - public override string Title => GetString("EnigmaClueNameTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - string killerName = killer.GetRealName(); - string letter = killerName[rd.Next(0, killerName.Length - 1)].ToString().ToLower(); - - switch (ClueStage) - { - case 1: - return GetStage1Clue(killer, letter); - case 2: - if (showStageClue) GetStage2Clue(letter); - - return GetStage1Clue(killer, letter); - case 3: - if (showStageClue) return GetStage3Clue(killerName, letter); - - if (rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt()) return GetStage2Clue(letter); - - return GetStage1Clue(killer, letter); - } - - return null; - } - - private string GetStage1Clue(PlayerControl killer, string letter) - { - string randomLetter = GetRandomLetter(killer, letter); - int random = rd.Next(1, 2); - return random == 1 ? string.Format(GetString("EnigmaClueName1"), letter, randomLetter) : string.Format(GetString("EnigmaClueName1"), randomLetter, letter); - } - - private static string GetStage2Clue(string letter) - { - return string.Format(GetString("EnigmaClueName2"), letter); - } - - private string GetStage3Clue(string killerName, string letter) - { - var letter2 = string.Empty; - string tmpName = killerName.Replace(letter, string.Empty); - if (!string.IsNullOrEmpty(tmpName)) letter2 = tmpName[rd.Next(0, tmpName.Length - 1)].ToString().ToLower(); - - return string.Format(GetString("EnigmaClueName3"), letter, letter2); - } - - private string GetRandomLetter(PlayerControl killer, string letter) - { - PlayerControl[] alivePlayers = Main.AllAlivePlayerControls.Where(a => a.PlayerId != killer.PlayerId).ToArray(); - PlayerControl rndPlayer = alivePlayers[rd.Next(0, alivePlayers.Length - 1)]; - string rndPlayerName = rndPlayer.GetRealName().Replace(letter, ""); - string letter2 = rndPlayerName[rd.Next(0, rndPlayerName.Length - 1)].ToString().ToLower(); - return letter2; - } - } - - private class EnigmaNameLengthClue : EnigmaClue - { - private readonly IRandom rd = IRandom.Instance; - - public override string Title => GetString("EnigmaClueNameLengthTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - int length = killer.GetRealName().Length; - - return ClueStage switch - { - 1 => GetStage1Clue(length), - 2 => showStageClue ? GetStage2Clue(length) : GetStage1Clue(length), - 3 when showStageClue => GetStage3Clue(length), - 3 => rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt() ? GetStage2Clue(length) : GetStage1Clue(length), - _ => null - }; - } - - private string GetStage1Clue(int length) - { - int start = length - rd.Next(1, 3); - int end = length + rd.Next(1, 3); - - start = start < 0 ? 0 : start; - end = end > 10 ? 10 : end; - - return string.Format(GetString("EnigmaClueNameLength1"), start, end); - } - - private string GetStage2Clue(int length) - { - int start = length - rd.Next(0, 2); - int end = length + rd.Next(0, 2); - - if (start == end) return GetStage3Clue(length); - - start = start < 0 ? 0 : start; - end = end > 10 ? 10 : end; - - return string.Format(GetString("EnigmaClueNameLength1"), start, end); - } - - private static string GetStage3Clue(int length) - { - return string.Format(GetString("EnigmaClueNameLength2"), length); - } - } - - private class EnigmaColorClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueColorTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; - - return ClueStage switch - { - 1 => GetStage1Clue(killerOutfit.ColorId), - 2 => GetStage1Clue(killerOutfit.ColorId), - 3 => showStageClue ? string.Format(GetString("EnigmaClueColor3"), killer.Data.ColorName) : GetStage1Clue(killerOutfit.ColorId), - _ => GetStage1Clue(killerOutfit.ColorId) - }; - } - - private static string GetStage1Clue(int colorId) - { - return colorId switch - { - 0 or 3 or 4 or 5 or 7 or 10 or 11 or 13 or 14 or 17 => GetString("EnigmaClueColor1"), - 1 or 2 or 6 or 8 or 9 or 12 or 15 or 16 => GetString("EnigmaClueColor2"), - _ => null - }; - } - } - - private class EnigmaLocationClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueLocationTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - var room = string.Empty; - PlainShipRoom targetRoom = Main.PlayerStates[killer.PlayerId].LastRoom; - - if (targetRoom == null) - room += GetString("FailToTrack"); - else - room += GetString(targetRoom.RoomId.ToString()); - - return string.Format(GetString("EnigmaClueLocation"), room); - } - } - - private class EnigmaKillerStatusClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueStatusTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - return killer.inVent ? GetString("EnigmaClueStatus1") : killer.onLadder ? GetString("EnigmaClueStatus2") : GetString(killer.Data.IsDead ? "EnigmaClueStatus3" : "EnigmaClueStatus4"); - } - } - - private class EnigmaKillerRoleClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueRoleTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - CustomRoles role = killer.GetCustomRole(); - - return ClueStage switch - { - 1 => role.IsImpostor() ? GetString("EnigmaClueRole1") : GetString(role.IsNeutral() ? "EnigmaClueRole2" : "EnigmaClueRole3"), - 2 when showStageClue => string.Format(GetString("EnigmaClueRole4"), killer.GetDisplayRoleName()), - 2 => role.IsImpostor() ? GetString("EnigmaClueRole1") : GetString(role.IsNeutral() ? "EnigmaClueRole2" : "EnigmaClueRole3"), - _ => null - }; - } - } - - private class EnigmaKillerLevelClue : EnigmaClue - { - private readonly IRandom rd = IRandom.Instance; - - public override string Title => GetString("EnigmaClueLevelTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - var level = (int)killer.Data.PlayerLevel; - - return ClueStage switch - { - 1 => GetStage1Clue(level), - 2 => showStageClue ? GetStage2Clue(level) : GetStage1Clue(level), - 3 when showStageClue => GetStage3Clue(level), - 3 => rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt() ? GetStage2Clue(level) : GetStage1Clue(level), - _ => null - }; - } - - private static string GetStage1Clue(int level) - { - return GetString(level > 50 ? "EnigmaClueLevel1" : "EnigmaClueLevel2"); - } - - private static string GetStage2Clue(int level) - { - int rangeStart = level - 15; - int rangeEnd = level + 15; - return string.Format(GetString("EnigmaClueLevel3"), rangeStart, rangeEnd >= 100 ? 100 : rangeEnd); - } - - private static string GetStage3Clue(int level) - { - return string.Format(GetString("EnigmaClueLevel4"), level); - } - } - - private class EnigmaFriendCodeClue : EnigmaClue - { - public override string Title => GetString("EnigmaClueFriendCodeTitle"); - - public override string GetMessage(PlayerControl killer, bool showStageClue) - { - string friendCode = killer.Data.FriendCode.Replace(':', '#'); - return string.Format(GetString("EnigmaClueFriendCode"), friendCode); - } - } - - private enum EnigmaClueType - { - HatClue, - VisorClue, - SkinClue, - PetClue, - NameClue, - NameLengthClue, - ColorClue, - LocationClue, - KillerStatusClue, - KillerRoleClue, - KillerLevelClue, - - FriendCodeClue - //SecurityClue, - //SabotageClue, - //RandomClue + MsgToSendTitle[playerId] = title; } } + + private abstract class EnigmaClue + { + public int ClueStage { get; init; } + public EnigmaClueType EnigmaClueType { get; init; } + + public abstract string Title { get; } + public abstract string GetMessage(PlayerControl killer, bool showStageClue); + } + + private class EnigmaHatClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueHatTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; + if (killerOutfit.HatId == "hat_EmptyHat") return GetString("EnigmaClueHat2"); + + return ClueStage switch + { + 1 => GetString("EnigmaClueHat1"), + 2 => GetString("EnigmaClueHat1"), + 3 => showStageClue ? string.Format(GetString("EnigmaClueHat3"), killerOutfit.HatId) : GetString("EnigmaClueHat1"), + _ => null + }; + } + } + + private class EnigmaVisorClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueVisorTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; + if (killerOutfit.VisorId == "visor_EmptyVisor") return GetString("EnigmaClueVisor2"); + + return ClueStage switch + { + 1 => GetString("EnigmaClueVisor1"), + 2 => GetString("EnigmaClueVisor1"), + 3 => showStageClue ? string.Format(GetString("EnigmaClueVisor3"), killerOutfit.VisorId) : GetString("EnigmaClueVisor1"), + _ => null + }; + } + } + + private class EnigmaSkinClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueSkinTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; + if (killerOutfit.SkinId == "skin_EmptySkin") return GetString("EnigmaClueSkin2"); + + return ClueStage switch + { + 1 => GetString("EnigmaClueSkin1"), + 2 => GetString("EnigmaClueSkin1"), + 3 => showStageClue ? string.Format(GetString("EnigmaClueSkin3"), killerOutfit.SkinId) : GetString("EnigmaClueSkin1"), + _ => null + }; + } + } + + private class EnigmaPetClue : EnigmaClue + { + public override string Title => GetString("EnigmaCluePetTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; + if (killerOutfit.PetId == "pet_EmptyPet") return GetString("EnigmaCluePet2"); + + return ClueStage switch + { + 1 => GetString("EnigmaCluePet1"), + 2 => GetString("EnigmaCluePet1"), + 3 => showStageClue ? string.Format(GetString("EnigmaCluePet3"), killerOutfit.PetId) : GetString("EnigmaCluePet1"), + _ => null + }; + } + } + + private class EnigmaNameClue : EnigmaClue + { + private readonly IRandom rd = IRandom.Instance; + + public override string Title => GetString("EnigmaClueNameTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + string killerName = killer.GetRealName(); + string letter = killerName[rd.Next(0, killerName.Length - 1)].ToString().ToLower(); + + switch (ClueStage) + { + case 1: + return GetStage1Clue(killer, letter); + case 2: + if (showStageClue) GetStage2Clue(letter); + + return GetStage1Clue(killer, letter); + case 3: + if (showStageClue) return GetStage3Clue(killerName, letter); + + if (rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt()) return GetStage2Clue(letter); + + return GetStage1Clue(killer, letter); + } + + return null; + } + + private string GetStage1Clue(PlayerControl killer, string letter) + { + string randomLetter = GetRandomLetter(killer, letter); + int random = rd.Next(1, 2); + return random == 1 ? string.Format(GetString("EnigmaClueName1"), letter, randomLetter) : string.Format(GetString("EnigmaClueName1"), randomLetter, letter); + } + + private static string GetStage2Clue(string letter) + { + return string.Format(GetString("EnigmaClueName2"), letter); + } + + private string GetStage3Clue(string killerName, string letter) + { + var letter2 = string.Empty; + string tmpName = killerName.Replace(letter, string.Empty); + if (!string.IsNullOrEmpty(tmpName)) letter2 = tmpName[rd.Next(0, tmpName.Length - 1)].ToString().ToLower(); + + return string.Format(GetString("EnigmaClueName3"), letter, letter2); + } + + private string GetRandomLetter(PlayerControl killer, string letter) + { + PlayerControl[] alivePlayers = Main.AllAlivePlayerControls.Where(a => a.PlayerId != killer.PlayerId).ToArray(); + PlayerControl rndPlayer = alivePlayers[rd.Next(0, alivePlayers.Length - 1)]; + string rndPlayerName = rndPlayer.GetRealName().Replace(letter, ""); + string letter2 = rndPlayerName[rd.Next(0, rndPlayerName.Length - 1)].ToString().ToLower(); + return letter2; + } + } + + private class EnigmaNameLengthClue : EnigmaClue + { + private readonly IRandom rd = IRandom.Instance; + + public override string Title => GetString("EnigmaClueNameLengthTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + int length = killer.GetRealName().Length; + + return ClueStage switch + { + 1 => GetStage1Clue(length), + 2 => showStageClue ? GetStage2Clue(length) : GetStage1Clue(length), + 3 when showStageClue => GetStage3Clue(length), + 3 => rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt() ? GetStage2Clue(length) : GetStage1Clue(length), + _ => null + }; + } + + private string GetStage1Clue(int length) + { + int start = length - rd.Next(1, 3); + int end = length + rd.Next(1, 3); + + start = start < 0 ? 0 : start; + end = end > 10 ? 10 : end; + + return string.Format(GetString("EnigmaClueNameLength1"), start, end); + } + + private string GetStage2Clue(int length) + { + int start = length - rd.Next(0, 2); + int end = length + rd.Next(0, 2); + + if (start == end) return GetStage3Clue(length); + + start = start < 0 ? 0 : start; + end = end > 10 ? 10 : end; + + return string.Format(GetString("EnigmaClueNameLength1"), start, end); + } + + private static string GetStage3Clue(int length) + { + return string.Format(GetString("EnigmaClueNameLength2"), length); + } + } + + private class EnigmaColorClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueColorTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + NetworkedPlayerInfo.PlayerOutfit killerOutfit = Camouflage.PlayerSkins[killer.PlayerId]; + + return ClueStage switch + { + 1 => GetStage1Clue(killerOutfit.ColorId), + 2 => GetStage1Clue(killerOutfit.ColorId), + 3 => showStageClue ? string.Format(GetString("EnigmaClueColor3"), killer.Data.ColorName) : GetStage1Clue(killerOutfit.ColorId), + _ => GetStage1Clue(killerOutfit.ColorId) + }; + } + + private static string GetStage1Clue(int colorId) + { + return colorId switch + { + 0 or 3 or 4 or 5 or 7 or 10 or 11 or 13 or 14 or 17 => GetString("EnigmaClueColor1"), + 1 or 2 or 6 or 8 or 9 or 12 or 15 or 16 => GetString("EnigmaClueColor2"), + _ => null + }; + } + } + + private class EnigmaLocationClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueLocationTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + var room = string.Empty; + PlainShipRoom targetRoom = Main.PlayerStates[killer.PlayerId].LastRoom; + + if (targetRoom == null) + room += GetString("FailToTrack"); + else + room += GetString(targetRoom.RoomId.ToString()); + + return string.Format(GetString("EnigmaClueLocation"), room); + } + } + + private class EnigmaKillerStatusClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueStatusTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + return killer.inVent ? GetString("EnigmaClueStatus1") : killer.onLadder ? GetString("EnigmaClueStatus2") : GetString(killer.Data.IsDead ? "EnigmaClueStatus3" : "EnigmaClueStatus4"); + } + } + + private class EnigmaKillerRoleClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueRoleTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + CustomRoles role = killer.GetCustomRole(); + + return ClueStage switch + { + 1 => role.IsImpostor() ? GetString("EnigmaClueRole1") : GetString(role.IsNeutral() ? "EnigmaClueRole2" : "EnigmaClueRole3"), + 2 when showStageClue => string.Format(GetString("EnigmaClueRole4"), killer.GetDisplayRoleName()), + 2 => role.IsImpostor() ? GetString("EnigmaClueRole1") : GetString(role.IsNeutral() ? "EnigmaClueRole2" : "EnigmaClueRole3"), + _ => null + }; + } + } + + private class EnigmaKillerLevelClue : EnigmaClue + { + private readonly IRandom rd = IRandom.Instance; + + public override string Title => GetString("EnigmaClueLevelTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + var level = (int)killer.Data.PlayerLevel; + + return ClueStage switch + { + 1 => GetStage1Clue(level), + 2 => showStageClue ? GetStage2Clue(level) : GetStage1Clue(level), + 3 when showStageClue => GetStage3Clue(level), + 3 => rd.Next(0, 100) < EnigmaClueStage2Probability.GetInt() ? GetStage2Clue(level) : GetStage1Clue(level), + _ => null + }; + } + + private static string GetStage1Clue(int level) + { + return GetString(level > 50 ? "EnigmaClueLevel1" : "EnigmaClueLevel2"); + } + + private static string GetStage2Clue(int level) + { + int rangeStart = level - 15; + int rangeEnd = level + 15; + return string.Format(GetString("EnigmaClueLevel3"), rangeStart, rangeEnd >= 100 ? 100 : rangeEnd); + } + + private static string GetStage3Clue(int level) + { + return string.Format(GetString("EnigmaClueLevel4"), level); + } + } + + private class EnigmaFriendCodeClue : EnigmaClue + { + public override string Title => GetString("EnigmaClueFriendCodeTitle"); + + public override string GetMessage(PlayerControl killer, bool showStageClue) + { + string friendCode = killer.Data.FriendCode.Replace(':', '#'); + return string.Format(GetString("EnigmaClueFriendCode"), friendCode); + } + } + + private enum EnigmaClueType + { + HatClue, + VisorClue, + SkinClue, + PetClue, + NameClue, + NameLengthClue, + ColorClue, + LocationClue, + KillerStatusClue, + KillerRoleClue, + KillerLevelClue, + + FriendCodeClue + //SecurityClue, + //SabotageClue, + //RandomClue + } } \ No newline at end of file diff --git a/Roles/Crewmate/Escort.cs b/Roles/Crewmate/Escort.cs index e8bba2842..36120aec9 100644 --- a/Roles/Crewmate/Escort.cs +++ b/Roles/Crewmate/Escort.cs @@ -2,75 +2,74 @@ using AmongUs.GameOptions; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Escort : RoleBase { - public class Escort : RoleBase + private const int Id = 642300; + private static List PlayerIdList = []; + + private static OptionItem CD; + private static OptionItem UseLimit; + private static OptionItem Duration; + public static OptionItem UsePet; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642300; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Escort); - private static OptionItem CD; - private static OptionItem UseLimit; - private static OptionItem Duration; - public static OptionItem UsePet; + CD = new FloatOptionItem(Id + 10, "RoleBlockCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escort]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + UseLimit = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 3, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escort]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Escort); + UsePet = Options.CreatePetUseSetting(Id + 12, CustomRoles.Escort); - CD = new FloatOptionItem(Id + 10, "RoleBlockCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escort]) - .SetValueFormat(OptionFormat.Seconds); + Duration = new FloatOptionItem(Id + 13, "RoleBlockDuration", new(1f, 60f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escort]) + .SetValueFormat(OptionFormat.Seconds); + } - UseLimit = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 3, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escort]) - .SetValueFormat(OptionFormat.Times); + public override void Init() + { + PlayerIdList = []; + } - UsePet = Options.CreatePetUseSetting(Id + 12, CustomRoles.Escort); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimit.GetInt()); + } - Duration = new FloatOptionItem(Id + 13, "RoleBlockDuration", new(1f, 60f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escort]) - .SetValueFormat(OptionFormat.Seconds); - } + public override void SetKillCooldown(byte playerId) + { + Main.AllPlayerKillCooldown[playerId] = playerId.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; + } - public override void Init() - { - PlayerIdList = []; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() >= 1; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override void SetKillCooldown(byte playerId) - { - Main.AllPlayerKillCooldown[playerId] = playerId.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() >= 1; - } + killer.RpcRemoveAbilityUse(); + killer.SetKillCooldown(); + target.BlockRole(Duration.GetFloat()); + killer.Notify(GetString("EscortTargetHacked")); - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; - - killer.RpcRemoveAbilityUse(); - killer.SetKillCooldown(); - target.BlockRole(Duration.GetFloat()); - killer.Notify(GetString("EscortTargetHacked")); - - return false; - } + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/Express.cs b/Roles/Crewmate/Express.cs index a525b009d..b22f54d11 100644 --- a/Roles/Crewmate/Express.cs +++ b/Roles/Crewmate/Express.cs @@ -1,60 +1,59 @@ using System.Collections.Generic; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Express : RoleBase { - internal class Express : RoleBase + public static Dictionary SpeedUp = []; + public static Dictionary SpeedNormal = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary SpeedUp = []; - public static Dictionary SpeedNormal = []; + Options.SetupRoleOptions(5585, TabGroup.CrewmateRoles, CustomRoles.Express); - public static bool On; - public override bool IsEnable => On; + Options.ExpressSpeed = new FloatOptionItem(5587, "ExpressSpeed", new(0.25f, 5f, 0.25f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Express]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() + Options.ExpressSpeedDur = new IntegerOptionItem(5588, "ExpressSpeedDur", new(0, 90, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Express]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + if (!SpeedUp.ContainsKey(player.PlayerId)) SpeedNormal[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId]; + + Main.AllPlayerSpeed[player.PlayerId] = Options.ExpressSpeed.GetFloat(); + SpeedUp[player.PlayerId] = Utils.TimeStamp; + player.MarkDirtySettings(); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) return; + + byte playerId = player.PlayerId; + long now = Utils.TimeStamp; + + if (SpeedUp.TryGetValue(playerId, out long etime) && etime + Options.ExpressSpeedDur.GetInt() < now) { - Options.SetupRoleOptions(5585, TabGroup.CrewmateRoles, CustomRoles.Express); - - Options.ExpressSpeed = new FloatOptionItem(5587, "ExpressSpeed", new(0.25f, 5f, 0.25f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Express]) - .SetValueFormat(OptionFormat.Multiplier); - - Options.ExpressSpeedDur = new IntegerOptionItem(5588, "ExpressSpeedDur", new(0, 90, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Express]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) - { - if (!SpeedUp.ContainsKey(player.PlayerId)) SpeedNormal[player.PlayerId] = Main.AllPlayerSpeed[player.PlayerId]; - - Main.AllPlayerSpeed[player.PlayerId] = Options.ExpressSpeed.GetFloat(); - SpeedUp[player.PlayerId] = Utils.TimeStamp; + SpeedUp.Remove(playerId); + Main.AllPlayerSpeed[playerId] = SpeedNormal[playerId]; player.MarkDirtySettings(); } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask) return; - - byte playerId = player.PlayerId; - long now = Utils.TimeStamp; - - if (SpeedUp.TryGetValue(playerId, out long etime) && etime + Options.ExpressSpeedDur.GetInt() < now) - { - SpeedUp.Remove(playerId); - Main.AllPlayerSpeed[playerId] = SpeedNormal[playerId]; - player.MarkDirtySettings(); - } - } } } \ No newline at end of file diff --git a/Roles/Crewmate/Farseer.cs b/Roles/Crewmate/Farseer.cs index be5b99ab2..ec255c77b 100644 --- a/Roles/Crewmate/Farseer.cs +++ b/Roles/Crewmate/Farseer.cs @@ -10,165 +10,164 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Farseer : RoleBase { - public class Farseer : RoleBase + private const int Id = 9700; + + private const string FontSize = "1.7"; + public static Dictionary FarseerTimer = []; + public static Dictionary<(byte, byte), bool> IsRevealed = []; + + private static OptionItem FarseerCooldown; + private static OptionItem FarseerRevealTime; + private static OptionItem Vision; + public static OptionItem UsePet; + + public static readonly Dictionary RandomRole = []; + + public static bool On; + + private static CustomRoles[] RandomRolesForTrickster => Enum.GetValues().Where(x => x.IsCrewmate()).ToArray(); + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 9700; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Farseer); - private const string FontSize = "1.7"; - public static Dictionary FarseerTimer = []; - public static Dictionary<(byte, byte), bool> IsRevealed = []; + FarseerCooldown = new FloatOptionItem(Id + 10, "FarseerRevealCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Farseer]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem FarseerCooldown; - private static OptionItem FarseerRevealTime; - private static OptionItem Vision; - public static OptionItem UsePet; + FarseerRevealTime = new FloatOptionItem(Id + 11, "FarseerRevealTime", new(0f, 30f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Farseer]) + .SetValueFormat(OptionFormat.Seconds); - public static readonly Dictionary RandomRole = []; + Vision = new FloatOptionItem(Id + 12, "FarseerVision", new(0f, 1f, 0.05f), 0.25f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Farseer]) + .SetValueFormat(OptionFormat.Multiplier); - public static bool On; + UsePet = CreatePetUseSetting(Id + 13, CustomRoles.Farseer); + } - private static CustomRoles[] RandomRolesForTrickster => Enum.GetValues().Where(x => x.IsCrewmate()).ToArray(); - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + + foreach (PlayerControl ar in Main.AllPlayerControls) IsRevealed[(playerId, ar.PlayerId)] = false; + + RandomRole[playerId] = GetRandomCrewRoleString(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = FarseerCooldown.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + killer.SetKillCooldown(FarseerRevealTime.GetFloat()); + + if (!IsRevealed[(killer.PlayerId, target.PlayerId)] && !FarseerTimer.ContainsKey(killer.PlayerId)) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Farseer); - - FarseerCooldown = new FloatOptionItem(Id + 10, "FarseerRevealCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Farseer]) - .SetValueFormat(OptionFormat.Seconds); - - FarseerRevealTime = new FloatOptionItem(Id + 11, "FarseerRevealTime", new(0f, 30f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Farseer]) - .SetValueFormat(OptionFormat.Seconds); - - Vision = new FloatOptionItem(Id + 12, "FarseerVision", new(0f, 1f, 0.05f), 0.25f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Farseer]) - .SetValueFormat(OptionFormat.Multiplier); - - UsePet = CreatePetUseSetting(Id + 13, CustomRoles.Farseer); + FarseerTimer.TryAdd(killer.PlayerId, (target, 0f)); + NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + RPC.SetCurrentRevealTarget(killer.PlayerId, target.PlayerId); } - public override void Init() + return false; + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (GameStates.IsInTask && FarseerTimer.ContainsKey(player.PlayerId)) { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - - foreach (PlayerControl ar in Main.AllPlayerControls) IsRevealed[(playerId, ar.PlayerId)] = false; - - RandomRole[playerId] = GetRandomCrewRoleString(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = FarseerCooldown.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - killer.SetKillCooldown(FarseerRevealTime.GetFloat()); - - if (!IsRevealed[(killer.PlayerId, target.PlayerId)] && !FarseerTimer.ContainsKey(killer.PlayerId)) + if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) { - FarseerTimer.TryAdd(killer.PlayerId, (target, 0f)); - NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - RPC.SetCurrentRevealTarget(killer.PlayerId, target.PlayerId); + FarseerTimer.Remove(player.PlayerId); + NotifyRoles(SpecifySeer: player); + RPC.ResetCurrentRevealTarget(player.PlayerId); } - - return false; - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (GameStates.IsInTask && FarseerTimer.ContainsKey(player.PlayerId)) + else { - if (!player.IsAlive() || Pelican.IsEaten(player.PlayerId)) - { + PlayerControl arTarget = FarseerTimer[player.PlayerId].PLAYER; + float arTime = FarseerTimer[player.PlayerId].TIMER; + + if (!arTarget.IsAlive()) FarseerTimer.Remove(player.PlayerId); - NotifyRoles(SpecifySeer: player); + else if (arTime >= FarseerRevealTime.GetFloat()) + { + if (UsePets.GetBool() && UsePet.GetBool()) + player.AddKCDAsAbilityCD(); + else + player.SetKillCooldown(); + + FarseerTimer.Remove(player.PlayerId); + IsRevealed[(player.PlayerId, arTarget.PlayerId)] = true; + player.RpcSetRevealtPlayer(arTarget, true); + NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget); RPC.ResetCurrentRevealTarget(player.PlayerId); } else { - PlayerControl arTarget = FarseerTimer[player.PlayerId].PLAYER; - float arTime = FarseerTimer[player.PlayerId].TIMER; + float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; + float dis = Vector2.Distance(player.transform.position, arTarget.transform.position); - if (!arTarget.IsAlive()) - FarseerTimer.Remove(player.PlayerId); - else if (arTime >= FarseerRevealTime.GetFloat()) - { - if (UsePets.GetBool() && UsePet.GetBool()) - player.AddKCDAsAbilityCD(); - else - player.SetKillCooldown(); - - FarseerTimer.Remove(player.PlayerId); - IsRevealed[(player.PlayerId, arTarget.PlayerId)] = true; - player.RpcSetRevealtPlayer(arTarget, true); - NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget); - RPC.ResetCurrentRevealTarget(player.PlayerId); - } + if (dis <= range) + FarseerTimer[player.PlayerId] = (arTarget, arTime + Time.fixedDeltaTime); else { - float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - float dis = Vector2.Distance(player.transform.position, arTarget.transform.position); + FarseerTimer.Remove(player.PlayerId); + NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); + RPC.ResetCurrentRevealTarget(player.PlayerId); - if (dis <= range) - FarseerTimer[player.PlayerId] = (arTarget, arTime + Time.fixedDeltaTime); - else - { - FarseerTimer.Remove(player.PlayerId); - NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); - RPC.ResetCurrentRevealTarget(player.PlayerId); - - Logger.Info($"Canceled: {player.GetNameWithRole().RemoveHtmlTags()}", "Arsonist"); - } + Logger.Info($"Canceled: {player.GetNameWithRole().RemoveHtmlTags()}", "Arsonist"); } } } } + } - public static string GetRandomCrewRoleString() - { - CustomRoles randomRole = RandomRolesForTrickster.RandomElement(); + public static string GetRandomCrewRoleString() + { + CustomRoles randomRole = RandomRolesForTrickster.RandomElement(); - return $"{ColorString(GetRoleColor(randomRole), GetString(randomRole.ToString()))}"; - } + return $"{ColorString(GetRoleColor(randomRole), GetString(randomRole.ToString()))}"; + } - public static string GetTaskState() - { - KeyValuePair[] playersWithTasks = Main.PlayerStates.Where(a => a.Value.TaskState.HasTasks).ToArray(); - if (playersWithTasks.Length == 0) return "\r\n"; + public static string GetTaskState() + { + KeyValuePair[] playersWithTasks = Main.PlayerStates.Where(a => a.Value.TaskState.HasTasks).ToArray(); + if (playersWithTasks.Length == 0) return "\r\n"; - KeyValuePair randomPlayer = playersWithTasks.RandomElement(); - TaskState taskState = randomPlayer.Value.TaskState; + KeyValuePair randomPlayer = playersWithTasks.RandomElement(); + TaskState taskState = randomPlayer.Value.TaskState; - Color taskCompleteColor = Color.green; - Color nonCompleteColor = Color.yellow; - Color normalColor = taskState.IsTaskFinished ? taskCompleteColor : nonCompleteColor; + Color taskCompleteColor = Color.green; + Color nonCompleteColor = Color.yellow; + Color normalColor = taskState.IsTaskFinished ? taskCompleteColor : nonCompleteColor; - Color textColor = Camouflager.IsActive ? Color.gray : normalColor; - string completed = Camouflager.IsActive ? "?" : $"{taskState.CompletedTasksCount}"; + Color textColor = Camouflager.IsActive ? Color.gray : normalColor; + string completed = Camouflager.IsActive ? "?" : $"{taskState.CompletedTasksCount}"; - return $" " + ColorString(textColor, $"({completed}/{taskState.AllTasksCount})") + "\r\n"; - } + return $" " + ColorString(textColor, $"({completed}/{taskState.AllTasksCount})") + "\r\n"; } } \ No newline at end of file diff --git a/Roles/Crewmate/Gaulois.cs b/Roles/Crewmate/Gaulois.cs index 122b74900..749457933 100644 --- a/Roles/Crewmate/Gaulois.cs +++ b/Roles/Crewmate/Gaulois.cs @@ -2,83 +2,82 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Gaulois : RoleBase { - public class Gaulois : RoleBase + private const int Id = 643070; + private static List PlayerIdList = []; + + private static OptionItem CD; + private static OptionItem AdditionalSpeed; + private static OptionItem UseLimitOpt; + public static OptionItem UsePet; + + public static List IncreasedSpeedPlayerList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 643070; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Gaulois); - private static OptionItem CD; - private static OptionItem AdditionalSpeed; - private static OptionItem UseLimitOpt; - public static OptionItem UsePet; + CD = new FloatOptionItem(Id + 5, "AbilityCooldown", new(0f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gaulois]) + .SetValueFormat(OptionFormat.Seconds); - public static List IncreasedSpeedPlayerList = []; + AdditionalSpeed = new FloatOptionItem(Id + 6, "GauloisSpeedBoost", new(0f, 2f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gaulois]) + .SetValueFormat(OptionFormat.Multiplier); - public override bool IsEnable => PlayerIdList.Count > 0; + UseLimitOpt = new IntegerOptionItem(Id + 7, "AbilityUseLimit", new(1, 14, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gaulois]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Gaulois); + UsePet = CreatePetUseSetting(Id + 8, CustomRoles.Gaulois); + } - CD = new FloatOptionItem(Id + 5, "AbilityCooldown", new(0f, 60f, 2.5f), 30f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gaulois]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + PlayerIdList = []; + IncreasedSpeedPlayerList = []; + } - AdditionalSpeed = new FloatOptionItem(Id + 6, "GauloisSpeedBoost", new(0f, 2f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gaulois]) - .SetValueFormat(OptionFormat.Multiplier); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + } - UseLimitOpt = new IntegerOptionItem(Id + 7, "AbilityUseLimit", new(1, 14, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gaulois]) - .SetValueFormat(OptionFormat.Times); + public override void SetKillCooldown(byte playerId) + { + if (!IsEnable) return; - UsePet = CreatePetUseSetting(Id + 8, CustomRoles.Gaulois); - } + Main.AllPlayerKillCooldown[playerId] = playerId.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; + } - public override void Init() - { - PlayerIdList = []; - IncreasedSpeedPlayerList = []; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() >= 1; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override void SetKillCooldown(byte playerId) - { - if (!IsEnable) return; + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; - Main.AllPlayerKillCooldown[playerId] = playerId.GetAbilityUseLimit() > 0 ? CD.GetFloat() : 300f; - } + Main.AllPlayerSpeed[target.PlayerId] += AdditionalSpeed.GetFloat(); + IncreasedSpeedPlayerList.Add(target.PlayerId); - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() >= 1; - } + killer.RpcRemoveAbilityUse(); + killer.SetKillCooldown(); - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } + target.MarkDirtySettings(); - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable || killer == null || target == null || killer.GetAbilityUseLimit() <= 0) return false; - - Main.AllPlayerSpeed[target.PlayerId] += AdditionalSpeed.GetFloat(); - IncreasedSpeedPlayerList.Add(target.PlayerId); - - killer.RpcRemoveAbilityUse(); - killer.SetKillCooldown(); - - target.MarkDirtySettings(); - - return false; - } + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/Goose.cs b/Roles/Crewmate/Goose.cs index e5f4a321a..8e12897c5 100644 --- a/Roles/Crewmate/Goose.cs +++ b/Roles/Crewmate/Goose.cs @@ -1,42 +1,41 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Goose : RoleBase { - public class Goose : RoleBase + private const int Id = 641820; + + public static OptionItem OptionAbductTimerLimit; + public static OptionItem OptionSpeedDuringDrag; + public static OptionItem OptionVictimCanUseAbilities; + public static OptionItem CanBeGuessed; + public static OptionItem Cooldown; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 641820; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Goose); - public static OptionItem OptionAbductTimerLimit; - public static OptionItem OptionSpeedDuringDrag; - public static OptionItem OptionVictimCanUseAbilities; - public static OptionItem CanBeGuessed; - public static OptionItem Cooldown; + OptionAbductTimerLimit = new FloatOptionItem(Id + 11, "PenguinAbductTimerLimit", new(1f, 20f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + OptionSpeedDuringDrag = new FloatOptionItem(Id + 13, "PenguinSpeedDuringDrag", new(0.1f, 3f, 0.1f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Goose); + OptionVictimCanUseAbilities = new BooleanOptionItem(Id + 14, "PenguinVictimCanUseAbilities", false, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]); - OptionAbductTimerLimit = new FloatOptionItem(Id + 11, "PenguinAbductTimerLimit", new(1f, 20f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]) - .SetValueFormat(OptionFormat.Seconds); + CanBeGuessed = new BooleanOptionItem(Id + 15, "CanBeGuessed", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]); - OptionSpeedDuringDrag = new FloatOptionItem(Id + 13, "PenguinSpeedDuringDrag", new(0.1f, 3f, 0.1f), 1f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]) - .SetValueFormat(OptionFormat.Multiplier); - - OptionVictimCanUseAbilities = new BooleanOptionItem(Id + 14, "PenguinVictimCanUseAbilities", false, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]); - - CanBeGuessed = new BooleanOptionItem(Id + 15, "CanBeGuessed", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]); - - Cooldown = new FloatOptionItem(Id + 16, "AbilityCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + Cooldown = new FloatOptionItem(Id + 16, "AbilityCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Goose]) + .SetValueFormat(OptionFormat.Seconds); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Grappler.cs b/Roles/Crewmate/Grappler.cs index 1dd2d036e..0b6a78ac2 100644 --- a/Roles/Crewmate/Grappler.cs +++ b/Roles/Crewmate/Grappler.cs @@ -3,83 +3,82 @@ using System.Linq; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Grappler : RoleBase { - public class Grappler : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem AbilityUseLimit; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private byte GrapplerId; + private bool InUse; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(647250) + .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.3f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times); + } - private static OptionItem AbilityUseLimit; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - private byte GrapplerId; - private bool InUse; + public override void Init() + { + On = false; + Instances = []; + } - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + GrapplerId = playerId; + InUse = false; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - public override void SetupCustomOption() + public override void AfterMeetingTasks() + { + if (GrapplerId.GetAbilityUseLimit() >= 1f) { - StartSetup(647250) - .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) - .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.3f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) - .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times); + InUse = true; + GrapplerId.GetPlayer().RpcRemoveAbilityUse(); } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - GrapplerId = playerId; + else InUse = false; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - public override void AfterMeetingTasks() + Utils.SendRPC(CustomRPC.SyncRoleData, GrapplerId, InUse); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return base.GetProgressText(playerId, comms) + (InUse ? "<#00ff00>\u271a" : string.Empty); + } + + public void ReceiveRPC(MessageReader reader) + { + InUse = reader.ReadBoolean(); + } + + public static bool OnAnyoneCheckMurder(PlayerControl target) + { + if (new[] { SystemTypes.Electrical, SystemTypes.Reactor, SystemTypes.Laboratory, SystemTypes.LifeSupp, SystemTypes.Comms, SystemTypes.HeliSabotage, SystemTypes.MushroomMixupSabotage }.Any(Utils.IsActive)) return true; + + foreach (Grappler instance in Instances) { - if (GrapplerId.GetAbilityUseLimit() >= 1f) + if (instance.InUse) { - InUse = true; - GrapplerId.GetPlayer().RpcRemoveAbilityUse(); + target.TP(instance.GrapplerId.GetPlayer()); + instance.InUse = false; + Utils.SendRPC(CustomRPC.SyncRoleData, instance.GrapplerId, instance.InUse); + return false; } - else - InUse = false; - - Utils.SendRPC(CustomRPC.SyncRoleData, GrapplerId, InUse); } - public override string GetProgressText(byte playerId, bool comms) - { - return base.GetProgressText(playerId, comms) + (InUse ? "<#00ff00>\u271a" : string.Empty); - } - - public void ReceiveRPC(MessageReader reader) - { - InUse = reader.ReadBoolean(); - } - - public static bool OnAnyoneCheckMurder(PlayerControl target) - { - if (new[] { SystemTypes.Electrical, SystemTypes.Reactor, SystemTypes.Laboratory, SystemTypes.LifeSupp, SystemTypes.Comms, SystemTypes.HeliSabotage, SystemTypes.MushroomMixupSabotage }.Any(Utils.IsActive)) return true; - - foreach (Grappler instance in Instances) - { - if (instance.InUse) - { - target.TP(instance.GrapplerId.GetPlayer()); - instance.InUse = false; - Utils.SendRPC(CustomRPC.SyncRoleData, instance.GrapplerId, instance.InUse); - return false; - } - } - - return true; - } + return true; } } \ No newline at end of file diff --git a/Roles/Crewmate/Grenadier.cs b/Roles/Crewmate/Grenadier.cs index b8e1c223b..9a5a52fd8 100644 --- a/Roles/Crewmate/Grenadier.cs +++ b/Roles/Crewmate/Grenadier.cs @@ -4,150 +4,149 @@ using AmongUs.GameOptions; using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Grenadier : RoleBase { - internal class Grenadier : RoleBase + public static Dictionary GrenadierBlinding = []; + public static Dictionary MadGrenadierBlinding = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary GrenadierBlinding = []; - public static Dictionary MadGrenadierBlinding = []; + SetupRoleOptions(6800, TabGroup.CrewmateRoles, CustomRoles.Grenadier); - public static bool On; - public override bool IsEnable => On; + GrenadierSkillCooldown = new FloatOptionItem(6810, "GrenadierSkillCooldown", new(0f, 180f, 1f), 25f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + GrenadierSkillDuration = new FloatOptionItem(6811, "GrenadierSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) + .SetValueFormat(OptionFormat.Seconds); + + GrenadierCauseVision = new FloatOptionItem(6812, "GrenadierCauseVision", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) + .SetValueFormat(OptionFormat.Multiplier); + + GrenadierCanAffectNeutral = new BooleanOptionItem(6813, "GrenadierCanAffectNeutral", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]); + + GrenadierSkillMaxOfUseage = new IntegerOptionItem(6814, "GrenadierSkillMaxOfUseage", new(0, 180, 1), 2, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) + .SetValueFormat(OptionFormat.Times); + + GrenadierAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(6815, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) + .SetValueFormat(OptionFormat.Times); + + GrenadierAbilityChargesWhenFinishedTasks = new FloatOptionItem(6816, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(GrenadierSkillMaxOfUseage.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = GrenadierSkillCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1; + } + + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + + ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, GrenadierBlinding.ContainsKey(playerId))); + ProgressText.Append(Utils.GetTaskCount(playerId, comms)); + + return ProgressText.ToString(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("GrenadierVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("GrenadierVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + BlindPlayers(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + BlindPlayers(pc); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) return; + + byte playerId = player.PlayerId; + long now = Utils.TimeStamp; + + if (GrenadierBlinding.TryGetValue(playerId, out long gtime) && gtime + GrenadierSkillDuration.GetInt() < now) { - SetupRoleOptions(6800, TabGroup.CrewmateRoles, CustomRoles.Grenadier); - - GrenadierSkillCooldown = new FloatOptionItem(6810, "GrenadierSkillCooldown", new(0f, 180f, 1f), 25f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) - .SetValueFormat(OptionFormat.Seconds); - - GrenadierSkillDuration = new FloatOptionItem(6811, "GrenadierSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) - .SetValueFormat(OptionFormat.Seconds); - - GrenadierCauseVision = new FloatOptionItem(6812, "GrenadierCauseVision", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) - .SetValueFormat(OptionFormat.Multiplier); - - GrenadierCanAffectNeutral = new BooleanOptionItem(6813, "GrenadierCanAffectNeutral", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]); - - GrenadierSkillMaxOfUseage = new IntegerOptionItem(6814, "GrenadierSkillMaxOfUseage", new(0, 180, 1), 2, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) - .SetValueFormat(OptionFormat.Times); - - GrenadierAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(6815, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) - .SetValueFormat(OptionFormat.Times); - - GrenadierAbilityChargesWhenFinishedTasks = new FloatOptionItem(6816, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Grenadier]) - .SetValueFormat(OptionFormat.Times); + GrenadierBlinding.Remove(playerId); + player.RpcResetAbilityCooldown(); + player.Notify(string.Format(Translator.GetString("GrenadierSkillStop"), (int)player.GetAbilityUseLimit())); + Utils.MarkEveryoneDirtySettingsV3(); } - public override void Add(byte playerId) + if (MadGrenadierBlinding.TryGetValue(playerId, out long mgtime) && mgtime + GrenadierSkillDuration.GetInt() < now) { - On = true; - playerId.SetAbilityUseLimit(GrenadierSkillMaxOfUseage.GetInt()); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = GrenadierSkillCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1; - } - - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); - - ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, GrenadierBlinding.ContainsKey(playerId))); - ProgressText.Append(Utils.GetTaskCount(playerId, comms)); - - return ProgressText.ToString(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("GrenadierVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("GrenadierVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - BlindPlayers(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - BlindPlayers(pc); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask) return; - - byte playerId = player.PlayerId; - long now = Utils.TimeStamp; - - if (GrenadierBlinding.TryGetValue(playerId, out long gtime) && gtime + GrenadierSkillDuration.GetInt() < now) - { - GrenadierBlinding.Remove(playerId); - player.RpcResetAbilityCooldown(); - player.Notify(string.Format(Translator.GetString("GrenadierSkillStop"), (int)player.GetAbilityUseLimit())); - Utils.MarkEveryoneDirtySettingsV3(); - } - - if (MadGrenadierBlinding.TryGetValue(playerId, out long mgtime) && mgtime + GrenadierSkillDuration.GetInt() < now) - { - MadGrenadierBlinding.Remove(playerId); - player.RpcResetAbilityCooldown(); - player.Notify(string.Format(Translator.GetString("GrenadierSkillStop"), (int)player.GetAbilityUseLimit())); - Utils.MarkEveryoneDirtySettingsV3(); - } - } - - private static void BlindPlayers(PlayerControl pc) - { - if (GrenadierBlinding.ContainsKey(pc.PlayerId) || MadGrenadierBlinding.ContainsKey(pc.PlayerId)) return; - - if (pc.GetAbilityUseLimit() >= 1) - { - if (pc.Is(CustomRoles.Madmate)) - { - MadGrenadierBlinding.Remove(pc.PlayerId); - MadGrenadierBlinding.Add(pc.PlayerId, Utils.TimeStamp); - Main.AllPlayerControls.Where(x => x.IsModClient()).Where(x => !x.GetCustomRole().IsImpostorTeam() && !x.Is(CustomRoles.Madmate)).Do(x => x.RPCPlayCustomSound("FlashBang")); - } - else - { - GrenadierBlinding.Remove(pc.PlayerId); - GrenadierBlinding.Add(pc.PlayerId, Utils.TimeStamp); - Main.AllPlayerControls.Where(x => x.IsModClient()).Where(x => x.IsImpostor() || (x.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool())).Do(x => x.RPCPlayCustomSound("FlashBang")); - } - - pc.RPCPlayCustomSound("FlashBang"); - pc.Notify(Translator.GetString("GrenadierSkillInUse"), GrenadierSkillDuration.GetFloat()); - pc.RpcRemoveAbilityUse(); - Utils.MarkEveryoneDirtySettingsV3(); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + MadGrenadierBlinding.Remove(playerId); + player.RpcResetAbilityCooldown(); + player.Notify(string.Format(Translator.GetString("GrenadierSkillStop"), (int)player.GetAbilityUseLimit())); + Utils.MarkEveryoneDirtySettingsV3(); } } + + private static void BlindPlayers(PlayerControl pc) + { + if (GrenadierBlinding.ContainsKey(pc.PlayerId) || MadGrenadierBlinding.ContainsKey(pc.PlayerId)) return; + + if (pc.GetAbilityUseLimit() >= 1) + { + if (pc.Is(CustomRoles.Madmate)) + { + MadGrenadierBlinding.Remove(pc.PlayerId); + MadGrenadierBlinding.Add(pc.PlayerId, Utils.TimeStamp); + Main.AllPlayerControls.Where(x => x.IsModClient()).Where(x => !x.GetCustomRole().IsImpostorTeam() && !x.Is(CustomRoles.Madmate)).Do(x => x.RPCPlayCustomSound("FlashBang")); + } + else + { + GrenadierBlinding.Remove(pc.PlayerId); + GrenadierBlinding.Add(pc.PlayerId, Utils.TimeStamp); + Main.AllPlayerControls.Where(x => x.IsModClient()).Where(x => x.IsImpostor() || (x.GetCustomRole().IsNeutral() && GrenadierCanAffectNeutral.GetBool())).Do(x => x.RPCPlayCustomSound("FlashBang")); + } + + pc.RPCPlayCustomSound("FlashBang"); + pc.Notify(Translator.GetString("GrenadierSkillInUse"), GrenadierSkillDuration.GetFloat()); + pc.RpcRemoveAbilityUse(); + Utils.MarkEveryoneDirtySettingsV3(); + } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Guardian.cs b/Roles/Crewmate/Guardian.cs index 77b6e323d..8c2d747a8 100644 --- a/Roles/Crewmate/Guardian.cs +++ b/Roles/Crewmate/Guardian.cs @@ -1,29 +1,28 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Guardian : RoleBase { - internal class Guardian : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !target.AllTasksCompleted(); + } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !target.AllTasksCompleted(); - } - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(9200, TabGroup.CrewmateRoles, CustomRoles.Guardian); - Options.OverrideTasksData.Create(9210, TabGroup.CrewmateRoles, CustomRoles.Guardian); - } + public override void SetupCustomOption() + { + Options.SetupRoleOptions(9200, TabGroup.CrewmateRoles, CustomRoles.Guardian); + Options.OverrideTasksData.Create(9210, TabGroup.CrewmateRoles, CustomRoles.Guardian); } } \ No newline at end of file diff --git a/Roles/Crewmate/GuessManager.cs b/Roles/Crewmate/GuessManager.cs index bfc26b53c..e76f42a2f 100644 --- a/Roles/Crewmate/GuessManager.cs +++ b/Roles/Crewmate/GuessManager.cs @@ -1,32 +1,31 @@ using System.Collections.Generic; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class GuessManagerRole : RoleBase { - public class GuessManagerRole : RoleBase + public static List PlayerIdList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - public static List PlayerIdList = []; + Options.SetupRoleOptions(642640, TabGroup.CrewmateRoles, CustomRoles.GuessManagerRole); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(642640, TabGroup.CrewmateRoles, CustomRoles.GuessManagerRole); - } + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public static void OnGuess(PlayerControl dp, PlayerControl pc) - { - foreach (byte guessManager in PlayerIdList) LateTask.New(() => { Utils.SendMessage(dp == pc ? string.Format(GetString("GuessManagerMessageAboutMisguess"), dp.GetRealName().Replace("\n", " + ")) : string.Format(GetString("GuessManagerMessageAboutGuessedRole"), dp.GetAllRoleName().Replace("\n", " + ")), guessManager); }, 1f, "Guess Manager Messages"); - } + public static void OnGuess(PlayerControl dp, PlayerControl pc) + { + foreach (byte guessManager in PlayerIdList) LateTask.New(() => { Utils.SendMessage(dp == pc ? string.Format(GetString("GuessManagerMessageAboutMisguess"), dp.GetRealName().Replace("\n", " + ")) : string.Format(GetString("GuessManagerMessageAboutGuessedRole"), dp.GetAllRoleName().Replace("\n", " + ")), guessManager); }, 1f, "Guess Manager Messages"); } } \ No newline at end of file diff --git a/Roles/Crewmate/Inquirer.cs b/Roles/Crewmate/Inquirer.cs index a02349dfe..c051cc10d 100644 --- a/Roles/Crewmate/Inquirer.cs +++ b/Roles/Crewmate/Inquirer.cs @@ -1,40 +1,39 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Inquirer : RoleBase { - public class Inquirer : RoleBase + public static OptionItem FailChance; + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachTaskCompleted; + private static OptionItem AbilityChargesWhenFinishedTasks; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public static OptionItem FailChance; - private static OptionItem AbilityUseLimit; - private static OptionItem AbilityUseGainWithEachTaskCompleted; - private static OptionItem AbilityChargesWhenFinishedTasks; + Options.SetupRoleOptions(649710, TabGroup.CrewmateRoles, CustomRoles.Inquirer); - public override bool IsEnable => false; + FailChance = new IntegerOptionItem(649712, "Inquirer.FailChance", new(0, 100, 5), 20, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) + .SetValueFormat(OptionFormat.Percent); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(649710, TabGroup.CrewmateRoles, CustomRoles.Inquirer); + AbilityUseLimit = new IntegerOptionItem(649713, "Inquirer.AbilityUseLimit", new(0, 10, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) + .SetValueFormat(OptionFormat.Times); - FailChance = new IntegerOptionItem(649712, "Inquirer.FailChance", new(0, 100, 5), 20, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) - .SetValueFormat(OptionFormat.Percent); + AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(649714, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.8f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) + .SetValueFormat(OptionFormat.Times); - AbilityUseLimit = new IntegerOptionItem(649713, "Inquirer.AbilityUseLimit", new(0, 10, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) - .SetValueFormat(OptionFormat.Times); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(649715, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) + .SetValueFormat(OptionFormat.Times); + } - AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(649714, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.8f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) - .SetValueFormat(OptionFormat.Times); + public override void Init() { } - AbilityChargesWhenFinishedTasks = new FloatOptionItem(649715, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Inquirer]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() { } - - public override void Add(byte playerId) - { - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } + public override void Add(byte playerId) + { + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); } } \ No newline at end of file diff --git a/Roles/Crewmate/Insight.cs b/Roles/Crewmate/Insight.cs index 2ee37ad02..4b9fa1c9d 100644 --- a/Roles/Crewmate/Insight.cs +++ b/Roles/Crewmate/Insight.cs @@ -1,56 +1,55 @@ using System.Collections.Generic; using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Insight : RoleBase { - internal class Insight : RoleBase + public static bool On; + private byte InsightId; + private List KnownRolesOfPlayerIds = []; + private List RolesKnownThisRound = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private byte InsightId; - private List KnownRolesOfPlayerIds = []; - private List RolesKnownThisRound = []; - public override bool IsEnable => On; + Options.SetupRoleOptions(5650, TabGroup.CrewmateRoles, CustomRoles.Insight); + Options.OverrideTasksData.Create(5653, TabGroup.CrewmateRoles, CustomRoles.Insight); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + InsightId = playerId; + KnownRolesOfPlayerIds = []; + RolesKnownThisRound = []; + } + + public override void Init() + { + On = false; + } + + public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + List list = Main.AllPlayerControls.Where(x => !KnownRolesOfPlayerIds.Contains(x.PlayerId) && !x.Is(CustomRoles.Insight) && !x.Is(CustomRoles.GM) && !x.Is(CustomRoles.NotAssigned)).ToList(); + + if (list.Count != 0) { - Options.SetupRoleOptions(5650, TabGroup.CrewmateRoles, CustomRoles.Insight); - Options.OverrideTasksData.Create(5653, TabGroup.CrewmateRoles, CustomRoles.Insight); - } - - public override void Add(byte playerId) - { - On = true; - InsightId = playerId; - KnownRolesOfPlayerIds = []; - RolesKnownThisRound = []; - } - - public override void Init() - { - On = false; - } - - public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) - { - List list = Main.AllPlayerControls.Where(x => !KnownRolesOfPlayerIds.Contains(x.PlayerId) && !x.Is(CustomRoles.Insight) && !x.Is(CustomRoles.GM) && !x.Is(CustomRoles.NotAssigned)).ToList(); - - if (list.Count != 0) - { - PlayerControl target = list.RandomElement(); - KnownRolesOfPlayerIds.Add(target.PlayerId); - CustomRoles role = target.GetCustomRole(); - player.Notify(string.Format(Translator.GetString("InsightNotify"), role.ToColoredString())); - RolesKnownThisRound.Add(role); - } - } - - public override void OnReportDeadBody() - { - LateTask.New(() => - { - RolesKnownThisRound.Do(x => Utils.SendMessage("\n", InsightId, string.Format(Translator.GetString("InsightNotify"), x.ToColoredString()))); - RolesKnownThisRound = []; - }, 10f, log: false); + PlayerControl target = list.RandomElement(); + KnownRolesOfPlayerIds.Add(target.PlayerId); + CustomRoles role = target.GetCustomRole(); + player.Notify(string.Format(Translator.GetString("InsightNotify"), role.ToColoredString())); + RolesKnownThisRound.Add(role); } } + + public override void OnReportDeadBody() + { + LateTask.New(() => + { + RolesKnownThisRound.Do(x => Utils.SendMessage("\n", InsightId, string.Format(Translator.GetString("InsightNotify"), x.ToColoredString()))); + RolesKnownThisRound = []; + }, 10f, log: false); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Jailer.cs b/Roles/Crewmate/Jailer.cs index 5bd712bba..76231b267 100644 --- a/Roles/Crewmate/Jailer.cs +++ b/Roles/Crewmate/Jailer.cs @@ -5,129 +5,128 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Jailor : RoleBase { - public class Jailor : RoleBase + private const int Id = 63420; + public static List PlayerIdList = []; + + private static OptionItem JailCooldown; + private static OptionItem NotifyJailedOnMeeting; + public static OptionItem UsePet; + private bool JailorDidVote; + + public byte JailorTarget; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 63420; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Jailor); - private static OptionItem JailCooldown; - private static OptionItem NotifyJailedOnMeeting; - public static OptionItem UsePet; - private bool JailorDidVote; + JailCooldown = new FloatOptionItem(Id + 10, "JailorJailCooldown", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jailor]) + .SetValueFormat(OptionFormat.Seconds); - public byte JailorTarget; + NotifyJailedOnMeeting = new BooleanOptionItem(Id + 18, "notifyJailedOnMeeting", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jailor]); - public override bool IsEnable => PlayerIdList.Count > 0; + UsePet = CreatePetUseSetting(Id + 11, CustomRoles.Jailor); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + JailorTarget = byte.MaxValue; + JailorDidVote = false; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + JailorTarget = byte.MaxValue; + JailorDidVote = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).IsAlive() ? JailCooldown.GetFloat() : 0f; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + private void SendRPC(byte jailerId, byte targetId = byte.MaxValue, bool setTarget = true) + { + if (!Utils.DoRPC) return; + + MessageWriter writer; + + if (!setTarget) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Jailor); - - JailCooldown = new FloatOptionItem(Id + 10, "JailorJailCooldown", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jailor]) - .SetValueFormat(OptionFormat.Seconds); - - NotifyJailedOnMeeting = new BooleanOptionItem(Id + 18, "notifyJailedOnMeeting", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jailor]); - - UsePet = CreatePetUseSetting(Id + 11, CustomRoles.Jailor); - } - - public override void Init() - { - PlayerIdList = []; - JailorTarget = byte.MaxValue; - JailorDidVote = false; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - JailorTarget = byte.MaxValue; - JailorDidVote = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).IsAlive() ? JailCooldown.GetFloat() : 0f; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - private void SendRPC(byte jailerId, byte targetId = byte.MaxValue, bool setTarget = true) - { - if (!Utils.DoRPC) return; - - MessageWriter writer; - - if (!setTarget) - { - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetJailorExeLimit, SendOption.Reliable); - writer.Write(jailerId); - writer.Write(JailorDidVote); - AmongUsClient.Instance.FinishRpcImmediately(writer); - return; - } - - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetJailorTarget, SendOption.Reliable); + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetJailorExeLimit, SendOption.Reliable); writer.Write(jailerId); - writer.Write(targetId); + writer.Write(JailorDidVote); AmongUsClient.Instance.FinishRpcImmediately(writer); + return; } - public static void ReceiveRPC(MessageReader reader, bool setTarget = true) + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetJailorTarget, SendOption.Reliable); + writer.Write(jailerId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader, bool setTarget = true) + { + byte jailerId = reader.ReadByte(); + if (Main.PlayerStates[jailerId].Role is not Jailor jl) return; + + if (!setTarget) { - byte jailerId = reader.ReadByte(); - if (Main.PlayerStates[jailerId].Role is not Jailor jl) return; - - if (!setTarget) - { - bool didvote = reader.ReadBoolean(); - jl.JailorDidVote = didvote; - } - - byte targetId = reader.ReadByte(); - jl.JailorTarget = targetId; + bool didvote = reader.ReadBoolean(); + jl.JailorDidVote = didvote; } - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + byte targetId = reader.ReadByte(); + jl.JailorTarget = targetId; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + + if (JailorTarget != byte.MaxValue) { - if (killer == null || target == null) return false; - - if (JailorTarget != byte.MaxValue) - { - killer.Notify(GetString("JailorTargetAlreadySelected")); - return false; - } - - JailorTarget = target.PlayerId; - killer.Notify(GetString("SuccessfullyJailed")); - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - SendRPC(killer.PlayerId, target.PlayerId); + killer.Notify(GetString("JailorTargetAlreadySelected")); return false; } - public override void OnReportDeadBody() - { - if (!NotifyJailedOnMeeting.GetBool()) return; + JailorTarget = target.PlayerId; + killer.Notify(GetString("SuccessfullyJailed")); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + SendRPC(killer.PlayerId, target.PlayerId); + return false; + } - if (JailorTarget == byte.MaxValue) return; + public override void OnReportDeadBody() + { + if (!NotifyJailedOnMeeting.GetBool()) return; - PlayerControl tpc = Utils.GetPlayerById(JailorTarget); - if (tpc == null) return; + if (JailorTarget == byte.MaxValue) return; - if (tpc.IsAlive()) LateTask.New(() => { Utils.SendMessage(GetString("JailedNotifyMsg"), JailorTarget, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); }, 0.3f, "JailorNotifyJailed"); - } + PlayerControl tpc = Utils.GetPlayerById(JailorTarget); + if (tpc == null) return; + + if (tpc.IsAlive()) LateTask.New(() => { Utils.SendMessage(GetString("JailedNotifyMsg"), JailorTarget, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); }, 0.3f, "JailorNotifyJailed"); } } \ No newline at end of file diff --git a/Roles/Crewmate/Journalist.cs b/Roles/Crewmate/Journalist.cs index 9c7ac2a74..5aa5e1762 100644 --- a/Roles/Crewmate/Journalist.cs +++ b/Roles/Crewmate/Journalist.cs @@ -2,93 +2,92 @@ using System.Collections.Generic; using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Journalist : RoleBase { - public class Journalist : RoleBase + public static bool On; + private byte JournalistId; + private List Notes; + private bool Sent; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private byte JournalistId; - private List Notes; - private bool Sent; + StartSetup(647450); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + Notes = []; + Sent = false; + JournalistId = playerId; + } + + public static void OnReceiveCommand(PlayerControl pc, string[] args) + { + if (Main.PlayerStates[pc.PlayerId].Role is not Journalist journalist) return; + + var error = string.Empty; + + try { - StartSetup(647450); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - Notes = []; - Sent = false; - JournalistId = playerId; - } - - public static void OnReceiveCommand(PlayerControl pc, string[] args) - { - if (Main.PlayerStates[pc.PlayerId].Role is not Journalist journalist) return; - - var error = string.Empty; - - try + switch (args[1]) { - switch (args[1]) - { - case "add": - journalist.Notes.Add(string.Join(' ', args[2..])); - break; - case "remove": - if (args.Length == 2 || !int.TryParse(args[2], out int index)) index = journalist.Notes.Count; + case "add": + journalist.Notes.Add(string.Join(' ', args[2..])); + break; + case "remove": + if (args.Length == 2 || !int.TryParse(args[2], out int index)) index = journalist.Notes.Count; - index--; - journalist.Notes.RemoveAt(index); - break; - case "clear": - journalist.Notes.Clear(); - break; - case "view": - Utils.SendMessage(string.Join('\n', journalist.Notes.Select((x, i) => $"{i + 1}. {x}")), pc.PlayerId, Translator.GetString("JournalistNotesTitle")); - break; - default: - error = Translator.GetString("JournalistError.InvalidArgument"); - break; - } + index--; + journalist.Notes.RemoveAt(index); + break; + case "clear": + journalist.Notes.Clear(); + break; + case "view": + Utils.SendMessage(string.Join('\n', journalist.Notes.Select((x, i) => $"{i + 1}. {x}")), pc.PlayerId, Translator.GetString("JournalistNotesTitle")); + break; + default: + error = Translator.GetString("JournalistError.InvalidArgument"); + break; } - catch (Exception e) - { - string usage = Translator.GetString("Journalist.CommandUsage"); - - switch (e) - { - case IndexOutOfRangeException: // Not enough arguments - error = $"{Translator.GetString("JournalistError.IndexOutOfRange")}\n{usage}"; - break; - case ArgumentOutOfRangeException: // Invalid index for remove - error = $"{Translator.GetString("JournalistError.ArgumentOutOfRange")}\n{usage}"; - break; - default: - error = $"{Translator.GetString("JournalistError.Unknown")}\n{usage}"; - Utils.ThrowException(e); - break; - } - } - - if (error != string.Empty) Utils.SendMessage(error, pc.PlayerId); } - - public override void OnReportDeadBody() + catch (Exception e) { - if (Sent || JournalistId.GetPlayer()?.IsAlive() == true) return; + string usage = Translator.GetString("Journalist.CommandUsage"); - LateTask.New(() => Utils.SendMessage(string.Join('\n', Notes), title: Translator.GetString("JournalistNotesTitle")), 10f, "Send Journalist Notes"); - Sent = true; + switch (e) + { + case IndexOutOfRangeException: // Not enough arguments + error = $"{Translator.GetString("JournalistError.IndexOutOfRange")}\n{usage}"; + break; + case ArgumentOutOfRangeException: // Invalid index for remove + error = $"{Translator.GetString("JournalistError.ArgumentOutOfRange")}\n{usage}"; + break; + default: + error = $"{Translator.GetString("JournalistError.Unknown")}\n{usage}"; + Utils.ThrowException(e); + break; + } } + + if (error != string.Empty) Utils.SendMessage(error, pc.PlayerId); + } + + public override void OnReportDeadBody() + { + if (Sent || JournalistId.GetPlayer()?.IsAlive() == true) return; + + LateTask.New(() => Utils.SendMessage(string.Join('\n', Notes), title: Translator.GetString("JournalistNotesTitle")), 10f, "Send Journalist Notes"); + Sent = true; } } \ No newline at end of file diff --git a/Roles/Crewmate/Judge.cs b/Roles/Crewmate/Judge.cs index 043a2db62..92ca79b8a 100644 --- a/Roles/Crewmate/Judge.cs +++ b/Roles/Crewmate/Judge.cs @@ -9,340 +9,339 @@ using UnityEngine; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Judge : RoleBase { - public class Judge : RoleBase + private const int Id = 9300; + private static List PlayerIdList = []; + + private static OptionItem TrialLimitPerMeeting; + private static OptionItem TrialLimitPerGame; + private static OptionItem AbilityUseLimit; + + private static OptionItem TryHideMsg; + private static OptionItem CanTrialMadmate; + + private static OptionItem CanTrialConverted; + + private static OptionItem CanTrialCrewKilling; + private static OptionItem CanTrialNeutralB; + private static OptionItem CanTrialNeutralK; + private static OptionItem CanTrialNeutralE; + public static OptionItem JudgeAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private static Dictionary MeetingUseLimit = []; + private static Dictionary TotalUseLimit = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 9300; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Judge); + TrialLimitPerMeeting = new FloatOptionItem(Id + 10, "TrialLimitPerMeeting", new(0f, 15f, 1f), 1f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); + TrialLimitPerGame = new FloatOptionItem(Id + 9, "TrialLimitPerGame", new(0f, 30f, 1f), 3f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); + AbilityUseLimit = new FloatOptionItem(Id + 18, "AbilityUseLimit", new(0f, 30f, 1f), 1f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); + CanTrialMadmate = new BooleanOptionItem(Id + 12, "JudgeCanTrialMadmate", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialConverted = new BooleanOptionItem(Id + 16, "JudgeCanTrialConverted", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialCrewKilling = new BooleanOptionItem(Id + 13, "JudgeCanTrialnCrewKilling", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialNeutralB = new BooleanOptionItem(Id + 14, "JudgeCanTrialNeutralB", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialNeutralE = new BooleanOptionItem(Id + 17, "JudgeCanTrialNeutralE", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + CanTrialNeutralK = new BooleanOptionItem(Id + 15, "JudgeCanTrialNeutralK", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); + TryHideMsg = new BooleanOptionItem(Id + 11, "JudgeTryHideMsg", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetColor(Color.green); + JudgeAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 19, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 20, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); + } - private static OptionItem TrialLimitPerMeeting; - private static OptionItem TrialLimitPerGame; - private static OptionItem AbilityUseLimit; + public override void Init() + { + PlayerIdList = []; + MeetingUseLimit = []; + TotalUseLimit = []; + } - private static OptionItem TryHideMsg; - private static OptionItem CanTrialMadmate; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + MeetingUseLimit[playerId] = TrialLimitPerMeeting.GetInt(); + TotalUseLimit[playerId] = TrialLimitPerGame.GetInt(); + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - private static OptionItem CanTrialConverted; + public override void OnReportDeadBody() + { + byte[] list = [.. PlayerIdList]; + foreach (byte pid in list) MeetingUseLimit[pid] = TrialLimitPerMeeting.GetInt(); + } - private static OptionItem CanTrialCrewKilling; - private static OptionItem CanTrialNeutralB; - private static OptionItem CanTrialNeutralK; - private static OptionItem CanTrialNeutralE; - public static OptionItem JudgeAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) + { + string originMsg = msg; - private static Dictionary MeetingUseLimit = []; - private static Dictionary TotalUseLimit = []; + if (!AmongUsClient.Instance.AmHost) return false; - public override bool IsEnable => PlayerIdList.Count > 0; + if (!GameStates.IsInGame || pc == null) return false; - public override void SetupCustomOption() + if (!pc.Is(CustomRoles.Judge)) return false; + + int operate; // 1:ID 2:Trial + msg = msg.ToLower().TrimStart().TrimEnd(); + + if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) + operate = 1; + else if (CheckCommand(ref msg, "shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|trial|审判|判|审", false)) + operate = 2; + else + return false; + + if (!pc.IsAlive()) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Judge); - TrialLimitPerMeeting = new FloatOptionItem(Id + 10, "TrialLimitPerMeeting", new(0f, 15f, 1f), 1f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); - TrialLimitPerGame = new FloatOptionItem(Id + 9, "TrialLimitPerGame", new(0f, 30f, 1f), 3f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); - AbilityUseLimit = new FloatOptionItem(Id + 18, "AbilityUseLimit", new(0f, 30f, 1f), 1f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); - CanTrialMadmate = new BooleanOptionItem(Id + 12, "JudgeCanTrialMadmate", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); - CanTrialConverted = new BooleanOptionItem(Id + 16, "JudgeCanTrialConverted", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); - CanTrialCrewKilling = new BooleanOptionItem(Id + 13, "JudgeCanTrialnCrewKilling", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); - CanTrialNeutralB = new BooleanOptionItem(Id + 14, "JudgeCanTrialNeutralB", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); - CanTrialNeutralE = new BooleanOptionItem(Id + 17, "JudgeCanTrialNeutralE", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); - CanTrialNeutralK = new BooleanOptionItem(Id + 15, "JudgeCanTrialNeutralK", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]); - TryHideMsg = new BooleanOptionItem(Id + 11, "JudgeTryHideMsg", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetColor(Color.green); - JudgeAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 19, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 20, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Judge]).SetValueFormat(OptionFormat.Times); + Utils.SendMessage(GetString("JudgeDead"), pc.PlayerId); + return true; } - public override void Init() + switch (operate) { - PlayerIdList = []; - MeetingUseLimit = []; - TotalUseLimit = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - MeetingUseLimit[playerId] = TrialLimitPerMeeting.GetInt(); - TotalUseLimit[playerId] = TrialLimitPerGame.GetInt(); - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - public override void OnReportDeadBody() - { - byte[] list = [.. PlayerIdList]; - foreach (byte pid in list) MeetingUseLimit[pid] = TrialLimitPerMeeting.GetInt(); - } - - public static bool TrialMsg(PlayerControl pc, string msg, bool isUI = false) - { - string originMsg = msg; - - if (!AmongUsClient.Instance.AmHost) return false; - - if (!GameStates.IsInGame || pc == null) return false; - - if (!pc.Is(CustomRoles.Judge)) return false; - - int operate; // 1:ID 2:Trial - msg = msg.ToLower().TrimStart().TrimEnd(); - - if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) - operate = 1; - else if (CheckCommand(ref msg, "shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|trial|审判|判|审", false)) - operate = 2; - else - return false; - - if (!pc.IsAlive()) - { - Utils.SendMessage(GetString("JudgeDead"), pc.PlayerId); + case 1: + Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); return true; - } - - switch (operate) + case 2: { - case 1: - Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); - return true; - case 2: - { - if (TryHideMsg.GetBool()) /*GuessManager.TryHideMsg();*/ - ChatManager.SendPreviousMessagesToAll(); - else if (pc.AmOwner) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + if (TryHideMsg.GetBool()) /*GuessManager.TryHideMsg();*/ + ChatManager.SendPreviousMessagesToAll(); + else if (pc.AmOwner) Utils.SendMessage(originMsg, 255, pc.GetRealName()); - if (!MsgToPlayerAndRole(msg, out byte targetId, out string error)) + if (!MsgToPlayerAndRole(msg, out byte targetId, out string error)) + { + Utils.SendMessage(error, pc.PlayerId); + return true; + } + + PlayerControl target = Utils.GetPlayerById(targetId); + + if (target != null) + { + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} trialed {target.GetNameWithRole().RemoveHtmlTags()}", "Judge"); + bool judgeSuicide; + + if (pc.GetAbilityUseLimit() < 1 || MeetingUseLimit[pc.PlayerId] < 1 || TotalUseLimit[pc.PlayerId] < 1) { - Utils.SendMessage(error, pc.PlayerId); + if (!isUI) + Utils.SendMessage(GetString("JudgeTrialMax"), pc.PlayerId); + else + pc.ShowPopUp(GetString("JudgeTrialMax")); + return true; } - PlayerControl target = Utils.GetPlayerById(targetId); - - if (target != null) + if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == target.PlayerId)) { - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} trialed {target.GetNameWithRole().RemoveHtmlTags()}", "Judge"); - bool judgeSuicide; + if (!isUI) + Utils.SendMessage(GetString("CanNotTrialJailed"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); + else + pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle")) + "\n" + GetString("CanNotTrialJailed")); - if (pc.GetAbilityUseLimit() < 1 || MeetingUseLimit[pc.PlayerId] < 1 || TotalUseLimit[pc.PlayerId] < 1) - { - if (!isUI) - Utils.SendMessage(GetString("JudgeTrialMax"), pc.PlayerId); - else - pc.ShowPopUp(GetString("JudgeTrialMax")); + return true; + } - return true; - } + if (Medic.InProtect(target.PlayerId) && !Medic.JudgingIgnoreShield.GetBool()) + { + if (!isUI) + Utils.SendMessage(GetString("CanNotTrialProtected"), pc.PlayerId, CustomRoles.Medic.ToColoredString()); + else + pc.ShowPopUp($"{CustomRoles.Medic.ToColoredString()}\n{GetString("CanNotTrialProtected")}"); - if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == target.PlayerId)) - { - if (!isUI) - Utils.SendMessage(GetString("CanNotTrialJailed"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); - else - pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle")) + "\n" + GetString("CanNotTrialJailed")); + return true; + } - return true; - } + if (pc.PlayerId == target.PlayerId) + { + if (!isUI) + Utils.SendMessage(GetString("LaughToWhoTrialSelf"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))); + else + pc.ShowPopUp(Utils.ColorString(Color.cyan, GetString("MessageFromKPD")) + "\n" + GetString("LaughToWhoTrialSelf")); - if (Medic.InProtect(target.PlayerId) && !Medic.JudgingIgnoreShield.GetBool()) - { - if (!isUI) - Utils.SendMessage(GetString("CanNotTrialProtected"), pc.PlayerId, CustomRoles.Medic.ToColoredString()); - else - pc.ShowPopUp($"{CustomRoles.Medic.ToColoredString()}\n{GetString("CanNotTrialProtected")}"); + judgeSuicide = true; + } + else if (pc.Is(CustomRoles.Madmate)) + judgeSuicide = false; + else if (pc.Is(CustomRoles.Charmed)) + judgeSuicide = false; + else if (pc.Is(CustomRoles.Recruit)) + judgeSuicide = false; + else if (pc.Is(CustomRoles.Contagious)) + judgeSuicide = false; + else if (target.Is(CustomRoles.Rascal)) + judgeSuicide = false; + else if (target.Is(CustomRoles.Pestilence)) + judgeSuicide = true; + else if (target.Is(CustomRoles.Trickster)) + judgeSuicide = true; + else if (target.Is(CustomRoles.Madmate) && CanTrialMadmate.GetBool()) + judgeSuicide = false; + else if (target.IsConverted() && CanTrialConverted.GetBool()) + judgeSuicide = false; + else if (target.IsNeutralKiller() && CanTrialNeutralK.GetBool()) + judgeSuicide = false; + else + { + CustomRoles targetRole = target.GetCustomRole(); - return true; - } - - if (pc.PlayerId == target.PlayerId) - { - if (!isUI) - Utils.SendMessage(GetString("LaughToWhoTrialSelf"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))); - else - pc.ShowPopUp(Utils.ColorString(Color.cyan, GetString("MessageFromKPD")) + "\n" + GetString("LaughToWhoTrialSelf")); - - judgeSuicide = true; - } - else if (pc.Is(CustomRoles.Madmate)) + if (targetRole.GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing && CanTrialCrewKilling.GetBool()) judgeSuicide = false; - else if (pc.Is(CustomRoles.Charmed)) + else if (targetRole.GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign && CanTrialNeutralB.GetBool()) judgeSuicide = false; - else if (pc.Is(CustomRoles.Recruit)) + else if (targetRole.GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil && CanTrialNeutralE.GetBool()) judgeSuicide = false; - else if (pc.Is(CustomRoles.Contagious)) + else if (targetRole.IsNonNK() && !CanTrialNeutralB.GetBool() && !CanTrialNeutralE.GetBool() && targetRole.GetNeutralRoleCategory() is not RoleOptionType.Neutral_Benign and not RoleOptionType.Neutral_Evil && CanTrialNeutralK.GetBool()) judgeSuicide = false; - else if (target.Is(CustomRoles.Rascal)) + else if (targetRole.IsImpostor()) judgeSuicide = false; - else if (target.Is(CustomRoles.Pestilence)) - judgeSuicide = true; - else if (target.Is(CustomRoles.Trickster)) - judgeSuicide = true; - else if (target.Is(CustomRoles.Madmate) && CanTrialMadmate.GetBool()) + else if (targetRole.IsMadmate() && CanTrialMadmate.GetBool()) judgeSuicide = false; - else if (target.IsConverted() && CanTrialConverted.GetBool()) - judgeSuicide = false; - else if (target.IsNeutralKiller() && CanTrialNeutralK.GetBool()) + else if (targetRole is CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Sidekick && CanTrialNeutralK.GetBool()) judgeSuicide = false; else - { - CustomRoles targetRole = target.GetCustomRole(); - - if (targetRole.GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing && CanTrialCrewKilling.GetBool()) - judgeSuicide = false; - else if (targetRole.GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign && CanTrialNeutralB.GetBool()) - judgeSuicide = false; - else if (targetRole.GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil && CanTrialNeutralE.GetBool()) - judgeSuicide = false; - else if (targetRole.IsNonNK() && !CanTrialNeutralB.GetBool() && !CanTrialNeutralE.GetBool() && targetRole.GetNeutralRoleCategory() is not RoleOptionType.Neutral_Benign and not RoleOptionType.Neutral_Evil && CanTrialNeutralK.GetBool()) - judgeSuicide = false; - else if (targetRole.IsImpostor()) - judgeSuicide = false; - else if (targetRole.IsMadmate() && CanTrialMadmate.GetBool()) - judgeSuicide = false; - else if (targetRole is CustomRoles.Necromancer or CustomRoles.Deathknight or CustomRoles.Sidekick && CanTrialNeutralK.GetBool()) - judgeSuicide = false; - else - judgeSuicide = true; - } - - if (pc.GetCustomSubRoles().Any(x => x.IsConverted() || x == CustomRoles.Bloodlust) && !target.Is(CustomRoles.Pestilence)) judgeSuicide = false; - - PlayerControl dp = judgeSuicide ? pc : target; - - string Name = dp.GetRealName(); - - pc.RpcRemoveAbilityUse(); - MeetingUseLimit[pc.PlayerId]--; - TotalUseLimit[pc.PlayerId]--; - - LateTask.New(() => - { - Main.PlayerStates[dp.PlayerId].deathReason = PlayerState.DeathReason.Trialed; - dp.SetRealKiller(pc); - dp.RpcGuesserMurderPlayer(); - - Utils.AfterPlayerDeathTasks(dp, true); - - Utils.NotifyRoles(NoCache: true); - - LateTask.New(() => { Utils.SendMessage(string.Format(GetString("TrialKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("TrialKillTitle"))); }, 0.6f, "Guess Msg"); - }, 0.2f, "Trial Kill"); + judgeSuicide = true; } - break; - } - } + if (pc.GetCustomSubRoles().Any(x => x.IsConverted() || x == CustomRoles.Bloodlust) && !target.Is(CustomRoles.Pestilence)) judgeSuicide = false; - return true; - } + PlayerControl dp = judgeSuicide ? pc : target; - private static bool MsgToPlayerAndRole(string msg, out byte id, out string error) - { - if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); + string Name = dp.GetRealName(); - Regex r = new("\\d+"); - MatchCollection mc = r.Matches(msg); - var result = string.Empty; - for (var i = 0; i < mc.Count; i++) result += mc[i]; + pc.RpcRemoveAbilityUse(); + MeetingUseLimit[pc.PlayerId]--; + TotalUseLimit[pc.PlayerId]--; - if (int.TryParse(result, out int num)) - id = Convert.ToByte(num); - else - { - id = byte.MaxValue; - error = GetString("TrialHelp"); - return false; - } - - PlayerControl target = Utils.GetPlayerById(id); - - if (target == null || target.Data.IsDead) - { - error = GetString("TrialNull"); - return false; - } - - error = string.Empty; - return true; - } - - public static bool CheckCommand(ref string msg, string command, bool exact = true) - { - string[] comList = command.Split('|'); - - foreach (string str in comList) - { - if (exact) - { - if (msg == "/" + str) return true; - } - else - { - if (msg.StartsWith("/" + str)) + LateTask.New(() => { - msg = msg.Replace("/" + str, string.Empty); - return true; - } - } - } + Main.PlayerStates[dp.PlayerId].deathReason = PlayerState.DeathReason.Trialed; + dp.SetRealKiller(pc); + dp.RpcGuesserMurderPlayer(); + Utils.AfterPlayerDeathTasks(dp, true); + + Utils.NotifyRoles(NoCache: true); + + LateTask.New(() => { Utils.SendMessage(string.Format(GetString("TrialKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("TrialKillTitle"))); }, 0.6f, "Guess Msg"); + }, 0.2f, "Trial Kill"); + } + + break; + } + } + + return true; + } + + private static bool MsgToPlayerAndRole(string msg, out byte id, out string error) + { + if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); + + Regex r = new("\\d+"); + MatchCollection mc = r.Matches(msg); + var result = string.Empty; + for (var i = 0; i < mc.Count; i++) result += mc[i]; + + if (int.TryParse(result, out int num)) + id = Convert.ToByte(num); + else + { + id = byte.MaxValue; + error = GetString("TrialHelp"); return false; } - private static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; + PlayerControl target = Utils.GetPlayerById(id); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Judge, SendOption.Reliable); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + if (target == null || target.Data.IsDead) + { + error = GetString("TrialNull"); + return false; } - public static void ReceiveRPC(MessageReader reader, PlayerControl pc) - { - int PlayerId = reader.ReadByte(); - TrialMsg(pc, $"/tl {PlayerId}", true); - } + error = string.Empty; + return true; + } - private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) - { - Logger.Msg($"Click: ID {playerId}", "Judge UI"); - PlayerControl pc = Utils.GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + public static bool CheckCommand(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); - if (AmongUsClient.Instance.AmHost) - TrialMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); + foreach (string str in comList) + { + if (exact) + { + if (msg == "/" + str) return true; + } else - SendRPC(playerId); - } - - public static void CreateJudgeButton(MeetingHud __instance) - { - foreach (PlayerVoteArea pva in __instance.playerStates) { - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; - - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); - var renderer = targetBox.GetComponent(); - renderer.sprite = CustomButton.Get("JudgeIcon"); - var button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => JudgeOnClick(pva.TargetPlayerId /*, __instance*/))); + if (msg.StartsWith("/" + str)) + { + msg = msg.Replace("/" + str, string.Empty); + return true; + } } } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - private class StartMeetingPatch + return false; + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.Judge, SendOption.Reliable); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + int PlayerId = reader.ReadByte(); + TrialMsg(pc, $"/tl {PlayerId}", true); + } + + private static void JudgeOnClick(byte playerId /*, MeetingHud __instance*/) + { + Logger.Msg($"Click: ID {playerId}", "Judge UI"); + PlayerControl pc = Utils.GetPlayerById(playerId); + if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + + if (AmongUsClient.Instance.AmHost) + TrialMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); + else + SendRPC(playerId); + } + + public static void CreateJudgeButton(MeetingHud __instance) + { + foreach (PlayerVoteArea pva in __instance.playerStates) { - public static void Postfix(MeetingHud __instance) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.Judge) && PlayerControl.LocalPlayer.IsAlive()) CreateJudgeButton(__instance); - } + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "ShootButton"; + targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); + var renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("JudgeIcon"); + var button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => JudgeOnClick(pva.TargetPlayerId /*, __instance*/))); + } + } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + private class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.Judge) && PlayerControl.LocalPlayer.IsAlive()) CreateJudgeButton(__instance); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Leery.cs b/Roles/Crewmate/Leery.cs index e82b46fb9..0e47777aa 100644 --- a/Roles/Crewmate/Leery.cs +++ b/Roles/Crewmate/Leery.cs @@ -3,109 +3,108 @@ using System.Linq; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Leery : RoleBase { - public class Leery : RoleBase + public static bool On; + + private static OptionItem Radius; + private static OptionItem Duration; + private static OptionItem ShowNearestPlayerName; + private static OptionItem ShowProgress; + + private int Count; + + private byte CurrentTarget; + private long InvestigationEndTS; + private byte LeeryId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(645750) + .AutoSetupOption(ref Radius, 1f, new FloatValueRule(0.1f, 10f, 0.1f), OptionFormat.Multiplier) + .AutoSetupOption(ref Duration, 15, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds) + .AutoSetupOption(ref ShowNearestPlayerName, true) + .AutoSetupOption(ref ShowProgress, true, overrideParent: ShowNearestPlayerName); + } - private static OptionItem Radius; - private static OptionItem Duration; - private static OptionItem ShowNearestPlayerName; - private static OptionItem ShowProgress; + public override void Init() + { + On = false; + } - private int Count; + public override void Add(byte playerId) + { + On = true; + LeeryId = playerId; + CurrentTarget = byte.MaxValue; + InvestigationEndTS = 0; + } - private byte CurrentTarget; - private long InvestigationEndTS; - private byte LeeryId; + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive() || Count++ < 5) return; - public override bool IsEnable => On; + Count = 0; - public override void SetupCustomOption() + Vector2 pos = pc.Pos(); + float radius = Radius.GetFloat(); + PlayerControl[] nearbyPlayers = Utils.GetPlayersInRadius(radius, pos).Without(pc).ToArray(); + + if (nearbyPlayers.Length == 0) { - StartSetup(645750) - .AutoSetupOption(ref Radius, 1f, new FloatValueRule(0.1f, 10f, 0.1f), OptionFormat.Multiplier) - .AutoSetupOption(ref Duration, 15, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds) - .AutoSetupOption(ref ShowNearestPlayerName, true) - .AutoSetupOption(ref ShowProgress, true, overrideParent: ShowNearestPlayerName); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - LeeryId = playerId; CurrentTarget = byte.MaxValue; InvestigationEndTS = 0; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive() || Count++ < 5) return; - - Count = 0; - - Vector2 pos = pc.Pos(); - float radius = Radius.GetFloat(); - PlayerControl[] nearbyPlayers = Utils.GetPlayersInRadius(radius, pos).Without(pc).ToArray(); - - if (nearbyPlayers.Length == 0) - { - CurrentTarget = byte.MaxValue; - InvestigationEndTS = 0; - SendRPC(); - return; - } - - PlayerControl nearestPlayer = nearbyPlayers.MinBy(p => Vector2.Distance(pos, p.Pos())); - - if (nearestPlayer.PlayerId == CurrentTarget && InvestigationEndTS == 0) return; - - if (nearestPlayer.PlayerId != CurrentTarget) - { - CurrentTarget = nearestPlayer.PlayerId; - InvestigationEndTS = Utils.TimeStamp + Duration.GetInt(); - SendRPC(); - return; - } - - if (Utils.TimeStamp < InvestigationEndTS) return; - - InvestigationEndTS = 0; SendRPC(); - if (!nearestPlayer.IsCrewmate()) pc.Notify(Translator.GetString("LeeryNotify")); + return; } - private void SendRPC() + PlayerControl nearestPlayer = nearbyPlayers.MinBy(p => Vector2.Distance(pos, p.Pos())); + + if (nearestPlayer.PlayerId == CurrentTarget && InvestigationEndTS == 0) return; + + if (nearestPlayer.PlayerId != CurrentTarget) { - Utils.SendRPC(CustomRPC.SyncRoleData, LeeryId, CurrentTarget, InvestigationEndTS); + CurrentTarget = nearestPlayer.PlayerId; + InvestigationEndTS = Utils.TimeStamp + Duration.GetInt(); + SendRPC(); + return; } - public void ReceiveRPC(MessageReader reader) - { - CurrentTarget = reader.ReadByte(); - InvestigationEndTS = long.Parse(reader.ReadString()); - } + if (Utils.TimeStamp < InvestigationEndTS) return; - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != LeeryId || seer.PlayerId != target.PlayerId || meeting || hud || !ShowNearestPlayerName.GetBool() || InvestigationEndTS == 0 || !seer.IsAlive()) return string.Empty; + InvestigationEndTS = 0; + SendRPC(); + if (!nearestPlayer.IsCrewmate()) pc.Notify(Translator.GetString("LeeryNotify")); + } - string text = string.Format(Translator.GetString("LeerySuffix"), CurrentTarget.ColoredPlayerName()); + private void SendRPC() + { + Utils.SendRPC(CustomRPC.SyncRoleData, LeeryId, CurrentTarget, InvestigationEndTS); + } + + public void ReceiveRPC(MessageReader reader) + { + CurrentTarget = reader.ReadByte(); + InvestigationEndTS = long.Parse(reader.ReadString()); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != LeeryId || seer.PlayerId != target.PlayerId || meeting || hud || !ShowNearestPlayerName.GetBool() || InvestigationEndTS == 0 || !seer.IsAlive()) return string.Empty; + + string text = string.Format(Translator.GetString("LeerySuffix"), CurrentTarget.ColoredPlayerName()); - if (ShowProgress.GetBool()) - { - long now = Utils.TimeStamp; - float percentage = (float)(InvestigationEndTS - now) / Duration.GetInt(); - text += $" {100 - (int)Math.Round(percentage * 100f)}%"; - } - - return text; + if (ShowProgress.GetBool()) + { + long now = Utils.TimeStamp; + float percentage = (float)(InvestigationEndTS - now) / Duration.GetInt(); + text += $" {100 - (int)Math.Round(percentage * 100f)}%"; } + + return text; } } \ No newline at end of file diff --git a/Roles/Crewmate/Lighter.cs b/Roles/Crewmate/Lighter.cs index 266742ebb..8314620ab 100644 --- a/Roles/Crewmate/Lighter.cs +++ b/Roles/Crewmate/Lighter.cs @@ -1,149 +1,148 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Lighter : RoleBase { - internal class Lighter : RoleBase + public static bool On; + private long ActivateTimeStamp; + private bool IsAbilityActive; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private long ActivateTimeStamp; - private bool IsAbilityActive; - public override bool IsEnable => On; + SetupRoleOptions(6850, TabGroup.CrewmateRoles, CustomRoles.Lighter); - public override void SetupCustomOption() + LighterSkillCooldown = new FloatOptionItem(6852, "LighterSkillCooldown", new(0f, 180f, 1f), 25f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Seconds); + + LighterSkillDuration = new FloatOptionItem(6853, "LighterSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Seconds); + + LighterVisionNormal = new FloatOptionItem(6854, "LighterVisionNormal", new(0f, 5f, 0.05f), 0.9f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Multiplier); + + LighterVisionOnLightsOut = new FloatOptionItem(6855, "LighterVisionOnLightsOut", new(0f, 5f, 0.05f), 0.35f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Multiplier); + + LighterSkillMaxOfUseage = new IntegerOptionItem(6856, "AbilityUseLimit", new(0, 180, 1), 2, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Times); + + LighterAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(6857, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Times); + + LighterAbilityChargesWhenFinishedTasks = new FloatOptionItem(6858, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(LighterSkillMaxOfUseage.GetInt()); + IsAbilityActive = false; + ActivateTimeStamp = 0; + } + + public override void Init() + { + On = false; + IsAbilityActive = false; + ActivateTimeStamp = 0; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (!UsePets.GetBool()) { - SetupRoleOptions(6850, TabGroup.CrewmateRoles, CustomRoles.Lighter); - - LighterSkillCooldown = new FloatOptionItem(6852, "LighterSkillCooldown", new(0f, 180f, 1f), 25f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Seconds); - - LighterSkillDuration = new FloatOptionItem(6853, "LighterSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Seconds); - - LighterVisionNormal = new FloatOptionItem(6854, "LighterVisionNormal", new(0f, 5f, 0.05f), 0.9f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Multiplier); - - LighterVisionOnLightsOut = new FloatOptionItem(6855, "LighterVisionOnLightsOut", new(0f, 5f, 0.05f), 0.35f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Multiplier); - - LighterSkillMaxOfUseage = new IntegerOptionItem(6856, "AbilityUseLimit", new(0, 180, 1), 2, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Times); - - LighterAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(6857, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Times); - - LighterAbilityChargesWhenFinishedTasks = new FloatOptionItem(6858, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lighter]) - .SetValueFormat(OptionFormat.Times); + AURoleOptions.EngineerInVentMaxTime = 1; + AURoleOptions.EngineerCooldown = LighterSkillCooldown.GetFloat(); } - public override void Add(byte playerId) + if (IsAbilityActive) { - On = true; - playerId.SetAbilityUseLimit(LighterSkillMaxOfUseage.GetInt()); - IsAbilityActive = false; - ActivateTimeStamp = 0; - } + opt.SetVision(false); - public override void Init() - { - On = false; - IsAbilityActive = false; - ActivateTimeStamp = 0; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (!UsePets.GetBool()) - { - AURoleOptions.EngineerInVentMaxTime = 1; - AURoleOptions.EngineerCooldown = LighterSkillCooldown.GetFloat(); - } - - if (IsAbilityActive) - { - opt.SetVision(false); - - if (Utils.IsActive(SystemTypes.Electrical)) - opt.SetFloat(FloatOptionNames.CrewLightMod, LighterVisionOnLightsOut.GetFloat() * 5); - else - opt.SetFloat(FloatOptionNames.CrewLightMod, LighterVisionNormal.GetFloat()); - } - } - - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); - - ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, IsAbilityActive)); - ProgressText.Append(Utils.GetTaskCount(playerId, comms)); - - return ProgressText.ToString(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("LighterVentButtonText"); + if (Utils.IsActive(SystemTypes.Electrical)) + opt.SetFloat(FloatOptionNames.CrewLightMod, LighterVisionOnLightsOut.GetFloat() * 5); else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("LighterVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - Light(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - Light(pc); - } - - private void Light(PlayerControl pc) - { - if (IsAbilityActive) return; - - if (pc.GetAbilityUseLimit() >= 1) - { - IsAbilityActive = true; - ActivateTimeStamp = Utils.TimeStamp; - - pc.Notify(Translator.GetString("LighterSkillInUse"), LighterSkillDuration.GetFloat()); - pc.RpcRemoveAbilityUse(); - pc.MarkDirtySettings(); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override void OnReportDeadBody() - { - IsAbilityActive = false; - ActivateTimeStamp = 0; - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask) return; - - if (IsAbilityActive && ActivateTimeStamp + LighterSkillDuration.GetInt() < Utils.TimeStamp) - { - IsAbilityActive = false; - player.RpcResetAbilityCooldown(); - player.Notify(Translator.GetString("LighterSkillStop")); - player.MarkDirtySettings(); - } - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + opt.SetFloat(FloatOptionNames.CrewLightMod, LighterVisionNormal.GetFloat()); } } + + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + + ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, IsAbilityActive)); + ProgressText.Append(Utils.GetTaskCount(playerId, comms)); + + return ProgressText.ToString(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("LighterVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("LighterVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + Light(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + Light(pc); + } + + private void Light(PlayerControl pc) + { + if (IsAbilityActive) return; + + if (pc.GetAbilityUseLimit() >= 1) + { + IsAbilityActive = true; + ActivateTimeStamp = Utils.TimeStamp; + + pc.Notify(Translator.GetString("LighterSkillInUse"), LighterSkillDuration.GetFloat()); + pc.RpcRemoveAbilityUse(); + pc.MarkDirtySettings(); + } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } + + public override void OnReportDeadBody() + { + IsAbilityActive = false; + ActivateTimeStamp = 0; + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) return; + + if (IsAbilityActive && ActivateTimeStamp + LighterSkillDuration.GetInt() < Utils.TimeStamp) + { + IsAbilityActive = false; + player.RpcResetAbilityCooldown(); + player.Notify(Translator.GetString("LighterSkillStop")); + player.MarkDirtySettings(); + } + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Lookout.cs b/Roles/Crewmate/Lookout.cs index 1eba4b516..0af0c7a14 100644 --- a/Roles/Crewmate/Lookout.cs +++ b/Roles/Crewmate/Lookout.cs @@ -1,52 +1,51 @@ using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Lookout : RoleBase { - internal class Lookout : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void Add(byte playerId) + public override void Init() + { + On = false; + } + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(9150, TabGroup.CrewmateRoles, CustomRoles.Lookout); + } + + public override void OnPet(PlayerControl pc) + { + PlayerControl[] aapc = Main.AllAlivePlayerControls; + var sb = new StringBuilder(); + + for (var i = 0; i < aapc.Length; i++) + if (i % 3 == 0) + sb.AppendLine(); + + for (var i = 0; i < aapc.Length; i++) { - On = true; + PlayerControl player = aapc[i]; + if (player == null) continue; + + if (i != 0) sb.Append("; "); + + string name = player.GetRealName(); + byte id = player.PlayerId; + if (Main.PlayerColors.TryGetValue(id, out Color32 color)) name = Utils.ColorString(color, name); + + sb.Append($"{name} {id}"); + if (i % 3 == 0 && i != aapc.Length - 1) sb.AppendLine(); } - public override void Init() - { - On = false; - } - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(9150, TabGroup.CrewmateRoles, CustomRoles.Lookout); - } - - public override void OnPet(PlayerControl pc) - { - PlayerControl[] aapc = Main.AllAlivePlayerControls; - var sb = new StringBuilder(); - - for (var i = 0; i < aapc.Length; i++) - if (i % 3 == 0) - sb.AppendLine(); - - for (var i = 0; i < aapc.Length; i++) - { - PlayerControl player = aapc[i]; - if (player == null) continue; - - if (i != 0) sb.Append("; "); - - string name = player.GetRealName(); - byte id = player.PlayerId; - if (Main.PlayerColors.TryGetValue(id, out Color32 color)) name = Utils.ColorString(color, name); - - sb.Append($"{name} {id}"); - if (i % 3 == 0 && i != aapc.Length - 1) sb.AppendLine(); - } - - pc.Notify(sb.ToString()); - } + pc.Notify(sb.ToString()); } } \ No newline at end of file diff --git a/Roles/Crewmate/Luckey.cs b/Roles/Crewmate/Luckey.cs index 9e5a5afbb..60f0fbdde 100644 --- a/Roles/Crewmate/Luckey.cs +++ b/Roles/Crewmate/Luckey.cs @@ -1,40 +1,39 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Luckey : RoleBase { - internal class Luckey : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(5800, TabGroup.CrewmateRoles, CustomRoles.Luckey); - public override void SetupCustomOption() + Options.LuckeyProbability = new IntegerOptionItem(5900, "LuckeyProbability", new(0, 100, 5), 50, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Luckey]) + .SetValueFormat(OptionFormat.Percent); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + var rd = IRandom.Instance; + + if (rd.Next(0, 100) < Options.LuckeyProbability.GetInt()) { - Options.SetupRoleOptions(5800, TabGroup.CrewmateRoles, CustomRoles.Luckey); - - Options.LuckeyProbability = new IntegerOptionItem(5900, "LuckeyProbability", new(0, 100, 5), 50, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Luckey]) - .SetValueFormat(OptionFormat.Percent); + killer.SetKillCooldown(15f); + return false; } - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - var rd = IRandom.Instance; - - if (rd.Next(0, 100) < Options.LuckeyProbability.GetInt()) - { - killer.SetKillCooldown(15f); - return false; - } - - return true; - } + return true; } } \ No newline at end of file diff --git a/Roles/Crewmate/Lyncher.cs b/Roles/Crewmate/Lyncher.cs index 1942bbfef..3686ef7af 100644 --- a/Roles/Crewmate/Lyncher.cs +++ b/Roles/Crewmate/Lyncher.cs @@ -4,152 +4,151 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Lyncher : RoleBase { - public class Lyncher : RoleBase + public static bool On; + public static List Instances = []; + + private static OptionItem TaskNum; + public static OptionItem GuessMode; + private static OptionItem Vision; + + private static readonly string[] GuessModes = + [ + "RoleOff", // 0 + "Untouched", // 1 + "RoleOn" // 2 + ]; + + private static Dictionary> AllRoleNames = []; + + private Dictionary> KnownCharacters = []; + private byte LyncherId; + private int TasksCompleted; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public static List Instances = []; + var id = 648550; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Lyncher); - private static OptionItem TaskNum; - public static OptionItem GuessMode; - private static OptionItem Vision; + TaskNum = new IntegerOptionItem(++id, "Lyncher.TaskNum", new(1, 10, 1), 3, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Lyncher]); - private static readonly string[] GuessModes = - [ - "RoleOff", // 0 - "Untouched", // 1 - "RoleOn" // 2 - ]; + GuessMode = new StringOptionItem(++id, "Lyncher.GuessMode", GuessModes, 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Lyncher]); - private static Dictionary> AllRoleNames = []; + Vision = new FloatOptionItem(++id, "Vision", new(0f, 1.5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetValueFormat(OptionFormat.Multiplier) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Lyncher]); - private Dictionary> KnownCharacters = []; - private byte LyncherId; - private int TasksCompleted; + Options.OverrideTasksData.Create(++id, TabGroup.CrewmateRoles, CustomRoles.Lyncher); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + Instances = []; + AllRoleNames = []; + KnownCharacters = []; + LateTask.New(() => AllRoleNames = Main.PlayerStates.ToDictionary(x => x.Key, x => Translator.GetString($"{x.Value.MainRole}").ToUpper().Where(c => c is not '-' and not ' ').Shuffle()), 10f, log: false); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + LyncherId = playerId; + TasksCompleted = 0; + Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 1, TasksCompleted); + + if (Main.HasJustStarted) + LateTask.New(Action, 12f, log: false); + else + Action(); + + return; + + void Action() { - var id = 648550; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Lyncher); - - TaskNum = new IntegerOptionItem(++id, "Lyncher.TaskNum", new(1, 10, 1), 3, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Lyncher]); - - GuessMode = new StringOptionItem(++id, "Lyncher.GuessMode", GuessModes, 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Lyncher]); - - Vision = new FloatOptionItem(++id, "Vision", new(0f, 1.5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetValueFormat(OptionFormat.Multiplier) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Lyncher]); - - Options.OverrideTasksData.Create(++id, TabGroup.CrewmateRoles, CustomRoles.Lyncher); - } - - public override void Init() - { - On = false; - Instances = []; - AllRoleNames = []; - KnownCharacters = []; - LateTask.New(() => AllRoleNames = Main.PlayerStates.ToDictionary(x => x.Key, x => Translator.GetString($"{x.Value.MainRole}").ToUpper().Where(c => c is not '-' and not ' ').Shuffle()), 10f, log: false); - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - LyncherId = playerId; - TasksCompleted = 0; - Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 1, TasksCompleted); - - if (Main.HasJustStarted) - LateTask.New(Action, 12f, log: false); - else - Action(); - - return; - - void Action() - { - KnownCharacters = AllRoleNames.ToDictionary(x => x.Key, _ => new List()); - } - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (!pc.IsAlive()) return; - - TasksCompleted++; - - if (TasksCompleted >= TaskNum.GetInt()) - { - RevealLetter(); - TasksCompleted = 0; - Utils.NotifyRoles(SpecifySeer: pc); - } - else - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 1, TasksCompleted); - } - - private void RevealLetter() - { - Dictionary nextLetters = AllRoleNames.ToDictionary(x => x.Key, x => x.Value.Except(KnownCharacters[x.Key]).RandomElement()); - - KnownCharacters.Do(x => - { - x.Value.Add(nextLetters[x.Key]); - Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 2, x.Key, nextLetters[x.Key]); - }); - } - - public void OnRoleChange(byte id) - { - Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 3, id); - CustomRoles newRole = Main.PlayerStates[id].MainRole; - AllRoleNames[id] = Translator.GetString($"{newRole}").ToUpper().Where(c => c is not '-' and not ' ').Shuffle(); - int count = KnownCharacters[id].Count; - KnownCharacters[id] = AllRoleNames[id].Take(count).ToList(); - KnownCharacters[id].ForEach(x => Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 2, id, x)); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - TasksCompleted = reader.ReadPackedInt32(); - break; - case 2: - KnownCharacters[reader.ReadByte()].Add(reader.ReadString()[0]); - break; - case 3: - KnownCharacters[reader.ReadByte()].Clear(); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != LyncherId) return string.Empty; - - return (seer.PlayerId == target.PlayerId) switch - { - false when KnownCharacters.TryGetValue(target.PlayerId, out List chars) && chars.Count > 0 => string.Join(' ', chars), - true when !seer.IsModClient() || hud => string.Format(Translator.GetString("Lyncher.Suffix"), TaskNum.GetInt() - TasksCompleted), - _ => string.Empty - }; + KnownCharacters = AllRoleNames.ToDictionary(x => x.Key, _ => new List()); } } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Vision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision.GetFloat()); + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (!pc.IsAlive()) return; + + TasksCompleted++; + + if (TasksCompleted >= TaskNum.GetInt()) + { + RevealLetter(); + TasksCompleted = 0; + Utils.NotifyRoles(SpecifySeer: pc); + } + else + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 1, TasksCompleted); + } + + private void RevealLetter() + { + Dictionary nextLetters = AllRoleNames.ToDictionary(x => x.Key, x => x.Value.Except(KnownCharacters[x.Key]).RandomElement()); + + KnownCharacters.Do(x => + { + x.Value.Add(nextLetters[x.Key]); + Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 2, x.Key, nextLetters[x.Key]); + }); + } + + public void OnRoleChange(byte id) + { + Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 3, id); + CustomRoles newRole = Main.PlayerStates[id].MainRole; + AllRoleNames[id] = Translator.GetString($"{newRole}").ToUpper().Where(c => c is not '-' and not ' ').Shuffle(); + int count = KnownCharacters[id].Count; + KnownCharacters[id] = AllRoleNames[id].Take(count).ToList(); + KnownCharacters[id].ForEach(x => Utils.SendRPC(CustomRPC.SyncRoleData, LyncherId, 2, id, x)); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + TasksCompleted = reader.ReadPackedInt32(); + break; + case 2: + KnownCharacters[reader.ReadByte()].Add(reader.ReadString()[0]); + break; + case 3: + KnownCharacters[reader.ReadByte()].Clear(); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != LyncherId) return string.Empty; + + return (seer.PlayerId == target.PlayerId) switch + { + false when KnownCharacters.TryGetValue(target.PlayerId, out List chars) && chars.Count > 0 => string.Join(' ', chars), + true when !seer.IsModClient() || hud => string.Format(Translator.GetString("Lyncher.Suffix"), TaskNum.GetInt() - TasksCompleted), + _ => string.Empty + }; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Markseeker.cs b/Roles/Crewmate/Markseeker.cs index 8b59425c2..fbce7565e 100644 --- a/Roles/Crewmate/Markseeker.cs +++ b/Roles/Crewmate/Markseeker.cs @@ -1,53 +1,52 @@ using System.Collections.Generic; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Markseeker : RoleBase { - internal class Markseeker : RoleBase + private const int Id = 643550; + public static List PlayerIdList = []; + public static bool On; + public static OptionItem CancelVote; + + public byte MarkedId; + public bool TargetRevealed; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 643550; - public static List PlayerIdList = []; - public static bool On; - public static OptionItem CancelVote; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Markseeker); + CancelVote = Options.CreateVoteCancellingUseSetting(Id + 2, CustomRoles.Markseeker, TabGroup.CrewmateRoles); + } - public byte MarkedId; - public bool TargetRevealed; - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + MarkedId = byte.MaxValue; + TargetRevealed = false; + PlayerIdList.Add(playerId); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Markseeker); - CancelVote = Options.CreateVoteCancellingUseSetting(Id + 2, CustomRoles.Markseeker, TabGroup.CrewmateRoles); - } + public override void Init() + { + On = false; + PlayerIdList = []; + } - public override void Add(byte playerId) - { - On = true; - MarkedId = byte.MaxValue; - TargetRevealed = false; - PlayerIdList.Add(playerId); - } + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null || player.PlayerId == target.PlayerId || MarkedId != byte.MaxValue || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - public override void Init() - { - On = false; - PlayerIdList = []; - } + MarkedId = target.PlayerId; - public override bool OnVote(PlayerControl player, PlayerControl target) - { - if (player == null || target == null || player.PlayerId == target.PlayerId || MarkedId != byte.MaxValue || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; + Main.DontCancelVoteList.Add(player.PlayerId); + return true; + } - MarkedId = target.PlayerId; + public static void OnDeath(PlayerControl player) + { + if (Main.PlayerStates[player.PlayerId].Role is not Markseeker { IsEnable: true } ms || ms.MarkedId == byte.MaxValue) return; - Main.DontCancelVoteList.Add(player.PlayerId); - return true; - } - - public static void OnDeath(PlayerControl player) - { - if (Main.PlayerStates[player.PlayerId].Role is not Markseeker { IsEnable: true } ms || ms.MarkedId == byte.MaxValue) return; - - ms.TargetRevealed = true; - } + ms.TargetRevealed = true; } } \ No newline at end of file diff --git a/Roles/Crewmate/Marshall.cs b/Roles/Crewmate/Marshall.cs index 6ec72efb6..34a83ae8e 100644 --- a/Roles/Crewmate/Marshall.cs +++ b/Roles/Crewmate/Marshall.cs @@ -2,71 +2,70 @@ using System; using System.Collections.Generic; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Marshall : RoleBase { - public class Marshall : RoleBase + private const int Id = 9400; + private static readonly List PlayerIdList = []; + + private static readonly Dictionary SeeingTeamOptions = []; + public static OptionItem CanBeGuessedOnTaskCompletion; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 9400; - private static readonly List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Marshall); - private static readonly Dictionary SeeingTeamOptions = []; - public static OptionItem CanBeGuessedOnTaskCompletion; - - public override bool IsEnable => PlayerIdList.Count > 0; - - public override void SetupCustomOption() + Enum.GetValues().Do(x => { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Marshall); - - Enum.GetValues().Do(x => - { - SeeingTeamOptions[x] = new BooleanOptionItem(Id + 2 + (int)x, $"{x}CanFindMarshall", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Marshall]); - }); - - CanBeGuessedOnTaskCompletion = new BooleanOptionItem(Id + 10, "MarshallCanBeGuessedOnTaskCompletion", false, TabGroup.CrewmateRoles) + SeeingTeamOptions[x] = new BooleanOptionItem(Id + 2 + (int)x, $"{x}CanFindMarshall", false, TabGroup.CrewmateRoles) .SetParent(CustomRoleSpawnChances[CustomRoles.Marshall]); + }); - OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Marshall); - } + CanBeGuessedOnTaskCompletion = new BooleanOptionItem(Id + 10, "MarshallCanBeGuessedOnTaskCompletion", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Marshall]); - public override void Init() + OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Marshall); + } + + public override void Init() + { + PlayerIdList.Clear(); + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public static string GetWarningMark(PlayerControl seer, PlayerControl target) + { + return CanSeeMarshall(seer) && PlayerIdList.Contains(target.PlayerId) && target.IsAlive() && target.GetTaskState().IsTaskFinished ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★") : string.Empty; + } + + public static bool CanSeeMarshall(PlayerControl seer) + { + Team team = seer.GetTeam(); + bool madmate = seer.IsMadmate(); + + return team switch { - PlayerIdList.Clear(); - } + Team.Crewmate when !madmate => true, + Team.Neutral when seer.IsNeutralKiller() => SeeingTeamOptions[SeeingTeam.NK].GetBool(), + Team.Neutral => SeeingTeamOptions[SeeingTeam.NNK].GetBool(), + Team.Impostor when madmate => SeeingTeamOptions[SeeingTeam.Madmate].GetBool(), + Team.Impostor => SeeingTeamOptions[SeeingTeam.Imp].GetBool(), + _ => false + }; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public static string GetWarningMark(PlayerControl seer, PlayerControl target) - { - return CanSeeMarshall(seer) && PlayerIdList.Contains(target.PlayerId) && target.IsAlive() && target.GetTaskState().IsTaskFinished ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Marshall), "★") : string.Empty; - } - - public static bool CanSeeMarshall(PlayerControl seer) - { - Team team = seer.GetTeam(); - bool madmate = seer.IsMadmate(); - - return team switch - { - Team.Crewmate when !madmate => true, - Team.Neutral when seer.IsNeutralKiller() => SeeingTeamOptions[SeeingTeam.NK].GetBool(), - Team.Neutral => SeeingTeamOptions[SeeingTeam.NNK].GetBool(), - Team.Impostor when madmate => SeeingTeamOptions[SeeingTeam.Madmate].GetBool(), - Team.Impostor => SeeingTeamOptions[SeeingTeam.Imp].GetBool(), - _ => false - }; - } - - private enum SeeingTeam - { - NNK, - NK, - Imp, - Madmate - } + private enum SeeingTeam + { + NNK, + NK, + Imp, + Madmate } } \ No newline at end of file diff --git a/Roles/Crewmate/Mathematician.cs b/Roles/Crewmate/Mathematician.cs index 25ddc5ef9..974b1d420 100644 --- a/Roles/Crewmate/Mathematician.cs +++ b/Roles/Crewmate/Mathematician.cs @@ -1,79 +1,78 @@ using System; using EHR.Modules; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Mathematician : RoleBase { - internal class Mathematician : RoleBase + public static (bool AskedQuestion, int Answer, byte ProtectedPlayerId, byte MathematicianPlayerId) State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); + public static bool On; + private static int Id => 643370; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static (bool AskedQuestion, int Answer, byte ProtectedPlayerId, byte MathematicianPlayerId) State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); - public static bool On; - private static int Id => 643370; + Options.SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mathematician); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); + } + + public static void Ask(PlayerControl pc, string num1Str, string num2Str) + { + try { - Options.SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mathematician); + if (pc == null || !pc.IsAlive() || !GameStates.IsMeeting || State.AskedQuestion || State.ProtectedPlayerId != byte.MaxValue || !int.TryParse(num1Str, out int num1) || !int.TryParse(num2Str, out int num2)) return; + + State.AskedQuestion = true; + State.Answer = num1 + num2; + State.MathematicianPlayerId = pc.PlayerId; + + string question = string.Format(Translator.GetString("MathematicianQuestionString"), num1, num2); + ChatManager.SendPreviousMessagesToAll(); + LateTask.New(() => Utils.SendMessage(question, title: Translator.GetString("Mathematician")), 0.2f, log: false); } - - public override void Init() + catch (Exception e) { - On = false; - State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); - } - - public override void Add(byte playerId) - { - On = true; - State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); - } - - public static void Ask(PlayerControl pc, string num1Str, string num2Str) - { - try - { - if (pc == null || !pc.IsAlive() || !GameStates.IsMeeting || State.AskedQuestion || State.ProtectedPlayerId != byte.MaxValue || !int.TryParse(num1Str, out int num1) || !int.TryParse(num2Str, out int num2)) return; - - State.AskedQuestion = true; - State.Answer = num1 + num2; - State.MathematicianPlayerId = pc.PlayerId; - - string question = string.Format(Translator.GetString("MathematicianQuestionString"), num1, num2); - ChatManager.SendPreviousMessagesToAll(); - LateTask.New(() => Utils.SendMessage(question, title: Translator.GetString("Mathematician")), 0.2f, log: false); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public static void Reply(PlayerControl pc, string answerStr) - { - try - { - if (pc == null || !pc.IsAlive() || !GameStates.IsMeeting || !State.AskedQuestion || State.MathematicianPlayerId == pc.PlayerId || State.ProtectedPlayerId != byte.MaxValue || !int.TryParse(answerStr, out int answer)) return; - - if (answer == State.Answer) - { - State.ProtectedPlayerId = pc.PlayerId; - pc.RpcIncreaseAbilityUseLimitBy(1f); - Utils.SendMessage(string.Format(Translator.GetString("MathematicianAnsweredString"), pc.GetRealName(), answer), title: Translator.GetString("Mathematician")); - State.AskedQuestion = false; - - if (pc.IsLocalPlayer()) - Achievements.Type.TheBestInSchool.Complete(); - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public override void OnReportDeadBody() - { - State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); + Utils.ThrowException(e); } } + + public static void Reply(PlayerControl pc, string answerStr) + { + try + { + if (pc == null || !pc.IsAlive() || !GameStates.IsMeeting || !State.AskedQuestion || State.MathematicianPlayerId == pc.PlayerId || State.ProtectedPlayerId != byte.MaxValue || !int.TryParse(answerStr, out int answer)) return; + + if (answer == State.Answer) + { + State.ProtectedPlayerId = pc.PlayerId; + pc.RpcIncreaseAbilityUseLimitBy(1f); + Utils.SendMessage(string.Format(Translator.GetString("MathematicianAnsweredString"), pc.GetRealName(), answer), title: Translator.GetString("Mathematician")); + State.AskedQuestion = false; + + if (pc.IsLocalPlayer()) + Achievements.Type.TheBestInSchool.Complete(); + } + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public override void OnReportDeadBody() + { + State = (false, int.MaxValue, byte.MaxValue, byte.MaxValue); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Mayor.cs b/Roles/Crewmate/Mayor.cs index 83227affb..120e72547 100644 --- a/Roles/Crewmate/Mayor.cs +++ b/Roles/Crewmate/Mayor.cs @@ -2,103 +2,102 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Mayor : RoleBase { - internal class Mayor : RoleBase + public static Dictionary MayorUsedButtonCount = []; + + public static bool On; + + public static OptionItem MayorAdditionalVote; + public static OptionItem MayorHasPortableButton; + public static OptionItem MayorNumOfUseButton; + public static OptionItem MayorHideVote; + public static OptionItem MayorRevealWhenDoneTasks; + public static OptionItem MayorSeesVoteColorsWhenDoneTasks; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static Dictionary MayorUsedButtonCount = []; + On = true; + MayorUsedButtonCount[playerId] = 0; + } - public static bool On; + public override void Init() + { + On = false; + } - public static OptionItem MayorAdditionalVote; - public static OptionItem MayorHasPortableButton; - public static OptionItem MayorNumOfUseButton; - public static OptionItem MayorHideVote; - public static OptionItem MayorRevealWhenDoneTasks; - public static OptionItem MayorSeesVoteColorsWhenDoneTasks; - public override bool IsEnable => On; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; - public override void Add(byte playerId) - { - On = true; - MayorUsedButtonCount[playerId] = 0; - } + AURoleOptions.EngineerCooldown = + !MayorUsedButtonCount.TryGetValue(playerId, out int count) || count < MayorNumOfUseButton.GetInt() + ? opt.GetInt(Int32OptionNames.EmergencyCooldown) + : 300f; - public override void Init() - { - On = false; - } + AURoleOptions.EngineerInVentMaxTime = 1f; + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; + public override void SetButtonTexts(HudManager hud, byte id) + { + if (!MayorHasPortableButton.GetBool()) return; - AURoleOptions.EngineerCooldown = - !MayorUsedButtonCount.TryGetValue(playerId, out int count) || count < MayorNumOfUseButton.GetInt() - ? opt.GetInt(Int32OptionNames.EmergencyCooldown) - : 300f; + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("MayorVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("MayorVentButtonText"); + } - AURoleOptions.EngineerInVentMaxTime = 1f; - } + public override void OnPet(PlayerControl pc) + { + Button(pc); + } - public override void SetButtonTexts(HudManager hud, byte id) - { - if (!MayorHasPortableButton.GetBool()) return; + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + pc.MyPhysics?.RpcBootFromVent(vent.Id); + Button(pc); + } - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("MayorVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("MayorVentButtonText"); - } + private static void Button(PlayerControl pc) + { + if (!MayorHasPortableButton.GetBool()) return; - public override void OnPet(PlayerControl pc) - { - Button(pc); - } + if (MayorUsedButtonCount.TryGetValue(pc.PlayerId, out int count) && count < MayorNumOfUseButton.GetInt()) pc.ReportDeadBody(null); + } - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - pc.MyPhysics?.RpcBootFromVent(vent.Id); - Button(pc); - } + public override void SetupCustomOption() + { + SetupRoleOptions(9500, TabGroup.CrewmateRoles, CustomRoles.Mayor); - private static void Button(PlayerControl pc) - { - if (!MayorHasPortableButton.GetBool()) return; + MayorAdditionalVote = new IntegerOptionItem(9510, "MayorAdditionalVote", new(0, 90, 1), 2, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]) + .SetValueFormat(OptionFormat.Votes); - if (MayorUsedButtonCount.TryGetValue(pc.PlayerId, out int count) && count < MayorNumOfUseButton.GetInt()) pc.ReportDeadBody(null); - } + MayorHasPortableButton = new BooleanOptionItem(9511, "MayorHasPortableButton", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); - public override void SetupCustomOption() - { - SetupRoleOptions(9500, TabGroup.CrewmateRoles, CustomRoles.Mayor); + MayorNumOfUseButton = new IntegerOptionItem(9512, "MayorNumOfUseButton", new(1, 90, 1), 1, TabGroup.CrewmateRoles) + .SetParent(MayorHasPortableButton) + .SetValueFormat(OptionFormat.Times); - MayorAdditionalVote = new IntegerOptionItem(9510, "MayorAdditionalVote", new(0, 90, 1), 2, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]) - .SetValueFormat(OptionFormat.Votes); + MayorHideVote = new BooleanOptionItem(9513, "MayorHideVote", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); - MayorHasPortableButton = new BooleanOptionItem(9511, "MayorHasPortableButton", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); + MayorRevealWhenDoneTasks = new BooleanOptionItem(9514, "MayorRevealWhenDoneTasks", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); - MayorNumOfUseButton = new IntegerOptionItem(9512, "MayorNumOfUseButton", new(1, 90, 1), 1, TabGroup.CrewmateRoles) - .SetParent(MayorHasPortableButton) - .SetValueFormat(OptionFormat.Times); + MayorSeesVoteColorsWhenDoneTasks = new BooleanOptionItem(9515, "MayorSeesVoteColorsWhenDoneTasks", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); - MayorHideVote = new BooleanOptionItem(9513, "MayorHideVote", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); + OverrideTasksData.Create(9516, TabGroup.CrewmateRoles, CustomRoles.Mayor); + } - MayorRevealWhenDoneTasks = new BooleanOptionItem(9514, "MayorRevealWhenDoneTasks", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); - - MayorSeesVoteColorsWhenDoneTasks = new BooleanOptionItem(9515, "MayorSeesVoteColorsWhenDoneTasks", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mayor]); - - OverrideTasksData.Create(9516, TabGroup.CrewmateRoles, CustomRoles.Mayor); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Medic.cs b/Roles/Crewmate/Medic.cs index 280334dba..1cd44ddf4 100644 --- a/Roles/Crewmate/Medic.cs +++ b/Roles/Crewmate/Medic.cs @@ -4,328 +4,327 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Medic : RoleBase { - public class Medic : RoleBase + private const int Id = 7100; + public static List PlayerIdList = []; + public static List ProtectList = []; + public static List TempMarkProtectedList = []; + public static int SkillLimit; + + private static int LocalPlayerTryKillShieldedTimes; + + public static OptionItem WhoCanSeeProtect; + private static OptionItem KnowShieldBroken; + private static OptionItem ShieldDeactivatesWhenMedicDies; + private static OptionItem ShieldDeactivationIsVisible; + private static OptionItem ShieldBreaksOnKillAttempt; + private static OptionItem ShieldBreakIsVisible; + private static OptionItem ResetCooldown; + public static OptionItem GuesserIgnoreShield; + public static OptionItem JudgingIgnoreShield; + private static OptionItem AmountOfShields; + public static OptionItem UsePet; + public static OptionItem CD; + + public static readonly string[] MedicWhoCanSeeProtectName = + [ + "SeeMedicAndTarget", + "SeeMedic", + "SeeTarget", + "SeeNoone" + ]; + + public static readonly string[] ShieldDeactivationIsVisibleOption = + [ + "DeactivationImmediately", + "DeactivationAfterMeeting", + "DeactivationIsVisibleOFF" + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 7100; - public static List PlayerIdList = []; - public static List ProtectList = []; - public static List TempMarkProtectedList = []; - public static int SkillLimit; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Medic); - private static int LocalPlayerTryKillShieldedTimes; + WhoCanSeeProtect = new StringOptionItem(Id + 2, "MedicWhoCanSeeProtect", MedicWhoCanSeeProtectName, 0, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - public static OptionItem WhoCanSeeProtect; - private static OptionItem KnowShieldBroken; - private static OptionItem ShieldDeactivatesWhenMedicDies; - private static OptionItem ShieldDeactivationIsVisible; - private static OptionItem ShieldBreaksOnKillAttempt; - private static OptionItem ShieldBreakIsVisible; - private static OptionItem ResetCooldown; - public static OptionItem GuesserIgnoreShield; - public static OptionItem JudgingIgnoreShield; - private static OptionItem AmountOfShields; - public static OptionItem UsePet; - public static OptionItem CD; + KnowShieldBroken = new StringOptionItem(Id + 3, "MedicKnowShieldBroken", MedicWhoCanSeeProtectName, 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - public static readonly string[] MedicWhoCanSeeProtectName = - [ - "SeeMedicAndTarget", - "SeeMedic", - "SeeTarget", - "SeeNoone" - ]; + ShieldDeactivatesWhenMedicDies = new BooleanOptionItem(Id + 4, "MedicShieldDeactivatesWhenMedicDies", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - public static readonly string[] ShieldDeactivationIsVisibleOption = - [ - "DeactivationImmediately", - "DeactivationAfterMeeting", - "DeactivationIsVisibleOFF" - ]; + ShieldDeactivationIsVisible = new StringOptionItem(Id + 5, "MedicShielDeactivationIsVisible", ShieldDeactivationIsVisibleOption, 0, TabGroup.CrewmateRoles) + .SetParent(ShieldDeactivatesWhenMedicDies); - public override bool IsEnable => PlayerIdList.Count > 0; + ShieldBreaksOnKillAttempt = new BooleanOptionItem(Id + 6, "MedicShieldBreaksOnKillAttempt", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - public override void SetupCustomOption() + ShieldBreakIsVisible = new StringOptionItem(Id + 7, "MedicShieldBreakIsVisible", ShieldDeactivationIsVisibleOption, 0, TabGroup.CrewmateRoles) + .SetParent(ShieldBreaksOnKillAttempt); + + ResetCooldown = new FloatOptionItem(Id + 8, "MedicResetCooldown", new(0f, 120f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]) + .SetValueFormat(OptionFormat.Seconds); + + GuesserIgnoreShield = new BooleanOptionItem(Id + 9, "MedicShieldedCanBeGuessed", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); + + JudgingIgnoreShield = new BooleanOptionItem(Id + 10, "MedicShieldedCanBeJudged", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); + + AmountOfShields = new IntegerOptionItem(Id + 11, "MedicAmountOfShields", new(1, 14, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); + + UsePet = Options.CreatePetUseSetting(Id + 12, CustomRoles.Medic); + + CD = new FloatOptionItem(Id + 13, "AbilityCooldown", new(0f, 180f, 2.5f), 7.5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + ProtectList = []; + TempMarkProtectedList = []; + SkillLimit = AmountOfShields.GetInt(); + + LocalPlayerTryKillShieldedTimes = 0; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(SkillLimit); + } + + private static void SendRPCForProtectList() + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetMedicalerProtectList, SendOption.Reliable); + writer.Write(ProtectList.Count); + ProtectList.Do(x => writer.Write(x)); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCForProtectList(MessageReader reader) + { + int count = reader.ReadInt32(); + ProtectList = []; + for (var i = 0; i < count; i++) ProtectList.Add(reader.ReadByte()); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !Main.PlayerStates[pc.PlayerId].IsDead + && pc.GetAbilityUseLimit() >= 1; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? CD.GetFloat() : 300f; + } + + public static bool InProtect(byte id) + { + return ProtectList.Contains(id) && Main.PlayerStates.TryGetValue(id, out PlayerState ps) && !ps.IsDead; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + + if (!CanUseKillButton(killer)) return false; + + if (ProtectList.Contains(target.PlayerId)) return false; + + killer.RpcRemoveAbilityUse(); + + ProtectList.Add(target.PlayerId); + TempMarkProtectedList.Add(target.PlayerId); + SendRPCForProtectList(); + + killer.SetKillCooldown(); + + switch (WhoCanSeeProtect.GetInt()) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Medic); - - WhoCanSeeProtect = new StringOptionItem(Id + 2, "MedicWhoCanSeeProtect", MedicWhoCanSeeProtectName, 0, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - KnowShieldBroken = new StringOptionItem(Id + 3, "MedicKnowShieldBroken", MedicWhoCanSeeProtectName, 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - ShieldDeactivatesWhenMedicDies = new BooleanOptionItem(Id + 4, "MedicShieldDeactivatesWhenMedicDies", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - ShieldDeactivationIsVisible = new StringOptionItem(Id + 5, "MedicShielDeactivationIsVisible", ShieldDeactivationIsVisibleOption, 0, TabGroup.CrewmateRoles) - .SetParent(ShieldDeactivatesWhenMedicDies); - - ShieldBreaksOnKillAttempt = new BooleanOptionItem(Id + 6, "MedicShieldBreaksOnKillAttempt", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - ShieldBreakIsVisible = new StringOptionItem(Id + 7, "MedicShieldBreakIsVisible", ShieldDeactivationIsVisibleOption, 0, TabGroup.CrewmateRoles) - .SetParent(ShieldBreaksOnKillAttempt); - - ResetCooldown = new FloatOptionItem(Id + 8, "MedicResetCooldown", new(0f, 120f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]) - .SetValueFormat(OptionFormat.Seconds); - - GuesserIgnoreShield = new BooleanOptionItem(Id + 9, "MedicShieldedCanBeGuessed", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - JudgingIgnoreShield = new BooleanOptionItem(Id + 10, "MedicShieldedCanBeJudged", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - AmountOfShields = new IntegerOptionItem(Id + 11, "MedicAmountOfShields", new(1, 14, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]); - - UsePet = Options.CreatePetUseSetting(Id + 12, CustomRoles.Medic); - - CD = new FloatOptionItem(Id + 13, "AbilityCooldown", new(0f, 180f, 2.5f), 7.5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Medic]) - .SetValueFormat(OptionFormat.Seconds); + case 0: + killer.RPCPlayCustomSound("Shield"); + target.RPCPlayCustomSound("Shield"); + break; + case 1: + killer.RPCPlayCustomSound("Shield"); + break; + case 2: + target.RPCPlayCustomSound("Shield"); + break; } - public override void Init() - { - PlayerIdList = []; - ProtectList = []; - TempMarkProtectedList = []; - SkillLimit = AmountOfShields.GetInt(); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - LocalPlayerTryKillShieldedTimes = 0; + if (target.IsLocalPlayer() && target.Is(CustomRoles.Snitch)) + { + if (WhoCanSeeProtect.GetInt() is 1 or 3) Achievements.Type.ImUnstoppable.CompleteAfterGameEnd(); + else Achievements.Type.ImUnstoppable.Complete(); } - public override void Add(byte playerId) + return false; + } + + public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + + if (!ProtectList.Contains(target.PlayerId)) return false; + + killer.SetKillCooldown(ResetCooldown.GetFloat()); + + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + + switch (KnowShieldBroken.GetInt()) { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(SkillLimit); + case 0: + target.RpcGuardAndKill(target); + Main.AllPlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId) && !x.Data.IsDead).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForMedic"))); + Main.AllPlayerControls.Where(x => ProtectList.Contains(x.PlayerId)).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForTarget"))); + break; + case 1: + Main.AllPlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId) && !x.Data.IsDead).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForMedic"))); + break; + case 2: + target.RpcGuardAndKill(target); + Main.AllPlayerControls.Where(x => ProtectList.Contains(x.PlayerId)).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForTarget"))); + break; } - private static void SendRPCForProtectList() + if (ShieldBreaksOnKillAttempt.GetBool()) { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetMedicalerProtectList, SendOption.Reliable); - writer.Write(ProtectList.Count); - ProtectList.Do(x => writer.Write(x)); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCForProtectList(MessageReader reader) - { - int count = reader.ReadInt32(); - ProtectList = []; - for (var i = 0; i < count; i++) ProtectList.Add(reader.ReadByte()); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !Main.PlayerStates[pc.PlayerId].IsDead - && pc.GetAbilityUseLimit() >= 1; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? CD.GetFloat() : 300f; - } - - public static bool InProtect(byte id) - { - return ProtectList.Contains(id) && Main.PlayerStates.TryGetValue(id, out PlayerState ps) && !ps.IsDead; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; - - if (!CanUseKillButton(killer)) return false; - - if (ProtectList.Contains(target.PlayerId)) return false; - - killer.RpcRemoveAbilityUse(); - - ProtectList.Add(target.PlayerId); - TempMarkProtectedList.Add(target.PlayerId); + ProtectList.Remove(target.PlayerId); SendRPCForProtectList(); - killer.SetKillCooldown(); - - switch (WhoCanSeeProtect.GetInt()) + if ((Visible)ShieldBreakIsVisible.GetInt() == Visible.Immediately) { - case 0: - killer.RPCPlayCustomSound("Shield"); - target.RPCPlayCustomSound("Shield"); - break; - case 1: - killer.RPCPlayCustomSound("Shield"); - break; - case 2: - target.RPCPlayCustomSound("Shield"); - break; + TempMarkProtectedList.Remove(target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); } - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - - if (target.IsLocalPlayer() && target.Is(CustomRoles.Snitch)) - { - if (WhoCanSeeProtect.GetInt() is 1 or 3) Achievements.Type.ImUnstoppable.CompleteAfterGameEnd(); - else Achievements.Type.ImUnstoppable.Complete(); - } - - return false; } - public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) + if (killer.IsLocalPlayer()) { - if (killer == null || target == null) return false; + LocalPlayerTryKillShieldedTimes++; + if (LocalPlayerTryKillShieldedTimes >= 2) Achievements.Type.DiePleaseDie.CompleteAfterGameEnd(); + } - if (!ProtectList.Contains(target.PlayerId)) return false; + return true; + } - killer.SetKillCooldown(ResetCooldown.GetFloat()); + public static void OnCheckMark() + { + var notify = false; + notify |= CheckMedicDeath(); + notify |= CheckShieldBreak(); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + if (notify) + { + foreach (byte id in PlayerIdList) + foreach (byte id2 in ProtectList) + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(id), SpecifyTarget: Utils.GetPlayerById(id2)); + } + } - switch (KnowShieldBroken.GetInt()) - { - case 0: - target.RpcGuardAndKill(target); - Main.AllPlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId) && !x.Data.IsDead).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForMedic"))); - Main.AllPlayerControls.Where(x => ProtectList.Contains(x.PlayerId)).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForTarget"))); - break; - case 1: - Main.AllPlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId) && !x.Data.IsDead).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForMedic"))); - break; - case 2: - target.RpcGuardAndKill(target); - Main.AllPlayerControls.Where(x => ProtectList.Contains(x.PlayerId)).Do(x => x.Notify(Translator.GetString("MedicKillerTryBrokenShieldTargetForTarget"))); - break; - } - - if (ShieldBreaksOnKillAttempt.GetBool()) - { - ProtectList.Remove(target.PlayerId); - SendRPCForProtectList(); - - if ((Visible)ShieldBreakIsVisible.GetInt() == Visible.Immediately) - { - TempMarkProtectedList.Remove(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - } - } - - if (killer.IsLocalPlayer()) - { - LocalPlayerTryKillShieldedTimes++; - if (LocalPlayerTryKillShieldedTimes >= 2) Achievements.Type.DiePleaseDie.CompleteAfterGameEnd(); - } + private static bool CheckMedicDeath() + { + if (!ShieldDeactivatesWhenMedicDies.GetBool()) return false; + if ((Visible)ShieldDeactivationIsVisible.GetInt() == Visible.AfterMeeting) + { + TempMarkProtectedList = []; return true; } - public static void OnCheckMark() - { - var notify = false; - notify |= CheckMedicDeath(); - notify |= CheckShieldBreak(); + return false; + } - if (notify) - { - foreach (byte id in PlayerIdList) - foreach (byte id2 in ProtectList) - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(id), SpecifyTarget: Utils.GetPlayerById(id2)); - } + private static bool CheckShieldBreak() + { + if (!ShieldBreaksOnKillAttempt.GetBool()) return false; + + if ((Visible)ShieldBreakIsVisible.GetInt() == Visible.AfterMeeting) + { + TempMarkProtectedList = []; + return true; } - private static bool CheckMedicDeath() + return false; + } + + public static void IsDead(PlayerControl target) + { + if (!target.Is(CustomRoles.Medic)) return; + + if (!ShieldDeactivatesWhenMedicDies.GetBool()) return; + + Utils.NotifyRoles(SpecifySeer: target); + + ProtectList.Clear(); + SendRPCForProtectList(); + Logger.Info($"{target.GetNameWithRole().RemoveHtmlTags()} : Medic is dead", "Medic"); + + if ((Visible)ShieldDeactivationIsVisible.GetInt() == Visible.Immediately) { - if (!ShieldDeactivatesWhenMedicDies.GetBool()) return false; + TempMarkProtectedList = []; - if ((Visible)ShieldDeactivationIsVisible.GetInt() == Visible.AfterMeeting) - { - TempMarkProtectedList = []; - return true; - } - - return false; - } - - private static bool CheckShieldBreak() - { - if (!ShieldBreaksOnKillAttempt.GetBool()) return false; - - if ((Visible)ShieldBreakIsVisible.GetInt() == Visible.AfterMeeting) - { - TempMarkProtectedList = []; - return true; - } - - return false; - } - - public static void IsDead(PlayerControl target) - { - if (!target.Is(CustomRoles.Medic)) return; - - if (!ShieldDeactivatesWhenMedicDies.GetBool()) return; - - Utils.NotifyRoles(SpecifySeer: target); - - ProtectList.Clear(); - SendRPCForProtectList(); - Logger.Info($"{target.GetNameWithRole().RemoveHtmlTags()} : Medic is dead", "Medic"); - - if ((Visible)ShieldDeactivationIsVisible.GetInt() == Visible.Immediately) - { - TempMarkProtectedList = []; - - foreach (byte pc in ProtectList) Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(pc), SpecifyTarget: target); - } - } - - public static string GetMark(PlayerControl seer, PlayerControl target) - { - if (ProtectList.Count > 0) - { - var shieldMark = $" ●"; - - bool self = seer.PlayerId == target.PlayerId; - bool seerIsMedic = seer.Is(CustomRoles.Medic); - bool targetProtected = InProtect(target.PlayerId); - bool seerProtected = InProtect(seer.PlayerId); - bool targetSeesProtection = WhoCanSeeProtect.GetInt() is 0 or 2; - bool medicSeesProtection = WhoCanSeeProtect.GetInt() is 0 or 1; - bool seerHasMark = TempMarkProtectedList.Contains(seer.PlayerId); - bool targetHasMark = TempMarkProtectedList.Contains(target.PlayerId); - - if (self && (seerProtected || seerHasMark) && targetSeesProtection) return shieldMark; - - if (seerIsMedic && (targetProtected || targetHasMark) && medicSeesProtection) return shieldMark; - - if (seer.Data.IsDead && targetProtected && !seerIsMedic) return shieldMark; - } - - return string.Empty; - } - - private enum Visible - { - Immediately, - AfterMeeting, - Off + foreach (byte pc in ProtectList) Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(pc), SpecifyTarget: target); } } + + public static string GetMark(PlayerControl seer, PlayerControl target) + { + if (ProtectList.Count > 0) + { + var shieldMark = $" ●"; + + bool self = seer.PlayerId == target.PlayerId; + bool seerIsMedic = seer.Is(CustomRoles.Medic); + bool targetProtected = InProtect(target.PlayerId); + bool seerProtected = InProtect(seer.PlayerId); + bool targetSeesProtection = WhoCanSeeProtect.GetInt() is 0 or 2; + bool medicSeesProtection = WhoCanSeeProtect.GetInt() is 0 or 1; + bool seerHasMark = TempMarkProtectedList.Contains(seer.PlayerId); + bool targetHasMark = TempMarkProtectedList.Contains(target.PlayerId); + + if (self && (seerProtected || seerHasMark) && targetSeesProtection) return shieldMark; + + if (seerIsMedic && (targetProtected || targetHasMark) && medicSeesProtection) return shieldMark; + + if (seer.Data.IsDead && targetProtected && !seerIsMedic) return shieldMark; + } + + return string.Empty; + } + + private enum Visible + { + Immediately, + AfterMeeting, + Off + } } \ No newline at end of file diff --git a/Roles/Crewmate/Mediumshiper.cs b/Roles/Crewmate/Mediumshiper.cs index 1966f81b2..528272784 100644 --- a/Roles/Crewmate/Mediumshiper.cs +++ b/Roles/Crewmate/Mediumshiper.cs @@ -2,120 +2,119 @@ using System.Linq; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Mediumshiper : RoleBase { - public class Mediumshiper : RoleBase + private const int Id = 7200; + public static List PlayerIdList = []; + + private static OptionItem ContactLimitOpt; + public static OptionItem OnlyReceiveMsgFromCrew; + public static OptionItem MediumAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public static Dictionary ContactPlayer = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 7200; - public static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mediumshiper); - private static OptionItem ContactLimitOpt; - public static OptionItem OnlyReceiveMsgFromCrew; - public static OptionItem MediumAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + ContactLimitOpt = new IntegerOptionItem(Id + 10, "MediumshiperContactLimit", new(0, 15, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]) + .SetValueFormat(OptionFormat.Times); - public static Dictionary ContactPlayer = []; + OnlyReceiveMsgFromCrew = new BooleanOptionItem(Id + 11, "MediumshiperOnlyReceiveMsgFromCrew", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]); - public override bool IsEnable => PlayerIdList.Count > 0; + MediumAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + ContactPlayer = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(ContactLimitOpt.GetInt()); + } + + public static void OnReportDeadBody(NetworkedPlayerInfo target) + { + ContactPlayer = []; + if (target == null || target.Object == null) return; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId) && x.PlayerId != target.PlayerId).ToArray()) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mediumshiper); + if (pc.GetAbilityUseLimit() < 1) continue; - ContactLimitOpt = new IntegerOptionItem(Id + 10, "MediumshiperContactLimit", new(0, 15, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]) - .SetValueFormat(OptionFormat.Times); - - OnlyReceiveMsgFromCrew = new BooleanOptionItem(Id + 11, "MediumshiperOnlyReceiveMsgFromCrew", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]); - - MediumAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Mediumshiper]) - .SetValueFormat(OptionFormat.Times); + pc.RpcRemoveAbilityUse(); + ContactPlayer.TryAdd(target.PlayerId, pc.PlayerId); + Logger.Info($"Medium Connection:{pc.GetNameWithRole().RemoveHtmlTags()} => {target.PlayerName}", "Mediumshiper"); } + } - public override void Init() + public static bool MsMsg(PlayerControl pc, string msg) + { + if (!AmongUsClient.Instance.AmHost) return false; + + if (!GameStates.IsMeeting || pc == null) return false; + + if (!ContactPlayer.ContainsKey(pc.PlayerId)) return false; + + if (OnlyReceiveMsgFromCrew.GetBool() && !pc.IsCrewmate()) return false; + + if (pc.IsAlive()) return false; + + msg = msg.ToLower().Trim(); + if (!CheckCommand(ref msg, "通灵|ms|mediumship|medium", false)) return false; + + bool ans; + + if (msg.Contains('n') || msg.Contains(GetString("No")) || msg.Contains('错') || msg.Contains("不是")) + ans = false; + else if (msg.Contains('y') || msg.Contains(GetString("Yes")) || msg.Contains('对')) + ans = true; + else { - PlayerIdList = []; - ContactPlayer = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(ContactLimitOpt.GetInt()); - } - - public static void OnReportDeadBody(NetworkedPlayerInfo target) - { - ContactPlayer = []; - if (target == null || target.Object == null) return; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId) && x.PlayerId != target.PlayerId).ToArray()) - { - if (pc.GetAbilityUseLimit() < 1) continue; - - pc.RpcRemoveAbilityUse(); - ContactPlayer.TryAdd(target.PlayerId, pc.PlayerId); - Logger.Info($"Medium Connection:{pc.GetNameWithRole().RemoveHtmlTags()} => {target.PlayerName}", "Mediumshiper"); - } - } - - public static bool MsMsg(PlayerControl pc, string msg) - { - if (!AmongUsClient.Instance.AmHost) return false; - - if (!GameStates.IsMeeting || pc == null) return false; - - if (!ContactPlayer.ContainsKey(pc.PlayerId)) return false; - - if (OnlyReceiveMsgFromCrew.GetBool() && !pc.IsCrewmate()) return false; - - if (pc.IsAlive()) return false; - - msg = msg.ToLower().Trim(); - if (!CheckCommand(ref msg, "通灵|ms|mediumship|medium", false)) return false; - - bool ans; - - if (msg.Contains('n') || msg.Contains(GetString("No")) || msg.Contains('错') || msg.Contains("不是")) - ans = false; - else if (msg.Contains('y') || msg.Contains(GetString("Yes")) || msg.Contains('对')) - ans = true; - else - { - Utils.SendMessage(GetString("MediumshipHelp"), pc.PlayerId); - return true; - } - - Utils.SendMessage(GetString("Mediumship" + (ans ? "Yes" : "No")), ContactPlayer[pc.PlayerId], Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); - Utils.SendMessage(GetString("MediumshipDone"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); - - ContactPlayer.Remove(pc.PlayerId); - + Utils.SendMessage(GetString("MediumshipHelp"), pc.PlayerId); return true; } - private static bool CheckCommand(ref string msg, string command, bool exact = true) + Utils.SendMessage(GetString("Mediumship" + (ans ? "Yes" : "No")), ContactPlayer[pc.PlayerId], Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); + Utils.SendMessage(GetString("MediumshipDone"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mediumshiper), GetString("MediumshipTitle"))); + + ContactPlayer.Remove(pc.PlayerId); + + return true; + } + + private static bool CheckCommand(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); + + foreach (string str in comList) { - string[] comList = command.Split('|'); + if (exact && msg == "/" + str) return true; - foreach (string str in comList) + if (msg.StartsWith("/" + str)) { - if (exact && msg == "/" + str) return true; - - if (msg.StartsWith("/" + str)) - { - msg = msg.Replace("/" + str, string.Empty); - return true; - } + msg = msg.Replace("/" + str, string.Empty); + return true; } - - return false; } + + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/Merchant.cs b/Roles/Crewmate/Merchant.cs index 489c3ac80..13aa164c9 100644 --- a/Roles/Crewmate/Merchant.cs +++ b/Roles/Crewmate/Merchant.cs @@ -4,176 +4,175 @@ using EHR.Neutral; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Merchant : RoleBase { - internal class Merchant : RoleBase + private const int Id = 7300; + private static readonly List PlayerIdList = []; + + public static Dictionary AddonsSold = []; + public static Dictionary> BribedKiller = []; + + private static List Addons = []; + private static Dictionary> GroupedAddons = []; + + private static OptionItem OptionMaxSell; + private static OptionItem OptionMoneyPerSell; + private static OptionItem OptionMoneyRequiredToBribe; + private static OptionItem OptionNotifyBribery; + private static OptionItem OptionCanTargetCrew; + private static OptionItem OptionCanTargetImpostor; + private static OptionItem OptionCanTargetNeutral; + private static OptionItem OptionCanSellMixed; + private static OptionItem OptionCanSellHelpful; + private static OptionItem OptionCanSellHarmful; + private static OptionItem OptionSellOnlyEnabledAddons; + private static OptionItem OptionSellOnlyHarmfulToEvil; + private static OptionItem OptionSellOnlyHelpfulToCrew; + private static OptionItem OptionGivesAllMoneyOnBribe; + + public override bool IsEnable => PlayerIdList.Count > 0; + + private static int GetCurrentAmountOfMoney(byte playerId) { - private const int Id = 7300; - private static readonly List PlayerIdList = []; + return (AddonsSold[playerId] * OptionMoneyPerSell.GetInt()) - (BribedKiller[playerId].Count * OptionMoneyRequiredToBribe.GetInt()); + } - public static Dictionary AddonsSold = []; - public static Dictionary> BribedKiller = []; + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Merchant); + OptionMaxSell = new IntegerOptionItem(Id + 2, "MerchantMaxSell", new(1, 20, 1), 5, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]).SetValueFormat(OptionFormat.Times); + OptionMoneyPerSell = new IntegerOptionItem(Id + 3, "MerchantMoneyPerSell", new(1, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]).SetValueFormat(OptionFormat.Times); + OptionMoneyRequiredToBribe = new IntegerOptionItem(Id + 4, "MerchantMoneyRequiredToBribe", new(1, 70, 1), 5, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]).SetValueFormat(OptionFormat.Times); + OptionNotifyBribery = new BooleanOptionItem(Id + 5, "MerchantNotifyBribery", false, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanTargetCrew = new BooleanOptionItem(Id + 6, "MerchantTargetCrew", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanTargetImpostor = new BooleanOptionItem(Id + 7, "MerchantTargetImpostor", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanTargetNeutral = new BooleanOptionItem(Id + 8, "MerchantTargetNeutral", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanSellMixed = new BooleanOptionItem(Id + 9, "MerchantSellMixed", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanSellHelpful = new BooleanOptionItem(Id + 10, "MerchantSellHelpful", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionCanSellHarmful = new BooleanOptionItem(Id + 11, "MerchantSellHarmful", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionSellOnlyEnabledAddons = new BooleanOptionItem(Id + 12, "MerchantSellOnlyEnabledAddons", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionSellOnlyHarmfulToEvil = new BooleanOptionItem(Id + 13, "MerchantSellHarmfulToEvil", false, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionSellOnlyHelpfulToCrew = new BooleanOptionItem(Id + 14, "MerchantSellHelpfulToCrew", false, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + OptionGivesAllMoneyOnBribe = new BooleanOptionItem(Id + 15, "MerchantGivesAllMoneyOnBribe", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - private static List Addons = []; - private static Dictionary> GroupedAddons = []; + OverrideTasksData.Create(Id + 18, TabGroup.CrewmateRoles, CustomRoles.Merchant); + } - private static OptionItem OptionMaxSell; - private static OptionItem OptionMoneyPerSell; - private static OptionItem OptionMoneyRequiredToBribe; - private static OptionItem OptionNotifyBribery; - private static OptionItem OptionCanTargetCrew; - private static OptionItem OptionCanTargetImpostor; - private static OptionItem OptionCanTargetNeutral; - private static OptionItem OptionCanSellMixed; - private static OptionItem OptionCanSellHelpful; - private static OptionItem OptionCanSellHarmful; - private static OptionItem OptionSellOnlyEnabledAddons; - private static OptionItem OptionSellOnlyHarmfulToEvil; - private static OptionItem OptionSellOnlyHelpfulToCrew; - private static OptionItem OptionGivesAllMoneyOnBribe; + public override void Init() + { + PlayerIdList.Clear(); - public override bool IsEnable => PlayerIdList.Count > 0; + AddonsSold = []; + BribedKiller = []; - private static int GetCurrentAmountOfMoney(byte playerId) + GroupedAddons = Options.GroupedAddons.ToDictionary(x => x.Key, x => x.Value.ToList()); + + if (!OptionCanSellHarmful.GetBool()) GroupedAddons.Remove(AddonTypes.Harmful); + + if (!OptionCanSellHelpful.GetBool()) GroupedAddons.Remove(AddonTypes.Helpful); + + if (!OptionCanSellMixed.GetBool()) GroupedAddons.Remove(AddonTypes.Mixed); + + GroupedAddons.Remove(AddonTypes.ImpOnly); + + Addons = GroupedAddons.Values.Flatten().ToList(); + + if (OptionSellOnlyEnabledAddons.GetBool()) Addons.RemoveAll(x => x.GetMode() == 0); + + Addons.RemoveAll(StartGameHostPatch.BasisChangingAddons.ContainsKey); + Addons.RemoveAll(x => x.IsNotAssignableMidGame()); + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + AddonsSold.Add(playerId, 0); + BribedKiller.Add(playerId, []); + } + + public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + if (!player.IsAlive() || !player.Is(CustomRoles.Merchant) || AddonsSold[player.PlayerId] >= OptionMaxSell.GetInt()) return; + + if (Addons.Count == 0) { - return (AddonsSold[playerId] * OptionMoneyPerSell.GetInt()) - (BribedKiller[playerId].Count * OptionMoneyRequiredToBribe.GetInt()); + player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); + Logger.Info("No addons to sell.", "Merchant"); + return; } - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Merchant); - OptionMaxSell = new IntegerOptionItem(Id + 2, "MerchantMaxSell", new(1, 20, 1), 5, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]).SetValueFormat(OptionFormat.Times); - OptionMoneyPerSell = new IntegerOptionItem(Id + 3, "MerchantMoneyPerSell", new(1, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]).SetValueFormat(OptionFormat.Times); - OptionMoneyRequiredToBribe = new IntegerOptionItem(Id + 4, "MerchantMoneyRequiredToBribe", new(1, 70, 1), 5, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]).SetValueFormat(OptionFormat.Times); - OptionNotifyBribery = new BooleanOptionItem(Id + 5, "MerchantNotifyBribery", false, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionCanTargetCrew = new BooleanOptionItem(Id + 6, "MerchantTargetCrew", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionCanTargetImpostor = new BooleanOptionItem(Id + 7, "MerchantTargetImpostor", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionCanTargetNeutral = new BooleanOptionItem(Id + 8, "MerchantTargetNeutral", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionCanSellMixed = new BooleanOptionItem(Id + 9, "MerchantSellMixed", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionCanSellHelpful = new BooleanOptionItem(Id + 10, "MerchantSellHelpful", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionCanSellHarmful = new BooleanOptionItem(Id + 11, "MerchantSellHarmful", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionSellOnlyEnabledAddons = new BooleanOptionItem(Id + 12, "MerchantSellOnlyEnabledAddons", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionSellOnlyHarmfulToEvil = new BooleanOptionItem(Id + 13, "MerchantSellHarmfulToEvil", false, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionSellOnlyHelpfulToCrew = new BooleanOptionItem(Id + 14, "MerchantSellHelpfulToCrew", false, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); - OptionGivesAllMoneyOnBribe = new BooleanOptionItem(Id + 15, "MerchantGivesAllMoneyOnBribe", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Merchant]); + CustomRoles addon = Addons.RandomElement(); - OverrideTasksData.Create(Id + 18, TabGroup.CrewmateRoles, CustomRoles.Merchant); + List availableTargets = + Main.AllAlivePlayerControls.Where(x => + x.PlayerId != player.PlayerId + && !Pelican.IsEaten(x.PlayerId) + && !x.Is(addon) + && CustomRolesHelper.CheckAddonConflict(addon, x) + && (Cleanser.CleansedCanGetAddon.GetBool() || (!Cleanser.CleansedCanGetAddon.GetBool() && !x.Is(CustomRoles.Cleansed))) + && ((OptionCanTargetCrew.GetBool() && x.IsCrewmate()) || + (OptionCanTargetImpostor.GetBool() && x.GetCustomRole().IsImpostor()) || + (OptionCanTargetNeutral.GetBool() && (x.GetCustomRole().IsNeutral() || x.IsNeutralKiller()))) + ).ToList(); + + if (availableTargets.Count <= 0) return; + + bool helpfulAddon = GroupedAddons.TryGetValue(AddonTypes.Helpful, out List helpful) && helpful.Contains(addon); + bool harmfulAddon = GroupedAddons.TryGetValue(AddonTypes.Harmful, out List harmful) && harmful.Contains(addon); + + if (helpfulAddon && OptionSellOnlyHarmfulToEvil.GetBool()) availableTargets.RemoveAll(x => !x.Is(Team.Crewmate)); + + if (harmfulAddon && OptionSellOnlyHelpfulToCrew.GetBool()) availableTargets.RemoveAll(x => x.Is(Team.Crewmate)); + + if (availableTargets.Count == 0) + { + player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); + return; } - public override void Init() + PlayerControl target = availableTargets.RandomElement(); + + target.RpcSetCustomRole(addon); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSell"))); + player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonDelivered"))); + + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: player); + + AddonsSold[player.PlayerId]++; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (IsBribedKiller(killer, target)) { - PlayerIdList.Clear(); - - AddonsSold = []; - BribedKiller = []; - - GroupedAddons = Options.GroupedAddons.ToDictionary(x => x.Key, x => x.Value.ToList()); - - if (!OptionCanSellHarmful.GetBool()) GroupedAddons.Remove(AddonTypes.Harmful); - - if (!OptionCanSellHelpful.GetBool()) GroupedAddons.Remove(AddonTypes.Helpful); - - if (!OptionCanSellMixed.GetBool()) GroupedAddons.Remove(AddonTypes.Mixed); - - GroupedAddons.Remove(AddonTypes.ImpOnly); - - Addons = GroupedAddons.Values.Flatten().ToList(); - - if (OptionSellOnlyEnabledAddons.GetBool()) Addons.RemoveAll(x => x.GetMode() == 0); - - Addons.RemoveAll(StartGameHostPatch.BasisChangingAddons.ContainsKey); - Addons.RemoveAll(x => x.IsNotAssignableMidGame()); + NotifyBribery(killer, target); + return true; } - public override void Add(byte playerId) + if (GetCurrentAmountOfMoney(target.PlayerId) >= OptionMoneyRequiredToBribe.GetInt()) { - PlayerIdList.Add(playerId); - AddonsSold.Add(playerId, 0); - BribedKiller.Add(playerId, []); + NotifyBribery(killer, target); + BribedKiller[target.PlayerId].Add(killer.PlayerId); + return true; } - public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) - { - if (!player.IsAlive() || !player.Is(CustomRoles.Merchant) || AddonsSold[player.PlayerId] >= OptionMaxSell.GetInt()) return; + return false; + } - if (Addons.Count == 0) - { - player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); - Logger.Info("No addons to sell.", "Merchant"); - return; - } + public static bool IsBribedKiller(PlayerControl killer, PlayerControl target) + { + return BribedKiller[target.PlayerId].Contains(killer.PlayerId); + } - CustomRoles addon = Addons.RandomElement(); + private static void NotifyBribery(PlayerControl killer, PlayerControl target) + { + if (OptionGivesAllMoneyOnBribe.GetBool()) AddonsSold[target.PlayerId] = 0; - List availableTargets = - Main.AllAlivePlayerControls.Where(x => - x.PlayerId != player.PlayerId - && !Pelican.IsEaten(x.PlayerId) - && !x.Is(addon) - && CustomRolesHelper.CheckAddonConflict(addon, x) - && (Cleanser.CleansedCanGetAddon.GetBool() || (!Cleanser.CleansedCanGetAddon.GetBool() && !x.Is(CustomRoles.Cleansed))) - && ((OptionCanTargetCrew.GetBool() && x.IsCrewmate()) || - (OptionCanTargetImpostor.GetBool() && x.GetCustomRole().IsImpostor()) || - (OptionCanTargetNeutral.GetBool() && (x.GetCustomRole().IsNeutral() || x.IsNeutralKiller()))) - ).ToList(); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("BribedByMerchant"))); - if (availableTargets.Count <= 0) return; - - bool helpfulAddon = GroupedAddons.TryGetValue(AddonTypes.Helpful, out List helpful) && helpful.Contains(addon); - bool harmfulAddon = GroupedAddons.TryGetValue(AddonTypes.Harmful, out List harmful) && harmful.Contains(addon); - - if (helpfulAddon && OptionSellOnlyHarmfulToEvil.GetBool()) availableTargets.RemoveAll(x => !x.Is(Team.Crewmate)); - - if (harmfulAddon && OptionSellOnlyHelpfulToCrew.GetBool()) availableTargets.RemoveAll(x => x.Is(Team.Crewmate)); - - if (availableTargets.Count == 0) - { - player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSellFail"))); - return; - } - - PlayerControl target = availableTargets.RandomElement(); - - target.RpcSetCustomRole(addon); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonSell"))); - player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantAddonDelivered"))); - - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: player); - - AddonsSold[player.PlayerId]++; - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (IsBribedKiller(killer, target)) - { - NotifyBribery(killer, target); - return true; - } - - if (GetCurrentAmountOfMoney(target.PlayerId) >= OptionMoneyRequiredToBribe.GetInt()) - { - NotifyBribery(killer, target); - BribedKiller[target.PlayerId].Add(killer.PlayerId); - return true; - } - - return false; - } - - public static bool IsBribedKiller(PlayerControl killer, PlayerControl target) - { - return BribedKiller[target.PlayerId].Contains(killer.PlayerId); - } - - private static void NotifyBribery(PlayerControl killer, PlayerControl target) - { - if (OptionGivesAllMoneyOnBribe.GetBool()) AddonsSold[target.PlayerId] = 0; - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("BribedByMerchant"))); - - if (OptionNotifyBribery.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantKillAttemptBribed"))); - } + if (OptionNotifyBribery.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Merchant), GetString("MerchantKillAttemptBribed"))); } } \ No newline at end of file diff --git a/Roles/Crewmate/Mole.cs b/Roles/Crewmate/Mole.cs index d1fd1bc41..e5d5be3b0 100644 --- a/Roles/Crewmate/Mole.cs +++ b/Roles/Crewmate/Mole.cs @@ -1,59 +1,58 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Mole : RoleBase { - internal class Mole : RoleBase + public static bool On; + + public static OptionItem CD; + public override bool IsEnable => On; + + private static int Id => 64400; + + public override void Add(byte playerId) { - public static bool On; + On = true; + } - public static OptionItem CD; - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - private static int Id => 64400; + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mole); - public override void Add(byte playerId) - { - On = true; - } + CD = new FloatOptionItem(Id + 2, "AbilityCooldown", new(0f, 120f, 0.5f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mole]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void Init() - { - On = false; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mole); + AURoleOptions.EngineerInVentMaxTime = 1f; + AURoleOptions.EngineerCooldown = CD.GetFloat(); + } - CD = new FloatOptionItem(Id + 2, "AbilityCooldown", new(0f, 120f, 0.5f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mole]) - .SetValueFormat(OptionFormat.Seconds); - } + public override void OnExitVent(PlayerControl pc, Vent vent) + { + if (UsePets.GetBool()) return; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; + LateTask.New(() => { pc.TPToRandomVent(); }, 0.5f, "Mole TP"); + } - AURoleOptions.EngineerInVentMaxTime = 1f; - AURoleOptions.EngineerCooldown = CD.GetFloat(); - } + public override void OnPet(PlayerControl pc) + { + pc.TPToRandomVent(); + } - public override void OnExitVent(PlayerControl pc, Vent vent) - { - if (UsePets.GetBool()) return; - - LateTask.New(() => { pc.TPToRandomVent(); }, 0.5f, "Mole TP"); - } - - public override void OnPet(PlayerControl pc) - { - pc.TPToRandomVent(); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Monarch.cs b/Roles/Crewmate/Monarch.cs index a258fee0f..483175090 100644 --- a/Roles/Crewmate/Monarch.cs +++ b/Roles/Crewmate/Monarch.cs @@ -3,91 +3,90 @@ using AmongUs.GameOptions; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Monarch : RoleBase { - public class Monarch : RoleBase + private const int Id = 9600; + private static List PlayerIdList = []; + + public static OptionItem KnightCooldown; + public static OptionItem KnightMax; + public static OptionItem UsePet; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 9600; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Monarch); - public static OptionItem KnightCooldown; - public static OptionItem KnightMax; - public static OptionItem UsePet; + KnightCooldown = new FloatOptionItem(Id + 10, "MonarchKnightCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Monarch]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + KnightMax = new IntegerOptionItem(Id + 12, "MonarchKnightMax", new(1, 15, 1), 2, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Monarch]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + UsePet = CreatePetUseSetting(Id + 11, CustomRoles.Monarch); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(KnightMax.GetInt()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KnightCooldown.GetFloat(); + } + + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead && player.GetAbilityUseLimit() >= 1; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return false; + + if (target != null && !target.GetCustomRole().IsNotKnightable() && !target.Is(CustomRoles.Knighted) && !target.Is(CustomRoles.TicketsStealer)) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Monarch); + killer.RpcRemoveAbilityUse(); + target.RpcSetCustomRole(CustomRoles.Knighted); - KnightCooldown = new FloatOptionItem(Id + 10, "MonarchKnightCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Monarch]) - .SetValueFormat(OptionFormat.Seconds); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Monarch), GetString("MonarchKnightedPlayer"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Monarch), GetString("KnightedByMonarch"))); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - KnightMax = new IntegerOptionItem(Id + 12, "MonarchKnightMax", new(1, 15, 1), 2, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Monarch]) - .SetValueFormat(OptionFormat.Times); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); - UsePet = CreatePetUseSetting(Id + 11, CustomRoles.Monarch); - } + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(KnightMax.GetInt()); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KnightCooldown.GetFloat(); - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead && player.GetAbilityUseLimit() >= 1; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { + Logger.Info("SetRole:" + target.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Knighted, "Assign " + CustomRoles.Knighted); return false; } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return false; - - if (target != null && !target.GetCustomRole().IsNotKnightable() && !target.Is(CustomRoles.Knighted) && !target.Is(CustomRoles.TicketsStealer)) - { - killer.RpcRemoveAbilityUse(); - target.RpcSetCustomRole(CustomRoles.Knighted); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Monarch), GetString("MonarchKnightedPlayer"))); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Monarch), GetString("KnightedByMonarch"))); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - - target.RpcGuardAndKill(killer); - target.RpcGuardAndKill(target); - - Logger.Info("SetRole:" + target.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Knighted, "Assign " + CustomRoles.Knighted); - return false; - } - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Monarch), GetString("MonarchInvalidTarget"))); - return false; - } + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Monarch), GetString("MonarchInvalidTarget"))); + return false; } } \ No newline at end of file diff --git a/Roles/Crewmate/Monitor.cs b/Roles/Crewmate/Monitor.cs index 5f0909d67..488c1dd07 100644 --- a/Roles/Crewmate/Monitor.cs +++ b/Roles/Crewmate/Monitor.cs @@ -1,34 +1,33 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Monitor : RoleBase { - internal class Monitor : RoleBase + private const int Id = 2350; + + public static OptionItem CanCheckCamera; + public static OptionItem CanVent; + public static bool On; + + public override bool IsEnable => On; + + public override void Init() { - private const int Id = 2350; + On = false; + } - public static OptionItem CanCheckCamera; - public static OptionItem CanVent; - public static bool On; + public override void Add(byte playerId) + { + On = true; + } - public override bool IsEnable => On; + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Monitor); - public override void Init() - { - On = false; - } + CanCheckCamera = new BooleanOptionItem(Id + 10, "CanCheckCamera", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Monitor]); - public override void Add(byte playerId) - { - On = true; - } - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Monitor); - - CanCheckCamera = new BooleanOptionItem(Id + 10, "CanCheckCamera", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Monitor]); - - CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Monitor]); - } + CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Monitor]); } } \ No newline at end of file diff --git a/Roles/Crewmate/Mortician.cs b/Roles/Crewmate/Mortician.cs index 15ae4a766..b313a9b91 100644 --- a/Roles/Crewmate/Mortician.cs +++ b/Roles/Crewmate/Mortician.cs @@ -2,95 +2,94 @@ using UnityEngine; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Mortician : RoleBase { - public class Mortician : RoleBase + private const int Id = 7400; + public static List PlayerIdList = []; + + private static OptionItem ShowArrows; + + private static Dictionary LastPlayerName = []; + public static Dictionary MsgToSend = []; + + private byte MorticianId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 7400; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mortician); - private static OptionItem ShowArrows; + ShowArrows = new BooleanOptionItem(Id + 2, "ShowArrows", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mortician]); + } - private static Dictionary LastPlayerName = []; - public static Dictionary MsgToSend = []; + public override void Init() + { + PlayerIdList = []; + LastPlayerName = []; + MsgToSend = []; + } - private byte MorticianId; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + MorticianId = playerId; + } - public override bool IsEnable => PlayerIdList.Count > 0; + public static void OnPlayerDead(PlayerControl target) + { + Vector2 pos = target.Pos(); + var minDis = float.MaxValue; + var minName = string.Empty; - public override void SetupCustomOption() + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Mortician); + if (pc.PlayerId == target.PlayerId) continue; - ShowArrows = new BooleanOptionItem(Id + 2, "ShowArrows", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mortician]); - } + float dis = Vector2.Distance(pc.Pos(), pos); - public override void Init() - { - PlayerIdList = []; - LastPlayerName = []; - MsgToSend = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - MorticianId = playerId; - } - - public static void OnPlayerDead(PlayerControl target) - { - Vector2 pos = target.Pos(); - var minDis = float.MaxValue; - var minName = string.Empty; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (dis < minDis && dis < 1.5f) { - if (pc.PlayerId == target.PlayerId) continue; - - float dis = Vector2.Distance(pc.Pos(), pos); - - if (dis < minDis && dis < 1.5f) - { - minDis = dis; - minName = pc.GetRealName(); - } - } - - LastPlayerName.TryAdd(target.PlayerId, minName); - - foreach (byte pc in PlayerIdList) - { - PlayerControl player = Utils.GetPlayerById(pc); - if (player == null || !player.IsAlive()) continue; - - LocateArrow.Add(pc, target.transform.position); + minDis = dis; + minName = pc.GetRealName(); } } - public static void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target) + LastPlayerName.TryAdd(target.PlayerId, minName); + + foreach (byte pc in PlayerIdList) { - foreach (byte apc in PlayerIdList) LocateArrow.RemoveAllTarget(apc); + PlayerControl player = Utils.GetPlayerById(pc); + if (player == null || !player.IsAlive()) continue; - if (!pc.Is(CustomRoles.Mortician) || target == null || pc.PlayerId == target.PlayerId) return; - - LastPlayerName.TryGetValue(target.PlayerId, out string name); - MsgToSend.Add(pc.PlayerId, name == "" ? string.Format(Translator.GetString("MorticianGetNoInfo"), target.PlayerName) : string.Format(Translator.GetString("MorticianGetInfo"), target.PlayerName, name)); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (ShowArrows.GetBool()) - { - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (seer.PlayerId != MorticianId) return string.Empty; - - return GameStates.IsMeeting ? string.Empty : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); - } - - return string.Empty; + LocateArrow.Add(pc, target.transform.position); } } + + public static void OnReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target) + { + foreach (byte apc in PlayerIdList) LocateArrow.RemoveAllTarget(apc); + + if (!pc.Is(CustomRoles.Mortician) || target == null || pc.PlayerId == target.PlayerId) return; + + LastPlayerName.TryGetValue(target.PlayerId, out string name); + MsgToSend.Add(pc.PlayerId, name == "" ? string.Format(Translator.GetString("MorticianGetNoInfo"), target.PlayerName) : string.Format(Translator.GetString("MorticianGetInfo"), target.PlayerName, name)); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (ShowArrows.GetBool()) + { + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + if (seer.PlayerId != MorticianId) return string.Empty; + + return GameStates.IsMeeting ? string.Empty : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); + } + + return string.Empty; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Needy.cs b/Roles/Crewmate/Needy.cs index 1b4141b28..811c31ba2 100644 --- a/Roles/Crewmate/Needy.cs +++ b/Roles/Crewmate/Needy.cs @@ -1,16 +1,15 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Needy : RoleBase { - internal class Needy : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(5700, TabGroup.CrewmateRoles, CustomRoles.Needy); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + Options.SetupRoleOptions(5700, TabGroup.CrewmateRoles, CustomRoles.Needy); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Negotiator.cs b/Roles/Crewmate/Negotiator.cs index 6268ee4ae..7ed610a29 100644 --- a/Roles/Crewmate/Negotiator.cs +++ b/Roles/Crewmate/Negotiator.cs @@ -4,147 +4,146 @@ using System.Linq; using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Negotiator : RoleBase { - public class Negotiator : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem MinVotingTimeLeftToNegotiate; + private static OptionItem LowVision; + private static OptionItem LowSpeed; + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachTaskCompleted; + private static OptionItem AbilityChargesWhenFinishedTasks; + private byte NegotiatorId; + private NegotiationType Penalty; + private Dictionary> PermanentPenalties; + private byte TargetId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(647950) + .AutoSetupOption(ref MinVotingTimeLeftToNegotiate, 10, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref LowVision, 0.4f, new FloatValueRule(0f, 1f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref LowSpeed, 0.9f, new FloatValueRule(0.1f, 2f, 0.1f), OptionFormat.Multiplier) + .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.4f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times); + } - private static OptionItem MinVotingTimeLeftToNegotiate; - private static OptionItem LowVision; - private static OptionItem LowSpeed; - private static OptionItem AbilityUseLimit; - private static OptionItem AbilityUseGainWithEachTaskCompleted; - private static OptionItem AbilityChargesWhenFinishedTasks; - private byte NegotiatorId; - private NegotiationType Penalty; - private Dictionary> PermanentPenalties; - private byte TargetId; + public override void Init() + { + On = false; + Instances = []; + } - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + NegotiatorId = playerId; + TargetId = byte.MaxValue; + Penalty = default; + PermanentPenalties = []; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - public override void SetupCustomOption() + public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte id) + { + bool lowVision = false, lowSpeed = false; + + foreach (Negotiator instance in Instances) { - StartSetup(647950) - .AutoSetupOption(ref MinVotingTimeLeftToNegotiate, 10, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) - .AutoSetupOption(ref LowVision, 0.4f, new FloatValueRule(0f, 1f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref LowSpeed, 0.9f, new FloatValueRule(0.1f, 2f, 0.1f), OptionFormat.Multiplier) - .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) - .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.4f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) - .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times); + if (instance.PermanentPenalties.TryGetValue(id, out HashSet penalties)) + { + lowVision |= penalties.Contains(NegotiationType.LowVision); + lowSpeed |= penalties.Contains(NegotiationType.LowSpeed); + } } - public override void Init() + if (lowVision) { - On = false; - Instances = []; + float vision = LowVision.GetFloat(); + + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, vision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); } - public override void Add(byte playerId) + if (lowSpeed) Main.AllPlayerSpeed[id] = LowSpeed.GetFloat(); + } + + public override void AfterMeetingTasks() + { + if (TargetId != byte.MaxValue) { - On = true; - Instances.Add(this); - NegotiatorId = playerId; + PlayerControl target = TargetId.GetPlayer(); TargetId = byte.MaxValue; - Penalty = default; - PermanentPenalties = []; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } + if (target == null || !target.IsAlive()) return; - public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte id) - { - bool lowVision = false, lowSpeed = false; + PlayerControl negotiator = NegotiatorId.GetPlayer(); - foreach (Negotiator instance in Instances) + switch (Penalty) { - if (instance.PermanentPenalties.TryGetValue(id, out HashSet penalties)) - { - lowVision |= penalties.Contains(NegotiationType.LowVision); - lowSpeed |= penalties.Contains(NegotiationType.LowSpeed); - } + case NegotiationType.Suicide: + LateTask.New(() => target.Suicide(PlayerState.DeathReason.Negotiation, negotiator), 1f, log: false); + break; + case NegotiationType.HarmfulAddon: + CustomRoles addon = Options.GroupedAddons[AddonTypes.Harmful].Shuffle().FirstOrDefault(x => CustomRolesHelper.CheckAddonConflict(x, target)); + if (addon != default) target.RpcSetCustomRole(addon); + + break; + case NegotiationType.LowVision: + case NegotiationType.LowSpeed: + if (PermanentPenalties.TryGetValue(target.PlayerId, out HashSet penalties)) + penalties.Add(Penalty); + else + PermanentPenalties[target.PlayerId] = [Penalty]; + + break; } - if (lowVision) - { - float vision = LowVision.GetFloat(); - - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, vision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); - } - - if (lowSpeed) Main.AllPlayerSpeed[id] = LowSpeed.GetFloat(); - } - - public override void AfterMeetingTasks() - { - if (TargetId != byte.MaxValue) - { - PlayerControl target = TargetId.GetPlayer(); - TargetId = byte.MaxValue; - if (target == null || !target.IsAlive()) return; - - PlayerControl negotiator = NegotiatorId.GetPlayer(); - - switch (Penalty) - { - case NegotiationType.Suicide: - LateTask.New(() => target.Suicide(PlayerState.DeathReason.Negotiation, negotiator), 1f, log: false); - break; - case NegotiationType.HarmfulAddon: - CustomRoles addon = Options.GroupedAddons[AddonTypes.Harmful].Shuffle().FirstOrDefault(x => CustomRolesHelper.CheckAddonConflict(x, target)); - if (addon != default) target.RpcSetCustomRole(addon); - - break; - case NegotiationType.LowVision: - case NegotiationType.LowSpeed: - if (PermanentPenalties.TryGetValue(target.PlayerId, out HashSet penalties)) - penalties.Add(Penalty); - else - PermanentPenalties[target.PlayerId] = [Penalty]; - - break; - } - - negotiator?.Notify(string.Format(Translator.GetString("Negotiator.FinishNotify"), Translator.GetString($"Negotiator.Type.{Penalty}"))); - } - } - - public override bool OnVote(PlayerControl voter, PlayerControl target) - { - if (target == null || voter == null || voter.PlayerId == target.PlayerId || TargetId != byte.MaxValue || voter.GetAbilityUseLimit() < 1f || MinVotingTimeLeftToNegotiate.GetInt() > MeetingTimeManager.VotingTimeLeft || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; - - TargetId = target.PlayerId; - Penalty = NegotiationType.Suicide; - voter.RpcRemoveAbilityUse(); - - string message = Enum.GetValues().Aggregate(Translator.GetString("Negotiator.TargetMessage"), (s, x) => $"{s}\n{(int)x}) {Translator.GetString($"Negotiator.Type.{x}")}"); - Utils.SendMessage(message, target.PlayerId, Translator.GetString("Negotiator.Title")); - - Main.DontCancelVoteList.Add(voter.PlayerId); - return true; - } - - public static void ReceiveCommand(PlayerControl pc, int index) - { - foreach (Negotiator instance in Instances) - { - if (pc.PlayerId != instance.TargetId || !pc.IsAlive()) continue; - - var type = (NegotiationType)index; - instance.Penalty = type; - string text = string.Format(Translator.GetString("Negotiator.TargetPickSuccess"), Translator.GetString($"Negotiator.Type.{type}")); - Utils.SendMessage(text, pc.PlayerId, Translator.GetString("Negotiator.Title")); - } - } - - private enum NegotiationType - { - Suicide, - HarmfulAddon, - LowVision, - LowSpeed + negotiator?.Notify(string.Format(Translator.GetString("Negotiator.FinishNotify"), Translator.GetString($"Negotiator.Type.{Penalty}"))); } } + + public override bool OnVote(PlayerControl voter, PlayerControl target) + { + if (target == null || voter == null || voter.PlayerId == target.PlayerId || TargetId != byte.MaxValue || voter.GetAbilityUseLimit() < 1f || MinVotingTimeLeftToNegotiate.GetInt() > MeetingTimeManager.VotingTimeLeft || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; + + TargetId = target.PlayerId; + Penalty = NegotiationType.Suicide; + voter.RpcRemoveAbilityUse(); + + string message = Enum.GetValues().Aggregate(Translator.GetString("Negotiator.TargetMessage"), (s, x) => $"{s}\n{(int)x}) {Translator.GetString($"Negotiator.Type.{x}")}"); + Utils.SendMessage(message, target.PlayerId, Translator.GetString("Negotiator.Title")); + + Main.DontCancelVoteList.Add(voter.PlayerId); + return true; + } + + public static void ReceiveCommand(PlayerControl pc, int index) + { + foreach (Negotiator instance in Instances) + { + if (pc.PlayerId != instance.TargetId || !pc.IsAlive()) continue; + + var type = (NegotiationType)index; + instance.Penalty = type; + string text = string.Format(Translator.GetString("Negotiator.TargetPickSuccess"), Translator.GetString($"Negotiator.Type.{type}")); + Utils.SendMessage(text, pc.PlayerId, Translator.GetString("Negotiator.Title")); + } + } + + private enum NegotiationType + { + Suicide, + HarmfulAddon, + LowVision, + LowSpeed + } } \ No newline at end of file diff --git a/Roles/Crewmate/NiceEraser.cs b/Roles/Crewmate/NiceEraser.cs index 78db63b48..87ea916b3 100644 --- a/Roles/Crewmate/NiceEraser.cs +++ b/Roles/Crewmate/NiceEraser.cs @@ -1,99 +1,98 @@ using System.Collections.Generic; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class NiceEraser : RoleBase { - internal class NiceEraser : RoleBase + private const int Id = 5580; + public static List PlayerIdList = []; + + private static OptionItem EraseLimitOpt; + public static OptionItem HideVote; + public static OptionItem CancelVote; + + private static List DidVote = []; + private static List PlayerToErase = []; + public static List ErasedPlayers = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 5580; - public static List PlayerIdList = []; + Options.SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.NiceEraser); - private static OptionItem EraseLimitOpt; - public static OptionItem HideVote; - public static OptionItem CancelVote; + EraseLimitOpt = new IntegerOptionItem(Id + 2, "EraseLimit", new(1, 15, 1), 1, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceEraser]) + .SetValueFormat(OptionFormat.Times); - private static List DidVote = []; - private static List PlayerToErase = []; - public static List ErasedPlayers = []; + HideVote = new BooleanOptionItem(Id + 3, "NiceEraserHideVote", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceEraser]); + CancelVote = Options.CreateVoteCancellingUseSetting(Id + 4, CustomRoles.NiceEraser, TabGroup.CrewmateRoles); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + ErasedPlayers = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(EraseLimitOpt.GetInt()); + } + + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null) return false; + + if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; + + DidVote.Add(player.PlayerId); + + if (player.GetAbilityUseLimit() < 1) return false; + + if (target.PlayerId == player.PlayerId) { - Options.SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.NiceEraser); - - EraseLimitOpt = new IntegerOptionItem(Id + 2, "EraseLimit", new(1, 15, 1), 1, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceEraser]) - .SetValueFormat(OptionFormat.Times); - - HideVote = new BooleanOptionItem(Id + 3, "NiceEraserHideVote", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceEraser]); - CancelVote = Options.CreateVoteCancellingUseSetting(Id + 4, CustomRoles.NiceEraser, TabGroup.CrewmateRoles); + Utils.SendMessage(GetString("EraserEraseSelf"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceEraser), GetString("EraserEraseMsgTitle"))); + return false; } - public override void Init() + if (target.GetCustomRole().IsNeutral()) { - PlayerIdList = []; - ErasedPlayers = []; + Utils.SendMessage(string.Format(GetString("EraserEraseNeutralNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceEraser), GetString("EraserEraseMsgTitle"))); + return false; } - public override void Add(byte playerId) + player.RpcRemoveAbilityUse(); + + if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId); + + Utils.SendMessage(string.Format(GetString("EraserEraseNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceEraser), GetString("EraserEraseMsgTitle"))); + + if (GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: target); + + Main.DontCancelVoteList.Add(player.PlayerId); + return true; + } + + public override void OnReportDeadBody() + { + PlayerToErase = []; + DidVote = []; + } + + public override void AfterMeetingTasks() + { + foreach (byte pc in PlayerToErase.ToArray()) { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(EraseLimitOpt.GetInt()); - } + PlayerControl player = Utils.GetPlayerById(pc); + if (player == null) continue; - public override bool OnVote(PlayerControl player, PlayerControl target) - { - if (player == null || target == null) return false; - - if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - - DidVote.Add(player.PlayerId); - - if (player.GetAbilityUseLimit() < 1) return false; - - if (target.PlayerId == player.PlayerId) - { - Utils.SendMessage(GetString("EraserEraseSelf"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceEraser), GetString("EraserEraseMsgTitle"))); - return false; - } - - if (target.GetCustomRole().IsNeutral()) - { - Utils.SendMessage(string.Format(GetString("EraserEraseNeutralNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceEraser), GetString("EraserEraseMsgTitle"))); - return false; - } - - player.RpcRemoveAbilityUse(); - - if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId); - - Utils.SendMessage(string.Format(GetString("EraserEraseNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceEraser), GetString("EraserEraseMsgTitle"))); - - if (GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: target); - - Main.DontCancelVoteList.Add(player.PlayerId); - return true; - } - - public override void OnReportDeadBody() - { - PlayerToErase = []; - DidVote = []; - } - - public override void AfterMeetingTasks() - { - foreach (byte pc in PlayerToErase.ToArray()) - { - PlayerControl player = Utils.GetPlayerById(pc); - if (player == null) continue; - - player.RpcSetCustomRole(player.GetCustomRole().GetErasedRole()); - player.Notify(GetString("LostRoleByNiceEraser")); - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} 被擦除了", "NiceEraser"); - player.MarkDirtySettings(); - ErasedPlayers.Add(pc); - } + player.RpcSetCustomRole(player.GetCustomRole().GetErasedRole()); + player.Notify(GetString("LostRoleByNiceEraser")); + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()} 被擦除了", "NiceEraser"); + player.MarkDirtySettings(); + ErasedPlayers.Add(pc); } } } \ No newline at end of file diff --git a/Roles/Crewmate/NiceGuesser.cs b/Roles/Crewmate/NiceGuesser.cs index b2191b27d..37dd98a8a 100644 --- a/Roles/Crewmate/NiceGuesser.cs +++ b/Roles/Crewmate/NiceGuesser.cs @@ -1,33 +1,32 @@ using UnityEngine; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class NiceGuesser : RoleBase { - internal class NiceGuesser : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(8600, TabGroup.CrewmateRoles, CustomRoles.NiceGuesser); - public override void SetupCustomOption() - { - SetupRoleOptions(8600, TabGroup.CrewmateRoles, CustomRoles.NiceGuesser); + GGCanGuessTime = new IntegerOptionItem(8610, "GuesserCanGuessTimes", new(0, 15, 1), 15, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]) + .SetValueFormat(OptionFormat.Times); - GGCanGuessTime = new IntegerOptionItem(8610, "GuesserCanGuessTimes", new(0, 15, 1), 15, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]) - .SetValueFormat(OptionFormat.Times); + GGCanGuessCrew = new BooleanOptionItem(8611, "GGCanGuessCrew", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]); - GGCanGuessCrew = new BooleanOptionItem(8611, "GGCanGuessCrew", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]); + GGCanGuessAdt = new BooleanOptionItem(8612, "GGCanGuessAdt", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]); - GGCanGuessAdt = new BooleanOptionItem(8612, "GGCanGuessAdt", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]); - - GGTryHideMsg = new BooleanOptionItem(8613, "GuesserTryHideMsg", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]) - .SetColor(Color.green); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + GGTryHideMsg = new BooleanOptionItem(8613, "GuesserTryHideMsg", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceGuesser]) + .SetColor(Color.green); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/NiceHacker.cs b/Roles/Crewmate/NiceHacker.cs index 02cd9e981..f51060c91 100644 --- a/Roles/Crewmate/NiceHacker.cs +++ b/Roles/Crewmate/NiceHacker.cs @@ -5,213 +5,212 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; +using static Translator; +using static Utils; + +public class NiceHacker : RoleBase { - using static Options; - using static Translator; - using static Utils; + private const int Id = 641000; + public static Dictionary PlayerIdList = []; + public static Dictionary UseLimit = []; + public static Dictionary UseLimitSeconds = []; - public class NiceHacker : RoleBase + private static Dictionary LastUpdate = []; + + public static OptionItem AbilityCD; + public static OptionItem UseLimitOpt; + public static OptionItem NiceHackerAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem ModdedClientAbilityUseSecondsMultiplier; + public static OptionItem ModdedClientCanMoveWhileViewingMap; + public static OptionItem VanillaClientSeesInfoFor; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 641000; - public static Dictionary PlayerIdList = []; - public static Dictionary UseLimit = []; - public static Dictionary UseLimitSeconds = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.NiceHacker); - private static Dictionary LastUpdate = []; + AbilityCD = new FloatOptionItem(Id + 10, "AbilityCD", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) + .SetValueFormat(OptionFormat.Seconds); - public static OptionItem AbilityCD; - public static OptionItem UseLimitOpt; - public static OptionItem NiceHackerAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem ModdedClientAbilityUseSecondsMultiplier; - public static OptionItem ModdedClientCanMoveWhileViewingMap; - public static OptionItem VanillaClientSeesInfoFor; + UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 2, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + NiceHackerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) + .SetValueFormat(OptionFormat.Times); + + ModdedClientAbilityUseSecondsMultiplier = new FloatOptionItem(Id + 14, "NiceHackerModdedClientAbilityUseSecondsMultiplier", new(0f, 70f, 1f), 3f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) + .SetValueFormat(OptionFormat.Seconds); + + ModdedClientCanMoveWhileViewingMap = new BooleanOptionItem(Id + 15, "NiceHackerModdedClientCanMoveWhileViewingMap", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]); + + VanillaClientSeesInfoFor = new FloatOptionItem(Id + 16, "NiceHackerVanillaClientSeesInfoFor", new(0f, 70f, 1f), 4f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + UseLimit = []; + UseLimitSeconds = []; + LastUpdate = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.TryAdd(playerId, GetPlayerById(playerId).IsModClient()); + + if (!GetPlayerById(playerId).IsModClient()) + UseLimit.Add(playerId, UseLimitOpt.GetInt()); + else + UseLimitSeconds.Add(playerId, UseLimitOpt.GetInt() * ModdedClientAbilityUseSecondsMultiplier.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = AbilityCD.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public static void SendRPC(byte playerId, float secondsLeft) + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetNiceHackerLimit, SendOption.Reliable); + writer.Write(playerId); + writer.Write(secondsLeft); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + if (AmongUsClient.Instance.AmHost) return; + + byte playerId = reader.ReadByte(); + float secondsLeft = reader.ReadSingle(); + + UseLimitSeconds[playerId] = secondsLeft; + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + UseAbility(pc); + } + + public override void OnPet(PlayerControl pc) + { + UseAbility(pc); + } + + private static void UseAbility(PlayerControl pc) + { + if (pc == null) return; + + if (pc.IsModClient() || !UseLimit.ContainsKey(pc.PlayerId)) return; + + if (UseLimit[pc.PlayerId] >= 1) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.NiceHacker); - - AbilityCD = new FloatOptionItem(Id + 10, "AbilityCD", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 2, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) - .SetValueFormat(OptionFormat.Times); - - NiceHackerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) - .SetValueFormat(OptionFormat.Times); - - ModdedClientAbilityUseSecondsMultiplier = new FloatOptionItem(Id + 14, "NiceHackerModdedClientAbilityUseSecondsMultiplier", new(0f, 70f, 1f), 3f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) - .SetValueFormat(OptionFormat.Seconds); - - ModdedClientCanMoveWhileViewingMap = new BooleanOptionItem(Id + 15, "NiceHackerModdedClientCanMoveWhileViewingMap", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]); - - VanillaClientSeesInfoFor = new FloatOptionItem(Id + 16, "NiceHackerVanillaClientSeesInfoFor", new(0f, 70f, 1f), 4f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.NiceHacker]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - UseLimit = []; - UseLimitSeconds = []; - LastUpdate = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.TryAdd(playerId, GetPlayerById(playerId).IsModClient()); - - if (!GetPlayerById(playerId).IsModClient()) - UseLimit.Add(playerId, UseLimitOpt.GetInt()); - else - UseLimitSeconds.Add(playerId, UseLimitOpt.GetInt() * ModdedClientAbilityUseSecondsMultiplier.GetInt()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = AbilityCD.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public static void SendRPC(byte playerId, float secondsLeft) - { - if (!DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetNiceHackerLimit, SendOption.Reliable); - writer.Write(playerId); - writer.Write(secondsLeft); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - if (AmongUsClient.Instance.AmHost) return; - - byte playerId = reader.ReadByte(); - float secondsLeft = reader.ReadSingle(); - - UseLimitSeconds[playerId] = secondsLeft; - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - UseAbility(pc); - } - - public override void OnPet(PlayerControl pc) - { - UseAbility(pc); - } - - private static void UseAbility(PlayerControl pc) - { - if (pc == null) return; - - if (pc.IsModClient() || !UseLimit.ContainsKey(pc.PlayerId)) return; - - if (UseLimit[pc.PlayerId] >= 1) - { - UseLimit[pc.PlayerId] -= 1; - Dictionary list = GetAllPlayerLocationsCount(); - var sb = new StringBuilder(); - foreach (KeyValuePair location in list) sb.Append($"\n{location.Key}: {location.Value}"); - - pc.Notify(sb.ToString(), VanillaClientSeesInfoFor.GetFloat()); - } - else - pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc == null) return; - - if (GameStates.IsMeeting) return; - - if (Main.PlayerStates[pc.PlayerId].TaskState.IsTaskFinished) - { - LastUpdate.TryAdd(pc.PlayerId, TimeStamp); - - if (LastUpdate[pc.PlayerId] + 5 < TimeStamp) - { - if (pc.IsModClient()) - UseLimitSeconds[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat() * ModdedClientAbilityUseSecondsMultiplier.GetInt(); - else - UseLimit[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat(); - - LastUpdate[pc.PlayerId] = TimeStamp; - } - } - } - - public static void MapHandle(PlayerControl pc, MapBehaviour map, MapOptions opts) - { - map.countOverlayAllowsMovement = ModdedClientCanMoveWhileViewingMap.GetBool(); - - if (UseLimitSeconds[pc.PlayerId] >= 1) - { - UseLimitSeconds[pc.PlayerId] -= 1; // Remove 1s for opening the map, so they can't utilize spamming - SendRPC(pc.PlayerId, UseLimitSeconds[pc.PlayerId]); - opts.Mode = MapOptions.Modes.CountOverlay; - LateTask.New(() => { MapCountdown(pc, map, opts, (int)UseLimitSeconds[pc.PlayerId]); }, 1f, "NiceHacker.StartCountdown"); - } - else - { - opts.Mode = pc.IsMadmate() ? MapOptions.Modes.Sabotage : MapOptions.Modes.Normal; - pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); - } - } - - private static void MapCountdown(PlayerControl pc, MapBehaviour map, MapOptions opts, int seconds) - { - if (!map.IsOpen) return; - - if (seconds <= 0) - { - map.Close(); - pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); - opts.Mode = pc.IsMadmate() ? MapOptions.Modes.Sabotage : MapOptions.Modes.Normal; - return; - } - - UseLimitSeconds[pc.PlayerId] -= 1; - SendRPC(pc.PlayerId, UseLimitSeconds[pc.PlayerId]); - LateTask.New(() => { MapCountdown(pc, map, opts, seconds - 1); }, 1f, "NiceHackerAbilityCountdown"); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || seer == null) return string.Empty; - - return !seer.Is(CustomRoles.NiceHacker) ? string.Empty : $"{GetString("NiceHackerAbilitySecondsLeft")}: {(int)UseLimitSeconds[seer.PlayerId]}s"; - } - - public override string GetProgressText(byte playerId, bool comms) - { - if (playerId.IsPlayerModClient() || !UseLimit.ContainsKey(playerId)) return string.Empty; - + UseLimit[pc.PlayerId] -= 1; + Dictionary list = GetAllPlayerLocationsCount(); var sb = new StringBuilder(); + foreach (KeyValuePair location in list) sb.Append($"\n{location.Key}: {location.Value}"); - sb.Append(GetTaskCount(playerId, comms)); - sb.Append(ColorString(UseLimit[playerId] < 1 ? Color.red : Color.white, $" - {Math.Round(UseLimit[playerId], 1)}")); - - return sb.ToString(); + pc.Notify(sb.ToString(), VanillaClientSeesInfoFor.GetFloat()); } + else + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override bool CanUseVent(PlayerControl pc, int ventId) + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc == null) return; + + if (GameStates.IsMeeting) return; + + if (Main.PlayerStates[pc.PlayerId].TaskState.IsTaskFinished) { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + LastUpdate.TryAdd(pc.PlayerId, TimeStamp); + + if (LastUpdate[pc.PlayerId] + 5 < TimeStamp) + { + if (pc.IsModClient()) + UseLimitSeconds[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat() * ModdedClientAbilityUseSecondsMultiplier.GetInt(); + else + UseLimit[pc.PlayerId] += AbilityChargesWhenFinishedTasks.GetFloat(); + + LastUpdate[pc.PlayerId] = TimeStamp; + } } } + + public static void MapHandle(PlayerControl pc, MapBehaviour map, MapOptions opts) + { + map.countOverlayAllowsMovement = ModdedClientCanMoveWhileViewingMap.GetBool(); + + if (UseLimitSeconds[pc.PlayerId] >= 1) + { + UseLimitSeconds[pc.PlayerId] -= 1; // Remove 1s for opening the map, so they can't utilize spamming + SendRPC(pc.PlayerId, UseLimitSeconds[pc.PlayerId]); + opts.Mode = MapOptions.Modes.CountOverlay; + LateTask.New(() => { MapCountdown(pc, map, opts, (int)UseLimitSeconds[pc.PlayerId]); }, 1f, "NiceHacker.StartCountdown"); + } + else + { + opts.Mode = pc.IsMadmate() ? MapOptions.Modes.Sabotage : MapOptions.Modes.Normal; + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + } + } + + private static void MapCountdown(PlayerControl pc, MapBehaviour map, MapOptions opts, int seconds) + { + if (!map.IsOpen) return; + + if (seconds <= 0) + { + map.Close(); + pc.Notify(GetString("OutOfAbilityUsesDoMoreTasks")); + opts.Mode = pc.IsMadmate() ? MapOptions.Modes.Sabotage : MapOptions.Modes.Normal; + return; + } + + UseLimitSeconds[pc.PlayerId] -= 1; + SendRPC(pc.PlayerId, UseLimitSeconds[pc.PlayerId]); + LateTask.New(() => { MapCountdown(pc, map, opts, seconds - 1); }, 1f, "NiceHackerAbilityCountdown"); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || seer == null) return string.Empty; + + return !seer.Is(CustomRoles.NiceHacker) ? string.Empty : $"{GetString("NiceHackerAbilitySecondsLeft")}: {(int)UseLimitSeconds[seer.PlayerId]}s"; + } + + public override string GetProgressText(byte playerId, bool comms) + { + if (playerId.IsPlayerModClient() || !UseLimit.ContainsKey(playerId)) return string.Empty; + + var sb = new StringBuilder(); + + sb.Append(GetTaskCount(playerId, comms)); + sb.Append(ColorString(UseLimit[playerId] < 1 ? Color.red : Color.white, $" - {Math.Round(UseLimit[playerId], 1)}")); + + return sb.ToString(); + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/NiceSwapper.cs b/Roles/Crewmate/NiceSwapper.cs index cc92369c3..7e2513465 100644 --- a/Roles/Crewmate/NiceSwapper.cs +++ b/Roles/Crewmate/NiceSwapper.cs @@ -10,321 +10,320 @@ using UnityEngine; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class NiceSwapper : RoleBase { - public class NiceSwapper : RoleBase + private const int Id = 642680; + + public static OptionItem SwapMax; + public static OptionItem HideMsg; + public static OptionItem CanSwapSelf; + public static OptionItem CanStartMeeting; + public static OptionItem NiceSwapperAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private static (byte, byte) SwapTargets = (byte.MaxValue, byte.MaxValue); + private static byte NiceSwapperId = byte.MaxValue; + + public override bool IsEnable => NiceSwapperId != byte.MaxValue; + public static bool On => NiceSwapperId != byte.MaxValue; + + public override void SetupCustomOption() { - private const int Id = 642680; + Options.SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.NiceSwapper); - public static OptionItem SwapMax; - public static OptionItem HideMsg; - public static OptionItem CanSwapSelf; - public static OptionItem CanStartMeeting; - public static OptionItem NiceSwapperAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + SwapMax = new IntegerOptionItem(Id + 3, "NiceSwapperMax", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]) + .SetValueFormat(OptionFormat.Times); - private static (byte, byte) SwapTargets = (byte.MaxValue, byte.MaxValue); - private static byte NiceSwapperId = byte.MaxValue; + CanSwapSelf = new BooleanOptionItem(Id + 2, "CanSwapSelfVotes", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]); + CanStartMeeting = new BooleanOptionItem(Id + 4, "JesterCanUseButton", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]); - public override bool IsEnable => NiceSwapperId != byte.MaxValue; - public static bool On => NiceSwapperId != byte.MaxValue; + NiceSwapperAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 6, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() - { - Options.SetupSingleRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.NiceSwapper); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 7, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]) + .SetValueFormat(OptionFormat.Times); - SwapMax = new IntegerOptionItem(Id + 3, "NiceSwapperMax", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]) - .SetValueFormat(OptionFormat.Times); + HideMsg = new BooleanOptionItem(Id + 5, "SwapperHideMsg", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]); + } - CanSwapSelf = new BooleanOptionItem(Id + 2, "CanSwapSelfVotes", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]); - CanStartMeeting = new BooleanOptionItem(Id + 4, "JesterCanUseButton", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]); + public override void Init() + { + SwapTargets = (byte.MaxValue, byte.MaxValue); + NiceSwapperId = byte.MaxValue; + } - NiceSwapperAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 6, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]) - .SetValueFormat(OptionFormat.Times); + public override void Add(byte playerId) + { + NiceSwapperId = playerId; + playerId.SetAbilityUseLimit(SwapMax.GetInt()); + } - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 7, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]) - .SetValueFormat(OptionFormat.Times); + public static bool SwapMsg(PlayerControl pc, string msg, bool isUI = false) + { + string originMsg = msg; - HideMsg = new BooleanOptionItem(Id + 5, "SwapperHideMsg", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.NiceSwapper]); - } + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || pc == null || pc.GetCustomRole() != CustomRoles.NiceSwapper) return false; - public override void Init() - { - SwapTargets = (byte.MaxValue, byte.MaxValue); - NiceSwapperId = byte.MaxValue; - } + Logger.Info($"{pc.GetNameWithRole()} : {msg} (UI: {isUI})", "Swapper"); - public override void Add(byte playerId) - { - NiceSwapperId = playerId; - playerId.SetAbilityUseLimit(SwapMax.GetInt()); - } - - public static bool SwapMsg(PlayerControl pc, string msg, bool isUI = false) - { - string originMsg = msg; - - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInGame || pc == null || pc.GetCustomRole() != CustomRoles.NiceSwapper) return false; - - Logger.Info($"{pc.GetNameWithRole()} : {msg} (UI: {isUI})", "Swapper"); - - int operate; - msg = msg.ToLower().TrimStart().TrimEnd(); - - if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) - operate = 1; - else if (CheckCommand(ref msg, "sw|换票|换|swap|st", false)) - operate = 2; - else - return false; - - switch (operate) - { - case 1: - Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); - return true; - case 2: - { - if (!pc.IsAlive()) - { - if (!isUI) Utils.SendMessage(GetString("SwapDead"), pc.PlayerId); - - pc.ShowPopUp(GetString("SwapDead")); - return true; - } - - if (pc.GetAbilityUseLimit() < 1) - { - if (!isUI) Utils.SendMessage(GetString("NiceSwapperTrialMax"), pc.PlayerId); - - pc.ShowPopUp(GetString("NiceSwapperTrialMax")); - return true; - } - - if (HideMsg.GetBool() && !isUI) - ChatManager.SendPreviousMessagesToAll(); - else if (pc.AmOwner && !isUI) Utils.SendMessage(originMsg, 255, pc.GetRealName()); - - if (!byte.TryParse(msg.Replace(" ", string.Empty), out byte targetId)) - { - Utils.SendMessage(GetString("SwapHelp"), pc.PlayerId); - return true; - } - - PlayerControl target = Utils.GetPlayerById(targetId); - if (target == null) break; - - bool targetIsntSelected = SwapTargets.Item1 != target.PlayerId && SwapTargets.Item2 != target.PlayerId; // Whether the picked target isn't already being swapped - - bool Vote1Empty = SwapTargets.Item1 == byte.MaxValue && targetIsntSelected; // Whether the first swapping slot is suitable to swap this target - bool Vote2Available = SwapTargets.Item1 != byte.MaxValue && SwapTargets.Item2 == byte.MaxValue && targetIsntSelected; // Whether the second swapping slot is suitable to swap this target - bool selfCheck = CanSwapSelf.GetBool() || target.PlayerId != pc.PlayerId; - - if (Vote1Empty && selfCheck) // Take first slot - { - SwapTargets.Item1 = target.PlayerId; - - if (!isUI) Utils.SendMessage(GetString("Swap1"), pc.PlayerId); - - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} chose to swap {target.GetNameWithRole()} (first target)", "Swapper"); - } - - else if (Vote2Available && selfCheck) // Take second slot - { - SwapTargets.Item2 = target.PlayerId; - - if (!isUI) Utils.SendMessage(GetString("Swap2"), pc.PlayerId); - - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} chose to swap {target.GetNameWithRole()} (second target)", "Swapper"); - } - - else if (target.PlayerId == SwapTargets.Item1) // If this player is already chosen to be swapped in the first slot, cancel it - { - SwapTargets.Item1 = byte.MaxValue; - - if (!isUI) Utils.SendMessage(GetString("CancelSwap1"), pc.PlayerId); - - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} canceled swapping on {target.GetNameWithRole()} (first target)", "Swapper"); - } - - else if (target.PlayerId == SwapTargets.Item2) // If this player is already chosen to be swapped in the second slot, cancel it - { - SwapTargets.Item2 = byte.MaxValue; - - if (!isUI) Utils.SendMessage(GetString("CancelSwap2"), pc.PlayerId); - - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} canceled swapping on {target.GetNameWithRole()} (second target)", "Swapper"); - } - - else if (!selfCheck) // When the Swapper tries to swap themselves, but they aren't allowed to - { - if (!isUI) - Utils.SendMessage(GetString("CantSwapSelf"), pc.PlayerId); - else - pc.ShowPopUp(GetString("CantSwapSelf")); - } - - LateTask.New(() => Utils.NotifyRoles(true, NoCache: true), 0.2f, "NiceSwapperNotifyRoles"); - - break; - } - } - - return true; - } - - public static void OnExileFinish() - { - if (SwapTargets != (byte.MaxValue, byte.MaxValue)) - { - Utils.GetPlayerById(NiceSwapperId).RpcRemoveAbilityUse(); - SwapTargets = (byte.MaxValue, byte.MaxValue); - } - } - - public static void OnCheckForEndVoting() - { - if (NiceSwapperId == byte.MaxValue || SwapTargets.Item1 == byte.MaxValue || SwapTargets.Item2 == byte.MaxValue) return; - - CheckForEndVotingPatch.RunRoleCode = false; - - try - { - Il2CppReferenceArray playerStates = MeetingHud.Instance.playerStates; - List votedFor2 = playerStates.Where(x => x.VotedFor == SwapTargets.Item2).ToList(); - - playerStates.DoIf(x => x.VotedFor == SwapTargets.Item1, x => - { - x.UnsetVote(); - - if (x.TargetPlayerId.IsHost()) - MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, SwapTargets.Item2); - else - MeetingHud.Instance.CastVote(x.TargetPlayerId, SwapTargets.Item2); - - x.VotedFor = SwapTargets.Item2; - }); - - playerStates.DoIf(votedFor2.Contains, x => - { - x.UnsetVote(); - - if (x.TargetPlayerId.IsHost()) - MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, SwapTargets.Item1); - else - MeetingHud.Instance.CastVote(x.TargetPlayerId, SwapTargets.Item1); - - x.VotedFor = SwapTargets.Item1; - }); - - PlayerControl Target1 = Utils.GetPlayerById(SwapTargets.Item1); - PlayerControl Target2 = Utils.GetPlayerById(SwapTargets.Item2); - if (Target1 == null || Target2 == null) return; - - Utils.SendMessage(string.Format(GetString("SwapVote"), Target1.GetRealName(), Target2.GetRealName()), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceSwapper), GetString("SwapTitle"))); - } - finally - { - CheckForEndVotingPatch.RunRoleCode = true; - } - } - - private static bool CheckCommand(ref string msg, string command, bool exact = true) - { - string[] comList = command.Split('|'); - - foreach (string str in comList) - { - if (exact) - { - if (msg == "/" + str) return true; - } - else - { - if (msg.StartsWith("/" + str)) - { - msg = msg.Replace("/" + str, string.Empty); - return true; - } - } - } + int operate; + msg = msg.ToLower().TrimStart().TrimEnd(); + if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) + operate = 1; + else if (CheckCommand(ref msg, "sw|换票|换|swap|st", false)) + operate = 2; + else return false; - } - private static void SendRPC(byte playerId) + switch (operate) { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetNiceSwapperVotes, SendOption.Reliable); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader, PlayerControl pc) - { - byte PlayerId = reader.ReadByte(); - SwapMsg(pc, $"/sw {PlayerId}", true); - } - - private static void SwapperOnClick(byte playerId, MeetingHud __instance) - { - Logger.Msg($"Click: ID {playerId}", "NiceSwapper UI"); - - PlayerControl pc = Utils.GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; - - if (AmongUsClient.Instance.AmHost) - SwapMsg(PlayerControl.LocalPlayer, $"/sw {playerId}", true); - else - SendRPC(playerId); - - if (PlayerControl.LocalPlayer.GetCustomRole() == CustomRoles.NiceSwapper && PlayerControl.LocalPlayer.IsAlive()) + case 1: + Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); + return true; + case 2: { - __instance.playerStates.ToList().ForEach(x => + if (!pc.IsAlive()) { - Transform swapButton = x.transform.FindChild("ShootButton"); - if (swapButton != null) Object.Destroy(swapButton.gameObject); - }); + if (!isUI) Utils.SendMessage(GetString("SwapDead"), pc.PlayerId); - CreateSwapperButton(__instance); + pc.ShowPopUp(GetString("SwapDead")); + return true; + } + + if (pc.GetAbilityUseLimit() < 1) + { + if (!isUI) Utils.SendMessage(GetString("NiceSwapperTrialMax"), pc.PlayerId); + + pc.ShowPopUp(GetString("NiceSwapperTrialMax")); + return true; + } + + if (HideMsg.GetBool() && !isUI) + ChatManager.SendPreviousMessagesToAll(); + else if (pc.AmOwner && !isUI) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + + if (!byte.TryParse(msg.Replace(" ", string.Empty), out byte targetId)) + { + Utils.SendMessage(GetString("SwapHelp"), pc.PlayerId); + return true; + } + + PlayerControl target = Utils.GetPlayerById(targetId); + if (target == null) break; + + bool targetIsntSelected = SwapTargets.Item1 != target.PlayerId && SwapTargets.Item2 != target.PlayerId; // Whether the picked target isn't already being swapped + + bool Vote1Empty = SwapTargets.Item1 == byte.MaxValue && targetIsntSelected; // Whether the first swapping slot is suitable to swap this target + bool Vote2Available = SwapTargets.Item1 != byte.MaxValue && SwapTargets.Item2 == byte.MaxValue && targetIsntSelected; // Whether the second swapping slot is suitable to swap this target + bool selfCheck = CanSwapSelf.GetBool() || target.PlayerId != pc.PlayerId; + + if (Vote1Empty && selfCheck) // Take first slot + { + SwapTargets.Item1 = target.PlayerId; + + if (!isUI) Utils.SendMessage(GetString("Swap1"), pc.PlayerId); + + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} chose to swap {target.GetNameWithRole()} (first target)", "Swapper"); + } + + else if (Vote2Available && selfCheck) // Take second slot + { + SwapTargets.Item2 = target.PlayerId; + + if (!isUI) Utils.SendMessage(GetString("Swap2"), pc.PlayerId); + + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} chose to swap {target.GetNameWithRole()} (second target)", "Swapper"); + } + + else if (target.PlayerId == SwapTargets.Item1) // If this player is already chosen to be swapped in the first slot, cancel it + { + SwapTargets.Item1 = byte.MaxValue; + + if (!isUI) Utils.SendMessage(GetString("CancelSwap1"), pc.PlayerId); + + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} canceled swapping on {target.GetNameWithRole()} (first target)", "Swapper"); + } + + else if (target.PlayerId == SwapTargets.Item2) // If this player is already chosen to be swapped in the second slot, cancel it + { + SwapTargets.Item2 = byte.MaxValue; + + if (!isUI) Utils.SendMessage(GetString("CancelSwap2"), pc.PlayerId); + + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} canceled swapping on {target.GetNameWithRole()} (second target)", "Swapper"); + } + + else if (!selfCheck) // When the Swapper tries to swap themselves, but they aren't allowed to + { + if (!isUI) + Utils.SendMessage(GetString("CantSwapSelf"), pc.PlayerId); + else + pc.ShowPopUp(GetString("CantSwapSelf")); + } + + LateTask.New(() => Utils.NotifyRoles(true, NoCache: true), 0.2f, "NiceSwapperNotifyRoles"); + + break; } } - private static void CreateSwapperButton(MeetingHud __instance) + return true; + } + + public static void OnExileFinish() + { + if (SwapTargets != (byte.MaxValue, byte.MaxValue)) { - foreach (PlayerVoteArea pva in __instance.playerStates) + Utils.GetPlayerById(NiceSwapperId).RpcRemoveAbilityUse(); + SwapTargets = (byte.MaxValue, byte.MaxValue); + } + } + + public static void OnCheckForEndVoting() + { + if (NiceSwapperId == byte.MaxValue || SwapTargets.Item1 == byte.MaxValue || SwapTargets.Item2 == byte.MaxValue) return; + + CheckForEndVotingPatch.RunRoleCode = false; + + try + { + Il2CppReferenceArray playerStates = MeetingHud.Instance.playerStates; + List votedFor2 = playerStates.Where(x => x.VotedFor == SwapTargets.Item2).ToList(); + + playerStates.DoIf(x => x.VotedFor == SwapTargets.Item1, x => { - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; + x.UnsetVote(); - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); - var renderer = targetBox.GetComponent(); - - if (pc.PlayerId == pva.TargetPlayerId && (SwapTargets.Item1 == pc.PlayerId || SwapTargets.Item2 == pc.PlayerId)) - renderer.sprite = CustomButton.Get("SwapYes"); + if (x.TargetPlayerId.IsHost()) + MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, SwapTargets.Item2); else - renderer.sprite = CustomButton.Get("SwapNo"); + MeetingHud.Instance.CastVote(x.TargetPlayerId, SwapTargets.Item2); - var button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => SwapperOnClick(pva.TargetPlayerId, __instance))); + x.VotedFor = SwapTargets.Item2; + }); + + playerStates.DoIf(votedFor2.Contains, x => + { + x.UnsetVote(); + + if (x.TargetPlayerId.IsHost()) + MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, SwapTargets.Item1); + else + MeetingHud.Instance.CastVote(x.TargetPlayerId, SwapTargets.Item1); + + x.VotedFor = SwapTargets.Item1; + }); + + PlayerControl Target1 = Utils.GetPlayerById(SwapTargets.Item1); + PlayerControl Target2 = Utils.GetPlayerById(SwapTargets.Item2); + if (Target1 == null || Target2 == null) return; + + Utils.SendMessage(string.Format(GetString("SwapVote"), Target1.GetRealName(), Target2.GetRealName()), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceSwapper), GetString("SwapTitle"))); + } + finally + { + CheckForEndVotingPatch.RunRoleCode = true; + } + } + + private static bool CheckCommand(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); + + foreach (string str in comList) + { + if (exact) + { + if (msg == "/" + str) return true; + } + else + { + if (msg.StartsWith("/" + str)) + { + msg = msg.Replace("/" + str, string.Empty); + return true; + } } } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - private class StartMeetingPatch + return false; + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetNiceSwapperVotes, SendOption.Reliable); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + byte PlayerId = reader.ReadByte(); + SwapMsg(pc, $"/sw {PlayerId}", true); + } + + private static void SwapperOnClick(byte playerId, MeetingHud __instance) + { + Logger.Msg($"Click: ID {playerId}", "NiceSwapper UI"); + + PlayerControl pc = Utils.GetPlayerById(playerId); + if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + + if (AmongUsClient.Instance.AmHost) + SwapMsg(PlayerControl.LocalPlayer, $"/sw {playerId}", true); + else + SendRPC(playerId); + + if (PlayerControl.LocalPlayer.GetCustomRole() == CustomRoles.NiceSwapper && PlayerControl.LocalPlayer.IsAlive()) { - // ReSharper disable once UnusedMember.Local - public static void Postfix(MeetingHud __instance) + __instance.playerStates.ToList().ForEach(x => { - if (PlayerControl.LocalPlayer.GetCustomRole() == CustomRoles.NiceSwapper && PlayerControl.LocalPlayer.IsAlive()) CreateSwapperButton(__instance); - } + Transform swapButton = x.transform.FindChild("ShootButton"); + if (swapButton != null) Object.Destroy(swapButton.gameObject); + }); + + CreateSwapperButton(__instance); + } + } + + private static void CreateSwapperButton(MeetingHud __instance) + { + foreach (PlayerVoteArea pva in __instance.playerStates) + { + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "ShootButton"; + targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); + var renderer = targetBox.GetComponent(); + + if (pc.PlayerId == pva.TargetPlayerId && (SwapTargets.Item1 == pc.PlayerId || SwapTargets.Item2 == pc.PlayerId)) + renderer.sprite = CustomButton.Get("SwapYes"); + else + renderer.sprite = CustomButton.Get("SwapNo"); + + var button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => SwapperOnClick(pva.TargetPlayerId, __instance))); + } + } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + private class StartMeetingPatch + { + // ReSharper disable once UnusedMember.Local + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.GetCustomRole() == CustomRoles.NiceSwapper && PlayerControl.LocalPlayer.IsAlive()) CreateSwapperButton(__instance); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Nightmare.cs b/Roles/Crewmate/Nightmare.cs index efc5ac3ff..1ce181e87 100644 --- a/Roles/Crewmate/Nightmare.cs +++ b/Roles/Crewmate/Nightmare.cs @@ -1,28 +1,27 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Nightmare : RoleBase { - public class Nightmare : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override void SetupCustomOption() + { + Options.SetupRoleOptions(642630, TabGroup.CrewmateRoles, CustomRoles.Nightmare); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(642630, TabGroup.CrewmateRoles, CustomRoles.Nightmare); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !Utils.IsActive(SystemTypes.Electrical); - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !Utils.IsActive(SystemTypes.Electrical); } } \ No newline at end of file diff --git a/Roles/Crewmate/Observer.cs b/Roles/Crewmate/Observer.cs index f0ec69f70..a624fb640 100644 --- a/Roles/Crewmate/Observer.cs +++ b/Roles/Crewmate/Observer.cs @@ -1,16 +1,15 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Observer : RoleBase { - internal class Observer : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(7500, TabGroup.CrewmateRoles, CustomRoles.Observer); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + Options.SetupRoleOptions(7500, TabGroup.CrewmateRoles, CustomRoles.Observer); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Oracle.cs b/Roles/Crewmate/Oracle.cs index 338c3fb02..72a667b9a 100644 --- a/Roles/Crewmate/Oracle.cs +++ b/Roles/Crewmate/Oracle.cs @@ -2,131 +2,130 @@ using System.Collections.Generic; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Oracle : RoleBase { - public class Oracle : RoleBase + private const int Id = 7600; + private static List PlayerIdList = []; + + public static OptionItem CheckLimitOpt; + public static OptionItem HideVote; + public static OptionItem FailChance; + public static OptionItem OracleAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem CancelVote; + + public static readonly List DidVote = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 7600; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Oracle); - public static OptionItem CheckLimitOpt; - public static OptionItem HideVote; - public static OptionItem FailChance; - public static OptionItem OracleAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem CancelVote; + CheckLimitOpt = new IntegerOptionItem(Id + 10, "OracleSkillLimit", new(0, 10, 1), 0, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) + .SetValueFormat(OptionFormat.Times); - public static readonly List DidVote = []; + HideVote = new BooleanOptionItem(Id + 12, "OracleHideVote", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]); - public override bool IsEnable => PlayerIdList.Count > 0; + FailChance = new IntegerOptionItem(Id + 13, "FailChance", new(0, 100, 5), 20, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) + .SetValueFormat(OptionFormat.Percent); - public override void SetupCustomOption() + OracleAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 14, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 15, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) + .SetValueFormat(OptionFormat.Times); + + CancelVote = CreateVoteCancellingUseSetting(Id + 11, CustomRoles.Oracle, TabGroup.CrewmateRoles); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(CheckLimitOpt.GetInt()); + } + + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null) return false; + + if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; + + DidVote.Add(player.PlayerId); + + if (player.GetAbilityUseLimit() < 1) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Oracle); - - CheckLimitOpt = new IntegerOptionItem(Id + 10, "OracleSkillLimit", new(0, 10, 1), 0, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) - .SetValueFormat(OptionFormat.Times); - - HideVote = new BooleanOptionItem(Id + 12, "OracleHideVote", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]); - - FailChance = new IntegerOptionItem(Id + 13, "FailChance", new(0, 100, 5), 20, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) - .SetValueFormat(OptionFormat.Percent); - - OracleAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 14, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 15, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Oracle]) - .SetValueFormat(OptionFormat.Times); - - CancelVote = CreateVoteCancellingUseSetting(Id + 11, CustomRoles.Oracle, TabGroup.CrewmateRoles); + Utils.SendMessage(GetString("OracleCheckReachLimit"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); + return false; } - public override void Init() + player.RpcRemoveAbilityUse(); + + if (player.PlayerId == target.PlayerId) { - PlayerIdList = []; + Utils.SendMessage(GetString("OracleCheckSelfMsg") + "\n\n" + string.Format(GetString("OracleCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); + return false; } - public override void Add(byte playerId) + string text; + + if (target.GetCustomRole().IsImpostor()) + text = "Imp"; + else if (target.GetCustomRole().IsNeutral()) + text = "Neut"; + else + text = "Crew"; + + if (FailChance.GetInt() > 0) { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(CheckLimitOpt.GetInt()); - } + int random_number_1 = IRandom.Instance.Next(1, 101); - public override bool OnVote(PlayerControl player, PlayerControl target) - { - if (player == null || target == null) return false; - - if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - - DidVote.Add(player.PlayerId); - - if (player.GetAbilityUseLimit() < 1) + if (random_number_1 <= FailChance.GetInt()) { - Utils.SendMessage(GetString("OracleCheckReachLimit"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - return false; - } + int random_number_2 = IRandom.Instance.Next(1, 3); - player.RpcRemoveAbilityUse(); - - if (player.PlayerId == target.PlayerId) - { - Utils.SendMessage(GetString("OracleCheckSelfMsg") + "\n\n" + string.Format(GetString("OracleCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - return false; - } - - string text; - - if (target.GetCustomRole().IsImpostor()) - text = "Imp"; - else if (target.GetCustomRole().IsNeutral()) - text = "Neut"; - else - text = "Crew"; - - if (FailChance.GetInt() > 0) - { - int random_number_1 = IRandom.Instance.Next(1, 101); - - if (random_number_1 <= FailChance.GetInt()) + text = text switch { - int random_number_2 = IRandom.Instance.Next(1, 3); - - text = text switch + "Crew" => random_number_2 switch { - "Crew" => random_number_2 switch - { - 1 => "Neut", - 2 => "Imp", - _ => text - }, - "Neut" => random_number_2 switch - { - 1 => "Crew", - 2 => "Imp", - _ => text - }, - "Imp" => random_number_2 switch - { - 1 => "Neut", - 2 => "Crew", - _ => text - }, + 1 => "Neut", + 2 => "Imp", _ => text - }; - } + }, + "Neut" => random_number_2 switch + { + 1 => "Crew", + 2 => "Imp", + _ => text + }, + "Imp" => random_number_2 switch + { + 1 => "Neut", + 2 => "Crew", + _ => text + }, + _ => text + }; } - - string msg = string.Format(GetString("OracleCheck." + text), target.GetRealName()); - - Utils.SendMessage(GetString("OracleCheck") + "\n" + msg + "\n\n" + string.Format(GetString("OracleCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); - - Main.DontCancelVoteList.Add(player.PlayerId); - return true; } + + string msg = string.Format(GetString("OracleCheck." + text), target.GetRealName()); + + Utils.SendMessage(GetString("OracleCheck") + "\n" + msg + "\n\n" + string.Format(GetString("OracleCheckLimit"), player.GetAbilityUseLimit()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Oracle), GetString("OracleCheckMsgTitle"))); + + Main.DontCancelVoteList.Add(player.PlayerId); + return true; } } \ No newline at end of file diff --git a/Roles/Crewmate/Oxyman.cs b/Roles/Crewmate/Oxyman.cs index b699e4299..646dbd035 100644 --- a/Roles/Crewmate/Oxyman.cs +++ b/Roles/Crewmate/Oxyman.cs @@ -5,200 +5,199 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Oxyman : RoleBase { - public class Oxyman : RoleBase + public static bool On; + + private static readonly Dictionary LevelSettings = []; + private static OptionItem IncrementByVenting; + private static OptionItem DecreasementEachSecond; + private static OptionItem IncreasedSpeed; + private static OptionItem DecreasedSpeed; + private long LastUpdate; + private int OxygenLevel; + private byte OxymanId; + private float StartingSpeed; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 647700; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Oxyman); + (List trueList, List falseList) = Enum.GetValues().Without(Level.None).Split(x => x <= Level.Slow); - private static readonly Dictionary LevelSettings = []; - private static OptionItem IncrementByVenting; - private static OptionItem DecreasementEachSecond; - private static OptionItem IncreasedSpeed; - private static OptionItem DecreasedSpeed; - private long LastUpdate; - private int OxygenLevel; - private byte OxymanId; - private float StartingSpeed; + trueList.ForEach(x => LevelSettings[x] = new IntegerOptionItem(++id, $"Oxyman.{x}.BelowPercentage", new(0, 100, 1), x == Level.Blind ? 10 : 30, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent)); - public override bool IsEnable => On; + falseList.ForEach(x => LevelSettings[x] = new IntegerOptionItem(++id, $"Oxyman.{x}.AbovePercentage", new(0, 100, 1), x == Level.Fast ? 60 : 80, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent)); - public override void SetupCustomOption() + IncrementByVenting = new IntegerOptionItem(++id, "Oxyman.IncrementByVenting", new(0, 100, 1), 7, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent); + + DecreasementEachSecond = new IntegerOptionItem(++id, "Oxyman.DecreasementEachSecond", new(0, 10, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Percent); + + IncreasedSpeed = new FloatOptionItem(++id, "IncreasedSpeed", new(0f, 3f, 0.05f), 1.75f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Multiplier); + + DecreasedSpeed = new FloatOptionItem(++id, "DecreasedSpeed", new(0f, 3f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + OxymanId = playerId; + OxygenLevel = 59; + LastUpdate = Utils.TimeStamp; + StartingSpeed = Main.AllPlayerSpeed[playerId]; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 1f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + PlayerControl pc = physics.myPlayer; + Level previousLevel = GetCurrentLevel(); + + OxygenLevel += IncrementByVenting.GetValue(); + if (OxygenLevel > 100) OxygenLevel = 100; + + Level nowLevel = GetCurrentLevel(); + if (nowLevel != previousLevel) ApplyLevelEffect(pc, nowLevel); + + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, OxygenLevel); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + + LastUpdate = now; + + if (OxygenLevel <= 0) { - var id = 647700; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Oxyman); - (List trueList, List falseList) = Enum.GetValues().Without(Level.None).Split(x => x <= Level.Slow); - - trueList.ForEach(x => LevelSettings[x] = new IntegerOptionItem(++id, $"Oxyman.{x}.BelowPercentage", new(0, 100, 1), x == Level.Blind ? 10 : 30, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) - .SetValueFormat(OptionFormat.Percent)); - - falseList.ForEach(x => LevelSettings[x] = new IntegerOptionItem(++id, $"Oxyman.{x}.AbovePercentage", new(0, 100, 1), x == Level.Fast ? 60 : 80, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) - .SetValueFormat(OptionFormat.Percent)); - - IncrementByVenting = new IntegerOptionItem(++id, "Oxyman.IncrementByVenting", new(0, 100, 1), 7, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) - .SetValueFormat(OptionFormat.Percent); - - DecreasementEachSecond = new IntegerOptionItem(++id, "Oxyman.DecreasementEachSecond", new(0, 10, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) - .SetValueFormat(OptionFormat.Percent); - - IncreasedSpeed = new FloatOptionItem(++id, "IncreasedSpeed", new(0f, 3f, 0.05f), 1.75f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) - .SetValueFormat(OptionFormat.Multiplier); - - DecreasedSpeed = new FloatOptionItem(++id, "DecreasedSpeed", new(0f, 3f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Oxyman]) - .SetValueFormat(OptionFormat.Multiplier); + pc.Suicide(); + return; } - public override void Init() + Level previousLevel = GetCurrentLevel(); + + OxygenLevel -= DecreasementEachSecond.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, OxygenLevel); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + Level nowLevel = GetCurrentLevel(); + + if (nowLevel != previousLevel) ApplyLevelEffect(pc, nowLevel); + } + + private void ApplyLevelEffect(PlayerControl pc, Level nowLevel) + { + switch (nowLevel) { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - OxymanId = playerId; - OxygenLevel = 59; - LastUpdate = Utils.TimeStamp; - StartingSpeed = Main.AllPlayerSpeed[playerId]; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = 1f; - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - PlayerControl pc = physics.myPlayer; - Level previousLevel = GetCurrentLevel(); - - OxygenLevel += IncrementByVenting.GetValue(); - if (OxygenLevel > 100) OxygenLevel = 100; - - Level nowLevel = GetCurrentLevel(); - if (nowLevel != previousLevel) ApplyLevelEffect(pc, nowLevel); - - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, OxygenLevel); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - - long now = Utils.TimeStamp; - if (now == LastUpdate) return; - - LastUpdate = now; - - if (OxygenLevel <= 0) - { - pc.Suicide(); - return; - } - - Level previousLevel = GetCurrentLevel(); - - OxygenLevel -= DecreasementEachSecond.GetInt(); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, OxygenLevel); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - Level nowLevel = GetCurrentLevel(); - - if (nowLevel != previousLevel) ApplyLevelEffect(pc, nowLevel); - } - - private void ApplyLevelEffect(PlayerControl pc, Level nowLevel) - { - switch (nowLevel) - { - case Level.Blind: - Main.PlayerStates[pc.PlayerId].IsBlackOut = true; - Main.AllPlayerSpeed[pc.PlayerId] = DecreasedSpeed.GetFloat(); - pc.MarkDirtySettings(); - break; - case Level.Slow: - Main.PlayerStates[pc.PlayerId].IsBlackOut = false; - Main.AllPlayerSpeed[pc.PlayerId] = DecreasedSpeed.GetFloat(); - pc.MarkDirtySettings(); - break; - case Level.None: - Main.PlayerStates[pc.PlayerId].IsBlackOut = false; - Main.AllPlayerSpeed[pc.PlayerId] = StartingSpeed; - pc.MarkDirtySettings(); - break; - case Level.Invulnerable: - case Level.Fast: - Main.PlayerStates[pc.PlayerId].IsBlackOut = false; - Main.AllPlayerSpeed[pc.PlayerId] = IncreasedSpeed.GetFloat(); - pc.MarkDirtySettings(); - break; - } - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return GetCurrentLevel() != Level.Invulnerable; - } - - private Level GetCurrentLevel() - { - // ReSharper disable ConvertIfStatementToReturnStatement - if (OxygenLevel <= LevelSettings[Level.Blind].GetInt()) return Level.Blind; - - if (OxygenLevel <= LevelSettings[Level.Slow].GetInt()) return Level.Slow; - - if (OxygenLevel >= LevelSettings[Level.Invulnerable].GetInt()) return Level.Invulnerable; - - if (OxygenLevel >= LevelSettings[Level.Fast].GetInt()) return Level.Fast; - - return Level.None; - // ReSharper restore ConvertIfStatementToReturnStatement - } - - private Color GetLevelColor() - { - return GetCurrentLevel() switch - { - Level.Blind => Palette.Orange, - Level.Slow => Color.yellow, - Level.Fast => Color.cyan, - Level.Invulnerable => Color.green, - _ => Color.white - }; - } - - public void ReceiveRPC(MessageReader reader) - { - OxygenLevel = reader.ReadPackedInt32(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != OxymanId || (seer.IsModClient() && !hud)) return string.Empty; - - return $"<#ff0000>O2: {Utils.ColorString(GetLevelColor(), OxygenLevel.ToString())}%"; - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } - - private enum Level - { - Blind, - Slow, - None, - Fast, - Invulnerable + case Level.Blind: + Main.PlayerStates[pc.PlayerId].IsBlackOut = true; + Main.AllPlayerSpeed[pc.PlayerId] = DecreasedSpeed.GetFloat(); + pc.MarkDirtySettings(); + break; + case Level.Slow: + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = DecreasedSpeed.GetFloat(); + pc.MarkDirtySettings(); + break; + case Level.None: + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = StartingSpeed; + pc.MarkDirtySettings(); + break; + case Level.Invulnerable: + case Level.Fast: + Main.PlayerStates[pc.PlayerId].IsBlackOut = false; + Main.AllPlayerSpeed[pc.PlayerId] = IncreasedSpeed.GetFloat(); + pc.MarkDirtySettings(); + break; } } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return GetCurrentLevel() != Level.Invulnerable; + } + + private Level GetCurrentLevel() + { + // ReSharper disable ConvertIfStatementToReturnStatement + if (OxygenLevel <= LevelSettings[Level.Blind].GetInt()) return Level.Blind; + + if (OxygenLevel <= LevelSettings[Level.Slow].GetInt()) return Level.Slow; + + if (OxygenLevel >= LevelSettings[Level.Invulnerable].GetInt()) return Level.Invulnerable; + + if (OxygenLevel >= LevelSettings[Level.Fast].GetInt()) return Level.Fast; + + return Level.None; + // ReSharper restore ConvertIfStatementToReturnStatement + } + + private Color GetLevelColor() + { + return GetCurrentLevel() switch + { + Level.Blind => Palette.Orange, + Level.Slow => Color.yellow, + Level.Fast => Color.cyan, + Level.Invulnerable => Color.green, + _ => Color.white + }; + } + + public void ReceiveRPC(MessageReader reader) + { + OxygenLevel = reader.ReadPackedInt32(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != OxymanId || (seer.IsModClient() && !hud)) return string.Empty; + + return $"<#ff0000>O2: {Utils.ColorString(GetLevelColor(), OxygenLevel.ToString())}%"; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } + + private enum Level + { + Blind, + Slow, + None, + Fast, + Invulnerable + } } \ No newline at end of file diff --git a/Roles/Crewmate/Paranoia.cs b/Roles/Crewmate/Paranoia.cs index 6978bfba7..bf4342aec 100644 --- a/Roles/Crewmate/Paranoia.cs +++ b/Roles/Crewmate/Paranoia.cs @@ -2,84 +2,83 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Paranoia : RoleBase { - internal class Paranoia : RoleBase + public static Dictionary ParaUsedButtonCount = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary ParaUsedButtonCount = []; + SetupRoleOptions(7800, TabGroup.CrewmateRoles, CustomRoles.Paranoia); - public static bool On; - public override bool IsEnable => On; + ParanoiaNumOfUseButton = new IntegerOptionItem(7810, "ParanoiaNumOfUseButton", new(0, 90, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + ParanoiaVentCooldown = new FloatOptionItem(7811, "ParanoiaVentCooldown", new(0, 180, 1), 10, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + ParaUsedButtonCount[playerId] = 0; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = + !ParaUsedButtonCount.TryGetValue(playerId, out int count2) || count2 < ParanoiaNumOfUseButton.GetInt() + ? ParanoiaVentCooldown.GetFloat() + : 300f; + + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("ParanoiaVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("ParanoiaVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + Panic(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + pc.MyPhysics?.RpcBootFromVent(vent.Id); + Panic(pc); + } + + private static void Panic(PlayerControl pc) + { + if (ParaUsedButtonCount.TryGetValue(pc.PlayerId, out int count2) && count2 < ParanoiaNumOfUseButton.GetInt()) { - SetupRoleOptions(7800, TabGroup.CrewmateRoles, CustomRoles.Paranoia); + ParaUsedButtonCount[pc.PlayerId] += 1; + if (AmongUsClient.Instance.AmHost) LateTask.New(() => { Utils.SendMessage(Translator.GetString("SkillUsedLeft") + (ParanoiaNumOfUseButton.GetInt() - ParaUsedButtonCount[pc.PlayerId]), pc.PlayerId); }, 4.0f, "Paranoia Skill Remain Message"); - ParanoiaNumOfUseButton = new IntegerOptionItem(7810, "ParanoiaNumOfUseButton", new(0, 90, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]) - .SetValueFormat(OptionFormat.Times); - - ParanoiaVentCooldown = new FloatOptionItem(7811, "ParanoiaVentCooldown", new(0, 180, 1), 10, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Paranoia]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - ParaUsedButtonCount[playerId] = 0; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = - !ParaUsedButtonCount.TryGetValue(playerId, out int count2) || count2 < ParanoiaNumOfUseButton.GetInt() - ? ParanoiaVentCooldown.GetFloat() - : 300f; - - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("ParanoiaVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("ParanoiaVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - Panic(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - pc.MyPhysics?.RpcBootFromVent(vent.Id); - Panic(pc); - } - - private static void Panic(PlayerControl pc) - { - if (ParaUsedButtonCount.TryGetValue(pc.PlayerId, out int count2) && count2 < ParanoiaNumOfUseButton.GetInt()) - { - ParaUsedButtonCount[pc.PlayerId] += 1; - if (AmongUsClient.Instance.AmHost) LateTask.New(() => { Utils.SendMessage(Translator.GetString("SkillUsedLeft") + (ParanoiaNumOfUseButton.GetInt() - ParaUsedButtonCount[pc.PlayerId]), pc.PlayerId); }, 4.0f, "Paranoia Skill Remain Message"); - - pc.NoCheckStartMeeting(pc.Data); - } - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + pc.NoCheckStartMeeting(pc.Data); } } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/ParityCop.cs b/Roles/Crewmate/ParityCop.cs index 09d2ace93..bc29acb34 100644 --- a/Roles/Crewmate/ParityCop.cs +++ b/Roles/Crewmate/ParityCop.cs @@ -9,434 +9,433 @@ using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class ParityCop : RoleBase { - public class ParityCop : RoleBase + private const int Id = 6900; + private static List PlayerIdList = []; + private static Dictionary RoundCheckLimit = []; + private static Dictionary FirstPick = []; + + private static readonly string[] PcEgoistCountMode = + [ + "EgoistCountMode.Original", + "EgoistCountMode.Neutral" + ]; + + private static OptionItem TryHideMsg; + private static OptionItem ParityCheckLimitMax; + private static OptionItem ParityCheckLimitPerMeeting; + private static OptionItem ParityCheckTargetKnow; + private static OptionItem ParityCheckOtherTargetKnow; + private static OptionItem ParityCheckEgoistCountType; + public static OptionItem ParityCheckBaitCountType; + private static OptionItem ParityCheckRevealTargetTeam; + public static OptionItem ParityAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 6900; - private static List PlayerIdList = []; - private static Dictionary RoundCheckLimit = []; - private static Dictionary FirstPick = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.ParityCop); - private static readonly string[] PcEgoistCountMode = - [ - "EgoistCountMode.Original", - "EgoistCountMode.Neutral" - ]; + TryHideMsg = new BooleanOptionItem(Id + 10, "ParityCopTryHideMsg", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) + .SetColor(Color.green); - private static OptionItem TryHideMsg; - private static OptionItem ParityCheckLimitMax; - private static OptionItem ParityCheckLimitPerMeeting; - private static OptionItem ParityCheckTargetKnow; - private static OptionItem ParityCheckOtherTargetKnow; - private static OptionItem ParityCheckEgoistCountType; - public static OptionItem ParityCheckBaitCountType; - private static OptionItem ParityCheckRevealTargetTeam; - public static OptionItem ParityAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + ParityCheckLimitMax = new IntegerOptionItem(Id + 11, "MaxParityCheckLimit", new(0, 20, 1), 2, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + ParityCheckLimitPerMeeting = new IntegerOptionItem(Id + 12, "ParityCheckLimitPerMeeting", new(1, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + ParityCheckEgoistCountType = new StringOptionItem(Id + 13, "ParityCheckEgoistickCountMode", PcEgoistCountMode, 0, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]); + + ParityCheckBaitCountType = new BooleanOptionItem(Id + 14, "ParityCheckBaitCountMode", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]); + + ParityCheckTargetKnow = new BooleanOptionItem(Id + 15, "ParityCheckTargetKnow", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]); + + ParityCheckOtherTargetKnow = new BooleanOptionItem(Id + 16, "ParityCheckOtherTargetKnow", true, TabGroup.CrewmateRoles) + .SetParent(ParityCheckTargetKnow); + + ParityCheckRevealTargetTeam = new BooleanOptionItem(Id + 17, "ParityCheckRevealTarget", false, TabGroup.CrewmateRoles) + .SetParent(ParityCheckOtherTargetKnow); + + ParityAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 18, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 19, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) + .SetValueFormat(OptionFormat.Times); + + OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.ParityCop); + } + + public static int ParityCheckEgoistInt() + { + return ParityCheckEgoistCountType.GetString() == "EgoistCountMode.Original" ? 0 : 1; + } + + public override void Init() + { + PlayerIdList = []; + RoundCheckLimit = []; + FirstPick = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(ParityCheckLimitMax.GetInt()); + RoundCheckLimit.Add(playerId, ParityCheckLimitPerMeeting.GetInt()); + } + + public override void OnReportDeadBody() + { + RoundCheckLimit.Clear(); + foreach (byte pc in PlayerIdList) RoundCheckLimit.Add(pc, ParityCheckLimitPerMeeting.GetInt()); + } + + public static bool ParityCheckMsg(PlayerControl pc, string msg, bool isUI = false) + { + string originMsg = msg; + + if (!AmongUsClient.Instance.AmHost) return false; + + if (!GameStates.IsInGame || pc == null) return false; + + if (!pc.Is(CustomRoles.ParityCop)) return false; + + int operate; // 1:ID 2:Check + msg = msg.ToLower().TrimStart().TrimEnd(); + + if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) + operate = 1; + else if (CheckCommand(ref msg, "compare|cp|cmp|比较", false)) + operate = 2; + else + return false; + + if (!pc.IsAlive()) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.ParityCop); - - TryHideMsg = new BooleanOptionItem(Id + 10, "ParityCopTryHideMsg", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) - .SetColor(Color.green); - - ParityCheckLimitMax = new IntegerOptionItem(Id + 11, "MaxParityCheckLimit", new(0, 20, 1), 2, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) - .SetValueFormat(OptionFormat.Times); - - ParityCheckLimitPerMeeting = new IntegerOptionItem(Id + 12, "ParityCheckLimitPerMeeting", new(1, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) - .SetValueFormat(OptionFormat.Times); - - ParityCheckEgoistCountType = new StringOptionItem(Id + 13, "ParityCheckEgoistickCountMode", PcEgoistCountMode, 0, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]); - - ParityCheckBaitCountType = new BooleanOptionItem(Id + 14, "ParityCheckBaitCountMode", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]); - - ParityCheckTargetKnow = new BooleanOptionItem(Id + 15, "ParityCheckTargetKnow", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]); - - ParityCheckOtherTargetKnow = new BooleanOptionItem(Id + 16, "ParityCheckOtherTargetKnow", true, TabGroup.CrewmateRoles) - .SetParent(ParityCheckTargetKnow); - - ParityCheckRevealTargetTeam = new BooleanOptionItem(Id + 17, "ParityCheckRevealTarget", false, TabGroup.CrewmateRoles) - .SetParent(ParityCheckOtherTargetKnow); - - ParityAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 18, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 19, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ParityCop]) - .SetValueFormat(OptionFormat.Times); - - OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.ParityCop); + Utils.SendMessage(GetString("ParityCopDead"), pc.PlayerId); + return true; } - public static int ParityCheckEgoistInt() + switch (operate) { - return ParityCheckEgoistCountType.GetString() == "EgoistCountMode.Original" ? 0 : 1; - } - - public override void Init() - { - PlayerIdList = []; - RoundCheckLimit = []; - FirstPick = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(ParityCheckLimitMax.GetInt()); - RoundCheckLimit.Add(playerId, ParityCheckLimitPerMeeting.GetInt()); - } - - public override void OnReportDeadBody() - { - RoundCheckLimit.Clear(); - foreach (byte pc in PlayerIdList) RoundCheckLimit.Add(pc, ParityCheckLimitPerMeeting.GetInt()); - } - - public static bool ParityCheckMsg(PlayerControl pc, string msg, bool isUI = false) - { - string originMsg = msg; - - if (!AmongUsClient.Instance.AmHost) return false; - - if (!GameStates.IsInGame || pc == null) return false; - - if (!pc.Is(CustomRoles.ParityCop)) return false; - - int operate; // 1:ID 2:Check - msg = msg.ToLower().TrimStart().TrimEnd(); - - if (CheckCommand(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) - operate = 1; - else if (CheckCommand(ref msg, "compare|cp|cmp|比较", false)) - operate = 2; - else - return false; - - if (!pc.IsAlive()) - { - Utils.SendMessage(GetString("ParityCopDead"), pc.PlayerId); + case 1: + Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); return true; - } - - switch (operate) + case 2: { - case 1: - Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); - return true; - case 2: + if (TryHideMsg.GetBool()) /*TryHideMsgForCompare();*/ + ChatManager.SendPreviousMessagesToAll(); + else if (pc.AmOwner) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + + if (!MsgToPlayerAndRole(msg, out byte targetId1, out byte targetId2, out string error)) { - if (TryHideMsg.GetBool()) /*TryHideMsgForCompare();*/ - ChatManager.SendPreviousMessagesToAll(); - else if (pc.AmOwner) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + Utils.SendMessage(error, pc.PlayerId); + return true; + } - if (!MsgToPlayerAndRole(msg, out byte targetId1, out byte targetId2, out string error)) + PlayerControl target1 = Utils.GetPlayerById(targetId1); + PlayerControl target2 = Utils.GetPlayerById(targetId2); + + if (target1 != null && target2 != null) + { + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} checked {target1.GetNameWithRole().RemoveHtmlTags()} and {target2.GetNameWithRole().RemoveHtmlTags()}", "ParityCop"); + + bool outOfUses = pc.GetAbilityUseLimit() < 1; + + if (outOfUses || RoundCheckLimit[pc.PlayerId] < 1) { - Utils.SendMessage(error, pc.PlayerId); - return true; - } - - PlayerControl target1 = Utils.GetPlayerById(targetId1); - PlayerControl target2 = Utils.GetPlayerById(targetId2); - - if (target1 != null && target2 != null) - { - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} checked {target1.GetNameWithRole().RemoveHtmlTags()} and {target2.GetNameWithRole().RemoveHtmlTags()}", "ParityCop"); - - bool outOfUses = pc.GetAbilityUseLimit() < 1; - - if (outOfUses || RoundCheckLimit[pc.PlayerId] < 1) - { - if (outOfUses) - { - LateTask.New(() => - { - if (!isUI) - Utils.SendMessage(GetString("ParityCheckMax"), pc.PlayerId); - else - pc.ShowPopUp(GetString("ParityCheckMax")); - - Logger.Msg("Check attempted at max checks per game", "Parity Cop"); - }, 0.2f, "ParityCop 0"); - } - else - { - LateTask.New(() => - { - if (!isUI) - Utils.SendMessage(GetString("ParityCheckRound"), pc.PlayerId); - else - pc.ShowPopUp(GetString("ParityCheckRound")); - - Logger.Msg("Check attempted at max checks per meeting", "Parity Cop"); - }, 0.2f, "ParityCop 1"); - } - - return true; - } - - if (pc.PlayerId == target1.PlayerId || pc.PlayerId == target2.PlayerId) + if (outOfUses) { LateTask.New(() => { if (!isUI) - Utils.SendMessage(GetString("ParityCheckSelf"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + Utils.SendMessage(GetString("ParityCheckMax"), pc.PlayerId); else - pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckSelf")) + "\n" + GetString("ParityCheckTitle")); + pc.ShowPopUp(GetString("ParityCheckMax")); - Logger.Msg("Check attempted on self", "Parity Cop"); - }, 0.2f, "ParityCop 2"); - - return true; - } - - if (target1.GetCustomRole().IsRevealingRole(target1) || target1.GetCustomSubRoles().Any(role => role.IsRevealingRole(target1)) || target2.GetCustomRole().IsRevealingRole(target2) || target2.GetCustomSubRoles().Any(role => role.IsRevealingRole(target2))) - { - LateTask.New(() => - { - if (!isUI) - Utils.SendMessage(GetString("ParityCheckReveal"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); - else - pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckReveal")) + "\n" + GetString("ParityCheckTitle")); - - Logger.Msg("Check attempted on revealed role", "Parity Cop"); - }, 0.2f, "ParityCop 3"); - - return true; - } - - if (AreInSameTeam(target1, target2)) - { - LateTask.New(() => - { - if (!isUI) - Utils.SendMessage(string.Format(GetString("ParityCheckTrue"), target1.GetRealName(), target2.GetRealName()), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); - else - pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTrue")) + "\n" + GetString("ParityCheckTitle")); - - Logger.Msg("Check attempt, result TRUE", "Parity Cop"); - }, 0.2f, "ParityCop 4"); + Logger.Msg("Check attempted at max checks per game", "Parity Cop"); + }, 0.2f, "ParityCop 0"); } else { LateTask.New(() => { if (!isUI) - Utils.SendMessage(string.Format(GetString("ParityCheckFalse"), target1.GetRealName(), target2.GetRealName()), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + Utils.SendMessage(GetString("ParityCheckRound"), pc.PlayerId); else - pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckFalse")) + "\n" + GetString("ParityCheckTitle")); + pc.ShowPopUp(GetString("ParityCheckRound")); - Logger.Msg("Check attempt, result FALSE", "Parity Cop"); - }, 0.2f, "ParityCop 5"); + Logger.Msg("Check attempted at max checks per meeting", "Parity Cop"); + }, 0.2f, "ParityCop 1"); } - if (ParityCheckTargetKnow.GetBool()) + return true; + } + + if (pc.PlayerId == target1.PlayerId || pc.PlayerId == target2.PlayerId) + { + LateTask.New(() => { - var textToSend = $"{target1.GetRealName()}"; - if (ParityCheckOtherTargetKnow.GetBool()) textToSend += $" and {target2.GetRealName()}"; + if (!isUI) + Utils.SendMessage(GetString("ParityCheckSelf"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + else + pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckSelf")) + "\n" + GetString("ParityCheckTitle")); - textToSend += GetString("ParityCheckTargetMsg"); + Logger.Msg("Check attempted on self", "Parity Cop"); + }, 0.2f, "ParityCop 2"); - var textToSend1 = $"{target2.GetRealName()}"; - if (ParityCheckOtherTargetKnow.GetBool()) textToSend1 += $" and {target1.GetRealName()}"; + return true; + } - textToSend1 += GetString("ParityCheckTargetMsg"); + if (target1.GetCustomRole().IsRevealingRole(target1) || target1.GetCustomSubRoles().Any(role => role.IsRevealingRole(target1)) || target2.GetCustomRole().IsRevealingRole(target2) || target2.GetCustomSubRoles().Any(role => role.IsRevealingRole(target2))) + { + LateTask.New(() => + { + if (!isUI) + Utils.SendMessage(GetString("ParityCheckReveal"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + else + pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckReveal")) + "\n" + GetString("ParityCheckTitle")); + + Logger.Msg("Check attempted on revealed role", "Parity Cop"); + }, 0.2f, "ParityCop 3"); + + return true; + } + + if (AreInSameTeam(target1, target2)) + { + LateTask.New(() => + { + if (!isUI) + Utils.SendMessage(string.Format(GetString("ParityCheckTrue"), target1.GetRealName(), target2.GetRealName()), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + else + pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTrue")) + "\n" + GetString("ParityCheckTitle")); + + Logger.Msg("Check attempt, result TRUE", "Parity Cop"); + }, 0.2f, "ParityCop 4"); + } + else + { + LateTask.New(() => + { + if (!isUI) + Utils.SendMessage(string.Format(GetString("ParityCheckFalse"), target1.GetRealName(), target2.GetRealName()), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + else + pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckFalse")) + "\n" + GetString("ParityCheckTitle")); + + Logger.Msg("Check attempt, result FALSE", "Parity Cop"); + }, 0.2f, "ParityCop 5"); + } + + if (ParityCheckTargetKnow.GetBool()) + { + var textToSend = $"{target1.GetRealName()}"; + if (ParityCheckOtherTargetKnow.GetBool()) textToSend += $" and {target2.GetRealName()}"; + + textToSend += GetString("ParityCheckTargetMsg"); + + var textToSend1 = $"{target2.GetRealName()}"; + if (ParityCheckOtherTargetKnow.GetBool()) textToSend1 += $" and {target1.GetRealName()}"; + + textToSend1 += GetString("ParityCheckTargetMsg"); + + LateTask.New(() => + { + Utils.SendMessage(textToSend, target1.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + Utils.SendMessage(textToSend1, target2.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + Logger.Msg("Check attempt, target1 notified", "Parity Cop"); + Logger.Msg("Check attempt, target2 notified", "Parity Cop"); + }, 0.2f, "ParityCop"); + + if (ParityCheckRevealTargetTeam.GetBool() && pc.AllTasksCompleted()) + { + string roleT1 = string.Empty, roleT2 = string.Empty; + + if (target1.Is(Team.Impostor) || target1.GetCustomSubRoles().Any(role => role.IsImpostorTeamV2())) + roleT1 = "Impostor"; + else if (target1.Is(Team.Neutral) || target1.GetCustomSubRoles().Any(role => role.IsNeutralTeamV2())) + roleT1 = "Neutral"; + else if (target1.Is(Team.Crewmate) && (target1.GetCustomSubRoles().Any(role => role.IsCrewmateTeamV2()) || target1.GetCustomSubRoles().Count == 0)) + + { + if (target2.Is(Team.Impostor) || target2.GetCustomSubRoles().Any(role => role.IsImpostorTeamV2())) + roleT2 = "Impostor"; + else if (target2.Is(Team.Neutral) || target2.GetCustomSubRoles().Any(role => role.IsNeutralTeamV2())) + roleT2 = "Neutral"; + else if (target2.Is(Team.Crewmate) && (target2.GetCustomSubRoles().Any(role => role.IsCrewmateTeamV2()) || target2.GetCustomSubRoles().Count == 0)) roleT2 = "Crewmate"; + } LateTask.New(() => { - Utils.SendMessage(textToSend, target1.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); - Utils.SendMessage(textToSend1, target2.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); - Logger.Msg("Check attempt, target1 notified", "Parity Cop"); - Logger.Msg("Check attempt, target2 notified", "Parity Cop"); - }, 0.2f, "ParityCop"); - - if (ParityCheckRevealTargetTeam.GetBool() && pc.AllTasksCompleted()) - { - string roleT1 = string.Empty, roleT2 = string.Empty; - - if (target1.Is(Team.Impostor) || target1.GetCustomSubRoles().Any(role => role.IsImpostorTeamV2())) - roleT1 = "Impostor"; - else if (target1.Is(Team.Neutral) || target1.GetCustomSubRoles().Any(role => role.IsNeutralTeamV2())) - roleT1 = "Neutral"; - else if (target1.Is(Team.Crewmate) && (target1.GetCustomSubRoles().Any(role => role.IsCrewmateTeamV2()) || target1.GetCustomSubRoles().Count == 0)) - - { - if (target2.Is(Team.Impostor) || target2.GetCustomSubRoles().Any(role => role.IsImpostorTeamV2())) - roleT2 = "Impostor"; - else if (target2.Is(Team.Neutral) || target2.GetCustomSubRoles().Any(role => role.IsNeutralTeamV2())) - roleT2 = "Neutral"; - else if (target2.Is(Team.Crewmate) && (target2.GetCustomSubRoles().Any(role => role.IsCrewmateTeamV2()) || target2.GetCustomSubRoles().Count == 0)) roleT2 = "Crewmate"; - } - - LateTask.New(() => - { - Utils.SendMessage(string.Format(GetString("ParityCopTargetReveal"), target2.GetRealName(), roleT2), target1.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); - Utils.SendMessage(string.Format(GetString("ParityCopTargetReveal"), target1.GetRealName(), roleT1), target2.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); - Logger.Msg($"check attempt, target1 notified target2 as {roleT2} and target2 notified target1 as {roleT1}", "Parity Cop"); - }, 0.3f, "ParityCop 6"); - } + Utils.SendMessage(string.Format(GetString("ParityCopTargetReveal"), target2.GetRealName(), roleT2), target1.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + Utils.SendMessage(string.Format(GetString("ParityCopTargetReveal"), target1.GetRealName(), roleT1), target2.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.ParityCop), GetString("ParityCheckTitle"))); + Logger.Msg($"check attempt, target1 notified target2 as {roleT2} and target2 notified target1 as {roleT1}", "Parity Cop"); + }, 0.3f, "ParityCop 6"); } - - pc.RpcRemoveAbilityUse(); - RoundCheckLimit[pc.PlayerId]--; } - break; + pc.RpcRemoveAbilityUse(); + RoundCheckLimit[pc.PlayerId]--; } - } - return true; + break; + } } - public static bool AreInSameTeam(PlayerControl first, PlayerControl second) + return true; + } + + public static bool AreInSameTeam(PlayerControl first, PlayerControl second) + { + CustomRoles firstRole = first.GetCustomRole(); + CustomRoles secondRole = second.GetCustomRole(); + + RoleBase firstRoleClass = Main.PlayerStates[first.PlayerId].Role; + RoleBase secondRoleClass = Main.PlayerStates[second.PlayerId].Role; + + List firstSubRoles = first.GetCustomSubRoles(); + List secondSubRoles = second.GetCustomSubRoles(); + + Team firstTeam = first.GetTeam(); + Team secondTeam = second.GetTeam(); + + switch (firstRoleClass) { - CustomRoles firstRole = first.GetCustomRole(); - CustomRoles secondRole = second.GetCustomRole(); - - RoleBase firstRoleClass = Main.PlayerStates[first.PlayerId].Role; - RoleBase secondRoleClass = Main.PlayerStates[second.PlayerId].Role; - - List firstSubRoles = first.GetCustomSubRoles(); - List secondSubRoles = second.GetCustomSubRoles(); - - Team firstTeam = first.GetTeam(); - Team secondTeam = second.GetTeam(); - - switch (firstRoleClass) - { - case Lawyer when Lawyer.Target[first.PlayerId] == second.PlayerId: return true; - case Totocalcio tc when tc.BetPlayer == second.PlayerId: return true; - case Romantic when Romantic.HasPickedPartner && Romantic.PartnerId == second.PlayerId: return true; - case Necromancer when secondRoleClass is Deathknight: return true; - } - - switch (secondRoleClass) - { - case Lawyer when Lawyer.Target[second.PlayerId] == first.PlayerId: return true; - case Totocalcio tc when tc.BetPlayer == first.PlayerId: return true; - case Romantic when Romantic.HasPickedPartner && Romantic.PartnerId == first.PlayerId: return true; - case Necromancer when firstRoleClass is Deathknight: return true; - } - - if (CustomTeamManager.AreInSameCustomTeam(first.PlayerId, second.PlayerId)) return true; - - if (firstSubRoles.Contains(CustomRoles.Bloodlust) || secondSubRoles.Contains(CustomRoles.Bloodlust)) return false; - - if (firstRole.IsNeutral() && secondRole.IsNeutral()) return false; - - return firstTeam == secondTeam || firstRole == secondRole; + case Lawyer when Lawyer.Target[first.PlayerId] == second.PlayerId: return true; + case Totocalcio tc when tc.BetPlayer == second.PlayerId: return true; + case Romantic when Romantic.HasPickedPartner && Romantic.PartnerId == second.PlayerId: return true; + case Necromancer when secondRoleClass is Deathknight: return true; } - private static bool MsgToPlayerAndRole(string msg, out byte id1, out byte id2, out string error) + switch (secondRoleClass) { - if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); - - msg = msg.TrimStart().TrimEnd(); - Logger.Msg(msg, "ParityCop"); - - string[] nums = msg.Split(" "); - - if (nums.Length != 2 || !int.TryParse(nums[0], out int num1) || !int.TryParse(nums[1], out int num2)) - { - Logger.Msg($"nums.Length {nums.Length}, nums0 {nums[0]}, nums1 {nums[1]}", "ParityCop"); - id1 = byte.MaxValue; - id2 = byte.MaxValue; - error = GetString("ParityCheckHelp"); - return false; - } - - id1 = Convert.ToByte(num1); - id2 = Convert.ToByte(num2); - - PlayerControl target1 = Utils.GetPlayerById(id1); - PlayerControl target2 = Utils.GetPlayerById(id2); - - if (target1 == null || target1.Data.IsDead || target2 == null || target2.Data.IsDead) - { - error = GetString("ParityCheckNull"); - return false; - } - - error = string.Empty; - return true; + case Lawyer when Lawyer.Target[second.PlayerId] == first.PlayerId: return true; + case Totocalcio tc when tc.BetPlayer == first.PlayerId: return true; + case Romantic when Romantic.HasPickedPartner && Romantic.PartnerId == first.PlayerId: return true; + case Necromancer when firstRoleClass is Deathknight: return true; } - public static bool CheckCommand(ref string msg, string command, bool exact = true) + if (CustomTeamManager.AreInSameCustomTeam(first.PlayerId, second.PlayerId)) return true; + + if (firstSubRoles.Contains(CustomRoles.Bloodlust) || secondSubRoles.Contains(CustomRoles.Bloodlust)) return false; + + if (firstRole.IsNeutral() && secondRole.IsNeutral()) return false; + + return firstTeam == secondTeam || firstRole == secondRole; + } + + private static bool MsgToPlayerAndRole(string msg, out byte id1, out byte id2, out string error) + { + if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); + + msg = msg.TrimStart().TrimEnd(); + Logger.Msg(msg, "ParityCop"); + + string[] nums = msg.Split(" "); + + if (nums.Length != 2 || !int.TryParse(nums[0], out int num1) || !int.TryParse(nums[1], out int num2)) { - string[] comList = command.Split('|'); - - foreach (string str in comList) - { - if (exact) - { - if (msg == "/" + str) return true; - } - else - { - if (msg.StartsWith("/" + str)) - { - msg = msg.Replace("/" + str, string.Empty); - return true; - } - } - } - + Logger.Msg($"nums.Length {nums.Length}, nums0 {nums[0]}, nums1 {nums[1]}", "ParityCop"); + id1 = byte.MaxValue; + id2 = byte.MaxValue; + error = GetString("ParityCheckHelp"); return false; } - private static void ParityCopOnClick(byte playerId /*, MeetingHud __instance*/) - { - Logger.Msg($"Click: ID {playerId}", "Inspector UI"); - PlayerControl pc = Utils.GetPlayerById(playerId); - byte lpcId = PlayerControl.LocalPlayer.PlayerId; - if (pc == null || !pc.IsAlive() || !GameStates.IsVoting || !AmongUsClient.Instance.AmHost) return; + id1 = Convert.ToByte(num1); + id2 = Convert.ToByte(num2); - if (FirstPick.TryGetValue(lpcId, out byte firstPick)) + PlayerControl target1 = Utils.GetPlayerById(id1); + PlayerControl target2 = Utils.GetPlayerById(id2); + + if (target1 == null || target1.Data.IsDead || target2 == null || target2.Data.IsDead) + { + error = GetString("ParityCheckNull"); + return false; + } + + error = string.Empty; + return true; + } + + public static bool CheckCommand(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); + + foreach (string str in comList) + { + if (exact) { - ParityCheckMsg(PlayerControl.LocalPlayer, $"/cp {playerId} {firstPick}"); - FirstPick.Remove(lpcId); + if (msg == "/" + str) return true; } else - FirstPick.Add(lpcId, playerId); - } - - public static void CreateParityCopButton(MeetingHud __instance) - { - foreach (PlayerVoteArea pva in __instance.playerStates) { - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; - - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); - var renderer = targetBox.GetComponent(); - renderer.sprite = CustomButton.Get("ParityCopIcon"); - var button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => ParityCopOnClick(pva.TargetPlayerId /*, __instance*/))); + if (msg.StartsWith("/" + str)) + { + msg = msg.Replace("/" + str, string.Empty); + return true; + } } } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - private class StartMeetingPatch + return false; + } + + private static void ParityCopOnClick(byte playerId /*, MeetingHud __instance*/) + { + Logger.Msg($"Click: ID {playerId}", "Inspector UI"); + PlayerControl pc = Utils.GetPlayerById(playerId); + byte lpcId = PlayerControl.LocalPlayer.PlayerId; + if (pc == null || !pc.IsAlive() || !GameStates.IsVoting || !AmongUsClient.Instance.AmHost) return; + + if (FirstPick.TryGetValue(lpcId, out byte firstPick)) { - public static void Postfix(MeetingHud __instance) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.ParityCop) && PlayerControl.LocalPlayer.IsAlive() && AmongUsClient.Instance.AmHost) CreateParityCopButton(__instance); - } + ParityCheckMsg(PlayerControl.LocalPlayer, $"/cp {playerId} {firstPick}"); + FirstPick.Remove(lpcId); + } + else + FirstPick.Add(lpcId, playerId); + } + + public static void CreateParityCopButton(MeetingHud __instance) + { + foreach (PlayerVoteArea pva in __instance.playerStates) + { + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "ShootButton"; + targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); + var renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("ParityCopIcon"); + var button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => ParityCopOnClick(pva.TargetPlayerId /*, __instance*/))); + } + } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + private class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.ParityCop) && PlayerControl.LocalPlayer.IsAlive() && AmongUsClient.Instance.AmHost) CreateParityCopButton(__instance); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Perceiver.cs b/Roles/Crewmate/Perceiver.cs index 118312ca1..48b6257ed 100644 --- a/Roles/Crewmate/Perceiver.cs +++ b/Roles/Crewmate/Perceiver.cs @@ -1,86 +1,85 @@ using System.Linq; using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Perceiver : RoleBase { - internal class Perceiver : RoleBase + private static OptionItem Radius; + public static OptionItem CD; + public static OptionItem Limit; + public static OptionItem PerceiverAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + public static bool On; + private static int Id => 643360; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private static OptionItem Radius; - public static OptionItem CD; - public static OptionItem Limit; - public static OptionItem PerceiverAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Perceiver); - public static bool On; - private static int Id => 643360; + Radius = new FloatOptionItem(Id + 2, "PerceiverRadius", new(0.25f, 10f, 0.25f), 2.5f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) + .SetValueFormat(OptionFormat.Multiplier); - public override bool IsEnable => On; + CD = Options.CreateCDSetting(Id + 3, TabGroup.CrewmateRoles, CustomRoles.Perceiver); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Perceiver); + Limit = new IntegerOptionItem(Id + 4, "AbilityUseLimit", new(0, 20, 1), 0, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) + .SetValueFormat(OptionFormat.Times); - Radius = new FloatOptionItem(Id + 2, "PerceiverRadius", new(0.25f, 10f, 0.25f), 2.5f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) - .SetValueFormat(OptionFormat.Multiplier); + PerceiverAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 5, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) + .SetValueFormat(OptionFormat.Times); - CD = Options.CreateCDSetting(Id + 3, TabGroup.CrewmateRoles, CustomRoles.Perceiver); + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 6, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) + .SetValueFormat(OptionFormat.Times); + } - Limit = new IntegerOptionItem(Id + 4, "AbilityUseLimit", new(0, 20, 1), 0, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) - .SetValueFormat(OptionFormat.Times); + public override void Init() + { + On = false; + } - PerceiverAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 5, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) - .SetValueFormat(OptionFormat.Times); + public override void Add(byte id) + { + On = true; + id.SetAbilityUseLimit(Limit.GetInt()); + } - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 6, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Perceiver]) - .SetValueFormat(OptionFormat.Times); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; - public override void Init() - { - On = false; - } + AURoleOptions.EngineerCooldown = CD.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } - public override void Add(byte id) - { - On = true; - id.SetAbilityUseLimit(Limit.GetInt()); - } + public override void OnPet(PlayerControl pc) + { + UseAbility(pc); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePets.GetBool()) return; + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + UseAbility(pc); + } - AURoleOptions.EngineerCooldown = CD.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } + public static void UseAbility(PlayerControl pc) + { + if (pc == null || pc.GetAbilityUseLimit() < 1f) return; - public override void OnPet(PlayerControl pc) - { - UseAbility(pc); - } + PlayerControl[] killers = Main.AllAlivePlayerControls.Where(x => !x.Is(Team.Crewmate) && x.HasKillButton() && Vector2.Distance(x.Pos(), pc.Pos()) <= Radius.GetFloat()).ToArray(); + pc.Notify(string.Format(Translator.GetString("PerceiverNotify"), killers.Length), 7f); - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - UseAbility(pc); - } + pc.RpcRemoveAbilityUse(); + } - public static void UseAbility(PlayerControl pc) - { - if (pc == null || pc.GetAbilityUseLimit() < 1f) return; - - PlayerControl[] killers = Main.AllAlivePlayerControls.Where(x => !x.Is(Team.Crewmate) && x.HasKillButton() && Vector2.Distance(x.Pos(), pc.Pos()) <= Radius.GetFloat()).ToArray(); - pc.Notify(string.Format(Translator.GetString("PerceiverNotify"), killers.Length), 7f); - - pc.RpcRemoveAbilityUse(); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Philantropist.cs b/Roles/Crewmate/Philantropist.cs index c79809556..f8b72c127 100644 --- a/Roles/Crewmate/Philantropist.cs +++ b/Roles/Crewmate/Philantropist.cs @@ -1,4 +1,3 @@ -namespace EHR.Crewmate -{ - internal class Philantropist { } -} \ No newline at end of file +namespace EHR.Crewmate; + +internal class Philantropist { } \ No newline at end of file diff --git a/Roles/Crewmate/President.cs b/Roles/Crewmate/President.cs index 61ea70326..0f8549c22 100644 --- a/Roles/Crewmate/President.cs +++ b/Roles/Crewmate/President.cs @@ -3,242 +3,241 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal static class DecreeExtension { - internal static class DecreeExtension + public static bool IsEnabled(this President.Decree decree) { - public static bool IsEnabled(this President.Decree decree) - { - return President.DecreeSettings[decree][0].GetBool(); - } + return President.DecreeSettings[decree][0].GetBool(); + } +} + +public class President : RoleBase +{ + public enum Decree + { + Reveal, + Finish, + Declassification, + GovernmentSupport, + Investigation, + GovernmentRecruiting } - public class President : RoleBase + public static bool On; + private static List Instances = []; + internal static readonly Dictionary> DecreeSettings = []; + + private static readonly CustomRoles[] GovernmentRecruitRoles = + [ + CustomRoles.Sheriff, + CustomRoles.Bodyguard, + CustomRoles.ParityCop, + CustomRoles.Jailor, + CustomRoles.SecurityGuard, + CustomRoles.NiceGuesser, + CustomRoles.Inquirer, + CustomRoles.Farseer + ]; + + private bool IsDeclassification; + private bool IsRecruiting; + + private byte PresidentId; + private bool Used; + private HashSet UsedDecrees; + + public override bool IsEnable => On; + + public bool IsRevealed => UsedDecrees.Contains(Decree.Reveal); + + public override void SetupCustomOption() { - public enum Decree + var id = 647850; + const TabGroup tab = TabGroup.CrewmateRoles; + const CustomRoles role = CustomRoles.President; + Options.SetupSingleRoleOptions(id++, tab, role, hideMaxSetting: true); + + foreach (Decree decree in Enum.GetValues()) { - Reveal, - Finish, - Declassification, - GovernmentSupport, - Investigation, - GovernmentRecruiting - } - - public static bool On; - private static List Instances = []; - internal static readonly Dictionary> DecreeSettings = []; - - private static readonly CustomRoles[] GovernmentRecruitRoles = - [ - CustomRoles.Sheriff, - CustomRoles.Bodyguard, - CustomRoles.ParityCop, - CustomRoles.Jailor, - CustomRoles.SecurityGuard, - CustomRoles.NiceGuesser, - CustomRoles.Inquirer, - CustomRoles.Farseer - ]; - - private bool IsDeclassification; - private bool IsRecruiting; - - private byte PresidentId; - private bool Used; - private HashSet UsedDecrees; - - public override bool IsEnable => On; - - public bool IsRevealed => UsedDecrees.Contains(Decree.Reveal); - - public override void SetupCustomOption() - { - var id = 647850; - const TabGroup tab = TabGroup.CrewmateRoles; - const CustomRoles role = CustomRoles.President; - Options.SetupSingleRoleOptions(id++, tab, role, hideMaxSetting: true); - - foreach (Decree decree in Enum.GetValues()) - { - DecreeSettings[decree] = - [ - new BooleanOptionItem(++id, $"President.Decree.{decree}", true, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - ]; - - switch (decree) - { - case Decree.Reveal: - DecreeSettings[decree].AddRange( - [ - new BooleanOptionItem(++id, "President.Reveal.CanBeConvertedAfterRevealing", false, tab) - .SetParent(DecreeSettings[decree][0]), - new BooleanOptionItem(++id, "President.Reveal.ConvertedPresidentCanReveal", false, tab) - .SetParent(DecreeSettings[decree][0]) - ]); - - break; - case Decree.GovernmentRecruiting: - DecreeSettings[decree].Add(new StringOptionItem(++id, "President.GovernmentRecruiting.RecruitedRole", GovernmentRecruitRoles.Select(x => x.ToColoredString()).ToArray(), 0, tab, noTranslation: true) - .SetParent(DecreeSettings[decree][0])); - - break; - } - } - } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - PresidentId = playerId; - UsedDecrees = []; - IsDeclassification = false; - IsRecruiting = false; - Used = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (IsRevealed) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision / 2f); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultCrewmateVision / 2f); - } - } - - public static void OnAnyoneApplyGameOptions(IGameOptions opt) - { - bool value = Main.RealOptionsData.GetBool(BoolOptionNames.AnonymousVotes); - if (Instances.Any(x => x.IsDeclassification)) value = false; - - opt.SetBool(BoolOptionNames.AnonymousVotes, value); - } - - public static void UseDecree(PlayerControl pc, string message) - { - President president = Instances.FirstOrDefault(x => x.PresidentId == pc.PlayerId); - - if (president == null || president.Used) - { - Utils.SendMessage(Translator.GetString("President.DecreeAlreadyChosenThisMeetingMessage"), pc.PlayerId); - return; - } - - if (!Utils.GetPlayerById(president.PresidentId).IsAlive()) return; - - if (!int.TryParse(message, out int num) || num > 5) - { - Utils.SendMessage(GetHelpMessage(), pc.PlayerId); - return; - } - - var decree = (Decree)num; - - if (!decree.IsEnabled() || president.UsedDecrees.Contains(decree)) - { - Utils.SendMessage(Translator.GetString("President.DecreeAlreadyUsedMessage"), pc.PlayerId); - return; - } + DecreeSettings[decree] = + [ + new BooleanOptionItem(++id, $"President.Decree.{decree}", true, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + ]; switch (decree) { case Decree.Reveal: - if (!DecreeSettings[decree][1].GetBool()) pc.RpcSetCustomRole(CustomRoles.Loyal); + DecreeSettings[decree].AddRange( + [ + new BooleanOptionItem(++id, "President.Reveal.CanBeConvertedAfterRevealing", false, tab) + .SetParent(DecreeSettings[decree][0]), + new BooleanOptionItem(++id, "President.Reveal.ConvertedPresidentCanReveal", false, tab) + .SetParent(DecreeSettings[decree][0]) + ]); - if ((!DecreeSettings[decree][2].GetBool() && pc.IsConverted()) || pc.Is(CustomRoles.Bloodlust)) return; - - Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); - Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.RevealMessage"), pc.PlayerId.ColoredPlayerName())); - break; - case Decree.Finish: - MeetingHud.Instance?.RpcClose(); - Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); - break; - case Decree.Declassification: - president.IsDeclassification = true; - Utils.SyncAllSettings(); - Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); - break; - case Decree.GovernmentSupport: - foreach (PlayerControl player in Main.AllPlayerControls) - { - if (!player.IsCrewmate()) continue; - - switch (Main.PlayerStates[player.PlayerId].Role) - { - case SabotageMaster sm: - sm.UsedSkillCount--; - break; - case NiceHacker when NiceHacker.UseLimit.ContainsKey(player.PlayerId): - NiceHacker.UseLimit[player.PlayerId]++; - break; - default: - player.RpcIncreaseAbilityUseLimitBy(1f); - break; - } - } - - Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); - break; - case Decree.Investigation: - Utils.SendMessage("\n", pc.PlayerId, Utils.GetRemainingKillers(president: true)); break; case Decree.GovernmentRecruiting: - if (MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == pc.PlayerId)?.DidVote == true) return; + DecreeSettings[decree].Add(new StringOptionItem(++id, "President.GovernmentRecruiting.RecruitedRole", GovernmentRecruitRoles.Select(x => x.ToColoredString()).ToArray(), 0, tab, noTranslation: true) + .SetParent(DecreeSettings[decree][0])); - president.IsRecruiting = true; break; } - - president.UsedDecrees.Add(decree); - president.Used = true; - } - - public static string GetHelpMessage() - { - return Enum.GetValues().Where(x => x.IsEnabled()).Aggregate("", (acc, x) => $"{acc}{Translator.GetString($"President.Decree.{x}")}: {(int)x}\n") + $"\n{Translator.GetString("President.Help")}"; - } - - public override void AfterMeetingTasks() - { - IsDeclassification = false; - IsRecruiting = false; - Used = false; - } - - public override bool KnowRole(PlayerControl seer, PlayerControl target) - { - if (base.KnowRole(seer, target)) return true; - - return target.PlayerId == PresidentId && IsRevealed; - } - - public override bool OnVote(PlayerControl voter, PlayerControl target) - { - if (voter.PlayerId != PresidentId || !IsRecruiting || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; - - if (!target.Is(CustomRoleTypes.Crewmate) || target.IsConverted()) - { - IsRecruiting = false; - return true; - } - - CustomRoles role = GovernmentRecruitRoles[DecreeSettings[Decree.GovernmentRecruiting][1].GetValue()]; - if (role.IsDesyncRole() && target.GetRoleTypes() != role.GetDYRole()) role = CustomRoles.NiceGuesser; - - target.RpcSetCustomRole(role); - Utils.SendMessage("\n", target.PlayerId, Translator.GetString("President.Recruit.TargetNotifyMessage")); - Main.DontCancelVoteList.Add(voter.PlayerId); - return true; } } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + PresidentId = playerId; + UsedDecrees = []; + IsDeclassification = false; + IsRecruiting = false; + Used = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (IsRevealed) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision / 2f); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultCrewmateVision / 2f); + } + } + + public static void OnAnyoneApplyGameOptions(IGameOptions opt) + { + bool value = Main.RealOptionsData.GetBool(BoolOptionNames.AnonymousVotes); + if (Instances.Any(x => x.IsDeclassification)) value = false; + + opt.SetBool(BoolOptionNames.AnonymousVotes, value); + } + + public static void UseDecree(PlayerControl pc, string message) + { + President president = Instances.FirstOrDefault(x => x.PresidentId == pc.PlayerId); + + if (president == null || president.Used) + { + Utils.SendMessage(Translator.GetString("President.DecreeAlreadyChosenThisMeetingMessage"), pc.PlayerId); + return; + } + + if (!Utils.GetPlayerById(president.PresidentId).IsAlive()) return; + + if (!int.TryParse(message, out int num) || num > 5) + { + Utils.SendMessage(GetHelpMessage(), pc.PlayerId); + return; + } + + var decree = (Decree)num; + + if (!decree.IsEnabled() || president.UsedDecrees.Contains(decree)) + { + Utils.SendMessage(Translator.GetString("President.DecreeAlreadyUsedMessage"), pc.PlayerId); + return; + } + + switch (decree) + { + case Decree.Reveal: + if (!DecreeSettings[decree][1].GetBool()) pc.RpcSetCustomRole(CustomRoles.Loyal); + + if ((!DecreeSettings[decree][2].GetBool() && pc.IsConverted()) || pc.Is(CustomRoles.Bloodlust)) return; + + Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); + Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.RevealMessage"), pc.PlayerId.ColoredPlayerName())); + break; + case Decree.Finish: + MeetingHud.Instance?.RpcClose(); + Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); + break; + case Decree.Declassification: + president.IsDeclassification = true; + Utils.SyncAllSettings(); + Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); + break; + case Decree.GovernmentSupport: + foreach (PlayerControl player in Main.AllPlayerControls) + { + if (!player.IsCrewmate()) continue; + + switch (Main.PlayerStates[player.PlayerId].Role) + { + case SabotageMaster sm: + sm.UsedSkillCount--; + break; + case NiceHacker when NiceHacker.UseLimit.ContainsKey(player.PlayerId): + NiceHacker.UseLimit[player.PlayerId]++; + break; + default: + player.RpcIncreaseAbilityUseLimitBy(1f); + break; + } + } + + Utils.SendMessage(string.Format(Translator.GetString("President.UsedDecreeMessage.Everyone"), Translator.GetString($"President.Decree.{decree}"))); + break; + case Decree.Investigation: + Utils.SendMessage("\n", pc.PlayerId, Utils.GetRemainingKillers(president: true)); + break; + case Decree.GovernmentRecruiting: + if (MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == pc.PlayerId)?.DidVote == true) return; + + president.IsRecruiting = true; + break; + } + + president.UsedDecrees.Add(decree); + president.Used = true; + } + + public static string GetHelpMessage() + { + return Enum.GetValues().Where(x => x.IsEnabled()).Aggregate("", (acc, x) => $"{acc}{Translator.GetString($"President.Decree.{x}")}: {(int)x}\n") + $"\n{Translator.GetString("President.Help")}"; + } + + public override void AfterMeetingTasks() + { + IsDeclassification = false; + IsRecruiting = false; + Used = false; + } + + public override bool KnowRole(PlayerControl seer, PlayerControl target) + { + if (base.KnowRole(seer, target)) return true; + + return target.PlayerId == PresidentId && IsRevealed; + } + + public override bool OnVote(PlayerControl voter, PlayerControl target) + { + if (voter.PlayerId != PresidentId || !IsRecruiting || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; + + if (!target.Is(CustomRoleTypes.Crewmate) || target.IsConverted()) + { + IsRecruiting = false; + return true; + } + + CustomRoles role = GovernmentRecruitRoles[DecreeSettings[Decree.GovernmentRecruiting][1].GetValue()]; + if (role.IsDesyncRole() && target.GetRoleTypes() != role.GetDYRole()) role = CustomRoles.NiceGuesser; + + target.RpcSetCustomRole(role); + Utils.SendMessage("\n", target.PlayerId, Translator.GetString("President.Recruit.TargetNotifyMessage")); + Main.DontCancelVoteList.Add(voter.PlayerId); + return true; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Psychic.cs b/Roles/Crewmate/Psychic.cs index 6d68253db..e7c1e2feb 100644 --- a/Roles/Crewmate/Psychic.cs +++ b/Roles/Crewmate/Psychic.cs @@ -6,146 +6,145 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Psychic : RoleBase { - public class Psychic : RoleBase + private const int Id = 7900; + private static List PlayerIdList = []; + + private static OptionItem CanSeeNum; + private static OptionItem Fresh; + private static OptionItem CkshowEvil; + private static OptionItem NBshowEvil; + private static OptionItem NEshowEvil; + private byte PsychicId; + + private List RedPlayer = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 7900; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Psychic); - private static OptionItem CanSeeNum; - private static OptionItem Fresh; - private static OptionItem CkshowEvil; - private static OptionItem NBshowEvil; - private static OptionItem NEshowEvil; - private byte PsychicId; + CanSeeNum = new IntegerOptionItem(Id + 2, "PsychicCanSeeNum", new(1, 10, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]) + .SetValueFormat(OptionFormat.Pieces); - private List RedPlayer = []; + Fresh = new BooleanOptionItem(Id + 6, "PsychicFresh", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - public override bool IsEnable => PlayerIdList.Count > 0; + CkshowEvil = new BooleanOptionItem(Id + 3, "CrewKillingRed", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - public override void SetupCustomOption() + NBshowEvil = new BooleanOptionItem(Id + 4, "NBareRed", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + + NEshowEvil = new BooleanOptionItem(Id + 5, "NEareRed", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + } + + public override void Init() + { + PlayerIdList = []; + RedPlayer = []; + PsychicId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + RedPlayer = []; + PsychicId = playerId; + } + + private void SendRPC() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncPsychicRedList, SendOption.Reliable); + writer.Write(PsychicId); + writer.Write(RedPlayer.Count); + foreach (byte pc in RedPlayer) writer.Write(pc); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + if (Main.PlayerStates[playerId].Role is not Psychic ph) return; + + int count = reader.ReadInt32(); + ph.RedPlayer = []; + for (var i = 0; i < count; i++) ph.RedPlayer.Add(reader.ReadByte()); + } + + public static bool IsRedForPsy(PlayerControl target, PlayerControl seer) + { + if (target == null || seer == null) return false; + + if (Main.PlayerStates[seer.PlayerId].Role is not Psychic ph) return false; + + if (seer.Is(CustomRoles.Madmate)) return target.GetCustomRole().IsNeutral() || target.GetCustomRole().GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing; + + return ph.RedPlayer != null && ph.RedPlayer.Contains(target.PlayerId); + } + + public override void OnReportDeadBody() + { + if (Fresh.GetBool() || RedPlayer == null || RedPlayer.Count == 0) GetRedName(); + } + + private void GetRedName() + { + if (!IsEnable || !AmongUsClient.Instance.AmHost) return; + + List BadListPc = Main.AllAlivePlayerControls.Where(x => + (x.Is(CustomRoleTypes.Impostor) && !x.Is(CustomRoles.Trickster)) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || Framer.FramedPlayers.Contains(x.PlayerId) || x.IsConverted() || + (x.GetCustomRole().GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing && CkshowEvil.GetBool()) || + (x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil && NEshowEvil.GetBool()) || + (x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign && NBshowEvil.GetBool()) + ).ToList(); + + List BadList = []; + BadListPc.Do(x => BadList.Add(x.PlayerId)); + List AllList = []; + Main.AllAlivePlayerControls.Where(x => !BadList.Contains(x.PlayerId) && !x.Is(CustomRoles.Psychic)).Do(x => AllList.Add(x.PlayerId)); + + var ENum = 1; + + for (var i = 1; i < CanSeeNum.GetInt(); i++) + if (IRandom.Instance.Next(0, 100) < 18) + ENum++; + + int BNum = CanSeeNum.GetInt() - ENum; + ENum = Math.Min(ENum, BadList.Count); + BNum = Math.Min(BNum, AllList.Count); + + if (ENum < 1) goto EndOfSelect; + + RedPlayer = []; + + for (var i = 0; i < ENum && BadList.Count > 0; i++) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Psychic); - - CanSeeNum = new IntegerOptionItem(Id + 2, "PsychicCanSeeNum", new(1, 10, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]) - .SetValueFormat(OptionFormat.Pieces); - - Fresh = new BooleanOptionItem(Id + 6, "PsychicFresh", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - - CkshowEvil = new BooleanOptionItem(Id + 3, "CrewKillingRed", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - - NBshowEvil = new BooleanOptionItem(Id + 4, "NBareRed", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); - - NEshowEvil = new BooleanOptionItem(Id + 5, "NEareRed", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Psychic]); + RedPlayer.Add(BadList.RandomElement()); + BadList.RemoveAll(RedPlayer.Contains); } - public override void Init() + AllList.RemoveAll(RedPlayer.Contains); + + for (var i = 0; i < BNum && AllList.Count > 0; i++) { - PlayerIdList = []; - RedPlayer = []; - PsychicId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - RedPlayer = []; - PsychicId = playerId; - } - - private void SendRPC() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncPsychicRedList, SendOption.Reliable); - writer.Write(PsychicId); - writer.Write(RedPlayer.Count); - foreach (byte pc in RedPlayer) writer.Write(pc); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - if (Main.PlayerStates[playerId].Role is not Psychic ph) return; - - int count = reader.ReadInt32(); - ph.RedPlayer = []; - for (var i = 0; i < count; i++) ph.RedPlayer.Add(reader.ReadByte()); - } - - public static bool IsRedForPsy(PlayerControl target, PlayerControl seer) - { - if (target == null || seer == null) return false; - - if (Main.PlayerStates[seer.PlayerId].Role is not Psychic ph) return false; - - if (seer.Is(CustomRoles.Madmate)) return target.GetCustomRole().IsNeutral() || target.GetCustomRole().GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing; - - return ph.RedPlayer != null && ph.RedPlayer.Contains(target.PlayerId); - } - - public override void OnReportDeadBody() - { - if (Fresh.GetBool() || RedPlayer == null || RedPlayer.Count == 0) GetRedName(); - } - - private void GetRedName() - { - if (!IsEnable || !AmongUsClient.Instance.AmHost) return; - - List BadListPc = Main.AllAlivePlayerControls.Where(x => - (x.Is(CustomRoleTypes.Impostor) && !x.Is(CustomRoles.Trickster)) || x.Is(CustomRoles.Madmate) || x.Is(CustomRoles.Rascal) || Framer.FramedPlayers.Contains(x.PlayerId) || x.IsConverted() || - (x.GetCustomRole().GetCrewmateRoleCategory() == RoleOptionType.Crewmate_Killing && CkshowEvil.GetBool()) || - (x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Evil && NEshowEvil.GetBool()) || - (x.GetCustomRole().GetNeutralRoleCategory() == RoleOptionType.Neutral_Benign && NBshowEvil.GetBool()) - ).ToList(); - - List BadList = []; - BadListPc.Do(x => BadList.Add(x.PlayerId)); - List AllList = []; - Main.AllAlivePlayerControls.Where(x => !BadList.Contains(x.PlayerId) && !x.Is(CustomRoles.Psychic)).Do(x => AllList.Add(x.PlayerId)); - - var ENum = 1; - - for (var i = 1; i < CanSeeNum.GetInt(); i++) - if (IRandom.Instance.Next(0, 100) < 18) - ENum++; - - int BNum = CanSeeNum.GetInt() - ENum; - ENum = Math.Min(ENum, BadList.Count); - BNum = Math.Min(BNum, AllList.Count); - - if (ENum < 1) goto EndOfSelect; - - RedPlayer = []; - - for (var i = 0; i < ENum && BadList.Count > 0; i++) - { - RedPlayer.Add(BadList.RandomElement()); - BadList.RemoveAll(RedPlayer.Contains); - } - + RedPlayer.Add(AllList.RandomElement()); AllList.RemoveAll(RedPlayer.Contains); - - for (var i = 0; i < BNum && AllList.Count > 0; i++) - { - RedPlayer.Add(AllList.RandomElement()); - AllList.RemoveAll(RedPlayer.Contains); - } - - EndOfSelect: - - Logger.Info($"Requires {CanSeeNum.GetInt()} red names, of which {ENum} evil names are required. After calculation, {RedPlayer.Count} red names are displayed.", "Psychic"); - RedPlayer.Do(x => Logger.Info($"Red for Psychic:{x}: {Main.AllPlayerNames[x]}", "Psychic")); - SendRPC(); } + + EndOfSelect: + + Logger.Info($"Requires {CanSeeNum.GetInt()} red names, of which {ENum} evil names are required. After calculation, {RedPlayer.Count} red names are displayed.", "Psychic"); + RedPlayer.Do(x => Logger.Info($"Red for Psychic:{x}: {Main.AllPlayerNames[x]}", "Psychic")); + SendRPC(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Rabbit.cs b/Roles/Crewmate/Rabbit.cs index 4b4e70d5d..3a4af3db6 100644 --- a/Roles/Crewmate/Rabbit.cs +++ b/Roles/Crewmate/Rabbit.cs @@ -3,106 +3,105 @@ using System.Linq; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Rabbit : RoleBase { - internal class Rabbit : RoleBase + private static readonly Dictionary RabbitStates = []; + private static OptionItem OptionTaskTrigger; + private static int TaskTrigger; + + private static int Id => 643330; + public override bool IsEnable => RabbitStates.Count > 0; + + public override void SetupCustomOption() { - private static readonly Dictionary RabbitStates = []; - private static OptionItem OptionTaskTrigger; - private static int TaskTrigger; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Rabbit); - private static int Id => 643330; - public override bool IsEnable => RabbitStates.Count > 0; + OptionTaskTrigger = new IntegerOptionItem(Id + 2, "RabbitMinTasks", new(0, 90, 1), 3, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rabbit]); - public override void SetupCustomOption() + Options.OverrideTasksData.Create(Id + 3, TabGroup.CrewmateRoles, CustomRoles.Rabbit); + } + + public override void Init() + { + RabbitStates.Clear(); + TaskTrigger = OptionTaskTrigger.GetInt(); + } + + public override void Add(byte playerId) + { + RabbitStates[playerId] = new(Utils.GetPlayerById(playerId)); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte id = reader.ReadByte(); + bool hasArrow = reader.ReadBoolean(); + RabbitStates[id].ReceiveRPC(hasArrow); + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (pc == null || !RabbitStates.TryGetValue(pc.PlayerId, out RabbitState state)) return; + + state.OnTaskComplete(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || seer.PlayerId != target.PlayerId || !RabbitStates.TryGetValue(seer.PlayerId, out RabbitState state)) return string.Empty; + + string suffix = state.Suffix; + return hud ? $"{suffix}" : suffix; + } + + private class RabbitState(PlayerControl player) + { + private bool HasArrow; + private PlayerControl Player => player; + private TaskState MyTaskState => Player.GetTaskState(); + + public string Suffix => !GameStates.IsInTask || !HasArrow + ? string.Empty + : Utils.ColorString(Utils.GetRoleColor(CustomRoles.Rabbit), LocateArrow.GetArrows(Player)); + + public void OnTaskComplete() { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Rabbit); + if (!Player.IsAlive() || (MyTaskState.CompletedTasksCount < TaskTrigger && !MyTaskState.IsTaskFinished)) return; - OptionTaskTrigger = new IntegerOptionItem(Id + 2, "RabbitMinTasks", new(0, 90, 1), 3, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rabbit]); + PlayerControl[] impostors = Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoleTypes.Impostor)).ToArray(); + PlayerControl target = impostors.RandomElement(); + if (target == null) return; - Options.OverrideTasksData.Create(Id + 3, TabGroup.CrewmateRoles, CustomRoles.Rabbit); - } + Vector2 pos = target.Pos(); + LocateArrow.Add(Player.PlayerId, pos); + HasArrow = true; + SendRPC(); + Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); + Logger.Info($"{Player.GetNameWithRole()}'s target: {target.GetNameWithRole()}", "Rabbit"); - public override void Init() - { - RabbitStates.Clear(); - TaskTrigger = OptionTaskTrigger.GetInt(); - } - - public override void Add(byte playerId) - { - RabbitStates[playerId] = new(Utils.GetPlayerById(playerId)); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte id = reader.ReadByte(); - bool hasArrow = reader.ReadBoolean(); - RabbitStates[id].ReceiveRPC(hasArrow); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (pc == null || !RabbitStates.TryGetValue(pc.PlayerId, out RabbitState state)) return; - - state.OnTaskComplete(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || seer.PlayerId != target.PlayerId || !RabbitStates.TryGetValue(seer.PlayerId, out RabbitState state)) return string.Empty; - - string suffix = state.Suffix; - return hud ? $"{suffix}" : suffix; - } - - private class RabbitState(PlayerControl player) - { - private bool HasArrow; - private PlayerControl Player => player; - private TaskState MyTaskState => Player.GetTaskState(); - - public string Suffix => !GameStates.IsInTask || !HasArrow - ? string.Empty - : Utils.ColorString(Utils.GetRoleColor(CustomRoles.Rabbit), LocateArrow.GetArrows(Player)); - - public void OnTaskComplete() + LateTask.New(() => { - if (!Player.IsAlive() || (MyTaskState.CompletedTasksCount < TaskTrigger && !MyTaskState.IsTaskFinished)) return; - - PlayerControl[] impostors = Main.AllAlivePlayerControls.Where(pc => pc.Is(CustomRoleTypes.Impostor)).ToArray(); - PlayerControl target = impostors.RandomElement(); - if (target == null) return; - - Vector2 pos = target.Pos(); - LocateArrow.Add(Player.PlayerId, pos); - HasArrow = true; + LocateArrow.Remove(Player.PlayerId, pos); + HasArrow = false; SendRPC(); Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); - Logger.Info($"{Player.GetNameWithRole()}'s target: {target.GetNameWithRole()}", "Rabbit"); + }, 5f, "Rabbit ShowArrow Empty"); + } - LateTask.New(() => - { - LocateArrow.Remove(Player.PlayerId, pos); - HasArrow = false; - SendRPC(); - Utils.NotifyRoles(SpecifySeer: Player, SpecifyTarget: Player); - }, 5f, "Rabbit ShowArrow Empty"); - } + private void SendRPC() + { + MessageWriter writer = Utils.CreateRPC(CustomRPC.SyncRabbit); + writer.Write(Player.PlayerId); + writer.Write(HasArrow); + Utils.EndRPC(writer); + } - private void SendRPC() - { - MessageWriter writer = Utils.CreateRPC(CustomRPC.SyncRabbit); - writer.Write(Player.PlayerId); - writer.Write(HasArrow); - Utils.EndRPC(writer); - } - - public void ReceiveRPC(bool hasArrow) - { - HasArrow = hasArrow; - } + public void ReceiveRPC(bool hasArrow) + { + HasArrow = hasArrow; } } } \ No newline at end of file diff --git a/Roles/Crewmate/Randomizer.cs b/Roles/Crewmate/Randomizer.cs index fccd94efb..814914275 100644 --- a/Roles/Crewmate/Randomizer.cs +++ b/Roles/Crewmate/Randomizer.cs @@ -8,820 +8,819 @@ using Hazel; using UnityEngine; using static EHR.Crewmate.Randomizer; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal static class EffectExtenstions { - internal static class EffectExtenstions + public static bool IsSpeedChangingEffect(this Effect effect) { - public static bool IsSpeedChangingEffect(this Effect effect) - { - return effect is - Effect.SuperSpeedForRandomPlayer or - Effect.SuperSpeedForAll or - Effect.FreezeRandomPlayer or - Effect.InvertControls; - } + return effect is + Effect.SuperSpeedForRandomPlayer or + Effect.SuperSpeedForAll or + Effect.FreezeRandomPlayer or + Effect.InvertControls; + } - public static bool IsVisionChangingEffect(this Effect effect) - { - return effect is - Effect.SuperVisionForRandomPlayer or - Effect.SuperVisionForAll or - Effect.BlindnessForRandomPlayer or - Effect.BlindnessForAll; - } + public static bool IsVisionChangingEffect(this Effect effect) + { + return effect is + Effect.SuperVisionForRandomPlayer or + Effect.SuperVisionForAll or + Effect.BlindnessForRandomPlayer or + Effect.BlindnessForAll; + } - public static void Apply(this Effect effect, PlayerControl randomizer) - { - if (!Exists) return; + public static void Apply(this Effect effect, PlayerControl randomizer) + { + if (!Exists) return; - try + try + { + switch (effect) { - switch (effect) + case Effect.ShieldRandomPlayer: { - case Effect.ShieldRandomPlayer: + PlayerControl pc = PickRandomPlayer(); + AddEffectForPlayer(pc, effect); + } + + break; + case Effect.ShieldAll: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - PlayerControl pc = PickRandomPlayer(); AddEffectForPlayer(pc, effect); + NotifyAboutRNG(pc); } + } - break; - case Effect.ShieldAll: + break; + case Effect.TPEveryoneToVents: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - AddEffectForPlayer(pc, effect); - NotifyAboutRNG(pc); - } + pc.TPToRandomVent(); + NotifyAboutRNG(pc); } + } - break; - case Effect.TPEveryoneToVents: + break; + case Effect.PullEveryone: + Utils.TPAll(randomizer.Pos()); + break; + case Effect.Twist: + { + List changePositionPlayers = []; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - pc.TPToRandomVent(); - NotifyAboutRNG(pc); - } + if (changePositionPlayers.Contains(pc.PlayerId) || Pelican.IsEaten(pc.PlayerId) || pc.onLadder || pc.inVent || pc.inMovingPlat || GameStates.IsMeeting) continue; + + PlayerControl[] filtered = Main.AllAlivePlayerControls.Where(a => !a.inVent && !Pelican.IsEaten(a.PlayerId) && !a.onLadder && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); + if (filtered.Length == 0) break; + + PlayerControl target = filtered.RandomElement(); + + changePositionPlayers.Add(target.PlayerId); + changePositionPlayers.Add(pc.PlayerId); + + pc.RPCPlayCustomSound("Teleport"); + + Vector2 originPs = target.Pos(); + target.TP(pc.Pos()); + pc.TP(originPs); + + NotifyAboutRNG(target); + NotifyAboutRNG(pc); } + } - break; - case Effect.PullEveryone: - Utils.TPAll(randomizer.Pos()); - break; - case Effect.Twist: + break; + case Effect.SuperSpeedForRandomPlayer: + { + PlayerControl pc = PickRandomPlayer(); + RevertSpeedChangesForPlayer(pc, false); + AddEffectForPlayer(pc, effect); + Main.AllPlayerSpeed[pc.PlayerId] = 5f; + pc.MarkDirtySettings(); + NotifyAboutRNG(pc); + } + + break; + case Effect.SuperSpeedForAll: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - List changePositionPlayers = []; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (changePositionPlayers.Contains(pc.PlayerId) || Pelican.IsEaten(pc.PlayerId) || pc.onLadder || pc.inVent || pc.inMovingPlat || GameStates.IsMeeting) continue; - - PlayerControl[] filtered = Main.AllAlivePlayerControls.Where(a => !a.inVent && !Pelican.IsEaten(a.PlayerId) && !a.onLadder && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); - if (filtered.Length == 0) break; - - PlayerControl target = filtered.RandomElement(); - - changePositionPlayers.Add(target.PlayerId); - changePositionPlayers.Add(pc.PlayerId); - - pc.RPCPlayCustomSound("Teleport"); - - Vector2 originPs = target.Pos(); - target.TP(pc.Pos()); - pc.TP(originPs); - - NotifyAboutRNG(target); - NotifyAboutRNG(pc); - } - } - - break; - case Effect.SuperSpeedForRandomPlayer: - { - PlayerControl pc = PickRandomPlayer(); RevertSpeedChangesForPlayer(pc, false); AddEffectForPlayer(pc, effect); Main.AllPlayerSpeed[pc.PlayerId] = 5f; - pc.MarkDirtySettings(); NotifyAboutRNG(pc); } - break; - case Effect.SuperSpeedForAll: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - RevertSpeedChangesForPlayer(pc, false); - AddEffectForPlayer(pc, effect); - Main.AllPlayerSpeed[pc.PlayerId] = 5f; - NotifyAboutRNG(pc); - } + Utils.MarkEveryoneDirtySettings(); + } - Utils.MarkEveryoneDirtySettings(); + break; + case Effect.FreezeRandomPlayer: + { + PlayerControl pc = PickRandomPlayer(); + RevertSpeedChangesForPlayer(pc, false); + AddEffectForPlayer(pc, effect); + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.MarkDirtySettings(); + NotifyAboutRNG(pc); + + if (pc.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); + } + + break; + case Effect.SuperVisionForRandomPlayer: + { + PlayerControl pc = PickRandomPlayer(); + RevertVisionChangesForPlayer(pc, false); + AddEffectForPlayer(pc, effect); + pc.MarkDirtySettings(); + NotifyAboutRNG(pc); + } + + break; + case Effect.SuperVisionForAll: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + RevertVisionChangesForPlayer(pc, false); + AddEffectForPlayer(pc, effect); + NotifyAboutRNG(pc); } - break; - case Effect.FreezeRandomPlayer: + Utils.MarkEveryoneDirtySettings(); + } + + break; + case Effect.BlindnessForRandomPlayer: + { + PlayerControl pc = PickRandomPlayer(); + RevertVisionChangesForPlayer(pc, false); + AddEffectForPlayer(pc, effect); + pc.MarkDirtySettings(); + NotifyAboutRNG(pc); + } + + break; + case Effect.BlindnessForAll: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + RevertVisionChangesForPlayer(pc, false); + AddEffectForPlayer(pc, effect); + NotifyAboutRNG(pc); + } + + Utils.MarkEveryoneDirtySettings(); + } + + break; + case Effect.AllKCDsReset: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.HasKillButton() && pc.CanUseKillButton()) + { + pc.SetKillCooldown(); + NotifyAboutRNG(pc); + } + } + } + + break; + case Effect.AllKCDsTo0: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.HasKillButton() && pc.CanUseKillButton()) + { + pc.SetKillCooldown(0.1f); + NotifyAboutRNG(pc); + } + } + } + + break; + case Effect.Meeting when TimeSinceLastMeeting > Math.Max(Main.NormalOptions.EmergencyCooldown, 30): + { + PlayerControl pc = PickRandomPlayer(); + pc.CmdReportDeadBody(null); + } + + break; + case Effect.Rift: + Rifts.TryAdd(PickRandomPlayer().Pos(), PickRandomPlayer().Pos()); + + try + { + var riftsToRemove = new List(); + + foreach (KeyValuePair rift1 in Rifts) + { + foreach (KeyValuePair rift2 in Rifts) + if (rift1.Key != rift2.Key && Vector2.Distance(rift1.Key, rift2.Key) <= 4f) + riftsToRemove.Add(rift2.Key); + } + + foreach (Vector2 rift in riftsToRemove) Rifts.Remove(rift); + } + catch (Exception ex) + { + Logger.Exception(ex, "Randomizer Rift Manager"); + } + + break; + case Effect.TimeBomb: + Bombs.TryAdd(PickRandomPlayer().Pos(), (Utils.TimeStamp, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration))); + Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 1, Bombs.Last().Key, Bombs.Last().Value.PlaceTimeStamp, Bombs.Last().Value.ExplosionDelay); + break; + case Effect.Tornado: + Tornado.SpawnTornado(PickRandomPlayer()); + break; + case Effect.RevertToBaseRole when TimeSinceLastMeeting > 40f: // To make this less frequent than the others + { + PlayerControl pc = PickRandomPlayer(); + if (pc.PlayerId == randomizer.PlayerId) break; + + pc.RpcSetCustomRole(pc.GetCustomRole().GetErasedRole()); + pc.SyncSettings(); + NotifyAboutRNG(pc); + } + + break; + case Effect.InvertControls: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - PlayerControl pc = PickRandomPlayer(); RevertSpeedChangesForPlayer(pc, false); AddEffectForPlayer(pc, effect); - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; - pc.MarkDirtySettings(); - NotifyAboutRNG(pc); - - if (pc.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - } - - break; - case Effect.SuperVisionForRandomPlayer: - { - PlayerControl pc = PickRandomPlayer(); - RevertVisionChangesForPlayer(pc, false); - AddEffectForPlayer(pc, effect); - pc.MarkDirtySettings(); + Main.AllPlayerSpeed[pc.PlayerId] = -AllPlayerDefaultSpeed[pc.PlayerId]; NotifyAboutRNG(pc); } - break; - case Effect.SuperVisionForAll: + Utils.MarkEveryoneDirtySettings(); + } + + break; + case Effect.AddonAssign: + { + CustomRoles[] addons = Enum.GetValues().Where(x => x.IsAdditionRole() && x != CustomRoles.NotAssigned).ToArray(); + PlayerControl pc = PickRandomPlayer(); + CustomRoles addon = addons.RandomElement(); + if (Main.PlayerStates[pc.PlayerId].SubRoles.Contains(addon)) break; + + Main.PlayerStates[pc.PlayerId].SetSubRole(addon); + pc.MarkDirtySettings(); + NotifyAboutRNG(pc); + } + + break; + case Effect.AddonRemove: + { + PlayerControl pc = PickRandomPlayer(); + List addons = Main.PlayerStates[pc.PlayerId].SubRoles; + if (addons.Count == 0) break; + + CustomRoles addon = addons.RandomElement(); + Main.PlayerStates[pc.PlayerId].RemoveSubRole(addon); + pc.MarkDirtySettings(); + NotifyAboutRNG(pc); + } + + break; + case Effect.HandcuffRandomPlayer: + { + PlayerControl pc = PickRandomPlayer(); + AddEffectForPlayer(pc, effect); + pc.BlockRole(IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration)); + NotifyAboutRNG(pc); + } + + break; + case Effect.HandcuffAll: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - RevertVisionChangesForPlayer(pc, false); - AddEffectForPlayer(pc, effect); - NotifyAboutRNG(pc); - } - - Utils.MarkEveryoneDirtySettings(); - } - - break; - case Effect.BlindnessForRandomPlayer: - { - PlayerControl pc = PickRandomPlayer(); - RevertVisionChangesForPlayer(pc, false); - AddEffectForPlayer(pc, effect); - pc.MarkDirtySettings(); - NotifyAboutRNG(pc); - } - - break; - case Effect.BlindnessForAll: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - RevertVisionChangesForPlayer(pc, false); - AddEffectForPlayer(pc, effect); - NotifyAboutRNG(pc); - } - - Utils.MarkEveryoneDirtySettings(); - } - - break; - case Effect.AllKCDsReset: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.HasKillButton() && pc.CanUseKillButton()) - { - pc.SetKillCooldown(); - NotifyAboutRNG(pc); - } - } - } - - break; - case Effect.AllKCDsTo0: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.HasKillButton() && pc.CanUseKillButton()) - { - pc.SetKillCooldown(0.1f); - NotifyAboutRNG(pc); - } - } - } - - break; - case Effect.Meeting when TimeSinceLastMeeting > Math.Max(Main.NormalOptions.EmergencyCooldown, 30): - { - PlayerControl pc = PickRandomPlayer(); - pc.CmdReportDeadBody(null); - } - - break; - case Effect.Rift: - Rifts.TryAdd(PickRandomPlayer().Pos(), PickRandomPlayer().Pos()); - - try - { - var riftsToRemove = new List(); - - foreach (KeyValuePair rift1 in Rifts) - { - foreach (KeyValuePair rift2 in Rifts) - if (rift1.Key != rift2.Key && Vector2.Distance(rift1.Key, rift2.Key) <= 4f) - riftsToRemove.Add(rift2.Key); - } - - foreach (Vector2 rift in riftsToRemove) Rifts.Remove(rift); - } - catch (Exception ex) - { - Logger.Exception(ex, "Randomizer Rift Manager"); - } - - break; - case Effect.TimeBomb: - Bombs.TryAdd(PickRandomPlayer().Pos(), (Utils.TimeStamp, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration))); - Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 1, Bombs.Last().Key, Bombs.Last().Value.PlaceTimeStamp, Bombs.Last().Value.ExplosionDelay); - break; - case Effect.Tornado: - Tornado.SpawnTornado(PickRandomPlayer()); - break; - case Effect.RevertToBaseRole when TimeSinceLastMeeting > 40f: // To make this less frequent than the others - { - PlayerControl pc = PickRandomPlayer(); - if (pc.PlayerId == randomizer.PlayerId) break; - - pc.RpcSetCustomRole(pc.GetCustomRole().GetErasedRole()); - pc.SyncSettings(); - NotifyAboutRNG(pc); - } - - break; - case Effect.InvertControls: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - RevertSpeedChangesForPlayer(pc, false); - AddEffectForPlayer(pc, effect); - Main.AllPlayerSpeed[pc.PlayerId] = -AllPlayerDefaultSpeed[pc.PlayerId]; - NotifyAboutRNG(pc); - } - - Utils.MarkEveryoneDirtySettings(); - } - - break; - case Effect.AddonAssign: - { - CustomRoles[] addons = Enum.GetValues().Where(x => x.IsAdditionRole() && x != CustomRoles.NotAssigned).ToArray(); - PlayerControl pc = PickRandomPlayer(); - CustomRoles addon = addons.RandomElement(); - if (Main.PlayerStates[pc.PlayerId].SubRoles.Contains(addon)) break; - - Main.PlayerStates[pc.PlayerId].SetSubRole(addon); - pc.MarkDirtySettings(); - NotifyAboutRNG(pc); - } - - break; - case Effect.AddonRemove: - { - PlayerControl pc = PickRandomPlayer(); - List addons = Main.PlayerStates[pc.PlayerId].SubRoles; - if (addons.Count == 0) break; - - CustomRoles addon = addons.RandomElement(); - Main.PlayerStates[pc.PlayerId].RemoveSubRole(addon); - pc.MarkDirtySettings(); - NotifyAboutRNG(pc); - } - - break; - case Effect.HandcuffRandomPlayer: - { - PlayerControl pc = PickRandomPlayer(); AddEffectForPlayer(pc, effect); pc.BlockRole(IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration)); NotifyAboutRNG(pc); } - - break; - case Effect.HandcuffAll: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - AddEffectForPlayer(pc, effect); - pc.BlockRole(IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration)); - NotifyAboutRNG(pc); - } - } - - break; - case Effect.DonutForAll: - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) DonutDelivery.RandomNotifyTarget(pc); - } - - break; - case Effect.AllDoorsOpen: - try - { - DoorsReset.OpenAllDoors(); - } - catch { } - - break; - case Effect.AllDoorsClose: - try - { - DoorsReset.CloseAllDoors(); - } - catch { } - - break; - case Effect.SetDoorsRandomly: - try - { - DoorsReset.OpenOrCloseAllDoorsRandomly(); - } - catch { } - - break; - case Effect.Patrol: - { - PlayerControl pc = PickRandomPlayer(); - var state = new PatrollingState(pc.PlayerId, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration), RandomFloat, pc); - Sentinel.PatrolStates.Add(state); - state.StartPatrolling(); - } - - break; - case Effect.GhostPlayer when TimeSinceLastMeeting > Options.DefaultKillCooldown: - { - PlayerControl killer = PickRandomPlayer(); - PlayerControl[] allPc = Main.AllAlivePlayerControls.Where(x => x.PlayerId != killer.PlayerId).ToArray(); - if (allPc.Length == 0) break; - - PlayerControl target = allPc.RandomElement(); - BallLightning.CheckBallLightningMurder(killer, target, true); - NotifyAboutRNG(target); - } - - break; - case Effect.Camouflage when TimeSinceLastMeeting > Camouflager.CamouflageCooldown.GetFloat(): - var camouflager = new Camouflager(); - camouflager.Init(); - camouflager.Add(randomizer.PlayerId); - camouflager.OnShapeshift(randomizer, randomizer, true); - LateTask.New(camouflager.OnReportDeadBody, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration), "Randomizer Revert Camo"); - break; - case Effect.Deathpact: - { - var deathpact = new Deathpact(); - deathpact.Init(); - deathpact.Add(randomizer.PlayerId); - deathpact.OnShapeshift(randomizer, PickRandomPlayer(), true); - } - - break; - case Effect.DevourRandomPlayer: - { - var devourer = new Devourer(); - devourer.Init(); - devourer.Add(randomizer.PlayerId); - devourer.OnShapeshift(randomizer, PickRandomPlayer(), true); - } - - break; - case Effect.Duel: - { - PlayerControl pc1 = PickRandomPlayer(); - PlayerControl[] allPc = Main.AllAlivePlayerControls.Where(x => x.CanUseKillButton() && x.PlayerId != pc1.PlayerId).ToArray(); - if (allPc.Length == 0) break; - - PlayerControl pc2 = allPc.RandomElement(); - var duellist = new Duellist(); - duellist.OnShapeshift(pc1, pc2, true); - } - - break; - default: - Logger.Info("Effect wasn't applied", "Randomizer"); - break; } + + break; + case Effect.DonutForAll: + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) DonutDelivery.RandomNotifyTarget(pc); + } + + break; + case Effect.AllDoorsOpen: + try + { + DoorsReset.OpenAllDoors(); + } + catch { } + + break; + case Effect.AllDoorsClose: + try + { + DoorsReset.CloseAllDoors(); + } + catch { } + + break; + case Effect.SetDoorsRandomly: + try + { + DoorsReset.OpenOrCloseAllDoorsRandomly(); + } + catch { } + + break; + case Effect.Patrol: + { + PlayerControl pc = PickRandomPlayer(); + var state = new PatrollingState(pc.PlayerId, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration), RandomFloat, pc); + Sentinel.PatrolStates.Add(state); + state.StartPatrolling(); + } + + break; + case Effect.GhostPlayer when TimeSinceLastMeeting > Options.DefaultKillCooldown: + { + PlayerControl killer = PickRandomPlayer(); + PlayerControl[] allPc = Main.AllAlivePlayerControls.Where(x => x.PlayerId != killer.PlayerId).ToArray(); + if (allPc.Length == 0) break; + + PlayerControl target = allPc.RandomElement(); + BallLightning.CheckBallLightningMurder(killer, target, true); + NotifyAboutRNG(target); + } + + break; + case Effect.Camouflage when TimeSinceLastMeeting > Camouflager.CamouflageCooldown.GetFloat(): + var camouflager = new Camouflager(); + camouflager.Init(); + camouflager.Add(randomizer.PlayerId); + camouflager.OnShapeshift(randomizer, randomizer, true); + LateTask.New(camouflager.OnReportDeadBody, IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration), "Randomizer Revert Camo"); + break; + case Effect.Deathpact: + { + var deathpact = new Deathpact(); + deathpact.Init(); + deathpact.Add(randomizer.PlayerId); + deathpact.OnShapeshift(randomizer, PickRandomPlayer(), true); + } + + break; + case Effect.DevourRandomPlayer: + { + var devourer = new Devourer(); + devourer.Init(); + devourer.Add(randomizer.PlayerId); + devourer.OnShapeshift(randomizer, PickRandomPlayer(), true); + } + + break; + case Effect.Duel: + { + PlayerControl pc1 = PickRandomPlayer(); + PlayerControl[] allPc = Main.AllAlivePlayerControls.Where(x => x.CanUseKillButton() && x.PlayerId != pc1.PlayerId).ToArray(); + if (allPc.Length == 0) break; + + PlayerControl pc2 = allPc.RandomElement(); + var duellist = new Duellist(); + duellist.OnShapeshift(pc1, pc2, true); + } + + break; + default: + Logger.Info("Effect wasn't applied", "Randomizer"); + break; } - catch (Exception ex) - { - Logger.Exception(ex, "Randomizer"); - } + } + catch (Exception ex) + { + Logger.Exception(ex, "Randomizer"); } } +} - internal class Randomizer : RoleBase +internal class Randomizer : RoleBase +{ + public enum Effect { - public enum Effect + ShieldRandomPlayer, + ShieldAll, + TPEveryoneToVents, + PullEveryone, + Twist, + SuperSpeedForRandomPlayer, + SuperSpeedForAll, + FreezeRandomPlayer, + SuperVisionForRandomPlayer, + SuperVisionForAll, + BlindnessForRandomPlayer, + BlindnessForAll, + AllKCDsReset, + AllKCDsTo0, + Meeting, + Rift, + TimeBomb, + Tornado, + RevertToBaseRole, + InvertControls, + AddonAssign, + AddonRemove, + HandcuffRandomPlayer, + HandcuffAll, + DonutForAll, + AllDoorsOpen, + AllDoorsClose, + SetDoorsRandomly, + Patrol, // Sentinel ability + GhostPlayer, // Lightning ability + Camouflage, + Deathpact, + DevourRandomPlayer, + Duel + } + + private static List PlayerIdList = []; + + private static OptionItem EffectFrequencyOpt; + private static OptionItem EffectDurMin; + private static OptionItem EffectDurMax; + private static OptionItem NotifyOpt; + + private static int EffectFrequency; + public static int MinimumEffectDuration; + public static int MaximumEffectDuration; + private static bool Notify; + + private static Dictionary> CurrentEffects = []; + public static Dictionary AllPlayerDefaultSpeed = []; + + public static Dictionary Rifts = []; + public static Dictionary Bombs = []; + + public static float TimeSinceLastMeeting; + private static Dictionary LastEffectPick = []; + private static Dictionary LastTP = []; + private static long LastDeathEffect; + + public static bool Exists; + + private static int Id => 643490; + + private static string RNGString => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Randomizer), Translator.GetString("RNGHasSpoken")); + + public static float RandomFloat => IRandom.Instance.Next(0, 5) + (IRandom.Instance.Next(0, 10) / 10f); + + public override bool IsEnable => Exists; + + public static void NotifyAboutRNG(PlayerControl pc) + { + if (!Notify) return; + + pc.Notify(RNGString, IRandom.Instance.Next(2, 7), log: false); + } + + public static bool IsShielded(PlayerControl pc) + { + return CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && (effects.ContainsKey(Effect.ShieldRandomPlayer) || effects.ContainsKey(Effect.ShieldAll)); + } + + public static bool HasSuperVision(PlayerControl pc) + { + return CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && (effects.ContainsKey(Effect.SuperVisionForRandomPlayer) || effects.ContainsKey(Effect.SuperVisionForAll)); + } + + public static bool IsBlind(PlayerControl pc) + { + return CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && (effects.ContainsKey(Effect.BlindnessForRandomPlayer) || effects.ContainsKey(Effect.BlindnessForAll)); + } + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Randomizer); + + EffectFrequencyOpt = new IntegerOptionItem(Id + 2, "RandomizerEffectFrequency", new(1, 90, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]) + .SetValueFormat(OptionFormat.Seconds); + + EffectDurMin = new IntegerOptionItem(Id + 3, "RandomizerEffectDurMin", new(1, 90, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]) + .SetValueFormat(OptionFormat.Seconds); + + EffectDurMax = new IntegerOptionItem(Id + 4, "RandomizerEffectDurMax", new(1, 90, 1), 15, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]) + .SetValueFormat(OptionFormat.Seconds); + + NotifyOpt = new BooleanOptionItem(Id + 5, "RandomizerNotifyOpt", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]); + } + + public override void Init() + { + long now = Utils.TimeStamp; + + PlayerIdList = []; + CurrentEffects = []; + AllPlayerDefaultSpeed = []; + Rifts = []; + Bombs = []; + + TimeSinceLastMeeting = 0; + LastEffectPick = []; + LastTP = []; + Main.AllPlayerControls.Do(x => LastTP[x.PlayerId] = now); + LastDeathEffect = 0; + + EffectFrequency = EffectFrequencyOpt.GetInt(); + MinimumEffectDuration = EffectDurMin.GetInt(); + MaximumEffectDuration = EffectDurMax.GetInt(); + Notify = NotifyOpt.GetBool(); + + Exists = false; + } + + public override void Add(byte playerId) + { + Exists = true; + PlayerIdList.Add(playerId); + AllPlayerDefaultSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); + } + + public static PlayerControl PickRandomPlayer() + { + PlayerControl[] allPc = Main.AllAlivePlayerControls; + if (allPc.Length == 0) return null; + + PlayerControl pc = allPc.RandomElement(); + return pc; + } + + public static void AddEffectForPlayer(PlayerControl pc, Effect effect) + { + if (pc == null || !Exists) return; + + if (!CurrentEffects.ContainsKey(pc.PlayerId)) CurrentEffects[pc.PlayerId] = []; + + int duration = IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration + 1); + CurrentEffects[pc.PlayerId].TryAdd(effect, (Utils.TimeStamp, duration)); + } + + private static Effect PickRandomEffect(byte id) + { + long now = Utils.TimeStamp; + + LastEffectPick[id] = now; + + Effect[] allEffects = Enum.GetValues(); + Effect effect = allEffects.RandomElement(); + + if (effect == Effect.GhostPlayer) { - ShieldRandomPlayer, - ShieldAll, - TPEveryoneToVents, - PullEveryone, - Twist, - SuperSpeedForRandomPlayer, - SuperSpeedForAll, - FreezeRandomPlayer, - SuperVisionForRandomPlayer, - SuperVisionForAll, - BlindnessForRandomPlayer, - BlindnessForAll, - AllKCDsReset, - AllKCDsTo0, - Meeting, - Rift, - TimeBomb, - Tornado, - RevertToBaseRole, - InvertControls, - AddonAssign, - AddonRemove, - HandcuffRandomPlayer, - HandcuffAll, - DonutForAll, - AllDoorsOpen, - AllDoorsClose, - SetDoorsRandomly, - Patrol, // Sentinel ability - GhostPlayer, // Lightning ability - Camouflage, - Deathpact, - DevourRandomPlayer, - Duel + if (LastDeathEffect + 60 > now) return Effect.AddonRemove; + + LastDeathEffect = now; } - private static List PlayerIdList = []; + Logger.Info($"Effect: {effect}", "Randomizer"); - private static OptionItem EffectFrequencyOpt; - private static OptionItem EffectDurMin; - private static OptionItem EffectDurMax; - private static OptionItem NotifyOpt; + return effect; + } - private static int EffectFrequency; - public static int MinimumEffectDuration; - public static int MaximumEffectDuration; - private static bool Notify; - - private static Dictionary> CurrentEffects = []; - public static Dictionary AllPlayerDefaultSpeed = []; - - public static Dictionary Rifts = []; - public static Dictionary Bombs = []; - - public static float TimeSinceLastMeeting; - private static Dictionary LastEffectPick = []; - private static Dictionary LastTP = []; - private static long LastDeathEffect; - - public static bool Exists; - - private static int Id => 643490; - - private static string RNGString => Utils.ColorString(Utils.GetRoleColor(CustomRoles.Randomizer), Translator.GetString("RNGHasSpoken")); - - public static float RandomFloat => IRandom.Instance.Next(0, 5) + (IRandom.Instance.Next(0, 10) / 10f); - - public override bool IsEnable => Exists; - - public static void NotifyAboutRNG(PlayerControl pc) - { - if (!Notify) return; - - pc.Notify(RNGString, IRandom.Instance.Next(2, 7), log: false); - } - - public static bool IsShielded(PlayerControl pc) - { - return CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && (effects.ContainsKey(Effect.ShieldRandomPlayer) || effects.ContainsKey(Effect.ShieldAll)); - } - - public static bool HasSuperVision(PlayerControl pc) - { - return CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && (effects.ContainsKey(Effect.SuperVisionForRandomPlayer) || effects.ContainsKey(Effect.SuperVisionForAll)); - } - - public static bool IsBlind(PlayerControl pc) - { - return CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && (effects.ContainsKey(Effect.BlindnessForRandomPlayer) || effects.ContainsKey(Effect.BlindnessForAll)); - } - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Randomizer); - - EffectFrequencyOpt = new IntegerOptionItem(Id + 2, "RandomizerEffectFrequency", new(1, 90, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]) - .SetValueFormat(OptionFormat.Seconds); - - EffectDurMin = new IntegerOptionItem(Id + 3, "RandomizerEffectDurMin", new(1, 90, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]) - .SetValueFormat(OptionFormat.Seconds); - - EffectDurMax = new IntegerOptionItem(Id + 4, "RandomizerEffectDurMax", new(1, 90, 1), 15, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]) - .SetValueFormat(OptionFormat.Seconds); - - NotifyOpt = new BooleanOptionItem(Id + 5, "RandomizerNotifyOpt", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Randomizer]); - } - - public override void Init() - { - long now = Utils.TimeStamp; - - PlayerIdList = []; - CurrentEffects = []; - AllPlayerDefaultSpeed = []; - Rifts = []; - Bombs = []; - - TimeSinceLastMeeting = 0; - LastEffectPick = []; - LastTP = []; - Main.AllPlayerControls.Do(x => LastTP[x.PlayerId] = now); - LastDeathEffect = 0; - - EffectFrequency = EffectFrequencyOpt.GetInt(); - MinimumEffectDuration = EffectDurMin.GetInt(); - MaximumEffectDuration = EffectDurMax.GetInt(); - Notify = NotifyOpt.GetBool(); - - Exists = false; - } - - public override void Add(byte playerId) - { - Exists = true; - PlayerIdList.Add(playerId); - AllPlayerDefaultSpeed = Main.AllPlayerSpeed.ToDictionary(x => x.Key, x => x.Value); - } - - public static PlayerControl PickRandomPlayer() - { - PlayerControl[] allPc = Main.AllAlivePlayerControls; - if (allPc.Length == 0) return null; - - PlayerControl pc = allPc.RandomElement(); - return pc; - } - - public static void AddEffectForPlayer(PlayerControl pc, Effect effect) + public static void RevertSpeedChangesForPlayer(PlayerControl pc, bool sync) + { + try { if (pc == null || !Exists) return; - if (!CurrentEffects.ContainsKey(pc.PlayerId)) CurrentEffects[pc.PlayerId] = []; + if (CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && effects.Any(x => x.Key.IsSpeedChangingEffect())) + { + Main.AllPlayerSpeed[pc.PlayerId] = AllPlayerDefaultSpeed[pc.PlayerId]; + if (sync) pc.MarkDirtySettings(); - int duration = IRandom.Instance.Next(MinimumEffectDuration, MaximumEffectDuration + 1); - CurrentEffects[pc.PlayerId].TryAdd(effect, (Utils.TimeStamp, duration)); + IEnumerable keys = effects.Keys.AsEnumerable(); + keys.DoIf(x => x.IsSpeedChangingEffect(), x => effects.Remove(x), false); + } } - - private static Effect PickRandomEffect(byte id) + catch (Exception e) { + Logger.Exception(e, "Randomizer"); + } + } + + public static void RevertVisionChangesForPlayer(PlayerControl pc, bool sync) + { + try + { + if (pc == null || !Exists) return; + + if (CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && effects.Any(x => x.Key.IsVisionChangingEffect())) + { + IEnumerable keys = effects.Keys.AsEnumerable(); + keys.DoIf(x => x.IsVisionChangingEffect(), x => effects.Remove(x), false); + if (sync) pc.MarkDirtySettings(); + } + } + catch (Exception e) + { + Logger.Exception(e, "Randomizer"); + } + } + + public static void OnAnyoneDeath(PlayerControl pc) + { + try + { + if (!Exists) return; + + RevertSpeedChangesForPlayer(pc, false); + RevertVisionChangesForPlayer(pc, false); + + Main.AllPlayerSpeed[pc.PlayerId] = AllPlayerDefaultSpeed[pc.PlayerId]; + pc.MarkDirtySettings(); + } + catch (Exception e) + { + Logger.Exception(e, "Randomizer"); + } + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + TimeSinceLastMeeting = 0; + LastDeathEffect = Utils.TimeStamp; + Rifts.Clear(); + Bombs.Clear(); + Utils.SendRPC(CustomRPC.SyncRoleData, PlayerIdList[0], 3); + + foreach (PlayerControl pc in Main.AllPlayerControls) + { + RevertSpeedChangesForPlayer(pc, false); + RevertVisionChangesForPlayer(pc, false); + } + } + + public override void AfterMeetingTasks() + { + if (!IsEnable) return; + + TimeSinceLastMeeting = 0; + LastDeathEffect = Utils.TimeStamp; + } + + public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + { + try + { + if (lowLoad || !Exists || !GameStates.IsInTask || Bombs.Count == 0 || Main.HasJustStarted) return; + + long now = Utils.TimeStamp; + PlayerControl randomizer = Utils.GetPlayerById(PlayerIdList.FirstOrDefault()); + + foreach (KeyValuePair bomb in Bombs) + { + if (bomb.Value.PlaceTimeStamp + bomb.Value.ExplosionDelay < now) + { + IEnumerable players = Utils.GetPlayersInRadius(RandomFloat, bomb.Key); + foreach (PlayerControl pc in players) pc.Suicide(PlayerState.DeathReason.RNG, randomizer); + + Bombs.Remove(bomb.Key); + Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 2, bomb.Key); + } + } + } + catch (Exception ex) + { + Logger.Exception(ex, "Randomizer"); + } + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + Bombs.TryAdd(NetHelpers.ReadVector2(reader), (long.Parse(reader.ReadString()), reader.ReadPackedInt32())); + break; + case 2: + Bombs.Remove(NetHelpers.ReadVector2(reader)); + break; + case 3: + Bombs.Clear(); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || seer.PlayerId != target.PlayerId || Bombs.Count == 0) return string.Empty; + + KeyValuePair bomb = Bombs.FirstOrDefault(x => Vector2.Distance(x.Key, seer.Pos()) <= 5f); + long time = bomb.Value.ExplosionDelay - (Utils.TimeStamp - bomb.Value.PlaceTimeStamp) + 1; + return time < 0 ? string.Empty : $"<#ffff00>⚠ {time}"; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + try + { + if (!IsEnable || !GameStates.IsInTask || Main.HasJustStarted) return; + + TimeSinceLastMeeting += Time.fixedDeltaTime; + + if (pc == null || !pc.IsAlive() || TimeSinceLastMeeting <= 10f) return; + long now = Utils.TimeStamp; - LastEffectPick[id] = now; - - Effect[] allEffects = Enum.GetValues(); - Effect effect = allEffects.RandomElement(); - - if (effect == Effect.GhostPlayer) + if (LastEffectPick.TryGetValue(pc.PlayerId, out long ts) && ts + EffectFrequency <= now) { - if (LastDeathEffect + 60 > now) return Effect.AddonRemove; - - LastDeathEffect = now; + Effect effect = PickRandomEffect(pc.PlayerId); + effect.Apply(pc); } - - Logger.Info($"Effect: {effect}", "Randomizer"); - - return effect; + else + LastEffectPick.TryAdd(pc.PlayerId, now); } - - public static void RevertSpeedChangesForPlayer(PlayerControl pc, bool sync) + catch (Exception ex) { - try + Logger.Exception(ex, "Randomizer"); + } + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + try + { + if (!IsEnable || !GameStates.IsInTask || Main.HasJustStarted) return; + + long now = Utils.TimeStamp; + if (LastTP[pc.PlayerId] + 5 > now) return; + + Vector2 pos = pc.Pos(); + + foreach (KeyValuePair rift in Rifts) { - if (pc == null || !Exists) return; - - if (CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && effects.Any(x => x.Key.IsSpeedChangingEffect())) + if (Vector2.Distance(pos, rift.Key) < 2f) { - Main.AllPlayerSpeed[pc.PlayerId] = AllPlayerDefaultSpeed[pc.PlayerId]; - if (sync) pc.MarkDirtySettings(); + pc.TP(rift.Value); + LastTP[pc.PlayerId] = now; + return; + } - IEnumerable keys = effects.Keys.AsEnumerable(); - keys.DoIf(x => x.IsSpeedChangingEffect(), x => effects.Remove(x), false); + if (Vector2.Distance(pos, rift.Value) < 2f) + { + pc.TP(rift.Key); + LastTP[pc.PlayerId] = now; + return; } } - catch (Exception e) - { - Logger.Exception(e, "Randomizer"); - } } - - public static void RevertVisionChangesForPlayer(PlayerControl pc, bool sync) + catch (Exception ex) { - try - { - if (pc == null || !Exists) return; - - if (CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects) && effects.Any(x => x.Key.IsVisionChangingEffect())) - { - IEnumerable keys = effects.Keys.AsEnumerable(); - keys.DoIf(x => x.IsVisionChangingEffect(), x => effects.Remove(x), false); - if (sync) pc.MarkDirtySettings(); - } - } - catch (Exception e) - { - Logger.Exception(e, "Randomizer"); - } + Logger.Exception(ex, "Randomizer"); } + } - public static void OnAnyoneDeath(PlayerControl pc) + public static void OnFixedUpdateForPlayers(PlayerControl pc) + { + try { - try + if (!Exists || pc == null || !pc.IsAlive() || !GameStates.IsInTask || Main.HasJustStarted) return; + + if (CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects)) { - if (!Exists) return; - - RevertSpeedChangesForPlayer(pc, false); - RevertVisionChangesForPlayer(pc, false); - - Main.AllPlayerSpeed[pc.PlayerId] = AllPlayerDefaultSpeed[pc.PlayerId]; - pc.MarkDirtySettings(); - } - catch (Exception e) - { - Logger.Exception(e, "Randomizer"); - } - } - - public override void OnReportDeadBody() - { - if (!IsEnable) return; - - TimeSinceLastMeeting = 0; - LastDeathEffect = Utils.TimeStamp; - Rifts.Clear(); - Bombs.Clear(); - Utils.SendRPC(CustomRPC.SyncRoleData, PlayerIdList[0], 3); - - foreach (PlayerControl pc in Main.AllPlayerControls) - { - RevertSpeedChangesForPlayer(pc, false); - RevertVisionChangesForPlayer(pc, false); - } - } - - public override void AfterMeetingTasks() - { - if (!IsEnable) return; - - TimeSinceLastMeeting = 0; - LastDeathEffect = Utils.TimeStamp; - } - - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) - { - try - { - if (lowLoad || !Exists || !GameStates.IsInTask || Bombs.Count == 0 || Main.HasJustStarted) return; - - long now = Utils.TimeStamp; - PlayerControl randomizer = Utils.GetPlayerById(PlayerIdList.FirstOrDefault()); - - foreach (KeyValuePair bomb in Bombs) - { - if (bomb.Value.PlaceTimeStamp + bomb.Value.ExplosionDelay < now) - { - IEnumerable players = Utils.GetPlayersInRadius(RandomFloat, bomb.Key); - foreach (PlayerControl pc in players) pc.Suicide(PlayerState.DeathReason.RNG, randomizer); - - Bombs.Remove(bomb.Key); - Utils.SendRPC(CustomRPC.SyncRoleData, randomizer.PlayerId, 2, bomb.Key); - } - } - } - catch (Exception ex) - { - Logger.Exception(ex, "Randomizer"); - } - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - Bombs.TryAdd(NetHelpers.ReadVector2(reader), (long.Parse(reader.ReadString()), reader.ReadPackedInt32())); - break; - case 2: - Bombs.Remove(NetHelpers.ReadVector2(reader)); - break; - case 3: - Bombs.Clear(); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || seer.PlayerId != target.PlayerId || Bombs.Count == 0) return string.Empty; - - KeyValuePair bomb = Bombs.FirstOrDefault(x => Vector2.Distance(x.Key, seer.Pos()) <= 5f); - long time = bomb.Value.ExplosionDelay - (Utils.TimeStamp - bomb.Value.PlaceTimeStamp) + 1; - return time < 0 ? string.Empty : $"<#ffff00>⚠ {time}"; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - try - { - if (!IsEnable || !GameStates.IsInTask || Main.HasJustStarted) return; - - TimeSinceLastMeeting += Time.fixedDeltaTime; - - if (pc == null || !pc.IsAlive() || TimeSinceLastMeeting <= 10f) return; - long now = Utils.TimeStamp; - if (LastEffectPick.TryGetValue(pc.PlayerId, out long ts) && ts + EffectFrequency <= now) + foreach (KeyValuePair item in effects.ToArray()) { - Effect effect = PickRandomEffect(pc.PlayerId); - effect.Apply(pc); + if (item.Value.StartTimeStamp + item.Value.Duration < now) + { + if (item.Key.IsVisionChangingEffect()) RevertVisionChangesForPlayer(pc, true); + + if (item.Key.IsSpeedChangingEffect()) RevertSpeedChangesForPlayer(pc, true); + + effects.Remove(item.Key); + } } - else - LastEffectPick.TryAdd(pc.PlayerId, now); - } - catch (Exception ex) - { - Logger.Exception(ex, "Randomizer"); } } - - public override void OnCheckPlayerPosition(PlayerControl pc) + catch (Exception ex) { - try - { - if (!IsEnable || !GameStates.IsInTask || Main.HasJustStarted) return; - - long now = Utils.TimeStamp; - if (LastTP[pc.PlayerId] + 5 > now) return; - - Vector2 pos = pc.Pos(); - - foreach (KeyValuePair rift in Rifts) - { - if (Vector2.Distance(pos, rift.Key) < 2f) - { - pc.TP(rift.Value); - LastTP[pc.PlayerId] = now; - return; - } - - if (Vector2.Distance(pos, rift.Value) < 2f) - { - pc.TP(rift.Key); - LastTP[pc.PlayerId] = now; - return; - } - } - } - catch (Exception ex) - { - Logger.Exception(ex, "Randomizer"); - } - } - - public static void OnFixedUpdateForPlayers(PlayerControl pc) - { - try - { - if (!Exists || pc == null || !pc.IsAlive() || !GameStates.IsInTask || Main.HasJustStarted) return; - - if (CurrentEffects.TryGetValue(pc.PlayerId, out Dictionary effects)) - { - long now = Utils.TimeStamp; - - foreach (KeyValuePair item in effects.ToArray()) - { - if (item.Value.StartTimeStamp + item.Value.Duration < now) - { - if (item.Key.IsVisionChangingEffect()) RevertVisionChangesForPlayer(pc, true); - - if (item.Key.IsSpeedChangingEffect()) RevertSpeedChangesForPlayer(pc, true); - - effects.Remove(item.Key); - } - } - } - } - catch (Exception ex) - { - Logger.Exception(ex, "Randomizer"); - } + Logger.Exception(ex, "Randomizer"); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Rhapsode.cs b/Roles/Crewmate/Rhapsode.cs index 643173ba7..c346c4011 100644 --- a/Roles/Crewmate/Rhapsode.cs +++ b/Roles/Crewmate/Rhapsode.cs @@ -4,137 +4,136 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Rhapsode : RoleBase { - public class Rhapsode : RoleBase + public static bool On; + private static List Instances = []; + + public static OptionItem AbilityCooldown; + public static OptionItem AbilityDuration; + private static OptionItem ExcludeCrewmates; + private static OptionItem AbilityUseLimit; + public static OptionItem RhapsodeAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private bool AbilityActive; + private long ActivateTimeStamp; + private long LastUpdate; + private byte RhapsodeId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + var id = 647650; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Rhapsode); - public static OptionItem AbilityCooldown; - public static OptionItem AbilityDuration; - private static OptionItem ExcludeCrewmates; - private static OptionItem AbilityUseLimit; - public static OptionItem RhapsodeAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - private bool AbilityActive; - private long ActivateTimeStamp; - private long LastUpdate; - private byte RhapsodeId; + AbilityCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + AbilityDuration = new IntegerOptionItem(++id, "AbilityDuration", new(0, 60, 1), 10, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + ExcludeCrewmates = new BooleanOptionItem(++id, "Rhapsode.ExcludeCrewmates", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]); + + AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Times); + + RhapsodeAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + RhapsodeId = playerId; + AbilityActive = false; + ActivateTimeStamp = 0; + LastUpdate = 0; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = AbilityCooldown.GetInt(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + ActivateAbility(physics.myPlayer); + } + + public override void OnPet(PlayerControl pc) + { + ActivateAbility(pc); + } + + private void ActivateAbility(PlayerControl pc) + { + AbilityActive = true; + ActivateTimeStamp = Utils.TimeStamp; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityActive); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + + LastUpdate = now; + + if (AbilityActive && Utils.TimeStamp - ActivateTimeStamp >= AbilityDuration.GetInt()) { - var id = 647650; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Rhapsode); - - AbilityCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(0, 60, 1), 30, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) - .SetValueFormat(OptionFormat.Seconds); - - AbilityDuration = new IntegerOptionItem(++id, "AbilityDuration", new(0, 60, 1), 10, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) - .SetValueFormat(OptionFormat.Seconds); - - ExcludeCrewmates = new BooleanOptionItem(++id, "Rhapsode.ExcludeCrewmates", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]); - - AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) - .SetValueFormat(OptionFormat.Times); - - RhapsodeAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rhapsode]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - RhapsodeId = playerId; AbilityActive = false; - ActivateTimeStamp = 0; - LastUpdate = 0; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = AbilityCooldown.GetInt(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - ActivateAbility(physics.myPlayer); - } - - public override void OnPet(PlayerControl pc) - { - ActivateAbility(pc); - } - - private void ActivateAbility(PlayerControl pc) - { - AbilityActive = true; - ActivateTimeStamp = Utils.TimeStamp; Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityActive); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } - public override void OnFixedUpdate(PlayerControl pc) - { - long now = Utils.TimeStamp; - if (now == LastUpdate) return; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } - LastUpdate = now; + public static bool CheckAbilityUse(PlayerControl pc) + { + if (pc.IsCrewmate() && ExcludeCrewmates.GetBool()) return true; - if (AbilityActive && Utils.TimeStamp - ActivateTimeStamp >= AbilityDuration.GetInt()) - { - AbilityActive = false; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, AbilityActive); - } + return !Instances.Any(x => x.AbilityActive); + } - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } + public void ReceiveRPC(MessageReader reader) + { + AbilityActive = reader.ReadBoolean(); + if (AbilityActive) ActivateTimeStamp = Utils.TimeStamp; + } - public static bool CheckAbilityUse(PlayerControl pc) - { - if (pc.IsCrewmate() && ExcludeCrewmates.GetBool()) return true; + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != RhapsodeId || hud || meeting || !AbilityActive) return string.Empty; - return !Instances.Any(x => x.AbilityActive); - } + return $"\u25b6 ({AbilityDuration.GetInt() - (Utils.TimeStamp - ActivateTimeStamp)}s)"; + } - public void ReceiveRPC(MessageReader reader) - { - AbilityActive = reader.ReadBoolean(); - if (AbilityActive) ActivateTimeStamp = Utils.TimeStamp; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != RhapsodeId || hud || meeting || !AbilityActive) return string.Empty; - - return $"\u25b6 ({AbilityDuration.GetInt() - (Utils.TimeStamp - ActivateTimeStamp)}s)"; - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Ricochet.cs b/Roles/Crewmate/Ricochet.cs index 669e37b8a..94cb5e361 100644 --- a/Roles/Crewmate/Ricochet.cs +++ b/Roles/Crewmate/Ricochet.cs @@ -2,116 +2,115 @@ using System.Collections.Generic; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; + +public class Ricochet : RoleBase { - using static Options; + private const int Id = 640100; + public static List PlayerIdList = []; - public class Ricochet : RoleBase + public static OptionItem UseLimitOpt; + public static OptionItem RicochetAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem CancelVote; + + public byte ProtectAgainst = byte.MaxValue; + private byte RicochetId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640100; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ricochet); - public static OptionItem UseLimitOpt; - public static OptionItem RicochetAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem CancelVote; + UseLimitOpt = new IntegerOptionItem(Id + 10, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Ricochet]) + .SetValueFormat(OptionFormat.Times); - public byte ProtectAgainst = byte.MaxValue; - private byte RicochetId; + RicochetAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 11, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ricochet]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ricochet]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + CancelVote = CreateVoteCancellingUseSetting(Id + 12, CustomRoles.Ricochet, TabGroup.CrewmateRoles); + } + + public override void Init() + { + PlayerIdList = []; + ProtectAgainst = byte.MaxValue; + RicochetId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + ProtectAgainst = byte.MaxValue; + RicochetId = playerId; + } + + private void SendRPCSyncTarget(byte targetId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRicochetTarget, SendOption.Reliable); + writer.Write(RicochetId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCSyncTarget(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not Ricochet rc) return; + + rc.ProtectAgainst = reader.ReadByte(); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + if (ProtectAgainst == killer.PlayerId) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Ricochet); - - UseLimitOpt = new IntegerOptionItem(Id + 10, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Ricochet]) - .SetValueFormat(OptionFormat.Times); - - RicochetAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 11, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ricochet]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ricochet]) - .SetValueFormat(OptionFormat.Times); - - CancelVote = CreateVoteCancellingUseSetting(Id + 12, CustomRoles.Ricochet, TabGroup.CrewmateRoles); - } - - public override void Init() - { - PlayerIdList = []; - ProtectAgainst = byte.MaxValue; - RicochetId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - ProtectAgainst = byte.MaxValue; - RicochetId = playerId; - } - - private void SendRPCSyncTarget(byte targetId) - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRicochetTarget, SendOption.Reliable); - writer.Write(RicochetId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCSyncTarget(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not Ricochet rc) return; - - rc.ProtectAgainst = reader.ReadByte(); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (killer == null) return false; - - if (target == null) return false; - - if (ProtectAgainst == killer.PlayerId) - { - killer.SetKillCooldown(5f); - return false; - } - - return true; - } - - public override bool OnVote(PlayerControl pc, PlayerControl target) - { - if (target == null || pc == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; - - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - ProtectAgainst = target.PlayerId; - SendRPCSyncTarget(ProtectAgainst); - Main.DontCancelVoteList.Add(pc.PlayerId); - return true; - } - + killer.SetKillCooldown(5f); return false; } - public override void OnReportDeadBody() + return true; + } + + public override bool OnVote(PlayerControl pc, PlayerControl target) + { + if (target == null || pc == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; + + if (pc.GetAbilityUseLimit() >= 1) { - ProtectAgainst = byte.MaxValue; + pc.RpcRemoveAbilityUse(); + ProtectAgainst = target.PlayerId; SendRPCSyncTarget(ProtectAgainst); + Main.DontCancelVoteList.Add(pc.PlayerId); + return true; } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - return ProtectAgainst != byte.MaxValue && seer.PlayerId == target.PlayerId && seer.PlayerId == RicochetId ? $"Target: {Utils.GetPlayerById(ProtectAgainst).GetRealName()}" : string.Empty; - } + return false; + } + + public override void OnReportDeadBody() + { + ProtectAgainst = byte.MaxValue; + SendRPCSyncTarget(ProtectAgainst); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return ProtectAgainst != byte.MaxValue && seer.PlayerId == target.PlayerId && seer.PlayerId == RicochetId ? $"Target: {Utils.GetPlayerById(ProtectAgainst).GetRealName()}" : string.Empty; } } \ No newline at end of file diff --git a/Roles/Crewmate/SabotageMaster.cs b/Roles/Crewmate/SabotageMaster.cs index 17b1a2f3f..e7bd5203e 100644 --- a/Roles/Crewmate/SabotageMaster.cs +++ b/Roles/Crewmate/SabotageMaster.cs @@ -6,268 +6,267 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class SabotageMaster : RoleBase { - public class SabotageMaster : RoleBase + private const int Id = 7000; + private static List PlayerIdList = []; + + public static OptionItem SkillLimit; + private static OptionItem FixesDoors; + private static OptionItem FixesReactors; + private static OptionItem FixesOxygens; + private static OptionItem FixesComms; + private static OptionItem FixesElectrical; + public static OptionItem SmAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private static OptionItem UsesUsedWhenFixingReactorOrO2; + private static OptionItem UsesUsedWhenFixingLightsOrComms; + + private static bool DoorsProgressing; + private bool fixedSabotage; + private byte SMId; + + public float UsedSkillCount; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 7000; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.SabotageMaster); - public static OptionItem SkillLimit; - private static OptionItem FixesDoors; - private static OptionItem FixesReactors; - private static OptionItem FixesOxygens; - private static OptionItem FixesComms; - private static OptionItem FixesElectrical; - public static OptionItem SmAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - private static OptionItem UsesUsedWhenFixingReactorOrO2; - private static OptionItem UsesUsedWhenFixingLightsOrComms; + SkillLimit = new IntegerOptionItem(Id + 10, "SabotageMasterSkillLimit", new(0, 80, 1), 2, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) + .SetValueFormat(OptionFormat.Times); - private static bool DoorsProgressing; - private bool fixedSabotage; - private byte SMId; + FixesDoors = new BooleanOptionItem(Id + 11, "SabotageMasterFixesDoors", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - public float UsedSkillCount; + FixesReactors = new BooleanOptionItem(Id + 12, "SabotageMasterFixesReactors", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - public override bool IsEnable => PlayerIdList.Count > 0; + FixesOxygens = new BooleanOptionItem(Id + 13, "SabotageMasterFixesOxygens", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - public override void SetupCustomOption() + FixesComms = new BooleanOptionItem(Id + 14, "SabotageMasterFixesCommunications", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); + + FixesElectrical = new BooleanOptionItem(Id + 15, "SabotageMasterFixesElectrical", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); + + SmAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 16, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 3f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 19, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) + .SetValueFormat(OptionFormat.Times); + + UsesUsedWhenFixingReactorOrO2 = new FloatOptionItem(Id + 17, "SMUsesUsedWhenFixingReactorOrO2", new(0f, 5f, 0.1f), 4f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) + .SetValueFormat(OptionFormat.Times); + + UsesUsedWhenFixingLightsOrComms = new FloatOptionItem(Id + 18, "SMUsesUsedWhenFixingLightsOrComms", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + UsedSkillCount = 0; + SMId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + UsedSkillCount = 0; + SMId = playerId; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 0f; + AURoleOptions.EngineerInVentMaxTime = 0f; + } + + public override string GetProgressText(byte playerId, bool comms) + { + double limit = Math.Round(SkillLimit.GetInt() - UsedSkillCount, 1); + string colored = Utils.ColorString(Utils.GetRoleColor(CustomRoles.SabotageMaster).ShadeColor(0.25f), limit.ToString(CultureInfo.CurrentCulture)); + return $"({colored}){base.GetProgressText(playerId, comms)}"; + } + + public void SendRPC() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetSabotageMasterLimit, SendOption.Reliable); + writer.Write(SMId); + writer.Write(UsedSkillCount); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not SabotageMaster sm) return; + + sm.UsedSkillCount = reader.ReadSingle(); + } + + public static void RepairSystem(byte playerId, SystemTypes systemType, byte amount) + { + if (Main.PlayerStates[playerId].Role is not SabotageMaster sm) return; + + switch (systemType) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.SabotageMaster); - - SkillLimit = new IntegerOptionItem(Id + 10, "SabotageMasterSkillLimit", new(0, 80, 1), 2, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) - .SetValueFormat(OptionFormat.Times); - - FixesDoors = new BooleanOptionItem(Id + 11, "SabotageMasterFixesDoors", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - - FixesReactors = new BooleanOptionItem(Id + 12, "SabotageMasterFixesReactors", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - - FixesOxygens = new BooleanOptionItem(Id + 13, "SabotageMasterFixesOxygens", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - - FixesComms = new BooleanOptionItem(Id + 14, "SabotageMasterFixesCommunications", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - - FixesElectrical = new BooleanOptionItem(Id + 15, "SabotageMasterFixesElectrical", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]); - - SmAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 16, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 3f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 19, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) - .SetValueFormat(OptionFormat.Times); - - UsesUsedWhenFixingReactorOrO2 = new FloatOptionItem(Id + 17, "SMUsesUsedWhenFixingReactorOrO2", new(0f, 5f, 0.1f), 4f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) - .SetValueFormat(OptionFormat.Times); - - UsesUsedWhenFixingLightsOrComms = new FloatOptionItem(Id + 18, "SMUsesUsedWhenFixingLightsOrComms", new(0f, 5f, 0.1f), 1f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SabotageMaster]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - PlayerIdList = []; - UsedSkillCount = 0; - SMId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - UsedSkillCount = 0; - SMId = playerId; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = 0f; - AURoleOptions.EngineerInVentMaxTime = 0f; - } - - public override string GetProgressText(byte playerId, bool comms) - { - double limit = Math.Round(SkillLimit.GetInt() - UsedSkillCount, 1); - string colored = Utils.ColorString(Utils.GetRoleColor(CustomRoles.SabotageMaster).ShadeColor(0.25f), limit.ToString(CultureInfo.CurrentCulture)); - return $"({colored}){base.GetProgressText(playerId, comms)}"; - } - - public void SendRPC() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetSabotageMasterLimit, SendOption.Reliable); - writer.Write(SMId); - writer.Write(UsedSkillCount); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not SabotageMaster sm) return; - - sm.UsedSkillCount = reader.ReadSingle(); - } - - public static void RepairSystem(byte playerId, SystemTypes systemType, byte amount) - { - if (Main.PlayerStates[playerId].Role is not SabotageMaster sm) return; - - switch (systemType) + case SystemTypes.Reactor: + case SystemTypes.Laboratory: { - case SystemTypes.Reactor: - case SystemTypes.Laboratory: + if (!FixesReactors.GetBool()) break; + + if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break; + + if (amount.HasAnyBit(ReactorSystemType.AddUserOp)) { - if (!FixesReactors.GetBool()) break; - - if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break; - - if (amount.HasAnyBit(ReactorSystemType.AddUserOp)) - { - ShipStatus.Instance.UpdateSystem((MapNames)Main.NormalOptions.MapId == MapNames.Polus ? SystemTypes.Laboratory : SystemTypes.Reactor, playerId.GetPlayer(), ReactorSystemType.ClearCountdown); - sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat(); - sm.SendRPC(); - } - - break; + ShipStatus.Instance.UpdateSystem((MapNames)Main.NormalOptions.MapId == MapNames.Polus ? SystemTypes.Laboratory : SystemTypes.Reactor, playerId.GetPlayer(), ReactorSystemType.ClearCountdown); + sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat(); + sm.SendRPC(); } - case SystemTypes.HeliSabotage: + + break; + } + case SystemTypes.HeliSabotage: + { + if (!FixesReactors.GetBool()) break; + + if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break; + + var tags = (HeliSabotageSystem.Tags)(amount & HeliSabotageSystem.TagMask); + if (tags == HeliSabotageSystem.Tags.ActiveBit) sm.fixedSabotage = false; + + if (!sm.fixedSabotage && tags == HeliSabotageSystem.Tags.FixBit) { - if (!FixesReactors.GetBool()) break; - - if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break; - - var tags = (HeliSabotageSystem.Tags)(amount & HeliSabotageSystem.TagMask); - if (tags == HeliSabotageSystem.Tags.ActiveBit) sm.fixedSabotage = false; - - if (!sm.fixedSabotage && tags == HeliSabotageSystem.Tags.FixBit) - { - sm.fixedSabotage = true; - int consoleId = amount & HeliSabotageSystem.IdMask; - int otherConsoleId = (consoleId + 1) % 2; - ShipStatus.Instance.UpdateSystem(SystemTypes.HeliSabotage, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HeliSabotageSystem.Tags.FixBit)); - sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat(); - sm.SendRPC(); - } - - break; + sm.fixedSabotage = true; + int consoleId = amount & HeliSabotageSystem.IdMask; + int otherConsoleId = (consoleId + 1) % 2; + ShipStatus.Instance.UpdateSystem(SystemTypes.HeliSabotage, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HeliSabotageSystem.Tags.FixBit)); + sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat(); + sm.SendRPC(); } - case SystemTypes.LifeSupp: + + break; + } + case SystemTypes.LifeSupp: + { + if (!FixesOxygens.GetBool()) break; + + if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break; + + if (amount.HasAnyBit(LifeSuppSystemType.AddUserOp)) { - if (!FixesOxygens.GetBool()) break; - - if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingReactorOrO2.GetFloat() - 1 >= SkillLimit.GetFloat()) break; - - if (amount.HasAnyBit(LifeSuppSystemType.AddUserOp)) - { - ShipStatus.Instance.UpdateSystem(SystemTypes.LifeSupp, playerId.GetPlayer(), LifeSuppSystemType.ClearCountdown); - sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat(); - sm.SendRPC(); - } - - break; + ShipStatus.Instance.UpdateSystem(SystemTypes.LifeSupp, playerId.GetPlayer(), LifeSuppSystemType.ClearCountdown); + sm.UsedSkillCount += UsesUsedWhenFixingReactorOrO2.GetFloat(); + sm.SendRPC(); } - case SystemTypes.Comms when Main.CurrentMap == MapNames.Mira: + + break; + } + case SystemTypes.Comms when Main.CurrentMap == MapNames.Mira: + { + if (!FixesComms.GetBool()) break; + + if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat()) break; + + var tags = (HqHudSystemType.Tags)(amount & HqHudSystemType.TagMask); + if (tags == HqHudSystemType.Tags.ActiveBit) sm.fixedSabotage = false; + + if (!sm.fixedSabotage && tags == HqHudSystemType.Tags.FixBit) { - if (!FixesComms.GetBool()) break; - - if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat()) break; - - var tags = (HqHudSystemType.Tags)(amount & HqHudSystemType.TagMask); - if (tags == HqHudSystemType.Tags.ActiveBit) sm.fixedSabotage = false; - - if (!sm.fixedSabotage && tags == HqHudSystemType.Tags.FixBit) - { - sm.fixedSabotage = true; - int consoleId = amount & HqHudSystemType.IdMask; - int otherConsoleId = (consoleId + 1) % 2; - ShipStatus.Instance.UpdateSystem(SystemTypes.Comms, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HqHudSystemType.Tags.FixBit)); - sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat(); - sm.SendRPC(); - } - - break; + sm.fixedSabotage = true; + int consoleId = amount & HqHudSystemType.IdMask; + int otherConsoleId = (consoleId + 1) % 2; + ShipStatus.Instance.UpdateSystem(SystemTypes.Comms, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HqHudSystemType.Tags.FixBit)); + sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat(); + sm.SendRPC(); } - case SystemTypes.Doors: + + break; + } + case SystemTypes.Doors: + { + if (!FixesDoors.GetBool()) break; + + if (DoorsProgressing) break; + + int mapId = Main.NormalOptions.MapId; + if (AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) mapId = AmongUsClient.Instance.TutorialMapId; + + var shipStatus = ShipStatus.Instance; + + DoorsProgressing = true; + + switch (mapId) { - if (!FixesDoors.GetBool()) break; - - if (DoorsProgressing) break; - - int mapId = Main.NormalOptions.MapId; - if (AmongUsClient.Instance.NetworkMode == NetworkModes.FreePlay) mapId = AmongUsClient.Instance.TutorialMapId; - - var shipStatus = ShipStatus.Instance; - - DoorsProgressing = true; - - switch (mapId) + case 2: // Polus { - case 2: // Polus + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 71, 72); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 67, 68); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 64, 66); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 73, 74); + break; + } + case 4: // Airship + { + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 64, 67); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 71, 73); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 74, 75); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 76, 78); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 68, 70); + RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 83, 84); + break; + } + case 5: // Fungle + { + int openedDoorId = amount & DoorsSystemType.IdMask; + OpenableDoor openedDoor = shipStatus.AllDoors.FirstOrDefault(door => door.Id == openedDoorId); + + if (openedDoor == null) + Logger.Warn($"An unknown door has been opened: {openedDoorId}", nameof(SabotageMaster)); + else { - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 71, 72); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 67, 68); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 64, 66); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 73, 74); - break; - } - case 4: // Airship - { - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 64, 67); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 71, 73); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 74, 75); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 76, 78); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 68, 70); - RepairSystemPatch.CheckAndOpenDoorsRange(shipStatus, amount, 83, 84); - break; - } - case 5: // Fungle - { - int openedDoorId = amount & DoorsSystemType.IdMask; - OpenableDoor openedDoor = shipStatus.AllDoors.FirstOrDefault(door => door.Id == openedDoorId); + SystemTypes room = openedDoor.Room; - if (openedDoor == null) - Logger.Warn($"An unknown door has been opened: {openedDoorId}", nameof(SabotageMaster)); - else - { - SystemTypes room = openedDoor.Room; - - foreach (OpenableDoor door in shipStatus.AllDoors) - if (door.Id != openedDoorId && door.Room == room) - door.SetDoorway(true); - } - - break; + foreach (OpenableDoor door in shipStatus.AllDoors) + if (door.Id != openedDoorId && door.Room == room) + door.SetDoorway(true); } + + break; } - - DoorsProgressing = false; - break; } + + DoorsProgressing = false; + break; } } + } - public static void SwitchSystemRepair(byte playerId, SwitchSystem switchSystem, byte amount) - { - if (!FixesElectrical.GetBool() || Main.PlayerStates[playerId].Role is not SabotageMaster sm) return; + public static void SwitchSystemRepair(byte playerId, SwitchSystem switchSystem, byte amount) + { + if (!FixesElectrical.GetBool() || Main.PlayerStates[playerId].Role is not SabotageMaster sm) return; - if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat()) return; + if (SkillLimit.GetFloat() > 0 && sm.UsedSkillCount + UsesUsedWhenFixingLightsOrComms.GetFloat() - 1 >= SkillLimit.GetFloat()) return; - if (amount.HasBit(SwitchSystem.DamageSystem)) return; + if (amount.HasBit(SwitchSystem.DamageSystem)) return; - int fixbit = 1 << amount; - switchSystem.ActualSwitches = (byte)(switchSystem.ExpectedSwitches ^ fixbit); + int fixbit = 1 << amount; + switchSystem.ActualSwitches = (byte)(switchSystem.ExpectedSwitches ^ fixbit); - sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat(); - sm.SendRPC(); - } + sm.UsedSkillCount += UsesUsedWhenFixingLightsOrComms.GetFloat(); + sm.SendRPC(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Safeguard.cs b/Roles/Crewmate/Safeguard.cs index dd21fac2c..de2aac108 100644 --- a/Roles/Crewmate/Safeguard.cs +++ b/Roles/Crewmate/Safeguard.cs @@ -1,79 +1,78 @@ using System; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Safeguard : RoleBase { - public class Safeguard : RoleBase + public static bool On; + + private static OptionItem ShieldDuration; + private static OptionItem MinTasks; + private byte SafeguardId; + + private float Timer; + private bool Shielded => Timer > 0; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + const TabGroup tab = TabGroup.CrewmateRoles; + const CustomRoles role = CustomRoles.Safeguard; + var id = 645500; - private static OptionItem ShieldDuration; - private static OptionItem MinTasks; - private byte SafeguardId; + Options.SetupRoleOptions(id++, tab, role); - private float Timer; - private bool Shielded => Timer > 0; + ShieldDuration = new FloatOptionItem(++id, "AidDur", new(0.5f, 60f, 0.5f), 5f, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + MinTasks = new IntegerOptionItem(++id, "MinTasksToActivateAbility", new(1, 10, 1), 3, tab) + .SetParent(Options.CustomRoleSpawnChances[role]); - public override void SetupCustomOption() + Options.OverrideTasksData.Create(++id, tab, role); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + Timer = 0; + SafeguardId = playerId; + } + + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + if (!pc.IsAlive()) return; + + if (completedTaskCount + 1 >= MinTasks.GetInt()) Timer += ShieldDuration.GetFloat(); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive()) return; + + if (Shielded) { - const TabGroup tab = TabGroup.CrewmateRoles; - const CustomRoles role = CustomRoles.Safeguard; - var id = 645500; - - Options.SetupRoleOptions(id++, tab, role); - - ShieldDuration = new FloatOptionItem(++id, "AidDur", new(0.5f, 60f, 0.5f), 5f, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - MinTasks = new IntegerOptionItem(++id, "MinTasksToActivateAbility", new(1, 10, 1), 3, tab) - .SetParent(Options.CustomRoleSpawnChances[role]); - - Options.OverrideTasksData.Create(++id, tab, role); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - Timer = 0; - SafeguardId = playerId; - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - if (!pc.IsAlive()) return; - - if (completedTaskCount + 1 >= MinTasks.GetInt()) Timer += ShieldDuration.GetFloat(); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive()) return; - - if (Shielded) - { - Timer -= Time.fixedDeltaTime; - if (Timer <= 0) Timer = 0; - } - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !Shielded; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != SafeguardId || meeting || (seer.IsModClient() && !hud) || !Shielded) return string.Empty; - - return seer.IsHost() ? string.Format(Translator.GetString("SafeguardSuffixTimer"), (int)Math.Ceiling(Timer)) : Translator.GetString("SafeguardSuffix"); + Timer -= Time.fixedDeltaTime; + if (Timer <= 0) Timer = 0; } } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !Shielded; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != SafeguardId || meeting || (seer.IsModClient() && !hud) || !Shielded) return string.Empty; + + return seer.IsHost() ? string.Format(Translator.GetString("SafeguardSuffixTimer"), (int)Math.Ceiling(Timer)) : Translator.GetString("SafeguardSuffix"); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Scout.cs b/Roles/Crewmate/Scout.cs index 75b67b50f..795eb7861 100644 --- a/Roles/Crewmate/Scout.cs +++ b/Roles/Crewmate/Scout.cs @@ -6,179 +6,178 @@ using UnityEngine; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Scout : RoleBase { - public class Scout : RoleBase + private const int Id = 8300; + private static List PlayerIdList = []; + + private static OptionItem TrackLimitOpt; + private static OptionItem OptionCanSeeLastRoomInMeeting; + private static OptionItem CanGetColoredArrow; + public static OptionItem HideVote; + public static OptionItem TrackerAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem CancelVote; + + public static bool CanSeeLastRoomInMeeting; + + private static Dictionary> TrackerTarget = []; + private byte TrackerId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 8300; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Scout); - private static OptionItem TrackLimitOpt; - private static OptionItem OptionCanSeeLastRoomInMeeting; - private static OptionItem CanGetColoredArrow; - public static OptionItem HideVote; - public static OptionItem TrackerAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem CancelVote; + TrackLimitOpt = new IntegerOptionItem(Id + 5, "DivinatorSkillLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]) + .SetValueFormat(OptionFormat.Times); - public static bool CanSeeLastRoomInMeeting; + CanGetColoredArrow = new BooleanOptionItem(Id + 6, "TrackerCanGetArrowColor", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]); - private static Dictionary> TrackerTarget = []; - private byte TrackerId; + OptionCanSeeLastRoomInMeeting = new BooleanOptionItem(Id + 7, "EvilTrackerCanSeeLastRoomInMeeting", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]); - public override bool IsEnable => PlayerIdList.Count > 0; + HideVote = new BooleanOptionItem(Id + 8, "TrackerHideVote", false, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]); - public override void SetupCustomOption() + TrackerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 9, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 3, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]) + .SetValueFormat(OptionFormat.Times); + + CancelVote = CreateVoteCancellingUseSetting(Id + 4, CustomRoles.Scout, TabGroup.CrewmateRoles); + } + + public override void Init() + { + PlayerIdList = []; + TrackerTarget = []; + CanSeeLastRoomInMeeting = OptionCanSeeLastRoomInMeeting.GetBool(); + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(TrackLimitOpt.GetInt()); + TrackerTarget.Add(playerId, []); + TrackerId = playerId; + } + + public static void SendRPC(byte trackerId = byte.MaxValue, byte targetId = byte.MaxValue) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetTrackerTarget, SendOption.Reliable); + writer.Write(trackerId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte trackerId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + + Utils.GetPlayerById(trackerId).RpcRemoveAbilityUse(); + + TrackerTarget[trackerId].Add(targetId); + } + + public static string GetTargetMark(PlayerControl seer, PlayerControl target) + { + return !(seer == null || target == null) && TrackerTarget.ContainsKey(seer.PlayerId) && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) ? Utils.ColorString(seer.GetRoleColor(), "◀") : string.Empty; + } + + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null || player.GetAbilityUseLimit() < 1f || player.PlayerId == target.PlayerId || TrackerTarget[player.PlayerId].Contains(target.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; + + player.RpcRemoveAbilityUse(); + + TrackerTarget[player.PlayerId].Add(target.PlayerId); + TargetArrow.Add(player.PlayerId, target.PlayerId); + + SendRPC(player.PlayerId, target.PlayerId); + + Main.DontCancelVoteList.Add(player.PlayerId); + return true; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || seer.PlayerId != TrackerId) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + if (!TrackerTarget.ContainsKey(seer.PlayerId)) return string.Empty; + + if (GameStates.IsMeeting) return string.Empty; + + return TrackerTarget[seer.PlayerId].Aggregate(string.Empty, (current, trackTarget) => current + Utils.ColorString(CanGetColoredArrow.GetBool() ? Main.PlayerColors[trackTarget] : Color.white, TargetArrow.GetArrows(seer, trackTarget))) + LocateArrow.GetArrows(seer); + } + + public static bool IsTrackTarget(PlayerControl seer, PlayerControl target) + { + return seer.IsAlive() && PlayerIdList.Contains(seer.PlayerId) + && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) + && target.IsAlive(); + } + + public static string GetArrowAndLastRoom(PlayerControl seer, PlayerControl target) + { + if (seer == null || target == null) return string.Empty; + + Color roleColor = Utils.GetRoleColor(CustomRoles.Scout); + string text = Utils.ColorString(roleColor, TargetArrow.GetArrows(seer, target.PlayerId)); + text += Utils.ColorString(roleColor, LocateArrow.GetArrows(seer)); + + PlainShipRoom room = Main.PlayerStates[target.PlayerId].LastRoom; + + if (room == null) + text += Utils.ColorString(Color.gray, "@" + GetString("FailToTrack")); + else + text += Utils.ColorString(roleColor, "@" + GetString(room.RoomId.ToString())); + + return text; + } + + public static void OnPlayerDeath(PlayerControl player) + { + if (player == null) return; + + foreach (KeyValuePair> kvp in TrackerTarget) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Scout); - - TrackLimitOpt = new IntegerOptionItem(Id + 5, "DivinatorSkillLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]) - .SetValueFormat(OptionFormat.Times); - - CanGetColoredArrow = new BooleanOptionItem(Id + 6, "TrackerCanGetArrowColor", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]); - - OptionCanSeeLastRoomInMeeting = new BooleanOptionItem(Id + 7, "EvilTrackerCanSeeLastRoomInMeeting", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]); - - HideVote = new BooleanOptionItem(Id + 8, "TrackerHideVote", false, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]); - - TrackerAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 9, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 3, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scout]) - .SetValueFormat(OptionFormat.Times); - - CancelVote = CreateVoteCancellingUseSetting(Id + 4, CustomRoles.Scout, TabGroup.CrewmateRoles); - } - - public override void Init() - { - PlayerIdList = []; - TrackerTarget = []; - CanSeeLastRoomInMeeting = OptionCanSeeLastRoomInMeeting.GetBool(); - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(TrackLimitOpt.GetInt()); - TrackerTarget.Add(playerId, []); - TrackerId = playerId; - } - - public static void SendRPC(byte trackerId = byte.MaxValue, byte targetId = byte.MaxValue) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetTrackerTarget, SendOption.Reliable); - writer.Write(trackerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte trackerId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - - Utils.GetPlayerById(trackerId).RpcRemoveAbilityUse(); - - TrackerTarget[trackerId].Add(targetId); - } - - public static string GetTargetMark(PlayerControl seer, PlayerControl target) - { - return !(seer == null || target == null) && TrackerTarget.ContainsKey(seer.PlayerId) && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) ? Utils.ColorString(seer.GetRoleColor(), "◀") : string.Empty; - } - - public override bool OnVote(PlayerControl player, PlayerControl target) - { - if (player == null || target == null || player.GetAbilityUseLimit() < 1f || player.PlayerId == target.PlayerId || TrackerTarget[player.PlayerId].Contains(target.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - - player.RpcRemoveAbilityUse(); - - TrackerTarget[player.PlayerId].Add(target.PlayerId); - TargetArrow.Add(player.PlayerId, target.PlayerId); - - SendRPC(player.PlayerId, target.PlayerId); - - Main.DontCancelVoteList.Add(player.PlayerId); - return true; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || seer.PlayerId != TrackerId) return string.Empty; - - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (!TrackerTarget.ContainsKey(seer.PlayerId)) return string.Empty; - - if (GameStates.IsMeeting) return string.Empty; - - return TrackerTarget[seer.PlayerId].Aggregate(string.Empty, (current, trackTarget) => current + Utils.ColorString(CanGetColoredArrow.GetBool() ? Main.PlayerColors[trackTarget] : Color.white, TargetArrow.GetArrows(seer, trackTarget))) + LocateArrow.GetArrows(seer); - } - - public static bool IsTrackTarget(PlayerControl seer, PlayerControl target) - { - return seer.IsAlive() && PlayerIdList.Contains(seer.PlayerId) - && TrackerTarget[seer.PlayerId].Contains(target.PlayerId) - && target.IsAlive(); - } - - public static string GetArrowAndLastRoom(PlayerControl seer, PlayerControl target) - { - if (seer == null || target == null) return string.Empty; - - Color roleColor = Utils.GetRoleColor(CustomRoles.Scout); - string text = Utils.ColorString(roleColor, TargetArrow.GetArrows(seer, target.PlayerId)); - text += Utils.ColorString(roleColor, LocateArrow.GetArrows(seer)); - - PlainShipRoom room = Main.PlayerStates[target.PlayerId].LastRoom; - - if (room == null) - text += Utils.ColorString(Color.gray, "@" + GetString("FailToTrack")); - else - text += Utils.ColorString(roleColor, "@" + GetString(room.RoomId.ToString())); - - return text; - } - - public static void OnPlayerDeath(PlayerControl player) - { - if (player == null) return; - - foreach (KeyValuePair> kvp in TrackerTarget) + if (kvp.Value.Contains(player.PlayerId)) { - if (kvp.Value.Contains(player.PlayerId)) - { - kvp.Value.Remove(player.PlayerId); - TargetArrow.Remove(kvp.Key, player.PlayerId); - LocateArrow.Add(kvp.Key, player.Pos()); - } + kvp.Value.Remove(player.PlayerId); + TargetArrow.Remove(kvp.Key, player.PlayerId); + LocateArrow.Add(kvp.Key, player.Pos()); } } + } - public override void AfterMeetingTasks() + public override void AfterMeetingTasks() + { + foreach (KeyValuePair> kvp in TrackerTarget) { - foreach (KeyValuePair> kvp in TrackerTarget) + LocateArrow.RemoveAllTarget(kvp.Key); + + foreach (byte id in kvp.Value.ToArray()) { - LocateArrow.RemoveAllTarget(kvp.Key); + PlayerControl pc = Utils.GetPlayerById(id); - foreach (byte id in kvp.Value.ToArray()) + if (pc == null || !pc.IsAlive()) { - PlayerControl pc = Utils.GetPlayerById(id); - - if (pc == null || !pc.IsAlive()) - { - kvp.Value.Remove(id); - TargetArrow.Remove(kvp.Key, id); - } + kvp.Value.Remove(id); + TargetArrow.Remove(kvp.Key, id); } } } diff --git a/Roles/Crewmate/SecurityGuard.cs b/Roles/Crewmate/SecurityGuard.cs index 57ba4523a..090704ea4 100644 --- a/Roles/Crewmate/SecurityGuard.cs +++ b/Roles/Crewmate/SecurityGuard.cs @@ -2,117 +2,116 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class SecurityGuard : RoleBase { - internal class SecurityGuard : RoleBase + public static Dictionary BlockSabo = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary BlockSabo = []; + SetupRoleOptions(6860, TabGroup.CrewmateRoles, CustomRoles.SecurityGuard); - public static bool On; - public override bool IsEnable => On; + SecurityGuardSkillCooldown = new FloatOptionItem(6862, "SecurityGuardSkillCooldown", new(0f, 180f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + SecurityGuardSkillDuration = new FloatOptionItem(6863, "SecurityGuardSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) + .SetValueFormat(OptionFormat.Seconds); + + SecurityGuardSkillMaxOfUseage = new IntegerOptionItem(6866, "AbilityUseLimit", new(0, 180, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) + .SetValueFormat(OptionFormat.Times); + + SecurityGuardAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(6867, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) + .SetValueFormat(OptionFormat.Times); + + SecurityGuardAbilityChargesWhenFinishedTasks = new FloatOptionItem(6868, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(SecurityGuardSkillMaxOfUseage.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerInVentMaxTime = 1; + AURoleOptions.EngineerCooldown = SecurityGuardSkillCooldown.GetFloat(); + } + + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + + ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, BlockSabo.ContainsKey(playerId))); + ProgressText.Append(Utils.GetTaskCount(playerId, comms)); + + return ProgressText.ToString(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("SecurityGuardVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("SecurityGuardVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + Guard(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + Guard(pc); + } + + private static void Guard(PlayerControl pc) + { + if (BlockSabo.ContainsKey(pc.PlayerId)) return; + + if (pc.GetAbilityUseLimit() >= 1) { - SetupRoleOptions(6860, TabGroup.CrewmateRoles, CustomRoles.SecurityGuard); - - SecurityGuardSkillCooldown = new FloatOptionItem(6862, "SecurityGuardSkillCooldown", new(0f, 180f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) - .SetValueFormat(OptionFormat.Seconds); - - SecurityGuardSkillDuration = new FloatOptionItem(6863, "SecurityGuardSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) - .SetValueFormat(OptionFormat.Seconds); - - SecurityGuardSkillMaxOfUseage = new IntegerOptionItem(6866, "AbilityUseLimit", new(0, 180, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) - .SetValueFormat(OptionFormat.Times); - - SecurityGuardAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(6867, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) - .SetValueFormat(OptionFormat.Times); - - SecurityGuardAbilityChargesWhenFinishedTasks = new FloatOptionItem(6868, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SecurityGuard]) - .SetValueFormat(OptionFormat.Times); + BlockSabo.Remove(pc.PlayerId); + BlockSabo.Add(pc.PlayerId, Utils.TimeStamp); + pc.Notify(Translator.GetString("SecurityGuardSkillInUse"), SecurityGuardSkillDuration.GetFloat()); + pc.RpcRemoveAbilityUse(); } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override void Add(byte playerId) + public override void OnFixedUpdate(PlayerControl player) + { + byte playerId = player.PlayerId; + + if (BlockSabo.TryGetValue(playerId, out long stime) && stime + SecurityGuardSkillDuration.GetInt() < Utils.TimeStamp) { - On = true; - playerId.SetAbilityUseLimit(SecurityGuardSkillMaxOfUseage.GetInt()); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerInVentMaxTime = 1; - AURoleOptions.EngineerCooldown = SecurityGuardSkillCooldown.GetFloat(); - } - - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); - - ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, BlockSabo.ContainsKey(playerId))); - ProgressText.Append(Utils.GetTaskCount(playerId, comms)); - - return ProgressText.ToString(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("SecurityGuardVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("SecurityGuardVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - Guard(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - Guard(pc); - } - - private static void Guard(PlayerControl pc) - { - if (BlockSabo.ContainsKey(pc.PlayerId)) return; - - if (pc.GetAbilityUseLimit() >= 1) - { - BlockSabo.Remove(pc.PlayerId); - BlockSabo.Add(pc.PlayerId, Utils.TimeStamp); - pc.Notify(Translator.GetString("SecurityGuardSkillInUse"), SecurityGuardSkillDuration.GetFloat()); - pc.RpcRemoveAbilityUse(); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override void OnFixedUpdate(PlayerControl player) - { - byte playerId = player.PlayerId; - - if (BlockSabo.TryGetValue(playerId, out long stime) && stime + SecurityGuardSkillDuration.GetInt() < Utils.TimeStamp) - { - BlockSabo.Remove(playerId); - player.RpcResetAbilityCooldown(); - player.Notify(Translator.GetString("SecurityGuardSkillStop")); - } - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + BlockSabo.Remove(playerId); + player.RpcResetAbilityCooldown(); + player.Notify(Translator.GetString("SecurityGuardSkillStop")); } } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Sentinel.cs b/Roles/Crewmate/Sentinel.cs index ce8a1eb6f..ebd8cfc52 100644 --- a/Roles/Crewmate/Sentinel.cs +++ b/Roles/Crewmate/Sentinel.cs @@ -5,206 +5,205 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRadius, PlayerControl sentinel = null, bool isPatrolling = false, Vector2? startingPosition = null, long patrolStartTimeStamp = 0) { - public class PatrollingState(byte sentinelId, int patrolDuration, float patrolRadius, PlayerControl sentinel = null, bool isPatrolling = false, Vector2? startingPosition = null, long patrolStartTimeStamp = 0) + private List LastNearbyKillers = []; + private long LastUpdate; + + public byte SentinelId => sentinelId; + + public PlayerControl Sentinel { get; private set; } = sentinel; + + public bool IsPatrolling { get; private set; } = isPatrolling; + + private Vector2 StartingPosition { get; set; } = startingPosition ?? Vector2.zero; + + private long PatrolStartTimeStamp { get; set; } = patrolStartTimeStamp; + + private int PatrolDuration => patrolDuration; + + private float PatrolRadius => patrolRadius; + + public PlayerControl[] NearbyKillers => GetPlayersInRadius(PatrolRadius, StartingPosition).Where(x => !x.Is(Team.Crewmate) && (!Sentinel.IsMadmate() || !x.Is(Team.Impostor)) && SentinelId != x.PlayerId).ToArray(); + + public void SetPlayer() { - private List LastNearbyKillers = []; - private long LastUpdate; - - public byte SentinelId => sentinelId; - - public PlayerControl Sentinel { get; private set; } = sentinel; - - public bool IsPatrolling { get; private set; } = isPatrolling; - - private Vector2 StartingPosition { get; set; } = startingPosition ?? Vector2.zero; - - private long PatrolStartTimeStamp { get; set; } = patrolStartTimeStamp; - - private int PatrolDuration => patrolDuration; - - private float PatrolRadius => patrolRadius; - - public PlayerControl[] NearbyKillers => GetPlayersInRadius(PatrolRadius, StartingPosition).Where(x => !x.Is(Team.Crewmate) && (!Sentinel.IsMadmate() || !x.Is(Team.Impostor)) && SentinelId != x.PlayerId).ToArray(); - - public void SetPlayer() - { - Sentinel = GetPlayerById(SentinelId); - } - - public void StartPatrolling() - { - if (IsPatrolling) return; - - IsPatrolling = true; - StartingPosition = Sentinel.Pos(); - PatrolStartTimeStamp = TimeStamp; - foreach (PlayerControl pc in NearbyKillers) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), PatrolDuration)); - - Sentinel.MarkDirtySettings(); - } - - public void Update() - { - if (!IsPatrolling) return; - - long now = TimeStamp; - if (LastUpdate >= now) return; - - LastUpdate = now; - - if (PatrolStartTimeStamp + PatrolDuration < now) FinishPatrolling(); - } - - public void OnCheckPlayerPosition(PlayerControl pc) - { - if (!IsPatrolling) return; - - long now = TimeStamp; - if (LastUpdate >= now) return; - - LastUpdate = now; - - PlayerControl[] killers = NearbyKillers; - - bool nowInRange = killers.Any(x => x.PlayerId == pc.PlayerId); - bool wasInRange = LastNearbyKillers.Contains(pc.PlayerId); - - long timeLeft = PatrolDuration - (TimeStamp - PatrolStartTimeStamp); - if (wasInRange && !nowInRange) pc.Notify(GetString("KillerEscapedFromSentinel")); - - if (nowInRange && timeLeft >= 0) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), timeLeft)); - - LastNearbyKillers = killers.Select(x => x.PlayerId).ToList(); - } - - public void FinishPatrolling() - { - IsPatrolling = false; - if (!GameStates.IsInTask) return; - - foreach (PlayerControl pc in NearbyKillers) pc.Suicide(realKiller: Sentinel); - - Sentinel.MarkDirtySettings(); - } + Sentinel = GetPlayerById(SentinelId); } - internal class Sentinel : RoleBase + public void StartPatrolling() { - public static OptionItem PatrolCooldown; - private static OptionItem PatrolDuration; - public static OptionItem LoweredVision; - private static OptionItem PatrolRadius; - private static int Id => 64430; + if (IsPatrolling) return; - public static List PatrolStates { get; } = []; + IsPatrolling = true; + StartingPosition = Sentinel.Pos(); + PatrolStartTimeStamp = TimeStamp; + foreach (PlayerControl pc in NearbyKillers) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), PatrolDuration)); - public override bool IsEnable => PatrolStates.Count > 0 || Randomizer.Exists; + Sentinel.MarkDirtySettings(); + } - public override void SetupCustomOption() + public void Update() + { + if (!IsPatrolling) return; + + long now = TimeStamp; + if (LastUpdate >= now) return; + + LastUpdate = now; + + if (PatrolStartTimeStamp + PatrolDuration < now) FinishPatrolling(); + } + + public void OnCheckPlayerPosition(PlayerControl pc) + { + if (!IsPatrolling) return; + + long now = TimeStamp; + if (LastUpdate >= now) return; + + LastUpdate = now; + + PlayerControl[] killers = NearbyKillers; + + bool nowInRange = killers.Any(x => x.PlayerId == pc.PlayerId); + bool wasInRange = LastNearbyKillers.Contains(pc.PlayerId); + + long timeLeft = PatrolDuration - (TimeStamp - PatrolStartTimeStamp); + if (wasInRange && !nowInRange) pc.Notify(GetString("KillerEscapedFromSentinel")); + + if (nowInRange && timeLeft >= 0) pc.Notify(string.Format(GetString("KillerNotifyPatrol"), timeLeft)); + + LastNearbyKillers = killers.Select(x => x.PlayerId).ToList(); + } + + public void FinishPatrolling() + { + IsPatrolling = false; + if (!GameStates.IsInTask) return; + + foreach (PlayerControl pc in NearbyKillers) pc.Suicide(realKiller: Sentinel); + + Sentinel.MarkDirtySettings(); + } +} + +internal class Sentinel : RoleBase +{ + public static OptionItem PatrolCooldown; + private static OptionItem PatrolDuration; + public static OptionItem LoweredVision; + private static OptionItem PatrolRadius; + private static int Id => 64430; + + public static List PatrolStates { get; } = []; + + public override bool IsEnable => PatrolStates.Count > 0 || Randomizer.Exists; + + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Sentinel); + PatrolCooldown = CreateCDSetting(Id + 2, TabGroup.CrewmateRoles, CustomRoles.Sentinel); + + PatrolDuration = new IntegerOptionItem(Id + 3, "SentinelPatrolDuration", new(1, 90, 1), 5, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sentinel]) + .SetValueFormat(OptionFormat.Seconds); + + LoweredVision = new FloatOptionItem(Id + 4, "FFA_LowerVision", new(0.05f, 3f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sentinel]) + .SetValueFormat(OptionFormat.Multiplier); + + PatrolRadius = new FloatOptionItem(Id + 5, "SentinelPatrolRadius", new(0.1f, 25f, 0.1f), 5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sentinel]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Init() + { + PatrolStates.Clear(); + } + + public override void Add(byte playerId) + { + var newPatrolState = new PatrollingState(playerId, PatrolDuration.GetInt(), PatrolRadius.GetFloat()); + PatrolStates.Add(newPatrolState); + LateTask.New(newPatrolState.SetPlayer, 8f, log: false); + } + + private static PatrollingState GetPatrollingState(byte playerId) + { + return PatrolStates.FirstOrDefault(x => x.SentinelId == playerId) ?? new(playerId, PatrolDuration.GetInt(), PatrolRadius.GetInt()); + } + + public static bool IsPatrolling(byte playerId) + { + return GetPatrollingState(playerId)?.IsPatrolling == true; + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + GetPatrollingState(pc.PlayerId)?.StartPatrolling(); + } + + public override void OnPet(PlayerControl pc) + { + GetPatrollingState(pc.PlayerId)?.StartPatrolling(); + } + + public static bool OnAnyoneCheckMurder(PlayerControl killer) + { + if (killer == null || !PatrolStates.Any(x => x.IsPatrolling)) return true; + + foreach (PatrollingState state in PatrolStates) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Sentinel); - PatrolCooldown = CreateCDSetting(Id + 2, TabGroup.CrewmateRoles, CustomRoles.Sentinel); + if (!state.IsPatrolling) continue; - PatrolDuration = new IntegerOptionItem(Id + 3, "SentinelPatrolDuration", new(1, 90, 1), 5, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sentinel]) - .SetValueFormat(OptionFormat.Seconds); - - LoweredVision = new FloatOptionItem(Id + 4, "FFA_LowerVision", new(0.05f, 3f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sentinel]) - .SetValueFormat(OptionFormat.Multiplier); - - PatrolRadius = new FloatOptionItem(Id + 5, "SentinelPatrolRadius", new(0.1f, 25f, 0.1f), 5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sentinel]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Init() - { - PatrolStates.Clear(); - } - - public override void Add(byte playerId) - { - var newPatrolState = new PatrollingState(playerId, PatrolDuration.GetInt(), PatrolRadius.GetFloat()); - PatrolStates.Add(newPatrolState); - LateTask.New(newPatrolState.SetPlayer, 8f, log: false); - } - - private static PatrollingState GetPatrollingState(byte playerId) - { - return PatrolStates.FirstOrDefault(x => x.SentinelId == playerId) ?? new(playerId, PatrolDuration.GetInt(), PatrolRadius.GetInt()); - } - - public static bool IsPatrolling(byte playerId) - { - return GetPatrollingState(playerId)?.IsPatrolling == true; - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - GetPatrollingState(pc.PlayerId)?.StartPatrolling(); - } - - public override void OnPet(PlayerControl pc) - { - GetPatrollingState(pc.PlayerId)?.StartPatrolling(); - } - - public static bool OnAnyoneCheckMurder(PlayerControl killer) - { - if (killer == null || !PatrolStates.Any(x => x.IsPatrolling)) return true; - - foreach (PatrollingState state in PatrolStates) + if (state.NearbyKillers.Any(x => x.PlayerId == killer.PlayerId)) { - if (!state.IsPatrolling) continue; - - if (state.NearbyKillers.Any(x => x.PlayerId == killer.PlayerId)) - { - state.Sentinel.RpcCheckAndMurder(killer); - return false; - } + state.Sentinel.RpcCheckAndMurder(killer); + return false; } - - return true; } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + return true; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = PatrolCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable) return; + + foreach (PatrollingState state in PatrolStates) state.Update(); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (!IsEnable) return; + + foreach (PatrollingState state in PatrolStates) state.OnCheckPlayerPosition(pc); + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + LateTask.New(() => { - AURoleOptions.EngineerCooldown = PatrolCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } + foreach (PatrollingState state in PatrolStates) + if (state.IsPatrolling) + state.FinishPatrolling(); + }, 0.1f, "SentinelFinishPatrol"); + } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable) return; - - foreach (PatrollingState state in PatrolStates) state.Update(); - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (!IsEnable) return; - - foreach (PatrollingState state in PatrolStates) state.OnCheckPlayerPosition(pc); - } - - public override void OnReportDeadBody() - { - if (!IsEnable) return; - - LateTask.New(() => - { - foreach (PatrollingState state in PatrolStates) - if (state.IsPatrolling) - state.FinishPatrolling(); - }, 0.1f, "SentinelFinishPatrol"); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/Sentry.cs b/Roles/Crewmate/Sentry.cs index eeda29331..8133beeda 100644 --- a/Roles/Crewmate/Sentry.cs +++ b/Roles/Crewmate/Sentry.cs @@ -4,321 +4,320 @@ using System.Linq; using EHR.Modules; using HarmonyLib; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Sentry : RoleBase { - internal class Sentry : RoleBase + public static bool On; + + public static OptionItem ShowInfoCooldown; + private static OptionItem ShowInfoDuration; + private static OptionItem PlayersKnowAboutCamera; + private static OptionItem CannotSeeInfoDuringComms; + private static OptionItem UsableDevicesForInfoView; + private static OptionItem AdditionalDevicesForInfoView; + private static OptionItem AbilityUseLimit; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private static readonly Dictionary TeamsCanSeeInfo = []; + + private static Vector2[] AvailableDevices = []; + + private readonly Dictionary LastInfoSend = []; + + private readonly HashSet LastNotified = []; + + private HashSet DeadBodiesInRoom; + public PlainShipRoom MonitoredRoom; + + private PlayerControl SentryPC; + + private HashSet UsingDevice = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 11370; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Sentry); - public static OptionItem ShowInfoCooldown; - private static OptionItem ShowInfoDuration; - private static OptionItem PlayersKnowAboutCamera; - private static OptionItem CannotSeeInfoDuringComms; - private static OptionItem UsableDevicesForInfoView; - private static OptionItem AdditionalDevicesForInfoView; - private static OptionItem AbilityUseLimit; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - private static readonly Dictionary TeamsCanSeeInfo = []; + ShowInfoCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(1, 60, 1), 15, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]) + .SetValueFormat(OptionFormat.Seconds); - private static Vector2[] AvailableDevices = []; + ShowInfoDuration = new IntegerOptionItem(++id, "Sentry.ShowInfoDuration", new(1, 60, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]) + .SetValueFormat(OptionFormat.Seconds); - private readonly Dictionary LastInfoSend = []; + PlayersKnowAboutCamera = new BooleanOptionItem(++id, "Sentry.PlayersKnowAboutCamera", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - private readonly HashSet LastNotified = []; + CannotSeeInfoDuringComms = new BooleanOptionItem(++id, "Sentry.CannotSeeInfoDuringComms", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - private HashSet DeadBodiesInRoom; - public PlainShipRoom MonitoredRoom; + UsableDevicesForInfoView = new StringOptionItem(++id, "Sentry.UsableDevicesForInfoView", Enum.GetNames(), 0, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - private PlayerControl SentryPC; + AdditionalDevicesForInfoView = new StringOptionItem(++id, "Sentry.AdditionalDevicesForInfoView", Enum.GetNames(), 0, TabGroup.CrewmateRoles) + .SetParent(UsableDevicesForInfoView); - private HashSet UsingDevice = []; - public override bool IsEnable => On; - - public override void SetupCustomOption() + Enum.GetValues().Do(x => { - var id = 11370; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Sentry); - - ShowInfoCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(1, 60, 1), 15, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]) - .SetValueFormat(OptionFormat.Seconds); - - ShowInfoDuration = new IntegerOptionItem(++id, "Sentry.ShowInfoDuration", new(1, 60, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]) - .SetValueFormat(OptionFormat.Seconds); - - PlayersKnowAboutCamera = new BooleanOptionItem(++id, "Sentry.PlayersKnowAboutCamera", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - - CannotSeeInfoDuringComms = new BooleanOptionItem(++id, "Sentry.CannotSeeInfoDuringComms", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - - UsableDevicesForInfoView = new StringOptionItem(++id, "Sentry.UsableDevicesForInfoView", Enum.GetNames(), 0, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - - AdditionalDevicesForInfoView = new StringOptionItem(++id, "Sentry.AdditionalDevicesForInfoView", Enum.GetNames(), 0, TabGroup.CrewmateRoles) + TeamsCanSeeInfo[x] = new BooleanOptionItem(++id, "Sentry.TeamsCanSeeInfo." + x, true, TabGroup.CrewmateRoles) .SetParent(UsableDevicesForInfoView); + }); - Enum.GetValues().Do(x => + AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 0, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); + + AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); + } + + public override void Add(byte playerId) + { + On = true; + SentryPC = Utils.GetPlayerById(playerId); + MonitoredRoom = null; + DeadBodiesInRoom = []; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + UsingDevice = []; + } + + public override void Init() + { + On = false; + + AvailableDevices = DisableDevice.DevicePos.Where(x => + { + bool correctMap = x.Key.Contains(Main.CurrentMap.ToString(), StringComparison.OrdinalIgnoreCase); + var devicesOpt = (UsableDevicesStrings)UsableDevicesForInfoView.GetValue(); + + bool enabled = devicesOpt switch { - TeamsCanSeeInfo[x] = new BooleanOptionItem(++id, "Sentry.TeamsCanSeeInfo." + x, true, TabGroup.CrewmateRoles) - .SetParent(UsableDevicesForInfoView); - }); - - AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 0, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - - AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sentry]); - } - - public override void Add(byte playerId) - { - On = true; - SentryPC = Utils.GetPlayerById(playerId); - MonitoredRoom = null; - DeadBodiesInRoom = []; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - UsingDevice = []; - } - - public override void Init() - { - On = false; - - AvailableDevices = DisableDevice.DevicePos.Where(x => - { - bool correctMap = x.Key.Contains(Main.CurrentMap.ToString(), StringComparison.OrdinalIgnoreCase); - var devicesOpt = (UsableDevicesStrings)UsableDevicesForInfoView.GetValue(); - - bool enabled = devicesOpt switch - { - UsableDevicesStrings.None => false, - UsableDevicesStrings.Cameras => x.Key.Contains("Camera") && !x.Key.Contains("Fungle"), - UsableDevicesStrings.Admin => x.Key.Contains("Admin"), - UsableDevicesStrings.CamerasAndAdmin => x.Key.Contains("Camera") || x.Key.Contains("Admin"), - _ => false - }; - - if (!enabled && devicesOpt != UsableDevicesStrings.None) - { - enabled |= (AdditionalDevicesStrings)AdditionalDevicesForInfoView.GetValue() switch - { - AdditionalDevicesStrings.None => false, - AdditionalDevicesStrings.DoorLog => x.Key.Contains("DoorLog"), - AdditionalDevicesStrings.Binoculars => x.Key.Contains("Camera") && x.Key.Contains("Fungle"), - AdditionalDevicesStrings.DoorLogAndBinoculars => x.Key.Contains("DoorLog") || (x.Key.Contains("Camera") && x.Key.Contains("Fungle")), - _ => false - }; - } - - return correctMap && enabled; - }).Select(x => x.Value).ToArray(); - } - - public override void OnPet(PlayerControl pc) - { - PlainShipRoom room = pc.GetPlainShipRoom(); - bool hasntChosenRoom = MonitoredRoom == null || MonitoredRoom == default || MonitoredRoom == default(PlainShipRoom); - - if (room == default(PlainShipRoom) && hasntChosenRoom) - { - pc.AddAbilityCD(3); - pc.Notify(Translator.GetString("Sentry.Notify.InvalidRoom")); - return; - } - - if (hasntChosenRoom) - { - MonitoredRoom = room; - Utils.SendRPC(CustomRPC.SyncSentry, pc.PlayerId); - } - else - DisplayRoomInfo(pc, false); - } - - private void DisplayRoomInfo(PlayerControl pc, bool fromDevice) - { - if (CannotSeeInfoDuringComms.GetBool() && Utils.IsActive(SystemTypes.Comms)) return; - - if (!fromDevice) - { - if (pc.GetAbilityUseLimit() < 1) return; - pc.RpcRemoveAbilityUse(); - } - - string roomName = Translator.GetString(MonitoredRoom.RoomId.ToString()); - string players = Main.AllAlivePlayerControls.Where(IsInMonitoredRoom).Select(x => Utils.GetPlayerById(x.shapeshiftTargetPlayerId) ?? x).Select(x => Utils.ColorString(Main.PlayerColors[x.PlayerId], x.GetRealName())).Join(); - string bodies = GetColoredNames(DeadBodiesInRoom); - - string noDataString = Translator.GetString("Sentry.Notify.Info.NoData"); - if (players.Length == 0) players = noDataString; - if (bodies.Length == 0) bodies = noDataString; - - pc.Notify(string.Format(Translator.GetString("Sentry.Notify.Info"), roomName, players, bodies), ShowInfoDuration.GetInt(), overrideAll: fromDevice); - return; - - static string GetColoredNames(IEnumerable ids) - { - return ids.Where(x => Utils.GetPlayerById(x) != null).Select(x => Utils.ColorString(Main.PlayerColors[x], Utils.GetPlayerById(x).GetRealName())).Join(); - } - } - - private bool IsInMonitoredRoom(PlayerControl pc) - { - return MonitoredRoom != null && SentryPC.IsAlive() && pc.GetPlainShipRoom() == MonitoredRoom; - } - - public void OnAnyoneShapeshiftLoop(PlayerControl shapeshifter, PlayerControl target) - { - if (IsInMonitoredRoom(shapeshifter) && NameNotifyManager.Notifies.TryGetValue(SentryPC.PlayerId, out Dictionary notifies) && notifies.Count > 0) - { - bool shapeshifting = shapeshifter.PlayerId != target.PlayerId; - PlayerControl ssTarget = shapeshifting ? target : shapeshifter; - PlayerControl ss = shapeshifting ? shapeshifter : Utils.GetPlayerById(shapeshifter.shapeshiftTargetPlayerId); - - string text = "\n" + string.Format( - Translator.GetString("Sentry.Notify.Shapeshifted"), - Utils.ColorString(Main.PlayerColors[ss.PlayerId], ss.GetRealName()), - Utils.ColorString(Main.PlayerColors[ssTarget.PlayerId], Main.AllPlayerNames[ssTarget.PlayerId])); - - SentryPC.Notify(text, 3f); - } - } - - public static void OnAnyoneMurder(PlayerControl target) - { - foreach (PlayerState state in Main.PlayerStates.Values) - if (state.Role is Sentry st && st.IsInMonitoredRoom(target)) - st.DeadBodiesInRoom.Add(target.PlayerId); - } - - public static void OnAnyoneEnterVent(PlayerControl pc) - { - if (!AmongUsClient.Instance.AmHost) return; - - foreach (PlayerState state in Main.PlayerStates.Values) - { - if (state.Role is Sentry st && st.IsInMonitoredRoom(pc) && NameNotifyManager.Notifies.TryGetValue(st.SentryPC.PlayerId, out Dictionary notifies) && notifies.Count > 0) - { - string text = "\n" + string.Format( - Translator.GetString("Sentry.Notify.Vented"), - Utils.ColorString(Main.PlayerColors[pc.PlayerId], pc.GetRealName())); - - st.SentryPC.Notify(text, 3f); - } - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId) return string.Empty; - - if (seer.PlayerId == SentryPC.PlayerId && MonitoredRoom != null) - return string.Format(Translator.GetString("Sentry.Suffix.Self"), Translator.GetString($"{MonitoredRoom.RoomId}")); - - if (!PlayersKnowAboutCamera.GetBool()) return string.Empty; - - return IsInMonitoredRoom(seer) ? string.Format(Translator.GetString("Sentry.Suffix.MonitoredRoom"), CustomRoles.Sentry.ToColoredString()) : string.Empty; - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (!GameStates.IsInTask || lowLoad) return; - - bool nowInMonitoredRoom = IsInMonitoredRoom(pc); - bool wasInMonitoredRoom = LastNotified.Contains(pc.PlayerId); - - switch (nowInMonitoredRoom) - { - case true when !wasInMonitoredRoom: - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - LastNotified.Add(pc.PlayerId); - break; - case false when wasInMonitoredRoom: - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - LastNotified.Remove(pc.PlayerId); - break; - } - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (MonitoredRoom == null || MonitoredRoom == default || MonitoredRoom == default(PlainShipRoom)) return; - - if (LastInfoSend.TryGetValue(pc.PlayerId, out long ts) && ts == Utils.TimeStamp) return; - - LastInfoSend[pc.PlayerId] = Utils.TimeStamp; - - if (!CheckTeam(pc)) return; - - Vector2 pos = pc.Pos(); - float range = DisableDevice.UsableDistance - 1f; - - if (!AvailableDevices.Any(x => Vector2.Distance(pos, x) <= range)) - { - if (UsingDevice.Remove(pc.PlayerId)) - { - NameNotifyManager.Notifies.Remove(pc.PlayerId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - return; - } - - DisplayRoomInfo(pc, true); - UsingDevice.Add(pc.PlayerId); - } - - private static bool CheckTeam(PlayerControl pc) - { - SimpleTeam team = pc.GetTeam() switch - { - Team.Crewmate => SimpleTeam.Crewmate, - Team.Impostor => SimpleTeam.Impostor, - Team.Neutral => pc.IsNeutralKiller() ? SimpleTeam.NK : SimpleTeam.NNK, - _ => SimpleTeam.Crewmate + UsableDevicesStrings.None => false, + UsableDevicesStrings.Cameras => x.Key.Contains("Camera") && !x.Key.Contains("Fungle"), + UsableDevicesStrings.Admin => x.Key.Contains("Admin"), + UsableDevicesStrings.CamerasAndAdmin => x.Key.Contains("Camera") || x.Key.Contains("Admin"), + _ => false }; - return TeamsCanSeeInfo[team].GetBool(); + if (!enabled && devicesOpt != UsableDevicesStrings.None) + { + enabled |= (AdditionalDevicesStrings)AdditionalDevicesForInfoView.GetValue() switch + { + AdditionalDevicesStrings.None => false, + AdditionalDevicesStrings.DoorLog => x.Key.Contains("DoorLog"), + AdditionalDevicesStrings.Binoculars => x.Key.Contains("Camera") && x.Key.Contains("Fungle"), + AdditionalDevicesStrings.DoorLogAndBinoculars => x.Key.Contains("DoorLog") || (x.Key.Contains("Camera") && x.Key.Contains("Fungle")), + _ => false + }; + } + + return correctMap && enabled; + }).Select(x => x.Value).ToArray(); + } + + public override void OnPet(PlayerControl pc) + { + PlainShipRoom room = pc.GetPlainShipRoom(); + bool hasntChosenRoom = MonitoredRoom == null || MonitoredRoom == default || MonitoredRoom == default(PlainShipRoom); + + if (room == default(PlainShipRoom) && hasntChosenRoom) + { + pc.AddAbilityCD(3); + pc.Notify(Translator.GetString("Sentry.Notify.InvalidRoom")); + return; } - public override void AfterMeetingTasks() + if (hasntChosenRoom) { - DeadBodiesInRoom.Clear(); + MonitoredRoom = room; + Utils.SendRPC(CustomRPC.SyncSentry, pc.PlayerId); + } + else + DisplayRoomInfo(pc, false); + } + + private void DisplayRoomInfo(PlayerControl pc, bool fromDevice) + { + if (CannotSeeInfoDuringComms.GetBool() && Utils.IsActive(SystemTypes.Comms)) return; + + if (!fromDevice) + { + if (pc.GetAbilityUseLimit() < 1) return; + pc.RpcRemoveAbilityUse(); } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.PetButton?.OverrideText(Translator.GetString("SentryPetButtonText")); - } + string roomName = Translator.GetString(MonitoredRoom.RoomId.ToString()); + string players = Main.AllAlivePlayerControls.Where(IsInMonitoredRoom).Select(x => Utils.GetPlayerById(x.shapeshiftTargetPlayerId) ?? x).Select(x => Utils.ColorString(Main.PlayerColors[x.PlayerId], x.GetRealName())).Join(); + string bodies = GetColoredNames(DeadBodiesInRoom); - private enum UsableDevicesStrings - { - None, - Cameras, - Admin, - CamerasAndAdmin - } + string noDataString = Translator.GetString("Sentry.Notify.Info.NoData"); + if (players.Length == 0) players = noDataString; + if (bodies.Length == 0) bodies = noDataString; - private enum AdditionalDevicesStrings - { - None, - DoorLog, - Binoculars, - DoorLogAndBinoculars - } + pc.Notify(string.Format(Translator.GetString("Sentry.Notify.Info"), roomName, players, bodies), ShowInfoDuration.GetInt(), overrideAll: fromDevice); + return; - private enum SimpleTeam + static string GetColoredNames(IEnumerable ids) { - Crewmate, - Impostor, - NK, - NNK + return ids.Where(x => Utils.GetPlayerById(x) != null).Select(x => Utils.ColorString(Main.PlayerColors[x], Utils.GetPlayerById(x).GetRealName())).Join(); } } + + private bool IsInMonitoredRoom(PlayerControl pc) + { + return MonitoredRoom != null && SentryPC.IsAlive() && pc.GetPlainShipRoom() == MonitoredRoom; + } + + public void OnAnyoneShapeshiftLoop(PlayerControl shapeshifter, PlayerControl target) + { + if (IsInMonitoredRoom(shapeshifter) && NameNotifyManager.Notifies.TryGetValue(SentryPC.PlayerId, out Dictionary notifies) && notifies.Count > 0) + { + bool shapeshifting = shapeshifter.PlayerId != target.PlayerId; + PlayerControl ssTarget = shapeshifting ? target : shapeshifter; + PlayerControl ss = shapeshifting ? shapeshifter : Utils.GetPlayerById(shapeshifter.shapeshiftTargetPlayerId); + + string text = "\n" + string.Format( + Translator.GetString("Sentry.Notify.Shapeshifted"), + Utils.ColorString(Main.PlayerColors[ss.PlayerId], ss.GetRealName()), + Utils.ColorString(Main.PlayerColors[ssTarget.PlayerId], Main.AllPlayerNames[ssTarget.PlayerId])); + + SentryPC.Notify(text, 3f); + } + } + + public static void OnAnyoneMurder(PlayerControl target) + { + foreach (PlayerState state in Main.PlayerStates.Values) + if (state.Role is Sentry st && st.IsInMonitoredRoom(target)) + st.DeadBodiesInRoom.Add(target.PlayerId); + } + + public static void OnAnyoneEnterVent(PlayerControl pc) + { + if (!AmongUsClient.Instance.AmHost) return; + + foreach (PlayerState state in Main.PlayerStates.Values) + { + if (state.Role is Sentry st && st.IsInMonitoredRoom(pc) && NameNotifyManager.Notifies.TryGetValue(st.SentryPC.PlayerId, out Dictionary notifies) && notifies.Count > 0) + { + string text = "\n" + string.Format( + Translator.GetString("Sentry.Notify.Vented"), + Utils.ColorString(Main.PlayerColors[pc.PlayerId], pc.GetRealName())); + + st.SentryPC.Notify(text, 3f); + } + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + if (seer.PlayerId == SentryPC.PlayerId && MonitoredRoom != null) + return string.Format(Translator.GetString("Sentry.Suffix.Self"), Translator.GetString($"{MonitoredRoom.RoomId}")); + + if (!PlayersKnowAboutCamera.GetBool()) return string.Empty; + + return IsInMonitoredRoom(seer) ? string.Format(Translator.GetString("Sentry.Suffix.MonitoredRoom"), CustomRoles.Sentry.ToColoredString()) : string.Empty; + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (!GameStates.IsInTask || lowLoad) return; + + bool nowInMonitoredRoom = IsInMonitoredRoom(pc); + bool wasInMonitoredRoom = LastNotified.Contains(pc.PlayerId); + + switch (nowInMonitoredRoom) + { + case true when !wasInMonitoredRoom: + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + LastNotified.Add(pc.PlayerId); + break; + case false when wasInMonitoredRoom: + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + LastNotified.Remove(pc.PlayerId); + break; + } + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (MonitoredRoom == null || MonitoredRoom == default || MonitoredRoom == default(PlainShipRoom)) return; + + if (LastInfoSend.TryGetValue(pc.PlayerId, out long ts) && ts == Utils.TimeStamp) return; + + LastInfoSend[pc.PlayerId] = Utils.TimeStamp; + + if (!CheckTeam(pc)) return; + + Vector2 pos = pc.Pos(); + float range = DisableDevice.UsableDistance - 1f; + + if (!AvailableDevices.Any(x => Vector2.Distance(pos, x) <= range)) + { + if (UsingDevice.Remove(pc.PlayerId)) + { + NameNotifyManager.Notifies.Remove(pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + return; + } + + DisplayRoomInfo(pc, true); + UsingDevice.Add(pc.PlayerId); + } + + private static bool CheckTeam(PlayerControl pc) + { + SimpleTeam team = pc.GetTeam() switch + { + Team.Crewmate => SimpleTeam.Crewmate, + Team.Impostor => SimpleTeam.Impostor, + Team.Neutral => pc.IsNeutralKiller() ? SimpleTeam.NK : SimpleTeam.NNK, + _ => SimpleTeam.Crewmate + }; + + return TeamsCanSeeInfo[team].GetBool(); + } + + public override void AfterMeetingTasks() + { + DeadBodiesInRoom.Clear(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.PetButton?.OverrideText(Translator.GetString("SentryPetButtonText")); + } + + private enum UsableDevicesStrings + { + None, + Cameras, + Admin, + CamerasAndAdmin + } + + private enum AdditionalDevicesStrings + { + None, + DoorLog, + Binoculars, + DoorLogAndBinoculars + } + + private enum SimpleTeam + { + Crewmate, + Impostor, + NK, + NNK + } } \ No newline at end of file diff --git a/Roles/Crewmate/Sheriff.cs b/Roles/Crewmate/Sheriff.cs index 0b234ac8e..1214f72c7 100644 --- a/Roles/Crewmate/Sheriff.cs +++ b/Roles/Crewmate/Sheriff.cs @@ -2,178 +2,177 @@ using System; using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Sheriff : RoleBase { - public class Sheriff : RoleBase + private const int Id = 8800; + private static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem MisfireKillsTarget; + private static OptionItem ShotLimitOpt; + private static OptionItem ShowShotLimit; + private static OptionItem CanKillAllAlive; + private static OptionItem CanKillNeutrals; + private static OptionItem CanKillNeutralsMode; + private static OptionItem CanKillMadmate; + private static OptionItem CanKillCharmed; + private static OptionItem CanKillLovers; + private static OptionItem CanKillSidekicks; + private static OptionItem CanKillContagious; + private static OptionItem SidekickSheriffCanGoBerserk; + private static OptionItem SetNonCrewCanKill; + private static OptionItem NonCrewCanKillCrew; + private static OptionItem NonCrewCanKillImp; + private static OptionItem NonCrewCanKillNeutral; + public static OptionItem KeepsGameGoing; + public static OptionItem UsePet; + private static readonly Dictionary KillTargetOptions = []; + + public static readonly string[] KillOption = + [ + "SheriffCanKillAll", + "SheriffCanKillSeparately" + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 8800; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Sheriff); + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 60f, 2.5f), 22.5f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]).SetValueFormat(OptionFormat.Seconds); + MisfireKillsTarget = new BooleanOptionItem(Id + 11, "SheriffMisfireKillsTarget", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + ShotLimitOpt = new IntegerOptionItem(Id + 12, "SheriffShotLimit", new(1, 15, 1), 5, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]).SetValueFormat(OptionFormat.Times); + ShowShotLimit = new BooleanOptionItem(Id + 13, "SheriffShowShotLimit", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillAllAlive = new BooleanOptionItem(Id + 15, "SheriffCanKillAllAlive", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillMadmate = new BooleanOptionItem(Id + 17, "SheriffCanKillMadmate", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillCharmed = new BooleanOptionItem(Id + 22, "SheriffCanKillCharmed", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillLovers = new BooleanOptionItem(Id + 24, "SheriffCanKillLovers", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillSidekicks = new BooleanOptionItem(Id + 23, "SheriffCanKillSidekick", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillContagious = new BooleanOptionItem(Id + 27, "SheriffCanKillContagious", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillNeutrals = new BooleanOptionItem(Id + 16, "SheriffCanKillNeutrals", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + CanKillNeutralsMode = new StringOptionItem(Id + 14, "SheriffCanKillNeutralsMode", KillOption, 0, TabGroup.CrewmateRoles).SetParent(CanKillNeutrals); + SetUpNeutralOptions(Id + 30); + SidekickSheriffCanGoBerserk = new BooleanOptionItem(Id + 28, "SidekickSheriffCanGoBerserk", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + SetNonCrewCanKill = new BooleanOptionItem(Id + 18, "SheriffSetMadCanKill", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + NonCrewCanKillImp = new BooleanOptionItem(Id + 19, "SheriffMadCanKillImp", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); + NonCrewCanKillCrew = new BooleanOptionItem(Id + 21, "SheriffMadCanKillCrew", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); + NonCrewCanKillNeutral = new BooleanOptionItem(Id + 20, "SheriffMadCanKillNeutral", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); + KeepsGameGoing = new BooleanOptionItem(Id + 26, "SheriffKeepsGameGoing", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); + UsePet = Options.CreatePetUseSetting(Id + 29, CustomRoles.Sheriff); + } - private static OptionItem KillCooldown; - private static OptionItem MisfireKillsTarget; - private static OptionItem ShotLimitOpt; - private static OptionItem ShowShotLimit; - private static OptionItem CanKillAllAlive; - private static OptionItem CanKillNeutrals; - private static OptionItem CanKillNeutralsMode; - private static OptionItem CanKillMadmate; - private static OptionItem CanKillCharmed; - private static OptionItem CanKillLovers; - private static OptionItem CanKillSidekicks; - private static OptionItem CanKillContagious; - private static OptionItem SidekickSheriffCanGoBerserk; - private static OptionItem SetNonCrewCanKill; - private static OptionItem NonCrewCanKillCrew; - private static OptionItem NonCrewCanKillImp; - private static OptionItem NonCrewCanKillNeutral; - public static OptionItem KeepsGameGoing; - public static OptionItem UsePet; - private static readonly Dictionary KillTargetOptions = []; - - public static readonly string[] KillOption = - [ - "SheriffCanKillAll", - "SheriffCanKillSeparately" - ]; - - public override bool IsEnable => PlayerIdList.Count > 0; - - public override void SetupCustomOption() + private static void SetUpNeutralOptions(int id) + { + foreach (CustomRoles neutral in Enum.GetValues()) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Sheriff); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 60f, 2.5f), 22.5f, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]).SetValueFormat(OptionFormat.Seconds); - MisfireKillsTarget = new BooleanOptionItem(Id + 11, "SheriffMisfireKillsTarget", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - ShotLimitOpt = new IntegerOptionItem(Id + 12, "SheriffShotLimit", new(1, 15, 1), 5, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]).SetValueFormat(OptionFormat.Times); - ShowShotLimit = new BooleanOptionItem(Id + 13, "SheriffShowShotLimit", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillAllAlive = new BooleanOptionItem(Id + 15, "SheriffCanKillAllAlive", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillMadmate = new BooleanOptionItem(Id + 17, "SheriffCanKillMadmate", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillCharmed = new BooleanOptionItem(Id + 22, "SheriffCanKillCharmed", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillLovers = new BooleanOptionItem(Id + 24, "SheriffCanKillLovers", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillSidekicks = new BooleanOptionItem(Id + 23, "SheriffCanKillSidekick", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillContagious = new BooleanOptionItem(Id + 27, "SheriffCanKillContagious", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillNeutrals = new BooleanOptionItem(Id + 16, "SheriffCanKillNeutrals", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - CanKillNeutralsMode = new StringOptionItem(Id + 14, "SheriffCanKillNeutralsMode", KillOption, 0, TabGroup.CrewmateRoles).SetParent(CanKillNeutrals); - SetUpNeutralOptions(Id + 30); - SidekickSheriffCanGoBerserk = new BooleanOptionItem(Id + 28, "SidekickSheriffCanGoBerserk", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - SetNonCrewCanKill = new BooleanOptionItem(Id + 18, "SheriffSetMadCanKill", true, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - NonCrewCanKillImp = new BooleanOptionItem(Id + 19, "SheriffMadCanKillImp", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); - NonCrewCanKillCrew = new BooleanOptionItem(Id + 21, "SheriffMadCanKillCrew", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); - NonCrewCanKillNeutral = new BooleanOptionItem(Id + 20, "SheriffMadCanKillNeutral", true, TabGroup.CrewmateRoles).SetParent(SetNonCrewCanKill); - KeepsGameGoing = new BooleanOptionItem(Id + 26, "SheriffKeepsGameGoing", false, TabGroup.CrewmateRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sheriff]); - UsePet = Options.CreatePetUseSetting(Id + 29, CustomRoles.Sheriff); - } - - private static void SetUpNeutralOptions(int id) - { - foreach (CustomRoles neutral in Enum.GetValues()) + if (neutral.IsNeutral() && neutral is not CustomRoles.Konan and not CustomRoles.Pestilence and not CustomRoles.GM and not CustomRoles.Convict && !neutral.IsForOtherGameMode()) { - if (neutral.IsNeutral() && neutral is not CustomRoles.Konan and not CustomRoles.Pestilence and not CustomRoles.GM and not CustomRoles.Convict && !neutral.IsForOtherGameMode()) - { - SetUpKillTargetOption(neutral, id, true, CanKillNeutralsMode); - id++; - } + SetUpKillTargetOption(neutral, id, true, CanKillNeutralsMode); + id++; } } + } - public static void SetUpKillTargetOption(CustomRoles role, int id, bool defaultValue = true, OptionItem parent = null) + public static void SetUpKillTargetOption(CustomRoles role, int id, bool defaultValue = true, OptionItem parent = null) + { + parent ??= Options.CustomRoleSpawnChances[CustomRoles.Sheriff]; + Dictionary replacementDic = new() { { "%role%", role.ToColoredString() } }; + KillTargetOptions[role] = new BooleanOptionItem(id, "SheriffCanKill%role%", defaultValue, TabGroup.CrewmateRoles).SetParent(parent); + KillTargetOptions[role].ReplacementDictionary = replacementDic; + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(ShotLimitOpt.GetInt()); + + Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()} : Shot Limit - {playerId.GetAbilityUseLimit()}", "Sheriff"); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? KillCooldown.GetFloat() : 15f; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !Main.PlayerStates[pc.PlayerId].IsDead + && (CanKillAllAlive.GetBool() || GameStates.AlreadyDied) + && pc.GetAbilityUseLimit() is float.NaN or > 0; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (CanBeKilledBySheriff(target) || (killer.Is(CustomRoles.Recruit) && SidekickSheriffCanGoBerserk.GetBool()) || (SetNonCrewCanKill.GetBool() && (killer.IsMadmate() || killer.IsConverted()) && ((target.IsImpostor() && NonCrewCanKillImp.GetBool()) || (target.IsCrewmate() && NonCrewCanKillCrew.GetBool()) || (target.GetCustomRole().IsNeutral() && NonCrewCanKillNeutral.GetBool())))) { - parent ??= Options.CustomRoleSpawnChances[CustomRoles.Sheriff]; - Dictionary replacementDic = new() { { "%role%", role.ToColoredString() } }; - KillTargetOptions[role] = new BooleanOptionItem(id, "SheriffCanKill%role%", defaultValue, TabGroup.CrewmateRoles).SetParent(parent); - KillTargetOptions[role].ReplacementDictionary = replacementDic; + SetKillCooldown(killer.PlayerId); + return true; } - public override void Init() - { - PlayerIdList = []; - } + killer.Suicide(PlayerState.DeathReason.Misfire); + return MisfireKillsTarget.GetBool(); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(ShotLimitOpt.GetInt()); + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + killer.RpcRemoveAbilityUse(); + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} : Number of kills left: {killer.GetAbilityUseLimit()}", "Sheriff"); + } - Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()} : Shot Limit - {playerId.GetAbilityUseLimit()}", "Sheriff"); - } + private static bool CanBeKilledBySheriff(PlayerControl player) + { + CustomRoles cRole = player.GetCustomRole(); + List subRole = player.GetCustomSubRoles(); + if (subRole.Contains(CustomRoles.Rascal)) return true; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? KillCooldown.GetFloat() : 15f; - } + var CanKill = false; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + foreach (CustomRoles SubRoleTarget in subRole) { - opt.SetVision(false); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !Main.PlayerStates[pc.PlayerId].IsDead - && (CanKillAllAlive.GetBool() || GameStates.AlreadyDied) - && pc.GetAbilityUseLimit() is float.NaN or > 0; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (CanBeKilledBySheriff(target) || (killer.Is(CustomRoles.Recruit) && SidekickSheriffCanGoBerserk.GetBool()) || (SetNonCrewCanKill.GetBool() && (killer.IsMadmate() || killer.IsConverted()) && ((target.IsImpostor() && NonCrewCanKillImp.GetBool()) || (target.IsCrewmate() && NonCrewCanKillCrew.GetBool()) || (target.GetCustomRole().IsNeutral() && NonCrewCanKillNeutral.GetBool())))) + CanKill = SubRoleTarget switch { - SetKillCooldown(killer.PlayerId); - return true; - } - - killer.Suicide(PlayerState.DeathReason.Misfire); - return MisfireKillsTarget.GetBool(); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - killer.RpcRemoveAbilityUse(); - Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} : Number of kills left: {killer.GetAbilityUseLimit()}", "Sheriff"); - } - - private static bool CanBeKilledBySheriff(PlayerControl player) - { - CustomRoles cRole = player.GetCustomRole(); - List subRole = player.GetCustomSubRoles(); - if (subRole.Contains(CustomRoles.Rascal)) return true; - - var CanKill = false; - - foreach (CustomRoles SubRoleTarget in subRole) - { - CanKill = SubRoleTarget switch - { - CustomRoles.Madmate => CanKillMadmate.GetBool(), - CustomRoles.Charmed => CanKillCharmed.GetBool(), - CustomRoles.Lovers => CanKillLovers.GetBool(), - CustomRoles.Recruit => CanKillSidekicks.GetBool(), - CustomRoles.Contagious => CanKillContagious.GetBool(), - CustomRoles.Rascal => true, - _ => false - }; - } - - return cRole switch - { - CustomRoles.Trickster => false, - CustomRoles.Pestilence => true, - _ => player.GetCustomRoleTypes() switch - { - CustomRoleTypes.Impostor => true, - CustomRoleTypes.Neutral => CanKillNeutrals.GetBool() && (CanKillNeutralsMode.GetValue() == 0 || !KillTargetOptions.TryGetValue(cRole, out OptionItem option) || option.GetBool()), - _ => CanKill - } + CustomRoles.Madmate => CanKillMadmate.GetBool(), + CustomRoles.Charmed => CanKillCharmed.GetBool(), + CustomRoles.Lovers => CanKillLovers.GetBool(), + CustomRoles.Recruit => CanKillSidekicks.GetBool(), + CustomRoles.Contagious => CanKillContagious.GetBool(), + CustomRoles.Rascal => true, + _ => false }; } - public override string GetProgressText(byte playerId, bool comms) + return cRole switch { - return ShowShotLimit.GetBool() ? base.GetProgressText(playerId, comms) : Utils.GetTaskCount(playerId, comms); - } + CustomRoles.Trickster => false, + CustomRoles.Pestilence => true, + _ => player.GetCustomRoleTypes() switch + { + CustomRoleTypes.Impostor => true, + CustomRoleTypes.Neutral => CanKillNeutrals.GetBool() && (CanKillNeutralsMode.GetValue() == 0 || !KillTargetOptions.TryGetValue(cRole, out OptionItem option) || option.GetBool()), + _ => CanKill + } + }; + } + + public override string GetProgressText(byte playerId, bool comms) + { + return ShowShotLimit.GetBool() ? base.GetProgressText(playerId, comms) : Utils.GetTaskCount(playerId, comms); } } \ No newline at end of file diff --git a/Roles/Crewmate/Shiftguard.cs b/Roles/Crewmate/Shiftguard.cs index 77075d703..f9148e759 100644 --- a/Roles/Crewmate/Shiftguard.cs +++ b/Roles/Crewmate/Shiftguard.cs @@ -1,16 +1,15 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Shiftguard : RoleBase { - internal class Shiftguard : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(5594, TabGroup.CrewmateRoles, CustomRoles.Shiftguard); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + Options.SetupRoleOptions(5594, TabGroup.CrewmateRoles, CustomRoles.Shiftguard); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Crewmate/Snitch.cs b/Roles/Crewmate/Snitch.cs index 39d81304f..8c89ec7b2 100644 --- a/Roles/Crewmate/Snitch.cs +++ b/Roles/Crewmate/Snitch.cs @@ -3,181 +3,180 @@ using System.Linq; using UnityEngine; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Snitch : RoleBase { - public class Snitch : RoleBase + private const int Id = 8000; + private static readonly List PlayerIdList = []; + private static readonly Color RoleColor = Utils.GetRoleColor(CustomRoles.Snitch); + + private static OptionItem OptionEnableTargetArrow; + private static OptionItem OptionCanGetColoredArrow; + private static OptionItem OptionCanFindNeutralKiller; + private static OptionItem OptionCanFindMadmate; + private static OptionItem OptionRemainingTasks; + + private static bool EnableTargetArrow; + private static bool CanGetColoredArrow; + private static bool CanFindNeutralKiller; + private static bool CanFindMadmate; + public static int RemainingTasksToBeFound; + + public static readonly Dictionary IsExposed = []; + public static readonly Dictionary IsComplete = []; + + private static readonly HashSet TargetList = []; + private static readonly Dictionary TargetColorlist = []; + private byte SnitchId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 8000; - private static readonly List PlayerIdList = []; - private static readonly Color RoleColor = Utils.GetRoleColor(CustomRoles.Snitch); + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Snitch); + OptionEnableTargetArrow = new BooleanOptionItem(Id + 10, "SnitchEnableTargetArrow", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OptionCanGetColoredArrow = new BooleanOptionItem(Id + 11, "SnitchCanGetArrowColor", true, TabGroup.CrewmateRoles).SetParent(OptionEnableTargetArrow); + OptionCanFindNeutralKiller = new BooleanOptionItem(Id + 12, "SnitchCanFindNeutralKiller", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OptionCanFindMadmate = new BooleanOptionItem(Id + 14, "SnitchCanFindMadmate", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OptionRemainingTasks = new IntegerOptionItem(Id + 13, "SnitchRemainingTaskFound", new(0, 10, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); + OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Snitch); + } - private static OptionItem OptionEnableTargetArrow; - private static OptionItem OptionCanGetColoredArrow; - private static OptionItem OptionCanFindNeutralKiller; - private static OptionItem OptionCanFindMadmate; - private static OptionItem OptionRemainingTasks; + public override void Init() + { + PlayerIdList.Clear(); - private static bool EnableTargetArrow; - private static bool CanGetColoredArrow; - private static bool CanFindNeutralKiller; - private static bool CanFindMadmate; - public static int RemainingTasksToBeFound; + IsExposed.Clear(); + IsComplete.Clear(); - public static readonly Dictionary IsExposed = []; - public static readonly Dictionary IsComplete = []; + TargetList.Clear(); + TargetColorlist.Clear(); + } - private static readonly HashSet TargetList = []; - private static readonly Dictionary TargetColorlist = []; - private byte SnitchId; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + SnitchId = playerId; - public override bool IsEnable => PlayerIdList.Count > 0; + EnableTargetArrow = OptionEnableTargetArrow.GetBool(); + CanGetColoredArrow = OptionCanGetColoredArrow.GetBool(); + CanFindNeutralKiller = OptionCanFindNeutralKiller.GetBool(); + CanFindMadmate = OptionCanFindMadmate.GetBool(); + RemainingTasksToBeFound = OptionRemainingTasks.GetInt(); - public override void SetupCustomOption() + IsExposed[playerId] = false; + IsComplete[playerId] = false; + } + + public static bool IsThisRole(byte playerId) + { + return PlayerIdList.Contains(playerId); + } + + private static bool GetExpose(PlayerControl pc) + { + if (!IsThisRole(pc.PlayerId) || !pc.IsAlive() || pc.Is(CustomRoles.Madmate)) return false; + + byte snitchId = pc.PlayerId; + return IsExposed[snitchId]; + } + + private static bool IsSnitchTarget(PlayerControl target) + { + return (target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Trickster)) || (target.IsSnitchTarget() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate); + } + + public static void CheckTask(PlayerControl snitch) + { + if (!snitch.IsAlive() || snitch.Is(CustomRoles.Madmate)) return; + + byte snitchId = snitch.PlayerId; + TaskState snitchTask = snitch.GetTaskState(); + + if (!IsExposed[snitchId] && snitchTask.RemainingTasksCount <= RemainingTasksToBeFound) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Snitch); - OptionEnableTargetArrow = new BooleanOptionItem(Id + 10, "SnitchEnableTargetArrow", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); - OptionCanGetColoredArrow = new BooleanOptionItem(Id + 11, "SnitchCanGetArrowColor", true, TabGroup.CrewmateRoles).SetParent(OptionEnableTargetArrow); - OptionCanFindNeutralKiller = new BooleanOptionItem(Id + 12, "SnitchCanFindNeutralKiller", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); - OptionCanFindMadmate = new BooleanOptionItem(Id + 14, "SnitchCanFindMadmate", true, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); - OptionRemainingTasks = new IntegerOptionItem(Id + 13, "SnitchRemainingTaskFound", new(0, 10, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Snitch]); - OverrideTasksData.Create(Id + 20, TabGroup.CrewmateRoles, CustomRoles.Snitch); - } - - public override void Init() - { - PlayerIdList.Clear(); - - IsExposed.Clear(); - IsComplete.Clear(); - - TargetList.Clear(); - TargetColorlist.Clear(); - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - SnitchId = playerId; - - EnableTargetArrow = OptionEnableTargetArrow.GetBool(); - CanGetColoredArrow = OptionCanGetColoredArrow.GetBool(); - CanFindNeutralKiller = OptionCanFindNeutralKiller.GetBool(); - CanFindMadmate = OptionCanFindMadmate.GetBool(); - RemainingTasksToBeFound = OptionRemainingTasks.GetInt(); - - IsExposed[playerId] = false; - IsComplete[playerId] = false; - } - - public static bool IsThisRole(byte playerId) - { - return PlayerIdList.Contains(playerId); - } - - private static bool GetExpose(PlayerControl pc) - { - if (!IsThisRole(pc.PlayerId) || !pc.IsAlive() || pc.Is(CustomRoles.Madmate)) return false; - - byte snitchId = pc.PlayerId; - return IsExposed[snitchId]; - } - - private static bool IsSnitchTarget(PlayerControl target) - { - return (target.Is(CustomRoleTypes.Impostor) && !target.Is(CustomRoles.Trickster)) || (target.IsSnitchTarget() && CanFindNeutralKiller) || (target.Is(CustomRoles.Madmate) && CanFindMadmate) || (target.Is(CustomRoles.Rascal) && CanFindMadmate); - } - - public static void CheckTask(PlayerControl snitch) - { - if (!snitch.IsAlive() || snitch.Is(CustomRoles.Madmate)) return; - - byte snitchId = snitch.PlayerId; - TaskState snitchTask = snitch.GetTaskState(); - - if (!IsExposed[snitchId] && snitchTask.RemainingTasksCount <= RemainingTasksToBeFound) - { - foreach (PlayerControl target in Main.AllAlivePlayerControls) - { - if (!IsSnitchTarget(target)) continue; - - TargetArrow.Add(target.PlayerId, snitchId); - } - - IsExposed[snitchId] = true; - } - - if (IsComplete[snitchId] || !snitchTask.IsTaskFinished) return; - foreach (PlayerControl target in Main.AllAlivePlayerControls) { if (!IsSnitchTarget(target)) continue; - byte targetId = target.PlayerId; - NameColorManager.Add(snitchId, targetId); - - if (!EnableTargetArrow) continue; - - TargetArrow.Add(snitchId, targetId); - - if (TargetList.Add(targetId)) - if (CanGetColoredArrow) - TargetColorlist.Add(targetId, target.GetRoleColor()); + TargetArrow.Add(target.PlayerId, snitchId); } - snitch.Notify(Translator.GetString("SnitchDoneTasks")); - - IsComplete[snitchId] = true; + IsExposed[snitchId] = true; } - public static string GetWarningMark(PlayerControl seer, PlayerControl target) + if (IsComplete[snitchId] || !snitchTask.IsTaskFinished) return; + + foreach (PlayerControl target in Main.AllAlivePlayerControls) { - return IsSnitchTarget(seer) && GetExpose(target) ? Utils.ColorString(RoleColor, " ★") : string.Empty; + if (!IsSnitchTarget(target)) continue; + + byte targetId = target.PlayerId; + NameColorManager.Add(snitchId, targetId); + + if (!EnableTargetArrow) continue; + + TargetArrow.Add(snitchId, targetId); + + if (TargetList.Add(targetId)) + if (CanGetColoredArrow) + TargetColorlist.Add(targetId, target.GetRoleColor()); } - public static string GetWarningArrow(PlayerControl seer, PlayerControl target = null) + snitch.Notify(Translator.GetString("SnitchDoneTasks")); + + IsComplete[snitchId] = true; + } + + public static string GetWarningMark(PlayerControl seer, PlayerControl target) + { + return IsSnitchTarget(seer) && GetExpose(target) ? Utils.ColorString(RoleColor, " ★") : string.Empty; + } + + public static string GetWarningArrow(PlayerControl seer, PlayerControl target = null) + { + if (GameStates.IsMeeting || !IsSnitchTarget(seer)) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + IEnumerable exposedSnitch = PlayerIdList.Where(s => !Main.PlayerStates[s].IsDead && IsExposed[s]); + byte[] snitch = exposedSnitch as byte[] ?? exposedSnitch.ToArray(); + if (snitch.Length == 0) return string.Empty; + + var warning = $"\n{Translator.GetString("Snitch")} "; + + if (EnableTargetArrow) + warning += TargetArrow.GetArrows(seer, snitch); + else + warning += "⚠"; + + return Utils.ColorString(RoleColor, warning); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.Is(CustomRoles.Madmate)) return string.Empty; + + if (!EnableTargetArrow || GameStates.IsMeeting || seer.PlayerId != SnitchId) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + var arrows = string.Empty; + + foreach (byte targetId in TargetList) { - if (GameStates.IsMeeting || !IsSnitchTarget(seer)) return string.Empty; - - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - IEnumerable exposedSnitch = PlayerIdList.Where(s => !Main.PlayerStates[s].IsDead && IsExposed[s]); - byte[] snitch = exposedSnitch as byte[] ?? exposedSnitch.ToArray(); - if (snitch.Length == 0) return string.Empty; - - var warning = $"\n{Translator.GetString("Snitch")} "; - - if (EnableTargetArrow) - warning += TargetArrow.GetArrows(seer, snitch); - else - warning += "⚠"; - - return Utils.ColorString(RoleColor, warning); + string arrow = TargetArrow.GetArrows(seer, targetId); + arrows += CanGetColoredArrow ? Utils.ColorString(TargetColorlist[targetId], arrow) : arrow; } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.Is(CustomRoles.Madmate)) return string.Empty; + return arrows; + } - if (!EnableTargetArrow || GameStates.IsMeeting || seer.PlayerId != SnitchId) return string.Empty; + public static void OnCompleteTask(PlayerControl player) + { + if (!IsThisRole(player.PlayerId) || player.Is(CustomRoles.Madmate)) return; - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - var arrows = string.Empty; - - foreach (byte targetId in TargetList) - { - string arrow = TargetArrow.GetArrows(seer, targetId); - arrows += CanGetColoredArrow ? Utils.ColorString(TargetColorlist[targetId], arrow) : arrow; - } - - return arrows; - } - - public static void OnCompleteTask(PlayerControl player) - { - if (!IsThisRole(player.PlayerId) || player.Is(CustomRoles.Madmate)) return; - - CheckTask(player); - } + CheckTask(player); } } \ No newline at end of file diff --git a/Roles/Crewmate/Socialite.cs b/Roles/Crewmate/Socialite.cs index c399c2558..ea3916a89 100644 --- a/Roles/Crewmate/Socialite.cs +++ b/Roles/Crewmate/Socialite.cs @@ -3,128 +3,127 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Socialite : RoleBase { - public class Socialite : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem Cooldown; + public static OptionItem UsePet; + public static OptionItem CancelVote; + public HashSet GuestList = []; + public byte MarkedPlayerId; + private byte SocialiteId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + var id = 647300; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Socialite); - private static OptionItem Cooldown; - public static OptionItem UsePet; - public static OptionItem CancelVote; - public HashSet GuestList = []; - public byte MarkedPlayerId; - private byte SocialiteId; + Cooldown = new FloatOptionItem(++id, "AbilityCooldown", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Socialite]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + UsePet = Options.CreatePetUseSetting(++id, CustomRoles.Socialite); + CancelVote = Options.CreateVoteCancellingUseSetting(++id, CustomRoles.Socialite, TabGroup.CrewmateRoles); + } - public override void SetupCustomOption() + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + GuestList = []; + SocialiteId = playerId; + MarkedPlayerId = byte.MaxValue; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Cooldown.GetFloat(); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive() && GuestList.Count < Main.PlayerStates.Count; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target) || MarkedPlayerId != byte.MaxValue) return false; + + MarkedPlayerId = target.PlayerId; + Utils.SendRPC(CustomRPC.SyncRoleData, SocialiteId, 1, MarkedPlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + return false; + } + + public override void OnReportDeadBody() + { + MarkedPlayerId = byte.MaxValue; + Utils.SendRPC(CustomRPC.SyncRoleData, SocialiteId, 1, MarkedPlayerId); + } + + public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) + { + foreach (Socialite socialite in Instances) { - var id = 647300; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Socialite); - - Cooldown = new FloatOptionItem(++id, "AbilityCooldown", new(0f, 60f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Socialite]) - .SetValueFormat(OptionFormat.Seconds); - - UsePet = Options.CreatePetUseSetting(++id, CustomRoles.Socialite); - CancelVote = Options.CreateVoteCancellingUseSetting(++id, CustomRoles.Socialite, TabGroup.CrewmateRoles); - } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - GuestList = []; - SocialiteId = playerId; - MarkedPlayerId = byte.MaxValue; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Cooldown.GetFloat(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive() && GuestList.Count < Main.PlayerStates.Count; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target) || MarkedPlayerId != byte.MaxValue) return false; - - MarkedPlayerId = target.PlayerId; - Utils.SendRPC(CustomRPC.SyncRoleData, SocialiteId, 1, MarkedPlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - return false; - } - - public override void OnReportDeadBody() - { - MarkedPlayerId = byte.MaxValue; - Utils.SendRPC(CustomRPC.SyncRoleData, SocialiteId, 1, MarkedPlayerId); - } - - public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) - { - foreach (Socialite socialite in Instances) + if (socialite.MarkedPlayerId == target.PlayerId && socialite.GuestList.Add(killer.PlayerId)) { - if (socialite.MarkedPlayerId == target.PlayerId && socialite.GuestList.Add(killer.PlayerId)) - { - Utils.SendRPC(CustomRPC.SyncRoleData, socialite.SocialiteId, 2, killer.PlayerId); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(socialite.SocialiteId), SpecifyTarget: killer); - return false; - } + Utils.SendRPC(CustomRPC.SyncRoleData, socialite.SocialiteId, 2, killer.PlayerId); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(socialite.SocialiteId), SpecifyTarget: killer); + return false; } + } + return true; + } + + public override bool OnVote(PlayerControl pc, PlayerControl target) + { + if (pc == null || target == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; + + if (GuestList.Add(target.PlayerId)) + { + Utils.SendRPC(CustomRPC.SyncRoleData, SocialiteId, 2, target.PlayerId); + Main.DontCancelVoteList.Add(pc.PlayerId); return true; } - public override bool OnVote(PlayerControl pc, PlayerControl target) + return false; + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - if (pc == null || target == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; - - if (GuestList.Add(target.PlayerId)) - { - Utils.SendRPC(CustomRPC.SyncRoleData, SocialiteId, 2, target.PlayerId); - Main.DontCancelVoteList.Add(pc.PlayerId); - return true; - } - - return false; - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - MarkedPlayerId = reader.ReadByte(); - break; - case 2: - GuestList.Add(reader.ReadByte()); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != SocialiteId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; - - return string.Format(Translator.GetString("Socialite.Suffix"), MarkedPlayerId.ColoredPlayerName()); + case 1: + MarkedPlayerId = reader.ReadByte(); + break; + case 2: + GuestList.Add(reader.ReadByte()); + break; } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != SocialiteId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; + + return string.Format(Translator.GetString("Socialite.Suffix"), MarkedPlayerId.ColoredPlayerName()); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Soothsayer.cs b/Roles/Crewmate/Soothsayer.cs index 0bcaf9380..4ab511713 100644 --- a/Roles/Crewmate/Soothsayer.cs +++ b/Roles/Crewmate/Soothsayer.cs @@ -1,55 +1,54 @@ using System.Collections.Generic; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Soothsayer : RoleBase { - public class Soothsayer : RoleBase + public static bool On; + private static List Instances = []; + + public static OptionItem CancelVote; + + private byte Target; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + const int id = 649450; + const TabGroup tab = TabGroup.CrewmateRoles; + const CustomRoles role = CustomRoles.Soothsayer; - public static OptionItem CancelVote; + Options.SetupRoleOptions(id, tab, role); + CancelVote = Options.CreateVoteCancellingUseSetting(id + 2, role, tab); + } - private byte Target; - public override bool IsEnable => On; + public override void Init() + { + On = false; + Instances = []; + } - public override void SetupCustomOption() - { - const int id = 649450; - const TabGroup tab = TabGroup.CrewmateRoles; - const CustomRoles role = CustomRoles.Soothsayer; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + Target = byte.MaxValue; + } - Options.SetupRoleOptions(id, tab, role); - CancelVote = Options.CreateVoteCancellingUseSetting(id + 2, role, tab); - } + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null || player.PlayerId == target.PlayerId) return false; - public override void Init() - { - On = false; - Instances = []; - } + if (Target != byte.MaxValue || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - Target = byte.MaxValue; - } + Target = target.PlayerId; - public override bool OnVote(PlayerControl player, PlayerControl target) - { - if (player == null || target == null || player.PlayerId == target.PlayerId) return false; + Main.DontCancelVoteList.Add(player.PlayerId); + return true; + } - if (Target != byte.MaxValue || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - - Target = target.PlayerId; - - Main.DontCancelVoteList.Add(player.PlayerId); - return true; - } - - public static void OnAnyoneDeath(PlayerControl killer, PlayerControl target) - { - Instances.DoIf(x => x.Target == killer.PlayerId, _ => Main.AllAlivePlayerControls.Do(p => p.Notify(string.Format(Translator.GetString("SoothsayerDiedNotify"), Utils.ColorString(Main.PlayerColors.GetValueOrDefault(killer.PlayerId), killer.GetRealName())), 10f))); - } + public static void OnAnyoneDeath(PlayerControl killer, PlayerControl target) + { + Instances.DoIf(x => x.Target == killer.PlayerId, _ => Main.AllAlivePlayerControls.Do(p => p.Notify(string.Format(Translator.GetString("SoothsayerDiedNotify"), Utils.ColorString(Main.PlayerColors.GetValueOrDefault(killer.PlayerId), killer.GetRealName())), 10f))); } } \ No newline at end of file diff --git a/Roles/Crewmate/SpeedBooster.cs b/Roles/Crewmate/SpeedBooster.cs index f8ddef260..af1d3a649 100644 --- a/Roles/Crewmate/SpeedBooster.cs +++ b/Roles/Crewmate/SpeedBooster.cs @@ -1,38 +1,37 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class SpeedBooster : RoleBase { - internal class SpeedBooster : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(649198, TabGroup.CrewmateRoles, CustomRoles.SpeedBooster); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + if (player.IsAlive() && completedTaskCount + 1 <= totalTaskCount) { - Options.SetupRoleOptions(649198, TabGroup.CrewmateRoles, CustomRoles.SpeedBooster); - } + PlayerControl target = Main.AllAlivePlayerControls.RandomElement(); + Main.AllPlayerSpeed[target.PlayerId] += 0.5f; - public override void Add(byte playerId) - { - On = true; - } + target.Notify(Main.AllPlayerSpeed[target.PlayerId] > 3 + ? Translator.GetString("SpeedBoosterSpeedLimit") + : string.Format(Translator.GetString("SpeedBoosterTaskDone"), Main.AllPlayerSpeed[player.PlayerId].ToString("0.0#####"))); - public override void Init() - { - On = false; - } - - public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) - { - if (player.IsAlive() && completedTaskCount + 1 <= totalTaskCount) - { - PlayerControl target = Main.AllAlivePlayerControls.RandomElement(); - Main.AllPlayerSpeed[target.PlayerId] += 0.5f; - - target.Notify(Main.AllPlayerSpeed[target.PlayerId] > 3 - ? Translator.GetString("SpeedBoosterSpeedLimit") - : string.Format(Translator.GetString("SpeedBoosterTaskDone"), Main.AllPlayerSpeed[player.PlayerId].ToString("0.0#####"))); - - target.MarkDirtySettings(); - } + target.MarkDirtySettings(); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Speedrunner.cs b/Roles/Crewmate/Speedrunner.cs index 0c5968d4d..9cbed00e6 100644 --- a/Roles/Crewmate/Speedrunner.cs +++ b/Roles/Crewmate/Speedrunner.cs @@ -2,92 +2,91 @@ using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Speedrunner : RoleBase { - internal class Speedrunner : RoleBase + public static bool On; + + public static OptionItem SpeedrunnerNotifyKillers; + public static OptionItem SpeedrunnerNotifyAtXTasksLeft; + public static OptionItem SpeedrunnerSpeed; + + private static PlayerControl SpeedrunnerPC; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; + On = true; + SpeedrunnerPC = playerId.GetPlayer(); + } - public static OptionItem SpeedrunnerNotifyKillers; - public static OptionItem SpeedrunnerNotifyAtXTasksLeft; - public static OptionItem SpeedrunnerSpeed; + public override void Init() + { + On = false; + SpeedrunnerPC = null; + } - private static PlayerControl SpeedrunnerPC; - public override bool IsEnable => On; + public override void SetupCustomOption() + { + SetupSingleRoleOptions(9170, TabGroup.CrewmateRoles, CustomRoles.Speedrunner, zeroOne: true); - public override void Add(byte playerId) + SpeedrunnerNotifyKillers = new BooleanOptionItem(9178, "SpeedrunnerNotifyKillers", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Speedrunner]); + + SpeedrunnerNotifyAtXTasksLeft = new IntegerOptionItem(9179, "SpeedrunnerNotifyAtXTasksLeft", new(0, 90, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Speedrunner]); + + SpeedrunnerSpeed = new FloatOptionItem(9177, "SpeedrunnerSpeed", new(0.1f, 3f, 0.1f), 1.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Speedrunner]) + .SetValueFormat(OptionFormat.Multiplier); + + OverrideTasksData.Create(9180, TabGroup.CrewmateRoles, CustomRoles.Speedrunner); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + Main.AllPlayerSpeed[playerId] = SpeedrunnerSpeed.GetFloat(); + } + + public override void OnTaskComplete(PlayerControl player, int CompletedTasksCount, int AllTasksCount) + { + if (!player.IsAlive()) return; + + int completedTasks = CompletedTasksCount + 1; + + if (completedTasks >= AllTasksCount) { - On = true; - SpeedrunnerPC = playerId.GetPlayer(); - } + Logger.Info("Speedrunner finished tasks", "Speedrunner"); + player.RPCPlayCustomSound("Congrats"); + GameData.Instance.CompletedTasks = GameData.Instance.TotalTasks; + Logger.Info($"TotalTaskCounts = {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", "TaskState.Update"); - public override void Init() - { - On = false; - SpeedrunnerPC = null; - } - - public override void SetupCustomOption() - { - SetupSingleRoleOptions(9170, TabGroup.CrewmateRoles, CustomRoles.Speedrunner, zeroOne: true); - - SpeedrunnerNotifyKillers = new BooleanOptionItem(9178, "SpeedrunnerNotifyKillers", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Speedrunner]); - - SpeedrunnerNotifyAtXTasksLeft = new IntegerOptionItem(9179, "SpeedrunnerNotifyAtXTasksLeft", new(0, 90, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Speedrunner]); - - SpeedrunnerSpeed = new FloatOptionItem(9177, "SpeedrunnerSpeed", new(0.1f, 3f, 0.1f), 1.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Speedrunner]) - .SetValueFormat(OptionFormat.Multiplier); - - OverrideTasksData.Create(9180, TabGroup.CrewmateRoles, CustomRoles.Speedrunner); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - Main.AllPlayerSpeed[playerId] = SpeedrunnerSpeed.GetFloat(); - } - - public override void OnTaskComplete(PlayerControl player, int CompletedTasksCount, int AllTasksCount) - { - if (!player.IsAlive()) return; - - int completedTasks = CompletedTasksCount + 1; - - if (completedTasks >= AllTasksCount) + LateTask.New(() => { - Logger.Info("Speedrunner finished tasks", "Speedrunner"); - player.RPCPlayCustomSound("Congrats"); - GameData.Instance.CompletedTasks = GameData.Instance.TotalTasks; - Logger.Info($"TotalTaskCounts = {GameData.Instance.CompletedTasks}/{GameData.Instance.TotalTasks}", "TaskState.Update"); - - LateTask.New(() => - { - if (!GameStates.IsEnded) CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Crewmate); - }, 1f, log: false); - } - else if (completedTasks >= SpeedrunnerNotifyAtXTasksLeft.GetInt() && SpeedrunnerNotifyKillers.GetBool()) - { - LateTask.New(() => - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (!pc.Is(Team.Crewmate)) - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - }, 0.1f, log: false); - } + if (!GameStates.IsEnded) CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Crewmate); + }, 1f, log: false); } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + else if (completedTasks >= SpeedrunnerNotifyAtXTasksLeft.GetInt() && SpeedrunnerNotifyKillers.GetBool()) { - if (seer.PlayerId != target.PlayerId || seer.Is(Team.Crewmate) || meeting || SpeedrunnerPC == null) return string.Empty; - - TaskState ts = SpeedrunnerPC.GetTaskState(); - if (ts.CompletedTasksCount < SpeedrunnerNotifyAtXTasksLeft.GetInt() || !SpeedrunnerNotifyKillers.GetBool()) return string.Empty; - - string speedrunnerName = SpeedrunnerPC.PlayerId.ColoredPlayerName(); - string notifyString = Translator.GetString("SpeedrunnerHasXTasksLeft"); - return string.Format(notifyString, speedrunnerName, ts.RemainingTasksCount); + LateTask.New(() => + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (!pc.Is(Team.Crewmate)) + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + }, 0.1f, log: false); } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.Is(Team.Crewmate) || meeting || SpeedrunnerPC == null) return string.Empty; + + TaskState ts = SpeedrunnerPC.GetTaskState(); + if (ts.CompletedTasksCount < SpeedrunnerNotifyAtXTasksLeft.GetInt() || !SpeedrunnerNotifyKillers.GetBool()) return string.Empty; + + string speedrunnerName = SpeedrunnerPC.PlayerId.ColoredPlayerName(); + string notifyString = Translator.GetString("SpeedrunnerHasXTasksLeft"); + return string.Format(notifyString, speedrunnerName, ts.RemainingTasksCount); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Spiritualist.cs b/Roles/Crewmate/Spiritualist.cs index ee5a9dd6b..c93814b59 100644 --- a/Roles/Crewmate/Spiritualist.cs +++ b/Roles/Crewmate/Spiritualist.cs @@ -3,135 +3,134 @@ using System.Linq; using static EHR.Options; using static EHR.Translator; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Spiritualist : RoleBase { - internal class Spiritualist : RoleBase + private const int Id = 8100; + + public static List PlayerIdList = []; + + private static OptionItem ShowGhostArrowEverySeconds; + private static OptionItem ShowGhostArrowForSeconds; + + public static byte SpiritualistTarget; + private long LastGhostArrowShowTime; + private long ShowGhostArrowUntil; + private byte SpiritualistId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + private bool ShowArrow { - private const int Id = 8100; - - public static List PlayerIdList = []; - - private static OptionItem ShowGhostArrowEverySeconds; - private static OptionItem ShowGhostArrowForSeconds; - - public static byte SpiritualistTarget; - private long LastGhostArrowShowTime; - private long ShowGhostArrowUntil; - private byte SpiritualistId; - - public override bool IsEnable => PlayerIdList.Count > 0; - - private bool ShowArrow + get { - get + long timestamp = Utils.TimeStamp; + + if (LastGhostArrowShowTime == 0 || LastGhostArrowShowTime + (long)ShowGhostArrowEverySeconds.GetFloat() <= timestamp) { - long timestamp = Utils.TimeStamp; - - if (LastGhostArrowShowTime == 0 || LastGhostArrowShowTime + (long)ShowGhostArrowEverySeconds.GetFloat() <= timestamp) - { - LastGhostArrowShowTime = timestamp; - ShowGhostArrowUntil = timestamp + (long)ShowGhostArrowForSeconds.GetFloat(); - return true; - } - - return ShowGhostArrowUntil >= timestamp; + LastGhostArrowShowTime = timestamp; + ShowGhostArrowUntil = timestamp + (long)ShowGhostArrowForSeconds.GetFloat(); + return true; } - } - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Spiritualist); - - ShowGhostArrowEverySeconds = new FloatOptionItem(Id + 10, "SpiritualistShowGhostArrowEverySeconds", new(1f, 60f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritualist]) - .SetValueFormat(OptionFormat.Seconds); - - ShowGhostArrowForSeconds = new FloatOptionItem(Id + 11, "SpiritualistShowGhostArrowForSeconds", new(1f, 60f, 1f), 2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritualist]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - SpiritualistTarget = new(); - LastGhostArrowShowTime = 0; - ShowGhostArrowUntil = 0; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - SpiritualistTarget = byte.MaxValue; - LastGhostArrowShowTime = 0; - ShowGhostArrowUntil = 0; - SpiritualistId = playerId; - } - - public static void OnReportDeadBody(NetworkedPlayerInfo target) - { - if (target == null) return; - - if (SpiritualistTarget != byte.MaxValue) RemoveTarget(); - - SpiritualistTarget = target.PlayerId; - } - - public override void AfterMeetingTasks() - { - foreach (byte spiritualist in PlayerIdList) - { - PlayerControl player = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == spiritualist); - if (!player.IsAlive()) continue; - - LastGhostArrowShowTime = 0; - ShowGhostArrowUntil = 0; - - PlayerControl target = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == SpiritualistTarget); - if (target == null) continue; - - target.Notify(GetString("SpiritualistTargetMessage")); - - TargetArrow.Add(spiritualist, target.PlayerId); - - var writer = CustomRpcSender.Create("SpiritualistSendMessage"); - writer.StartMessage(target.GetClientId()); - - writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) - .Write(target.Data.NetId) - .Write(GetString("SpiritualistNoticeTitle")) - .EndRpc(); - - writer.StartRpc(target.NetId, (byte)RpcCalls.SendChat) - .Write(GetString("SpiritualistNoticeMessage")) - .EndRpc(); - - writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) - .Write(target.Data.NetId) - .Write(target.Data.PlayerName) - .EndRpc(); - - writer.EndMessage(); - writer.SendMessage(); - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!seer.IsAlive() || seer.PlayerId != SpiritualistId) return string.Empty; - - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (GameStates.IsMeeting) return string.Empty; - - return SpiritualistTarget != byte.MaxValue && ShowArrow ? Utils.ColorString(seer.GetRoleColor(), TargetArrow.GetArrows(seer, SpiritualistTarget)) : string.Empty; - } - - public static void RemoveTarget() - { - foreach (byte spiritualist in PlayerIdList) TargetArrow.Remove(spiritualist, SpiritualistTarget); - - SpiritualistTarget = byte.MaxValue; + return ShowGhostArrowUntil >= timestamp; } } + + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Spiritualist); + + ShowGhostArrowEverySeconds = new FloatOptionItem(Id + 10, "SpiritualistShowGhostArrowEverySeconds", new(1f, 60f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritualist]) + .SetValueFormat(OptionFormat.Seconds); + + ShowGhostArrowForSeconds = new FloatOptionItem(Id + 11, "SpiritualistShowGhostArrowForSeconds", new(1f, 60f, 1f), 2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritualist]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + SpiritualistTarget = new(); + LastGhostArrowShowTime = 0; + ShowGhostArrowUntil = 0; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + SpiritualistTarget = byte.MaxValue; + LastGhostArrowShowTime = 0; + ShowGhostArrowUntil = 0; + SpiritualistId = playerId; + } + + public static void OnReportDeadBody(NetworkedPlayerInfo target) + { + if (target == null) return; + + if (SpiritualistTarget != byte.MaxValue) RemoveTarget(); + + SpiritualistTarget = target.PlayerId; + } + + public override void AfterMeetingTasks() + { + foreach (byte spiritualist in PlayerIdList) + { + PlayerControl player = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == spiritualist); + if (!player.IsAlive()) continue; + + LastGhostArrowShowTime = 0; + ShowGhostArrowUntil = 0; + + PlayerControl target = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == SpiritualistTarget); + if (target == null) continue; + + target.Notify(GetString("SpiritualistTargetMessage")); + + TargetArrow.Add(spiritualist, target.PlayerId); + + var writer = CustomRpcSender.Create("SpiritualistSendMessage"); + writer.StartMessage(target.GetClientId()); + + writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) + .Write(target.Data.NetId) + .Write(GetString("SpiritualistNoticeTitle")) + .EndRpc(); + + writer.StartRpc(target.NetId, (byte)RpcCalls.SendChat) + .Write(GetString("SpiritualistNoticeMessage")) + .EndRpc(); + + writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) + .Write(target.Data.NetId) + .Write(target.Data.PlayerName) + .EndRpc(); + + writer.EndMessage(); + writer.SendMessage(); + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!seer.IsAlive() || seer.PlayerId != SpiritualistId) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + if (GameStates.IsMeeting) return string.Empty; + + return SpiritualistTarget != byte.MaxValue && ShowArrow ? Utils.ColorString(seer.GetRoleColor(), TargetArrow.GetArrows(seer, SpiritualistTarget)) : string.Empty; + } + + public static void RemoveTarget() + { + foreach (byte spiritualist in PlayerIdList) TargetArrow.Remove(spiritualist, SpiritualistTarget); + + SpiritualistTarget = byte.MaxValue; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Spy.cs b/Roles/Crewmate/Spy.cs index 53584336c..f779f3834 100644 --- a/Roles/Crewmate/Spy.cs +++ b/Roles/Crewmate/Spy.cs @@ -2,130 +2,129 @@ using System.Collections.Generic; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; +using static Utils; + +public class Spy : RoleBase { - using static Options; - using static Utils; + private const int Id = 640400; + private static List PlayerIdList = []; + public static Dictionary SpyRedNameList = []; - public class Spy : RoleBase + private static OptionItem SpyRedNameDur; + private static OptionItem UseLimitOpt; + public static OptionItem SpyAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private long LastUpdate; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640400; - private static List PlayerIdList = []; - public static Dictionary SpyRedNameList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Spy); - private static OptionItem SpyRedNameDur; - private static OptionItem UseLimitOpt; - public static OptionItem SpyAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + UseLimitOpt = new IntegerOptionItem(Id + 10, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) + .SetValueFormat(OptionFormat.Times); - private long LastUpdate; + SpyRedNameDur = new FloatOptionItem(Id + 11, "SpyRedNameDur", new(0f, 70f, 1f), 3f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + SpyAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + SpyRedNameList = []; + } + + public override void Add(byte playerId) + { + LastUpdate = TimeStamp; + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + } + + private static void SendRPC(int operate, byte id = byte.MaxValue, bool changeColor = false) + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncSpy, SendOption.Reliable); + writer.Write(operate); + + switch (operate) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Spy); - - UseLimitOpt = new IntegerOptionItem(Id + 10, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) - .SetValueFormat(OptionFormat.Times); - - SpyRedNameDur = new FloatOptionItem(Id + 11, "SpyRedNameDur", new(0f, 70f, 1f), 3f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) - .SetValueFormat(OptionFormat.Seconds); - - SpyAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.5f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 13, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spy]) - .SetValueFormat(OptionFormat.Times); + case 1: // Red Name Add + writer.Write(id); + writer.Write(SpyRedNameList[id].ToString()); + break; + case 3: // Red Name Remove + writer.Write(id); + writer.Write(changeColor); + break; } - public override void Init() + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + int operate = reader.ReadInt32(); + + switch (operate) { - PlayerIdList = []; - SpyRedNameList = []; + case 1: + byte susId = reader.ReadByte(); + string stimeStamp = reader.ReadString(); + if (long.TryParse(stimeStamp, out long timeStamp)) SpyRedNameList[susId] = timeStamp; + + return; + case 3: + SpyRedNameList.Remove(reader.ReadByte()); + reader.ReadBoolean(); + return; } + } - public override void Add(byte playerId) + public static bool OnKillAttempt(PlayerControl killer, PlayerControl target) // Special handling for Spy ---- remains as a static method + { + if (killer == null || target == null || !target.Is(CustomRoles.Spy) || killer.PlayerId == target.PlayerId || target.GetAbilityUseLimit() < 1) return true; + + target.RpcRemoveAbilityUse(); + SpyRedNameList.TryAdd(killer.PlayerId, TimeStamp); + SendRPC(1, killer.PlayerId); + NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc == null || !pc.Is(CustomRoles.Spy) || SpyRedNameList.Count == 0) return; + + long now = TimeStamp; + if (now == LastUpdate) return; + + LastUpdate = now; + + foreach (KeyValuePair x in SpyRedNameList) { - LastUpdate = TimeStamp; - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - } - - private static void SendRPC(int operate, byte id = byte.MaxValue, bool changeColor = false) - { - if (!DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncSpy, SendOption.Reliable); - writer.Write(operate); - - switch (operate) + if (x.Value + SpyRedNameDur.GetInt() < now || !GameStates.IsInTask) { - case 1: // Red Name Add - writer.Write(id); - writer.Write(SpyRedNameList[id].ToString()); - break; - case 3: // Red Name Remove - writer.Write(id); - writer.Write(changeColor); - break; - } - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - int operate = reader.ReadInt32(); - - switch (operate) - { - case 1: - byte susId = reader.ReadByte(); - string stimeStamp = reader.ReadString(); - if (long.TryParse(stimeStamp, out long timeStamp)) SpyRedNameList[susId] = timeStamp; - - return; - case 3: - SpyRedNameList.Remove(reader.ReadByte()); - reader.ReadBoolean(); - return; - } - } - - public static bool OnKillAttempt(PlayerControl killer, PlayerControl target) // Special handling for Spy ---- remains as a static method - { - if (killer == null || target == null || !target.Is(CustomRoles.Spy) || killer.PlayerId == target.PlayerId || target.GetAbilityUseLimit() < 1) return true; - - target.RpcRemoveAbilityUse(); - SpyRedNameList.TryAdd(killer.PlayerId, TimeStamp); - SendRPC(1, killer.PlayerId); - NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc == null || !pc.Is(CustomRoles.Spy) || SpyRedNameList.Count == 0) return; - - long now = TimeStamp; - if (now == LastUpdate) return; - - LastUpdate = now; - - foreach (KeyValuePair x in SpyRedNameList) - { - if (x.Value + SpyRedNameDur.GetInt() < now || !GameStates.IsInTask) - { - SpyRedNameList.Remove(x.Key); - SendRPC(3, x.Key, true); - NotifyRoles(SpecifySeer: pc, SpecifyTarget: x.Key.GetPlayer()); - } + SpyRedNameList.Remove(x.Key); + SendRPC(3, x.Key, true); + NotifyRoles(SpecifySeer: pc, SpecifyTarget: x.Key.GetPlayer()); } } } diff --git a/Roles/Crewmate/SuperStar.cs b/Roles/Crewmate/SuperStar.cs index d1e110e26..079cf6a8e 100644 --- a/Roles/Crewmate/SuperStar.cs +++ b/Roles/Crewmate/SuperStar.cs @@ -1,33 +1,32 @@ using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class SuperStar : RoleBase { - internal class SuperStar : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(6000, TabGroup.CrewmateRoles, CustomRoles.SuperStar); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(6000, TabGroup.CrewmateRoles, CustomRoles.SuperStar); + Options.EveryOneKnowSuperStar = new BooleanOptionItem(6010, "EveryOneKnowSuperStar", true, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SuperStar]); + } - Options.EveryOneKnowSuperStar = new BooleanOptionItem(6010, "EveryOneKnowSuperStar", true, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SuperStar]); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !Main.AllAlivePlayerControls.Any(x => x.PlayerId != killer.PlayerId && x.PlayerId != target.PlayerId && Vector2.Distance(x.Pos(), target.Pos()) < 2f); - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !Main.AllAlivePlayerControls.Any(x => x.PlayerId != killer.PlayerId && x.PlayerId != target.PlayerId && Vector2.Distance(x.Pos(), target.Pos()) < 2f); } } \ No newline at end of file diff --git a/Roles/Crewmate/SwordsMan.cs b/Roles/Crewmate/SwordsMan.cs index 5b62946ca..bea506355 100644 --- a/Roles/Crewmate/SwordsMan.cs +++ b/Roles/Crewmate/SwordsMan.cs @@ -4,102 +4,101 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class SwordsMan : RoleBase { - public class SwordsMan : RoleBase + private const int Id = 9000; + private static List PlayerIdList = []; + public static List Killed = []; + private static OptionItem CanVent; + private static OptionItem KCD; + public static OptionItem UsePet; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 9000; - private static List PlayerIdList = []; - public static List Killed = []; - private static OptionItem CanVent; - private static OptionItem KCD; - public static OptionItem UsePet; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.SwordsMan); - public override bool IsEnable => PlayerIdList.Count > 0; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", false, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SwordsMan]); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.SwordsMan); + KCD = new FloatOptionItem(Id + 9, "KillCooldown", new(0f, 120f, 0.5f), 15f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SwordsMan]) + .SetValueFormat(OptionFormat.Seconds); - CanVent = new BooleanOptionItem(Id + 11, "CanVent", false, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SwordsMan]); + UsePet = Options.CreatePetUseSetting(Id + 10, CustomRoles.SwordsMan); + } - KCD = new FloatOptionItem(Id + 9, "KillCooldown", new(0f, 120f, 0.5f), 15f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SwordsMan]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + Killed = []; + PlayerIdList = []; + } - UsePet = Options.CreatePetUseSetting(Id + 10, CustomRoles.SwordsMan); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = IsKilled(id) ? 300f : KCD.GetFloat(); + } - public override void Init() - { - Killed = []; - PlayerIdList = []; - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = IsKilled(id) ? 300f : KCD.GetFloat(); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + public override string GetProgressText(byte id, bool comms) + { + return Utils.ColorString(!IsKilled(id) ? Utils.GetRoleColor(CustomRoles.SwordsMan).ShadeColor(0.25f) : Color.gray, !IsKilled(id) ? "(1)" : "(0)"); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return !Main.PlayerStates[pc.PlayerId].IsDead + && !IsKilled(pc.PlayerId); + } - public override string GetProgressText(byte id, bool comms) - { - return Utils.ColorString(!IsKilled(id) ? Utils.GetRoleColor(CustomRoles.SwordsMan).ShadeColor(0.25f) : Color.gray, !IsKilled(id) ? "(1)" : "(0)"); - } + private static bool IsKilled(byte playerId) + { + return Killed.Contains(playerId); + } - public override bool CanUseKillButton(PlayerControl pc) - { - return !Main.PlayerStates[pc.PlayerId].IsDead - && !IsKilled(pc.PlayerId); - } + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - private static bool IsKilled(byte playerId) - { - return Killed.Contains(playerId); - } + public static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SwordsManKill, SendOption.Reliable); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; + public static void ReceiveRPC(MessageReader reader) + { + byte SwordsManId = reader.ReadByte(); + if (!Killed.Contains(SwordsManId)) Killed.Add(SwordsManId); + } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SwordsManKill, SendOption.Reliable); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return CanUseKillButton(killer); + } - public static void ReceiveRPC(MessageReader reader) - { - byte SwordsManId = reader.ReadByte(); - if (!Killed.Contains(SwordsManId)) Killed.Add(SwordsManId); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return CanUseKillButton(killer); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - SendRPC(killer.PlayerId); - Killed.Add(killer.PlayerId); - SetKillCooldown(killer.PlayerId); - killer.RpcChangeRoleBasis(CustomRoles.CrewmateEHR); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); - } + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + SendRPC(killer.PlayerId); + Killed.Add(killer.PlayerId); + SetKillCooldown(killer.PlayerId); + killer.RpcChangeRoleBasis(CustomRoles.CrewmateEHR); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); } } \ No newline at end of file diff --git a/Roles/Crewmate/TaskManager.cs b/Roles/Crewmate/TaskManager.cs index 4e089717e..f6541be5c 100644 --- a/Roles/Crewmate/TaskManager.cs +++ b/Roles/Crewmate/TaskManager.cs @@ -1,35 +1,34 @@ -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class TaskManager : RoleBase { - internal class TaskManager : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(5575, TabGroup.CrewmateRoles, CustomRoles.TaskManager); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(5575, TabGroup.CrewmateRoles, CustomRoles.TaskManager); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); + ProgressText.Append(Utils.GetTaskCount(playerId, comms)); - ProgressText.Append(Utils.GetTaskCount(playerId, comms)); + string totalCompleted = comms ? "?" : $"{GameData.Instance.CompletedTasks}"; + ProgressText.Append($" - {totalCompleted}/{GameData.Instance.TotalTasks}"); - string totalCompleted = comms ? "?" : $"{GameData.Instance.CompletedTasks}"; - ProgressText.Append($" - {totalCompleted}/{GameData.Instance.TotalTasks}"); - - return ProgressText.ToString(); - } + return ProgressText.ToString(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Telekinetic.cs b/Roles/Crewmate/Telekinetic.cs index 9ad60b9a6..1981385bf 100644 --- a/Roles/Crewmate/Telekinetic.cs +++ b/Roles/Crewmate/Telekinetic.cs @@ -5,226 +5,225 @@ using EHR.Neutral; using EHR.Patches; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Telekinetic : RoleBase { - public class Telekinetic : RoleBase + public static bool On; + + private static OptionItem FreezeDuration; + private static OptionItem ShieldDuration; + private static OptionItem SpeedDuration; + private static OptionItem IncreasedSpeed; + + private Mode CurrentMode; + private long LastUpdate; + private bool Shielded; + private PlayerControl TelekineticPC; + private int Timer; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + const int id = 649350; + const TabGroup tab = TabGroup.CrewmateRoles; + const CustomRoles role = CustomRoles.Telekinetic; - private static OptionItem FreezeDuration; - private static OptionItem ShieldDuration; - private static OptionItem SpeedDuration; - private static OptionItem IncreasedSpeed; + Options.SetupRoleOptions(id, tab, role); - private Mode CurrentMode; - private long LastUpdate; - private bool Shielded; - private PlayerControl TelekineticPC; - private int Timer; - public override bool IsEnable => On; + FreezeDuration = new IntegerOptionItem(id + 2, "Telekinetic.FreezeDuration", new(0, 60, 1), 10, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + ShieldDuration = new IntegerOptionItem(id + 3, "Telekinetic.ShieldDuration", new(0, 60, 1), 20, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); + + SpeedDuration = new IntegerOptionItem(id + 4, "Telekinetic.SpeedDuration", new(0, 60, 1), 15, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); + + IncreasedSpeed = new FloatOptionItem(id + 5, "Telekinetic.IncreasedSpeed", new(0.05f, 5f, 0.05f), 2f, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + TelekineticPC = Utils.GetPlayerById(playerId); + CurrentMode = default; + Timer = 40; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 0f; + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance || Main.HasJustStarted) return; + + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + + LastUpdate = now; + + switch (Timer) { - const int id = 649350; - const TabGroup tab = TabGroup.CrewmateRoles; - const CustomRoles role = CustomRoles.Telekinetic; - - Options.SetupRoleOptions(id, tab, role); - - FreezeDuration = new IntegerOptionItem(id + 2, "Telekinetic.FreezeDuration", new(0, 60, 1), 10, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - ShieldDuration = new IntegerOptionItem(id + 3, "Telekinetic.ShieldDuration", new(0, 60, 1), 20, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - SpeedDuration = new IntegerOptionItem(id + 4, "Telekinetic.SpeedDuration", new(0, 60, 1), 15, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - IncreasedSpeed = new FloatOptionItem(id + 5, "Telekinetic.IncreasedSpeed", new(0.05f, 5f, 0.05f), 2f, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Multiplier); + case > 60: + pc.Suicide(); + break; + case > 0: + Timer--; + SendRPC(); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + break; } + } - public override void Init() + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + CurrentMode = (Mode)(((int)CurrentMode + 1) % Enum.GetValues(typeof(Mode)).Length); + SendRPC(); + } + + public override void OnPet(PlayerControl pc) + { + PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); + bool hasTarget = target != null; + + switch (CurrentMode) { - On = false; - } + case Mode.TeleportTarget when hasTarget: + target.TP(Pelican.GetBlackRoomPS()); + Timer += 40; + Freeze(); + break; + case Mode.TeleportSelf: + pc.TP(Pelican.GetBlackRoomPS()); + Timer += 30; + Freeze(); + break; + case Mode.Freeze when hasTarget: + float speed = Main.AllPlayerSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); - public override void Add(byte playerId) - { - On = true; - TelekineticPC = Utils.GetPlayerById(playerId); - CurrentMode = default; - Timer = 40; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = 0f; - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance || Main.HasJustStarted) return; - - long now = Utils.TimeStamp; - if (now == LastUpdate) return; - - LastUpdate = now; - - switch (Timer) - { - case > 60: - pc.Suicide(); - break; - case > 0: - Timer--; - SendRPC(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - break; - } - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - CurrentMode = (Mode)(((int)CurrentMode + 1) % Enum.GetValues(typeof(Mode)).Length); - SendRPC(); - } - - public override void OnPet(PlayerControl pc) - { - PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); - bool hasTarget = target != null; - - switch (CurrentMode) - { - case Mode.TeleportTarget when hasTarget: - target.TP(Pelican.GetBlackRoomPS()); - Timer += 40; - Freeze(); - break; - case Mode.TeleportSelf: - pc.TP(Pelican.GetBlackRoomPS()); - Timer += 30; - Freeze(); - break; - case Mode.Freeze when hasTarget: - float speed = Main.AllPlayerSpeed[target.PlayerId]; - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + LateTask.New(() => + { + Main.AllPlayerSpeed[target.PlayerId] = speed; target.MarkDirtySettings(); + }, FreezeDuration.GetFloat(), "Telekinetic.Freeze"); - LateTask.New(() => - { - Main.AllPlayerSpeed[target.PlayerId] = speed; - target.MarkDirtySettings(); - }, FreezeDuration.GetFloat(), "Telekinetic.Freeze"); + Timer += 35; - Timer += 35; + if (target.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); - if (target.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - - break; - case Mode.Kill when hasTarget: - pc.RpcCheckAndMurder(target); - Timer += 60; - Freeze(); - break; - case Mode.Shield: - Shielded = true; - LateTask.New(() => Shielded = false, ShieldDuration.GetFloat(), "Telekinetic.Shield"); - Timer += 45; - Freeze(); - break; - case Mode.Speed: - float selfSpeed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.AllPlayerSpeed[pc.PlayerId] = IncreasedSpeed.GetFloat(); - pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[pc.PlayerId] = selfSpeed; - pc.MarkDirtySettings(); - }, SpeedDuration.GetFloat(), "Telekinetic.Speed"); - - Timer += 30; - break; - case Mode.Doors: - DoorsReset.OpenAllDoors(); - Timer += 35; - Freeze(); - break; - } - - SendRPC(); - return; - - void Freeze() - { - float speed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + break; + case Mode.Kill when hasTarget: + pc.RpcCheckAndMurder(target); + Timer += 60; + Freeze(); + break; + case Mode.Shield: + Shielded = true; + LateTask.New(() => Shielded = false, ShieldDuration.GetFloat(), "Telekinetic.Shield"); + Timer += 45; + Freeze(); + break; + case Mode.Speed: + float selfSpeed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = IncreasedSpeed.GetFloat(); pc.MarkDirtySettings(); LateTask.New(() => { - Main.AllPlayerSpeed[pc.PlayerId] = speed; + Main.AllPlayerSpeed[pc.PlayerId] = selfSpeed; pc.MarkDirtySettings(); - }, 5f, "Telekinetic.SelfFreeze"); - } + }, SpeedDuration.GetFloat(), "Telekinetic.Speed"); + + Timer += 30; + break; + case Mode.Doors: + DoorsReset.OpenAllDoors(); + Timer += 35; + Freeze(); + break; } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !Shielded; - } + SendRPC(); + return; - public override void AfterMeetingTasks() + void Freeze() { - Timer = 40; - SendRPC(); - } + float speed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.MarkDirtySettings(); - private void SendRPC() - { - Utils.SendRPC(CustomRPC.SyncRoleData, TelekineticPC.PlayerId, Timer, (int)CurrentMode); - } - - public void ReceiveRPC(MessageReader reader) - { - Timer = reader.ReadPackedInt32(); - CurrentMode = (Mode)reader.ReadPackedInt32(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != TelekineticPC.PlayerId || (seer.IsModClient() && !hud) || meeting) return string.Empty; - - return string.Format(Translator.GetString("Telekinetic.Suffix"), Translator.GetString($"Telekinetic.Mode.{CurrentMode}")); - } - - public override string GetProgressText(byte playerId, bool comms) - { - return $"<#ffffff>{Timer}{base.GetProgressText(playerId, comms)}"; - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } - - private enum Mode - { - TeleportTarget, - TeleportSelf, - Freeze, - Kill, - Shield, - Speed, - Doors + LateTask.New(() => + { + Main.AllPlayerSpeed[pc.PlayerId] = speed; + pc.MarkDirtySettings(); + }, 5f, "Telekinetic.SelfFreeze"); } } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !Shielded; + } + + public override void AfterMeetingTasks() + { + Timer = 40; + SendRPC(); + } + + private void SendRPC() + { + Utils.SendRPC(CustomRPC.SyncRoleData, TelekineticPC.PlayerId, Timer, (int)CurrentMode); + } + + public void ReceiveRPC(MessageReader reader) + { + Timer = reader.ReadPackedInt32(); + CurrentMode = (Mode)reader.ReadPackedInt32(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != TelekineticPC.PlayerId || (seer.IsModClient() && !hud) || meeting) return string.Empty; + + return string.Format(Translator.GetString("Telekinetic.Suffix"), Translator.GetString($"Telekinetic.Mode.{CurrentMode}")); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return $"<#ffffff>{Timer}{base.GetProgressText(playerId, comms)}"; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } + + private enum Mode + { + TeleportTarget, + TeleportSelf, + Freeze, + Kill, + Shield, + Speed, + Doors + } } \ No newline at end of file diff --git a/Roles/Crewmate/Tether.cs b/Roles/Crewmate/Tether.cs index 67231b154..bd8d14804 100644 --- a/Roles/Crewmate/Tether.cs +++ b/Roles/Crewmate/Tether.cs @@ -3,152 +3,151 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +using static Options; + +public class Tether : RoleBase { - using static Options; + private const int Id = 640300; + public static List PlayerIdList = []; - public class Tether : RoleBase + public static OptionItem VentCooldown; + public static OptionItem UseLimitOpt; + public static OptionItem TetherAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + public static OptionItem CancelVote; + + private byte Target = byte.MaxValue; + private byte TetherId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640300; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Tether); - public static OptionItem VentCooldown; - public static OptionItem UseLimitOpt; - public static OptionItem TetherAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - public static OptionItem CancelVote; + VentCooldown = new FloatOptionItem(Id + 10, "VentCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) + .SetValueFormat(OptionFormat.Seconds); - private byte Target = byte.MaxValue; - private byte TetherId; + UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + TetherAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 14, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) + .SetValueFormat(OptionFormat.Times); + + CancelVote = CreateVoteCancellingUseSetting(Id + 13, CustomRoles.Tether, TabGroup.CrewmateRoles); + } + + public override void Init() + { + PlayerIdList = []; + Target = byte.MaxValue; + TetherId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + Target = byte.MaxValue; + TetherId = playerId; + } + + private void SendRPCSyncTarget() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetTetherTarget, SendOption.Reliable); + writer.Write(TetherId); + writer.Write(Target); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCSyncTarget(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not Tether th) return; + + th.Target = reader.ReadByte(); + } + + public override void OnPet(PlayerControl pc) + { + Teleport(pc, 0, true); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + Teleport(pc, vent.Id); + } + + private void Teleport(PlayerControl pc, int ventId, bool isPet = false) + { + if (pc == null) return; + + if (Target != byte.MaxValue) { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Tether); - - VentCooldown = new FloatOptionItem(Id + 10, "VentCooldown", new(0f, 70f, 1f), 15f, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) - .SetValueFormat(OptionFormat.Times); - - TetherAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(Id + 14, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tether]) - .SetValueFormat(OptionFormat.Times); - - CancelVote = CreateVoteCancellingUseSetting(Id + 13, CustomRoles.Tether, TabGroup.CrewmateRoles); - } - - public override void Init() - { - PlayerIdList = []; - Target = byte.MaxValue; - TetherId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - Target = byte.MaxValue; - TetherId = playerId; - } - - private void SendRPCSyncTarget() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetTetherTarget, SendOption.Reliable); - writer.Write(TetherId); - writer.Write(Target); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCSyncTarget(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not Tether th) return; - - th.Target = reader.ReadByte(); - } - - public override void OnPet(PlayerControl pc) - { - Teleport(pc, 0, true); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - Teleport(pc, vent.Id); - } - - private void Teleport(PlayerControl pc, int ventId, bool isPet = false) - { - if (pc == null) return; - - if (Target != byte.MaxValue) + LateTask.New(() => { - LateTask.New(() => - { - if (GameStates.IsInTask) pc.TP(Utils.GetPlayerById(Target).Pos()); - }, isPet ? 0.1f : 2f, "Tether TP"); - } - else if (!isPet) LateTask.New(() => { pc.MyPhysics?.RpcBootFromVent(ventId); }, 0.5f, "Tether No Target Boot From Vent"); + if (GameStates.IsInTask) pc.TP(Utils.GetPlayerById(Target).Pos()); + }, isPet ? 0.1f : 2f, "Tether TP"); } + else if (!isPet) LateTask.New(() => { pc.MyPhysics?.RpcBootFromVent(ventId); }, 0.5f, "Tether No Target Boot From Vent"); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override bool OnVote(PlayerControl pc, PlayerControl target) + { + if (pc == null || target == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; + + if (pc.GetAbilityUseLimit() >= 1) { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override bool OnVote(PlayerControl pc, PlayerControl target) - { - if (pc == null || target == null || pc.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(pc.PlayerId)) return false; - - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - Target = target.PlayerId; - SendRPCSyncTarget(); - Main.DontCancelVoteList.Add(pc.PlayerId); - return true; - } - - return false; - } - - public override void OnReportDeadBody() - { - Target = byte.MaxValue; + pc.RpcRemoveAbilityUse(); + Target = target.PlayerId; SendRPCSyncTarget(); + Main.DontCancelVoteList.Add(pc.PlayerId); + return true; } - public override string GetProgressText(byte playerId, bool comms) - { - var sb = new StringBuilder(); + return false; + } - sb.Append(Utils.GetAbilityUseLimitDisplay(playerId, Target != byte.MaxValue)); - sb.Append(Utils.GetTaskCount(playerId, comms)); + public override void OnReportDeadBody() + { + Target = byte.MaxValue; + SendRPCSyncTarget(); + } - return sb.ToString(); - } + public override string GetProgressText(byte playerId, bool comms) + { + var sb = new StringBuilder(); - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - return Target != byte.MaxValue && seer.PlayerId == target.PlayerId && seer.PlayerId == TetherId ? $"Target: {Utils.GetPlayerById(Target).GetRealName()}" : string.Empty; - } + sb.Append(Utils.GetAbilityUseLimitDisplay(playerId, Target != byte.MaxValue)); + sb.Append(Utils.GetTaskCount(playerId, comms)); - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } + return sb.ToString(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return Target != byte.MaxValue && seer.PlayerId == target.PlayerId && seer.PlayerId == TetherId ? $"Target: {Utils.GetPlayerById(Target).GetRealName()}" : string.Empty; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Crewmate/TimeManager.cs b/Roles/Crewmate/TimeManager.cs index ea099ebc8..70dfa0cef 100644 --- a/Roles/Crewmate/TimeManager.cs +++ b/Roles/Crewmate/TimeManager.cs @@ -1,66 +1,65 @@ using System.Collections.Generic; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class TimeManager : RoleBase { - public class TimeManager : RoleBase + private const int Id = 8200; + public static List PlayerIdList = []; + private static OptionItem IncreaseMeetingTime; + public static OptionItem MeetingTimeLimit; + public static OptionItem MadMinMeetingTimeLimit; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 8200; - public static List PlayerIdList = []; - private static OptionItem IncreaseMeetingTime; - public static OptionItem MeetingTimeLimit; - public static OptionItem MadMinMeetingTimeLimit; + Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.TimeManager); - public override bool IsEnable => PlayerIdList.Count > 0; + IncreaseMeetingTime = new IntegerOptionItem(Id + 10, "TimeManagerIncreaseMeetingTime", new(1, 30, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeManager]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + MeetingTimeLimit = new IntegerOptionItem(Id + 11, "TimeManagerLimitMeetingTime", new(100, 500, 10), 150, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeManager]) + .SetValueFormat(OptionFormat.Seconds); + + MadMinMeetingTimeLimit = new IntegerOptionItem(Id + 12, "MadTimeManagerLimitMeetingTime", new(5, 150, 5), 50, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeManager]) + .SetValueFormat(OptionFormat.Seconds); + + Options.OverrideTasksData.Create(Id + 13, TabGroup.CrewmateRoles, CustomRoles.TimeManager); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + private static int AdditionalTime(byte id) + { + PlayerControl pc = Utils.GetPlayerById(id); + return PlayerIdList.Contains(id) && pc.IsAlive() ? IncreaseMeetingTime.GetInt() * pc.GetTaskState().CompletedTasksCount : 0; + } + + public static int TotalIncreasedMeetingTime() + { + var sec = 0; + + foreach (byte playerId in PlayerIdList.ToArray()) { - Options.SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.TimeManager); - - IncreaseMeetingTime = new IntegerOptionItem(Id + 10, "TimeManagerIncreaseMeetingTime", new(1, 30, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeManager]) - .SetValueFormat(OptionFormat.Seconds); - - MeetingTimeLimit = new IntegerOptionItem(Id + 11, "TimeManagerLimitMeetingTime", new(100, 500, 10), 150, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeManager]) - .SetValueFormat(OptionFormat.Seconds); - - MadMinMeetingTimeLimit = new IntegerOptionItem(Id + 12, "MadTimeManagerLimitMeetingTime", new(5, 150, 5), 50, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeManager]) - .SetValueFormat(OptionFormat.Seconds); - - Options.OverrideTasksData.Create(Id + 13, TabGroup.CrewmateRoles, CustomRoles.TimeManager); + if (Utils.GetPlayerById(playerId).Is(CustomRoles.Madmate)) + sec -= AdditionalTime(playerId); + else + sec += AdditionalTime(playerId); } - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - private static int AdditionalTime(byte id) - { - PlayerControl pc = Utils.GetPlayerById(id); - return PlayerIdList.Contains(id) && pc.IsAlive() ? IncreaseMeetingTime.GetInt() * pc.GetTaskState().CompletedTasksCount : 0; - } - - public static int TotalIncreasedMeetingTime() - { - var sec = 0; - - foreach (byte playerId in PlayerIdList.ToArray()) - { - if (Utils.GetPlayerById(playerId).Is(CustomRoles.Madmate)) - sec -= AdditionalTime(playerId); - else - sec += AdditionalTime(playerId); - } - - Logger.Info($"{sec}second", "TimeManager.TotalIncreasedMeetingTime"); - return sec; - } + Logger.Info($"{sec}second", "TimeManager.TotalIncreasedMeetingTime"); + return sec; } } \ No newline at end of file diff --git a/Roles/Crewmate/TimeMaster.cs b/Roles/Crewmate/TimeMaster.cs index 5688a229f..0b13c0511 100644 --- a/Roles/Crewmate/TimeMaster.cs +++ b/Roles/Crewmate/TimeMaster.cs @@ -3,148 +3,147 @@ using AmongUs.GameOptions; using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class TimeMaster : RoleBase { - internal class TimeMaster : RoleBase + public static bool On; + public static Dictionary TimeMasterBackTrack = []; + private static readonly Dictionary TimeMasterInProtect = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public static Dictionary TimeMasterBackTrack = []; - private static readonly Dictionary TimeMasterInProtect = []; - public override bool IsEnable => On; + SetupRoleOptions(8950, TabGroup.CrewmateRoles, CustomRoles.TimeMaster); - public override void SetupCustomOption() + TimeMasterSkillCooldown = new FloatOptionItem(8960, "TimeMasterSkillCooldown", new(0f, 180f, 1f), 20f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) + .SetValueFormat(OptionFormat.Seconds); + + TimeMasterSkillDuration = new FloatOptionItem(8961, "TimeMasterSkillDuration", new(0f, 180f, 1f), 15f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) + .SetValueFormat(OptionFormat.Seconds); + + TimeMasterMaxUses = new IntegerOptionItem(8962, "TimeMasterMaxUses", new(0, 180, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) + .SetValueFormat(OptionFormat.Times); + + TimeMasterAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(8963, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) + .SetValueFormat(OptionFormat.Times); + + TimeMasterAbilityChargesWhenFinishedTasks = new FloatOptionItem(8964, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(TimeMasterMaxUses.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = TimeMasterSkillCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + + ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, TimeMasterInProtect.ContainsKey(playerId))); + ProgressText.Append(Utils.GetTaskCount(playerId, comms)); + + return ProgressText.ToString(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("TimeMasterVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("TimeMasterVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + Rewind(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + pc.MyPhysics?.RpcBootFromVent(vent.Id); + Rewind(pc); + } + + private static void Rewind(PlayerControl pc) + { + if (TimeMasterInProtect.ContainsKey(pc.PlayerId)) return; + + if (pc.GetAbilityUseLimit() >= 1) { - SetupRoleOptions(8950, TabGroup.CrewmateRoles, CustomRoles.TimeMaster); + pc.RpcRemoveAbilityUse(); + TimeMasterInProtect.Remove(pc.PlayerId); + TimeMasterInProtect.Add(pc.PlayerId, Utils.TimeStamp); + pc.Notify(Translator.GetString("TimeMasterOnGuard"), TimeMasterSkillDuration.GetFloat()); - TimeMasterSkillCooldown = new FloatOptionItem(8960, "TimeMasterSkillCooldown", new(0f, 180f, 1f), 20f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) - .SetValueFormat(OptionFormat.Seconds); - - TimeMasterSkillDuration = new FloatOptionItem(8961, "TimeMasterSkillDuration", new(0f, 180f, 1f), 15f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) - .SetValueFormat(OptionFormat.Seconds); - - TimeMasterMaxUses = new IntegerOptionItem(8962, "TimeMasterMaxUses", new(0, 180, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) - .SetValueFormat(OptionFormat.Times); - - TimeMasterAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(8963, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.4f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) - .SetValueFormat(OptionFormat.Times); - - TimeMasterAbilityChargesWhenFinishedTasks = new FloatOptionItem(8964, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.TimeMaster]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(TimeMasterMaxUses.GetInt()); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = TimeMasterSkillCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); - - ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, TimeMasterInProtect.ContainsKey(playerId))); - ProgressText.Append(Utils.GetTaskCount(playerId, comms)); - - return ProgressText.ToString(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("TimeMasterVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("TimeMasterVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - Rewind(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - pc.MyPhysics?.RpcBootFromVent(vent.Id); - Rewind(pc); - } - - private static void Rewind(PlayerControl pc) - { - if (TimeMasterInProtect.ContainsKey(pc.PlayerId)) return; - - if (pc.GetAbilityUseLimit() >= 1) + foreach (PlayerControl player in Main.AllPlayerControls) { - pc.RpcRemoveAbilityUse(); - TimeMasterInProtect.Remove(pc.PlayerId); - TimeMasterInProtect.Add(pc.PlayerId, Utils.TimeStamp); - pc.Notify(Translator.GetString("TimeMasterOnGuard"), TimeMasterSkillDuration.GetFloat()); - - foreach (PlayerControl player in Main.AllPlayerControls) + if (TimeMasterBackTrack.TryGetValue(player.PlayerId, out Vector2 position)) { - if (TimeMasterBackTrack.TryGetValue(player.PlayerId, out Vector2 position)) - { - if (!pc.inVent && !pc.inMovingPlat && pc.IsAlive() && !pc.onLadder && pc.MyPhysics != null && !pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() && !pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() && player.PlayerId != pc.PlayerId) - player.TP(position); - else if (pc.PlayerId == player.PlayerId) player.MyPhysics?.RpcBootFromVent(Main.LastEnteredVent.TryGetValue(player.PlayerId, out Vent vent) ? vent.Id : player.PlayerId); + if (!pc.inVent && !pc.inMovingPlat && pc.IsAlive() && !pc.onLadder && pc.MyPhysics != null && !pc.MyPhysics.Animations.IsPlayingAnyLadderAnimation() && !pc.MyPhysics.Animations.IsPlayingEnterVentAnimation() && player.PlayerId != pc.PlayerId) + player.TP(position); + else if (pc.PlayerId == player.PlayerId) player.MyPhysics?.RpcBootFromVent(Main.LastEnteredVent.TryGetValue(player.PlayerId, out Vent vent) ? vent.Id : player.PlayerId); - TimeMasterBackTrack.Remove(player.PlayerId); - } - else TimeMasterBackTrack.Add(player.PlayerId, player.Pos()); + TimeMasterBackTrack.Remove(player.PlayerId); } - - if (pc.IsLocalPlayer()) - Achievements.Type.APerfectTimeToRewindIt.Complete(); - } - else pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (killer.PlayerId != target.PlayerId && TimeMasterInProtect.TryGetValue(target.PlayerId, out long ts) && ts + TimeMasterSkillDuration.GetInt() >= Utils.TimeStamp) - { - foreach (PlayerControl player in Main.AllPlayerControls) - if (!killer.Is(CustomRoles.Pestilence) && TimeMasterBackTrack.TryGetValue(player.PlayerId, out Vector2 pos)) - player.TP(pos); - - killer.SetKillCooldown(target: target); - return false; + else TimeMasterBackTrack.Add(player.PlayerId, player.Pos()); } - return true; + if (pc.IsLocalPlayer()) + Achievements.Type.APerfectTimeToRewindIt.Complete(); + } + else pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer.PlayerId != target.PlayerId && TimeMasterInProtect.TryGetValue(target.PlayerId, out long ts) && ts + TimeMasterSkillDuration.GetInt() >= Utils.TimeStamp) + { + foreach (PlayerControl player in Main.AllPlayerControls) + if (!killer.Is(CustomRoles.Pestilence) && TimeMasterBackTrack.TryGetValue(player.PlayerId, out Vector2 pos)) + player.TP(pos); + + killer.SetKillCooldown(target: target); + return false; } - public override void OnFixedUpdate(PlayerControl player) - { - byte playerId = player.PlayerId; + return true; + } - if (TimeMasterInProtect.TryGetValue(playerId, out long ttime) && ttime + TimeMasterSkillDuration.GetInt() < Utils.TimeStamp) - { - TimeMasterInProtect.Remove(playerId); - player.RpcResetAbilityCooldown(); - player.Notify(Translator.GetString("TimeMasterSkillStop"), (int)player.GetAbilityUseLimit()); - } - } + public override void OnFixedUpdate(PlayerControl player) + { + byte playerId = player.PlayerId; - public override bool CanUseVent(PlayerControl pc, int ventId) + if (TimeMasterInProtect.TryGetValue(playerId, out long ttime) && ttime + TimeMasterSkillDuration.GetInt() < Utils.TimeStamp) { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + TimeMasterInProtect.Remove(playerId); + player.RpcResetAbilityCooldown(); + player.Notify(Translator.GetString("TimeMasterSkillStop"), (int)player.GetAbilityUseLimit()); } } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/ToiletMaster.cs b/Roles/Crewmate/ToiletMaster.cs index 7d0a2cd31..c064c2ab5 100644 --- a/Roles/Crewmate/ToiletMaster.cs +++ b/Roles/Crewmate/ToiletMaster.cs @@ -6,326 +6,325 @@ using EHR.Modules; using UnityEngine; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class ToiletMaster : RoleBase { - public class ToiletMaster : RoleBase + public static bool On; + private static List Instances = []; + + public static OptionItem AbilityCooldown; + private static OptionItem AbilityUses; + private static OptionItem ToiletDuration; + private static OptionItem ToiletVisibility; + private static OptionItem ToiletUseRadius; + private static OptionItem ToiletUseTime; + private static OptionItem ToiletMaxUses; + private static OptionItem BrownPoopSpeedBoost; + private static OptionItem GreenPoopRadius; + private static OptionItem RedPoopRadius; + private static OptionItem RedPoopRoleBlockDuration; + private static OptionItem PurplePoopNotifyOnKillAttempt; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + private static readonly Dictionary PoopDurationSettings = []; + + private Dictionary ActivePoops = []; + + private long LastUpdate; + private Dictionary PlayersUsingToilet = []; + + private Dictionary Toilets = []; + private static ToiletVisibilityOptions ToiletVisible => (ToiletVisibilityOptions)ToiletVisibility.GetValue(); + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + var id = 644700; + const TabGroup tab = TabGroup.CrewmateRoles; + Poop[] poops = Enum.GetValues(); + Dictionary replacements = poops.ToDictionary(x => x.ToString(), x => Utils.ColorString(GetPoopColor(x), x.ToString())); - public static OptionItem AbilityCooldown; - private static OptionItem AbilityUses; - private static OptionItem ToiletDuration; - private static OptionItem ToiletVisibility; - private static OptionItem ToiletUseRadius; - private static OptionItem ToiletUseTime; - private static OptionItem ToiletMaxUses; - private static OptionItem BrownPoopSpeedBoost; - private static OptionItem GreenPoopRadius; - private static OptionItem RedPoopRadius; - private static OptionItem RedPoopRoleBlockDuration; - private static OptionItem PurplePoopNotifyOnKillAttempt; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; - private static readonly Dictionary PoopDurationSettings = []; + SetupRoleOptions(id++, tab, CustomRoles.ToiletMaster); - private Dictionary ActivePoops = []; + AbilityCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(0, 60, 1), 5, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Seconds); - private long LastUpdate; - private Dictionary PlayersUsingToilet = []; + AbilityUses = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 10, 1), 3, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); - private Dictionary Toilets = []; - private static ToiletVisibilityOptions ToiletVisible => (ToiletVisibilityOptions)ToiletVisibility.GetValue(); - public override bool IsEnable => On; + ToiletDuration = new IntegerOptionItem(++id, "TM.ToiletDuration", new(0, 60, 1), 10, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + ToiletVisibility = new StringOptionItem(++id, "TM.ToiletVisibility", Enum.GetNames(), 0, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + + ToiletUseRadius = new FloatOptionItem(++id, "TM.ToiletUseRadius", new(0f, 5f, 0.25f), 1f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Multiplier); + + ToiletUseTime = new IntegerOptionItem(++id, "TM.ToiletUseTime", new(0, 60, 1), 5, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Seconds); + + ToiletMaxUses = new IntegerOptionItem(++id, "TM.ToiletMaxUses", new(0, 10, 1), 3, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + + BrownPoopSpeedBoost = new FloatOptionItem(++id, "TM.BrownPoopSpeedBoost", new(0f, 3f, 0.05f), 0.5f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Multiplier); + + GreenPoopRadius = new FloatOptionItem(++id, "TM.GreenPoopRadius", new(0f, 5f, 0.25f), 1f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Multiplier); + + RedPoopRadius = new FloatOptionItem(++id, "TM.RedPoopRadius", new(0f, 5f, 0.25f), 1f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Multiplier); + + RedPoopRoleBlockDuration = new IntegerOptionItem(++id, "TM.RedPoopRoleBlockDuration", new(0, 60, 1), 10, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) + .SetValueFormat(OptionFormat.Seconds); + + PurplePoopNotifyOnKillAttempt = new BooleanOptionItem(++id, "TM.PurplePoopNotifyOnKillAttempt", false, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + + AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.6f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.1f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + + poops.Do(x => { - var id = 644700; - const TabGroup tab = TabGroup.CrewmateRoles; - Poop[] poops = Enum.GetValues(); - Dictionary replacements = poops.ToDictionary(x => x.ToString(), x => Utils.ColorString(GetPoopColor(x), x.ToString())); - - SetupRoleOptions(id++, tab, CustomRoles.ToiletMaster); - - AbilityCooldown = new IntegerOptionItem(++id, "AbilityCooldown", new(0, 60, 1), 5, tab) + PoopDurationSettings[x] = new IntegerOptionItem(++id, $"TM.{x}PoopDuration", new(0, 60, 1), 10, tab) .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) .SetValueFormat(OptionFormat.Seconds); + }); - AbilityUses = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 10, 1), 3, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + PoopDurationSettings.Values + .CombineWith([BrownPoopSpeedBoost], [GreenPoopRadius], [RedPoopRadius], [RedPoopRoleBlockDuration], [PurplePoopNotifyOnKillAttempt]) + .Do(x => x.ReplacementDictionary = replacements); + } - ToiletDuration = new IntegerOptionItem(++id, "TM.ToiletDuration", new(0, 60, 1), 10, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + On = false; + Instances = []; + Toilets = []; + ActivePoops = []; + PlayersUsingToilet = []; + } - ToiletVisibility = new StringOptionItem(++id, "TM.ToiletVisibility", Enum.GetNames(), 0, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + LastUpdate = 8 + AbilityCooldown.GetInt() + ToiletDuration.GetInt(); + playerId.SetAbilityUseLimit(AbilityUses.GetFloat()); + } - ToiletUseRadius = new FloatOptionItem(++id, "TM.ToiletUseRadius", new(0f, 5f, 0.25f), 1f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Multiplier); + public override void OnPet(PlayerControl pc) + { + Vector2 pos = pc.Pos(); - ToiletUseTime = new IntegerOptionItem(++id, "TM.ToiletUseTime", new(0, 60, 1), 5, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Seconds); + IEnumerable hideList = ToiletVisible switch + { + ToiletVisibilityOptions.Instant => [], + _ => Main.AllPlayerControls.Without(pc) + }; - ToiletMaxUses = new IntegerOptionItem(++id, "TM.ToiletMaxUses", new(0, 10, 1), 3, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); + Toilets[pos] = (new(pos, hideList), 0, Utils.TimeStamp); + pc.RpcRemoveAbilityUse(); - BrownPoopSpeedBoost = new FloatOptionItem(++id, "TM.BrownPoopSpeedBoost", new(0f, 3f, 0.05f), 0.5f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Multiplier); + if (ToiletVisible == ToiletVisibilityOptions.Delayed) LateTask.New(() => Toilets[pos] = (new(pos, []), Toilets[pos].Uses, Toilets[pos].PlaceTimeStamp), 5f, log: false); + } - GreenPoopRadius = new FloatOptionItem(++id, "TM.GreenPoopRadius", new(0f, 5f, 0.25f), 1f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Multiplier); + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask) return; - RedPoopRadius = new FloatOptionItem(++id, "TM.RedPoopRadius", new(0f, 5f, 0.25f), 1f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Multiplier); - - RedPoopRoleBlockDuration = new IntegerOptionItem(++id, "TM.RedPoopRoleBlockDuration", new(0, 60, 1), 10, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Seconds); - - PurplePoopNotifyOnKillAttempt = new BooleanOptionItem(++id, "TM.PurplePoopNotifyOnKillAttempt", false, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); - - AbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.6f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.1f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]); - - poops.Do(x => + if (ActivePoops.TryGetValue(pc.PlayerId, out (Poop Poop, long TimeStamp, object Data) activePoop)) + { + if (activePoop.TimeStamp + PoopDurationSettings[activePoop.Poop].GetInt() <= Utils.TimeStamp) { - PoopDurationSettings[x] = new IntegerOptionItem(++id, $"TM.{x}PoopDuration", new(0, 60, 1), 10, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ToiletMaster]) - .SetValueFormat(OptionFormat.Seconds); - }); + ActivePoops.Remove(pc.PlayerId); - PoopDurationSettings.Values - .CombineWith([BrownPoopSpeedBoost], [GreenPoopRadius], [RedPoopRadius], [RedPoopRoleBlockDuration], [PurplePoopNotifyOnKillAttempt]) - .Do(x => x.ReplacementDictionary = replacements); + switch (activePoop.Poop) + { + case Poop.Brown: + Main.AllPlayerSpeed[pc.PlayerId] -= BrownPoopSpeedBoost.GetFloat(); + break; + case Poop.Red: + float defaultSpeed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + + ((List)activePoop.Data).DoIf(x => x != null, x => + { + Main.AllPlayerSpeed[x.PlayerId] = defaultSpeed; + x.MarkDirtySettings(); + }); + + break; + } + } + + return; } - public override void Init() - { - On = false; - Instances = []; - Toilets = []; - ActivePoops = []; - PlayersUsingToilet = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - LastUpdate = 8 + AbilityCooldown.GetInt() + ToiletDuration.GetInt(); - playerId.SetAbilityUseLimit(AbilityUses.GetFloat()); - } - - public override void OnPet(PlayerControl pc) + try { Vector2 pos = pc.Pos(); + (Toilet NetObject, int Uses, long PlaceTimeStamp) toilet = Toilets.First(x => Vector2.Distance(x.Key, pos) <= ToiletUseRadius.GetFloat()).Value; - IEnumerable hideList = ToiletVisible switch + if (toilet.Uses >= AbilityUses.GetInt()) return; + + if (!PlayersUsingToilet.TryGetValue(pc.PlayerId, out long ts)) { - ToiletVisibilityOptions.Instant => [], - _ => Main.AllPlayerControls.Without(pc) - }; - - Toilets[pos] = (new(pos, hideList), 0, Utils.TimeStamp); - pc.RpcRemoveAbilityUse(); - - if (ToiletVisible == ToiletVisibilityOptions.Delayed) LateTask.New(() => Toilets[pos] = (new(pos, []), Toilets[pos].Uses, Toilets[pos].PlaceTimeStamp), 5f, log: false); - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask) return; - - if (ActivePoops.TryGetValue(pc.PlayerId, out (Poop Poop, long TimeStamp, object Data) activePoop)) - { - if (activePoop.TimeStamp + PoopDurationSettings[activePoop.Poop].GetInt() <= Utils.TimeStamp) - { - ActivePoops.Remove(pc.PlayerId); - - switch (activePoop.Poop) - { - case Poop.Brown: - Main.AllPlayerSpeed[pc.PlayerId] -= BrownPoopSpeedBoost.GetFloat(); - break; - case Poop.Red: - float defaultSpeed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - - ((List)activePoop.Data).DoIf(x => x != null, x => - { - Main.AllPlayerSpeed[x.PlayerId] = defaultSpeed; - x.MarkDirtySettings(); - }); - - break; - } - } - + PlayersUsingToilet[pc.PlayerId] = Utils.TimeStamp; return; } - try + if (ts + ToiletUseTime.GetInt() > Utils.TimeStamp) return; + + toilet.Uses++; + + Poop poop = Enum.GetValues().RandomElement(); + if (poop != Poop.Green) pc.Notify(Utils.ColorString(GetPoopColor(poop), string.Format(Translator.GetString("TM.GetPoopNotify"), poop))); + + switch (poop) { - Vector2 pos = pc.Pos(); - (Toilet NetObject, int Uses, long PlaceTimeStamp) toilet = Toilets.First(x => Vector2.Distance(x.Key, pos) <= ToiletUseRadius.GetFloat()).Value; + case Poop.Brown: + Main.AllPlayerSpeed[pc.PlayerId] += BrownPoopSpeedBoost.GetFloat(); + ActivePoops[pc.PlayerId] = (poop, Utils.TimeStamp, null); + break; + case Poop.Green: + float radius = GreenPoopRadius.GetFloat(); + bool isKillerNearby = Main.AllAlivePlayerControls.Any(x => x.PlayerId != pc.PlayerId && Vector2.Distance(x.Pos(), pos) <= radius && (x.IsImpostor() || x.IsNeutralKiller())); + Color color = isKillerNearby ? Color.red : Color.green; + string str = Translator.GetString(isKillerNearby ? "TM.GreenPoopKiller" : "TM.GreenPoop"); + pc.Notify(Utils.ColorString(color, str)); + break; + case Poop.Red: + int duration = RedPoopRoleBlockDuration.GetInt(); + List affectedPlayers = []; - if (toilet.Uses >= AbilityUses.GetInt()) return; + Utils.GetPlayersInRadius(RedPoopRadius.GetFloat(), pos).Without(pc).Do(x => + { + x.BlockRole(duration); + Main.AllPlayerSpeed[x.PlayerId] = Main.MinSpeed; + x.MarkDirtySettings(); + affectedPlayers.Add(x); - if (!PlayersUsingToilet.TryGetValue(pc.PlayerId, out long ts)) - { - PlayersUsingToilet[pc.PlayerId] = Utils.TimeStamp; - return; - } + if (x.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); + }); - if (ts + ToiletUseTime.GetInt() > Utils.TimeStamp) return; - - toilet.Uses++; - - Poop poop = Enum.GetValues().RandomElement(); - if (poop != Poop.Green) pc.Notify(Utils.ColorString(GetPoopColor(poop), string.Format(Translator.GetString("TM.GetPoopNotify"), poop))); - - switch (poop) - { - case Poop.Brown: - Main.AllPlayerSpeed[pc.PlayerId] += BrownPoopSpeedBoost.GetFloat(); - ActivePoops[pc.PlayerId] = (poop, Utils.TimeStamp, null); - break; - case Poop.Green: - float radius = GreenPoopRadius.GetFloat(); - bool isKillerNearby = Main.AllAlivePlayerControls.Any(x => x.PlayerId != pc.PlayerId && Vector2.Distance(x.Pos(), pos) <= radius && (x.IsImpostor() || x.IsNeutralKiller())); - Color color = isKillerNearby ? Color.red : Color.green; - string str = Translator.GetString(isKillerNearby ? "TM.GreenPoopKiller" : "TM.GreenPoop"); - pc.Notify(Utils.ColorString(color, str)); - break; - case Poop.Red: - int duration = RedPoopRoleBlockDuration.GetInt(); - List affectedPlayers = []; - - Utils.GetPlayersInRadius(RedPoopRadius.GetFloat(), pos).Without(pc).Do(x => - { - x.BlockRole(duration); - Main.AllPlayerSpeed[x.PlayerId] = Main.MinSpeed; - x.MarkDirtySettings(); - affectedPlayers.Add(x); - - if (x.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - }); - - ActivePoops[pc.PlayerId] = (poop, Utils.TimeStamp, affectedPlayers); - break; - case Poop.Blue: - case Poop.Purple: - ActivePoops[pc.PlayerId] = (poop, Utils.TimeStamp, null); - break; - } - - PlayersUsingToilet.Remove(pc.PlayerId); - Logger.Info($"{pc.GetNameWithRole()} used a toilet => {poop} poop", "ToiletMaster"); - } - catch - { - PlayersUsingToilet.Remove(pc.PlayerId); - } - } - - public override void AfterMeetingTasks() - { - if (ToiletVisible == ToiletVisibilityOptions.AfterMeeting) Toilets.Values.Do(x => x.NetObject = new(Toilets.GetKeyByValue((x.NetObject, x.Uses, x.PlaceTimeStamp)), [])); - } - - private static Color GetPoopColor(Poop poop) - { - return poop switch - { - Poop.Brown => Palette.Brown, - Poop.Green => Color.green, - Poop.Red => Color.red, - Poop.Purple => Palette.Purple, - Poop.Blue => Color.blue, - _ => Color.white - }; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask) return; - - long now = Utils.TimeStamp; - if (LastUpdate >= now) return; - - LastUpdate = now; - - int duration = ToiletDuration.GetInt(); - int maxUses = ToiletMaxUses.GetInt(); - - Toilets.DoIf(x => x.Value.PlaceTimeStamp + duration <= now || x.Value.Uses >= maxUses, x => - { - Toilets.Remove(x.Key); - x.Value.NetObject.Despawn(); - }, false); - } - - public static bool OnAnyoneCheckMurderStart(PlayerControl killer, PlayerControl target) - { - foreach (ToiletMaster tm in Instances) - { - if (tm.ActivePoops.TryGetValue(killer.PlayerId, out (Poop Poop, long TimeStamp, object Data) poop) && poop.Poop == Poop.Blue) - { - killer.RpcCheckAndMurder(target); - return true; - } + ActivePoops[pc.PlayerId] = (poop, Utils.TimeStamp, affectedPlayers); + break; + case Poop.Blue: + case Poop.Purple: + ActivePoops[pc.PlayerId] = (poop, Utils.TimeStamp, null); + break; } - return false; + PlayersUsingToilet.Remove(pc.PlayerId); + Logger.Info($"{pc.GetNameWithRole()} used a toilet => {poop} poop", "ToiletMaster"); } - - public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) + catch { - foreach (ToiletMaster tm in Instances) - { - if (tm.ActivePoops.TryGetValue(killer.PlayerId, out (Poop Poop, long TimeStamp, object Data) poop) && poop.Poop == Poop.Purple) - { - if (PurplePoopNotifyOnKillAttempt.GetBool()) target.Notify(Translator.GetString("TM.TryKillNotify")); - - return false; - } - } - - return true; - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; - } - - private enum ToiletVisibilityOptions - { - Instant, - Delayed, - AfterMeeting, - - // ReSharper disable once UnusedMember.Local - Invisible - } - - private enum Poop - { - Brown, - Green, - Red, - Purple, - Blue + PlayersUsingToilet.Remove(pc.PlayerId); } } + + public override void AfterMeetingTasks() + { + if (ToiletVisible == ToiletVisibilityOptions.AfterMeeting) Toilets.Values.Do(x => x.NetObject = new(Toilets.GetKeyByValue((x.NetObject, x.Uses, x.PlaceTimeStamp)), [])); + } + + private static Color GetPoopColor(Poop poop) + { + return poop switch + { + Poop.Brown => Palette.Brown, + Poop.Green => Color.green, + Poop.Red => Color.red, + Poop.Purple => Palette.Purple, + Poop.Blue => Color.blue, + _ => Color.white + }; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask) return; + + long now = Utils.TimeStamp; + if (LastUpdate >= now) return; + + LastUpdate = now; + + int duration = ToiletDuration.GetInt(); + int maxUses = ToiletMaxUses.GetInt(); + + Toilets.DoIf(x => x.Value.PlaceTimeStamp + duration <= now || x.Value.Uses >= maxUses, x => + { + Toilets.Remove(x.Key); + x.Value.NetObject.Despawn(); + }, false); + } + + public static bool OnAnyoneCheckMurderStart(PlayerControl killer, PlayerControl target) + { + foreach (ToiletMaster tm in Instances) + { + if (tm.ActivePoops.TryGetValue(killer.PlayerId, out (Poop Poop, long TimeStamp, object Data) poop) && poop.Poop == Poop.Blue) + { + killer.RpcCheckAndMurder(target); + return true; + } + } + + return false; + } + + public static bool OnAnyoneCheckMurder(PlayerControl killer, PlayerControl target) + { + foreach (ToiletMaster tm in Instances) + { + if (tm.ActivePoops.TryGetValue(killer.PlayerId, out (Poop Poop, long TimeStamp, object Data) poop) && poop.Poop == Poop.Purple) + { + if (PurplePoopNotifyOnKillAttempt.GetBool()) target.Notify(Translator.GetString("TM.TryKillNotify")); + + return false; + } + } + + return true; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } + + private enum ToiletVisibilityOptions + { + Instant, + Delayed, + AfterMeeting, + + // ReSharper disable once UnusedMember.Local + Invisible + } + + private enum Poop + { + Brown, + Green, + Red, + Purple, + Blue + } } \ No newline at end of file diff --git a/Roles/Crewmate/Tornado.cs b/Roles/Crewmate/Tornado.cs index d7b8cb5a1..f42b35991 100644 --- a/Roles/Crewmate/Tornado.cs +++ b/Roles/Crewmate/Tornado.cs @@ -7,176 +7,175 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Tornado : RoleBase { - internal class Tornado : RoleBase + private static readonly List PlayerIdList = []; + + public static OptionItem TornadoCooldown; + public static OptionItem TornadoDuration; + private static OptionItem TornadoRange; + + private static readonly Dictionary ReplacementDict = new() { { "Tornado", ColorString(GetRoleColor(CustomRoles.Tornado), "Tornado") } }; + + private static RandomSpawn.SpawnMap Map; + private static readonly Dictionary<(Vector2 Location, string RoomName), long> Tornados = []; + private static long LastNotify = TimeStamp; + private static bool CanUseMap; + private PlayerControl TornadoPC; + private static int Id => 64420; + + public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + + public override void SetupCustomOption() { - private static readonly List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Tornado); - public static OptionItem TornadoCooldown; - public static OptionItem TornadoDuration; - private static OptionItem TornadoRange; + TornadoCooldown = new IntegerOptionItem(Id + 2, "TornadoCooldown", new(1, 90, 1), 15, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tornado]) + .SetValueFormat(OptionFormat.Seconds); - private static readonly Dictionary ReplacementDict = new() { { "Tornado", ColorString(GetRoleColor(CustomRoles.Tornado), "Tornado") } }; + TornadoDuration = new IntegerOptionItem(Id + 3, "TornadoDuration", new(1, 90, 1), 25, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tornado]) + .SetValueFormat(OptionFormat.Seconds); - private static RandomSpawn.SpawnMap Map; - private static readonly Dictionary<(Vector2 Location, string RoomName), long> Tornados = []; - private static long LastNotify = TimeStamp; - private static bool CanUseMap; - private PlayerControl TornadoPC; - private static int Id => 64420; + TornadoRange = new FloatOptionItem(Id + 4, "TornadoRange", new(0.5f, 25f, 0.5f), 3f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tornado]) + .SetValueFormat(OptionFormat.Multiplier); - public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + TornadoCooldown.ReplacementDictionary = ReplacementDict; + TornadoDuration.ReplacementDictionary = ReplacementDict; + TornadoRange.ReplacementDictionary = ReplacementDict; + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList.Clear(); + Tornados.Clear(); + LastNotify = TimeStamp; + + try { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Tornado); - - TornadoCooldown = new IntegerOptionItem(Id + 2, "TornadoCooldown", new(1, 90, 1), 15, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tornado]) - .SetValueFormat(OptionFormat.Seconds); - - TornadoDuration = new IntegerOptionItem(Id + 3, "TornadoDuration", new(1, 90, 1), 25, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tornado]) - .SetValueFormat(OptionFormat.Seconds); - - TornadoRange = new FloatOptionItem(Id + 4, "TornadoRange", new(0.5f, 25f, 0.5f), 3f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tornado]) - .SetValueFormat(OptionFormat.Multiplier); - - TornadoCooldown.ReplacementDictionary = ReplacementDict; - TornadoDuration.ReplacementDictionary = ReplacementDict; - TornadoRange.ReplacementDictionary = ReplacementDict; - } - - public override void Init() - { - PlayerIdList.Clear(); - Tornados.Clear(); - LastNotify = TimeStamp; - - try + Map = Main.CurrentMap switch { - Map = Main.CurrentMap switch - { - MapNames.Skeld => new RandomSpawn.SkeldSpawnMap(), - MapNames.Mira => new RandomSpawn.MiraHQSpawnMap(), - MapNames.Polus => new RandomSpawn.PolusSpawnMap(), - MapNames.Dleks => new RandomSpawn.DleksSpawnMap(), - MapNames.Airship => new RandomSpawn.AirshipSpawnMap(), - MapNames.Fungle => new RandomSpawn.FungleSpawnMap(), - _ => throw new ArgumentOutOfRangeException(Main.CurrentMap.ToString(), "Unsupported Map") - }; + MapNames.Skeld => new RandomSpawn.SkeldSpawnMap(), + MapNames.Mira => new RandomSpawn.MiraHQSpawnMap(), + MapNames.Polus => new RandomSpawn.PolusSpawnMap(), + MapNames.Dleks => new RandomSpawn.DleksSpawnMap(), + MapNames.Airship => new RandomSpawn.AirshipSpawnMap(), + MapNames.Fungle => new RandomSpawn.FungleSpawnMap(), + _ => throw new ArgumentOutOfRangeException(Main.CurrentMap.ToString(), "Unsupported Map") + }; - CanUseMap = true; - } - catch (ArgumentOutOfRangeException) - { - Logger.CurrentMethod(); - Logger.Error("Unsupported Map", "Torando"); - CanUseMap = false; - } + CanUseMap = true; } - - public override void Add(byte playerId) + catch (ArgumentOutOfRangeException) { - TornadoPC = GetPlayerById(playerId); - PlayerIdList.Add(playerId); - } - - private static void SendRPCAddTornado(bool add, Vector2 pos, string roomname, long timestamp = 0) - { - if (!DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AddTornado, SendOption.Reliable); - writer.Write(add); - writer.Write(pos.x); - writer.Write(pos.y); - writer.Write(roomname); - if (add) writer.Write(timestamp.ToString()); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCAddTornado(MessageReader reader) - { - bool add = reader.ReadBoolean(); - float x = reader.ReadSingle(); - float y = reader.ReadSingle(); - string roomname = reader.ReadString(); - - if (add) - { - long timestamp = long.Parse(reader.ReadString()); - Tornados.TryAdd((new(x, y), roomname), timestamp); - } - else - Tornados.Remove((new(x, y), roomname)); - } - - public override void OnPet(PlayerControl pc) - { - SpawnTornado(pc); - } - - public static void SpawnTornado(PlayerControl pc) - { - if (pc == null) return; - - (Vector2 Location, string RoomName) info = pc.GetPositionInfo(); - long now = TimeStamp; - Tornados.TryAdd(info, now); - SendRPCAddTornado(true, info.Location, info.RoomName, now); - _ = new TornadoObject(info.Location, [pc.PlayerId]); - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (!IsEnable || !GameStates.IsInTask || Tornados.Count == 0 || pc == null) return; - - long now = TimeStamp; - - if (!pc.Is(CustomRoles.Tornado)) - { - var Random = IRandom.Instance; - string NotifyString = GetString("TeleportedByTornado"); - float tornadoRange = TornadoRange.GetFloat(); - int tornadoDuration = TornadoDuration.GetInt(); - - foreach (KeyValuePair<(Vector2 Location, string RoomName), long> tornado in Tornados) - { - if (Vector2.Distance(tornado.Key.Location, pc.Pos()) <= tornadoRange) - { - if (!CanUseMap || Random.Next(0, 100) < 50) - pc.TPToRandomVent(); - else - Map.RandomTeleport(pc); - - pc.Notify(NotifyString); - } - - if (tornado.Value + tornadoDuration < now) - { - Tornados.Remove(tornado.Key); - SendRPCAddTornado(false, tornado.Key.Location, tornado.Key.RoomName); - NotifyRoles(SpecifySeer: TornadoPC, SpecifyTarget: TornadoPC); - } - } - } - else - { - if (LastNotify >= now || pc.HasAbilityCD()) return; - - NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - LastNotify = now; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || !IsEnable || (seer.IsModClient() && !hud) || seer.PlayerId != TornadoPC.PlayerId) return string.Empty; - - return string.Join(hud ? "\n" : ", ", Tornados.Select(x => $"Tornado {GetFormattedRoomName(x.Key.RoomName)} {GetFormattedVectorText(x.Key.Location)} ({(int)(TornadoDuration.GetInt() - (TimeStamp - x.Value) + 1)}s)")); + Logger.CurrentMethod(); + Logger.Error("Unsupported Map", "Torando"); + CanUseMap = false; } } + + public override void Add(byte playerId) + { + TornadoPC = GetPlayerById(playerId); + PlayerIdList.Add(playerId); + } + + private static void SendRPCAddTornado(bool add, Vector2 pos, string roomname, long timestamp = 0) + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.AddTornado, SendOption.Reliable); + writer.Write(add); + writer.Write(pos.x); + writer.Write(pos.y); + writer.Write(roomname); + if (add) writer.Write(timestamp.ToString()); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCAddTornado(MessageReader reader) + { + bool add = reader.ReadBoolean(); + float x = reader.ReadSingle(); + float y = reader.ReadSingle(); + string roomname = reader.ReadString(); + + if (add) + { + long timestamp = long.Parse(reader.ReadString()); + Tornados.TryAdd((new(x, y), roomname), timestamp); + } + else + Tornados.Remove((new(x, y), roomname)); + } + + public override void OnPet(PlayerControl pc) + { + SpawnTornado(pc); + } + + public static void SpawnTornado(PlayerControl pc) + { + if (pc == null) return; + + (Vector2 Location, string RoomName) info = pc.GetPositionInfo(); + long now = TimeStamp; + Tornados.TryAdd(info, now); + SendRPCAddTornado(true, info.Location, info.RoomName, now); + _ = new TornadoObject(info.Location, [pc.PlayerId]); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (!IsEnable || !GameStates.IsInTask || Tornados.Count == 0 || pc == null) return; + + long now = TimeStamp; + + if (!pc.Is(CustomRoles.Tornado)) + { + var Random = IRandom.Instance; + string NotifyString = GetString("TeleportedByTornado"); + float tornadoRange = TornadoRange.GetFloat(); + int tornadoDuration = TornadoDuration.GetInt(); + + foreach (KeyValuePair<(Vector2 Location, string RoomName), long> tornado in Tornados) + { + if (Vector2.Distance(tornado.Key.Location, pc.Pos()) <= tornadoRange) + { + if (!CanUseMap || Random.Next(0, 100) < 50) + pc.TPToRandomVent(); + else + Map.RandomTeleport(pc); + + pc.Notify(NotifyString); + } + + if (tornado.Value + tornadoDuration < now) + { + Tornados.Remove(tornado.Key); + SendRPCAddTornado(false, tornado.Key.Location, tornado.Key.RoomName); + NotifyRoles(SpecifySeer: TornadoPC, SpecifyTarget: TornadoPC); + } + } + } + else + { + if (LastNotify >= now || pc.HasAbilityCD()) return; + + NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + LastNotify = now; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || !IsEnable || (seer.IsModClient() && !hud) || seer.PlayerId != TornadoPC.PlayerId) return string.Empty; + + return string.Join(hud ? "\n" : ", ", Tornados.Select(x => $"Tornado {GetFormattedRoomName(x.Key.RoomName)} {GetFormattedVectorText(x.Key.Location)} ({(int)(TornadoDuration.GetInt() - (TimeStamp - x.Value) + 1)}s)")); + } } \ No newline at end of file diff --git a/Roles/Crewmate/Tracefinder.cs b/Roles/Crewmate/Tracefinder.cs index 73f1a936b..ec2e1f484 100644 --- a/Roles/Crewmate/Tracefinder.cs +++ b/Roles/Crewmate/Tracefinder.cs @@ -4,103 +4,102 @@ using AmongUs.GameOptions; using UnityEngine; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Tracefinder : RoleBase { - public class Tracefinder : RoleBase + private const int Id = 6100; + private static List PlayerIdList = []; + private static OptionItem VitalsDuration; + private static OptionItem VitalsCooldown; + private static OptionItem ArrowDelayMin; + private static OptionItem ArrowDelayMax; + + public static bool On; + private byte TracefinderId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 6100; - private static List PlayerIdList = []; - private static OptionItem VitalsDuration; - private static OptionItem VitalsCooldown; - private static OptionItem ArrowDelayMin; - private static OptionItem ArrowDelayMax; + SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Tracefinder); - public static bool On; - private byte TracefinderId; + VitalsCooldown = new FloatOptionItem(Id + 10, "VitalsCooldown", new(1f, 60f, 1f), 25f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + VitalsDuration = new FloatOptionItem(Id + 11, "VitalsDuration", new(1f, 30f, 1f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + ArrowDelayMin = new FloatOptionItem(Id + 12, "ArrowDelayMin", new(1f, 30f, 1f), 2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) + .SetValueFormat(OptionFormat.Seconds); + + ArrowDelayMax = new FloatOptionItem(Id + 13, "ArrowDelayMax", new(1f, 30f, 1f), 7f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + On = false; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + TracefinderId = playerId; + On = true; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ScientistCooldown = VitalsCooldown.GetFloat(); + AURoleOptions.ScientistBatteryCharge = VitalsDuration.GetFloat(); + } + + public override void OnReportDeadBody() + { + foreach (byte apc in PlayerIdList) LocateArrow.RemoveAllTarget(apc); + } + + public static void OnPlayerDead(PlayerControl target) + { + if (!On || !GameStates.IsInTask || target == null || target.Data.Disconnected) return; + + float delay; + + if (ArrowDelayMax.GetFloat() < ArrowDelayMin.GetFloat()) + delay = 0f; + else + delay = IRandom.Instance.Next((int)ArrowDelayMin.GetFloat(), (int)ArrowDelayMax.GetFloat() + 1); + + delay = Math.Max(delay, 0.15f); + + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.CrewmateRoles, CustomRoles.Tracefinder); - - VitalsCooldown = new FloatOptionItem(Id + 10, "VitalsCooldown", new(1f, 60f, 1f), 25f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) - .SetValueFormat(OptionFormat.Seconds); - - VitalsDuration = new FloatOptionItem(Id + 11, "VitalsDuration", new(1f, 30f, 1f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) - .SetValueFormat(OptionFormat.Seconds); - - ArrowDelayMin = new FloatOptionItem(Id + 12, "ArrowDelayMin", new(1f, 30f, 1f), 2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) - .SetValueFormat(OptionFormat.Seconds); - - ArrowDelayMax = new FloatOptionItem(Id + 13, "ArrowDelayMax", new(1f, 30f, 1f), 7f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Tracefinder]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - On = false; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - TracefinderId = playerId; - On = true; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ScientistCooldown = VitalsCooldown.GetFloat(); - AURoleOptions.ScientistBatteryCharge = VitalsDuration.GetFloat(); - } - - public override void OnReportDeadBody() - { - foreach (byte apc in PlayerIdList) LocateArrow.RemoveAllTarget(apc); - } - - public static void OnPlayerDead(PlayerControl target) - { - if (!On || !GameStates.IsInTask || target == null || target.Data.Disconnected) return; - - float delay; - - if (ArrowDelayMax.GetFloat() < ArrowDelayMin.GetFloat()) - delay = 0f; - else - delay = IRandom.Instance.Next((int)ArrowDelayMin.GetFloat(), (int)ArrowDelayMax.GetFloat() + 1); - - delay = Math.Max(delay, 0.15f); - - LateTask.New(() => + if (GameStates.IsInTask) { - if (GameStates.IsInTask) + foreach (byte id in PlayerIdList) { - foreach (byte id in PlayerIdList) - { - PlayerControl pc = Utils.GetPlayerById(id); - if (pc == null || !pc.IsAlive()) continue; + PlayerControl pc = Utils.GetPlayerById(id); + if (pc == null || !pc.IsAlive()) continue; - LocateArrow.Add(id, target.transform.position); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } + LocateArrow.Add(id, target.transform.position); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } - }, delay, "Tracefinder arrow delay"); - } + } + }, delay, "Tracefinder arrow delay"); + } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != TracefinderId) return string.Empty; + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != TracefinderId) return string.Empty; - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - return Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); - } + return Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); } } \ No newline at end of file diff --git a/Roles/Crewmate/Transmitter.cs b/Roles/Crewmate/Transmitter.cs index 84b8f25df..fb59cceba 100644 --- a/Roles/Crewmate/Transmitter.cs +++ b/Roles/Crewmate/Transmitter.cs @@ -1,30 +1,29 @@ using System.Linq; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Transmitter : RoleBase { - public class Transmitter : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override void SetupCustomOption() + { + Options.SetupRoleOptions(642610, TabGroup.CrewmateRoles, CustomRoles.Transmitter); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(642610, TabGroup.CrewmateRoles, CustomRoles.Transmitter); - } - - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - pc.TP(Main.AllAlivePlayerControls.OrderBy(x => Vector2.Distance(x.Pos(), pc.Pos())).FirstOrDefault(x => x.PlayerId != pc.PlayerId) ?? pc); - } + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + pc.TP(Main.AllAlivePlayerControls.OrderBy(x => Vector2.Distance(x.Pos(), pc.Pos())).FirstOrDefault(x => x.PlayerId != pc.PlayerId) ?? pc); } } \ No newline at end of file diff --git a/Roles/Crewmate/Transporter.cs b/Roles/Crewmate/Transporter.cs index f896cf6b4..b864e2176 100644 --- a/Roles/Crewmate/Transporter.cs +++ b/Roles/Crewmate/Transporter.cs @@ -3,56 +3,55 @@ using System.Linq; using EHR.Modules; using EHR.Neutral; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Transporter : RoleBase { - internal class Transporter : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(6200, TabGroup.CrewmateRoles, CustomRoles.Transporter); - public override void SetupCustomOption() + Options.TransporterTeleportMax = new IntegerOptionItem(6210, "TransporterTeleportMax", new(0, 90, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Transporter]) + .SetValueFormat(OptionFormat.Times); + + Options.OverrideTasksData.Create(6211, TabGroup.CrewmateRoles, CustomRoles.Transporter); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + if (player.IsAlive() && completedTaskCount + 1 <= Options.TransporterTeleportMax.GetInt()) { - Options.SetupRoleOptions(6200, TabGroup.CrewmateRoles, CustomRoles.Transporter); + List AllAlivePlayer = Main.AllAlivePlayerControls.Where(x => !Pelican.IsEaten(x.PlayerId) && !x.inVent && !x.onLadder).ToList(); - Options.TransporterTeleportMax = new IntegerOptionItem(6210, "TransporterTeleportMax", new(0, 90, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Transporter]) - .SetValueFormat(OptionFormat.Times); - - Options.OverrideTasksData.Create(6211, TabGroup.CrewmateRoles, CustomRoles.Transporter); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) - { - if (player.IsAlive() && completedTaskCount + 1 <= Options.TransporterTeleportMax.GetInt()) + if (AllAlivePlayer.Count >= 2) { - List AllAlivePlayer = Main.AllAlivePlayerControls.Where(x => !Pelican.IsEaten(x.PlayerId) && !x.inVent && !x.onLadder).ToList(); - - if (AllAlivePlayer.Count >= 2) - { - PlayerControl tar1 = AllAlivePlayer.RandomElement(); - AllAlivePlayer.Remove(tar1); - PlayerControl tar2 = AllAlivePlayer.RandomElement(); - Vector2 pos = tar1.Pos(); - tar1.TP(tar2); - tar2.TP(pos); - tar1.RPCPlayCustomSound("Teleport"); - tar2.RPCPlayCustomSound("Teleport"); - tar1.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Transporter), string.Format(Translator.GetString("TeleportedByTransporter"), tar2.GetRealName()))); - tar2.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Transporter), string.Format(Translator.GetString("TeleportedByTransporter"), tar1.GetRealName()))); - } - else - player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), string.Format(Translator.GetString("ErrorTeleport"), player.GetRealName()))); + PlayerControl tar1 = AllAlivePlayer.RandomElement(); + AllAlivePlayer.Remove(tar1); + PlayerControl tar2 = AllAlivePlayer.RandomElement(); + Vector2 pos = tar1.Pos(); + tar1.TP(tar2); + tar2.TP(pos); + tar1.RPCPlayCustomSound("Teleport"); + tar2.RPCPlayCustomSound("Teleport"); + tar1.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Transporter), string.Format(Translator.GetString("TeleportedByTransporter"), tar2.GetRealName()))); + tar2.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Transporter), string.Format(Translator.GetString("TeleportedByTransporter"), tar1.GetRealName()))); } + else + player.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Impostor), string.Format(Translator.GetString("ErrorTeleport"), player.GetRealName()))); } } } \ No newline at end of file diff --git a/Roles/Crewmate/Tunneler.cs b/Roles/Crewmate/Tunneler.cs index 91548aed1..b71ca8dcb 100644 --- a/Roles/Crewmate/Tunneler.cs +++ b/Roles/Crewmate/Tunneler.cs @@ -1,48 +1,47 @@ using System.Collections.Generic; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Tunneler : RoleBase { - internal class Tunneler : RoleBase + public static Dictionary TunnelerPositions = []; + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary TunnelerPositions = []; - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(5592, TabGroup.CrewmateRoles, CustomRoles.Tunneler); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + TunnelerPositions = []; + } + + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + + ProgressText.Append(base.GetProgressText(playerId, comms)); + if (TunnelerPositions.ContainsKey(playerId)) ProgressText.Append('●'); + + return ProgressText.ToString(); + } + + public override void OnPet(PlayerControl pc) + { + if (TunnelerPositions.TryGetValue(pc.PlayerId, out Vector2 ps)) { - Options.SetupRoleOptions(5592, TabGroup.CrewmateRoles, CustomRoles.Tunneler); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - TunnelerPositions = []; - } - - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); - - ProgressText.Append(base.GetProgressText(playerId, comms)); - if (TunnelerPositions.ContainsKey(playerId)) ProgressText.Append('●'); - - return ProgressText.ToString(); - } - - public override void OnPet(PlayerControl pc) - { - if (TunnelerPositions.TryGetValue(pc.PlayerId, out Vector2 ps)) - { - pc.TP(ps); - TunnelerPositions.Remove(pc.PlayerId); - } - else - TunnelerPositions[pc.PlayerId] = pc.Pos(); + pc.TP(ps); + TunnelerPositions.Remove(pc.PlayerId); } + else + TunnelerPositions[pc.PlayerId] = pc.Pos(); } } \ No newline at end of file diff --git a/Roles/Crewmate/Ventguard.cs b/Roles/Crewmate/Ventguard.cs index 808cb2b20..c3307f84a 100644 --- a/Roles/Crewmate/Ventguard.cs +++ b/Roles/Crewmate/Ventguard.cs @@ -2,90 +2,89 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Ventguard : RoleBase { - internal class Ventguard : RoleBase + public static bool On; + + public static List BlockedVents = []; + + public static OptionItem VentguardBlockDoesNotAffectCrew; + public static OptionItem VentguardAbilityUseGainWithEachTaskCompleted; + public static OptionItem VentguardAbilityChargesWhenFinishedTasks; + public static OptionItem VentguardMaxGuards; + public static OptionItem VentguardNotifyOnBlockedVentUse; + public static OptionItem VentguardBlocksResetOnMeeting; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupSingleRoleOptions(5525, TabGroup.CrewmateRoles, CustomRoles.Ventguard); - public static List BlockedVents = []; + VentguardAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(5527, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]) + .SetValueFormat(OptionFormat.Times); - public static OptionItem VentguardBlockDoesNotAffectCrew; - public static OptionItem VentguardAbilityUseGainWithEachTaskCompleted; - public static OptionItem VentguardAbilityChargesWhenFinishedTasks; - public static OptionItem VentguardMaxGuards; - public static OptionItem VentguardNotifyOnBlockedVentUse; - public static OptionItem VentguardBlocksResetOnMeeting; - public override bool IsEnable => On; + VentguardAbilityChargesWhenFinishedTasks = new FloatOptionItem(5530, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + VentguardMaxGuards = new IntegerOptionItem(5528, "VentguardMaxGuards", new(1, 30, 1), 3, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); + + VentguardBlockDoesNotAffectCrew = new BooleanOptionItem(5529, "VentguardBlockDoesNotAffectCrew", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); + + VentguardNotifyOnBlockedVentUse = new BooleanOptionItem(5531, "VentguardNotifyOnBlockedVentUse", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); + + VentguardBlocksResetOnMeeting = new BooleanOptionItem(5532, "VentguardBlocksResetOnMeeting", true, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(VentguardMaxGuards.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerInVentMaxTime = 1f; + AURoleOptions.EngineerCooldown = 15f; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton.buttonLabelText.text = Translator.GetString("VentguardVentButtonText"); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (pc.GetAbilityUseLimit() >= 1) { - SetupSingleRoleOptions(5525, TabGroup.CrewmateRoles, CustomRoles.Ventguard); + pc.RpcRemoveAbilityUse(); + if (!BlockedVents.Contains(vent.Id)) BlockedVents.Add(vent.Id); - VentguardAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(5527, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]) - .SetValueFormat(OptionFormat.Times); - - VentguardAbilityChargesWhenFinishedTasks = new FloatOptionItem(5530, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]) - .SetValueFormat(OptionFormat.Times); - - VentguardMaxGuards = new IntegerOptionItem(5528, "VentguardMaxGuards", new(1, 30, 1), 3, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); - - VentguardBlockDoesNotAffectCrew = new BooleanOptionItem(5529, "VentguardBlockDoesNotAffectCrew", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); - - VentguardNotifyOnBlockedVentUse = new BooleanOptionItem(5531, "VentguardNotifyOnBlockedVentUse", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); - - VentguardBlocksResetOnMeeting = new BooleanOptionItem(5532, "VentguardBlocksResetOnMeeting", true, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ventguard]); + pc.Notify(Translator.GetString("VentBlockSuccess")); } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(VentguardMaxGuards.GetInt()); - } + public override void AfterMeetingTasks() + { + if (VentguardBlocksResetOnMeeting.GetBool()) BlockedVents.Clear(); + } - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerInVentMaxTime = 1f; - AURoleOptions.EngineerCooldown = 15f; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton.buttonLabelText.text = Translator.GetString("VentguardVentButtonText"); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (pc.GetAbilityUseLimit() >= 1) - { - pc.RpcRemoveAbilityUse(); - if (!BlockedVents.Contains(vent.Id)) BlockedVents.Add(vent.Id); - - pc.Notify(Translator.GetString("VentBlockSuccess")); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override void AfterMeetingTasks() - { - if (VentguardBlocksResetOnMeeting.GetBool()) BlockedVents.Clear(); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !BlockedVents.Contains(ventId) || (pc.IsCrewmate() && VentguardBlockDoesNotAffectCrew.GetBool()); - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !BlockedVents.Contains(ventId) || (pc.IsCrewmate() && VentguardBlockDoesNotAffectCrew.GetBool()); } } \ No newline at end of file diff --git a/Roles/Crewmate/Veteran.cs b/Roles/Crewmate/Veteran.cs index 35fd87f07..0f4601f81 100644 --- a/Roles/Crewmate/Veteran.cs +++ b/Roles/Crewmate/Veteran.cs @@ -3,146 +3,145 @@ using AmongUs.GameOptions; using EHR.Modules; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Veteran : RoleBase { - internal class Veteran : RoleBase + public static Dictionary VeteranInProtect = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary VeteranInProtect = []; + const int id = 8990; + SetupRoleOptions(id, TabGroup.CrewmateRoles, CustomRoles.Veteran); - public static bool On; - public override bool IsEnable => On; + VeteranSkillCooldown = new FloatOptionItem(id + 2, "VeteranSkillCooldown", new(0f, 180f, 1f), 20f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + VeteranSkillDuration = new FloatOptionItem(id + 3, "VeteranSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) + .SetValueFormat(OptionFormat.Seconds); + + VeteranSkillMaxOfUseage = new IntegerOptionItem(id + 4, "VeteranSkillMaxOfUseage", new(0, 180, 1), 1, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) + .SetValueFormat(OptionFormat.Times); + + VeteranAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(id + 5, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) + .SetValueFormat(OptionFormat.Times); + + VeteranAbilityChargesWhenFinishedTasks = new FloatOptionItem(id + 6, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(VeteranSkillMaxOfUseage.GetInt()); + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePets.GetBool()) return; + + AURoleOptions.EngineerCooldown = VeteranSkillCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1; + } + + public override string GetProgressText(byte playerId, bool comms) + { + var ProgressText = new StringBuilder(); + + ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, VeteranInProtect.ContainsKey(playerId))); + ProgressText.Append(Utils.GetTaskCount(playerId, comms)); + + return ProgressText.ToString(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton.buttonLabelText.text = Translator.GetString("VeteranVentButtonText"); + else + hud.AbilityButton.buttonLabelText.text = Translator.GetString("VeteranVentButtonText"); + } + + public override void OnPet(PlayerControl pc) + { + Alert(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + Alert(pc); + } + + private static void Alert(PlayerControl pc) + { + if (VeteranInProtect.ContainsKey(pc.PlayerId)) return; + + if (pc.GetAbilityUseLimit() >= 1) { - const int id = 8990; - SetupRoleOptions(id, TabGroup.CrewmateRoles, CustomRoles.Veteran); - - VeteranSkillCooldown = new FloatOptionItem(id + 2, "VeteranSkillCooldown", new(0f, 180f, 1f), 20f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) - .SetValueFormat(OptionFormat.Seconds); - - VeteranSkillDuration = new FloatOptionItem(id + 3, "VeteranSkillDuration", new(0f, 180f, 1f), 10f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) - .SetValueFormat(OptionFormat.Seconds); - - VeteranSkillMaxOfUseage = new IntegerOptionItem(id + 4, "VeteranSkillMaxOfUseage", new(0, 180, 1), 1, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) - .SetValueFormat(OptionFormat.Times); - - VeteranAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(id + 5, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 0.3f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) - .SetValueFormat(OptionFormat.Times); - - VeteranAbilityChargesWhenFinishedTasks = new FloatOptionItem(id + 6, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Veteran]) - .SetValueFormat(OptionFormat.Times); + VeteranInProtect[pc.PlayerId] = Utils.TimeStamp; + pc.RpcRemoveAbilityUse(); + pc.RPCPlayCustomSound("Gunload"); + pc.Notify(Translator.GetString("VeteranOnGuard"), VeteranSkillDuration.GetFloat()); + pc.MarkDirtySettings(); } + else + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + } - public override void Add(byte playerId) + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (VeteranInProtect.ContainsKey(target.PlayerId) + && killer.PlayerId != target.PlayerId + && VeteranInProtect[target.PlayerId] + VeteranSkillDuration.GetInt() >= Utils.TimeStamp) { - On = true; - playerId.SetAbilityUseLimit(VeteranSkillMaxOfUseage.GetInt()); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePets.GetBool()) return; - - AURoleOptions.EngineerCooldown = VeteranSkillCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1; - } - - public override string GetProgressText(byte playerId, bool comms) - { - var ProgressText = new StringBuilder(); - - ProgressText.Append(Utils.GetAbilityUseLimitDisplay(playerId, VeteranInProtect.ContainsKey(playerId))); - ProgressText.Append(Utils.GetTaskCount(playerId, comms)); - - return ProgressText.ToString(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton.buttonLabelText.text = Translator.GetString("VeteranVentButtonText"); - else - hud.AbilityButton.buttonLabelText.text = Translator.GetString("VeteranVentButtonText"); - } - - public override void OnPet(PlayerControl pc) - { - Alert(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - Alert(pc); - } - - private static void Alert(PlayerControl pc) - { - if (VeteranInProtect.ContainsKey(pc.PlayerId)) return; - - if (pc.GetAbilityUseLimit() >= 1) + if (!killer.Is(CustomRoles.Pestilence)) { - VeteranInProtect[pc.PlayerId] = Utils.TimeStamp; - pc.RpcRemoveAbilityUse(); - pc.RPCPlayCustomSound("Gunload"); - pc.Notify(Translator.GetString("VeteranOnGuard"), VeteranSkillDuration.GetFloat()); - pc.MarkDirtySettings(); - } - else - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (VeteranInProtect.ContainsKey(target.PlayerId) - && killer.PlayerId != target.PlayerId - && VeteranInProtect[target.PlayerId] + VeteranSkillDuration.GetInt() >= Utils.TimeStamp) - { - if (!killer.Is(CustomRoles.Pestilence)) - { - killer.SetRealKiller(target); - target.Kill(killer); - Logger.Info($"{target.GetRealName()} reverse killed:{killer.GetRealName()}", "Veteran Kill"); - return false; - } - - target.SetRealKiller(killer); - killer.Kill(target); - Logger.Info($"{target.GetRealName()} reverse reverse killed:{target.GetRealName()}", "Pestilence Reflect"); - - if (killer.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); - + killer.SetRealKiller(target); + target.Kill(killer); + Logger.Info($"{target.GetRealName()} reverse killed:{killer.GetRealName()}", "Veteran Kill"); return false; } - return true; + target.SetRealKiller(killer); + killer.Kill(target); + Logger.Info($"{target.GetRealName()} reverse reverse killed:{target.GetRealName()}", "Pestilence Reflect"); + + if (killer.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); + + return false; } - public override void OnFixedUpdate(PlayerControl player) - { - byte playerId = player.PlayerId; + return true; + } - if (VeteranInProtect.TryGetValue(playerId, out long vtime) && vtime + VeteranSkillDuration.GetInt() < Utils.TimeStamp) - { - VeteranInProtect.Remove(playerId); - player.RpcResetAbilityCooldown(); - player.Notify(string.Format(Translator.GetString("VeteranOffGuard"), (int)player.GetAbilityUseLimit())); - } - } + public override void OnFixedUpdate(PlayerControl player) + { + byte playerId = player.PlayerId; - public override bool CanUseVent(PlayerControl pc, int ventId) + if (VeteranInProtect.TryGetValue(playerId, out long vtime) && vtime + VeteranSkillDuration.GetInt() < Utils.TimeStamp) { - return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + VeteranInProtect.Remove(playerId); + player.RpcResetAbilityCooldown(); + player.Notify(string.Format(Translator.GetString("VeteranOffGuard"), (int)player.GetAbilityUseLimit())); } } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsThisRole(pc) || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Whisperer.cs b/Roles/Crewmate/Whisperer.cs index 6ab4641d4..6e5fde9df 100644 --- a/Roles/Crewmate/Whisperer.cs +++ b/Roles/Crewmate/Whisperer.cs @@ -5,242 +5,241 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Whisperer : RoleBase { - public class Whisperer : RoleBase + public static bool On; + private static List Instances = []; + + public static OptionItem Cooldown; + public static OptionItem Duration; + public static OptionItem AbilityUseLimit; + public static OptionItem WhispererAbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private static DateTime LastMeetingStart; + + private int Count; + private (string Name, int Percent) CurrentlyQuestioning; + private List Info; + private List Souls; + private byte WhispererId; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + var id = 649550; + Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Whisperer); - public static OptionItem Cooldown; - public static OptionItem Duration; - public static OptionItem AbilityUseLimit; - public static OptionItem WhispererAbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + Cooldown = new IntegerOptionItem(++id, "WhispererCooldown", new(0, 60, 1), 5, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Seconds); - private static DateTime LastMeetingStart; + Duration = new IntegerOptionItem(++id, "WhispererDuration", new(0, 60, 1), 7, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; - private (string Name, int Percent) CurrentlyQuestioning; - private List Info; - private List Souls; - private byte WhispererId; - public override bool IsEnable => On; + AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + WhispererAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Times); + + AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Count = 0; + Souls = []; + Info = []; + Instances.Add(this); + WhispererId = playerId; + CurrentlyQuestioning = (string.Empty, 0); + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override void OnPet(PlayerControl pc) + { + if (Souls.Exists(x => x.IsQuestioning)) return; + + Vector2 pos = pc.Pos(); + List souls = Souls.FindAll(x => x.IsQuestionAble && Vector2.Distance(pos, x.Position) <= 1f); + if (souls.Count == 0) return; + + Soul soul = souls.MinBy(x => Vector2.Distance(pos, x.Position)); + soul.QuestioningTime = Duration.GetInt(); + CurrentlyQuestioning.Name = soul.Player.PlayerId.ColoredPlayerName(); + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 1, CurrentlyQuestioning.Name); + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soul.Player.PlayerId, soul.QuestioningTime); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + + Soul soul = Souls.Find(x => x.IsQuestioning); + if (soul == null) return; + + byte soulPlayerId = soul.Player.PlayerId; + + if (Vector2.Distance(pc.Pos(), soul.Position) > 1f) { - var id = 649550; - Options.SetupRoleOptions(id++, TabGroup.CrewmateRoles, CustomRoles.Whisperer); - - Cooldown = new IntegerOptionItem(++id, "WhispererCooldown", new(0, 60, 1), 5, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - .SetValueFormat(OptionFormat.Seconds); - - Duration = new IntegerOptionItem(++id, "WhispererDuration", new(0, 60, 1), 7, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - .SetValueFormat(OptionFormat.Seconds); - - AbilityUseLimit = new IntegerOptionItem(++id, "AbilityUseLimit", new(0, 20, 1), 1, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - .SetValueFormat(OptionFormat.Times); - - WhispererAbilityUseGainWithEachTaskCompleted = new FloatOptionItem(++id, "AbilityUseGainWithEachTaskCompleted", new(0f, 5f, 0.05f), 1f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - .SetValueFormat(OptionFormat.Times); - - AbilityChargesWhenFinishedTasks = new FloatOptionItem(++id, "AbilityChargesWhenFinishedTasks", new(0f, 5f, 0.05f), 0.2f, TabGroup.CrewmateRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Whisperer]) - .SetValueFormat(OptionFormat.Times); + soul.QuestioningTime = 0f; + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soulPlayerId, soul.QuestioningTime); + return; } - public override void Init() - { - On = false; - Instances = []; - } + soul.QuestioningTime -= Time.fixedDeltaTime; - public override void Add(byte playerId) + if (soul.QuestioningTime <= 0f) { - On = true; - Count = 0; - Souls = []; - Info = []; - Instances.Add(this); - WhispererId = playerId; + soul.QuestioningTime = 0f; CurrentlyQuestioning = (string.Empty, 0); - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - public override void OnPet(PlayerControl pc) - { - if (Souls.Exists(x => x.IsQuestioning)) return; - - Vector2 pos = pc.Pos(); - List souls = Souls.FindAll(x => x.IsQuestionAble && Vector2.Distance(pos, x.Position) <= 1f); - if (souls.Count == 0) return; - - Soul soul = souls.MinBy(x => Vector2.Distance(pos, x.Position)); - soul.QuestioningTime = Duration.GetInt(); - CurrentlyQuestioning.Name = soul.Player.PlayerId.ColoredPlayerName(); Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 1, CurrentlyQuestioning.Name); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soul.Player.PlayerId, soul.QuestioningTime); - } + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 2, CurrentlyQuestioning.Percent); + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soulPlayerId, soul.QuestioningTime); - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; + string info; - Soul soul = Souls.Find(x => x.IsQuestioning); - if (soul == null) return; - - byte soulPlayerId = soul.Player.PlayerId; - - if (Vector2.Distance(pc.Pos(), soul.Position) > 1f) + try { - soul.QuestioningTime = 0f; - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soulPlayerId, soul.QuestioningTime); - return; + PlayerState state = Main.PlayerStates[soulPlayerId]; + (DateTime TimeStamp, byte ID) killer = state.RealKiller; + int next = IRandom.Instance.Next(4); + if (state.deathReason == PlayerState.DeathReason.Disconnected) next = 2; + + info = next switch + { + 0 => string.Format(Translator.GetString("WhispererInfo.Color"), GetColorInfo(Utils.GetPlayerInfoById(killer.ID).DefaultOutfit.ColorId, out string colors), colors), + 1 => string.Format(Translator.GetString("WhispererInfo.Time"), (int)Math.Round((LastMeetingStart - killer.TimeStamp).TotalSeconds)), + 2 => string.Format(Translator.GetString("WhispererInfo.Role"), state.MainRole.ToColoredString()), + 3 => string.Format(Translator.GetString("WhispererInfo.KillerRole"), Main.PlayerStates[killer.ID].MainRole.ToColoredString()), + _ => string.Empty + }; + + info = $"{soulPlayerId.ColoredPlayerName()}: {info}"; + } + catch (Exception e) + { + Utils.ThrowException(e); + info = string.Empty; } - soul.QuestioningTime -= Time.fixedDeltaTime; - - if (soul.QuestioningTime <= 0f) + if (!string.IsNullOrEmpty(info)) { - soul.QuestioningTime = 0f; - CurrentlyQuestioning = (string.Empty, 0); + Info.Add(info); + if (Info.Count > 5) Info.RemoveAt(0); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 1, CurrentlyQuestioning.Name); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 2, CurrentlyQuestioning.Percent); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soulPlayerId, soul.QuestioningTime); - - string info; - - try - { - PlayerState state = Main.PlayerStates[soulPlayerId]; - (DateTime TimeStamp, byte ID) killer = state.RealKiller; - int next = IRandom.Instance.Next(4); - if (state.deathReason == PlayerState.DeathReason.Disconnected) next = 2; - - info = next switch - { - 0 => string.Format(Translator.GetString("WhispererInfo.Color"), GetColorInfo(Utils.GetPlayerInfoById(killer.ID).DefaultOutfit.ColorId, out string colors), colors), - 1 => string.Format(Translator.GetString("WhispererInfo.Time"), (int)Math.Round((LastMeetingStart - killer.TimeStamp).TotalSeconds)), - 2 => string.Format(Translator.GetString("WhispererInfo.Role"), state.MainRole.ToColoredString()), - 3 => string.Format(Translator.GetString("WhispererInfo.KillerRole"), Main.PlayerStates[killer.ID].MainRole.ToColoredString()), - _ => string.Empty - }; - - info = $"{soulPlayerId.ColoredPlayerName()}: {info}"; - } - catch (Exception e) - { - Utils.ThrowException(e); - info = string.Empty; - } - - if (!string.IsNullOrEmpty(info)) - { - Info.Add(info); - if (Info.Count > 5) Info.RemoveAt(0); - - pc.RpcRemoveAbilityUse(); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 4, info); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - } - else - { - if (Count++ < 10) return; - - Count = 0; - CurrentlyQuestioning.Percent = 100 - (int)(soul.QuestioningTime * 100f / Duration.GetFloat()); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 2, CurrentlyQuestioning.Percent); - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soulPlayerId, soul.QuestioningTime); + pc.RpcRemoveAbilityUse(); + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 4, info); Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } } - - // Color IDs: Red-0, Blue-1, Green-2, Pink-3, Orange-4, Yellow-5, Black-6, White-7, Purple-8, Brown-9, Cyan-10, Lime-11, Maroon-12, Rose-13, Banana-14, Gray-15, Tan-16, Coral-17 - // Lighter color IDs: 3, 4, 5, 7, 10, 11, 13, 14, 16, 17 == Pink, Orange, Yellow, White, Cyan, Lime, Rose, Banana, Tan, Coral - // Darker color IDs: 0, 1, 2, 6, 8, 9, 12, 15 == Red, Blue, Green, Black, Purple, Brown, Maroon, Gray - - private static string GetColorInfo(int colorId, out string colors) + else { - var darker = new List { 0, 1, 2, 6, 8, 9, 12, 15 }; - bool isDarker = darker.Contains(colorId); - Func selector = x => Utils.ColorString(Palette.PlayerColors[x], Palette.GetColorName(x)); - colors = isDarker ? string.Join('/', darker.Select(selector)) : string.Join('/', Enumerable.Range(0, 18).Except(darker).Select(selector)); - return isDarker ? Translator.GetString("WhispererInfo.ColorDark") : Translator.GetString("WhispererInfo.ColorLight"); - } + if (Count++ < 10) return; - public static void OnAnyoneDied(PlayerControl target) - { - foreach (Whisperer instance in Instances) - { - if (instance.Souls.Exists(x => x.Player.PlayerId == target.PlayerId)) continue; - - instance.Souls.Add(new(target)); - Utils.SendRPC(CustomRPC.SyncRoleData, instance.WhispererId, 5, target.PlayerId); - } - } - - public override void OnReportDeadBody() - { - LastMeetingStart = DateTime.Now; - } - - public override void AfterMeetingTasks() - { - foreach (Soul soul in Souls) - { - soul.QuestioningTime = 0f; - soul.NetObject ??= new(soul.Position, WhispererId.GetPlayer()); - soul.IsQuestionAble = true; - Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soul.Player.PlayerId, soul.QuestioningTime); - } - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - CurrentlyQuestioning.Name = reader.ReadString(); - break; - case 2: - CurrentlyQuestioning.Percent = reader.ReadPackedInt32(); - break; - case 3: - byte id = reader.ReadByte(); - Souls.Find(x => x.Player.PlayerId == id).QuestioningTime = reader.ReadSingle(); - break; - case 4: - Info.Add(reader.ReadString()); - if (Info.Count > 5) Info.RemoveAt(0); - - break; - case 5: - Souls.Add(new(reader.ReadByte().GetPlayer())); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != WhispererId || meeting || (seer.IsModClient() && !hud)) return string.Empty; - - return "" + string.Join('\n', Info) + (CurrentlyQuestioning.Percent > 0 ? string.Format(Translator.GetString("WhispererQuestioning"), CurrentlyQuestioning.Name, CurrentlyQuestioning.Percent) : string.Empty) + ""; - } - - private class Soul(PlayerControl player) - { - public bool IsQuestionAble { get; set; } - public Vector2 Position { get; } = player.Pos(); - public PlayerControl Player { get; } = player; - public SoulObject NetObject { get; set; } - public float QuestioningTime { get; set; } - public bool IsQuestioning => QuestioningTime > 0f; + Count = 0; + CurrentlyQuestioning.Percent = 100 - (int)(soul.QuestioningTime * 100f / Duration.GetFloat()); + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 2, CurrentlyQuestioning.Percent); + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soulPlayerId, soul.QuestioningTime); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } } + + // Color IDs: Red-0, Blue-1, Green-2, Pink-3, Orange-4, Yellow-5, Black-6, White-7, Purple-8, Brown-9, Cyan-10, Lime-11, Maroon-12, Rose-13, Banana-14, Gray-15, Tan-16, Coral-17 + // Lighter color IDs: 3, 4, 5, 7, 10, 11, 13, 14, 16, 17 == Pink, Orange, Yellow, White, Cyan, Lime, Rose, Banana, Tan, Coral + // Darker color IDs: 0, 1, 2, 6, 8, 9, 12, 15 == Red, Blue, Green, Black, Purple, Brown, Maroon, Gray + + private static string GetColorInfo(int colorId, out string colors) + { + var darker = new List { 0, 1, 2, 6, 8, 9, 12, 15 }; + bool isDarker = darker.Contains(colorId); + Func selector = x => Utils.ColorString(Palette.PlayerColors[x], Palette.GetColorName(x)); + colors = isDarker ? string.Join('/', darker.Select(selector)) : string.Join('/', Enumerable.Range(0, 18).Except(darker).Select(selector)); + return isDarker ? Translator.GetString("WhispererInfo.ColorDark") : Translator.GetString("WhispererInfo.ColorLight"); + } + + public static void OnAnyoneDied(PlayerControl target) + { + foreach (Whisperer instance in Instances) + { + if (instance.Souls.Exists(x => x.Player.PlayerId == target.PlayerId)) continue; + + instance.Souls.Add(new(target)); + Utils.SendRPC(CustomRPC.SyncRoleData, instance.WhispererId, 5, target.PlayerId); + } + } + + public override void OnReportDeadBody() + { + LastMeetingStart = DateTime.Now; + } + + public override void AfterMeetingTasks() + { + foreach (Soul soul in Souls) + { + soul.QuestioningTime = 0f; + soul.NetObject ??= new(soul.Position, WhispererId.GetPlayer()); + soul.IsQuestionAble = true; + Utils.SendRPC(CustomRPC.SyncRoleData, WhispererId, 3, soul.Player.PlayerId, soul.QuestioningTime); + } + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + CurrentlyQuestioning.Name = reader.ReadString(); + break; + case 2: + CurrentlyQuestioning.Percent = reader.ReadPackedInt32(); + break; + case 3: + byte id = reader.ReadByte(); + Souls.Find(x => x.Player.PlayerId == id).QuestioningTime = reader.ReadSingle(); + break; + case 4: + Info.Add(reader.ReadString()); + if (Info.Count > 5) Info.RemoveAt(0); + + break; + case 5: + Souls.Add(new(reader.ReadByte().GetPlayer())); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != WhispererId || meeting || (seer.IsModClient() && !hud)) return string.Empty; + + return "" + string.Join('\n', Info) + (CurrentlyQuestioning.Percent > 0 ? string.Format(Translator.GetString("WhispererQuestioning"), CurrentlyQuestioning.Name, CurrentlyQuestioning.Percent) : string.Empty) + ""; + } + + private class Soul(PlayerControl player) + { + public bool IsQuestionAble { get; set; } + public Vector2 Position { get; } = player.Pos(); + public PlayerControl Player { get; } = player; + public SoulObject NetObject { get; set; } + public float QuestioningTime { get; set; } + public bool IsQuestioning => QuestioningTime > 0f; + } } \ No newline at end of file diff --git a/Roles/Crewmate/Witness.cs b/Roles/Crewmate/Witness.cs index a860b08de..c540b6f4d 100644 --- a/Roles/Crewmate/Witness.cs +++ b/Roles/Crewmate/Witness.cs @@ -2,76 +2,75 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +internal class Witness : RoleBase { - internal class Witness : RoleBase + public static Dictionary AllKillers = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary AllKillers = []; + SetupRoleOptions(8550, TabGroup.CrewmateRoles, CustomRoles.Witness); - public static bool On; - public override bool IsEnable => On; + WitnessCD = new FloatOptionItem(8552, "AbilityCD", new(0f, 60f, 2.5f), 10f, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(8550, TabGroup.CrewmateRoles, CustomRoles.Witness); + WitnessTime = new IntegerOptionItem(8553, "WitnessTime", new(0, 90, 1), 10, TabGroup.CrewmateRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) + .SetValueFormat(OptionFormat.Seconds); - WitnessCD = new FloatOptionItem(8552, "AbilityCD", new(0f, 60f, 2.5f), 10f, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) - .SetValueFormat(OptionFormat.Seconds); + WitnessUsePet = CreatePetUseSetting(8554, CustomRoles.Witness); + } - WitnessTime = new IntegerOptionItem(8553, "WitnessTime", new(0, 90, 1), 10, TabGroup.CrewmateRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Witness]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte playerId) + { + On = true; + } - WitnessUsePet = CreatePetUseSetting(8554, CustomRoles.Witness); - } + public override void Init() + { + On = false; + AllKillers = []; + } - public override void Add(byte playerId) - { - On = true; - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = WitnessCD.GetFloat(); + } - public override void Init() - { - On = false; - AllKillers = []; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = WitnessCD.GetFloat(); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("WitnessButtonText")); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("WitnessButtonText")); - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + killer.SetKillCooldown(); + killer.Notify(AllKillers.ContainsKey(target.PlayerId) ? Translator.GetString("WitnessFoundKiller") : Translator.GetString("WitnessFoundInnocent")); + return false; + } - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } + public override void OnReportDeadBody() + { + AllKillers.Clear(); + } - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - killer.SetKillCooldown(); - killer.Notify(AllKillers.ContainsKey(target.PlayerId) ? Translator.GetString("WitnessFoundKiller") : Translator.GetString("WitnessFoundInnocent")); - return false; - } - - public override void OnReportDeadBody() - { - AllKillers.Clear(); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (AllKillers.TryGetValue(pc.PlayerId, out long ktime) && ktime + WitnessTime.GetInt() < Utils.TimeStamp) AllKillers.Remove(pc.PlayerId); - } + public override void OnFixedUpdate(PlayerControl pc) + { + if (AllKillers.TryGetValue(pc.PlayerId, out long ktime) && ktime + WitnessTime.GetInt() < Utils.TimeStamp) AllKillers.Remove(pc.PlayerId); } } \ No newline at end of file diff --git a/Roles/Crewmate/Wizard.cs b/Roles/Crewmate/Wizard.cs index 6e146ac71..9431a1139 100644 --- a/Roles/Crewmate/Wizard.cs +++ b/Roles/Crewmate/Wizard.cs @@ -5,298 +5,297 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Crewmate +namespace EHR.Crewmate; + +public class Wizard : RoleBase { - public class Wizard : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem Vision; + private static OptionItem MinSpeedValue; + private static OptionItem MaxSpeedValue; + private static OptionItem MinVisionValue; + private static OptionItem MaxVisionValue; + private static OptionItem MinKCDValue; + private static OptionItem MaxKCDValue; + private static OptionItem SpeedStep; + private static OptionItem VisionStep; + private static OptionItem KCDStep; + private static OptionItem AbilityCooldown; + private static OptionItem AbilityUseLimit; + public static OptionItem AbilityUseGainWithEachTaskCompleted; + public static OptionItem AbilityChargesWhenFinishedTasks; + + private static readonly Dictionary MaxBuffValues = new() { - public static bool On; - private static List Instances = []; + [Buff.Speed] = 3f, + [Buff.Vision] = 1.2f, + [Buff.KCD] = 40f + }; - private static OptionItem Vision; - private static OptionItem MinSpeedValue; - private static OptionItem MaxSpeedValue; - private static OptionItem MinVisionValue; - private static OptionItem MaxVisionValue; - private static OptionItem MinKCDValue; - private static OptionItem MaxKCDValue; - private static OptionItem SpeedStep; - private static OptionItem VisionStep; - private static OptionItem KCDStep; - private static OptionItem AbilityCooldown; - private static OptionItem AbilityUseLimit; - public static OptionItem AbilityUseGainWithEachTaskCompleted; - public static OptionItem AbilityChargesWhenFinishedTasks; + private static readonly Dictionary MinBuffValues = new() + { + [Buff.Speed] = 0.6f, + [Buff.Vision] = 0.3f, + [Buff.KCD] = 5f + }; - private static readonly Dictionary MaxBuffValues = new() + private static readonly Dictionary Steps = new() + { + [Buff.Speed] = 0.3f, + [Buff.Vision] = 0.15f, + [Buff.KCD] = 5f + }; + + private Dictionary BuffValues; + private int Count; + private Dictionary> PlayerBuffs; + private Buff SelectedBuff; + + private bool TaskMode; + + private byte WizardId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + StartSetup(648250) + .AutoSetupOption(ref Vision, 0.5f, new FloatValueRule(0f, 1.3f, 0.1f), OptionFormat.Multiplier) + .AutoSetupOption(ref MinSpeedValue, 0.6f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref MaxSpeedValue, 3f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref MinVisionValue, 0.3f, new FloatValueRule(0f, 1.35f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref MaxVisionValue, 1.2f, new FloatValueRule(0f, 1.35f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref MinKCDValue, 5f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref MaxKCDValue, 40f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref SpeedStep, 0.3f, new FloatValueRule(0.05f, 1f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref VisionStep, 0.15f, new FloatValueRule(0.05f, 0.5f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref KCDStep, 5f, new FloatValueRule(0.5f, 10f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityCooldown, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 20, 1), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.3f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) + .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) + .CreateOverrideTasksData(); + } + + public override void Init() + { + On = false; + Instances = []; + + MaxBuffValues[Buff.Speed] = MaxSpeedValue.GetFloat(); + MaxBuffValues[Buff.Vision] = MaxVisionValue.GetFloat(); + MaxBuffValues[Buff.KCD] = MaxKCDValue.GetFloat(); + + MinBuffValues[Buff.Speed] = MinSpeedValue.GetFloat(); + MinBuffValues[Buff.Vision] = MinVisionValue.GetFloat(); + MinBuffValues[Buff.KCD] = MinKCDValue.GetFloat(); + + Steps[Buff.Speed] = SpeedStep.GetFloat(); + Steps[Buff.Vision] = VisionStep.GetFloat(); + Steps[Buff.KCD] = KCDStep.GetFloat(); + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + WizardId = playerId; + + BuffValues = new() { - [Buff.Speed] = 3f, - [Buff.Vision] = 1.2f, - [Buff.KCD] = 40f + [Buff.Speed] = 1.5f, + [Buff.Vision] = 0.9f, + [Buff.KCD] = 15f }; - private static readonly Dictionary MinBuffValues = new() + PlayerBuffs = []; + SelectedBuff = default; + TaskMode = false; + Count = 0; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetInt(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = 1f; + AURoleOptions.ShapeshifterDuration = 1f; + + float vision = Vision.GetFloat(); + + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, vision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); + } + + public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte id) + { + foreach (Wizard instance in Instances) { - [Buff.Speed] = 0.6f, - [Buff.Vision] = 0.3f, - [Buff.KCD] = 5f - }; + if (!instance.PlayerBuffs.TryGetValue(id, out Dictionary buffs)) continue; - private static readonly Dictionary Steps = new() - { - [Buff.Speed] = 0.3f, - [Buff.Vision] = 0.15f, - [Buff.KCD] = 5f - }; - - private Dictionary BuffValues; - private int Count; - private Dictionary> PlayerBuffs; - private Buff SelectedBuff; - - private bool TaskMode; - - private byte WizardId; - - public override bool IsEnable => On; - - public override void SetupCustomOption() - { - StartSetup(648250) - .AutoSetupOption(ref Vision, 0.5f, new FloatValueRule(0f, 1.3f, 0.1f), OptionFormat.Multiplier) - .AutoSetupOption(ref MinSpeedValue, 0.6f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref MaxSpeedValue, 3f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref MinVisionValue, 0.3f, new FloatValueRule(0f, 1.35f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref MaxVisionValue, 1.2f, new FloatValueRule(0f, 1.35f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref MinKCDValue, 5f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref MaxKCDValue, 40f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref SpeedStep, 0.3f, new FloatValueRule(0.05f, 1f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref VisionStep, 0.15f, new FloatValueRule(0.05f, 0.5f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref KCDStep, 5f, new FloatValueRule(0.5f, 10f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref AbilityCooldown, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds) - .AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 20, 1), OptionFormat.Times) - .AutoSetupOption(ref AbilityUseGainWithEachTaskCompleted, 0.3f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) - .AutoSetupOption(ref AbilityChargesWhenFinishedTasks, 0.2f, new FloatValueRule(0f, 5f, 0.05f), OptionFormat.Times) - .CreateOverrideTasksData(); - } - - public override void Init() - { - On = false; - Instances = []; - - MaxBuffValues[Buff.Speed] = MaxSpeedValue.GetFloat(); - MaxBuffValues[Buff.Vision] = MaxVisionValue.GetFloat(); - MaxBuffValues[Buff.KCD] = MaxKCDValue.GetFloat(); - - MinBuffValues[Buff.Speed] = MinSpeedValue.GetFloat(); - MinBuffValues[Buff.Vision] = MinVisionValue.GetFloat(); - MinBuffValues[Buff.KCD] = MinKCDValue.GetFloat(); - - Steps[Buff.Speed] = SpeedStep.GetFloat(); - Steps[Buff.Vision] = VisionStep.GetFloat(); - Steps[Buff.KCD] = KCDStep.GetFloat(); - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - WizardId = playerId; - - BuffValues = new() + foreach ((Buff buff, float value) in buffs) { - [Buff.Speed] = 1.5f, - [Buff.Vision] = 0.9f, - [Buff.KCD] = 15f - }; - - PlayerBuffs = []; - SelectedBuff = default; - TaskMode = false; - Count = 0; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetInt(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = 1f; - AURoleOptions.ShapeshifterDuration = 1f; - - float vision = Vision.GetFloat(); - - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, vision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); - } - - public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte id) - { - foreach (Wizard instance in Instances) - { - if (!instance.PlayerBuffs.TryGetValue(id, out Dictionary buffs)) continue; - - foreach ((Buff buff, float value) in buffs) + switch (buff) { - switch (buff) + case Buff.Speed: { - case Buff.Speed: - { - Main.AllPlayerSpeed[id] = value; - break; - } - case Buff.Vision: - { - opt.SetFloat(FloatOptionNames.CrewLightMod, value); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, value); - break; - } - case Buff.KCD: - { - Main.AllPlayerKillCooldown[id] = value; - break; - } + Main.AllPlayerSpeed[id] = value; + break; + } + case Buff.Vision: + { + opt.SetFloat(FloatOptionNames.CrewLightMod, value); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, value); + break; + } + case Buff.KCD: + { + Main.AllPlayerKillCooldown[id] = value; + break; } } } } + } - public override void OnPet(PlayerControl pc) + public override void OnPet(PlayerControl pc) + { + if (SelectedBuff == Buff.KCD) + SelectedBuff = default; + else + SelectedBuff++; + + Utils.SendRPC(CustomRPC.SyncRoleData, WizardId, 1, (int)SelectedBuff); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifting) return false; + + BuffValues[SelectedBuff] += Steps[SelectedBuff]; + + if (BuffValues[SelectedBuff] > MaxBuffValues[SelectedBuff]) + BuffValues[SelectedBuff] = MinBuffValues[SelectedBuff]; + else + BuffValues[SelectedBuff] = (float)Math.Round(BuffValues[SelectedBuff], 1); + + Utils.SendRPC(CustomRPC.SyncRoleData, WizardId, 2, BuffValues[SelectedBuff]); + Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter); + return false; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target) || killer.GetAbilityUseLimit() < 1f) return false; + + float value = BuffValues[SelectedBuff]; + if (SelectedBuff == Buff.Speed) value = Math.Max(value, Main.MinSpeed); + + if (!PlayerBuffs.TryGetValue(target.PlayerId, out Dictionary buffs)) + PlayerBuffs[target.PlayerId] = new() { { SelectedBuff, value } }; + else + buffs[SelectedBuff] = value; + + target.SyncSettings(); + killer.SetKillCooldown(); + killer.RpcRemoveAbilityUse(); + + killer.Notify(string.Format(Translator.GetString("Wizard.BuffGivenNotify"), target.PlayerId.ColoredPlayerName(), Translator.GetString($"Wizard.Buff.{SelectedBuff}"), Math.Round(value, 1))); + Utils.SendRPC(CustomRPC.SyncRoleData, 3, target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance) return; + + if (Count++ < 40) return; + + Count = 0; + + switch (TaskMode) { - if (SelectedBuff == Buff.KCD) - SelectedBuff = default; - else - SelectedBuff++; - - Utils.SendRPC(CustomRPC.SyncRoleData, WizardId, 1, (int)SelectedBuff); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifting) return false; - - BuffValues[SelectedBuff] += Steps[SelectedBuff]; - - if (BuffValues[SelectedBuff] > MaxBuffValues[SelectedBuff]) - BuffValues[SelectedBuff] = MinBuffValues[SelectedBuff]; - else - BuffValues[SelectedBuff] = (float)Math.Round(BuffValues[SelectedBuff], 1); - - Utils.SendRPC(CustomRPC.SyncRoleData, WizardId, 2, BuffValues[SelectedBuff]); - Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter); - return false; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target) || killer.GetAbilityUseLimit() < 1f) return false; - - float value = BuffValues[SelectedBuff]; - if (SelectedBuff == Buff.Speed) value = Math.Max(value, Main.MinSpeed); - - if (!PlayerBuffs.TryGetValue(target.PlayerId, out Dictionary buffs)) - PlayerBuffs[target.PlayerId] = new() { { SelectedBuff, value } }; - else - buffs[SelectedBuff] = value; - - target.SyncSettings(); - killer.SetKillCooldown(); - killer.RpcRemoveAbilityUse(); - - killer.Notify(string.Format(Translator.GetString("Wizard.BuffGivenNotify"), target.PlayerId.ColoredPlayerName(), Translator.GetString($"Wizard.Buff.{SelectedBuff}"), Math.Round(value, 1))); - Utils.SendRPC(CustomRPC.SyncRoleData, 3, target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || ExileController.Instance) return; - - if (Count++ < 40) return; - - Count = 0; - - switch (TaskMode) - { - case true when (pc.GetAbilityUseLimit() >= 1 || pc.GetTaskState().IsTaskFinished) && pc.IsAlive(): - pc.RpcChangeRoleBasis(CustomRoles.Wizard); - TaskMode = false; - break; - case false when !pc.IsAlive(): - pc.RpcSetRoleDesync(RoleTypes.CrewmateGhost, pc.GetClientId()); - TaskMode = true; - break; - case false when pc.GetAbilityUseLimit() < 1 && pc.IsAlive(): - pc.RpcChangeRoleBasis(CustomRoles.CrewmateEHR); - pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); - TaskMode = true; - break; - } - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - SelectedBuff = (Buff)reader.ReadPackedInt32(); - break; - case 2: - BuffValues[SelectedBuff] = reader.ReadSingle(); - break; - case 3: - byte id = reader.ReadByte(); - float value = BuffValues[SelectedBuff]; - if (SelectedBuff == Buff.Speed) value = Math.Max(value, Main.MinSpeed); - - if (!PlayerBuffs.TryGetValue(id, out Dictionary buffs)) - PlayerBuffs[id] = new() { { SelectedBuff, value } }; - else - buffs[SelectedBuff] = value; - - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != WizardId || meeting) return string.Empty; - - if (PlayerBuffs.TryGetValue(target.PlayerId, out Dictionary buffs)) return string.Join('\n', buffs.Select(x => $"{Translator.GetString($"Wizard.Buff.{x.Key}")}: {Math.Round(x.Value, 1):N1}{GetBuffFormat(x.Key)}")); - - if (seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; - - return string.Format(Translator.GetString("Wizard.SelectedBuff"), SelectedBuff, BuffValues[SelectedBuff], GetBuffFormat(SelectedBuff)); - - string GetBuffFormat(Buff buff) - { - return buff switch - { - Buff.Speed => "x", - Buff.Vision => "x", - Buff.KCD => "s", - _ => throw new ArgumentOutOfRangeException(nameof(buff), buff, "Invalid buff") - }; - } - } - - private enum Buff - { - Speed, - Vision, - KCD + case true when (pc.GetAbilityUseLimit() >= 1 || pc.GetTaskState().IsTaskFinished) && pc.IsAlive(): + pc.RpcChangeRoleBasis(CustomRoles.Wizard); + TaskMode = false; + break; + case false when !pc.IsAlive(): + pc.RpcSetRoleDesync(RoleTypes.CrewmateGhost, pc.GetClientId()); + TaskMode = true; + break; + case false when pc.GetAbilityUseLimit() < 1 && pc.IsAlive(): + pc.RpcChangeRoleBasis(CustomRoles.CrewmateEHR); + pc.Notify(Translator.GetString("OutOfAbilityUsesDoMoreTasks")); + TaskMode = true; + break; } } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + SelectedBuff = (Buff)reader.ReadPackedInt32(); + break; + case 2: + BuffValues[SelectedBuff] = reader.ReadSingle(); + break; + case 3: + byte id = reader.ReadByte(); + float value = BuffValues[SelectedBuff]; + if (SelectedBuff == Buff.Speed) value = Math.Max(value, Main.MinSpeed); + + if (!PlayerBuffs.TryGetValue(id, out Dictionary buffs)) + PlayerBuffs[id] = new() { { SelectedBuff, value } }; + else + buffs[SelectedBuff] = value; + + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != WizardId || meeting) return string.Empty; + + if (PlayerBuffs.TryGetValue(target.PlayerId, out Dictionary buffs)) return string.Join('\n', buffs.Select(x => $"{Translator.GetString($"Wizard.Buff.{x.Key}")}: {Math.Round(x.Value, 1):N1}{GetBuffFormat(x.Key)}")); + + if (seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; + + return string.Format(Translator.GetString("Wizard.SelectedBuff"), SelectedBuff, BuffValues[SelectedBuff], GetBuffFormat(SelectedBuff)); + + string GetBuffFormat(Buff buff) + { + return buff switch + { + Buff.Speed => "x", + Buff.Vision => "x", + Buff.KCD => "s", + _ => throw new ArgumentOutOfRangeException(nameof(buff), buff, "Invalid buff") + }; + } + } + + private enum Buff + { + Speed, + Vision, + KCD + } } \ No newline at end of file diff --git a/Roles/IVanillaSettingHolder.cs b/Roles/IVanillaSettingHolder.cs index 21bac9cef..e1ee0fd6b 100644 --- a/Roles/IVanillaSettingHolder.cs +++ b/Roles/IVanillaSettingHolder.cs @@ -1,8 +1,7 @@ -namespace EHR +namespace EHR; + +internal interface IVanillaSettingHolder { - internal interface IVanillaSettingHolder - { - public TabGroup Tab { get; } - public void SetupCustomOption(); - } + public TabGroup Tab { get; } + public void SetupCustomOption(); } \ No newline at end of file diff --git a/Roles/Impostor/Abyssbringer.cs b/Roles/Impostor/Abyssbringer.cs index f841b38ba..8876fcb88 100644 --- a/Roles/Impostor/Abyssbringer.cs +++ b/Roles/Impostor/Abyssbringer.cs @@ -6,237 +6,236 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Abyssbringer : RoleBase { - public class Abyssbringer : RoleBase + public static bool On; + + public static OptionItem BlackHolePlaceCooldown; + private static OptionItem BlackHoleDespawnMode; + private static OptionItem BlackHoleDespawnTime; + private static OptionItem BlackHoleMovesTowardsNearestPlayer; + private static OptionItem BlackHoleMoveSpeed; + private static OptionItem BlackHoleRadius; + private byte AbyssbringerId; + + private List BlackHoles = []; + + private int Count; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 649800; + const TabGroup tab = TabGroup.ImpostorRoles; + const CustomRoles role = CustomRoles.Abyssbringer; + Options.SetupRoleOptions(id++, tab, role); - public static OptionItem BlackHolePlaceCooldown; - private static OptionItem BlackHoleDespawnMode; - private static OptionItem BlackHoleDespawnTime; - private static OptionItem BlackHoleMovesTowardsNearestPlayer; - private static OptionItem BlackHoleMoveSpeed; - private static OptionItem BlackHoleRadius; - private byte AbyssbringerId; + BlackHolePlaceCooldown = new IntegerOptionItem(++id, "BlackHolePlaceCooldown", new(1, 180, 1), 30, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); - private List BlackHoles = []; + BlackHoleDespawnMode = new StringOptionItem(++id, "BlackHoleDespawnMode", Enum.GetNames(), 0, tab) + .SetParent(Options.CustomRoleSpawnChances[role]); - private int Count; - public override bool IsEnable => On; + BlackHoleDespawnTime = new IntegerOptionItem(++id, "BlackHoleDespawnTime", new(1, 60, 1), 15, tab) + .SetParent(BlackHoleDespawnMode) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + BlackHoleMovesTowardsNearestPlayer = new BooleanOptionItem(++id, "BlackHoleMovesTowardsNearestPlayer", true, tab) + .SetParent(Options.CustomRoleSpawnChances[role]); + + BlackHoleMoveSpeed = new FloatOptionItem(++id, "BlackHoleMoveSpeed", new(0.25f, 10f, 0.25f), 1f, tab) + .SetParent(BlackHoleMovesTowardsNearestPlayer); + + BlackHoleRadius = new FloatOptionItem(++id, "BlackHoleRadius", new(0.1f, 5f, 0.1f), 1.2f, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Add(byte playerId) + { + On = true; + BlackHoles = []; + AbyssbringerId = playerId; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = BlackHolePlaceCooldown.GetInt(); + else { - var id = 649800; - const TabGroup tab = TabGroup.ImpostorRoles; - const CustomRoles role = CustomRoles.Abyssbringer; - Options.SetupRoleOptions(id++, tab, role); - - BlackHolePlaceCooldown = new IntegerOptionItem(++id, "BlackHolePlaceCooldown", new(1, 180, 1), 30, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - BlackHoleDespawnMode = new StringOptionItem(++id, "BlackHoleDespawnMode", Enum.GetNames(), 0, tab) - .SetParent(Options.CustomRoleSpawnChances[role]); - - BlackHoleDespawnTime = new IntegerOptionItem(++id, "BlackHoleDespawnTime", new(1, 60, 1), 15, tab) - .SetParent(BlackHoleDespawnMode) - .SetValueFormat(OptionFormat.Seconds); - - BlackHoleMovesTowardsNearestPlayer = new BooleanOptionItem(++id, "BlackHoleMovesTowardsNearestPlayer", true, tab) - .SetParent(Options.CustomRoleSpawnChances[role]); - - BlackHoleMoveSpeed = new FloatOptionItem(++id, "BlackHoleMoveSpeed", new(0.25f, 10f, 0.25f), 1f, tab) - .SetParent(BlackHoleMovesTowardsNearestPlayer); - - BlackHoleRadius = new FloatOptionItem(++id, "BlackHoleRadius", new(0.1f, 5f, 0.1f), 1.2f, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Add(byte playerId) - { - On = true; - BlackHoles = []; - AbyssbringerId = playerId; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = BlackHolePlaceCooldown.GetInt(); - else - { - AURoleOptions.ShapeshifterCooldown = BlackHolePlaceCooldown.GetInt(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - - CreateBlackHole(shapeshifter); - return false; - } - - public override void OnPet(PlayerControl pc) - { - CreateBlackHole(pc); - } - - public override bool OnVanish(PlayerControl pc) - { - CreateBlackHole(pc); - return false; - } - - private void CreateBlackHole(PlayerControl shapeshifter) - { - Vector2 pos = shapeshifter.Pos(); - PlainShipRoom room = shapeshifter.GetPlainShipRoom(); - string roomName = room == null ? string.Empty : Translator.GetString($"{room.RoomId}"); - BlackHoles.Add(new(new(pos), Utils.TimeStamp, pos, roomName, 0)); - Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 1, pos, roomName); - } - - public override void OnReportDeadBody() - { - if ((DespawnMode)BlackHoleDespawnMode.GetValue() == DespawnMode.AfterMeeting) - { - for (var i = 0; i < BlackHoles.Count; i++) - { - BlackHoles[i].NetObject.Despawn(); - BlackHoles.RemoveAt(i); - Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 3, i); - } - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (Count++ < 3) return; - - Count = 0; - - PlayerControl abyssbringer = AbyssbringerId.GetPlayer(); - int count = BlackHoles.Count; - - for (var i = 0; i < count; i++) - { - BlackHoleData blackHole = BlackHoles[i]; - - var despawnMode = (DespawnMode)BlackHoleDespawnMode.GetValue(); - - switch (despawnMode) - { - case DespawnMode.AfterTime when Utils.TimeStamp - blackHole.PlaceTimeStamp > BlackHoleDespawnTime.GetInt(): - RemoveBlackHole(); - continue; - case DespawnMode.AfterMeeting when GameStates.IsMeeting: - RemoveBlackHole(); - continue; - } - - PlayerControl nearestPlayer = Main.AllAlivePlayerControls.Without(pc).MinBy(x => Vector2.Distance(x.Pos(), blackHole.Position)); - - if (nearestPlayer != null) - { - Vector2 pos = nearestPlayer.Pos(); - - if (BlackHoleMovesTowardsNearestPlayer.GetBool() && GameStates.IsInTask && !ExileController.Instance) - { - Vector2 direction = (pos - blackHole.Position).normalized; - Vector2 newPosition = blackHole.Position + (direction * BlackHoleMoveSpeed.GetFloat() * Time.fixedDeltaTime); - blackHole.NetObject.TP(newPosition); - blackHole.Position = newPosition; - } - - if (Vector2.Distance(pos, blackHole.Position) <= BlackHoleRadius.GetFloat()) - { - nearestPlayer.RpcExileV2(); - blackHole.PlayersConsumed++; - Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 2, i); - Notify(); - - PlayerState state = Main.PlayerStates[nearestPlayer.PlayerId]; - state.deathReason = PlayerState.DeathReason.Consumed; - state.RealKiller = (DateTime.Now, AbyssbringerId); - state.SetDead(); - - if (despawnMode == DespawnMode.After1PlayerEaten) RemoveBlackHole(); - } - } - - continue; - - void RemoveBlackHole() - { - BlackHoles.RemoveAt(i); - blackHole.NetObject.Despawn(); - Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 3, i); - Notify(); - } - - void Notify() - { - Utils.NotifyRoles(SpecifySeer: abyssbringer, SpecifyTarget: abyssbringer); - } - } - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - Vector2 pos = reader.ReadVector2(); - string roomName = reader.ReadString(); - BlackHoles.Add(new(new(pos), Utils.TimeStamp, pos, roomName, 0)); - break; - case 2: - BlackHoleData blackHole = BlackHoles[reader.ReadPackedInt32()]; - blackHole.PlayersConsumed++; - break; - case 3: - BlackHoles.RemoveAt(reader.ReadPackedInt32()); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != AbyssbringerId || meeting || (seer.IsModClient() && !hud) || BlackHoles.Count == 0) return string.Empty; - - return string.Format(Translator.GetString("Abyssbringer.Suffix"), BlackHoles.Count, string.Join('\n', BlackHoles.Select(x => GetBlackHoleFormatText(x.RoomName, x.PlayersConsumed)))); - - string GetBlackHoleFormatText(string roomName, int playersConsumed) - { - string rn = roomName == string.Empty ? Translator.GetString("Outside") : roomName; - return string.Format(Translator.GetString("Abyssbringer.Suffix.BlackHole"), rn, playersConsumed); - } - } - - private enum DespawnMode - { - None, - AfterTime, - After1PlayerEaten, - AfterMeeting - } - - private class BlackHoleData(BlackHole NetObject, long PlaceTimeStamp, Vector2 Position, string RoomName, int PlayersConsumed) - { - public BlackHole NetObject { get; } = NetObject; - public long PlaceTimeStamp { get; } = PlaceTimeStamp; - public Vector2 Position { get; set; } = Position; - public string RoomName { get; } = RoomName; - public int PlayersConsumed { get; set; } = PlayersConsumed; + AURoleOptions.ShapeshifterCooldown = BlackHolePlaceCooldown.GetInt(); + AURoleOptions.ShapeshifterDuration = 1f; } } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + + CreateBlackHole(shapeshifter); + return false; + } + + public override void OnPet(PlayerControl pc) + { + CreateBlackHole(pc); + } + + public override bool OnVanish(PlayerControl pc) + { + CreateBlackHole(pc); + return false; + } + + private void CreateBlackHole(PlayerControl shapeshifter) + { + Vector2 pos = shapeshifter.Pos(); + PlainShipRoom room = shapeshifter.GetPlainShipRoom(); + string roomName = room == null ? string.Empty : Translator.GetString($"{room.RoomId}"); + BlackHoles.Add(new(new(pos), Utils.TimeStamp, pos, roomName, 0)); + Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 1, pos, roomName); + } + + public override void OnReportDeadBody() + { + if ((DespawnMode)BlackHoleDespawnMode.GetValue() == DespawnMode.AfterMeeting) + { + for (var i = 0; i < BlackHoles.Count; i++) + { + BlackHoles[i].NetObject.Despawn(); + BlackHoles.RemoveAt(i); + Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 3, i); + } + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (Count++ < 3) return; + + Count = 0; + + PlayerControl abyssbringer = AbyssbringerId.GetPlayer(); + int count = BlackHoles.Count; + + for (var i = 0; i < count; i++) + { + BlackHoleData blackHole = BlackHoles[i]; + + var despawnMode = (DespawnMode)BlackHoleDespawnMode.GetValue(); + + switch (despawnMode) + { + case DespawnMode.AfterTime when Utils.TimeStamp - blackHole.PlaceTimeStamp > BlackHoleDespawnTime.GetInt(): + RemoveBlackHole(); + continue; + case DespawnMode.AfterMeeting when GameStates.IsMeeting: + RemoveBlackHole(); + continue; + } + + PlayerControl nearestPlayer = Main.AllAlivePlayerControls.Without(pc).MinBy(x => Vector2.Distance(x.Pos(), blackHole.Position)); + + if (nearestPlayer != null) + { + Vector2 pos = nearestPlayer.Pos(); + + if (BlackHoleMovesTowardsNearestPlayer.GetBool() && GameStates.IsInTask && !ExileController.Instance) + { + Vector2 direction = (pos - blackHole.Position).normalized; + Vector2 newPosition = blackHole.Position + (direction * BlackHoleMoveSpeed.GetFloat() * Time.fixedDeltaTime); + blackHole.NetObject.TP(newPosition); + blackHole.Position = newPosition; + } + + if (Vector2.Distance(pos, blackHole.Position) <= BlackHoleRadius.GetFloat()) + { + nearestPlayer.RpcExileV2(); + blackHole.PlayersConsumed++; + Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 2, i); + Notify(); + + PlayerState state = Main.PlayerStates[nearestPlayer.PlayerId]; + state.deathReason = PlayerState.DeathReason.Consumed; + state.RealKiller = (DateTime.Now, AbyssbringerId); + state.SetDead(); + + if (despawnMode == DespawnMode.After1PlayerEaten) RemoveBlackHole(); + } + } + + continue; + + void RemoveBlackHole() + { + BlackHoles.RemoveAt(i); + blackHole.NetObject.Despawn(); + Utils.SendRPC(CustomRPC.SyncRoleData, AbyssbringerId, 3, i); + Notify(); + } + + void Notify() + { + Utils.NotifyRoles(SpecifySeer: abyssbringer, SpecifyTarget: abyssbringer); + } + } + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + Vector2 pos = reader.ReadVector2(); + string roomName = reader.ReadString(); + BlackHoles.Add(new(new(pos), Utils.TimeStamp, pos, roomName, 0)); + break; + case 2: + BlackHoleData blackHole = BlackHoles[reader.ReadPackedInt32()]; + blackHole.PlayersConsumed++; + break; + case 3: + BlackHoles.RemoveAt(reader.ReadPackedInt32()); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != AbyssbringerId || meeting || (seer.IsModClient() && !hud) || BlackHoles.Count == 0) return string.Empty; + + return string.Format(Translator.GetString("Abyssbringer.Suffix"), BlackHoles.Count, string.Join('\n', BlackHoles.Select(x => GetBlackHoleFormatText(x.RoomName, x.PlayersConsumed)))); + + string GetBlackHoleFormatText(string roomName, int playersConsumed) + { + string rn = roomName == string.Empty ? Translator.GetString("Outside") : roomName; + return string.Format(Translator.GetString("Abyssbringer.Suffix.BlackHole"), rn, playersConsumed); + } + } + + private enum DespawnMode + { + None, + AfterTime, + After1PlayerEaten, + AfterMeeting + } + + private class BlackHoleData(BlackHole NetObject, long PlaceTimeStamp, Vector2 Position, string RoomName, int PlayersConsumed) + { + public BlackHole NetObject { get; } = NetObject; + public long PlaceTimeStamp { get; } = PlaceTimeStamp; + public Vector2 Position { get; set; } = Position; + public string RoomName { get; } = RoomName; + public int PlayersConsumed { get; set; } = PlayersConsumed; + } } \ No newline at end of file diff --git a/Roles/Impostor/AntiAdminer.cs b/Roles/Impostor/AntiAdminer.cs index f643c2a40..b4387eb34 100644 --- a/Roles/Impostor/AntiAdminer.cs +++ b/Roles/Impostor/AntiAdminer.cs @@ -4,321 +4,320 @@ using System.Linq; using AmongUs.GameOptions; using Monitor = EHR.Crewmate.Monitor; -namespace EHR.Impostor +namespace EHR.Impostor; + +// Reference: https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Mode/SuperHostRoles/BlockTool.cs +// Reference:https://github.com/Yumenopai/TownOfHost_Y/tree/AntiAdminer +internal class AntiAdminer : RoleBase { - // Reference: https://github.com/ykundesu/SuperNewRoles/blob/master/SuperNewRoles/Mode/SuperHostRoles/BlockTool.cs - // Reference:https://github.com/Yumenopai/TownOfHost_Y/tree/AntiAdminer - internal class AntiAdminer : RoleBase + public enum Device { - public enum Device + Admin, + Vitals, + DoorLog, + Camera + } + + private const int Id = 2300; + private static List PlayerIdList = []; + + private static OptionItem CanCheckCamera; + public static OptionItem EnableExtraAbility; + private static OptionItem CanOnlyUseWhileAnyWatch; + private static OptionItem Delay; + + public static bool IsAdminWatch; + public static bool IsVitalWatch; + public static bool IsDoorLogWatch; + public static bool IsCameraWatch; + public static Dictionary> PlayersNearDevices = []; + private byte AntiAdminerId; + + private int Count; + private long ExtraAbilityStartTimeStamp; + + private bool IsMonitor; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.AntiAdminer); + + CanCheckCamera = new BooleanOptionItem(Id + 10, "CanCheckCamera", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.AntiAdminer]); + + EnableExtraAbility = new BooleanOptionItem(Id + 11, "EnableExtraAbility", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.AntiAdminer]); + + CanOnlyUseWhileAnyWatch = new BooleanOptionItem(Id + 12, "CanOnlyUseWhileAnyWatch", true, TabGroup.ImpostorRoles) + .SetParent(EnableExtraAbility); + + Delay = new FloatOptionItem(Id + 13, "AADelay", new(0f, 20f, 0.5f), 5f, TabGroup.ImpostorRoles) + .SetParent(EnableExtraAbility) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + PlayersNearDevices = []; + IsAdminWatch = false; + IsVitalWatch = false; + IsDoorLogWatch = false; + IsCameraWatch = false; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + IsMonitor = Main.PlayerStates[playerId].MainRole == CustomRoles.Monitor; + ExtraAbilityStartTimeStamp = 0; + AntiAdminerId = playerId; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) return true; + + if (IsMonitor || !EnableExtraAbility.GetBool() || ExtraAbilityStartTimeStamp > 0 || (CanOnlyUseWhileAnyWatch.GetBool() && !IsAdminWatch && !IsVitalWatch && !IsDoorLogWatch && !IsCameraWatch)) return false; + + ExtraAbilityStartTimeStamp = Utils.TimeStamp; + shapeshifter.RpcResetAbilityCooldown(); + Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter); + + foreach (PlayerControl pc in PlayersNearDevices.Keys.ToValidPlayers().Where(x => x.IsAlive())) pc.Notify(Translator.GetString("AAWarning"), Delay.GetFloat()); + + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (!IsMonitor) { - Admin, - Vitals, - DoorLog, - Camera - } - - private const int Id = 2300; - private static List PlayerIdList = []; - - private static OptionItem CanCheckCamera; - public static OptionItem EnableExtraAbility; - private static OptionItem CanOnlyUseWhileAnyWatch; - private static OptionItem Delay; - - public static bool IsAdminWatch; - public static bool IsVitalWatch; - public static bool IsDoorLogWatch; - public static bool IsCameraWatch; - public static Dictionary> PlayersNearDevices = []; - private byte AntiAdminerId; - - private int Count; - private long ExtraAbilityStartTimeStamp; - - private bool IsMonitor; - - public override bool IsEnable => PlayerIdList.Count > 0; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.AntiAdminer); - - CanCheckCamera = new BooleanOptionItem(Id + 10, "CanCheckCamera", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.AntiAdminer]); - - EnableExtraAbility = new BooleanOptionItem(Id + 11, "EnableExtraAbility", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.AntiAdminer]); - - CanOnlyUseWhileAnyWatch = new BooleanOptionItem(Id + 12, "CanOnlyUseWhileAnyWatch", true, TabGroup.ImpostorRoles) - .SetParent(EnableExtraAbility); - - Delay = new FloatOptionItem(Id + 13, "AADelay", new(0f, 20f, 0.5f), 5f, TabGroup.ImpostorRoles) - .SetParent(EnableExtraAbility) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - PlayersNearDevices = []; - IsAdminWatch = false; - IsVitalWatch = false; - IsDoorLogWatch = false; - IsCameraWatch = false; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - IsMonitor = Main.PlayerStates[playerId].MainRole == CustomRoles.Monitor; - ExtraAbilityStartTimeStamp = 0; - AntiAdminerId = playerId; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting) return true; - - if (IsMonitor || !EnableExtraAbility.GetBool() || ExtraAbilityStartTimeStamp > 0 || (CanOnlyUseWhileAnyWatch.GetBool() && !IsAdminWatch && !IsVitalWatch && !IsDoorLogWatch && !IsCameraWatch)) return false; - - ExtraAbilityStartTimeStamp = Utils.TimeStamp; - shapeshifter.RpcResetAbilityCooldown(); - Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: shapeshifter); - - foreach (PlayerControl pc in PlayersNearDevices.Keys.ToValidPlayers().Where(x => x.IsAlive())) pc.Notify(Translator.GetString("AAWarning"), Delay.GetFloat()); - - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (!IsMonitor) + if (EnableExtraAbility.GetBool()) { - if (EnableExtraAbility.GetBool()) - { - AURoleOptions.ShapeshifterCooldown = Math.Clamp(Options.DefaultKillCooldown - Delay.GetFloat() - 2f, Delay.GetFloat() + 1f, Options.DefaultKillCooldown); - AURoleOptions.ShapeshifterDuration = 1f; - } - - return; + AURoleOptions.ShapeshifterCooldown = Math.Clamp(Options.DefaultKillCooldown - Delay.GetFloat() - 2f, Delay.GetFloat() + 1f, Options.DefaultKillCooldown); + AURoleOptions.ShapeshifterDuration = 1f; } - AURoleOptions.EngineerCooldown = 0f; - AURoleOptions.EngineerInVentMaxTime = 0f; - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!IsEnable) return; - - var notify = false; - - if (!IsMonitor && ExtraAbilityStartTimeStamp > 0 && EnableExtraAbility.GetBool()) - { - if (ExtraAbilityStartTimeStamp + Delay.GetInt() < Utils.TimeStamp) - { - ExtraAbilityStartTimeStamp = 0; - player.RpcResetAbilityCooldown(); - player.Notify(Translator.GetString("AADone")); - - foreach (PlayerControl pc in PlayersNearDevices.Keys.ToValidPlayers().Where(x => x.IsAlive() && player.RpcCheckAndMurder(x, true))) - pc.Suicide(realKiller: player); - } - else - { - notify = true; - - foreach (PlayerControl pc in PlayersNearDevices.Keys.ToValidPlayers().Where(x => x.IsAlive())) - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - } - - Count--; - if (Count > 0) return; - - Count = notify || ExtraAbilityStartTimeStamp > 0 ? 1 : 5; - - PlayersNearDevices = []; - bool Admin = false, Camera = false, DoorLog = false, Vital = false; - float usableDistance = DisableDevice.UsableDistance; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.inVent || (pc.IsImpostor() && !IsMonitor)) continue; - - try - { - Vector2 PlayerPos = pc.Pos(); - - switch (Main.NormalOptions.MapId) - { - case 0: - if (!Options.DisableSkeldAdmin.GetBool()) - { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldAdmin"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Admin); - } - - if (!Options.DisableSkeldCamera.GetBool()) - { - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldCamera"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Camera); - } - - break; - case 1: - if (!Options.DisableMiraHQAdmin.GetBool()) - { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQAdmin"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Admin); - } - - if (!Options.DisableMiraHQDoorLog.GetBool()) - { - DoorLog |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQDoorLog"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.DoorLog); - } - - break; - case 2: - if (!Options.DisablePolusAdmin.GetBool()) - { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusLeftAdmin"]) <= usableDistance; - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusRightAdmin"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Admin); - } - - if (!Options.DisablePolusCamera.GetBool()) - { - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusCamera"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Camera); - } - - if (!Options.DisablePolusVital.GetBool()) - { - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusVital"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Vitals); - } - - break; - case 3: - if (!Options.DisableSkeldAdmin.GetBool()) - { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksAdmin"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Admin); - } - - if (!Options.DisableSkeldCamera.GetBool()) - { - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksCamera"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Camera); - } - - break; - case 4: - if (!Options.DisableAirshipCockpitAdmin.GetBool()) - { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCockpitAdmin"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Admin); - } - - if (!Options.DisableAirshipRecordsAdmin.GetBool()) - { - Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipRecordsAdmin"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Admin); - } - - if (!Options.DisableAirshipCamera.GetBool()) - { - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCamera"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Camera); - } - - if (!Options.DisableAirshipVital.GetBool()) - { - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipVital"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Vitals); - } - - break; - case 5: - if (!Options.DisableFungleCamera.GetBool()) - { - Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleCamera"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Camera); - } - - if (!Options.DisableFungleVital.GetBool()) - { - Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleVital"]) <= usableDistance; - AddDeviceUse(pc.PlayerId, Device.Vitals); - } - - break; - } - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "AntiAdminer"); - } - } - - var change = false; - - change |= IsAdminWatch != Admin; - IsAdminWatch = Admin; - change |= IsVitalWatch != Vital; - IsVitalWatch = Vital; - change |= IsDoorLogWatch != DoorLog; - IsDoorLogWatch = DoorLog; - - if (IsMonitor ? Monitor.CanCheckCamera.GetBool() : CanCheckCamera.GetBool()) - { - change |= IsCameraWatch != Camera; - IsCameraWatch = Camera; - } - - if (notify || change) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - return; + } - void AddDeviceUse(byte id, Device device) + AURoleOptions.EngineerCooldown = 0f; + AURoleOptions.EngineerInVentMaxTime = 0f; + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!IsEnable) return; + + var notify = false; + + if (!IsMonitor && ExtraAbilityStartTimeStamp > 0 && EnableExtraAbility.GetBool()) + { + if (ExtraAbilityStartTimeStamp + Delay.GetInt() < Utils.TimeStamp) { - if (!PlayersNearDevices.TryGetValue(id, out var devices)) - PlayersNearDevices[id] = [device]; - else devices.Add(device); + ExtraAbilityStartTimeStamp = 0; + player.RpcResetAbilityCooldown(); + player.Notify(Translator.GetString("AADone")); + + foreach (PlayerControl pc in PlayersNearDevices.Keys.ToValidPlayers().Where(x => x.IsAlive() && player.RpcCheckAndMurder(x, true))) + pc.Suicide(realKiller: player); + } + else + { + notify = true; + + foreach (PlayerControl pc in PlayersNearDevices.Keys.ToValidPlayers().Where(x => x.IsAlive())) + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + Count--; + if (Count > 0) return; + + Count = notify || ExtraAbilityStartTimeStamp > 0 ? 1 : 5; + + PlayersNearDevices = []; + bool Admin = false, Camera = false, DoorLog = false, Vital = false; + float usableDistance = DisableDevice.UsableDistance; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (Main.PlayerStates[seer.PlayerId].Role is AntiAdminer self && seer.PlayerId == target.PlayerId && self.AntiAdminerId == seer.PlayerId) + if (pc.inVent || (pc.IsImpostor() && !IsMonitor)) continue; + + try { - return self.ExtraAbilityStartTimeStamp > 0 - ? $"<#ffffff>▩ {Delay.GetInt() - (Utils.TimeStamp - self.ExtraAbilityStartTimeStamp):N0}" - : string.Empty; + Vector2 PlayerPos = pc.Pos(); + + switch (Main.NormalOptions.MapId) + { + case 0: + if (!Options.DisableSkeldAdmin.GetBool()) + { + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldAdmin"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Admin); + } + + if (!Options.DisableSkeldCamera.GetBool()) + { + Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["SkeldCamera"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Camera); + } + + break; + case 1: + if (!Options.DisableMiraHQAdmin.GetBool()) + { + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQAdmin"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Admin); + } + + if (!Options.DisableMiraHQDoorLog.GetBool()) + { + DoorLog |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["MiraHQDoorLog"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.DoorLog); + } + + break; + case 2: + if (!Options.DisablePolusAdmin.GetBool()) + { + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusLeftAdmin"]) <= usableDistance; + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusRightAdmin"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Admin); + } + + if (!Options.DisablePolusCamera.GetBool()) + { + Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusCamera"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Camera); + } + + if (!Options.DisablePolusVital.GetBool()) + { + Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["PolusVital"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Vitals); + } + + break; + case 3: + if (!Options.DisableSkeldAdmin.GetBool()) + { + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksAdmin"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Admin); + } + + if (!Options.DisableSkeldCamera.GetBool()) + { + Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["DleksCamera"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Camera); + } + + break; + case 4: + if (!Options.DisableAirshipCockpitAdmin.GetBool()) + { + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCockpitAdmin"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Admin); + } + + if (!Options.DisableAirshipRecordsAdmin.GetBool()) + { + Admin |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipRecordsAdmin"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Admin); + } + + if (!Options.DisableAirshipCamera.GetBool()) + { + Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipCamera"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Camera); + } + + if (!Options.DisableAirshipVital.GetBool()) + { + Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["AirshipVital"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Vitals); + } + + break; + case 5: + if (!Options.DisableFungleCamera.GetBool()) + { + Camera |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleCamera"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Camera); + } + + if (!Options.DisableFungleVital.GetBool()) + { + Vital |= Vector2.Distance(PlayerPos, DisableDevice.DevicePos["FungleVital"]) <= usableDistance; + AddDeviceUse(pc.PlayerId, Device.Vitals); + } + + break; + } } - - if (!PlayersNearDevices.ContainsKey(seer.PlayerId)) return string.Empty; - - AntiAdminer aa = null; - - foreach (byte id in PlayerIdList) + catch (Exception ex) { - if (Main.PlayerStates[id].Role is not AntiAdminer { IsEnable: true, IsMonitor: false } x || x.ExtraAbilityStartTimeStamp == 0) continue; - - if (aa != null && x.ExtraAbilityStartTimeStamp >= aa.ExtraAbilityStartTimeStamp) continue; - - aa = x; + Logger.Error(ex.ToString(), "AntiAdminer"); } - - return aa == null ? string.Empty : $"<#ffff00>\u26a0 {Delay.GetInt() - (Utils.TimeStamp - aa.ExtraAbilityStartTimeStamp):N0}"; } - public override bool CanUseVent(PlayerControl pc, int ventId) + var change = false; + + change |= IsAdminWatch != Admin; + IsAdminWatch = Admin; + change |= IsVitalWatch != Vital; + IsVitalWatch = Vital; + change |= IsDoorLogWatch != DoorLog; + IsDoorLogWatch = DoorLog; + + if (IsMonitor ? Monitor.CanCheckCamera.GetBool() : CanCheckCamera.GetBool()) { - return !IsMonitor || pc.GetClosestVent()?.Id == ventId; + change |= IsCameraWatch != Camera; + IsCameraWatch = Camera; + } + + if (notify || change) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + + return; + + void AddDeviceUse(byte id, Device device) + { + if (!PlayersNearDevices.TryGetValue(id, out var devices)) + PlayersNearDevices[id] = [device]; + else devices.Add(device); } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (Main.PlayerStates[seer.PlayerId].Role is AntiAdminer self && seer.PlayerId == target.PlayerId && self.AntiAdminerId == seer.PlayerId) + { + return self.ExtraAbilityStartTimeStamp > 0 + ? $"<#ffffff>▩ {Delay.GetInt() - (Utils.TimeStamp - self.ExtraAbilityStartTimeStamp):N0}" + : string.Empty; + } + + if (!PlayersNearDevices.ContainsKey(seer.PlayerId)) return string.Empty; + + AntiAdminer aa = null; + + foreach (byte id in PlayerIdList) + { + if (Main.PlayerStates[id].Role is not AntiAdminer { IsEnable: true, IsMonitor: false } x || x.ExtraAbilityStartTimeStamp == 0) continue; + + if (aa != null && x.ExtraAbilityStartTimeStamp >= aa.ExtraAbilityStartTimeStamp) continue; + + aa = x; + } + + return aa == null ? string.Empty : $"<#ffff00>\u26a0 {Delay.GetInt() - (Utils.TimeStamp - aa.ExtraAbilityStartTimeStamp):N0}"; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !IsMonitor || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Impostor/Assassin.cs b/Roles/Impostor/Assassin.cs index 27e4454ce..c3217865c 100644 --- a/Roles/Impostor/Assassin.cs +++ b/Roles/Impostor/Assassin.cs @@ -6,194 +6,193 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Assassin : RoleBase { - internal class Assassin : RoleBase + private const int Id = 700; + public static List PlayerIdList = []; + + private static OptionItem MarkCooldownOpt; + public static OptionItem AssassinateCooldownOpt; + private static OptionItem CanKillAfterAssassinateOpt; + private float AssassinateCooldown; + private bool CanKillAfterAssassinate; + private bool IsUndertaker; + + private float MarkCooldown; + + public byte MarkedPlayer; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 700; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Assassin); - private static OptionItem MarkCooldownOpt; - public static OptionItem AssassinateCooldownOpt; - private static OptionItem CanKillAfterAssassinateOpt; - private float AssassinateCooldown; - private bool CanKillAfterAssassinate; - private bool IsUndertaker; + MarkCooldownOpt = new FloatOptionItem(Id + 10, "AssassinMarkCooldown", new(0f, 180f, 0.5f), 1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Assassin]) + .SetValueFormat(OptionFormat.Seconds); - private float MarkCooldown; + AssassinateCooldownOpt = new FloatOptionItem(Id + 11, "AssassinAssassinateCooldown", new(0f, 180f, 0.5f), 18.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Assassin]) + .SetValueFormat(OptionFormat.Seconds); - public byte MarkedPlayer; + CanKillAfterAssassinateOpt = new BooleanOptionItem(Id + 12, "AssassinCanKillAfterAssassinate", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Assassin]); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + MarkedPlayer = byte.MaxValue; + IsUndertaker = false; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + MarkedPlayer = byte.MaxValue; + IsUndertaker = Main.PlayerStates[playerId].MainRole == CustomRoles.Undertaker; + + if (IsUndertaker) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Assassin); - - MarkCooldownOpt = new FloatOptionItem(Id + 10, "AssassinMarkCooldown", new(0f, 180f, 0.5f), 1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Assassin]) - .SetValueFormat(OptionFormat.Seconds); - - AssassinateCooldownOpt = new FloatOptionItem(Id + 11, "AssassinAssassinateCooldown", new(0f, 180f, 0.5f), 18.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Assassin]) - .SetValueFormat(OptionFormat.Seconds); - - CanKillAfterAssassinateOpt = new BooleanOptionItem(Id + 12, "AssassinCanKillAfterAssassinate", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Assassin]); + MarkCooldown = Undertaker.UndertakerMarkCooldown.GetFloat(); + AssassinateCooldown = Undertaker.UndertakerAssassinateCooldown.GetFloat(); + CanKillAfterAssassinate = Undertaker.UndertakerCanKillAfterAssassinate.GetBool(); } - - public override void Init() + else { - PlayerIdList = []; - MarkedPlayer = byte.MaxValue; - IsUndertaker = false; + MarkCooldown = MarkCooldownOpt.GetFloat(); + AssassinateCooldown = AssassinateCooldownOpt.GetFloat(); + CanKillAfterAssassinate = CanKillAfterAssassinateOpt.GetBool(); } + } - public override void Add(byte playerId) + private void SendRPC(byte playerId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetMarkedPlayer, SendOption.Reliable); + writer.Write(playerId); + writer.Write(MarkedPlayer); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + + if (Main.PlayerStates[playerId].Role is not Assassin assassin) return; + + assassin.MarkedPlayer = targetId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = id.IsPlayerShifted() ? DefaultKillCooldown : MarkCooldown; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = AssassinateCooldown; + else { - PlayerIdList.Add(playerId); - MarkedPlayer = byte.MaxValue; - IsUndertaker = Main.PlayerStates[playerId].MainRole == CustomRoles.Undertaker; + if (UsePets.GetBool()) return; - if (IsUndertaker) - { - MarkCooldown = Undertaker.UndertakerMarkCooldown.GetFloat(); - AssassinateCooldown = Undertaker.UndertakerAssassinateCooldown.GetFloat(); - CanKillAfterAssassinate = Undertaker.UndertakerCanKillAfterAssassinate.GetBool(); - } - else - { - MarkCooldown = MarkCooldownOpt.GetFloat(); - AssassinateCooldown = AssassinateCooldownOpt.GetFloat(); - CanKillAfterAssassinate = CanKillAfterAssassinateOpt.GetBool(); - } + AURoleOptions.ShapeshifterCooldown = AssassinateCooldown; } + } - private void SendRPC(byte playerId) + public override bool CanUseKillButton(PlayerControl pc) + { + if (pc == null || !pc.IsAlive()) return false; + + return CanKillAfterAssassinate || !pc.IsShifted(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.IsShifted()) { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetMarkedPlayer, SendOption.Reliable); - writer.Write(playerId); - writer.Write(MarkedPlayer); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - - if (Main.PlayerStates[playerId].Role is not Assassin assassin) return; - - assassin.MarkedPlayer = targetId; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = id.IsPlayerShifted() ? DefaultKillCooldown : MarkCooldown; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = AssassinateCooldown; - else - { - if (UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = AssassinateCooldown; - } - } - - public override bool CanUseKillButton(PlayerControl pc) - { - if (pc == null || !pc.IsAlive()) return false; - - return CanKillAfterAssassinate || !pc.IsShifted(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.IsShifted()) - { - killer.ResetKillCooldown(); - killer.SyncSettings(); - return CanUseKillButton(killer); - } - - MarkedPlayer = target.PlayerId; - SendRPC(killer.PlayerId); killer.ResetKillCooldown(); - killer.SetKillCooldown(); - if (killer.IsModClient()) killer.RpcResetAbilityCooldown(); - killer.SyncSettings(); - killer.RPCPlayCustomSound("Clothe"); - return false; + return CanUseKillButton(killer); } - public override void OnPet(PlayerControl pc) + MarkedPlayer = target.PlayerId; + SendRPC(killer.PlayerId); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + if (killer.IsModClient()) killer.RpcResetAbilityCooldown(); + + killer.SyncSettings(); + killer.RPCPlayCustomSound("Clothe"); + return false; + } + + public override void OnPet(PlayerControl pc) + { + OnShapeshift(pc, null, true); + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl t, bool shapeshifting) + { + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; + + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + Take(pc); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + if (pc == null || !pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; + + if (!IsUndertaker) { - OnShapeshift(pc, null, true); + LateTask.New(() => Take(pc), 1.5f, "Ninja Vanish"); + return true; } - public override bool OnShapeshift(PlayerControl pc, PlayerControl t, bool shapeshifting) + Take(pc); + return false; + } + + private void Take(PlayerControl pc) + { + if (MarkedPlayer != byte.MaxValue) { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; + PlayerControl target = Utils.GetPlayerById(MarkedPlayer); + bool tpSuccess = IsUndertaker ? target.TP(pc) : pc.TP(target); - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - Take(pc); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - if (pc == null || !pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; - - if (!IsUndertaker) + if (!(target == null || !target.IsAlive() || Pelican.IsEaten(target.PlayerId) || target.inVent || !GameStates.IsInTask) && tpSuccess && pc.RpcCheckAndMurder(target)) { - LateTask.New(() => Take(pc), 1.5f, "Ninja Vanish"); - return true; + MarkedPlayer = byte.MaxValue; + SendRPC(pc.PlayerId); + pc.ResetKillCooldown(); + pc.SyncSettings(); + pc.SetKillCooldown(DefaultKillCooldown); } - - Take(pc); - return false; + else if (!tpSuccess) pc.Notify(GetString("TargetCannotBeTeleported")); } + } - private void Take(PlayerControl pc) + public override void SetButtonTexts(HudManager __instance, byte playerId) + { + bool shifted = playerId.IsPlayerShifted(); + __instance.KillButton.OverrideText(!shifted ? GetString("AssassinMarkButtonText") : GetString("KillButtonText")); + + if (MarkedPlayer != byte.MaxValue && !shifted) { - if (MarkedPlayer != byte.MaxValue) - { - PlayerControl target = Utils.GetPlayerById(MarkedPlayer); - bool tpSuccess = IsUndertaker ? target.TP(pc) : pc.TP(target); - - if (!(target == null || !target.IsAlive() || Pelican.IsEaten(target.PlayerId) || target.inVent || !GameStates.IsInTask) && tpSuccess && pc.RpcCheckAndMurder(target)) - { - MarkedPlayer = byte.MaxValue; - SendRPC(pc.PlayerId); - pc.ResetKillCooldown(); - pc.SyncSettings(); - pc.SetKillCooldown(DefaultKillCooldown); - } - else if (!tpSuccess) pc.Notify(GetString("TargetCannotBeTeleported")); - } - } - - public override void SetButtonTexts(HudManager __instance, byte playerId) - { - bool shifted = playerId.IsPlayerShifted(); - __instance.KillButton.OverrideText(!shifted ? GetString("AssassinMarkButtonText") : GetString("KillButtonText")); - - if (MarkedPlayer != byte.MaxValue && !shifted) - { - if (!UsePets.GetBool()) - __instance.AbilityButton.OverrideText(GetString("AssassinShapeshiftText")); - else - __instance.PetButton.OverrideText(GetString("AssassinShapeshiftText")); - } + if (!UsePets.GetBool()) + __instance.AbilityButton.OverrideText(GetString("AssassinShapeshiftText")); + else + __instance.PetButton.OverrideText(GetString("AssassinShapeshiftText")); } } } \ No newline at end of file diff --git a/Roles/Impostor/Assumer.cs b/Roles/Impostor/Assumer.cs index 1eb3008f8..68e67df84 100644 --- a/Roles/Impostor/Assumer.cs +++ b/Roles/Impostor/Assumer.cs @@ -1,87 +1,86 @@ using System.Collections.Generic; using EHR.Patches; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Assumer : RoleBase { - public class Assumer : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem VoteReceiverDies; + private static OptionItem MinPlayersToAssume; + private static OptionItem CanKill; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private byte AssumerId; + + private (byte Id, int VoteNum) Assumption; + private bool HasAssumed => Assumption.Id != byte.MaxValue; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(649250) + .AutoSetupOption(ref VoteReceiverDies, false) + .AutoSetupOption(ref MinPlayersToAssume, 6, new IntegerValueRule(1, 15, 1), OptionFormat.Players) + .AutoSetupOption(ref CanKill, true) + .AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanKill) + .AutoSetupOption(ref CanVent, false); + } - private static OptionItem VoteReceiverDies; - private static OptionItem MinPlayersToAssume; - private static OptionItem CanKill; - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private byte AssumerId; + public override void Init() + { + On = false; + Instances = []; + } - private (byte Id, int VoteNum) Assumption; - private bool HasAssumed => Assumption.Id != byte.MaxValue; - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + AssumerId = playerId; + Assumption = (byte.MaxValue, 0); + } - public override void SetupCustomOption() + public override bool CanUseKillButton(PlayerControl pc) + { + return CanKill.GetBool(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public static void OnVotingEnd(Dictionary voteNum) + { + if (!On) return; + + foreach (Assumer instance in Instances) { - StartSetup(649250) - .AutoSetupOption(ref VoteReceiverDies, false) - .AutoSetupOption(ref MinPlayersToAssume, 6, new IntegerValueRule(1, 15, 1), OptionFormat.Players) - .AutoSetupOption(ref CanKill, true) - .AutoSetupOption(ref KillCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanKill) - .AutoSetupOption(ref CanVent, false); - } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - AssumerId = playerId; - Assumption = (byte.MaxValue, 0); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return CanKill.GetBool(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public static void OnVotingEnd(Dictionary voteNum) - { - if (!On) return; - - foreach (Assumer instance in Instances) + if (instance.HasAssumed && voteNum.TryGetValue(instance.Assumption.Id, out int num) && num == instance.Assumption.VoteNum) { - if (instance.HasAssumed && voteNum.TryGetValue(instance.Assumption.Id, out int num) && num == instance.Assumption.VoteNum) - { - foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) - if (pva.VotedFor == instance.Assumption.Id || (VoteReceiverDies.GetBool() && pva.TargetPlayerId == instance.Assumption.Id)) - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Assumed, pva.TargetPlayerId); - } + foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) + if (pva.VotedFor == instance.Assumption.Id || (VoteReceiverDies.GetBool() && pva.TargetPlayerId == instance.Assumption.Id)) + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Assumed, pva.TargetPlayerId); } } + } - public static void Assume(byte assumerId, byte id, int num) - { - if (Main.AllAlivePlayerControls.Length < MinPlayersToAssume.GetInt()) return; + public static void Assume(byte assumerId, byte id, int num) + { + if (Main.AllAlivePlayerControls.Length < MinPlayersToAssume.GetInt()) return; - Assumer assumer = Instances.Find(x => x.AssumerId == assumerId); - if (assumer == null || assumer.HasAssumed) return; + Assumer assumer = Instances.Find(x => x.AssumerId == assumerId); + if (assumer == null || assumer.HasAssumed) return; - assumer.Assumption = (id, num); - Utils.SendMessage("\n", assumerId, string.Format(Translator.GetString("Assumer.AssumedMessage"), id.ColoredPlayerName(), num)); - } + assumer.Assumption = (id, num); + Utils.SendMessage("\n", assumerId, string.Format(Translator.GetString("Assumer.AssumedMessage"), id.ColoredPlayerName(), num)); } } \ No newline at end of file diff --git a/Roles/Impostor/Augmenter.cs b/Roles/Impostor/Augmenter.cs index 0c7f6013e..790655692 100644 --- a/Roles/Impostor/Augmenter.cs +++ b/Roles/Impostor/Augmenter.cs @@ -1,55 +1,54 @@ -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Augmenter : RoleBase { - public class Augmenter : RoleBase + public static bool On; + + public byte Target; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(649700, TabGroup.ImpostorRoles, CustomRoles.Augmenter); + } - public byte Target; - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + Target = byte.MaxValue; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) return true; + + Target = target.PlayerId; + return false; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + PlayerControl newTarget = Utils.GetPlayerById(Target); + + if (newTarget == null || !newTarget.IsAlive() || !killer.RpcCheckAndMurder(newTarget, true)) { - Options.SetupRoleOptions(649700, TabGroup.ImpostorRoles, CustomRoles.Augmenter); + killer.Notify(string.Format(Translator.GetString("AugmenterFail"), Target.ColoredPlayerName())); + return true; } - public override void Init() - { - On = false; - } + Vector2 pos = newTarget.Pos(); + newTarget.TP(target); + target.TP(pos); - public override void Add(byte playerId) - { - On = true; - Target = byte.MaxValue; - } + Target = byte.MaxValue; - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting) return true; + LateTask.New(() => killer.Kill(newTarget), 0.2f, "AugmenterKill"); - Target = target.PlayerId; - return false; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - PlayerControl newTarget = Utils.GetPlayerById(Target); - - if (newTarget == null || !newTarget.IsAlive() || !killer.RpcCheckAndMurder(newTarget, true)) - { - killer.Notify(string.Format(Translator.GetString("AugmenterFail"), Target.ColoredPlayerName())); - return true; - } - - Vector2 pos = newTarget.Pos(); - newTarget.TP(target); - target.TP(pos); - - Target = byte.MaxValue; - - LateTask.New(() => killer.Kill(newTarget), 0.2f, "AugmenterKill"); - - return false; - } + return false; } } \ No newline at end of file diff --git a/Roles/Impostor/BallLightning.cs b/Roles/Impostor/BallLightning.cs index 05cc50701..960748864 100644 --- a/Roles/Impostor/BallLightning.cs +++ b/Roles/Impostor/BallLightning.cs @@ -7,207 +7,206 @@ using Hazel; using UnityEngine; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class BallLightning : RoleBase { - public class BallLightning : RoleBase + private const int Id = 16700; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem ConvertTime; + private static OptionItem KillerConvertGhost; + + private static List GhostPlayer = []; + private static Dictionary RealKiller = []; + + public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + + public override void SetupCustomOption() { - private const int Id = 16700; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.BallLightning); - private static OptionItem KillCooldown; - private static OptionItem ConvertTime; - private static OptionItem KillerConvertGhost; + KillCooldown = new FloatOptionItem(Id + 10, "BallLightningKillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.BallLightning]) + .SetValueFormat(OptionFormat.Seconds); - private static List GhostPlayer = []; - private static Dictionary RealKiller = []; + ConvertTime = new FloatOptionItem(Id + 12, "BallLightningConvertTime", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.BallLightning]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + KillerConvertGhost = new BooleanOptionItem(Id + 14, "BallLightningKillerConvertGhost", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.BallLightning]); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + GhostPlayer = []; + RealKiller = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetGhostPlayer, SendOption.Reliable); + writer.Write(playerId); + writer.Write(IsGhost(playerId)); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte GhostId = reader.ReadByte(); + bool isGhost = reader.ReadBoolean(); + + if (GhostId == byte.MaxValue) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.BallLightning); - - KillCooldown = new FloatOptionItem(Id + 10, "BallLightningKillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.BallLightning]) - .SetValueFormat(OptionFormat.Seconds); - - ConvertTime = new FloatOptionItem(Id + 12, "BallLightningConvertTime", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.BallLightning]) - .SetValueFormat(OptionFormat.Seconds); - - KillerConvertGhost = new BooleanOptionItem(Id + 14, "BallLightningKillerConvertGhost", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.BallLightning]); - } - - public override void Init() - { - PlayerIdList = []; GhostPlayer = []; - RealKiller = []; + return; } - public override void Add(byte playerId) + if (isGhost) { - PlayerIdList.Add(playerId); + if (!GhostPlayer.Contains(GhostId)) GhostPlayer.Add(GhostId); } - - private static void SendRPC(byte playerId) + else { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetGhostPlayer, SendOption.Reliable); - writer.Write(playerId); - writer.Write(IsGhost(playerId)); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte GhostId = reader.ReadByte(); - bool isGhost = reader.ReadBoolean(); - - if (GhostId == byte.MaxValue) - { - GhostPlayer = []; - return; - } - - if (isGhost) - { - if (!GhostPlayer.Contains(GhostId)) GhostPlayer.Add(GhostId); - } - else - { - if (GhostPlayer.Contains(GhostId)) GhostPlayer.Remove(GhostId); - } - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public static bool IsGhost(PlayerControl player) - { - return GhostPlayer.Contains(player.PlayerId); - } - - public static bool IsGhost(byte id) - { - return GhostPlayer.Contains(id); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return !CheckBallLightningMurder(killer, target); - } - - public static bool CheckBallLightningMurder(PlayerControl killer, PlayerControl target, bool force = false) - { - if (killer == null || target == null || (!killer.Is(CustomRoles.BallLightning) && !force)) return false; - - if (IsGhost(target)) return false; - - if (!force) - { - killer.SetKillCooldown(); - killer.RPCPlayCustomSound("Shield"); - } - - StartConvertCountDown(killer, target); - return true; - } - - private static void StartConvertCountDown(PlayerControl killer, PlayerControl target) - { - LateTask.New(() => - { - if (GameStates.IsInGame && GameStates.IsInTask && !GameStates.IsMeeting && target.IsAlive() && !Pelican.IsEaten(target.PlayerId)) - { - GhostPlayer.Add(target.PlayerId); - SendRPC(target.PlayerId); - RealKiller.TryAdd(target.PlayerId, killer); - if (!killer.inVent) killer.SetKillCooldown(); - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - Logger.Info($"{target.GetNameWithRole().RemoveHtmlTags()} is now converted to a 'non-spherical lightning' XD", "BallLightning"); - } - }, ConvertTime.GetFloat(), "BallLightning Convert Player To Ghost"); - } - - public static void MurderPlayer(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null || !target.Is(CustomRoles.BallLightning)) return; - - if (!KillerConvertGhost.GetBool() || IsGhost(killer)) return; - - RealKiller.TryAdd(killer.PlayerId, target); - StartConvertCountDown(target, killer); - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (!GameStates.IsInTask) return; - - List deList = []; - - foreach (byte ghost in GhostPlayer.ToArray()) - { - PlayerControl gs = Utils.GetPlayerById(ghost); - - if (gs == null || !gs.IsAlive() || gs.Data.Disconnected) - { - //deList.Add(gs.PlayerId); // This will always result in a null reference exception - continue; - } - - if (pc.PlayerId != gs.PlayerId && pc.IsAlive() && !pc.Is(CustomRoles.BallLightning) && !IsGhost(pc) && !Pelican.IsEaten(pc.PlayerId)) - { - Vector3 pos = gs.transform.position; - float dis = Vector2.Distance(pos, pc.Pos()); - if (dis > 0.3f) continue; - - deList.Add(gs.PlayerId); - gs.Suicide(PlayerState.DeathReason.Quantization, RealKiller[gs.PlayerId]); - - break; - } - } - - if (deList.Count > 0) - { - GhostPlayer.RemoveAll(deList.Contains); - - foreach (byte gs in deList.ToArray()) - { - SendRPC(gs); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(gs)); - } - } - } - - public override void OnReportDeadBody() - { - if (!(IsEnable || CustomRoles.BallLightning.IsEnable())) return; - - foreach (byte ghost in GhostPlayer.ToArray()) - { - PlayerControl gs = Utils.GetPlayerById(ghost); - if (gs == null) continue; - - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Quantization, gs.PlayerId); - gs.SetRealKiller(RealKiller[gs.PlayerId]); - Utils.NotifyRoles(SpecifySeer: gs); - } - - GhostPlayer = []; - SendRPC(byte.MaxValue); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("BallLightningButtonText")); + if (GhostPlayer.Contains(GhostId)) GhostPlayer.Remove(GhostId); } } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public static bool IsGhost(PlayerControl player) + { + return GhostPlayer.Contains(player.PlayerId); + } + + public static bool IsGhost(byte id) + { + return GhostPlayer.Contains(id); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return !CheckBallLightningMurder(killer, target); + } + + public static bool CheckBallLightningMurder(PlayerControl killer, PlayerControl target, bool force = false) + { + if (killer == null || target == null || (!killer.Is(CustomRoles.BallLightning) && !force)) return false; + + if (IsGhost(target)) return false; + + if (!force) + { + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("Shield"); + } + + StartConvertCountDown(killer, target); + return true; + } + + private static void StartConvertCountDown(PlayerControl killer, PlayerControl target) + { + LateTask.New(() => + { + if (GameStates.IsInGame && GameStates.IsInTask && !GameStates.IsMeeting && target.IsAlive() && !Pelican.IsEaten(target.PlayerId)) + { + GhostPlayer.Add(target.PlayerId); + SendRPC(target.PlayerId); + RealKiller.TryAdd(target.PlayerId, killer); + if (!killer.inVent) killer.SetKillCooldown(); + + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + Logger.Info($"{target.GetNameWithRole().RemoveHtmlTags()} is now converted to a 'non-spherical lightning' XD", "BallLightning"); + } + }, ConvertTime.GetFloat(), "BallLightning Convert Player To Ghost"); + } + + public static void MurderPlayer(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || !target.Is(CustomRoles.BallLightning)) return; + + if (!KillerConvertGhost.GetBool() || IsGhost(killer)) return; + + RealKiller.TryAdd(killer.PlayerId, target); + StartConvertCountDown(target, killer); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (!GameStates.IsInTask) return; + + List deList = []; + + foreach (byte ghost in GhostPlayer.ToArray()) + { + PlayerControl gs = Utils.GetPlayerById(ghost); + + if (gs == null || !gs.IsAlive() || gs.Data.Disconnected) + { + //deList.Add(gs.PlayerId); // This will always result in a null reference exception + continue; + } + + if (pc.PlayerId != gs.PlayerId && pc.IsAlive() && !pc.Is(CustomRoles.BallLightning) && !IsGhost(pc) && !Pelican.IsEaten(pc.PlayerId)) + { + Vector3 pos = gs.transform.position; + float dis = Vector2.Distance(pos, pc.Pos()); + if (dis > 0.3f) continue; + + deList.Add(gs.PlayerId); + gs.Suicide(PlayerState.DeathReason.Quantization, RealKiller[gs.PlayerId]); + + break; + } + } + + if (deList.Count > 0) + { + GhostPlayer.RemoveAll(deList.Contains); + + foreach (byte gs in deList.ToArray()) + { + SendRPC(gs); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(gs)); + } + } + } + + public override void OnReportDeadBody() + { + if (!(IsEnable || CustomRoles.BallLightning.IsEnable())) return; + + foreach (byte ghost in GhostPlayer.ToArray()) + { + PlayerControl gs = Utils.GetPlayerById(ghost); + if (gs == null) continue; + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Quantization, gs.PlayerId); + gs.SetRealKiller(RealKiller[gs.PlayerId]); + Utils.NotifyRoles(SpecifySeer: gs); + } + + GhostPlayer = []; + SendRPC(byte.MaxValue); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("BallLightningButtonText")); + } } \ No newline at end of file diff --git a/Roles/Impostor/Bard.cs b/Roles/Impostor/Bard.cs index 75e1c32e6..2bdb520b4 100644 --- a/Roles/Impostor/Bard.cs +++ b/Roles/Impostor/Bard.cs @@ -1,38 +1,37 @@ -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Bard : RoleBase { - public class Bard : RoleBase + public static int BardCreations; + public static bool On; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static int BardCreations; - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void Add(byte playerId) + public override void Init() + { + On = false; + BardCreations = 0; + } + + public override void SetupCustomOption() { } + + public static void OnMeetingHudDestroy(ref string name) + { + BardCreations++; + + try { - On = true; + name = ModUpdater.Get("https://v1.hitokoto.cn/?encode=text"); + } + catch + { + name = Translator.GetString("ByBardGetFailed"); } - public override void Init() - { - On = false; - BardCreations = 0; - } - - public override void SetupCustomOption() { } - - public static void OnMeetingHudDestroy(ref string name) - { - BardCreations++; - - try - { - name = ModUpdater.Get("https://v1.hitokoto.cn/?encode=text"); - } - catch - { - name = Translator.GetString("ByBardGetFailed"); - } - - name += "\n\t\t——" + Translator.GetString("ByBard"); - } + name += "\n\t\t——" + Translator.GetString("ByBard"); } } \ No newline at end of file diff --git a/Roles/Impostor/Blackmailer.cs b/Roles/Impostor/Blackmailer.cs index 10b25f5be..bb2568748 100644 --- a/Roles/Impostor/Blackmailer.cs +++ b/Roles/Impostor/Blackmailer.cs @@ -2,125 +2,124 @@ using System.Linq; using EHR.Patches; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Blackmailer : RoleBase { - internal class Blackmailer : RoleBase + public static bool On; + + private static OptionItem AbilityExpires; + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityUseGainWithEachKill; + private static OptionItem MaxBlackmailedPlayersPerMeeting; + private static OptionItem MaxBlackmailedPlayersAtOnce; + private static OptionItem WhoSeesBlackmailedPlayers; + + public List BlackmailedPlayerIds; + private int NumBlackmailedThisRound; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(12190) + .AutoSetupOption(ref AbilityExpires, 0, new[] { "BMAE.AfterMeeting", "BMAE.Never" }) + .AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 20, 1), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.4f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times) + .AutoSetupOption(ref MaxBlackmailedPlayersPerMeeting, 1, new IntegerValueRule(1, 14, 1), OptionFormat.Players) + .AutoSetupOption(ref MaxBlackmailedPlayersAtOnce, 1, new IntegerValueRule(1, 14, 1), OptionFormat.Players) + .AutoSetupOption(ref WhoSeesBlackmailedPlayers, 1, new[] { "BMWSBP.Blackmailer", "BMWSBP.BlackmailerAndBlackmailed", "BMWSBP.Impostors", "BMWSBP.Everyone" }); + } - private static OptionItem AbilityExpires; - private static OptionItem AbilityUseLimit; - private static OptionItem AbilityUseGainWithEachKill; - private static OptionItem MaxBlackmailedPlayersPerMeeting; - private static OptionItem MaxBlackmailedPlayersAtOnce; - private static OptionItem WhoSeesBlackmailedPlayers; + public override void Add(byte playerId) + { + On = true; + BlackmailedPlayerIds = []; + NumBlackmailedThisRound = 0; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - public List BlackmailedPlayerIds; - private int NumBlackmailedThisRound; - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void SetupCustomOption() + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return true; + + if (NumBlackmailedThisRound >= MaxBlackmailedPlayersPerMeeting.GetInt()) return true; + + if (BlackmailedPlayerIds.Count >= MaxBlackmailedPlayersAtOnce.GetInt()) return true; + + if (BlackmailedPlayerIds.Contains(target.PlayerId)) return true; + + return killer.CheckDoubleTrigger(target, () => { - StartSetup(12190) - .AutoSetupOption(ref AbilityExpires, 0, new[] { "BMAE.AfterMeeting", "BMAE.Never" }) - .AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 20, 1), OptionFormat.Times) - .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.4f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times) - .AutoSetupOption(ref MaxBlackmailedPlayersPerMeeting, 1, new IntegerValueRule(1, 14, 1), OptionFormat.Players) - .AutoSetupOption(ref MaxBlackmailedPlayersAtOnce, 1, new IntegerValueRule(1, 14, 1), OptionFormat.Players) - .AutoSetupOption(ref WhoSeesBlackmailedPlayers, 1, new[] { "BMWSBP.Blackmailer", "BMWSBP.BlackmailerAndBlackmailed", "BMWSBP.Impostors", "BMWSBP.Everyone" }); - } + BlackmailedPlayerIds.Add(target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + killer.SetKillCooldown(3f); + killer.RpcRemoveAbilityUse(); + NumBlackmailedThisRound++; + }); + } - public override void Add(byte playerId) + public override void AfterMeetingTasks() + { + if (AbilityExpires.GetValue() == 0) BlackmailedPlayerIds.Clear(); + } + + public static void OnCheckForEndVoting() + { + if (!On) return; + + CheckForEndVotingPatch.RunRoleCode = false; + + try { - On = true; - BlackmailedPlayerIds = []; - NumBlackmailedThisRound = 0; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } + KeyValuePair bmState = Main.PlayerStates.FirstOrDefault(x => x.Value.MainRole == CustomRoles.Blackmailer); + if (bmState.Value.Role is not Blackmailer { IsEnable: true } bm || bm.BlackmailedPlayerIds.Count == 0) return; - public override void Init() - { - On = false; - } + byte? bmVotedForTemp = MeetingHud.Instance.playerStates.FirstOrDefault(x => x.TargetPlayerId == bmState.Key)?.VotedFor; + if (bmVotedForTemp == null) return; - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return true; + var bmVotedFor = (byte)bmVotedForTemp; - if (NumBlackmailedThisRound >= MaxBlackmailedPlayersPerMeeting.GetInt()) return true; - - if (BlackmailedPlayerIds.Count >= MaxBlackmailedPlayersAtOnce.GetInt()) return true; - - if (BlackmailedPlayerIds.Contains(target.PlayerId)) return true; - - return killer.CheckDoubleTrigger(target, () => + MeetingHud.Instance.playerStates.DoIf(x => bm.BlackmailedPlayerIds.Contains(x.TargetPlayerId), x => { - BlackmailedPlayerIds.Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - killer.SetKillCooldown(3f); - killer.RpcRemoveAbilityUse(); - NumBlackmailedThisRound++; + if (x.DidVote) + { + x.UnsetVote(); + MeetingHud.Instance.RpcClearVote(x.TargetPlayerId.GetPlayer().GetClientId()); + } + + if (x.TargetPlayerId.IsHost()) + MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, bmVotedFor); + else + MeetingHud.Instance.CastVote(x.TargetPlayerId, bmVotedFor); + + x.VotedFor = bmVotedFor; }); } - - public override void AfterMeetingTasks() + finally { - if (AbilityExpires.GetValue() == 0) BlackmailedPlayerIds.Clear(); + CheckForEndVotingPatch.RunRoleCode = true; } + } - public static void OnCheckForEndVoting() + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!On || !meeting || !BlackmailedPlayerIds.Contains(target.PlayerId)) return string.Empty; + + switch (WhoSeesBlackmailedPlayers.GetValue()) { - if (!On) return; + case 0 when seer.Is(CustomRoles.Blackmailer): + case 1 when seer.Is(CustomRoles.Blackmailer) || (BlackmailedPlayerIds.Contains(seer.PlayerId) && seer.PlayerId == target.PlayerId): + case 2 when seer.IsImpostor(): + case 3: + return Translator.GetString("BlackmailedSuffix"); - CheckForEndVotingPatch.RunRoleCode = false; - - try - { - KeyValuePair bmState = Main.PlayerStates.FirstOrDefault(x => x.Value.MainRole == CustomRoles.Blackmailer); - if (bmState.Value.Role is not Blackmailer { IsEnable: true } bm || bm.BlackmailedPlayerIds.Count == 0) return; - - byte? bmVotedForTemp = MeetingHud.Instance.playerStates.FirstOrDefault(x => x.TargetPlayerId == bmState.Key)?.VotedFor; - if (bmVotedForTemp == null) return; - - var bmVotedFor = (byte)bmVotedForTemp; - - MeetingHud.Instance.playerStates.DoIf(x => bm.BlackmailedPlayerIds.Contains(x.TargetPlayerId), x => - { - if (x.DidVote) - { - x.UnsetVote(); - MeetingHud.Instance.RpcClearVote(x.TargetPlayerId.GetPlayer().GetClientId()); - } - - if (x.TargetPlayerId.IsHost()) - MeetingHud.Instance.CmdCastVote(x.TargetPlayerId, bmVotedFor); - else - MeetingHud.Instance.CastVote(x.TargetPlayerId, bmVotedFor); - - x.VotedFor = bmVotedFor; - }); - } - finally - { - CheckForEndVotingPatch.RunRoleCode = true; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!On || !meeting || !BlackmailedPlayerIds.Contains(target.PlayerId)) return string.Empty; - - switch (WhoSeesBlackmailedPlayers.GetValue()) - { - case 0 when seer.Is(CustomRoles.Blackmailer): - case 1 when seer.Is(CustomRoles.Blackmailer) || (BlackmailedPlayerIds.Contains(seer.PlayerId) && seer.PlayerId == target.PlayerId): - case 2 when seer.IsImpostor(): - case 3: - return Translator.GetString("BlackmailedSuffix"); - - default: - return string.Empty; - } + default: + return string.Empty; } } } \ No newline at end of file diff --git a/Roles/Impostor/Bomber.cs b/Roles/Impostor/Bomber.cs index 131d2f53d..5ee31e555 100644 --- a/Roles/Impostor/Bomber.cs +++ b/Roles/Impostor/Bomber.cs @@ -4,179 +4,178 @@ using EHR.Modules; using EHR.Neutral; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Bomber : RoleBase { - internal class Bomber : RoleBase + public static bool On; + + public static OptionItem BomberRadius; + public static OptionItem BomberCanKill; + public static OptionItem CooldownsResetEachOther; + public static OptionItem BomberKillCD; + public static OptionItem BombCooldown; + public static OptionItem ImpostorsSurviveBombs; + public static OptionItem BomberDiesInExplosion; + public static OptionItem NukerChance; + public static OptionItem NukeRadius; + public static OptionItem NukeCooldown; + private bool IsNuker; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupRoleOptions(2400, TabGroup.ImpostorRoles, CustomRoles.Bomber); - public static OptionItem BomberRadius; - public static OptionItem BomberCanKill; - public static OptionItem CooldownsResetEachOther; - public static OptionItem BomberKillCD; - public static OptionItem BombCooldown; - public static OptionItem ImpostorsSurviveBombs; - public static OptionItem BomberDiesInExplosion; - public static OptionItem NukerChance; - public static OptionItem NukeRadius; - public static OptionItem NukeCooldown; - private bool IsNuker; - public override bool IsEnable => On; + BomberRadius = new FloatOptionItem(2018, "BomberRadius", new(0.5f, 5f, 0.5f), 3f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() + BomberCanKill = new BooleanOptionItem(2015, "CanKill", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]); + + BomberKillCD = new FloatOptionItem(2020, "KillCooldown", new(0f, 180f, 2.5f), 40f, TabGroup.ImpostorRoles) + .SetParent(BomberCanKill) + .SetValueFormat(OptionFormat.Seconds); + + CooldownsResetEachOther = new BooleanOptionItem(2021, "BomberCooldownsResetEachOther", false, TabGroup.ImpostorRoles) + .SetParent(BomberCanKill); + + BombCooldown = new FloatOptionItem(2030, "BombCooldown", new(5f, 180f, 2.5f), 40f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]) + .SetValueFormat(OptionFormat.Seconds); + + ImpostorsSurviveBombs = new BooleanOptionItem(2031, "ImpostorsSurviveBombs", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]); + + BomberDiesInExplosion = new BooleanOptionItem(2032, "BomberDiesInExplosion", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]); + + NukerChance = new IntegerOptionItem(2033, "NukerChance", new(0, 100, 5), 5, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]) + .SetValueFormat(OptionFormat.Percent); + + NukeCooldown = new FloatOptionItem(2035, "NukeCooldown", new(5f, 180f, 2.5f), 60f, TabGroup.ImpostorRoles) + .SetParent(NukerChance) + .SetValueFormat(OptionFormat.Seconds); + + NukeRadius = new FloatOptionItem(2034, "NukeRadius", new(5f, 100f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(NukerChance) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Add(byte playerId) + { + On = true; + IsNuker = Main.PlayerStates[playerId].MainRole == CustomRoles.Nuker; + } + + public override void Init() + { + On = false; + IsNuker = false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return base.CanUseKillButton(pc) && !IsNuker && BomberCanKill.GetBool(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = !IsNuker && BomberCanKill.GetBool() ? BomberKillCD.GetFloat() : 300f; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + try { - SetupRoleOptions(2400, TabGroup.ImpostorRoles, CustomRoles.Bomber); - - BomberRadius = new FloatOptionItem(2018, "BomberRadius", new(0.5f, 5f, 0.5f), 3f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]) - .SetValueFormat(OptionFormat.Multiplier); - - BomberCanKill = new BooleanOptionItem(2015, "CanKill", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]); - - BomberKillCD = new FloatOptionItem(2020, "KillCooldown", new(0f, 180f, 2.5f), 40f, TabGroup.ImpostorRoles) - .SetParent(BomberCanKill) - .SetValueFormat(OptionFormat.Seconds); - - CooldownsResetEachOther = new BooleanOptionItem(2021, "BomberCooldownsResetEachOther", false, TabGroup.ImpostorRoles) - .SetParent(BomberCanKill); - - BombCooldown = new FloatOptionItem(2030, "BombCooldown", new(5f, 180f, 2.5f), 40f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]) - .SetValueFormat(OptionFormat.Seconds); - - ImpostorsSurviveBombs = new BooleanOptionItem(2031, "ImpostorsSurviveBombs", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]); - - BomberDiesInExplosion = new BooleanOptionItem(2032, "BomberDiesInExplosion", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]); - - NukerChance = new IntegerOptionItem(2033, "NukerChance", new(0, 100, 5), 5, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bomber]) - .SetValueFormat(OptionFormat.Percent); - - NukeCooldown = new FloatOptionItem(2035, "NukeCooldown", new(5f, 180f, 2.5f), 60f, TabGroup.ImpostorRoles) - .SetParent(NukerChance) - .SetValueFormat(OptionFormat.Seconds); - - NukeRadius = new FloatOptionItem(2034, "NukeRadius", new(5f, 100f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(NukerChance) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Add(byte playerId) - { - On = true; - IsNuker = Main.PlayerStates[playerId].MainRole == CustomRoles.Nuker; - } - - public override void Init() - { - On = false; - IsNuker = false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return base.CanUseKillButton(pc) && !IsNuker && BomberCanKill.GetBool(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = !IsNuker && BomberCanKill.GetBool() ? BomberKillCD.GetFloat() : 300f; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - try - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = IsNuker ? NukeCooldown.GetFloat() : BombCooldown.GetFloat(); - else - { - if (UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = IsNuker ? NukeCooldown.GetFloat() : BombCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 2f; - } - } - catch { } - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton?.OverrideText(Translator.GetString("BomberShapeshiftText")); + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = IsNuker ? NukeCooldown.GetFloat() : BombCooldown.GetFloat(); else - hud.AbilityButton?.OverrideText(Translator.GetString("BomberShapeshiftText")); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (CooldownsResetEachOther.GetBool()) killer.RpcResetAbilityCooldown(); - } - - public override void OnPet(PlayerControl pc) - { - Bomb(pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - Bomb(shapeshifter); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - Bomb(pc); - return false; - } - - private void Bomb(PlayerControl pc) - { - Logger.Info("Bomber explosion", "Boom"); - CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); - - float radius = IsNuker ? NukeRadius.GetFloat() : BomberRadius.GetFloat(); - - int murderCount = 0; - - foreach (PlayerControl tg in Main.AllPlayerControls) { - if (!tg.IsModClient()) tg.KillFlash(); + if (UsePets.GetBool()) return; - Vector2 pos = pc.Pos(); - float dis = Vector2.Distance(pos, tg.Pos()); - - if (!tg.IsAlive() || Pelican.IsEaten(tg.PlayerId) || Medic.ProtectList.Contains(tg.PlayerId) || (tg.Is(CustomRoleTypes.Impostor) && ImpostorsSurviveBombs.GetBool()) || tg.inVent || tg.Is(CustomRoles.Pestilence)) continue; - - if (dis > radius) continue; - - if (tg.PlayerId == pc.PlayerId) continue; - - tg.Suicide(PlayerState.DeathReason.Bombed, pc); - murderCount++; + AURoleOptions.ShapeshifterCooldown = IsNuker ? NukeCooldown.GetFloat() : BombCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 2f; } - - LateTask.New(() => - { - int totalAlive = Main.AllAlivePlayerControls.Length; - - if (BomberDiesInExplosion.GetBool() && totalAlive > 1 && !GameStates.IsEnded) - pc.Suicide(PlayerState.DeathReason.Bombed); - - Utils.NotifyRoles(ForceLoop: true); - - if (pc.IsLocalPlayer() && totalAlive <= murderCount) - Achievements.Type.ItsJustAPrankBro.Complete(); - }, 1.5f, "Bomber Suiscide"); - - if (CooldownsResetEachOther.GetBool() && BomberCanKill.GetBool()) - pc.SetKillCooldown(); } + catch { } + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton?.OverrideText(Translator.GetString("BomberShapeshiftText")); + else + hud.AbilityButton?.OverrideText(Translator.GetString("BomberShapeshiftText")); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (CooldownsResetEachOther.GetBool()) killer.RpcResetAbilityCooldown(); + } + + public override void OnPet(PlayerControl pc) + { + Bomb(pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + Bomb(shapeshifter); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + Bomb(pc); + return false; + } + + private void Bomb(PlayerControl pc) + { + Logger.Info("Bomber explosion", "Boom"); + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); + + float radius = IsNuker ? NukeRadius.GetFloat() : BomberRadius.GetFloat(); + + int murderCount = 0; + + foreach (PlayerControl tg in Main.AllPlayerControls) + { + if (!tg.IsModClient()) tg.KillFlash(); + + Vector2 pos = pc.Pos(); + float dis = Vector2.Distance(pos, tg.Pos()); + + if (!tg.IsAlive() || Pelican.IsEaten(tg.PlayerId) || Medic.ProtectList.Contains(tg.PlayerId) || (tg.Is(CustomRoleTypes.Impostor) && ImpostorsSurviveBombs.GetBool()) || tg.inVent || tg.Is(CustomRoles.Pestilence)) continue; + + if (dis > radius) continue; + + if (tg.PlayerId == pc.PlayerId) continue; + + tg.Suicide(PlayerState.DeathReason.Bombed, pc); + murderCount++; + } + + LateTask.New(() => + { + int totalAlive = Main.AllAlivePlayerControls.Length; + + if (BomberDiesInExplosion.GetBool() && totalAlive > 1 && !GameStates.IsEnded) + pc.Suicide(PlayerState.DeathReason.Bombed); + + Utils.NotifyRoles(ForceLoop: true); + + if (pc.IsLocalPlayer() && totalAlive <= murderCount) + Achievements.Type.ItsJustAPrankBro.Complete(); + }, 1.5f, "Bomber Suiscide"); + + if (CooldownsResetEachOther.GetBool() && BomberCanKill.GetBool()) + pc.SetKillCooldown(); } } \ No newline at end of file diff --git a/Roles/Impostor/BoobyTrap.cs b/Roles/Impostor/BoobyTrap.cs index 086cb2b41..28f841947 100644 --- a/Roles/Impostor/BoobyTrap.cs +++ b/Roles/Impostor/BoobyTrap.cs @@ -2,99 +2,98 @@ using EHR.Modules; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class BoobyTrap : RoleBase { - internal class BoobyTrap : RoleBase + private static List BoobyTrapBody = []; + private static Dictionary KillerOfBoobyTrapBody = []; + + public static bool On; + + private static OptionItem BtKillCooldown; + private static OptionItem TrapOnlyWorksOnTheBodyBoobyTrap; + private static OptionItem TrapConsecutiveBodies; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private static List BoobyTrapBody = []; - private static Dictionary KillerOfBoobyTrapBody = []; + SetupRoleOptions(16500, TabGroup.ImpostorRoles, CustomRoles.BoobyTrap); - public static bool On; + BtKillCooldown = new FloatOptionItem(16510, "KillCooldown", new(2.5f, 180f, 2.5f), 20f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.BoobyTrap]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem BtKillCooldown; - private static OptionItem TrapOnlyWorksOnTheBodyBoobyTrap; - private static OptionItem TrapConsecutiveBodies; - public override bool IsEnable => On; + TrapOnlyWorksOnTheBodyBoobyTrap = new BooleanOptionItem(16511, "TrapOnlyWorksOnTheBodyBoobyTrap", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.BoobyTrap]); - public override void SetupCustomOption() + TrapConsecutiveBodies = new BooleanOptionItem(16512, "TrapConsecutiveBodies", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.BoobyTrap]); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + BoobyTrapBody = []; + KillerOfBoobyTrapBody = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = BtKillCooldown.GetFloat(); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (TrapOnlyWorksOnTheBodyBoobyTrap.GetBool() && !GameStates.IsMeeting) + BoobyTrapBody.Add(target.PlayerId); + + return true; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (!TrapOnlyWorksOnTheBodyBoobyTrap.GetBool() && killer != target) { - SetupRoleOptions(16500, TabGroup.ImpostorRoles, CustomRoles.BoobyTrap); - - BtKillCooldown = new FloatOptionItem(16510, "KillCooldown", new(2.5f, 180f, 2.5f), 20f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.BoobyTrap]) - .SetValueFormat(OptionFormat.Seconds); - - TrapOnlyWorksOnTheBodyBoobyTrap = new BooleanOptionItem(16511, "TrapOnlyWorksOnTheBodyBoobyTrap", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.BoobyTrap]); - - TrapConsecutiveBodies = new BooleanOptionItem(16512, "TrapConsecutiveBodies", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.BoobyTrap]); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - BoobyTrapBody = []; - KillerOfBoobyTrapBody = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = BtKillCooldown.GetFloat(); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (TrapOnlyWorksOnTheBodyBoobyTrap.GetBool() && !GameStates.IsMeeting) + if (!BoobyTrapBody.Contains(target.PlayerId)) BoobyTrapBody.Add(target.PlayerId); - return true; + KillerOfBoobyTrapBody.TryAdd(target.PlayerId, killer.PlayerId); + + killer.Suicide(); } + } - public override void OnMurder(PlayerControl killer, PlayerControl target) + public static bool OnAnyoneCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) + { + if (BoobyTrapBody.Contains(target.PlayerId) && reporter.IsAlive() && !target.Object.Is(CustomRoles.Unreportable)) { - if (!TrapOnlyWorksOnTheBodyBoobyTrap.GetBool() && killer != target) + if (!TrapOnlyWorksOnTheBodyBoobyTrap.GetBool()) { - if (!BoobyTrapBody.Contains(target.PlayerId)) - BoobyTrapBody.Add(target.PlayerId); + byte killerID = KillerOfBoobyTrapBody[target.PlayerId]; - KillerOfBoobyTrapBody.TryAdd(target.PlayerId, killer.PlayerId); + reporter.Suicide(PlayerState.DeathReason.Trapped, Utils.GetPlayerById(killerID)); + RPC.PlaySoundRPC(killerID, Sounds.KillSound); - killer.Suicide(); - } - } + if (!BoobyTrapBody.Contains(reporter.PlayerId) && TrapConsecutiveBodies.GetBool()) + BoobyTrapBody.Add(reporter.PlayerId); - public static bool OnAnyoneCheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target) - { - if (BoobyTrapBody.Contains(target.PlayerId) && reporter.IsAlive() && !target.Object.Is(CustomRoles.Unreportable)) - { - if (!TrapOnlyWorksOnTheBodyBoobyTrap.GetBool()) - { - byte killerID = KillerOfBoobyTrapBody[target.PlayerId]; - - reporter.Suicide(PlayerState.DeathReason.Trapped, Utils.GetPlayerById(killerID)); - RPC.PlaySoundRPC(killerID, Sounds.KillSound); - - if (!BoobyTrapBody.Contains(reporter.PlayerId) && TrapConsecutiveBodies.GetBool()) - BoobyTrapBody.Add(reporter.PlayerId); - - KillerOfBoobyTrapBody.TryAdd(reporter.PlayerId, killerID); - return false; - } - - byte killerID2 = target.PlayerId; - - reporter.Suicide(PlayerState.DeathReason.Trapped, Utils.GetPlayerById(killerID2)); - RPC.PlaySoundRPC(killerID2, Sounds.KillSound); + KillerOfBoobyTrapBody.TryAdd(reporter.PlayerId, killerID); return false; } - return true; + byte killerID2 = target.PlayerId; + + reporter.Suicide(PlayerState.DeathReason.Trapped, Utils.GetPlayerById(killerID2)); + RPC.PlaySoundRPC(killerID2, Sounds.KillSound); + return false; } + + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/BountyHunter.cs b/Roles/Impostor/BountyHunter.cs index a92f54f73..f77b052d0 100644 --- a/Roles/Impostor/BountyHunter.cs +++ b/Roles/Impostor/BountyHunter.cs @@ -5,235 +5,234 @@ using Hazel; using UnityEngine; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class BountyHunter : RoleBase { - public class BountyHunter : RoleBase + private const int Id = 800; + private static List PlayerIdList = []; + + private static OptionItem OptionTargetChangeTime; + private static OptionItem OptionSuccessKillCooldown; + private static OptionItem OptionFailureKillCooldown; + private static OptionItem OptionShowTargetArrow; + + private static float TargetChangeTime; + private static float SuccessKillCooldown; + private static float FailureKillCooldown; + private static bool ShowTargetArrow; + private byte BountyId; + private float ChangeTimer; + private byte Target; + + private int Timer; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 800; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.BountyHunter); - private static OptionItem OptionTargetChangeTime; - private static OptionItem OptionSuccessKillCooldown; - private static OptionItem OptionFailureKillCooldown; - private static OptionItem OptionShowTargetArrow; + OptionTargetChangeTime = new FloatOptionItem(Id + 10, "BountyTargetChangeTime", new(10f, 180f, 2.5f), 50f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]) + .SetValueFormat(OptionFormat.Seconds); - private static float TargetChangeTime; - private static float SuccessKillCooldown; - private static float FailureKillCooldown; - private static bool ShowTargetArrow; - private byte BountyId; - private float ChangeTimer; - private byte Target; + OptionSuccessKillCooldown = new FloatOptionItem(Id + 11, "BountySuccessKillCooldown", new(0f, 180f, 0.5f), 3f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]) + .SetValueFormat(OptionFormat.Seconds); - private int Timer; + OptionFailureKillCooldown = new FloatOptionItem(Id + 12, "BountyFailureKillCooldown", new(0f, 180f, 2.5f), 35f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + OptionShowTargetArrow = new BooleanOptionItem(Id + 13, "BountyShowTargetArrow", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + + Target = byte.MaxValue; + ChangeTimer = OptionTargetChangeTime.GetFloat(); + Timer = OptionTargetChangeTime.GetInt(); + BountyId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + BountyId = playerId; + + TargetChangeTime = OptionTargetChangeTime.GetFloat(); + SuccessKillCooldown = OptionSuccessKillCooldown.GetFloat(); + FailureKillCooldown = OptionFailureKillCooldown.GetFloat(); + ShowTargetArrow = OptionShowTargetArrow.GetBool(); + + Timer = (int)TargetChangeTime; + + Target = byte.MaxValue; + ChangeTimer = TargetChangeTime; + + if (AmongUsClient.Instance.AmHost) ResetTarget(Utils.GetPlayerById(playerId)); + } + + private void SendRPC() + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetBountyTarget, SendOption.Reliable); + writer.Write(BountyId); + writer.Write(Target); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(byte bountyId, byte targetId) + { + Target = targetId; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + + if (GetTarget(killer) == target.PlayerId) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.BountyHunter); - - OptionTargetChangeTime = new FloatOptionItem(Id + 10, "BountyTargetChangeTime", new(10f, 180f, 2.5f), 50f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]) - .SetValueFormat(OptionFormat.Seconds); - - OptionSuccessKillCooldown = new FloatOptionItem(Id + 11, "BountySuccessKillCooldown", new(0f, 180f, 0.5f), 3f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]) - .SetValueFormat(OptionFormat.Seconds); - - OptionFailureKillCooldown = new FloatOptionItem(Id + 12, "BountyFailureKillCooldown", new(0f, 180f, 2.5f), 35f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]) - .SetValueFormat(OptionFormat.Seconds); - - OptionShowTargetArrow = new BooleanOptionItem(Id + 13, "BountyShowTargetArrow", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.BountyHunter]); + Logger.Info($"{killer.Data?.PlayerName}: Killed Target", "BountyHunter"); + Main.AllPlayerKillCooldown[killer.PlayerId] = SuccessKillCooldown; + killer.SyncSettings(); + ResetTarget(killer); + } + else + { + Logger.Info($"{killer.Data?.PlayerName}: Killed Non-Target", "BountyHunter"); + Main.AllPlayerKillCooldown[killer.PlayerId] = FailureKillCooldown; + killer.SyncSettings(); } - public override void Init() + return base.OnCheckMurder(killer, target); + } + + public override void OnReportDeadBody() + { + ChangeTimer = TargetChangeTime; + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask || float.IsNaN(ChangeTimer)) return; + + if (!player.IsAlive()) + ChangeTimer = float.NaN; + else { - PlayerIdList = []; + byte targetId = GetTarget(player); - Target = byte.MaxValue; - ChangeTimer = OptionTargetChangeTime.GetFloat(); - Timer = OptionTargetChangeTime.GetInt(); - BountyId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - BountyId = playerId; - - TargetChangeTime = OptionTargetChangeTime.GetFloat(); - SuccessKillCooldown = OptionSuccessKillCooldown.GetFloat(); - FailureKillCooldown = OptionFailureKillCooldown.GetFloat(); - ShowTargetArrow = OptionShowTargetArrow.GetBool(); - - Timer = (int)TargetChangeTime; - - Target = byte.MaxValue; - ChangeTimer = TargetChangeTime; - - if (AmongUsClient.Instance.AmHost) ResetTarget(Utils.GetPlayerById(playerId)); - } - - private void SendRPC() - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetBountyTarget, SendOption.Reliable); - writer.Write(BountyId); - writer.Write(Target); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(byte bountyId, byte targetId) - { - Target = targetId; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; - - if (GetTarget(killer) == target.PlayerId) + if (ChangeTimer >= TargetChangeTime) { - Logger.Info($"{killer.Data?.PlayerName}: Killed Target", "BountyHunter"); - Main.AllPlayerKillCooldown[killer.PlayerId] = SuccessKillCooldown; - killer.SyncSettings(); - ResetTarget(killer); - } - else - { - Logger.Info($"{killer.Data?.PlayerName}: Killed Non-Target", "BountyHunter"); - Main.AllPlayerKillCooldown[killer.PlayerId] = FailureKillCooldown; - killer.SyncSettings(); + byte newTargetId = ResetTarget(player); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: Utils.GetPlayerById(newTargetId)); } - return base.OnCheckMurder(killer, target); - } - - public override void OnReportDeadBody() - { - ChangeTimer = TargetChangeTime; - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask || float.IsNaN(ChangeTimer)) return; - - if (!player.IsAlive()) - ChangeTimer = float.NaN; - else + if (ChangeTimer >= 0) { - byte targetId = GetTarget(player); - - if (ChangeTimer >= TargetChangeTime) - { - byte newTargetId = ResetTarget(player); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: Utils.GetPlayerById(newTargetId)); - } - - if (ChangeTimer >= 0) - { - ChangeTimer += Time.fixedDeltaTime; - int tempTimer = Timer; - Timer = (int)(TargetChangeTime - ChangeTimer); - if (tempTimer != Timer && Timer <= 15 && !player.IsModClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - } - - if (Utils.GetPlayerById(targetId)?.IsAlive() == false) - { - ResetTarget(player); - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}'s target was reset because the previous target died", "BountyHunter"); - Utils.NotifyRoles(SpecifySeer: player); - } - } - } - - public byte GetTarget(PlayerControl player) - { - if (player == null) return 0xff; - - byte targetId = Target == byte.MaxValue ? ResetTarget(player) : Target; - return targetId; - } - - private byte ResetTarget(PlayerControl player) - { - if (!AmongUsClient.Instance.AmHost) return 0xff; - - byte playerId = player.PlayerId; - - ChangeTimer = 0f; - Timer = (int)TargetChangeTime; - - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: Reset Target", "BountyHunter"); - - List cTargets = new(Main.AllAlivePlayerControls.Where(pc => !pc.Is(CustomRoleTypes.Impostor))); - - if (cTargets.Count >= 2 && Target != byte.MaxValue) cTargets.RemoveAll(x => x.PlayerId == Target); - - if (cTargets.Count == 0) - { - Logger.Warn("No Targets Available", "BountyHunter"); - return 0xff; + ChangeTimer += Time.fixedDeltaTime; + int tempTimer = Timer; + Timer = (int)(TargetChangeTime - ChangeTimer); + if (tempTimer != Timer && Timer <= 15 && !player.IsModClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); } - PlayerControl target = cTargets.RandomElement(); - byte targetId = target.PlayerId; - Target = targetId; - if (ShowTargetArrow) TargetArrow.Add(playerId, targetId); - - Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}'s New Target Is {target.GetNameWithRole().RemoveHtmlTags()}", "BountyHunter"); - - SendRPC(); - return targetId; - } - - public override void AfterMeetingTasks() - { - foreach (byte id in PlayerIdList.ToArray()) + if (Utils.GetPlayerById(targetId)?.IsAlive() == false) { - if (!Main.PlayerStates[id].IsDead) - { - ChangeTimer = 0f; - Timer = (int)TargetChangeTime; - - if (Utils.GetPlayerById(id).GetCustomRole() == CustomRoles.BountyHunter) - { - Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; - Utils.GetPlayerById(id).SyncSettings(); - } - } + ResetTarget(player); + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}'s target was reset because the previous target died", "BountyHunter"); + Utils.NotifyRoles(SpecifySeer: player); } } + } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + public byte GetTarget(PlayerControl player) + { + if (player == null) return 0xff; + + byte targetId = Target == byte.MaxValue ? ResetTarget(player) : Target; + return targetId; + } + + private byte ResetTarget(PlayerControl player) + { + if (!AmongUsClient.Instance.AmHost) return 0xff; + + byte playerId = player.PlayerId; + + ChangeTimer = 0f; + Timer = (int)TargetChangeTime; + + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}: Reset Target", "BountyHunter"); + + List cTargets = new(Main.AllAlivePlayerControls.Where(pc => !pc.Is(CustomRoleTypes.Impostor))); + + if (cTargets.Count >= 2 && Target != byte.MaxValue) cTargets.RemoveAll(x => x.PlayerId == Target); + + if (cTargets.Count == 0) { - return GetTargetText(seer, target, hud) + GetTargetArrow(seer, target); + Logger.Warn("No Targets Available", "BountyHunter"); + return 0xff; } + + PlayerControl target = cTargets.RandomElement(); + byte targetId = target.PlayerId; + Target = targetId; + if (ShowTargetArrow) TargetArrow.Add(playerId, targetId); - private static string GetTargetText(PlayerControl bounty, PlayerControl tar, bool hud) + Logger.Info($"{player.GetNameWithRole().RemoveHtmlTags()}'s New Target Is {target.GetNameWithRole().RemoveHtmlTags()}", "BountyHunter"); + + SendRPC(); + return targetId; + } + + public override void AfterMeetingTasks() + { + foreach (byte id in PlayerIdList.ToArray()) { - if (GameStates.IsMeeting || bounty.PlayerId != tar.PlayerId) return string.Empty; + if (!Main.PlayerStates[id].IsDead) + { + ChangeTimer = 0f; + Timer = (int)TargetChangeTime; + + if (Utils.GetPlayerById(id).GetCustomRole() == CustomRoles.BountyHunter) + { + Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; + Utils.GetPlayerById(id).SyncSettings(); + } + } + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return GetTargetText(seer, target, hud) + GetTargetArrow(seer, target); + } - if (Main.PlayerStates[bounty.PlayerId].Role is not BountyHunter bh) return string.Empty; + private static string GetTargetText(PlayerControl bounty, PlayerControl tar, bool hud) + { + if (GameStates.IsMeeting || bounty.PlayerId != tar.PlayerId) return string.Empty; - byte targetId = bh.GetTarget(bounty); - return targetId != 0xff ? $"{(hud ? GetString("BountyCurrentTarget") : GetString("Target"))}: {Main.AllPlayerNames[targetId].RemoveHtmlTags().Replace("\r\n", string.Empty)}" : string.Empty; - } + if (Main.PlayerStates[bounty.PlayerId].Role is not BountyHunter bh) return string.Empty; + + byte targetId = bh.GetTarget(bounty); + return targetId != 0xff ? $"{(hud ? GetString("BountyCurrentTarget") : GetString("Target"))}: {Main.AllPlayerNames[targetId].RemoveHtmlTags().Replace("\r\n", string.Empty)}" : string.Empty; + } - private static string GetTargetArrow(PlayerControl seer, PlayerControl target = null) - { - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + private static string GetTargetArrow(PlayerControl seer, PlayerControl target = null) + { + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - if (!ShowTargetArrow || GameStates.IsMeeting) return string.Empty; + if (!ShowTargetArrow || GameStates.IsMeeting) return string.Empty; - if (Main.PlayerStates[seer.PlayerId].Role is not BountyHunter bh) return string.Empty; + if (Main.PlayerStates[seer.PlayerId].Role is not BountyHunter bh) return string.Empty; - byte targetId = bh.GetTarget(seer); - return $" {TargetArrow.GetArrows(seer, targetId)}"; - } + byte targetId = bh.GetTarget(seer); + return $" {TargetArrow.GetArrows(seer, targetId)}"; } } \ No newline at end of file diff --git a/Roles/Impostor/Camouflager.cs b/Roles/Impostor/Camouflager.cs index 1e4cdff61..ccbe96235 100644 --- a/Roles/Impostor/Camouflager.cs +++ b/Roles/Impostor/Camouflager.cs @@ -1,117 +1,116 @@ using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Camouflager : RoleBase { - public class Camouflager : RoleBase + private const int Id = 2500; + + public static OptionItem CamouflageCooldown; + private static OptionItem CamouflageDuration; + private static OptionItem CamoLimitOpt; + public static OptionItem AbilityUseGainWithEachKill; + public static OptionItem DoesntSpawnOnFungle; + + public static bool IsActive; + public static bool On; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 2500; + Options.SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Camouflager); - public static OptionItem CamouflageCooldown; - private static OptionItem CamouflageDuration; - private static OptionItem CamoLimitOpt; - public static OptionItem AbilityUseGainWithEachKill; - public static OptionItem DoesntSpawnOnFungle; + CamouflageCooldown = new FloatOptionItem(Id + 2, "CamouflageCooldown", new(1f, 60f, 1f), 25f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) + .SetValueFormat(OptionFormat.Seconds); - public static bool IsActive; - public static bool On; + CamouflageDuration = new FloatOptionItem(Id + 3, "CamouflageDuration", new(1f, 30f, 1f), 12f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + CamoLimitOpt = new IntegerOptionItem(Id + 4, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + AbilityUseGainWithEachKill = new FloatOptionItem(Id + 5, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) + .SetValueFormat(OptionFormat.Times); + + DoesntSpawnOnFungle = new BooleanOptionItem(Id + 6, "DoesntSpawnOnFungle", false, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (Options.UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = CamouflageCooldown.GetFloat(); + else { - Options.SetupSingleRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Camouflager); - - CamouflageCooldown = new FloatOptionItem(Id + 2, "CamouflageCooldown", new(1f, 60f, 1f), 25f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) - .SetValueFormat(OptionFormat.Seconds); - - CamouflageDuration = new FloatOptionItem(Id + 3, "CamouflageDuration", new(1f, 30f, 1f), 12f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) - .SetValueFormat(OptionFormat.Seconds); - - CamoLimitOpt = new IntegerOptionItem(Id + 4, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) - .SetValueFormat(OptionFormat.Times); - - AbilityUseGainWithEachKill = new FloatOptionItem(Id + 5, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]) - .SetValueFormat(OptionFormat.Times); - - DoesntSpawnOnFungle = new BooleanOptionItem(Id + 6, "DoesntSpawnOnFungle", false, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Camouflager]); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (Options.UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = CamouflageCooldown.GetFloat(); - else - { - AURoleOptions.ShapeshifterCooldown = CamouflageCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = CamouflageDuration.GetFloat(); - } - } - - public override void Init() - { - IsActive = false; - On = false; - } - - public override void Add(byte playerId) - { - playerId.SetAbilityUseLimit(CamoLimitOpt.GetInt()); - On = true; - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) - { - bool unshift = Options.UseUnshiftTrigger.GetBool(); - - if (!shapeshifting && !unshift) - { - IsActive = false; - Camouflage.CheckCamouflage(); - return true; - } - - if (pc.GetAbilityUseLimit() < 1 && !Options.DisableShapeshiftAnimations.GetBool() && !unshift) pc.SetKillCooldown(CamouflageDuration.GetFloat() + 1f); - - pc.RpcRemoveAbilityUse(); - IsActive = true; - Camouflage.CheckCamouflage(); - - if (unshift) - { - LateTask.New(() => - { - if (!IsActive) return; - - IsActive = false; - Camouflage.CheckCamouflage(); - }, CamouflageDuration.GetFloat()); - } - - return !unshift; - } - - public override void OnReportDeadBody() - { - IsActive = false; - Camouflage.CheckCamouflage(); - } - - public static void IsDead() - { - IsActive = false; - Camouflage.CheckCamouflage(); - Utils.NotifyRoles(ForceLoop: true); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton?.OverrideText(Translator.GetString("CamouflagerShapeshiftText")); - hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); + AURoleOptions.ShapeshifterCooldown = CamouflageCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = CamouflageDuration.GetFloat(); } } + + public override void Init() + { + IsActive = false; + On = false; + } + + public override void Add(byte playerId) + { + playerId.SetAbilityUseLimit(CamoLimitOpt.GetInt()); + On = true; + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) + { + bool unshift = Options.UseUnshiftTrigger.GetBool(); + + if (!shapeshifting && !unshift) + { + IsActive = false; + Camouflage.CheckCamouflage(); + return true; + } + + if (pc.GetAbilityUseLimit() < 1 && !Options.DisableShapeshiftAnimations.GetBool() && !unshift) pc.SetKillCooldown(CamouflageDuration.GetFloat() + 1f); + + pc.RpcRemoveAbilityUse(); + IsActive = true; + Camouflage.CheckCamouflage(); + + if (unshift) + { + LateTask.New(() => + { + if (!IsActive) return; + + IsActive = false; + Camouflage.CheckCamouflage(); + }, CamouflageDuration.GetFloat()); + } + + return !unshift; + } + + public override void OnReportDeadBody() + { + IsActive = false; + Camouflage.CheckCamouflage(); + } + + public static void IsDead() + { + IsActive = false; + Camouflage.CheckCamouflage(); + Utils.NotifyRoles(ForceLoop: true); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(Translator.GetString("CamouflagerShapeshiftText")); + hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); + } } \ No newline at end of file diff --git a/Roles/Impostor/Cantankerous.cs b/Roles/Impostor/Cantankerous.cs index 4359ba6a4..ce9f0bf8e 100644 --- a/Roles/Impostor/Cantankerous.cs +++ b/Roles/Impostor/Cantankerous.cs @@ -1,75 +1,74 @@ using System.Collections.Generic; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Cantankerous : RoleBase { - public class Cantankerous : RoleBase + private const int Id = 642860; + private static List PlayerIdList = []; + + private static OptionItem PointsGainedPerEjection; + private static OptionItem StartingPoints; + private static OptionItem KCD; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642860; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Cantankerous); - private static OptionItem PointsGainedPerEjection; - private static OptionItem StartingPoints; - private static OptionItem KCD; + KCD = new FloatOptionItem(Id + 5, "KillCooldown", new(0f, 60f, 2.5f), 22.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cantankerous]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + PointsGainedPerEjection = new IntegerOptionItem(Id + 6, "CantankerousPointsGainedPerEjection", new(1, 5, 1), 2, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cantankerous]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Cantankerous); + StartingPoints = new IntegerOptionItem(Id + 7, "CantankerousStartingPoints", new(0, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cantankerous]) + .SetValueFormat(OptionFormat.Times); + } - KCD = new FloatOptionItem(Id + 5, "KillCooldown", new(0f, 60f, 2.5f), 22.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cantankerous]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + PlayerIdList = []; + } - PointsGainedPerEjection = new IntegerOptionItem(Id + 6, "CantankerousPointsGainedPerEjection", new(1, 5, 1), 2, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cantankerous]) - .SetValueFormat(OptionFormat.Times); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(StartingPoints.GetInt()); + } - StartingPoints = new IntegerOptionItem(Id + 7, "CantankerousStartingPoints", new(0, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cantankerous]) - .SetValueFormat(OptionFormat.Times); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KCD.GetFloat(); + } - public override void Init() - { - PlayerIdList = []; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() > 0; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(StartingPoints.GetInt()); - } + public static void OnCrewmateEjected() + { + int value = PointsGainedPerEjection.GetInt(); - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KCD.GetFloat(); - } + foreach (KeyValuePair state in Main.PlayerStates) + if (state.Value.Role is Cantankerous) + Utils.GetPlayerById(state.Key).RpcIncreaseAbilityUseLimitBy(value); + } - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() > 0; - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; - public static void OnCrewmateEjected() - { - int value = PointsGainedPerEjection.GetInt(); + if (killer.GetAbilityUseLimit() <= 0) return false; - foreach (KeyValuePair state in Main.PlayerStates) - if (state.Value.Role is Cantankerous) - Utils.GetPlayerById(state.Key).RpcIncreaseAbilityUseLimitBy(value); - } + killer.RpcRemoveAbilityUse(); - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - if (killer.GetAbilityUseLimit() <= 0) return false; - - killer.RpcRemoveAbilityUse(); - - return true; - } + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/Capitalism.cs b/Roles/Impostor/Capitalism.cs index 1ce92d959..1d18951e1 100644 --- a/Roles/Impostor/Capitalism.cs +++ b/Roles/Impostor/Capitalism.cs @@ -1,76 +1,75 @@ using System.Collections.Generic; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Capitalism : RoleBase { - internal class Capitalism : RoleBase + private static readonly Dictionary CapitalismAddTask = []; + public static readonly Dictionary CapitalismAssignTask = []; + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private static readonly Dictionary CapitalismAddTask = []; - public static readonly Dictionary CapitalismAssignTask = []; - public static bool On; - public override bool IsEnable => On; + SetupRoleOptions(16600, TabGroup.ImpostorRoles, CustomRoles.Capitalism); - public override void SetupCustomOption() + CapitalismSkillCooldown = new FloatOptionItem(16610, "CapitalismSkillCooldown", new(0f, 60f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Capitalism]) + .SetValueFormat(OptionFormat.Seconds); + + CapitalismKillCooldown = new FloatOptionItem(16611, "KillCooldown", new(2.5f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Capitalism]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("CapitalismButtonText")); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CapitalismKillCooldown.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return killer.CheckDoubleTrigger(target, () => { - SetupRoleOptions(16600, TabGroup.ImpostorRoles, CustomRoles.Capitalism); + CapitalismAddTask.TryAdd(target.PlayerId, 0); + CapitalismAddTask[target.PlayerId]++; + CapitalismAssignTask.TryAdd(target.PlayerId, 0); + CapitalismAssignTask[target.PlayerId]++; + Logger.Info($"{killer.GetRealName()} added a task for:{target.GetRealName()}", "Capitalism Add Task"); + killer.SetKillCooldown(CapitalismSkillCooldown.GetFloat()); + }); + } - CapitalismSkillCooldown = new FloatOptionItem(16610, "CapitalismSkillCooldown", new(0f, 60f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Capitalism]) - .SetValueFormat(OptionFormat.Seconds); - - CapitalismKillCooldown = new FloatOptionItem(16611, "KillCooldown", new(2.5f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Capitalism]) - .SetValueFormat(OptionFormat.Seconds); + public static bool AddTaskForPlayer(PlayerControl player) + { + if (CapitalismAddTask.TryGetValue(player.PlayerId, out int amount)) + { + TaskState taskState = player.GetTaskState(); + taskState.AllTasksCount += amount; + CapitalismAddTask.Remove(player.PlayerId); + taskState.CompletedTasksCount++; + player.Data.RpcSetTasks(new(0)); // Redistribute tasks + player.SyncSettings(); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + return false; } - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("CapitalismButtonText")); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CapitalismKillCooldown.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return killer.CheckDoubleTrigger(target, () => - { - CapitalismAddTask.TryAdd(target.PlayerId, 0); - CapitalismAddTask[target.PlayerId]++; - CapitalismAssignTask.TryAdd(target.PlayerId, 0); - CapitalismAssignTask[target.PlayerId]++; - Logger.Info($"{killer.GetRealName()} added a task for:{target.GetRealName()}", "Capitalism Add Task"); - killer.SetKillCooldown(CapitalismSkillCooldown.GetFloat()); - }); - } - - public static bool AddTaskForPlayer(PlayerControl player) - { - if (CapitalismAddTask.TryGetValue(player.PlayerId, out int amount)) - { - TaskState taskState = player.GetTaskState(); - taskState.AllTasksCount += amount; - CapitalismAddTask.Remove(player.PlayerId); - taskState.CompletedTasksCount++; - player.Data.RpcSetTasks(new(0)); // Redistribute tasks - player.SyncSettings(); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - return false; - } - - return true; - } + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/Changeling.cs b/Roles/Impostor/Changeling.cs index 6c5d3eb25..ccdf7758c 100644 --- a/Roles/Impostor/Changeling.cs +++ b/Roles/Impostor/Changeling.cs @@ -4,136 +4,135 @@ using System.Linq; using EHR.Modules; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Changeling : RoleBase { - internal class Changeling : RoleBase + private const int Id = 643510; + public static readonly Dictionary ChangedRole = []; + + private static OptionItem CanPickPartnerRole; + private static OptionItem CanKillBeforeRoleChange; + private static OptionItem AvailableRoles; + + private static readonly string[] AvailableRolesMode = + [ + "CL.All", // 0 + "CL.AllImp", // 1 + "CL.AllSS", // 2 + "CL.Enabled", // 3 + "CL.EnabledImp", // 4 + "CL.EnabledSS" // 5 + ]; + + private static List Roles = []; + public static bool On; + private byte ChangelingId; + + public CustomRoles CurrentRole; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 643510; - public static readonly Dictionary ChangedRole = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Changeling); + CanPickPartnerRole = new BooleanOptionItem(Id + 10, "CanPickPartnerRole", true, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Changeling]); + CanKillBeforeRoleChange = new BooleanOptionItem(Id + 11, "CanKillBeforeRoleChange", true, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Changeling]); + AvailableRoles = new StringOptionItem(Id + 12, "AvailableRoles", AvailableRolesMode, 0, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Changeling]); + } - private static OptionItem CanPickPartnerRole; - private static OptionItem CanKillBeforeRoleChange; - private static OptionItem AvailableRoles; - - private static readonly string[] AvailableRolesMode = - [ - "CL.All", // 0 - "CL.AllImp", // 1 - "CL.AllSS", // 2 - "CL.Enabled", // 3 - "CL.EnabledImp", // 4 - "CL.EnabledSS" // 5 - ]; - - private static List Roles = []; - public static bool On; - private byte ChangelingId; - - public CustomRoles CurrentRole; - public override bool IsEnable => On; - - public override void SetupCustomOption() + public static List GetAvailableRoles(bool check = false) + { + try { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Changeling); - CanPickPartnerRole = new BooleanOptionItem(Id + 10, "CanPickPartnerRole", true, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Changeling]); - CanKillBeforeRoleChange = new BooleanOptionItem(Id + 11, "CanKillBeforeRoleChange", true, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Changeling]); - AvailableRoles = new StringOptionItem(Id + 12, "AvailableRoles", AvailableRolesMode, 0, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Changeling]); - } + CustomRoles[] allRoles = Enum.GetValues(); - public static List GetAvailableRoles(bool check = false) - { - try + IEnumerable result = AvailableRoles.GetValue() switch { - CustomRoles[] allRoles = Enum.GetValues(); + 0 => allRoles, + 1 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR), + 2 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR), + 3 => allRoles.Where(x => x.GetMode() != 0), + 4 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR && x.GetMode() != 0), + 5 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR && x.GetMode() != 0), + _ => allRoles + }; - IEnumerable result = AvailableRoles.GetValue() switch - { - 0 => allRoles, - 1 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR), - 2 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR), - 3 => allRoles.Where(x => x.GetMode() != 0), - 4 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Impostor or CustomRoles.ImpostorEHR && x.GetMode() != 0), - 5 => allRoles.Where(x => x.GetVNRole(true) is CustomRoles.Shapeshifter or CustomRoles.ShapeshifterEHR && x.GetMode() != 0), - _ => allRoles - }; + if (!CanPickPartnerRole.GetBool() && !check) result = result.Where(x => !CustomRoleSelector.RoleResult.ContainsValue(x)); - if (!CanPickPartnerRole.GetBool() && !check) result = result.Where(x => !CustomRoleSelector.RoleResult.ContainsValue(x)); - - List rolesList = result.ToList(); - rolesList.Remove(CustomRoles.Changeling); - rolesList.RemoveAll(x => !x.IsImpostor() || x.IsVanilla() || x.IsAdditionRole()); - return rolesList; - } - catch - { - return []; - } + List rolesList = result.ToList(); + rolesList.Remove(CustomRoles.Changeling); + rolesList.RemoveAll(x => !x.IsImpostor() || x.IsVanilla() || x.IsAdditionRole()); + return rolesList; } - - public override void Add(byte playerId) + catch { - On = true; - ChangelingId = playerId; - ChangedRole[playerId] = false; - - try - { - CurrentRole = Roles.First(); - Utils.SendRPC(CustomRPC.SyncChangeling, playerId, (int)CurrentRole); - } - catch (InvalidOperationException) - { - Logger.Error("No roles for Changeling", "Changeling"); - Utils.GetPlayerById(playerId).RpcSetCustomRole(CustomRoles.ImpostorEHR); - } - } - - public override void Init() - { - On = false; - ChangedRole.Clear(); - Roles = GetAvailableRoles(); - } - - private void SelectNextRole(PlayerControl pc) - { - int currentIndex = Roles.IndexOf(CurrentRole); - CurrentRole = currentIndex == Roles.Count - 1 ? Roles.First() : Roles[currentIndex + 1]; - Utils.SendRPC(CustomRPC.SyncChangeling, pc.PlayerId, (int)CurrentRole); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return CanKillBeforeRoleChange.GetBool(); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - SelectNextRole(physics.myPlayer); - } - - public override void OnPet(PlayerControl pc) - { - SelectNextRole(pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - LateTask.New(() => - { - shapeshifter.RpcSetCustomRole(CurrentRole); - ChangedRole[shapeshifter.PlayerId] = true; - shapeshifter.RpcResetAbilityCooldown(); - if (!DisableShapeshiftAnimations.GetBool()) LateTask.New(() => { shapeshifter.RpcShapeshift(shapeshifter, false); }, 1f, log: false); - }, 0.3f, log: false); - - return false; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - return seer.PlayerId != target.PlayerId || ChangelingId != seer.PlayerId ? string.Empty : string.Format(Translator.GetString("ChangelingCurrentRole"), CurrentRole.ToColoredString()); + return []; } } + + public override void Add(byte playerId) + { + On = true; + ChangelingId = playerId; + ChangedRole[playerId] = false; + + try + { + CurrentRole = Roles.First(); + Utils.SendRPC(CustomRPC.SyncChangeling, playerId, (int)CurrentRole); + } + catch (InvalidOperationException) + { + Logger.Error("No roles for Changeling", "Changeling"); + Utils.GetPlayerById(playerId).RpcSetCustomRole(CustomRoles.ImpostorEHR); + } + } + + public override void Init() + { + On = false; + ChangedRole.Clear(); + Roles = GetAvailableRoles(); + } + + private void SelectNextRole(PlayerControl pc) + { + int currentIndex = Roles.IndexOf(CurrentRole); + CurrentRole = currentIndex == Roles.Count - 1 ? Roles.First() : Roles[currentIndex + 1]; + Utils.SendRPC(CustomRPC.SyncChangeling, pc.PlayerId, (int)CurrentRole); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return CanKillBeforeRoleChange.GetBool(); + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + SelectNextRole(physics.myPlayer); + } + + public override void OnPet(PlayerControl pc) + { + SelectNextRole(pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + LateTask.New(() => + { + shapeshifter.RpcSetCustomRole(CurrentRole); + ChangedRole[shapeshifter.PlayerId] = true; + shapeshifter.RpcResetAbilityCooldown(); + if (!DisableShapeshiftAnimations.GetBool()) LateTask.New(() => { shapeshifter.RpcShapeshift(shapeshifter, false); }, 1f, log: false); + }, 0.3f, log: false); + + return false; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return seer.PlayerId != target.PlayerId || ChangelingId != seer.PlayerId ? string.Empty : string.Format(Translator.GetString("ChangelingCurrentRole"), CurrentRole.ToColoredString()); + } } \ No newline at end of file diff --git a/Roles/Impostor/Chronomancer.cs b/Roles/Impostor/Chronomancer.cs index b8d80ac98..e238c7495 100644 --- a/Roles/Impostor/Chronomancer.cs +++ b/Roles/Impostor/Chronomancer.cs @@ -3,163 +3,162 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Chronomancer : RoleBase { - public class Chronomancer : RoleBase + private const int Id = 642100; + public static List PlayerIdList = []; + + private static OptionItem KCD; + private static OptionItem ChargeInterval; + private static OptionItem ChargeLossInterval; + private int ChargePercent; + private byte ChronomancerId; + + private bool IsRampaging; + private long LastUpdate; + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642100; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Chronomancer); - private static OptionItem KCD; - private static OptionItem ChargeInterval; - private static OptionItem ChargeLossInterval; - private int ChargePercent; - private byte ChronomancerId; + KCD = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chronomancer]) + .SetValueFormat(OptionFormat.Seconds); - private bool IsRampaging; - private long LastUpdate; - public override bool IsEnable => PlayerIdList.Count > 0; + ChargeInterval = new IntegerOptionItem(Id + 12, "ChargeInterval", new(1, 20, 1), 5, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chronomancer]) + .SetValueFormat(OptionFormat.Percent); - public override void SetupCustomOption() + ChargeLossInterval = new IntegerOptionItem(Id + 13, "ChargeLossInterval", new(1, 50, 1), 25, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chronomancer]) + .SetValueFormat(OptionFormat.Percent); + } + + private void SendRPC() + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncChronomancer, SendOption.Reliable); + writer.Write(ChronomancerId); + writer.Write(IsRampaging); + writer.Write(ChargePercent); + writer.Write(LastUpdate.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(bool isRampaging, int chargePercent, long lastUpdate) + { + IsRampaging = isRampaging; + ChargePercent = chargePercent; + LastUpdate = lastUpdate; + } + + public override void Init() + { + PlayerIdList = []; + IsRampaging = false; + ChargePercent = 0; + LastUpdate = Utils.TimeStamp + 30; + ChronomancerId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + IsRampaging = false; + ChargePercent = 0; + LastUpdate = Utils.TimeStamp + 10; + ChronomancerId = playerId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = IsRampaging ? 0.01f : KCD.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (ChargePercent <= 0) return base.OnCheckMurder(killer, target); + + if (!IsRampaging) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Chronomancer); - - KCD = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chronomancer]) - .SetValueFormat(OptionFormat.Seconds); - - ChargeInterval = new IntegerOptionItem(Id + 12, "ChargeInterval", new(1, 20, 1), 5, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chronomancer]) - .SetValueFormat(OptionFormat.Percent); - - ChargeLossInterval = new IntegerOptionItem(Id + 13, "ChargeLossInterval", new(1, 50, 1), 25, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chronomancer]) - .SetValueFormat(OptionFormat.Percent); - } - - private void SendRPC() - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncChronomancer, SendOption.Reliable); - writer.Write(ChronomancerId); - writer.Write(IsRampaging); - writer.Write(ChargePercent); - writer.Write(LastUpdate.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(bool isRampaging, int chargePercent, long lastUpdate) - { - IsRampaging = isRampaging; - ChargePercent = chargePercent; - LastUpdate = lastUpdate; - } - - public override void Init() - { - PlayerIdList = []; - IsRampaging = false; - ChargePercent = 0; - LastUpdate = Utils.TimeStamp + 30; - ChronomancerId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - IsRampaging = false; - ChargePercent = 0; - LastUpdate = Utils.TimeStamp + 10; - ChronomancerId = playerId; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = IsRampaging ? 0.01f : KCD.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (ChargePercent <= 0) return base.OnCheckMurder(killer, target); - - if (!IsRampaging) - { - IsRampaging = true; - SendRPC(); - killer.ResetKillCooldown(); - killer.SyncSettings(); - } - - return base.OnCheckMurder(killer, target); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc == null) return; - - if (!pc.Is(CustomRoles.Chronomancer)) return; - - if (!GameStates.IsInTask) return; - - if (LastUpdate >= Utils.TimeStamp) return; - - LastUpdate = Utils.TimeStamp; - - var notify = false; - int beforeCharge = ChargePercent; - - if (IsRampaging) - { - ChargePercent -= ChargeLossInterval.GetInt(); - - if (ChargePercent <= 0) - { - ChargePercent = 0; - IsRampaging = false; - pc.ResetKillCooldown(); - pc.SyncSettings(); - pc.SetKillCooldown(); - } - - notify = true; - } - else if (Main.KillTimers[pc.PlayerId] <= 0 && !MeetingStates.FirstMeeting) - { - ChargePercent += ChargeInterval.GetInt(); - if (ChargePercent > 100) ChargePercent = 100; - - notify = true; - } - - if (notify && !pc.IsModClient()) pc.Notify(string.Format(Translator.GetString("ChronomancerPercent"), ChargePercent), 300f); - - if (beforeCharge != ChargePercent && pc.IsModClient() && !pc.IsHost()) SendRPC(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || seer.PlayerId != ChronomancerId) return string.Empty; - - return ChargePercent > 0 ? string.Format(Translator.GetString("ChronomancerPercent"), ChargePercent) : string.Empty; - } - - public override void OnReportDeadBody() - { - LastUpdate = Utils.TimeStamp; - ChargePercent = 0; - IsRampaging = false; + IsRampaging = true; SendRPC(); + killer.ResetKillCooldown(); + killer.SyncSettings(); } - public override void AfterMeetingTasks() + return base.OnCheckMurder(killer, target); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc == null) return; + + if (!pc.Is(CustomRoles.Chronomancer)) return; + + if (!GameStates.IsInTask) return; + + if (LastUpdate >= Utils.TimeStamp) return; + + LastUpdate = Utils.TimeStamp; + + var notify = false; + int beforeCharge = ChargePercent; + + if (IsRampaging) { - OnReportDeadBody(); + ChargePercent -= ChargeLossInterval.GetInt(); + + if (ChargePercent <= 0) + { + ChargePercent = 0; + IsRampaging = false; + pc.ResetKillCooldown(); + pc.SyncSettings(); + pc.SetKillCooldown(); + } + + notify = true; + } + else if (Main.KillTimers[pc.PlayerId] <= 0 && !MeetingStates.FirstMeeting) + { + ChargePercent += ChargeInterval.GetInt(); + if (ChargePercent > 100) ChargePercent = 100; + + notify = true; } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton?.ToggleVisible(false); - } + if (notify && !pc.IsModClient()) pc.Notify(string.Format(Translator.GetString("ChronomancerPercent"), ChargePercent), 300f); + + if (beforeCharge != ChargePercent && pc.IsModClient() && !pc.IsHost()) SendRPC(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || seer.PlayerId != ChronomancerId) return string.Empty; + + return ChargePercent > 0 ? string.Format(Translator.GetString("ChronomancerPercent"), ChargePercent) : string.Empty; + } + + public override void OnReportDeadBody() + { + LastUpdate = Utils.TimeStamp; + ChargePercent = 0; + IsRampaging = false; + SendRPC(); + } + + public override void AfterMeetingTasks() + { + OnReportDeadBody(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.ToggleVisible(false); } } \ No newline at end of file diff --git a/Roles/Impostor/Cleaner.cs b/Roles/Impostor/Cleaner.cs index b3bd39475..eceebedc5 100644 --- a/Roles/Impostor/Cleaner.cs +++ b/Roles/Impostor/Cleaner.cs @@ -3,109 +3,108 @@ using AmongUs.GameOptions; using EHR.Neutral; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Cleaner : RoleBase { - internal class Cleaner : RoleBase + public static bool On; + public static List CleanerBodies = []; + private static OptionItem CleanerKillCooldown; + private static OptionItem KillCooldownAfterCleaning; + private static OptionItem CannotCleanWhenKCDIsntUp; + + private bool CanVent; + private bool HasImpostorVision; + private bool IsMedusa; + private float KCDAfterClean; + private float KillCooldown; + private bool WaitForKCDUp; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public static List CleanerBodies = []; - private static OptionItem CleanerKillCooldown; - private static OptionItem KillCooldownAfterCleaning; - private static OptionItem CannotCleanWhenKCDIsntUp; + SetupRoleOptions(2600, TabGroup.ImpostorRoles, CustomRoles.Cleaner); - private bool CanVent; - private bool HasImpostorVision; - private bool IsMedusa; - private float KCDAfterClean; - private float KillCooldown; - private bool WaitForKCDUp; + CleanerKillCooldown = new FloatOptionItem(2610, "KillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleaner]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + KillCooldownAfterCleaning = new FloatOptionItem(2611, "KillCooldownAfterCleaning", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleaner]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + CannotCleanWhenKCDIsntUp = new BooleanOptionItem(2612, "CannotCleanWhenKCDIsntUp", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Cleaner]); + } + + public override void Add(byte playerId) + { + On = true; + + IsMedusa = Main.PlayerStates[playerId].MainRole == CustomRoles.Medusa; + + if (IsMedusa) { - SetupRoleOptions(2600, TabGroup.ImpostorRoles, CustomRoles.Cleaner); - - CleanerKillCooldown = new FloatOptionItem(2610, "KillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cleaner]) - .SetValueFormat(OptionFormat.Seconds); - - KillCooldownAfterCleaning = new FloatOptionItem(2611, "KillCooldownAfterCleaning", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cleaner]) - .SetValueFormat(OptionFormat.Seconds); - - CannotCleanWhenKCDIsntUp = new BooleanOptionItem(2612, "CannotCleanWhenKCDIsntUp", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Cleaner]); + HasImpostorVision = Medusa.HasImpostorVision.GetBool(); + CanVent = Medusa.CanVent.GetBool(); + KillCooldown = Medusa.KillCooldown.GetFloat(); + KCDAfterClean = Medusa.KillCooldownAfterStoneGazing.GetFloat(); + WaitForKCDUp = Medusa.CannotStoneGazeWhenKCDIsntUp.GetBool(); } - - public override void Add(byte playerId) + else { - On = true; - - IsMedusa = Main.PlayerStates[playerId].MainRole == CustomRoles.Medusa; - - if (IsMedusa) - { - HasImpostorVision = Medusa.HasImpostorVision.GetBool(); - CanVent = Medusa.CanVent.GetBool(); - KillCooldown = Medusa.KillCooldown.GetFloat(); - KCDAfterClean = Medusa.KillCooldownAfterStoneGazing.GetFloat(); - WaitForKCDUp = Medusa.CannotStoneGazeWhenKCDIsntUp.GetBool(); - } - else - { - HasImpostorVision = true; - CanVent = true; - KillCooldown = CleanerKillCooldown.GetFloat(); - KCDAfterClean = KillCooldownAfterCleaning.GetFloat(); - WaitForKCDUp = CannotCleanWhenKCDIsntUp.GetBool(); - } - } - - public override void Init() - { - On = false; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpostorVision); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (IsMedusa) - { - hud.KillButton?.OverrideText(Translator.GetString("KillButtonText")); - hud.ReportButton?.OverrideText(Translator.GetString("MedusaReportButtonText")); - } - else - hud.ReportButton?.OverrideText(Translator.GetString("CleanerReportButtonText")); - } - - public override bool CheckReportDeadBody(PlayerControl cleaner, NetworkedPlayerInfo target, PlayerControl killer) - { - if (WaitForKCDUp && Main.KillTimers[cleaner.PlayerId] > 0f) return true; - - CleanerBodies.Remove(target.PlayerId); - CleanerBodies.Add(target.PlayerId); - - cleaner.Notify(Translator.GetString("CleanerCleanBody")); - cleaner.SetKillCooldown(KCDAfterClean); - - Logger.Info($"{cleaner.GetRealName()} cleans up the corpse of {target.Object.GetRealName()}", "Cleaner/Medusa"); - - return false; + HasImpostorVision = true; + CanVent = true; + KillCooldown = CleanerKillCooldown.GetFloat(); + KCDAfterClean = KillCooldownAfterCleaning.GetFloat(); + WaitForKCDUp = CannotCleanWhenKCDIsntUp.GetBool(); } } + + public override void Init() + { + On = false; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpostorVision); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (IsMedusa) + { + hud.KillButton?.OverrideText(Translator.GetString("KillButtonText")); + hud.ReportButton?.OverrideText(Translator.GetString("MedusaReportButtonText")); + } + else + hud.ReportButton?.OverrideText(Translator.GetString("CleanerReportButtonText")); + } + + public override bool CheckReportDeadBody(PlayerControl cleaner, NetworkedPlayerInfo target, PlayerControl killer) + { + if (WaitForKCDUp && Main.KillTimers[cleaner.PlayerId] > 0f) return true; + + CleanerBodies.Remove(target.PlayerId); + CleanerBodies.Add(target.PlayerId); + + cleaner.Notify(Translator.GetString("CleanerCleanBody")); + cleaner.SetKillCooldown(KCDAfterClean); + + Logger.Info($"{cleaner.GetRealName()} cleans up the corpse of {target.Object.GetRealName()}", "Cleaner/Medusa"); + + return false; + } } \ No newline at end of file diff --git a/Roles/Impostor/Commander.cs b/Roles/Impostor/Commander.cs index 6d736ba8f..82cd3f835 100644 --- a/Roles/Impostor/Commander.cs +++ b/Roles/Impostor/Commander.cs @@ -6,270 +6,269 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Commander : RoleBase { - internal class Commander : RoleBase + private const int Id = 643560; + public static List PlayerList = []; + public static bool On; + public static OptionItem CannotSpawnAsSoloImp; + private static OptionItem ShapeshiftCooldown; + + private byte CommanderId; + private Mode CurrentMode; + public HashSet DontKillMarks = []; + public bool IsWhistling; + public byte MarkedPlayer; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 643560; - public static List PlayerList = []; - public static bool On; - public static OptionItem CannotSpawnAsSoloImp; - private static OptionItem ShapeshiftCooldown; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Commander); - private byte CommanderId; - private Mode CurrentMode; - public HashSet DontKillMarks = []; - public bool IsWhistling; - public byte MarkedPlayer; - public override bool IsEnable => On; + CannotSpawnAsSoloImp = new BooleanOptionItem(Id + 2, "CannotSpawnAsSoloImp", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commander]); - public override void SetupCustomOption() + ShapeshiftCooldown = new FloatOptionItem(Id + 3, "ShapeshiftCooldown", new(0f, 60f, 1f), 1f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commander]); + } + + public override void Add(byte playerId) + { + On = true; + IsWhistling = false; + MarkedPlayer = byte.MaxValue; + DontKillMarks = []; + CurrentMode = Mode.Whistle; + CommanderId = playerId; + PlayerList.Add(this); + } + + public override void Init() + { + On = false; + PlayerList = []; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetValue(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + private void SendRPC() + { + Utils.SendRPC(CustomRPC.SyncRoleData, CommanderId, 1, (int)CurrentMode, IsWhistling, MarkedPlayer); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Commander); - - CannotSpawnAsSoloImp = new BooleanOptionItem(Id + 2, "CannotSpawnAsSoloImp", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commander]); - - ShapeshiftCooldown = new FloatOptionItem(Id + 3, "ShapeshiftCooldown", new(0f, 60f, 1f), 1f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Commander]); + case 1: + CurrentMode = (Mode)reader.ReadPackedInt32(); + IsWhistling = reader.ReadBoolean(); + MarkedPlayer = reader.ReadByte(); + break; + case 2: + DontKillMarks.Add(reader.ReadByte()); + break; + case 3: + DontKillMarks = []; + break; } + } - public override void Add(byte playerId) + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (!Options.UsePets.GetBool()) CycleMode(physics.myPlayer); + } + + public override void OnPet(PlayerControl pc) + { + CycleMode(pc); + } + + private void CycleMode(PlayerControl pc) + { + CurrentMode = (Mode)(((int)CurrentMode + 1) % Enum.GetValues(typeof(Mode)).Length); + SendRPC(); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) return true; + + switch (CurrentMode) { - On = true; - IsWhistling = false; - MarkedPlayer = byte.MaxValue; - DontKillMarks = []; - CurrentMode = Mode.Whistle; - CommanderId = playerId; - PlayerList.Add(this); - } - - public override void Init() - { - On = false; - PlayerList = []; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetValue(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - private void SendRPC() - { - Utils.SendRPC(CustomRPC.SyncRoleData, CommanderId, 1, (int)CurrentMode, IsWhistling, MarkedPlayer); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - CurrentMode = (Mode)reader.ReadPackedInt32(); - IsWhistling = reader.ReadBoolean(); - MarkedPlayer = reader.ReadByte(); - break; - case 2: - DontKillMarks.Add(reader.ReadByte()); - break; - case 3: - DontKillMarks = []; - break; - } - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (!Options.UsePets.GetBool()) CycleMode(physics.myPlayer); - } - - public override void OnPet(PlayerControl pc) - { - CycleMode(pc); - } - - private void CycleMode(PlayerControl pc) - { - CurrentMode = (Mode)(((int)CurrentMode + 1) % Enum.GetValues(typeof(Mode)).Length); - SendRPC(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting) return true; - - switch (CurrentMode) - { - case Mode.Whistle: - Whistle(shapeshifter, target.Is(Team.Impostor) ? target : null); - break; - case Mode.Mark: - MarkPlayer(target); - break; - case Mode.KillAnyone: - if (target.Is(Team.Impostor)) - target.Notify(Translator.GetString("CommanderKillAnyoneNotify"), 7f); - else - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!pc.Is(Team.Impostor) || pc.PlayerId == shapeshifter.PlayerId) continue; - - pc.Notify(Translator.GetString("CommanderKillAnyoneNotify"), 7f); - } - } - - break; - case Mode.DontKillMark: - if (target.Is(Team.Impostor)) - target.Notify(Translator.GetString("CommanderDontKillAnyoneNotify"), 7f); - else - MarkPlayerAsDontKill(target); - - break; - case Mode.DontSabotage: + case Mode.Whistle: + Whistle(shapeshifter, target.Is(Team.Impostor) ? target : null); + break; + case Mode.Mark: + MarkPlayer(target); + break; + case Mode.KillAnyone: + if (target.Is(Team.Impostor)) + target.Notify(Translator.GetString("CommanderKillAnyoneNotify"), 7f); + else + { foreach (PlayerControl pc in Main.AllAlivePlayerControls) { if (!pc.Is(Team.Impostor) || pc.PlayerId == shapeshifter.PlayerId) continue; - pc.Notify(Translator.GetString("CommanderDontSabotageNotify"), 7f); + pc.Notify(Translator.GetString("CommanderKillAnyoneNotify"), 7f); } + } - break; - case Mode.UseAbility: - if (target.Is(Team.Impostor)) - target.Notify(Translator.GetString("CommanderUseAbilityNotify"), 7f); - else + break; + case Mode.DontKillMark: + if (target.Is(Team.Impostor)) + target.Notify(Translator.GetString("CommanderDontKillAnyoneNotify"), 7f); + else + MarkPlayerAsDontKill(target); + + break; + case Mode.DontSabotage: + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (!pc.Is(Team.Impostor) || pc.PlayerId == shapeshifter.PlayerId) continue; + + pc.Notify(Translator.GetString("CommanderDontSabotageNotify"), 7f); + } + + break; + case Mode.UseAbility: + if (target.Is(Team.Impostor)) + target.Notify(Translator.GetString("CommanderUseAbilityNotify"), 7f); + else + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!pc.Is(Team.Impostor) || pc.PlayerId == shapeshifter.PlayerId) continue; + if (!pc.Is(Team.Impostor) || pc.PlayerId == shapeshifter.PlayerId) continue; - pc.Notify(Translator.GetString("CommanderUseAbilityNotify"), 7f); - } + pc.Notify(Translator.GetString("CommanderUseAbilityNotify"), 7f); } + } - break; - } - - return false; + break; } - private void Whistle(PlayerControl commander, PlayerControl target = null) + return false; + } + + private void Whistle(PlayerControl commander, PlayerControl target = null) + { + if (IsWhistling) return; + + IsWhistling = true; + + if (target != null) { - if (IsWhistling) return; - - IsWhistling = true; - - if (target != null) - { - AddArrowAndNotify(target); - return; - } - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!pc.Is(Team.Impostor) || pc.Is(CustomRoles.Commander)) continue; - - AddArrowAndNotify(pc); - } - - SendRPC(); + AddArrowAndNotify(target); return; - - void AddArrowAndNotify(PlayerControl pc) - { - TargetArrow.Add(pc.PlayerId, commander.PlayerId); - pc.Notify(Translator.GetString("CommanderNotify")); - } } - private void MarkPlayer(PlayerControl target) + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (target == null) - { - MarkedPlayer = byte.MaxValue; - return; - } + if (!pc.Is(Team.Impostor) || pc.Is(CustomRoles.Commander)) continue; - MarkedPlayer = target.PlayerId; - SendRPC(); - Utils.NotifyRoles(SpecifyTarget: target); + AddArrowAndNotify(pc); } - private void MarkPlayerAsDontKill(PlayerControl target) + SendRPC(); + return; + + void AddArrowAndNotify(PlayerControl pc) { - if (target == null) return; - - DontKillMarks.Add(target.PlayerId); - Utils.SendRPC(CustomRPC.SyncRoleData, CommanderId, 2, target.PlayerId); - Utils.NotifyRoles(SpecifyTarget: target); - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !GameStates.IsInTask || !On || !IsWhistling) return; - - if (TargetArrow.GetArrows(pc, CommanderId) == "・") - { - TargetArrow.Remove(pc.PlayerId, CommanderId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - if (Main.AllPlayerControls.Where(x => x.Is(Team.Impostor)).All(x => TargetArrow.GetArrows(x, CommanderId) == string.Empty)) - { - IsWhistling = false; - SendRPC(); - } - } - - public override void OnReportDeadBody() - { - IsWhistling = false; - MarkedPlayer = byte.MaxValue; - DontKillMarks = []; - SendRPC(); - Utils.SendRPC(CustomRPC.SyncRoleData, CommanderId, 3); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || !seer.Is(Team.Impostor)) return string.Empty; - - if (seer.PlayerId == target.PlayerId && Main.PlayerStates[seer.PlayerId].Role is Commander { IsEnable: true } cm) - { - if (seer.IsModClient() && !hud) return string.Empty; - - string whistlingText = cm.IsWhistling ? $"\n{Translator.GetString("CommanderWhistling")}" : string.Empty; - return $"{string.Format(Translator.GetString("WMMode"), Translator.GetString($"Commander{cm.CurrentMode}Mode"))}{whistlingText}"; - } - - bool isTargetTarget = PlayerList.Any(x => x.MarkedPlayer == target.PlayerId); - bool isTargetDontKill = PlayerList.Any(x => x.DontKillMarks.Contains(target.PlayerId)); - string arrowToCommander = PlayerList.Aggregate(string.Empty, (result, commander) => result + TargetArrow.GetArrows(seer, commander.CommanderId)); - - if (seer.PlayerId == target.PlayerId) - { - if (arrowToCommander.Length > 0) return $"{Translator.GetString("Commander")} {arrowToCommander}"; - } - else if (isTargetTarget) - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sprayer), Translator.GetString("CommanderTarget")); - else if (isTargetDontKill) return Utils.ColorString(ColorUtility.TryParseHtmlString("#0daeff", out Color color) ? color : Utils.GetRoleColor(CustomRoles.TaskManager), Translator.GetString("CommanderDontKill")); - - return string.Empty; - } - - private enum Mode - { - Whistle, - Mark, - KillAnyone, - DontKillMark, - DontSabotage, - UseAbility + TargetArrow.Add(pc.PlayerId, commander.PlayerId); + pc.Notify(Translator.GetString("CommanderNotify")); } } + + private void MarkPlayer(PlayerControl target) + { + if (target == null) + { + MarkedPlayer = byte.MaxValue; + return; + } + + MarkedPlayer = target.PlayerId; + SendRPC(); + Utils.NotifyRoles(SpecifyTarget: target); + } + + private void MarkPlayerAsDontKill(PlayerControl target) + { + if (target == null) return; + + DontKillMarks.Add(target.PlayerId); + Utils.SendRPC(CustomRPC.SyncRoleData, CommanderId, 2, target.PlayerId); + Utils.NotifyRoles(SpecifyTarget: target); + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !GameStates.IsInTask || !On || !IsWhistling) return; + + if (TargetArrow.GetArrows(pc, CommanderId) == "・") + { + TargetArrow.Remove(pc.PlayerId, CommanderId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + if (Main.AllPlayerControls.Where(x => x.Is(Team.Impostor)).All(x => TargetArrow.GetArrows(x, CommanderId) == string.Empty)) + { + IsWhistling = false; + SendRPC(); + } + } + + public override void OnReportDeadBody() + { + IsWhistling = false; + MarkedPlayer = byte.MaxValue; + DontKillMarks = []; + SendRPC(); + Utils.SendRPC(CustomRPC.SyncRoleData, CommanderId, 3); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || !seer.Is(Team.Impostor)) return string.Empty; + + if (seer.PlayerId == target.PlayerId && Main.PlayerStates[seer.PlayerId].Role is Commander { IsEnable: true } cm) + { + if (seer.IsModClient() && !hud) return string.Empty; + + string whistlingText = cm.IsWhistling ? $"\n{Translator.GetString("CommanderWhistling")}" : string.Empty; + return $"{string.Format(Translator.GetString("WMMode"), Translator.GetString($"Commander{cm.CurrentMode}Mode"))}{whistlingText}"; + } + + bool isTargetTarget = PlayerList.Any(x => x.MarkedPlayer == target.PlayerId); + bool isTargetDontKill = PlayerList.Any(x => x.DontKillMarks.Contains(target.PlayerId)); + string arrowToCommander = PlayerList.Aggregate(string.Empty, (result, commander) => result + TargetArrow.GetArrows(seer, commander.CommanderId)); + + if (seer.PlayerId == target.PlayerId) + { + if (arrowToCommander.Length > 0) return $"{Translator.GetString("Commander")} {arrowToCommander}"; + } + else if (isTargetTarget) + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Sprayer), Translator.GetString("CommanderTarget")); + else if (isTargetDontKill) return Utils.ColorString(ColorUtility.TryParseHtmlString("#0daeff", out Color color) ? color : Utils.GetRoleColor(CustomRoles.TaskManager), Translator.GetString("CommanderDontKill")); + + return string.Empty; + } + + private enum Mode + { + Whistle, + Mark, + KillAnyone, + DontKillMark, + DontSabotage, + UseAbility + } } \ No newline at end of file diff --git a/Roles/Impostor/Consort.cs b/Roles/Impostor/Consort.cs index c9e6749e5..06e368934 100644 --- a/Roles/Impostor/Consort.cs +++ b/Roles/Impostor/Consort.cs @@ -1,60 +1,59 @@ using System.Collections.Generic; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Consort : RoleBase { - public class Consort : RoleBase + private const int Id = 642400; + private static List PlayerIdList = []; + + private static OptionItem CD; + private static OptionItem UseLimit; + private static OptionItem Duration; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642400; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Consort); - private static OptionItem CD; - private static OptionItem UseLimit; - private static OptionItem Duration; + CD = new FloatOptionItem(Id + 10, "RoleBlockCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Consort]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + UseLimit = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 3, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Consort]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + Duration = new FloatOptionItem(Id + 12, "RoleBlockDuration", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Consort]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(UseLimit.GetInt()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable || killer == null || target == null) return false; + + if (killer.GetAbilityUseLimit() <= 0 || !killer.Is(CustomRoles.Consort)) return true; + + return killer.CheckDoubleTrigger(target, () => { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Consort); - - CD = new FloatOptionItem(Id + 10, "RoleBlockCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Consort]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimit = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(1, 20, 1), 3, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Consort]) - .SetValueFormat(OptionFormat.Times); - - Duration = new FloatOptionItem(Id + 12, "RoleBlockDuration", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Consort]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable || killer == null || target == null) return false; - - if (killer.GetAbilityUseLimit() <= 0 || !killer.Is(CustomRoles.Consort)) return true; - - return killer.CheckDoubleTrigger(target, () => - { - killer.RpcRemoveAbilityUse(); - target.BlockRole(Duration.GetFloat()); - killer.Notify(GetString("EscortTargetHacked")); - killer.SetKillCooldown(CD.GetFloat()); - }); - } + killer.RpcRemoveAbilityUse(); + target.BlockRole(Duration.GetFloat()); + killer.Notify(GetString("EscortTargetHacked")); + killer.SetKillCooldown(CD.GetFloat()); + }); } } \ No newline at end of file diff --git a/Roles/Impostor/Councillor.cs b/Roles/Impostor/Councillor.cs index 32a1e45a4..ce2b05263 100644 --- a/Roles/Impostor/Councillor.cs +++ b/Roles/Impostor/Councillor.cs @@ -10,339 +10,338 @@ using UnityEngine; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Councillor : RoleBase { - public class Councillor : RoleBase + private const int Id = 900; + private static List PlayerIdList = []; + private static OptionItem AbilityUseLimit; + private static OptionItem MurderLimitPerGame; + private static OptionItem MurderLimitPerMeeting; + private static OptionItem TryHideMsg; + private static OptionItem CanMurderMadmate; + private static OptionItem CanMurderImpostor; + private static OptionItem KillCooldown; + public static OptionItem CouncillorAbilityUseGainWithEachKill; + private static Dictionary MeetingKillLimit = []; + private static Dictionary TotalKillLimit = []; + + private byte CouncillorId; + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 900; - private static List PlayerIdList = []; - private static OptionItem AbilityUseLimit; - private static OptionItem MurderLimitPerGame; - private static OptionItem MurderLimitPerMeeting; - private static OptionItem TryHideMsg; - private static OptionItem CanMurderMadmate; - private static OptionItem CanMurderImpostor; - private static OptionItem KillCooldown; - public static OptionItem CouncillorAbilityUseGainWithEachKill; - private static Dictionary MeetingKillLimit = []; - private static Dictionary TotalKillLimit = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Councillor); - private byte CouncillorId; - public override bool IsEnable => PlayerIdList.Count > 0; + KillCooldown = new FloatOptionItem(Id + 15, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + AbilityUseLimit = new IntegerOptionItem(Id + 13, "AbilityUseLimit", new(0, 15, 1), 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) + .SetValueFormat(OptionFormat.Times); + + MurderLimitPerGame = new IntegerOptionItem(Id + 10, "MurderLimitPerGame", new(0, 15, 1), 3, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) + .SetValueFormat(OptionFormat.Times); + + MurderLimitPerMeeting = new IntegerOptionItem(Id + 14, "MurderLimitPerMeeting", new(0, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) + .SetValueFormat(OptionFormat.Times); + + CanMurderMadmate = new BooleanOptionItem(Id + 12, "CouncillorCanMurderMadmate", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]); + + CanMurderImpostor = new BooleanOptionItem(Id + 16, "CouncillorCanMurderImpostor", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]); + + TryHideMsg = new BooleanOptionItem(Id + 11, "CouncillorTryHideMsg", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) + .SetColor(Color.green); + + CouncillorAbilityUseGainWithEachKill = new FloatOptionItem(Id + 17, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.2f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + MeetingKillLimit = []; + TotalKillLimit = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + CouncillorId = playerId; + MeetingKillLimit[playerId] = MurderLimitPerMeeting.GetInt(); + TotalKillLimit[playerId] = MurderLimitPerGame.GetInt(); + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override void AfterMeetingTasks() + { + MeetingKillLimit[CouncillorId] = MurderLimitPerMeeting.GetInt(); + } + + public static bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) + { + string originMsg = msg; + + if (!AmongUsClient.Instance.AmHost) return false; + + if (!GameStates.IsInGame || pc == null) return false; + + if (!pc.Is(CustomRoles.Councillor)) return false; + + int operate; // 1:ID 2:Kill + msg = msg.ToLower().TrimStart().TrimEnd(); + + if (CheckCommond(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) + operate = 1; + else if (CheckCommond(ref msg, "shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|审判|判|审", false)) + operate = 2; + else + return false; + + if (!pc.IsAlive()) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Councillor); - - KillCooldown = new FloatOptionItem(Id + 15, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) - .SetValueFormat(OptionFormat.Seconds); - - AbilityUseLimit = new IntegerOptionItem(Id + 13, "AbilityUseLimit", new(0, 15, 1), 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) - .SetValueFormat(OptionFormat.Times); - - MurderLimitPerGame = new IntegerOptionItem(Id + 10, "MurderLimitPerGame", new(0, 15, 1), 3, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) - .SetValueFormat(OptionFormat.Times); - - MurderLimitPerMeeting = new IntegerOptionItem(Id + 14, "MurderLimitPerMeeting", new(0, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) - .SetValueFormat(OptionFormat.Times); - - CanMurderMadmate = new BooleanOptionItem(Id + 12, "CouncillorCanMurderMadmate", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]); - - CanMurderImpostor = new BooleanOptionItem(Id + 16, "CouncillorCanMurderImpostor", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]); - - TryHideMsg = new BooleanOptionItem(Id + 11, "CouncillorTryHideMsg", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) - .SetColor(Color.green); - - CouncillorAbilityUseGainWithEachKill = new FloatOptionItem(Id + 17, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.2f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Councillor]) - .SetValueFormat(OptionFormat.Times); + Utils.SendMessage(GetString("CouncillorDead"), pc.PlayerId); + return true; } - public override void Init() + switch (operate) { - PlayerIdList = []; - MeetingKillLimit = []; - TotalKillLimit = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - CouncillorId = playerId; - MeetingKillLimit[playerId] = MurderLimitPerMeeting.GetInt(); - TotalKillLimit[playerId] = MurderLimitPerGame.GetInt(); - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - public override void AfterMeetingTasks() - { - MeetingKillLimit[CouncillorId] = MurderLimitPerMeeting.GetInt(); - } - - public static bool MurderMsg(PlayerControl pc, string msg, bool isUI = false) - { - string originMsg = msg; - - if (!AmongUsClient.Instance.AmHost) return false; - - if (!GameStates.IsInGame || pc == null) return false; - - if (!pc.Is(CustomRoles.Councillor)) return false; - - int operate; // 1:ID 2:Kill - msg = msg.ToLower().TrimStart().TrimEnd(); - - if (CheckCommond(ref msg, "id|guesslist|gl编号|玩家编号|玩家id|id列表|玩家列表|列表|所有id|全部id")) - operate = 1; - else if (CheckCommond(ref msg, "shoot|guess|bet|st|gs|bt|猜|赌|sp|jj|tl|审判|判|审", false)) - operate = 2; - else - return false; - - if (!pc.IsAlive()) - { - Utils.SendMessage(GetString("CouncillorDead"), pc.PlayerId); + case 1: + Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); return true; - } - - switch (operate) + case 2: { - case 1: - Utils.SendMessage(GuessManager.GetFormatString(), pc.PlayerId); + if (TryHideMsg.GetBool()) + ChatManager.SendPreviousMessagesToAll(); + else if (pc.AmOwner) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + + if (!MsgToPlayerAndRole(msg, out byte targetId, out string error)) + { + Utils.SendMessage(error, pc.PlayerId); return true; - case 2: - { - if (TryHideMsg.GetBool()) - ChatManager.SendPreviousMessagesToAll(); - else if (pc.AmOwner) Utils.SendMessage(originMsg, 255, pc.GetRealName()); + } - if (!MsgToPlayerAndRole(msg, out byte targetId, out string error)) + PlayerControl target = Utils.GetPlayerById(targetId); + + if (target != null) + { + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} murdered {target.GetNameWithRole().RemoveHtmlTags()}", "Councillor"); + var CouncillorSuicide = true; + + if (pc.GetAbilityUseLimit() < 1) { - Utils.SendMessage(error, pc.PlayerId); + if (!isUI) + Utils.SendMessage(GetString("CouncillorMurderMax"), pc.PlayerId); + else + pc.ShowPopUp(GetString("CouncillorMurderMax")); + return true; } - PlayerControl target = Utils.GetPlayerById(targetId); - - if (target != null) + if (MeetingKillLimit[pc.PlayerId] < 1) { - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} murdered {target.GetNameWithRole().RemoveHtmlTags()}", "Councillor"); - var CouncillorSuicide = true; + if (!isUI) + Utils.SendMessage(GetString("CouncillorMurderMaxMeeting"), pc.PlayerId); + else + pc.ShowPopUp(GetString("CouncillorMurderMaxMeeting")); - if (pc.GetAbilityUseLimit() < 1) - { - if (!isUI) - Utils.SendMessage(GetString("CouncillorMurderMax"), pc.PlayerId); - else - pc.ShowPopUp(GetString("CouncillorMurderMax")); - - return true; - } - - if (MeetingKillLimit[pc.PlayerId] < 1) - { - if (!isUI) - Utils.SendMessage(GetString("CouncillorMurderMaxMeeting"), pc.PlayerId); - else - pc.ShowPopUp(GetString("CouncillorMurderMaxMeeting")); - - return true; - } - - if (TotalKillLimit[pc.PlayerId] < 1) - { - if (!isUI) - Utils.SendMessage(GetString("MurderMaxGame"), pc.PlayerId); - else - pc.ShowPopUp(GetString("MurderMaxGame")); - - return true; - } - - if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == targetId)) - { - if (!isUI) - Utils.SendMessage(GetString("CanNotTrialJailed"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); - else - pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle")) + "\n" + GetString("CanNotTrialJailed")); - - return true; - } - - var NoSuicide = false; - - if (pc.PlayerId == targetId) - { - if (!isUI) - Utils.SendMessage(GetString("LaughToWhoMurderSelf"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))); - else - pc.ShowPopUp(Utils.ColorString(Color.cyan, GetString("MessageFromKPD")) + "\n" + GetString("LaughToWhoMurderSelf")); - } - else if (target.IsMadmate() && CanMurderMadmate.GetBool()) - CouncillorSuicide = false; - else if (target.Is(CustomRoles.SuperStar)) - NoSuicide = true; - else if (target.Is(CustomRoles.Snitch) && target.AllTasksCompleted()) - NoSuicide = true; - else if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) - NoSuicide = true; - else if (target.Is(CustomRoles.Merchant) && Merchant.IsBribedKiller(pc, target)) - NoSuicide = true; - else if (target.IsImpostor() && CanMurderImpostor.GetBool()) - CouncillorSuicide = false; - else if (target.IsCrewmate()) - CouncillorSuicide = false; - else if (target.GetCustomRole().IsNeutral() && !target.Is(CustomRoles.Pestilence)) CouncillorSuicide = false; - - if (NoSuicide) return true; - - PlayerControl dp = CouncillorSuicide ? pc : target; - - string Name = dp.GetRealName(); - - pc.RpcRemoveAbilityUse(); - MeetingKillLimit[pc.PlayerId]--; - TotalKillLimit[pc.PlayerId]--; - - LateTask.New(() => - { - Main.PlayerStates[dp.PlayerId].deathReason = PlayerState.DeathReason.Trialed; - dp.SetRealKiller(pc); - dp.RpcGuesserMurderPlayer(); - - Utils.AfterPlayerDeathTasks(dp, true); - - Utils.NotifyRoles(NoCache: true); - - LateTask.New(() => Utils.SendMessage(string.Format(GetString("MurderKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("MurderKillTitle"))), 0.6f, "Guess Msg"); - }, 0.2f, "Murder Kill"); - } - - break; - } - } - - return true; - } - - private static bool MsgToPlayerAndRole(string msg, out byte id, out string error) - { - if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); - - Regex r = new("\\d+"); - MatchCollection mc = r.Matches(msg); - var result = string.Empty; - for (var i = 0; i < mc.Count; i++) result += mc[i]; - - if (int.TryParse(result, out int num)) - id = Convert.ToByte(num); - else - { - id = byte.MaxValue; - error = GetString("MurderHelp"); - return false; - } - - PlayerControl target = Utils.GetPlayerById(id); - - if (target == null || target.Data.IsDead) - { - error = GetString("MurderNull"); - return false; - } - - error = string.Empty; - return true; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public static bool CheckCommond(ref string msg, string command, bool exact = true) - { - string[] comList = command.Split('|'); - - foreach (string str in comList) - { - if (exact) - { - if (msg == "/" + str) return true; - } - else - { - if (msg.StartsWith("/" + str)) - { - msg = msg.Replace("/" + str, string.Empty); return true; } - } - } + if (TotalKillLimit[pc.PlayerId] < 1) + { + if (!isUI) + Utils.SendMessage(GetString("MurderMaxGame"), pc.PlayerId); + else + pc.ShowPopUp(GetString("MurderMaxGame")); + + return true; + } + + if (Jailor.PlayerIdList.Any(x => Main.PlayerStates[x].Role is Jailor { IsEnable: true } jl && jl.JailorTarget == targetId)) + { + if (!isUI) + Utils.SendMessage(GetString("CanNotTrialJailed"), pc.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle"))); + else + pc.ShowPopUp(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jailor), GetString("JailorTitle")) + "\n" + GetString("CanNotTrialJailed")); + + return true; + } + + var NoSuicide = false; + + if (pc.PlayerId == targetId) + { + if (!isUI) + Utils.SendMessage(GetString("LaughToWhoMurderSelf"), pc.PlayerId, Utils.ColorString(Color.cyan, GetString("MessageFromKPD"))); + else + pc.ShowPopUp(Utils.ColorString(Color.cyan, GetString("MessageFromKPD")) + "\n" + GetString("LaughToWhoMurderSelf")); + } + else if (target.IsMadmate() && CanMurderMadmate.GetBool()) + CouncillorSuicide = false; + else if (target.Is(CustomRoles.SuperStar)) + NoSuicide = true; + else if (target.Is(CustomRoles.Snitch) && target.AllTasksCompleted()) + NoSuicide = true; + else if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) + NoSuicide = true; + else if (target.Is(CustomRoles.Merchant) && Merchant.IsBribedKiller(pc, target)) + NoSuicide = true; + else if (target.IsImpostor() && CanMurderImpostor.GetBool()) + CouncillorSuicide = false; + else if (target.IsCrewmate()) + CouncillorSuicide = false; + else if (target.GetCustomRole().IsNeutral() && !target.Is(CustomRoles.Pestilence)) CouncillorSuicide = false; + + if (NoSuicide) return true; + + PlayerControl dp = CouncillorSuicide ? pc : target; + + string Name = dp.GetRealName(); + + pc.RpcRemoveAbilityUse(); + MeetingKillLimit[pc.PlayerId]--; + TotalKillLimit[pc.PlayerId]--; + + LateTask.New(() => + { + Main.PlayerStates[dp.PlayerId].deathReason = PlayerState.DeathReason.Trialed; + dp.SetRealKiller(pc); + dp.RpcGuesserMurderPlayer(); + + Utils.AfterPlayerDeathTasks(dp, true); + + Utils.NotifyRoles(NoCache: true); + + LateTask.New(() => Utils.SendMessage(string.Format(GetString("MurderKill"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.NiceGuesser), GetString("MurderKillTitle"))), 0.6f, "Guess Msg"); + }, 0.2f, "Murder Kill"); + } + + break; + } + } + + return true; + } + + private static bool MsgToPlayerAndRole(string msg, out byte id, out string error) + { + if (msg.StartsWith("/")) msg = msg.Replace("/", string.Empty); + + Regex r = new("\\d+"); + MatchCollection mc = r.Matches(msg); + var result = string.Empty; + for (var i = 0; i < mc.Count; i++) result += mc[i]; + + if (int.TryParse(result, out int num)) + id = Convert.ToByte(num); + else + { + id = byte.MaxValue; + error = GetString("MurderHelp"); return false; } - private static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; + PlayerControl target = Utils.GetPlayerById(id); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MeetingKill, SendOption.Reliable); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + if (target == null || target.Data.IsDead) + { + error = GetString("MurderNull"); + return false; } - public static void ReceiveRPC(MessageReader reader, PlayerControl pc) - { - int PlayerId = reader.ReadByte(); - MurderMsg(pc, $"/tl {PlayerId}", true); - } + error = string.Empty; + return true; + } - private static void CouncillorOnClick(byte playerId /*, MeetingHud __instance*/) - { - Logger.Msg($"Click: ID {playerId}", "Councillor UI"); - PlayerControl pc = Utils.GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - if (AmongUsClient.Instance.AmHost) - MurderMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); + public static bool CheckCommond(ref string msg, string command, bool exact = true) + { + string[] comList = command.Split('|'); + + foreach (string str in comList) + { + if (exact) + { + if (msg == "/" + str) return true; + } else - SendRPC(playerId); - } - - public static void CreateCouncillorButton(MeetingHud __instance) - { - foreach (PlayerVoteArea pva in __instance.playerStates.ToArray()) { - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; - - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); - var renderer = targetBox.GetComponent(); - renderer.sprite = CustomButton.Get("MeetingKillButton"); - var button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => CouncillorOnClick(pva.TargetPlayerId /*, __instance*/))); + if (msg.StartsWith("/" + str)) + { + msg = msg.Replace("/" + str, string.Empty); + return true; + } } } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - private class StartMeetingPatch + return false; + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MeetingKill, SendOption.Reliable); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + int PlayerId = reader.ReadByte(); + MurderMsg(pc, $"/tl {PlayerId}", true); + } + + private static void CouncillorOnClick(byte playerId /*, MeetingHud __instance*/) + { + Logger.Msg($"Click: ID {playerId}", "Councillor UI"); + PlayerControl pc = Utils.GetPlayerById(playerId); + if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + + if (AmongUsClient.Instance.AmHost) + MurderMsg(PlayerControl.LocalPlayer, $"/tl {playerId}", true); + else + SendRPC(playerId); + } + + public static void CreateCouncillorButton(MeetingHud __instance) + { + foreach (PlayerVoteArea pva in __instance.playerStates.ToArray()) { - public static void Postfix(MeetingHud __instance) - { - if (PlayerControl.LocalPlayer.Is(CustomRoles.Councillor) && PlayerControl.LocalPlayer.IsAlive()) CreateCouncillorButton(__instance); - } + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "ShootButton"; + targetBox.transform.localPosition = new(-0.35f, 0.03f, -1.31f); + var renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("MeetingKillButton"); + var button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => CouncillorOnClick(pva.TargetPlayerId /*, __instance*/))); + } + } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + private class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.Councillor) && PlayerControl.LocalPlayer.IsAlive()) CreateCouncillorButton(__instance); } } } \ No newline at end of file diff --git a/Roles/Impostor/Crewpostor.cs b/Roles/Impostor/Crewpostor.cs index c54fcbff3..5deab4022 100644 --- a/Roles/Impostor/Crewpostor.cs +++ b/Roles/Impostor/Crewpostor.cs @@ -5,138 +5,137 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Crewpostor : RoleBase { - internal class Crewpostor : RoleBase + public static Dictionary TasksDone = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary TasksDone = []; + SetupRoleOptions(4800, TabGroup.ImpostorRoles, CustomRoles.Crewpostor); - public static bool On; - public override bool IsEnable => On; + CrewpostorCanKillAllies = new BooleanOptionItem(4810, "CanKillAllies", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); - public override void SetupCustomOption() + CrewpostorKnowsAllies = new BooleanOptionItem(4811, "CrewpostorKnowsAllies", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); + + AlliesKnowCrewpostor = new BooleanOptionItem(4812, "AlliesKnowCrewpostor", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); + + CrewpostorLungeKill = new BooleanOptionItem(4813, "CrewpostorLungeKill", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); + + CrewpostorKillAfterTask = new IntegerOptionItem(4814, "CrewpostorKillAfterTask", new(1, 50, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); + + OverrideTasksData.Create(4815, TabGroup.ImpostorRoles, CustomRoles.Crewpostor); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = 0f; + AURoleOptions.EngineerInVentMaxTime = 0f; + } + + public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) + { + if (!TasksDone.TryAdd(player.PlayerId, 0)) TasksDone[player.PlayerId]++; + + SendRPC(player.PlayerId, TasksDone[player.PlayerId]); + + PlayerControl[] list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && (CrewpostorCanKillAllies.GetBool() || !x.GetCustomRole().IsImpostorTeam())).ToArray(); + + if (list.Length == 0) + Logger.Info("No target to kill", "Crewpostor"); + else if (TasksDone[player.PlayerId] % CrewpostorKillAfterTask.GetInt() != 0 && TasksDone[player.PlayerId] != 0) + Logger.Info($"Crewpostor task done but kill skipped, {TasksDone[player.PlayerId]} tasks completed, but it kills after {CrewpostorKillAfterTask.GetInt()} tasks", "Crewpostor"); + else { - SetupRoleOptions(4800, TabGroup.ImpostorRoles, CustomRoles.Crewpostor); + list = [.. list.OrderBy(x => Vector2.Distance(player.Pos(), x.Pos()))]; + PlayerControl target = list[0]; - CrewpostorCanKillAllies = new BooleanOptionItem(4810, "CanKillAllies", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); - - CrewpostorKnowsAllies = new BooleanOptionItem(4811, "CrewpostorKnowsAllies", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); - - AlliesKnowCrewpostor = new BooleanOptionItem(4812, "AlliesKnowCrewpostor", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); - - CrewpostorLungeKill = new BooleanOptionItem(4813, "CrewpostorLungeKill", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); - - CrewpostorKillAfterTask = new IntegerOptionItem(4814, "CrewpostorKillAfterTask", new(1, 50, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Crewpostor]); - - OverrideTasksData.Create(4815, TabGroup.ImpostorRoles, CustomRoles.Crewpostor); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = 0f; - AURoleOptions.EngineerInVentMaxTime = 0f; - } - - public override void OnTaskComplete(PlayerControl player, int completedTaskCount, int totalTaskCount) - { - if (!TasksDone.TryAdd(player.PlayerId, 0)) TasksDone[player.PlayerId]++; - - SendRPC(player.PlayerId, TasksDone[player.PlayerId]); - - PlayerControl[] list = Main.AllAlivePlayerControls.Where(x => x.PlayerId != player.PlayerId && (CrewpostorCanKillAllies.GetBool() || !x.GetCustomRole().IsImpostorTeam())).ToArray(); - - if (list.Length == 0) - Logger.Info("No target to kill", "Crewpostor"); - else if (TasksDone[player.PlayerId] % CrewpostorKillAfterTask.GetInt() != 0 && TasksDone[player.PlayerId] != 0) - Logger.Info($"Crewpostor task done but kill skipped, {TasksDone[player.PlayerId]} tasks completed, but it kills after {CrewpostorKillAfterTask.GetInt()} tasks", "Crewpostor"); - else + if (!target.Is(CustomRoles.Pestilence)) { - list = [.. list.OrderBy(x => Vector2.Distance(player.Pos(), x.Pos()))]; - PlayerControl target = list[0]; - - if (!target.Is(CustomRoles.Pestilence)) + if (!CrewpostorLungeKill.GetBool()) { - if (!CrewpostorLungeKill.GetBool()) - { - target.SetRealKiller(player); + target.SetRealKiller(player); - if (player.RpcCheckAndMurder(target, true)) - { - target.Suicide(PlayerState.DeathReason.Kill, player); - player.RpcGuardAndKill(); - } - - Logger.Info("No lunge mode kill", "Crewpostor"); - } - else + if (player.RpcCheckAndMurder(target, true)) { - player.SetRealKiller(target); - player.RpcCheckAndMurder(target); - //player.RpcGuardAndKill(); - Logger.Info("lunge mode kill", "Crewpostor"); + target.Suicide(PlayerState.DeathReason.Kill, player); + player.RpcGuardAndKill(); } - Logger.Info($"Crewpostor completed task to kill:{player.GetNameWithRole()} => {target.GetNameWithRole()}", "Crewpostor"); + Logger.Info("No lunge mode kill", "Crewpostor"); } else { - target.SetRealKiller(player); - target.Kill(player); - - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); - - Logger.Info($"Crewpostor tried to kill Pestilence:{target.GetNameWithRole()} => {player.GetNameWithRole().RemoveHtmlTags()}", "Pestilence Reflect"); + player.SetRealKiller(target); + player.RpcCheckAndMurder(target); + //player.RpcGuardAndKill(); + Logger.Info("lunge mode kill", "Crewpostor"); } - } - } - public override bool CanUseKillButton(PlayerControl pc) - { - return false; - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return false; - } - - public static void SendRPC(byte cpID, int tasksDone) - { - if (PlayerControl.LocalPlayer.PlayerId == cpID) - { - if (!TasksDone.TryAdd(cpID, 0)) TasksDone[cpID] = tasksDone; + Logger.Info($"Crewpostor completed task to kill:{player.GetNameWithRole()} => {target.GetNameWithRole()}", "Crewpostor"); } else { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCpTasksDone, SendOption.Reliable); - writer.Write(cpID); - writer.Write(tasksDone); - AmongUsClient.Instance.FinishRpcImmediately(writer); + target.SetRealKiller(player); + target.Kill(player); + + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); + + Logger.Info($"Crewpostor tried to kill Pestilence:{target.GetNameWithRole()} => {player.GetNameWithRole().RemoveHtmlTags()}", "Pestilence Reflect"); } } + } - public static void RecieveRPC(MessageReader reader) + public override bool CanUseKillButton(PlayerControl pc) + { + return false; + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return false; + } + + public static void SendRPC(byte cpID, int tasksDone) + { + if (PlayerControl.LocalPlayer.PlayerId == cpID) { - byte PlayerId = reader.ReadByte(); - int tasksDone = reader.ReadInt32(); - if (!TasksDone.TryAdd(PlayerId, 0)) TasksDone[PlayerId] = tasksDone; + if (!TasksDone.TryAdd(cpID, 0)) TasksDone[cpID] = tasksDone; } - - public override void Init() + else { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - TasksDone[playerId] = 0; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCpTasksDone, SendOption.Reliable); + writer.Write(cpID); + writer.Write(tasksDone); + AmongUsClient.Instance.FinishRpcImmediately(writer); } } + + public static void RecieveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + int tasksDone = reader.ReadInt32(); + if (!TasksDone.TryAdd(PlayerId, 0)) TasksDone[PlayerId] = tasksDone; + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + TasksDone[playerId] = 0; + } } \ No newline at end of file diff --git a/Roles/Impostor/CursedWolf.cs b/Roles/Impostor/CursedWolf.cs index 103879797..e5b197a79 100644 --- a/Roles/Impostor/CursedWolf.cs +++ b/Roles/Impostor/CursedWolf.cs @@ -1,96 +1,95 @@ using AmongUs.GameOptions; using EHR.Neutral; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class CursedWolf : RoleBase { - internal class CursedWolf : RoleBase + public static bool On; + private bool CanVent; + private bool HasImpostorVision; + private bool IsJinx; + private bool KillAttacker; + + private float KillCooldown; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private bool CanVent; - private bool HasImpostorVision; - private bool IsJinx; - private bool KillAttacker; + Options.SetupRoleOptions(1000, TabGroup.ImpostorRoles, CustomRoles.CursedWolf); // From TOH_Y - private float KillCooldown; - public override bool IsEnable => On; + Options.GuardSpellTimes = new IntegerOptionItem(1010, "GuardSpellTimes", new(1, 15, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.CursedWolf]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + Options.killAttacker = new BooleanOptionItem(1011, "killAttacker", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.CursedWolf]); + } + + public override void Add(byte playerId) + { + On = true; + IsJinx = Main.PlayerStates[playerId].MainRole == CustomRoles.Jinx; + playerId.SetAbilityUseLimit(IsJinx ? Jinx.JinxSpellTimes.GetInt() : Options.GuardSpellTimes.GetInt()); + + if (IsJinx) { - Options.SetupRoleOptions(1000, TabGroup.ImpostorRoles, CustomRoles.CursedWolf); // From TOH_Y - - Options.GuardSpellTimes = new IntegerOptionItem(1010, "GuardSpellTimes", new(1, 15, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.CursedWolf]) - .SetValueFormat(OptionFormat.Times); - - Options.killAttacker = new BooleanOptionItem(1011, "killAttacker", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.CursedWolf]); + KillCooldown = Jinx.KillCooldown.GetFloat(); + CanVent = Jinx.CanVent.GetBool(); + HasImpostorVision = Jinx.HasImpostorVision.GetBool(); + KillAttacker = Jinx.KillAttacker.GetBool(); } - - public override void Add(byte playerId) + else { - On = true; - IsJinx = Main.PlayerStates[playerId].MainRole == CustomRoles.Jinx; - playerId.SetAbilityUseLimit(IsJinx ? Jinx.JinxSpellTimes.GetInt() : Options.GuardSpellTimes.GetInt()); - - if (IsJinx) - { - KillCooldown = Jinx.KillCooldown.GetFloat(); - CanVent = Jinx.CanVent.GetBool(); - HasImpostorVision = Jinx.HasImpostorVision.GetBool(); - KillAttacker = Jinx.KillAttacker.GetBool(); - } - else - { - KillCooldown = Options.DefaultKillCooldown; - CanVent = true; - HasImpostorVision = true; - KillAttacker = Options.killAttacker.GetBool(); - } - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpostorVision); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown; - } - - public override void Init() - { - On = false; - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (target.GetAbilityUseLimit() <= 0) return true; - - if (killer.Is(CustomRoles.Pestilence)) return true; - - if (killer == target) return true; - - float kcd = Main.KillTimers[target.PlayerId] + Main.AllPlayerKillCooldown[target.PlayerId]; - - killer.RpcGuardAndKill(target); - target.RpcRemoveAbilityUse(); - Logger.Info($"{target.GetNameWithRole().RemoveHtmlTags()} : {target.GetAbilityUseLimit()} curses remain", "CursedWolf"); - - if (KillAttacker) - { - Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Curse; - killer.SetRealKiller(target); - target.Kill(killer); - LateTask.New(() => { target.SetKillCooldown(kcd); }, 0.1f, log: false); - } - - return false; + KillCooldown = Options.DefaultKillCooldown; + CanVent = true; + HasImpostorVision = true; + KillAttacker = Options.killAttacker.GetBool(); } } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpostorVision); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown; + } + + public override void Init() + { + On = false; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (target.GetAbilityUseLimit() <= 0) return true; + + if (killer.Is(CustomRoles.Pestilence)) return true; + + if (killer == target) return true; + + float kcd = Main.KillTimers[target.PlayerId] + Main.AllPlayerKillCooldown[target.PlayerId]; + + killer.RpcGuardAndKill(target); + target.RpcRemoveAbilityUse(); + Logger.Info($"{target.GetNameWithRole().RemoveHtmlTags()} : {target.GetAbilityUseLimit()} curses remain", "CursedWolf"); + + if (KillAttacker) + { + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Curse; + killer.SetRealKiller(target); + target.Kill(killer); + LateTask.New(() => { target.SetKillCooldown(kcd); }, 0.1f, log: false); + } + + return false; + } } \ No newline at end of file diff --git a/Roles/Impostor/Dazzler.cs b/Roles/Impostor/Dazzler.cs index 32c27e839..dd473a356 100644 --- a/Roles/Impostor/Dazzler.cs +++ b/Roles/Impostor/Dazzler.cs @@ -4,110 +4,109 @@ using EHR.Neutral; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Dazzler : RoleBase { - public class Dazzler : RoleBase + private const int Id = 3500; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem ShapeshiftCooldown; + private static OptionItem CauseVision; + public static OptionItem DazzleLimitOpt; + private static OptionItem ResetDazzledVisionOnDeath; + public static OptionItem DazzlerAbilityUseGainWithEachKill; + + public List PlayersDazzled = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 3500; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Dazzler); - private static OptionItem KillCooldown; - private static OptionItem ShapeshiftCooldown; - private static OptionItem CauseVision; - public static OptionItem DazzleLimitOpt; - private static OptionItem ResetDazzledVisionOnDeath; - public static OptionItem DazzlerAbilityUseGainWithEachKill; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) + .SetValueFormat(OptionFormat.Seconds); - public List PlayersDazzled = []; + ShapeshiftCooldown = new FloatOptionItem(Id + 11, "DazzleCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + CauseVision = new FloatOptionItem(Id + 13, "DazzlerCauseVision", new(0f, 5f, 0.05f), 0.4f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() + DazzleLimitOpt = new IntegerOptionItem(Id + 14, "DazzlerDazzleLimit", new(0, 15, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) + .SetValueFormat(OptionFormat.Times); + + ResetDazzledVisionOnDeath = new BooleanOptionItem(Id + 15, "DazzlerResetDazzledVisionOnDeath", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]); + + DazzlerAbilityUseGainWithEachKill = new FloatOptionItem(Id + 16, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + PlayersDazzled = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + PlayersDazzled = []; + playerId.SetAbilityUseLimit(DazzleLimitOpt.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) + { + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || !shapeshifting) return false; + + if (!PlayersDazzled.Contains(target.PlayerId) && PlayersDazzled.Count < pc.GetAbilityUseLimit()) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Dazzler); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftCooldown = new FloatOptionItem(Id + 11, "DazzleCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) - .SetValueFormat(OptionFormat.Seconds); - - CauseVision = new FloatOptionItem(Id + 13, "DazzlerCauseVision", new(0f, 5f, 0.05f), 0.4f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) - .SetValueFormat(OptionFormat.Multiplier); - - DazzleLimitOpt = new IntegerOptionItem(Id + 14, "DazzlerDazzleLimit", new(0, 15, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) - .SetValueFormat(OptionFormat.Times); - - ResetDazzledVisionOnDeath = new BooleanOptionItem(Id + 15, "DazzlerResetDazzledVisionOnDeath", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]); - - DazzlerAbilityUseGainWithEachKill = new FloatOptionItem(Id + 16, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Dazzler]) - .SetValueFormat(OptionFormat.Times); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Dazzler), GetString("DazzlerDazzled"))); + PlayersDazzled.Add(target.PlayerId); + target.MarkDirtySettings(); } - public override void Init() - { - PlayerIdList = []; - PlayersDazzled = []; - } + return false; + } - public override void Add(byte playerId) + public static void SetDazzled(PlayerControl player, IGameOptions opt) + { + foreach (KeyValuePair state in Main.PlayerStates) { - PlayerIdList.Add(playerId); - PlayersDazzled = []; - playerId.SetAbilityUseLimit(DazzleLimitOpt.GetInt()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) - { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || !shapeshifting) return false; - - if (!PlayersDazzled.Contains(target.PlayerId) && PlayersDazzled.Count < pc.GetAbilityUseLimit()) + if (state.Value.Role is Dazzler { IsEnable: true } dz) { - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Dazzler), GetString("DazzlerDazzled"))); - PlayersDazzled.Add(target.PlayerId); - target.MarkDirtySettings(); - } - - return false; - } - - public static void SetDazzled(PlayerControl player, IGameOptions opt) - { - foreach (KeyValuePair state in Main.PlayerStates) - { - if (state.Value.Role is Dazzler { IsEnable: true } dz) + if (dz.PlayersDazzled.Contains(player.PlayerId) && (!ResetDazzledVisionOnDeath.GetBool() || Utils.GetPlayerById(state.Key).IsAlive())) { - if (dz.PlayersDazzled.Contains(player.PlayerId) && (!ResetDazzledVisionOnDeath.GetBool() || Utils.GetPlayerById(state.Key).IsAlive())) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, CauseVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, CauseVision.GetFloat()); - } + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, CauseVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, CauseVision.GetFloat()); } } } + } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton?.OverrideText(GetString("DazzleButtonText")); - hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(GetString("DazzleButtonText")); + hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); } } \ No newline at end of file diff --git a/Roles/Impostor/Deathpact.cs b/Roles/Impostor/Deathpact.cs index ee5f2abf5..d9cfa8f30 100644 --- a/Roles/Impostor/Deathpact.cs +++ b/Roles/Impostor/Deathpact.cs @@ -9,320 +9,319 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Deathpact : RoleBase { - public class Deathpact : RoleBase + private const int Id = 1100; + public static List PlayerIdList = []; + + public static List ActiveDeathpacts = []; + + private static OptionItem KillCooldown; + private static OptionItem ShapeshiftCooldown; + private static OptionItem DeathpactDuration; + private static OptionItem NumberOfPlayersInPact; + private static OptionItem ShowArrowsToOtherPlayersInPact; + private static OptionItem ReduceVisionWhileInPact; + private static OptionItem VisionWhileInPact; + private static OptionItem KillDeathpactPlayersOnMeeting; + private byte DeathPactId; + public long DeathpactTime; + + public List PlayersInDeathpact = []; + + public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + + public override void SetupCustomOption() { - private const int Id = 1100; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Deathpact); - public static List ActiveDeathpacts = []; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem KillCooldown; - private static OptionItem ShapeshiftCooldown; - private static OptionItem DeathpactDuration; - private static OptionItem NumberOfPlayersInPact; - private static OptionItem ShowArrowsToOtherPlayersInPact; - private static OptionItem ReduceVisionWhileInPact; - private static OptionItem VisionWhileInPact; - private static OptionItem KillDeathpactPlayersOnMeeting; - private byte DeathPactId; - public long DeathpactTime; + ShapeshiftCooldown = new FloatOptionItem(Id + 11, "DeathPactCooldown", new(0f, 180f, 2.5f), 5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) + .SetValueFormat(OptionFormat.Seconds); - public List PlayersInDeathpact = []; + DeathpactDuration = new FloatOptionItem(Id + 13, "DeathpactDuration", new(0f, 180f, 2.5f), 17.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + NumberOfPlayersInPact = new IntegerOptionItem(Id + 14, "DeathpactNumberOfPlayersInPact", new(2, 5, 1), 2, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + ShowArrowsToOtherPlayersInPact = new BooleanOptionItem(Id + 15, "DeathpactShowArrowsToOtherPlayersInPact", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]); + + ReduceVisionWhileInPact = new BooleanOptionItem(Id + 16, "DeathpactReduceVisionWhileInPact", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]); + + VisionWhileInPact = new FloatOptionItem(Id + 17, "DeathpactVisionWhileInPact", new(0f, 5f, 0.05f), 0.4f, TabGroup.ImpostorRoles) + .SetParent(ReduceVisionWhileInPact) + .SetValueFormat(OptionFormat.Multiplier); + + KillDeathpactPlayersOnMeeting = new BooleanOptionItem(Id + 18, "DeathpactKillPlayersInDeathpactOnMeeting", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]); + } + + public override void Init() + { + PlayerIdList = []; + PlayersInDeathpact = []; + DeathpactTime = 0; + ActiveDeathpacts = []; + DeathPactId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + PlayersInDeathpact = []; + DeathpactTime = 0; + DeathPactId = playerId; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) + { + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || !shapeshifting) return false; + + if (!target.IsAlive() || Pelican.IsEaten(target.PlayerId)) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Deathpact); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftCooldown = new FloatOptionItem(Id + 11, "DeathPactCooldown", new(0f, 180f, 2.5f), 5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) - .SetValueFormat(OptionFormat.Seconds); - - DeathpactDuration = new FloatOptionItem(Id + 13, "DeathpactDuration", new(0f, 180f, 2.5f), 17.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) - .SetValueFormat(OptionFormat.Seconds); - - NumberOfPlayersInPact = new IntegerOptionItem(Id + 14, "DeathpactNumberOfPlayersInPact", new(2, 5, 1), 2, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]) - .SetValueFormat(OptionFormat.Times); - - ShowArrowsToOtherPlayersInPact = new BooleanOptionItem(Id + 15, "DeathpactShowArrowsToOtherPlayersInPact", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]); - - ReduceVisionWhileInPact = new BooleanOptionItem(Id + 16, "DeathpactReduceVisionWhileInPact", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]); - - VisionWhileInPact = new FloatOptionItem(Id + 17, "DeathpactVisionWhileInPact", new(0f, 5f, 0.05f), 0.4f, TabGroup.ImpostorRoles) - .SetParent(ReduceVisionWhileInPact) - .SetValueFormat(OptionFormat.Multiplier); - - KillDeathpactPlayersOnMeeting = new BooleanOptionItem(Id + 18, "DeathpactKillPlayersInDeathpactOnMeeting", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Deathpact]); - } - - public override void Init() - { - PlayerIdList = []; - PlayersInDeathpact = []; - DeathpactTime = 0; - ActiveDeathpacts = []; - DeathPactId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - PlayersInDeathpact = []; - DeathpactTime = 0; - DeathPactId = playerId; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) - { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || !shapeshifting) return false; - - if (!target.IsAlive() || Pelican.IsEaten(target.PlayerId)) - { - pc.Notify(GetString("DeathpactCouldNotAddTarget")); - return false; - } - - if (PlayersInDeathpact.All(b => b.PlayerId != target.PlayerId)) PlayersInDeathpact.Add(target); - - if (PlayersInDeathpact.Count < NumberOfPlayersInPact.GetInt()) return false; - - if (ReduceVisionWhileInPact.GetBool()) - { - foreach (PlayerControl player in PlayersInDeathpact) - { - foreach (PlayerControl otherPlayerInPact in PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId)) - { - otherPlayerInPact.MarkDirtySettings(); - player.MarkDirtySettings(); - } - } - } - - pc.Notify(GetString("DeathpactComplete")); - DeathpactTime = TimeStamp + DeathpactDuration.GetInt(); - ActiveDeathpacts.Add(pc.PlayerId); - - if (ShowArrowsToOtherPlayersInPact.GetBool()) - { - foreach (PlayerControl player in PlayersInDeathpact) - { - foreach (PlayerControl otherPlayerInPact in PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId)) - { - TargetArrow.Add(player.PlayerId, otherPlayerInPact.PlayerId); - otherPlayerInPact.MarkDirtySettings(); - } - } - } - + pc.Notify(GetString("DeathpactCouldNotAddTarget")); return false; } - public static void SetDeathpactVision(PlayerControl player, IGameOptions opt) + if (PlayersInDeathpact.All(b => b.PlayerId != target.PlayerId)) PlayersInDeathpact.Add(target); + + if (PlayersInDeathpact.Count < NumberOfPlayersInPact.GetInt()) return false; + + if (ReduceVisionWhileInPact.GetBool()) { - if (!ReduceVisionWhileInPact.GetBool()) return; - - if (Main.PlayerStates[player.PlayerId].Role is not Deathpact { IsEnable: true } dp) return; - - if (dp.PlayersInDeathpact.Any(b => b.PlayerId == player.PlayerId) && dp.PlayersInDeathpact.Count == NumberOfPlayersInPact.GetInt()) + foreach (PlayerControl player in PlayersInDeathpact) { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, VisionWhileInPact.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, VisionWhileInPact.GetFloat()); - } - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!IsEnable || !GameStates.IsInTask || player.GetCustomRole() is not CustomRoles.Deathpact and not CustomRoles.Randomizer) return; - - if (!ActiveDeathpacts.Contains(player.PlayerId)) return; - - if (CheckCancelDeathpact(player)) return; - - if (DeathpactTime < TimeStamp && DeathpactTime != 0) - { - foreach (PlayerControl playerInDeathpact in PlayersInDeathpact) KillPlayerInDeathpact(player, playerInDeathpact); - - ClearDeathpact(player.PlayerId); - player.Notify(GetString("DeathpactExecuted")); - } - } - - public static bool CheckCancelDeathpact(PlayerControl deathpact) - { - if (Main.PlayerStates[deathpact.PlayerId].Role is not Deathpact { IsEnable: true } dp) return true; - - if (dp.PlayersInDeathpact.Any(a => a.Data.Disconnected || a.Data.IsDead)) - { - ClearDeathpact(deathpact.PlayerId); - deathpact.Notify(GetString("DeathpactAverted")); - return true; - } - - var cancelDeathpact = true; - - foreach (PlayerControl player in dp.PlayersInDeathpact) - { - float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - cancelDeathpact = dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId).Select(otherPlayerInPact => Vector2.Distance(player.transform.position, otherPlayerInPact.transform.position)).Aggregate(cancelDeathpact, (current, dis) => current && dis <= range); - } - - if (cancelDeathpact) - { - ClearDeathpact(deathpact.PlayerId); - deathpact.Notify(GetString("DeathpactAverted")); - } - - return cancelDeathpact; - } - - public static void KillPlayerInDeathpact(PlayerControl deathpact, PlayerControl target) - { - if (deathpact == null || target == null || target.Data.Disconnected) return; - - if (!target.IsAlive()) return; - - target.Suicide(realKiller: deathpact); - - if (target.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (GameStates.IsMeeting) return string.Empty; - - if (!ShowArrowsToOtherPlayersInPact.GetBool()) return string.Empty; - - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - if (!IsInActiveDeathpact(seer)) return string.Empty; - - var arrows = string.Empty; - - foreach (KeyValuePair state in Main.PlayerStates) - if (state.Value.Role is Deathpact { IsEnable: true } dp) - arrows = dp.PlayersInDeathpact.Where(a => a.PlayerId != seer.PlayerId).Select(otherPlayerInPact => TargetArrow.GetArrows(seer, otherPlayerInPact.PlayerId)).Aggregate(arrows, (current, arrow) => current + ColorString(GetRoleColor(CustomRoles.Crewmate), arrow)); - - return arrows; - } - - public static string GetDeathpactMark(PlayerControl seer, PlayerControl target) - { - if (!seer.Is(CustomRoles.Deathpact) || !IsInDeathpact(seer.PlayerId, target)) return string.Empty; - - return ColorString(Palette.ImpostorRed, "◀"); - } - - public static bool IsInActiveDeathpact(PlayerControl player) - { - if (ActiveDeathpacts.Count == 0) return false; - - foreach (KeyValuePair state in Main.PlayerStates) - { - if (state.Value.Role is Deathpact { IsEnable: true } dp) + foreach (PlayerControl otherPlayerInPact in PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId)) { - if (!ActiveDeathpacts.Contains(dp.DeathPactId) || dp.PlayersInDeathpact.All(b => b.PlayerId != player.PlayerId)) continue; - - return true; - } - } - - return false; - } - - public static bool IsInDeathpact(byte deathpact, PlayerControl target) - { - return Main.PlayerStates[deathpact].Role is Deathpact { IsEnable: true } dp && dp.PlayersInDeathpact.Any(a => a.PlayerId == target.PlayerId); - } - - public static string GetDeathpactString(PlayerControl player) - { - var result = string.Empty; - - foreach (KeyValuePair state in Main.PlayerStates) - { - if (state.Value.Role is Deathpact { IsEnable: true } dp) - { - if (!ActiveDeathpacts.Contains(dp.DeathPactId) || dp.PlayersInDeathpact.All(b => b.PlayerId != player.PlayerId)) continue; - - string otherPlayerNames = dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId).Aggregate(string.Empty, (current, otherPlayerInPact) => current + otherPlayerInPact.name.ToUpper() + ","); - otherPlayerNames = otherPlayerNames.Remove(otherPlayerNames.Length - 1); - - var countdown = (int)(dp.DeathpactTime - TimeStamp); - - result += $"{ColorString(GetRoleColor(CustomRoles.Impostor), string.Format(GetString("DeathpactActiveDeathpact"), otherPlayerNames, countdown))}"; - } - } - - return result; - } - - public static void ClearDeathpact(byte deathpact) - { - if (Main.PlayerStates[deathpact].Role is not Deathpact { IsEnable: true } dp) return; - - dp.DeathpactTime = 0; - ActiveDeathpacts.Remove(deathpact); - dp.PlayersInDeathpact.Clear(); - - if (ReduceVisionWhileInPact.GetBool()) - { - foreach (PlayerControl player in dp.PlayersInDeathpact) - { - foreach (PlayerControl otherPlayerInPact in dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId)) - { - if (ShowArrowsToOtherPlayersInPact.GetBool()) TargetArrow.Remove(player.PlayerId, otherPlayerInPact.PlayerId); - - otherPlayerInPact.MarkDirtySettings(); - player.MarkDirtySettings(); - } + otherPlayerInPact.MarkDirtySettings(); + player.MarkDirtySettings(); } } } - public override void OnReportDeadBody() + pc.Notify(GetString("DeathpactComplete")); + DeathpactTime = TimeStamp + DeathpactDuration.GetInt(); + ActiveDeathpacts.Add(pc.PlayerId); + + if (ShowArrowsToOtherPlayersInPact.GetBool()) { - foreach (byte deathpact in ActiveDeathpacts) + foreach (PlayerControl player in PlayersInDeathpact) { - if (KillDeathpactPlayersOnMeeting.GetBool()) + foreach (PlayerControl otherPlayerInPact in PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId)) { - PlayerControl deathpactPlayer = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == deathpact); - if (deathpactPlayer == null || deathpactPlayer.Data.IsDead) continue; - - foreach (PlayerControl player in PlayersInDeathpact) KillPlayerInDeathpact(deathpactPlayer, player); + TargetArrow.Add(player.PlayerId, otherPlayerInPact.PlayerId); + otherPlayerInPact.MarkDirtySettings(); } - - ClearDeathpact(deathpact); } } - public override void SetButtonTexts(HudManager hud, byte id) + return false; + } + + public static void SetDeathpactVision(PlayerControl player, IGameOptions opt) + { + if (!ReduceVisionWhileInPact.GetBool()) return; + + if (Main.PlayerStates[player.PlayerId].Role is not Deathpact { IsEnable: true } dp) return; + + if (dp.PlayersInDeathpact.Any(b => b.PlayerId == player.PlayerId) && dp.PlayersInDeathpact.Count == NumberOfPlayersInPact.GetInt()) { - hud.AbilityButton?.OverrideText(GetString("DeathpactButtonText")); + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, VisionWhileInPact.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, VisionWhileInPact.GetFloat()); } } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!IsEnable || !GameStates.IsInTask || player.GetCustomRole() is not CustomRoles.Deathpact and not CustomRoles.Randomizer) return; + + if (!ActiveDeathpacts.Contains(player.PlayerId)) return; + + if (CheckCancelDeathpact(player)) return; + + if (DeathpactTime < TimeStamp && DeathpactTime != 0) + { + foreach (PlayerControl playerInDeathpact in PlayersInDeathpact) KillPlayerInDeathpact(player, playerInDeathpact); + + ClearDeathpact(player.PlayerId); + player.Notify(GetString("DeathpactExecuted")); + } + } + + public static bool CheckCancelDeathpact(PlayerControl deathpact) + { + if (Main.PlayerStates[deathpact.PlayerId].Role is not Deathpact { IsEnable: true } dp) return true; + + if (dp.PlayersInDeathpact.Any(a => a.Data.Disconnected || a.Data.IsDead)) + { + ClearDeathpact(deathpact.PlayerId); + deathpact.Notify(GetString("DeathpactAverted")); + return true; + } + + var cancelDeathpact = true; + + foreach (PlayerControl player in dp.PlayersInDeathpact) + { + float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; + cancelDeathpact = dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId).Select(otherPlayerInPact => Vector2.Distance(player.transform.position, otherPlayerInPact.transform.position)).Aggregate(cancelDeathpact, (current, dis) => current && dis <= range); + } + + if (cancelDeathpact) + { + ClearDeathpact(deathpact.PlayerId); + deathpact.Notify(GetString("DeathpactAverted")); + } + + return cancelDeathpact; + } + + public static void KillPlayerInDeathpact(PlayerControl deathpact, PlayerControl target) + { + if (deathpact == null || target == null || target.Data.Disconnected) return; + + if (!target.IsAlive()) return; + + target.Suicide(realKiller: deathpact); + + if (target.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (GameStates.IsMeeting) return string.Empty; + + if (!ShowArrowsToOtherPlayersInPact.GetBool()) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + if (!IsInActiveDeathpact(seer)) return string.Empty; + + var arrows = string.Empty; + + foreach (KeyValuePair state in Main.PlayerStates) + if (state.Value.Role is Deathpact { IsEnable: true } dp) + arrows = dp.PlayersInDeathpact.Where(a => a.PlayerId != seer.PlayerId).Select(otherPlayerInPact => TargetArrow.GetArrows(seer, otherPlayerInPact.PlayerId)).Aggregate(arrows, (current, arrow) => current + ColorString(GetRoleColor(CustomRoles.Crewmate), arrow)); + + return arrows; + } + + public static string GetDeathpactMark(PlayerControl seer, PlayerControl target) + { + if (!seer.Is(CustomRoles.Deathpact) || !IsInDeathpact(seer.PlayerId, target)) return string.Empty; + + return ColorString(Palette.ImpostorRed, "◀"); + } + + public static bool IsInActiveDeathpact(PlayerControl player) + { + if (ActiveDeathpacts.Count == 0) return false; + + foreach (KeyValuePair state in Main.PlayerStates) + { + if (state.Value.Role is Deathpact { IsEnable: true } dp) + { + if (!ActiveDeathpacts.Contains(dp.DeathPactId) || dp.PlayersInDeathpact.All(b => b.PlayerId != player.PlayerId)) continue; + + return true; + } + } + + return false; + } + + public static bool IsInDeathpact(byte deathpact, PlayerControl target) + { + return Main.PlayerStates[deathpact].Role is Deathpact { IsEnable: true } dp && dp.PlayersInDeathpact.Any(a => a.PlayerId == target.PlayerId); + } + + public static string GetDeathpactString(PlayerControl player) + { + var result = string.Empty; + + foreach (KeyValuePair state in Main.PlayerStates) + { + if (state.Value.Role is Deathpact { IsEnable: true } dp) + { + if (!ActiveDeathpacts.Contains(dp.DeathPactId) || dp.PlayersInDeathpact.All(b => b.PlayerId != player.PlayerId)) continue; + + string otherPlayerNames = dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId).Aggregate(string.Empty, (current, otherPlayerInPact) => current + otherPlayerInPact.name.ToUpper() + ","); + otherPlayerNames = otherPlayerNames.Remove(otherPlayerNames.Length - 1); + + var countdown = (int)(dp.DeathpactTime - TimeStamp); + + result += $"{ColorString(GetRoleColor(CustomRoles.Impostor), string.Format(GetString("DeathpactActiveDeathpact"), otherPlayerNames, countdown))}"; + } + } + + return result; + } + + public static void ClearDeathpact(byte deathpact) + { + if (Main.PlayerStates[deathpact].Role is not Deathpact { IsEnable: true } dp) return; + + dp.DeathpactTime = 0; + ActiveDeathpacts.Remove(deathpact); + dp.PlayersInDeathpact.Clear(); + + if (ReduceVisionWhileInPact.GetBool()) + { + foreach (PlayerControl player in dp.PlayersInDeathpact) + { + foreach (PlayerControl otherPlayerInPact in dp.PlayersInDeathpact.Where(a => a.PlayerId != player.PlayerId)) + { + if (ShowArrowsToOtherPlayersInPact.GetBool()) TargetArrow.Remove(player.PlayerId, otherPlayerInPact.PlayerId); + + otherPlayerInPact.MarkDirtySettings(); + player.MarkDirtySettings(); + } + } + } + } + + public override void OnReportDeadBody() + { + foreach (byte deathpact in ActiveDeathpacts) + { + if (KillDeathpactPlayersOnMeeting.GetBool()) + { + PlayerControl deathpactPlayer = Main.AllPlayerControls.FirstOrDefault(a => a.PlayerId == deathpact); + if (deathpactPlayer == null || deathpactPlayer.Data.IsDead) continue; + + foreach (PlayerControl player in PlayersInDeathpact) KillPlayerInDeathpact(deathpactPlayer, player); + } + + ClearDeathpact(deathpact); + } + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(GetString("DeathpactButtonText")); + } } \ No newline at end of file diff --git a/Roles/Impostor/Devourer.cs b/Roles/Impostor/Devourer.cs index 986446be9..5c5e979a6 100644 --- a/Roles/Impostor/Devourer.cs +++ b/Roles/Impostor/Devourer.cs @@ -5,171 +5,170 @@ using EHR.Neutral; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Devourer : RoleBase { - public class Devourer : RoleBase + private const int Id = 3550; + private static readonly NetworkedPlayerInfo.PlayerOutfit ConsumedOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "visor_Crack", "", ""); + private static Dictionary OriginalPlayerSkins = []; + public static List PlayerIdList = []; + + private static OptionItem DefaultKillCooldown; + private static OptionItem ReduceKillCooldown; + private static OptionItem MinKillCooldown; + private static OptionItem ShapeshiftCooldown; + public static OptionItem HideNameOfConsumedPlayer; + private float NowCooldown; + + public List PlayerSkinsCosumed = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 3550; - private static readonly NetworkedPlayerInfo.PlayerOutfit ConsumedOutfit = new NetworkedPlayerInfo.PlayerOutfit().Set("", 15, "", "", "visor_Crack", "", ""); - private static Dictionary OriginalPlayerSkins = []; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Devourer); - private static OptionItem DefaultKillCooldown; - private static OptionItem ReduceKillCooldown; - private static OptionItem MinKillCooldown; - private static OptionItem ShapeshiftCooldown; - public static OptionItem HideNameOfConsumedPlayer; - private float NowCooldown; + DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) + .SetValueFormat(OptionFormat.Seconds); - public List PlayerSkinsCosumed = []; + ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 1.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 0.5f), 21f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + ShapeshiftCooldown = new FloatOptionItem(Id + 14, "DevourCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) + .SetValueFormat(OptionFormat.Seconds); + + HideNameOfConsumedPlayer = new BooleanOptionItem(Id + 16, "DevourerHideNameConsumed", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]); + } + + public override void Init() + { + PlayerIdList = []; + PlayerSkinsCosumed = []; + OriginalPlayerSkins = []; + NowCooldown = DefaultKillCooldown.GetFloat(); + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + PlayerSkinsCosumed = []; + NowCooldown = DefaultKillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = NowCooldown; + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) + { + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || !shapeshifting) return false; + + if (!PlayerSkinsCosumed.Contains(target.PlayerId)) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Devourer); + if (!Camouflage.IsCamouflage) SetSkin(target, ConsumedOutfit); - DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) - .SetValueFormat(OptionFormat.Seconds); + PlayerSkinsCosumed.Add(target.PlayerId); + pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Devourer), GetString("DevourerEatenSkin"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Devourer), GetString("EatenByDevourer"))); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: pc); - ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 1.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) - .SetValueFormat(OptionFormat.Seconds); + OriginalPlayerSkins.Add(target.PlayerId, Camouflage.PlayerSkins[target.PlayerId]); + Camouflage.PlayerSkins[target.PlayerId] = ConsumedOutfit; - MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 0.5f), 21f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) - .SetValueFormat(OptionFormat.Seconds); + float cdReduction = ReduceKillCooldown.GetFloat() * PlayerSkinsCosumed.Count; + float cd = DefaultKillCooldown.GetFloat() - cdReduction; - ShapeshiftCooldown = new FloatOptionItem(Id + 14, "DevourCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]) - .SetValueFormat(OptionFormat.Seconds); - - HideNameOfConsumedPlayer = new BooleanOptionItem(Id + 16, "DevourerHideNameConsumed", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Devourer]); + NowCooldown = cd < MinKillCooldown.GetFloat() ? MinKillCooldown.GetFloat() : cd; } - public override void Init() - { - PlayerIdList = []; - PlayerSkinsCosumed = []; - OriginalPlayerSkins = []; - NowCooldown = DefaultKillCooldown.GetFloat(); - } + return false; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - PlayerSkinsCosumed = []; - NowCooldown = DefaultKillCooldown.GetFloat(); - } + public static void OnDevourerDied(byte Devourer) + { + if (Main.PlayerStates[Devourer].Role is not Devourer { IsEnable: true } dv) return; - public override void ApplyGameOptions(IGameOptions opt, byte id) + foreach (byte player in dv.PlayerSkinsCosumed.ToArray()) { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } + Camouflage.PlayerSkins[player] = OriginalPlayerSkins[player]; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = NowCooldown; - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) - { - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId) || !shapeshifting) return false; - - if (!PlayerSkinsCosumed.Contains(target.PlayerId)) + if (!Camouflage.IsCamouflage) { - if (!Camouflage.IsCamouflage) SetSkin(target, ConsumedOutfit); + PlayerControl pc = Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == player); + if (pc == null) continue; - PlayerSkinsCosumed.Add(target.PlayerId); - pc.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Devourer), GetString("DevourerEatenSkin"))); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Devourer), GetString("EatenByDevourer"))); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: pc); - - OriginalPlayerSkins.Add(target.PlayerId, Camouflage.PlayerSkins[target.PlayerId]); - Camouflage.PlayerSkins[target.PlayerId] = ConsumedOutfit; - - float cdReduction = ReduceKillCooldown.GetFloat() * PlayerSkinsCosumed.Count; - float cd = DefaultKillCooldown.GetFloat() - cdReduction; - - NowCooldown = cd < MinKillCooldown.GetFloat() ? MinKillCooldown.GetFloat() : cd; + SetSkin(pc, OriginalPlayerSkins[player]); } - - return false; } - public static void OnDevourerDied(byte Devourer) - { - if (Main.PlayerStates[Devourer].Role is not Devourer { IsEnable: true } dv) return; + dv.PlayerSkinsCosumed.Clear(); + } - foreach (byte player in dv.PlayerSkinsCosumed.ToArray()) - { - Camouflage.PlayerSkins[player] = OriginalPlayerSkins[player]; + private static void SetSkin(PlayerControl target, NetworkedPlayerInfo.PlayerOutfit outfit) + { + var sender = CustomRpcSender.Create($"Camouflage.RpcSetSkin({target.Data.PlayerName})"); - if (!Camouflage.IsCamouflage) - { - PlayerControl pc = Main.AllAlivePlayerControls.FirstOrDefault(a => a.PlayerId == player); - if (pc == null) continue; + target.SetColor(outfit.ColorId); - SetSkin(pc, OriginalPlayerSkins[player]); - } - } + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) + .Write(target.Data.NetId) + .Write((byte)outfit.ColorId) + .EndRpc(); - dv.PlayerSkinsCosumed.Clear(); - } + target.SetHat(outfit.HatId, outfit.ColorId); + target.Data.DefaultOutfit.HatSequenceId += 10; - private static void SetSkin(PlayerControl target, NetworkedPlayerInfo.PlayerOutfit outfit) - { - var sender = CustomRpcSender.Create($"Camouflage.RpcSetSkin({target.Data.PlayerName})"); + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) + .Write(outfit.HatId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); - target.SetColor(outfit.ColorId); + target.SetSkin(outfit.SkinId, outfit.ColorId); + target.Data.DefaultOutfit.SkinSequenceId += 10; - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetColor) - .Write(target.Data.NetId) - .Write((byte)outfit.ColorId) - .EndRpc(); + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) + .Write(outfit.SkinId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); - target.SetHat(outfit.HatId, outfit.ColorId); - target.Data.DefaultOutfit.HatSequenceId += 10; + target.SetVisor(outfit.VisorId, outfit.ColorId); + target.Data.DefaultOutfit.VisorSequenceId += 10; - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetHatStr) - .Write(outfit.HatId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) + .Write(outfit.VisorId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); - target.SetSkin(outfit.SkinId, outfit.ColorId); - target.Data.DefaultOutfit.SkinSequenceId += 10; + target.SetPet(outfit.PetId); + target.Data.DefaultOutfit.PetSequenceId += 10; - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetSkinStr) - .Write(outfit.SkinId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); + sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) + .Write(outfit.PetId) + .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); - target.SetVisor(outfit.VisorId, outfit.ColorId); - target.Data.DefaultOutfit.VisorSequenceId += 10; + sender.SendMessage(); + } - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetVisorStr) - .Write(outfit.VisorId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - target.SetPet(outfit.PetId); - target.Data.DefaultOutfit.PetSequenceId += 10; - - sender.AutoStartRpc(target.NetId, (byte)RpcCalls.SetPetStr) - .Write(outfit.PetId) - .Write(target.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - sender.SendMessage(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton?.OverrideText(GetString("DevourerButtonText")); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(GetString("DevourerButtonText")); } } \ No newline at end of file diff --git a/Roles/Impostor/Disperser.cs b/Roles/Impostor/Disperser.cs index 6b9b7eb08..d83092b3b 100644 --- a/Roles/Impostor/Disperser.cs +++ b/Roles/Impostor/Disperser.cs @@ -4,126 +4,125 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Disperser : RoleBase { - public class Disperser : RoleBase + private const int Id = 17000; + + public static OptionItem DisperserShapeshiftCooldown; + private static OptionItem DisperserShapeshiftDuration; + private static OptionItem DisperserLimitOpt; + public static OptionItem DisperserAbilityUseGainWithEachKill; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 17000; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Disperser); - public static OptionItem DisperserShapeshiftCooldown; - private static OptionItem DisperserShapeshiftDuration; - private static OptionItem DisperserLimitOpt; - public static OptionItem DisperserAbilityUseGainWithEachKill; + DisperserShapeshiftCooldown = new FloatOptionItem(Id + 5, "ShapeshiftCooldown", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) + .SetValueFormat(OptionFormat.Seconds); - public static bool On; - public override bool IsEnable => On; + DisperserShapeshiftDuration = new FloatOptionItem(Id + 6, "ShapeshiftDuration", new(1f, 30f, 1f), 1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + DisperserLimitOpt = new IntegerOptionItem(Id + 7, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) + .SetValueFormat(OptionFormat.Times); + + DisperserAbilityUseGainWithEachKill = new FloatOptionItem(Id + 8, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + playerId.SetAbilityUseLimit(DisperserLimitOpt.GetInt()); + On = true; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = DisperserShapeshiftCooldown.GetFloat(); + else { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Disperser); + if (UsePets.GetBool()) return; - DisperserShapeshiftCooldown = new FloatOptionItem(Id + 5, "ShapeshiftCooldown", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) - .SetValueFormat(OptionFormat.Seconds); - - DisperserShapeshiftDuration = new FloatOptionItem(Id + 6, "ShapeshiftDuration", new(1f, 30f, 1f), 1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) - .SetValueFormat(OptionFormat.Seconds); - - DisperserLimitOpt = new IntegerOptionItem(Id + 7, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) - .SetValueFormat(OptionFormat.Times); - - DisperserAbilityUseGainWithEachKill = new FloatOptionItem(Id + 8, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Disperser]) - .SetValueFormat(OptionFormat.Times); + AURoleOptions.ShapeshifterCooldown = DisperserShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = DisperserShapeshiftDuration.GetFloat(); } + } - public override void Init() + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifter == null || (!shapeshifting && !UseUnshiftTrigger.GetBool())) return false; + + if (shapeshifter.GetAbilityUseLimit() < 1 && !DisableShapeshiftAnimations.GetBool()) { - On = false; - } - - public override void Add(byte playerId) - { - playerId.SetAbilityUseLimit(DisperserLimitOpt.GetInt()); - On = true; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = DisperserShapeshiftCooldown.GetFloat(); - else - { - if (UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = DisperserShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = DisperserShapeshiftDuration.GetFloat(); - } - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifter == null || (!shapeshifting && !UseUnshiftTrigger.GetBool())) return false; - - if (shapeshifter.GetAbilityUseLimit() < 1 && !DisableShapeshiftAnimations.GetBool()) - { - shapeshifter.SetKillCooldown(DisperserShapeshiftDuration.GetFloat() + 1f); - return false; - } - - Disperse(shapeshifter); - + shapeshifter.SetKillCooldown(DisperserShapeshiftDuration.GetFloat() + 1f); return false; } - public override bool OnVanish(PlayerControl pc) + Disperse(shapeshifter); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + if (pc == null || pc.GetAbilityUseLimit() < 1) return false; + + Disperse(pc); + + return false; + } + + public override void OnPet(PlayerControl pc) + { + if (pc == null || pc.GetAbilityUseLimit() < 1) return; + + Disperse(pc); + } + + private static void Disperse(PlayerControl player) + { + player.RpcRemoveAbilityUse(); + + foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { - if (pc == null || pc.GetAbilityUseLimit() < 1) return false; - - Disperse(pc); - - return false; - } - - public override void OnPet(PlayerControl pc) - { - if (pc == null || pc.GetAbilityUseLimit() < 1) return; - - Disperse(pc); - } - - private static void Disperse(PlayerControl player) - { - player.RpcRemoveAbilityUse(); - - foreach (PlayerControl pc in PlayerControl.AllPlayerControls) + if (player.PlayerId == pc.PlayerId || pc.Data.IsDead || pc.onLadder || pc.inMovingPlat || pc.inVent || GameStates.IsMeeting) { - if (player.PlayerId == pc.PlayerId || pc.Data.IsDead || pc.onLadder || pc.inMovingPlat || pc.inVent || GameStates.IsMeeting) - { - if (!pc.Is(CustomRoles.Disperser)) pc.Notify(ColorString(GetRoleColor(CustomRoles.Disperser), string.Format(GetString("ErrorTeleport"), pc.GetRealName()))); + if (!pc.Is(CustomRoles.Disperser)) pc.Notify(ColorString(GetRoleColor(CustomRoles.Disperser), string.Format(GetString("ErrorTeleport"), pc.GetRealName()))); - continue; - } - - pc.RPCPlayCustomSound("Teleport"); - pc.TPToRandomVent(); - pc.Notify(ColorString(GetRoleColor(CustomRoles.Disperser), string.Format(GetString("TeleportedInRndVentByDisperser"), pc.GetRealName()))); + continue; } + + pc.RPCPlayCustomSound("Teleport"); + pc.TPToRandomVent(); + pc.Notify(ColorString(GetRoleColor(CustomRoles.Disperser), string.Format(GetString("TeleportedInRndVentByDisperser"), pc.GetRealName()))); } + } - public override void SetButtonTexts(HudManager __instance, byte id) + public override void SetButtonTexts(HudManager __instance, byte id) + { + __instance.AbilityButton.ToggleVisible(GetPlayerById(id).IsAlive()); + + if (UsePets.GetBool()) + __instance.PetButton?.OverrideText(GetString("DisperserVentButtonText")); + else { - __instance.AbilityButton.ToggleVisible(GetPlayerById(id).IsAlive()); - - if (UsePets.GetBool()) - __instance.PetButton?.OverrideText(GetString("DisperserVentButtonText")); - else - { - __instance.AbilityButton?.OverrideText(GetString("DisperserVentButtonText")); - __instance.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); - } + __instance.AbilityButton?.OverrideText(GetString("DisperserVentButtonText")); + __instance.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); } } } \ No newline at end of file diff --git a/Roles/Impostor/Duellist.cs b/Roles/Impostor/Duellist.cs index adf4f4311..b4d7af7ec 100644 --- a/Roles/Impostor/Duellist.cs +++ b/Roles/Impostor/Duellist.cs @@ -6,94 +6,93 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Duellist : RoleBase { - public class Duellist : RoleBase + private const int Id = 642850; + private static List PlayerIdList = []; + private static Dictionary DuelPair = []; + private static OptionItem SSCD; + + private int Count; + + public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + + public override void SetupCustomOption() { - private const int Id = 642850; - private static List PlayerIdList = []; - private static Dictionary DuelPair = []; - private static OptionItem SSCD; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Duellist); - private int Count; + SSCD = new FloatOptionItem(Id + 5, "ShapeshiftCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Duellist]) + .SetValueFormat(OptionFormat.Seconds); + } - public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + public override void Init() + { + PlayerIdList = []; + DuelPair = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); + } + + public override bool OnShapeshift(PlayerControl duellist, PlayerControl target, bool shapeshifting) + { + if (!IsEnable) return false; + + if (duellist == null || target == null) return false; + + Vector2 pos = Pelican.GetBlackRoomPS(); + + if (target.TP(pos)) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Duellist); + if (Main.KillTimers[duellist.PlayerId] < 1f) duellist.SetKillCooldown(1f); // Give the other player a chance to kill - SSCD = new FloatOptionItem(Id + 5, "ShapeshiftCooldown", new(0f, 60f, 2.5f), 15f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Duellist]) - .SetValueFormat(OptionFormat.Seconds); + duellist.TP(pos); + DuelPair[duellist.PlayerId] = target.PlayerId; } + else + duellist.Notify(GetString("TargetCannotBeTeleported")); - public override void Init() + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (DuelPair.Count == 0) return; + + if (pc.IsAlive() && Count++ < 40) return; + + Count = 0; + + foreach (KeyValuePair pair in DuelPair) { - PlayerIdList = []; - DuelPair = []; - } + PlayerControl duellist = GetPlayerById(pair.Key); + PlayerControl target = GetPlayerById(pair.Value); + bool DAlive = duellist.IsAlive(); + bool TAlive = target.IsAlive(); - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); - } - - public override bool OnShapeshift(PlayerControl duellist, PlayerControl target, bool shapeshifting) - { - if (!IsEnable) return false; - - if (duellist == null || target == null) return false; - - Vector2 pos = Pelican.GetBlackRoomPS(); - - if (target.TP(pos)) + switch (DAlive) { - if (Main.KillTimers[duellist.PlayerId] < 1f) duellist.SetKillCooldown(1f); // Give the other player a chance to kill - - duellist.TP(pos); - DuelPair[duellist.PlayerId] = target.PlayerId; - } - else - duellist.Notify(GetString("TargetCannotBeTeleported")); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (DuelPair.Count == 0) return; - - if (pc.IsAlive() && Count++ < 40) return; - - Count = 0; - - foreach (KeyValuePair pair in DuelPair) - { - PlayerControl duellist = GetPlayerById(pair.Key); - PlayerControl target = GetPlayerById(pair.Value); - bool DAlive = duellist.IsAlive(); - bool TAlive = target.IsAlive(); - - switch (DAlive) - { - case false when !TAlive: - DuelPair.Remove(pair.Key); - break; - case true when !TAlive: - DuelPair.Remove(pair.Key); - LateTask.New(() => duellist.TPToRandomVent(), 0.5f, log: false); - break; - case false: - DuelPair.Remove(pair.Key); - LateTask.New(() => target.TPToRandomVent(), 0.5f, log: false); - break; - } + case false when !TAlive: + DuelPair.Remove(pair.Key); + break; + case true when !TAlive: + DuelPair.Remove(pair.Key); + LateTask.New(() => duellist.TPToRandomVent(), 0.5f, log: false); + break; + case false: + DuelPair.Remove(pair.Key); + LateTask.New(() => target.TPToRandomVent(), 0.5f, log: false); + break; } } } diff --git a/Roles/Impostor/Echo.cs b/Roles/Impostor/Echo.cs index 99ba0ae6a..51e7b058f 100644 --- a/Roles/Impostor/Echo.cs +++ b/Roles/Impostor/Echo.cs @@ -2,143 +2,142 @@ using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Echo : RoleBase { - public class Echo : RoleBase + public static bool On; + public static List Instances = []; + + private static OptionItem ShapeshiftCooldown; + private static OptionItem ShapeshiftDuration; + private static OptionItem DisableVentingWhileControlling; + + public PlayerControl EchoPC; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public static List Instances = []; + var id = 647600; + const TabGroup tab = TabGroup.ImpostorRoles; + const CustomRoles role = CustomRoles.Echo; + Options.SetupRoleOptions(id++, tab, role); + StringOptionItem parent = Options.CustomRoleSpawnChances[role]; - private static OptionItem ShapeshiftCooldown; - private static OptionItem ShapeshiftDuration; - private static OptionItem DisableVentingWhileControlling; + ShapeshiftCooldown = new FloatOptionItem(++id, "ShapeshiftCooldown", new(0f, 180f, 0.5f), 30f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Seconds); - public PlayerControl EchoPC; - public override bool IsEnable => On; + ShapeshiftDuration = new FloatOptionItem(++id, "ShapeshiftDuration", new(0f, 180f, 0.5f), 10f, tab) + .SetParent(parent) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + DisableVentingWhileControlling = new BooleanOptionItem(++id, "Echo.DisableVentingWhileControlling", true, tab) + .SetParent(parent); + } + + public override void Init() + { + Instances = []; + On = false; + } + + public override void Add(byte playerId) + { + On = true; + EchoPC = Utils.GetPlayerById(playerId); + Instances.Add(this); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + bool shifted = id.IsPlayerShifted(); + string text = !shifted ? "EchoShapeshiftButtonText" : "KillButtonText"; + hud.AbilityButton.OverrideText(Translator.GetString(text)); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat(); + AURoleOptions.ShapeshifterLeaveSkin = false; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return !DisableVentingWhileControlling.GetBool() || !pc.IsShifted(); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifting) { - var id = 647600; - const TabGroup tab = TabGroup.ImpostorRoles; - const CustomRoles role = CustomRoles.Echo; - Options.SetupRoleOptions(id++, tab, role); - StringOptionItem parent = Options.CustomRoleSpawnChances[role]; + Vector2 pos = target.Pos(); + if (!shapeshifter.RpcCheckAndMurder(target, true) || !target.TP(shapeshifter)) return false; - ShapeshiftCooldown = new FloatOptionItem(++id, "ShapeshiftCooldown", new(0f, 180f, 0.5f), 30f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Seconds); + target.RpcShapeshift(shapeshifter, false); + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); + shapeshifter.TP(pos); - ShapeshiftDuration = new FloatOptionItem(++id, "ShapeshiftDuration", new(0f, 180f, 0.5f), 10f, tab) - .SetParent(parent) - .SetValueFormat(OptionFormat.Seconds); + if (target.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); + } + else + { + target = Utils.GetPlayerById(shapeshifter.shapeshiftTargetPlayerId); + if (target == null) return true; - DisableVentingWhileControlling = new BooleanOptionItem(++id, "Echo.DisableVentingWhileControlling", true, tab) - .SetParent(parent); + RevertSwap(shapeshifter, target); + LateTask.New(() => target.Suicide(PlayerState.DeathReason.Kill, shapeshifter), 0.2f, "Echo Unshift Kill"); } - public override void Init() + return true; + } + + private static void RevertSwap(PlayerControl echo, PlayerControl target) + { + target.RpcShapeshift(target, false); + Vector2 pos = echo.Pos(); + echo.TP(target); + target.TP(pos); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (!target.IsShifted()) return true; + + PlayerControl ssTarget = Utils.GetPlayerById(target.shapeshiftTargetPlayerId); + if (ssTarget == null || !killer.RpcCheckAndMurder(ssTarget, true)) return true; + + RevertSwap(target, ssTarget); + LateTask.New(() => killer.Kill(ssTarget), 0.2f, log: false); + return false; + } + + public void OnTargetCheckMurder(PlayerControl killer, PlayerControl target) + { + RevertSwap(EchoPC, target); + + LateTask.New(() => { - Instances = []; - On = false; - } + killer.RpcCheckAndMurder(EchoPC); + target.Suicide(PlayerState.DeathReason.Kill, EchoPC); + }, 0.2f, log: false); + } - public override void Add(byte playerId) - { - On = true; - EchoPC = Utils.GetPlayerById(playerId); - Instances.Add(this); - } + public override void OnReportDeadBody() + { + if (!EchoPC.IsShifted()) return; - public override void SetButtonTexts(HudManager hud, byte id) - { - bool shifted = id.IsPlayerShifted(); - string text = !shifted ? "EchoShapeshiftButtonText" : "KillButtonText"; - hud.AbilityButton.OverrideText(Translator.GetString(text)); - } + PlayerControl ssTarget = ((byte)EchoPC.shapeshiftTargetPlayerId).GetPlayer(); + if (ssTarget == null || !ssTarget.IsAlive()) return; - public override bool CanUseKillButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat(); - AURoleOptions.ShapeshifterLeaveSkin = false; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return !DisableVentingWhileControlling.GetBool() || !pc.IsShifted(); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifting) - { - Vector2 pos = target.Pos(); - if (!shapeshifter.RpcCheckAndMurder(target, true) || !target.TP(shapeshifter)) return false; - - target.RpcShapeshift(shapeshifter, false); - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; - target.MarkDirtySettings(); - shapeshifter.TP(pos); - - if (target.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - } - else - { - target = Utils.GetPlayerById(shapeshifter.shapeshiftTargetPlayerId); - if (target == null) return true; - - RevertSwap(shapeshifter, target); - LateTask.New(() => target.Suicide(PlayerState.DeathReason.Kill, shapeshifter), 0.2f, "Echo Unshift Kill"); - } - - return true; - } - - private static void RevertSwap(PlayerControl echo, PlayerControl target) - { - target.RpcShapeshift(target, false); - Vector2 pos = echo.Pos(); - echo.TP(target); - target.TP(pos); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (!target.IsShifted()) return true; - - PlayerControl ssTarget = Utils.GetPlayerById(target.shapeshiftTargetPlayerId); - if (ssTarget == null || !killer.RpcCheckAndMurder(ssTarget, true)) return true; - - RevertSwap(target, ssTarget); - LateTask.New(() => killer.Kill(ssTarget), 0.2f, log: false); - return false; - } - - public void OnTargetCheckMurder(PlayerControl killer, PlayerControl target) - { - RevertSwap(EchoPC, target); - - LateTask.New(() => - { - killer.RpcCheckAndMurder(EchoPC); - target.Suicide(PlayerState.DeathReason.Kill, EchoPC); - }, 0.2f, log: false); - } - - public override void OnReportDeadBody() - { - if (!EchoPC.IsShifted()) return; - - PlayerControl ssTarget = ((byte)EchoPC.shapeshiftTargetPlayerId).GetPlayer(); - if (ssTarget == null || !ssTarget.IsAlive()) return; - - ssTarget.Suicide(PlayerState.DeathReason.Kill, EchoPC); - } + ssTarget.Suicide(PlayerState.DeathReason.Kill, EchoPC); } } \ No newline at end of file diff --git a/Roles/Impostor/Eraser.cs b/Roles/Impostor/Eraser.cs index d37bcdded..289da68c4 100644 --- a/Roles/Impostor/Eraser.cs +++ b/Roles/Impostor/Eraser.cs @@ -2,154 +2,153 @@ using AmongUs.GameOptions; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Eraser : RoleBase { - internal class Eraser : RoleBase + private const int Id = 16800; + private static List PlayerIdList = []; + + private static readonly string[] EraseMode = + [ + "EKill", + "EVote" + ]; + + private static readonly string[] WhenTargetIsNeutralAction = + [ + "E2Block", + "E2Kill" + ]; + + private static OptionItem EraseLimitOpt; + private static OptionItem EraseMethod; + private static OptionItem WhenTargetIsNeutral; + public static OptionItem HideVote; + public static OptionItem CancelVote; + + private static List DidVote = []; + private static List PlayerToErase = []; + public static List ErasedPlayers = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 16800; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Eraser); - private static readonly string[] EraseMode = - [ - "EKill", - "EVote" - ]; + EraseMethod = new StringOptionItem(Id + 10, "EraseMethod", EraseMode, 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); - private static readonly string[] WhenTargetIsNeutralAction = - [ - "E2Block", - "E2Kill" - ]; + WhenTargetIsNeutral = new StringOptionItem(Id + 11, "WhenTargetIsNeutral", WhenTargetIsNeutralAction, 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); - private static OptionItem EraseLimitOpt; - private static OptionItem EraseMethod; - private static OptionItem WhenTargetIsNeutral; - public static OptionItem HideVote; - public static OptionItem CancelVote; + EraseLimitOpt = new IntegerOptionItem(Id + 12, "EraseLimit", new(1, 15, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]) + .SetValueFormat(OptionFormat.Times); - private static List DidVote = []; - private static List PlayerToErase = []; - public static List ErasedPlayers = []; + HideVote = new BooleanOptionItem(Id + 13, "EraserHideVote", false, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); - public override bool IsEnable => PlayerIdList.Count > 0; + CancelVote = Options.CreateVoteCancellingUseSetting(Id + 14, CustomRoles.Eraser, TabGroup.ImpostorRoles); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + ErasedPlayers = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(EraseLimitOpt.GetInt()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return true; + + if (target.PlayerId == killer.PlayerId) return true; + + if (target.GetCustomRole().IsNeutral() && EraseMethod.GetInt() == 0) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Eraser); + killer.Notify(GetString("EraserEraseNeutralNotice")); + if (WhenTargetIsNeutral.GetInt() == 1) return true; - EraseMethod = new StringOptionItem(Id + 10, "EraseMethod", EraseMode, 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); - - WhenTargetIsNeutral = new StringOptionItem(Id + 11, "WhenTargetIsNeutral", WhenTargetIsNeutralAction, 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); - - EraseLimitOpt = new IntegerOptionItem(Id + 12, "EraseLimit", new(1, 15, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]) - .SetValueFormat(OptionFormat.Times); - - HideVote = new BooleanOptionItem(Id + 13, "EraserHideVote", false, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Eraser]); - - CancelVote = Options.CreateVoteCancellingUseSetting(Id + 14, CustomRoles.Eraser, TabGroup.ImpostorRoles); + killer.SetKillCooldown(5f); + return false; } - public override void Init() + if (EraseMethod.GetInt() == 0) { - PlayerIdList = []; - ErasedPlayers = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(EraseLimitOpt.GetInt()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return true; - - if (target.PlayerId == killer.PlayerId) return true; - - if (target.GetCustomRole().IsNeutral() && EraseMethod.GetInt() == 0) + return killer.CheckDoubleTrigger(target, () => { - killer.Notify(GetString("EraserEraseNeutralNotice")); - if (WhenTargetIsNeutral.GetInt() == 1) return true; - - killer.SetKillCooldown(5f); - return false; - } - - if (EraseMethod.GetInt() == 0) - { - return killer.CheckDoubleTrigger(target, () => - { - killer.RpcRemoveAbilityUse(); - killer.SetKillCooldown(); - killer.Notify(GetString("TargetErasedInRound")); - if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId); - }); - } - - return true; + killer.RpcRemoveAbilityUse(); + killer.SetKillCooldown(); + killer.Notify(GetString("TargetErasedInRound")); + if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId); + }); } - public override bool OnVote(PlayerControl player, PlayerControl target) + return true; + } + + public override bool OnVote(PlayerControl player, PlayerControl target) + { + if (player == null || target == null || EraseMethod.GetInt() == 0) return false; + + if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; + + DidVote.Add(player.PlayerId); + + if (player.GetAbilityUseLimit() < 1) return false; + + if (target.PlayerId == player.PlayerId) { - if (player == null || target == null || EraseMethod.GetInt() == 0) return false; - - if (DidVote.Contains(player.PlayerId) || Main.DontCancelVoteList.Contains(player.PlayerId)) return false; - - DidVote.Add(player.PlayerId); - - if (player.GetAbilityUseLimit() < 1) return false; - - if (target.PlayerId == player.PlayerId) - { - Utils.SendMessage(GetString("EraserEraseSelf"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); - return false; - } - - if (target.GetCustomRole().IsNeutral()) - { - Utils.SendMessage(string.Format(GetString("EraserEraseNeutralNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); - return false; - } - - player.RpcRemoveAbilityUse(); - - if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId); - - Utils.SendMessage(string.Format(GetString("EraserEraseNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); - - if (GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: target); - - Main.DontCancelVoteList.Add(player.PlayerId); - return true; + Utils.SendMessage(GetString("EraserEraseSelf"), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); + return false; } - public override void OnReportDeadBody() + if (target.GetCustomRole().IsNeutral()) { - DidVote = []; + Utils.SendMessage(string.Format(GetString("EraserEraseNeutralNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); + return false; } - public override void AfterMeetingTasks() + player.RpcRemoveAbilityUse(); + + if (!PlayerToErase.Contains(target.PlayerId)) PlayerToErase.Add(target.PlayerId); + + Utils.SendMessage(string.Format(GetString("EraserEraseNotice"), target.GetRealName()), player.PlayerId, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Eraser), GetString("EraserEraseMsgTitle"))); + + if (GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: target); + + Main.DontCancelVoteList.Add(player.PlayerId); + return true; + } + + public override void OnReportDeadBody() + { + DidVote = []; + } + + public override void AfterMeetingTasks() + { + foreach (byte id in PlayerToErase) { - foreach (byte id in PlayerToErase) - { - PlayerControl pc = Utils.GetPlayerById(id); - if (pc == null || !pc.IsAlive() || pc.Is(CustomRoles.Bloodlust)) continue; + PlayerControl pc = Utils.GetPlayerById(id); + if (pc == null || !pc.IsAlive() || pc.Is(CustomRoles.Bloodlust)) continue; - CustomRoles erasedRole = pc.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Noisemaker or RoleTypes.Scientist or RoleTypes.Tracker ? CustomRoles.CrewmateEHR : CustomRoles.ImpostorEHR; - pc.RpcSetCustomRole(erasedRole); - pc.RpcChangeRoleBasis(erasedRole); - pc.Notify(GetString("LostRoleByEraser")); - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} lost their role", "Eraser"); - pc.MarkDirtySettings(); - ErasedPlayers.Add(id); - } - - PlayerToErase = []; + CustomRoles erasedRole = pc.GetRoleTypes() is RoleTypes.Crewmate or RoleTypes.Engineer or RoleTypes.Noisemaker or RoleTypes.Scientist or RoleTypes.Tracker ? CustomRoles.CrewmateEHR : CustomRoles.ImpostorEHR; + pc.RpcSetCustomRole(erasedRole); + pc.RpcChangeRoleBasis(erasedRole); + pc.Notify(GetString("LostRoleByEraser")); + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} lost their role", "Eraser"); + pc.MarkDirtySettings(); + ErasedPlayers.Add(id); } + + PlayerToErase = []; } } \ No newline at end of file diff --git a/Roles/Impostor/Escapee.cs b/Roles/Impostor/Escapee.cs index 7e34d5962..e40b7036b 100644 --- a/Roles/Impostor/Escapee.cs +++ b/Roles/Impostor/Escapee.cs @@ -1,85 +1,84 @@ using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Escapee : RoleBase { - internal class Escapee : RoleBase + public static bool On; + public Vector2? EscapeeLocation; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public Vector2? EscapeeLocation; - public override bool IsEnable => On; + Options.SetupRoleOptions(3600, TabGroup.ImpostorRoles, CustomRoles.Escapee); - public override void SetupCustomOption() + Options.EscapeeSSCD = new FloatOptionItem(3611, "ShapeshiftCooldown", new(1f, 180f, 1f), 5f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escapee]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + EscapeeLocation = null; + } + + public override void Init() + { + On = false; + EscapeeLocation = null; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (Options.UsePets.GetBool()) + hud.PetButton?.OverrideText(Translator.GetString("EscapeeAbilityButtonText")); + else + hud.AbilityButton?.OverrideText(Translator.GetString("EscapeeAbilityButtonText")); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (Options.UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = Options.EscapeeSSCD.GetFloat(); + else { - Options.SetupRoleOptions(3600, TabGroup.ImpostorRoles, CustomRoles.Escapee); + if (Options.UsePets.GetBool()) return; - Options.EscapeeSSCD = new FloatOptionItem(3611, "ShapeshiftCooldown", new(1f, 180f, 1f), 5f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Escapee]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - EscapeeLocation = null; - } - - public override void Init() - { - On = false; - EscapeeLocation = null; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (Options.UsePets.GetBool()) - hud.PetButton?.OverrideText(Translator.GetString("EscapeeAbilityButtonText")); - else - hud.AbilityButton?.OverrideText(Translator.GetString("EscapeeAbilityButtonText")); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (Options.UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = Options.EscapeeSSCD.GetFloat(); - else - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = Options.EscapeeSSCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - - public override void OnPet(PlayerControl pc) - { - TeleportOrMark(pc); - } - - public override bool OnVanish(PlayerControl pc) - { - TeleportOrMark(pc); - return false; - } - - private void TeleportOrMark(PlayerControl pc) - { - if (EscapeeLocation != null) - { - var position = (Vector2)EscapeeLocation; - EscapeeLocation = null; - pc.TP(position); - pc.RPCPlayCustomSound("Teleport"); - } - else - EscapeeLocation = pc.Pos(); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifting || Options.UseUnshiftTrigger.GetBool()) TeleportOrMark(shapeshifter); - - return false; + AURoleOptions.ShapeshifterCooldown = Options.EscapeeSSCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; } } + + public override void OnPet(PlayerControl pc) + { + TeleportOrMark(pc); + } + + public override bool OnVanish(PlayerControl pc) + { + TeleportOrMark(pc); + return false; + } + + private void TeleportOrMark(PlayerControl pc) + { + if (EscapeeLocation != null) + { + var position = (Vector2)EscapeeLocation; + EscapeeLocation = null; + pc.TP(position); + pc.RPCPlayCustomSound("Teleport"); + } + else + EscapeeLocation = pc.Pos(); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifting || Options.UseUnshiftTrigger.GetBool()) TeleportOrMark(shapeshifter); + + return false; + } } \ No newline at end of file diff --git a/Roles/Impostor/EvilDiviner.cs b/Roles/Impostor/EvilDiviner.cs index e8fba7fdf..3d5bd090a 100644 --- a/Roles/Impostor/EvilDiviner.cs +++ b/Roles/Impostor/EvilDiviner.cs @@ -5,130 +5,129 @@ using EHR.Neutral; using Hazel; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class EvilDiviner : RoleBase { - public class EvilDiviner : RoleBase + private const int Id = 2700; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem DivinationMaxCount; + public static OptionItem EdAbilityUseGainWithEachKill; + + private bool CanVent; + + private List DivinationTarget = []; + private bool HasImpVision; + private bool IsRitualist; + private float KCD; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 2700; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.EvilDiviner); - private static OptionItem KillCooldown; - private static OptionItem DivinationMaxCount; - public static OptionItem EdAbilityUseGainWithEachKill; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilDiviner]) + .SetValueFormat(OptionFormat.Seconds); - private bool CanVent; + DivinationMaxCount = new IntegerOptionItem(Id + 11, "DivinationMaxCount", new(0, 15, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilDiviner]) + .SetValueFormat(OptionFormat.Times); - private List DivinationTarget = []; - private bool HasImpVision; - private bool IsRitualist; - private float KCD; + EdAbilityUseGainWithEachKill = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilDiviner]) + .SetValueFormat(OptionFormat.Times); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + DivinationTarget = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + DivinationTarget = []; + + IsRitualist = Main.PlayerStates[playerId].MainRole == CustomRoles.Ritualist; + playerId.SetAbilityUseLimit(IsRitualist ? Ritualist.RitualMaxCount.GetInt() : DivinationMaxCount.GetInt()); + + if (IsRitualist) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.EvilDiviner); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilDiviner]) - .SetValueFormat(OptionFormat.Seconds); - - DivinationMaxCount = new IntegerOptionItem(Id + 11, "DivinationMaxCount", new(0, 15, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilDiviner]) - .SetValueFormat(OptionFormat.Times); - - EdAbilityUseGainWithEachKill = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilDiviner]) - .SetValueFormat(OptionFormat.Times); + KCD = Ritualist.KillCooldown.GetFloat(); + CanVent = Ritualist.CanVent.GetBool(); + HasImpVision = Ritualist.HasImpostorVision.GetBool(); } - - public override void Init() + else { - PlayerIdList = []; - DivinationTarget = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - DivinationTarget = []; - - IsRitualist = Main.PlayerStates[playerId].MainRole == CustomRoles.Ritualist; - playerId.SetAbilityUseLimit(IsRitualist ? Ritualist.RitualMaxCount.GetInt() : DivinationMaxCount.GetInt()); - - if (IsRitualist) - { - KCD = Ritualist.KillCooldown.GetFloat(); - CanVent = Ritualist.CanVent.GetBool(); - HasImpVision = Ritualist.HasImpostorVision.GetBool(); - } - else - { - KCD = KillCooldown.GetFloat(); - CanVent = true; - HasImpVision = true; - } - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpVision); - } - - private static void SendRPC(byte playerId, byte targetId) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetEvilDiviner, SendOption.Reliable); - writer.Write(playerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(byte targetId) - { - DivinationTarget.Add(targetId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KCD; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return !(killer.GetAbilityUseLimit() >= 1) || killer.CheckDoubleTrigger(target, () => { SetDivination(killer, target); }); - } - - public bool IsDivination(byte target) - { - return DivinationTarget.Contains(target); - } - - public void SetDivination(PlayerControl killer, PlayerControl target) - { - if (!IsDivination(target.PlayerId)) - { - killer.RpcRemoveAbilityUse(); - DivinationTarget.Add(target.PlayerId); - Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()}: Divination target → {target.GetNameWithRole().RemoveHtmlTags()} || Remaining: {killer.GetAbilityUseLimit()} uses", "EvilDiviner"); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - - SendRPC(killer.PlayerId, target.PlayerId); - killer.SetKillCooldown(); - } - } - - public override bool KnowRole(PlayerControl seer, PlayerControl target) - { - if (base.KnowRole(seer, target)) return true; - - return Main.PlayerStates[seer.PlayerId].Role is EvilDiviner ed && ed.DivinationTarget.Contains(target.PlayerId); + KCD = KillCooldown.GetFloat(); + CanVent = true; + HasImpVision = true; } } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpVision); + } + + private static void SendRPC(byte playerId, byte targetId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetEvilDiviner, SendOption.Reliable); + writer.Write(playerId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(byte targetId) + { + DivinationTarget.Add(targetId); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KCD; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return !(killer.GetAbilityUseLimit() >= 1) || killer.CheckDoubleTrigger(target, () => { SetDivination(killer, target); }); + } + + public bool IsDivination(byte target) + { + return DivinationTarget.Contains(target); + } + + public void SetDivination(PlayerControl killer, PlayerControl target) + { + if (!IsDivination(target.PlayerId)) + { + killer.RpcRemoveAbilityUse(); + DivinationTarget.Add(target.PlayerId); + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()}: Divination target → {target.GetNameWithRole().RemoveHtmlTags()} || Remaining: {killer.GetAbilityUseLimit()} uses", "EvilDiviner"); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + + SendRPC(killer.PlayerId, target.PlayerId); + killer.SetKillCooldown(); + } + } + + public override bool KnowRole(PlayerControl seer, PlayerControl target) + { + if (base.KnowRole(seer, target)) return true; + + return Main.PlayerStates[seer.PlayerId].Role is EvilDiviner ed && ed.DivinationTarget.Contains(target.PlayerId); + } } \ No newline at end of file diff --git a/Roles/Impostor/EvilGuesser.cs b/Roles/Impostor/EvilGuesser.cs index fa3d686b9..2a0d53396 100644 --- a/Roles/Impostor/EvilGuesser.cs +++ b/Roles/Impostor/EvilGuesser.cs @@ -1,33 +1,32 @@ using UnityEngine; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class EvilGuesser : RoleBase { - internal class EvilGuesser : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(1200, TabGroup.ImpostorRoles, CustomRoles.EvilGuesser); - public override void SetupCustomOption() - { - SetupRoleOptions(1200, TabGroup.ImpostorRoles, CustomRoles.EvilGuesser); + EGCanGuessTime = new IntegerOptionItem(1205, "GuesserCanGuessTimes", new(1, 15, 1), 15, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]) + .SetValueFormat(OptionFormat.Times); - EGCanGuessTime = new IntegerOptionItem(1205, "GuesserCanGuessTimes", new(1, 15, 1), 15, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]) - .SetValueFormat(OptionFormat.Times); + EGCanGuessImp = new BooleanOptionItem(1206, "EGCanGuessImp", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]); - EGCanGuessImp = new BooleanOptionItem(1206, "EGCanGuessImp", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]); + EGCanGuessAdt = new BooleanOptionItem(1207, "EGCanGuessAdt", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]); - EGCanGuessAdt = new BooleanOptionItem(1207, "EGCanGuessAdt", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]); - - EGTryHideMsg = new BooleanOptionItem(1209, "GuesserTryHideMsg", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]) - .SetColor(Color.green); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + EGTryHideMsg = new BooleanOptionItem(1209, "GuesserTryHideMsg", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilGuesser]) + .SetColor(Color.green); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Impostor/EvilTracker.cs b/Roles/Impostor/EvilTracker.cs index e03fac6a2..0c96425b0 100644 --- a/Roles/Impostor/EvilTracker.cs +++ b/Roles/Impostor/EvilTracker.cs @@ -9,241 +9,240 @@ using UnityEngine; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class EvilTracker : RoleBase { - public class EvilTracker : RoleBase + private const int Id = 500; + private static List PlayerIdList = []; + + private static OptionItem OptionCanSeeKillFlash; + private static OptionItem OptionTargetMode; + private static OptionItem OptionCanSeeLastRoomInMeeting; + + private static bool CanSeeKillFlash; + private static TargetMode CurrentTargetMode; + private static RoleTypes RoleTypes; + public static bool CanSeeLastRoomInMeeting; + + private static readonly string[] TargetModeText = + [ + "EvilTrackerTargetMode.Never", + "EvilTrackerTargetMode.OnceInGame", + "EvilTrackerTargetMode.EveryMeeting", + "EvilTrackerTargetMode.Always" + ]; + + public bool CanSetTarget; + + private byte EvilTrackerId; + public byte Target = byte.MaxValue; + private byte[] ImpostorsId => Main.AllAlivePlayerControls.Where(x => x.PlayerId != EvilTrackerId && x.Is(CustomRoleTypes.Impostor)).Select(x => x.PlayerId).ToArray(); + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 500; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.EvilTracker); - private static OptionItem OptionCanSeeKillFlash; - private static OptionItem OptionTargetMode; - private static OptionItem OptionCanSeeLastRoomInMeeting; + OptionCanSeeKillFlash = new BooleanOptionItem(Id + 10, "EvilTrackerCanSeeKillFlash", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilTracker]); - private static bool CanSeeKillFlash; - private static TargetMode CurrentTargetMode; - private static RoleTypes RoleTypes; - public static bool CanSeeLastRoomInMeeting; + OptionTargetMode = new StringOptionItem(Id + 11, "EvilTrackerTargetMode", TargetModeText, 2, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilTracker]); - private static readonly string[] TargetModeText = - [ - "EvilTrackerTargetMode.Never", - "EvilTrackerTargetMode.OnceInGame", - "EvilTrackerTargetMode.EveryMeeting", - "EvilTrackerTargetMode.Always" - ]; + OptionCanSeeLastRoomInMeeting = new BooleanOptionItem(Id + 12, "EvilTrackerCanSeeLastRoomInMeeting", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.EvilTracker]); + } - public bool CanSetTarget; + public override void Init() + { + PlayerIdList = []; + Target = byte.MaxValue; + CanSetTarget = false; + EvilTrackerId = byte.MaxValue; + } - private byte EvilTrackerId; - public byte Target = byte.MaxValue; - private byte[] ImpostorsId => Main.AllAlivePlayerControls.Where(x => x.PlayerId != EvilTrackerId && x.Is(CustomRoleTypes.Impostor)).Select(x => x.PlayerId).ToArray(); + public override void Add(byte playerId) + { + CanSeeKillFlash = OptionCanSeeKillFlash.GetBool(); + CurrentTargetMode = (TargetMode)OptionTargetMode.GetValue(); + RoleTypes = CurrentTargetMode == TargetMode.Never ? RoleTypes.Impostor : RoleTypes.Shapeshifter; + CanSeeLastRoomInMeeting = OptionCanSeeLastRoomInMeeting.GetBool(); - public override bool IsEnable => PlayerIdList.Count > 0; + PlayerIdList.Add(playerId); + Target = byte.MaxValue; + CanSetTarget = CurrentTargetMode != TargetMode.Never; + EvilTrackerId = playerId; - public override void SetupCustomOption() + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.EvilTracker); + foreach (byte id in ImpostorsId) TargetArrow.Add(playerId, id); + }, 3f, "Add Evil Tracker Arrows"); + } - OptionCanSeeKillFlash = new BooleanOptionItem(Id + 10, "EvilTrackerCanSeeKillFlash", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilTracker]); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (RoleTypes != RoleTypes.Shapeshifter) return; - OptionTargetMode = new StringOptionItem(Id + 11, "EvilTrackerTargetMode", TargetModeText, 2, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilTracker]); + AURoleOptions.ShapeshifterCooldown = CanTarget(playerId) ? 1f : 255f; + AURoleOptions.ShapeshifterDuration = 1f; + } - OptionCanSeeLastRoomInMeeting = new BooleanOptionItem(Id + 12, "EvilTrackerCanSeeLastRoomInMeeting", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.EvilTracker]); - } + public override void SetButtonTexts(HudManager __instance, byte playerId) + { + __instance.AbilityButton.ToggleVisible(CanTarget(playerId)); + __instance.AbilityButton.OverrideText(GetString("EvilTrackerChangeButtonText")); + } - public override void Init() + private bool CanTarget(byte playerId) + { + return !Main.PlayerStates[playerId].IsDead && CanSetTarget; + } + + public static bool IsTrackTarget(PlayerControl seer, PlayerControl target) + { + return Main.PlayerStates[seer.PlayerId].Role is EvilTracker et + && seer.IsAlive() && PlayerIdList.Contains(seer.PlayerId) + && target.IsAlive() && seer != target + && (target.Is(CustomRoleTypes.Impostor) || et.Target == target.PlayerId); + } + + public static bool KillFlashCheck(PlayerControl killer, PlayerControl target) + { + if (!CanSeeKillFlash) return false; + + PlayerControl realKiller = target.GetRealKiller() ?? killer; + return realKiller.Is(CustomRoleTypes.Impostor) && realKiller != target; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!CanTarget(shapeshifter.PlayerId) || !shapeshifting) return false; + + if (target == null || target.Is(CustomRoleTypes.Impostor)) return false; + + SetTarget(shapeshifter.PlayerId, target.PlayerId); + Logger.Info($"{shapeshifter.GetNameWithRole().RemoveHtmlTags()}'s target is now {target.GetNameWithRole().RemoveHtmlTags()}", "EvilTrackerTarget"); + shapeshifter.MarkDirtySettings(); + Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: shapeshifter); + + return false; + } + + public override void AfterMeetingTasks() + { + try { - PlayerIdList = []; - Target = byte.MaxValue; - CanSetTarget = false; - EvilTrackerId = byte.MaxValue; - } + if (CurrentTargetMode == TargetMode.EveryMeeting) SetTarget(); - public override void Add(byte playerId) - { - CanSeeKillFlash = OptionCanSeeKillFlash.GetBool(); - CurrentTargetMode = (TargetMode)OptionTargetMode.GetValue(); - RoleTypes = CurrentTargetMode == TargetMode.Never ? RoleTypes.Impostor : RoleTypes.Shapeshifter; - CanSeeLastRoomInMeeting = OptionCanSeeLastRoomInMeeting.GetBool(); - - PlayerIdList.Add(playerId); - Target = byte.MaxValue; - CanSetTarget = CurrentTargetMode != TargetMode.Never; - EvilTrackerId = playerId; - - LateTask.New(() => + foreach (byte playerId in PlayerIdList) { - foreach (byte id in ImpostorsId) TargetArrow.Add(playerId, id); - }, 3f, "Add Evil Tracker Arrows"); - } + PlayerControl pc = Utils.GetPlayerById(playerId); + PlayerControl target = Utils.GetPlayerById(Target); - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (RoleTypes != RoleTypes.Shapeshifter) return; - - AURoleOptions.ShapeshifterCooldown = CanTarget(playerId) ? 1f : 255f; - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override void SetButtonTexts(HudManager __instance, byte playerId) - { - __instance.AbilityButton.ToggleVisible(CanTarget(playerId)); - __instance.AbilityButton.OverrideText(GetString("EvilTrackerChangeButtonText")); - } - - private bool CanTarget(byte playerId) - { - return !Main.PlayerStates[playerId].IsDead && CanSetTarget; - } - - public static bool IsTrackTarget(PlayerControl seer, PlayerControl target) - { - return Main.PlayerStates[seer.PlayerId].Role is EvilTracker et - && seer.IsAlive() && PlayerIdList.Contains(seer.PlayerId) - && target.IsAlive() && seer != target - && (target.Is(CustomRoleTypes.Impostor) || et.Target == target.PlayerId); - } - - public static bool KillFlashCheck(PlayerControl killer, PlayerControl target) - { - if (!CanSeeKillFlash) return false; - - PlayerControl realKiller = target.GetRealKiller() ?? killer; - return realKiller.Is(CustomRoleTypes.Impostor) && realKiller != target; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!CanTarget(shapeshifter.PlayerId) || !shapeshifting) return false; - - if (target == null || target.Is(CustomRoleTypes.Impostor)) return false; - - SetTarget(shapeshifter.PlayerId, target.PlayerId); - Logger.Info($"{shapeshifter.GetNameWithRole().RemoveHtmlTags()}'s target is now {target.GetNameWithRole().RemoveHtmlTags()}", "EvilTrackerTarget"); - shapeshifter.MarkDirtySettings(); - Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: shapeshifter); - - return false; - } - - public override void AfterMeetingTasks() - { - try - { - if (CurrentTargetMode == TargetMode.EveryMeeting) SetTarget(); - - foreach (byte playerId in PlayerIdList) + try { - PlayerControl pc = Utils.GetPlayerById(playerId); - PlayerControl target = Utils.GetPlayerById(Target); - - try - { - if (!pc.IsAlive() || !target.IsAlive()) SetTarget(playerId); - } - catch (NullReferenceException) { } - - pc?.SyncSettings(); - pc?.RpcResetAbilityCooldown(); - target?.MarkDirtySettings(); + if (!pc.IsAlive() || !target.IsAlive()) SetTarget(playerId); } - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "EvilTracker.AfterMeetingTasks"); + catch (NullReferenceException) { } + + pc?.SyncSettings(); + pc?.RpcResetAbilityCooldown(); + target?.MarkDirtySettings(); } } - - private void SetTarget(byte trackerId = byte.MaxValue, byte targetId = byte.MaxValue) + catch (Exception ex) { - if (trackerId == byte.MaxValue) - CanSetTarget = true; - else if (targetId == byte.MaxValue) - Target = byte.MaxValue; - else - { - Target = targetId; - if (CurrentTargetMode != TargetMode.Always) CanSetTarget = false; - - TargetArrow.Add(trackerId, targetId); - } - - if (!AmongUsClient.Instance.AmHost) return; - - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetEvilTrackerTarget, SendOption.Reliable); - writer.Write(trackerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte trackerId = reader.ReadByte(); - byte targetId = reader.ReadByte(); - (Main.PlayerStates[trackerId].Role as EvilTracker)?.SetTarget(trackerId, targetId); - } - - public override string GetProgressText(byte playerId, bool comms) - { - return CanTarget(playerId) ? Utils.ColorString(Palette.ImpostorRed.ShadeColor(0.5f), "◁") : string.Empty; - } - - public static string GetTargetMark(PlayerControl seer, PlayerControl target) - { - return Main.PlayerStates[seer.PlayerId].Role is not EvilTracker et ? string.Empty : et.Target == target.PlayerId ? Utils.ColorString(Palette.ImpostorRed, "◀") : string.Empty; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!GameStates.IsInTask) return string.Empty; - - byte trackerId = target.PlayerId; - if (seer.PlayerId != trackerId || seer.PlayerId != EvilTrackerId) return string.Empty; - - byte[] imps = ImpostorsId; - var sb = new StringBuilder(80); - - if (imps.Length > 0) - { - sb.Append($""); - foreach (byte impostorId in imps) sb.Append(TargetArrow.GetArrows(target, impostorId)); - sb.Append(""); - } - - if (Target != byte.MaxValue) sb.Append(Utils.ColorString(Color.white, TargetArrow.GetArrows(target, Target))); - - return sb.ToString(); - } - - public static string GetArrowAndLastRoom(PlayerControl seer, PlayerControl target) - { - string text = Utils.ColorString(Palette.ImpostorRed, TargetArrow.GetArrows(seer, target.PlayerId)); - PlainShipRoom room = Main.PlayerStates[target.PlayerId].LastRoom; - - if (room == null) - text += Utils.ColorString(Color.gray, "@" + GetString("FailToTrack")); - else - text += Utils.ColorString(Palette.ImpostorRed, "@" + GetString(room.RoomId.ToString())); - - return text; - } - -#pragma warning disable IDE0079 // Remove unnecessary suppression - [SuppressMessage("ReSharper", "UnusedMember.Local")] -#pragma warning restore IDE0079 // Remove unnecessary suppression - private enum TargetMode - { - Never, - OnceInGame, - EveryMeeting, - Always + Logger.Error(ex.ToString(), "EvilTracker.AfterMeetingTasks"); } } + + private void SetTarget(byte trackerId = byte.MaxValue, byte targetId = byte.MaxValue) + { + if (trackerId == byte.MaxValue) + CanSetTarget = true; + else if (targetId == byte.MaxValue) + Target = byte.MaxValue; + else + { + Target = targetId; + if (CurrentTargetMode != TargetMode.Always) CanSetTarget = false; + + TargetArrow.Add(trackerId, targetId); + } + + if (!AmongUsClient.Instance.AmHost) return; + + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetEvilTrackerTarget, SendOption.Reliable); + writer.Write(trackerId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte trackerId = reader.ReadByte(); + byte targetId = reader.ReadByte(); + (Main.PlayerStates[trackerId].Role as EvilTracker)?.SetTarget(trackerId, targetId); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return CanTarget(playerId) ? Utils.ColorString(Palette.ImpostorRed.ShadeColor(0.5f), "◁") : string.Empty; + } + + public static string GetTargetMark(PlayerControl seer, PlayerControl target) + { + return Main.PlayerStates[seer.PlayerId].Role is not EvilTracker et ? string.Empty : et.Target == target.PlayerId ? Utils.ColorString(Palette.ImpostorRed, "◀") : string.Empty; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!GameStates.IsInTask) return string.Empty; + + byte trackerId = target.PlayerId; + if (seer.PlayerId != trackerId || seer.PlayerId != EvilTrackerId) return string.Empty; + + byte[] imps = ImpostorsId; + var sb = new StringBuilder(80); + + if (imps.Length > 0) + { + sb.Append($""); + foreach (byte impostorId in imps) sb.Append(TargetArrow.GetArrows(target, impostorId)); + sb.Append(""); + } + + if (Target != byte.MaxValue) sb.Append(Utils.ColorString(Color.white, TargetArrow.GetArrows(target, Target))); + + return sb.ToString(); + } + + public static string GetArrowAndLastRoom(PlayerControl seer, PlayerControl target) + { + string text = Utils.ColorString(Palette.ImpostorRed, TargetArrow.GetArrows(seer, target.PlayerId)); + PlainShipRoom room = Main.PlayerStates[target.PlayerId].LastRoom; + + if (room == null) + text += Utils.ColorString(Color.gray, "@" + GetString("FailToTrack")); + else + text += Utils.ColorString(Palette.ImpostorRed, "@" + GetString(room.RoomId.ToString())); + + return text; + } + +#pragma warning disable IDE0079 // Remove unnecessary suppression + [SuppressMessage("ReSharper", "UnusedMember.Local")] +#pragma warning restore IDE0079 // Remove unnecessary suppression + private enum TargetMode + { + Never, + OnceInGame, + EveryMeeting, + Always + } } \ No newline at end of file diff --git a/Roles/Impostor/FireWorks.cs b/Roles/Impostor/FireWorks.cs index 9bed85e75..75f11cf45 100644 --- a/Roles/Impostor/FireWorks.cs +++ b/Roles/Impostor/FireWorks.cs @@ -6,231 +6,230 @@ using Hazel; using UnityEngine; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class FireWorks : RoleBase { - public class FireWorks : RoleBase + [Flags] + public enum FireWorksState { - [Flags] - public enum FireWorksState + Initial = 1, + SettingFireWorks = 2, + WaitTime = 4, + ReadyFire = 8, + FireEnd = 16, + CanUseKill = Initial | FireEnd + } + + private const int Id = 2800; + private static OptionItem FireWorksCountOpt; + private static OptionItem FireWorksRadiusOpt; + private static OptionItem CanKill; + private static OptionItem KillCooldown; + private static OptionItem CanIgniteBeforePlacingAllFireworks; + + public static bool On; + private static int FireWorksCount = 1; + private static float FireWorksRadius = 1; + private List fireWorksPosition = []; + + public int nowFireWorksCount; + private FireWorksState state; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.FireWorks); + + FireWorksCountOpt = new IntegerOptionItem(Id + 10, "FireWorksMaxCount", new(1, 10, 1), 3, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]) + .SetValueFormat(OptionFormat.Pieces); + + FireWorksRadiusOpt = new FloatOptionItem(Id + 11, "FireWorksRadius", new(0.5f, 5f, 0.5f), 2f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]) + .SetValueFormat(OptionFormat.Multiplier); + + CanKill = new BooleanOptionItem(Id + 12, "CanKill", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]); + + KillCooldown = new FloatOptionItem(Id + 13, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]) + .SetValueFormat(OptionFormat.Seconds); + + CanIgniteBeforePlacingAllFireworks = new BooleanOptionItem(Id + 14, "CanIgniteBeforePlacingAllFireworks", false, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]); + } + + public override void Init() + { + On = false; + nowFireWorksCount = 0; + fireWorksPosition = []; + state = FireWorksState.Initial; + } + + public override void Add(byte playerId) + { + On = true; + FireWorksCount = FireWorksCountOpt.GetInt(); + FireWorksRadius = FireWorksRadiusOpt.GetFloat(); + nowFireWorksCount = FireWorksCount; + fireWorksPosition = []; + state = FireWorksState.Initial; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + private void SendRPC(byte playerId) + { + if (!On || !Utils.DoRPC) return; + + Logger.Info($"Player{playerId}:SendRPC", "FireWorks"); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SendFireWorksState, SendOption.Reliable); + writer.Write(playerId); + writer.Write(nowFireWorksCount); + writer.Write((int)state); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(int count, FireWorksState newState) + { + nowFireWorksCount = count; + state = newState; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + if (pc == null || pc.Data.IsDead) return false; + + try { - Initial = 1, - SettingFireWorks = 2, - WaitTime = 4, - ReadyFire = 8, - FireEnd = 16, - CanUseKill = Initial | FireEnd + return CanKill.GetBool() || (state & FireWorksState.CanUseKill) != 0; } - - private const int Id = 2800; - private static OptionItem FireWorksCountOpt; - private static OptionItem FireWorksRadiusOpt; - private static OptionItem CanKill; - private static OptionItem KillCooldown; - private static OptionItem CanIgniteBeforePlacingAllFireworks; - - public static bool On; - private static int FireWorksCount = 1; - private static float FireWorksRadius = 1; - private List fireWorksPosition = []; - - public int nowFireWorksCount; - private FireWorksState state; - - public override bool IsEnable => On; - - public override void SetupCustomOption() + catch { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.FireWorks); - - FireWorksCountOpt = new IntegerOptionItem(Id + 10, "FireWorksMaxCount", new(1, 10, 1), 3, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]) - .SetValueFormat(OptionFormat.Pieces); - - FireWorksRadiusOpt = new FloatOptionItem(Id + 11, "FireWorksRadius", new(0.5f, 5f, 0.5f), 2f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]) - .SetValueFormat(OptionFormat.Multiplier); - - CanKill = new BooleanOptionItem(Id + 12, "CanKill", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]); - - KillCooldown = new FloatOptionItem(Id + 13, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]) - .SetValueFormat(OptionFormat.Seconds); - - CanIgniteBeforePlacingAllFireworks = new BooleanOptionItem(Id + 14, "CanIgniteBeforePlacingAllFireworks", false, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.FireWorks]); - } - - public override void Init() - { - On = false; - nowFireWorksCount = 0; - fireWorksPosition = []; - state = FireWorksState.Initial; - } - - public override void Add(byte playerId) - { - On = true; - FireWorksCount = FireWorksCountOpt.GetInt(); - FireWorksRadius = FireWorksRadiusOpt.GetFloat(); - nowFireWorksCount = FireWorksCount; - fireWorksPosition = []; - state = FireWorksState.Initial; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - private void SendRPC(byte playerId) - { - if (!On || !Utils.DoRPC) return; - - Logger.Info($"Player{playerId}:SendRPC", "FireWorks"); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SendFireWorksState, SendOption.Reliable); - writer.Write(playerId); - writer.Write(nowFireWorksCount); - writer.Write((int)state); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(int count, FireWorksState newState) - { - nowFireWorksCount = count; - state = newState; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - if (pc == null || pc.Data.IsDead) return false; - - try - { - return CanKill.GetBool() || (state & FireWorksState.CanUseKill) != 0; - } - catch - { - return false; - } - } - - public override void OnPet(PlayerControl pc) - { - FireWorksState beforeState = state; - if (CanIgniteBeforePlacingAllFireworks.GetBool()) state = FireWorksState.ReadyFire; - - OnShapeshift(pc, null, true); - - if (beforeState == FireWorksState.ReadyFire) return; - - state = beforeState; - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl _, bool shapeshifting) - { - Logger.Info("FireWorks ShapeShift", "FireWorks"); - if (pc == null || pc.Data.IsDead || (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) || Pelican.IsEaten(pc.PlayerId)) return false; - - UseAbility(pc); - return false; } + } - public override bool OnVanish(PlayerControl pc) - { - Logger.Info("FireWorks Vanish", "FireWorks"); - if (pc == null || pc.Data.IsDead || Pelican.IsEaten(pc.PlayerId)) return false; + public override void OnPet(PlayerControl pc) + { + FireWorksState beforeState = state; + if (CanIgniteBeforePlacingAllFireworks.GetBool()) state = FireWorksState.ReadyFire; + + OnShapeshift(pc, null, true); + + if (beforeState == FireWorksState.ReadyFire) return; + + state = beforeState; + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl _, bool shapeshifting) + { + Logger.Info("FireWorks ShapeShift", "FireWorks"); + if (pc == null || pc.Data.IsDead || (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) || Pelican.IsEaten(pc.PlayerId)) return false; + + UseAbility(pc); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + Logger.Info("FireWorks Vanish", "FireWorks"); + if (pc == null || pc.Data.IsDead || Pelican.IsEaten(pc.PlayerId)) return false; - UseAbility(pc); + UseAbility(pc); - return false; - } + return false; + } - private void UseAbility(PlayerControl pc) + private void UseAbility(PlayerControl pc) + { + switch (state) { - switch (state) - { - case FireWorksState.Initial: - case FireWorksState.SettingFireWorks: - Logger.Info("Install Firework", "FireWorks"); - fireWorksPosition.Add(pc.Pos()); - nowFireWorksCount--; - - state = nowFireWorksCount == 0 - ? Main.AliveImpostorCount <= 1 ? FireWorksState.ReadyFire : FireWorksState.WaitTime - : FireWorksState.SettingFireWorks; - - break; - case FireWorksState.ReadyFire: - Logger.Info("Explode fireworks", "FireWorks"); - var suicide = false; - - foreach (PlayerControl target in Main.AllAlivePlayerControls) + case FireWorksState.Initial: + case FireWorksState.SettingFireWorks: + Logger.Info("Install Firework", "FireWorks"); + fireWorksPosition.Add(pc.Pos()); + nowFireWorksCount--; + + state = nowFireWorksCount == 0 + ? Main.AliveImpostorCount <= 1 ? FireWorksState.ReadyFire : FireWorksState.WaitTime + : FireWorksState.SettingFireWorks; + + break; + case FireWorksState.ReadyFire: + Logger.Info("Explode fireworks", "FireWorks"); + var suicide = false; + + foreach (PlayerControl target in Main.AllAlivePlayerControls) + { + foreach (Vector3 pos in fireWorksPosition) { - foreach (Vector3 pos in fireWorksPosition) - { - float dis = Vector2.Distance(pos, target.transform.position); - if (dis > FireWorksRadius) continue; - - if (target == pc) - suicide = true; - else - target.Suicide(PlayerState.DeathReason.Bombed, pc); - } - } + float dis = Vector2.Distance(pos, target.transform.position); + if (dis > FireWorksRadius) continue; - if (suicide) - { - int totalAlive = Main.AllAlivePlayerControls.Length; - if (totalAlive != 1) pc.Suicide(); + if (target == pc) + suicide = true; + else + target.Suicide(PlayerState.DeathReason.Bombed, pc); } + } - state = FireWorksState.FireEnd; - break; - } + if (suicide) + { + int totalAlive = Main.AllAlivePlayerControls.Length; + if (totalAlive != 1) pc.Suicide(); + } - SendRPC(pc.PlayerId); - Utils.NotifyRoles(ForceLoop: true); + state = FireWorksState.FireEnd; + break; } + + SendRPC(pc.PlayerId); + Utils.NotifyRoles(ForceLoop: true); + } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + var retText = string.Empty; + if (seer == null || seer.Data.IsDead || seer.PlayerId != target.PlayerId) return retText; + + if (Main.PlayerStates[seer.PlayerId].Role is not FireWorks fw) return retText; + + if (fw.state == FireWorksState.WaitTime && Main.AliveImpostorCount <= 1) { - var retText = string.Empty; - if (seer == null || seer.Data.IsDead || seer.PlayerId != target.PlayerId) return retText; - - if (Main.PlayerStates[seer.PlayerId].Role is not FireWorks fw) return retText; - - if (fw.state == FireWorksState.WaitTime && Main.AliveImpostorCount <= 1) - { - fw.state = FireWorksState.ReadyFire; - fw.SendRPC(seer.PlayerId); - Utils.NotifyRoles(SpecifySeer: seer, SpecifyTarget: seer); - } - - switch (fw.state) - { - case FireWorksState.Initial: - case FireWorksState.SettingFireWorks: - retText = string.Format(GetString("FireworksPutPhase"), fw.nowFireWorksCount); - break; - case FireWorksState.WaitTime: - retText = GetString("FireworksWaitPhase"); - break; - case FireWorksState.ReadyFire: - retText = GetString("FireworksReadyFirePhase"); - break; - case FireWorksState.FireEnd: - break; - } - - return retText; + fw.state = FireWorksState.ReadyFire; + fw.SendRPC(seer.PlayerId); + Utils.NotifyRoles(SpecifySeer: seer, SpecifyTarget: seer); } - public override void SetButtonTexts(HudManager hud, byte id) + switch (fw.state) { - hud.AbilityButton?.OverrideText(nowFireWorksCount == 0 ? GetString("FireWorksExplosionButtonText") : GetString("FireWorksInstallAtionButtonText")); + case FireWorksState.Initial: + case FireWorksState.SettingFireWorks: + retText = string.Format(GetString("FireworksPutPhase"), fw.nowFireWorksCount); + break; + case FireWorksState.WaitTime: + retText = GetString("FireworksWaitPhase"); + break; + case FireWorksState.ReadyFire: + retText = GetString("FireworksReadyFirePhase"); + break; + case FireWorksState.FireEnd: + break; } + + return retText; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(nowFireWorksCount == 0 ? GetString("FireWorksExplosionButtonText") : GetString("FireWorksInstallAtionButtonText")); } } \ No newline at end of file diff --git a/Roles/Impostor/Framer.cs b/Roles/Impostor/Framer.cs index 4a50ca178..ed93fb46a 100644 --- a/Roles/Impostor/Framer.cs +++ b/Roles/Impostor/Framer.cs @@ -1,53 +1,52 @@ using System.Collections.Generic; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Framer : RoleBase { - public class Framer : RoleBase + public static bool On; + public static readonly HashSet FramedPlayers = []; + + private static OptionItem AbilityUseLimit; + public static OptionItem FramerAbilityUseGainWithEachKill; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public static readonly HashSet FramedPlayers = []; + Options.SetupRoleOptions(647196, TabGroup.ImpostorRoles, CustomRoles.Framer); - private static OptionItem AbilityUseLimit; - public static OptionItem FramerAbilityUseGainWithEachKill; + AbilityUseLimit = new IntegerOptionItem(647198, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Framer]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => On; + FramerAbilityUseGainWithEachKill = new FloatOptionItem(647199, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.4f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Framer]) + .SetValueFormat(OptionFormat.Times); + } - public override void SetupCustomOption() + public override void Init() + { + On = false; + FramedPlayers.Clear(); + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1f) return true; + + return killer.CheckDoubleTrigger(target, () => { - Options.SetupRoleOptions(647196, TabGroup.ImpostorRoles, CustomRoles.Framer); - - AbilityUseLimit = new IntegerOptionItem(647198, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Framer]) - .SetValueFormat(OptionFormat.Times); - - FramerAbilityUseGainWithEachKill = new FloatOptionItem(647199, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.4f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Framer]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - On = false; - FramedPlayers.Clear(); - } - - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1f) return true; - - return killer.CheckDoubleTrigger(target, () => - { - FramedPlayers.Add(target.PlayerId); - killer.SetKillCooldown(); - killer.Notify(string.Format(Translator.GetString("Framer.TargetFramedNotify"), target.PlayerId.ColoredPlayerName())); - killer.RpcRemoveAbilityUse(); - }); - } + FramedPlayers.Add(target.PlayerId); + killer.SetKillCooldown(); + killer.Notify(string.Format(Translator.GetString("Framer.TargetFramedNotify"), target.PlayerId.ColoredPlayerName())); + killer.RpcRemoveAbilityUse(); + }); } } \ No newline at end of file diff --git a/Roles/Impostor/Freezer.cs b/Roles/Impostor/Freezer.cs index d9848293f..2f8d443a8 100644 --- a/Roles/Impostor/Freezer.cs +++ b/Roles/Impostor/Freezer.cs @@ -1,65 +1,64 @@ using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Freezer : RoleBase { - internal class Freezer : RoleBase + private const int Id = 643530; + public static bool On; + + private static OptionItem FreezeCooldown; + private static OptionItem FreezeDuration; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 643530; - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Freezer); - private static OptionItem FreezeCooldown; - private static OptionItem FreezeDuration; - public override bool IsEnable => On; + FreezeCooldown = new FloatOptionItem(Id + 2, "FreezeCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Freezer]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + FreezeDuration = new FloatOptionItem(Id + 3, "FreezeDuration", new(0f, 180f, 0.5f), 10f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Freezer]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = FreezeCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifting) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Freezer); + float beforeSpeed = Main.AllPlayerSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + target.MarkDirtySettings(); - FreezeCooldown = new FloatOptionItem(Id + 2, "FreezeCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Freezer]) - .SetValueFormat(OptionFormat.Seconds); - - FreezeDuration = new FloatOptionItem(Id + 3, "FreezeDuration", new(0f, 180f, 0.5f), 10f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Freezer]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = FreezeCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifting) + LateTask.New(() => { - float beforeSpeed = Main.AllPlayerSpeed[target.PlayerId]; - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + Main.AllPlayerSpeed[target.PlayerId] = beforeSpeed; target.MarkDirtySettings(); + }, FreezeDuration.GetFloat(), "FreezerFreezeDuration"); - LateTask.New(() => - { - Main.AllPlayerSpeed[target.PlayerId] = beforeSpeed; - target.MarkDirtySettings(); - }, FreezeDuration.GetFloat(), "FreezerFreezeDuration"); - - if (target.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - } - - return false; + if (target.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); } + + return false; } } \ No newline at end of file diff --git a/Roles/Impostor/Gambler.cs b/Roles/Impostor/Gambler.cs index a67c97c01..766030dbf 100644 --- a/Roles/Impostor/Gambler.cs +++ b/Roles/Impostor/Gambler.cs @@ -8,300 +8,299 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Gambler : RoleBase { - public class Gambler : RoleBase + private const int Id = 640700; + private static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem KillDelay; + private static OptionItem BsrDelay; + private static OptionItem TPDelay; + private static OptionItem ShieldDur; + private static OptionItem FreezeDur; + private static OptionItem LowVision; + private static OptionItem LowVisionDur; + private static OptionItem LowKCD; + private static OptionItem HighKCD; + private static OptionItem Speed; + private static OptionItem SpeedDur; + private static OptionItem WhatToIgnore; + private static OptionItem IgnoreMedicShield; + private static OptionItem IgnoreVeteranAlert; + private static OptionItem IgnoreCursedWolfAndJinx; + private static OptionItem IgnorePestilence; + private static OptionItem PositiveEffectChance; + + private static Dictionary WaitingDelayedKills = []; + public static readonly Dictionary IsShielded = []; + private static Dictionary IsSpeedChange = []; + private static Dictionary IsVisionChange = []; + + private byte EffectID = byte.MaxValue; + private bool isPositiveEffect; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640700; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Gambler); - private static OptionItem KillCooldown; - private static OptionItem KillDelay; - private static OptionItem BsrDelay; - private static OptionItem TPDelay; - private static OptionItem ShieldDur; - private static OptionItem FreezeDur; - private static OptionItem LowVision; - private static OptionItem LowVisionDur; - private static OptionItem LowKCD; - private static OptionItem HighKCD; - private static OptionItem Speed; - private static OptionItem SpeedDur; - private static OptionItem WhatToIgnore; - private static OptionItem IgnoreMedicShield; - private static OptionItem IgnoreVeteranAlert; - private static OptionItem IgnoreCursedWolfAndJinx; - private static OptionItem IgnorePestilence; - private static OptionItem PositiveEffectChance; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); - private static Dictionary WaitingDelayedKills = []; - public static readonly Dictionary IsShielded = []; - private static Dictionary IsSpeedChange = []; - private static Dictionary IsVisionChange = []; + KillDelay = new IntegerOptionItem(Id + 11, "GamblerKillDelay", new(0, 10, 1), 3, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); - private byte EffectID = byte.MaxValue; - private bool isPositiveEffect; + ShieldDur = new IntegerOptionItem(Id + 12, "GamblerShieldDur", new(1, 30, 1), 15, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + FreezeDur = new IntegerOptionItem(Id + 13, "GamblerFreezeDur", new(1, 10, 1), 3, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + LowVision = new FloatOptionItem(Id + 14, "GamblerLowVision", new(0f, 1f, 0.05f), 0.7f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Multiplier); + + LowVisionDur = new IntegerOptionItem(Id + 15, "GamblerLowVisionDur", new(1, 30, 1), 10, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); + + Speed = new FloatOptionItem(Id + 16, "GamblerSpeedup", new(0.1f, 3f, 0.05f), 1.5f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Multiplier); + + SpeedDur = new IntegerOptionItem(Id + 17, "GamblerSpeedupDur", new(1, 20, 1), 5, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); + + BsrDelay = new IntegerOptionItem(Id + 18, "GamblerBSRDelay", new(0, 10, 1), 2, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); + + HighKCD = new FloatOptionItem(Id + 19, "GamblerHighKCD", new(10f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); + + LowKCD = new FloatOptionItem(Id + 20, "GamblerLowKCD", new(10f, 60f, 2.5f), 17.5f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); + + TPDelay = new IntegerOptionItem(Id + 21, "GamblerTPDelay", new(0, 10, 1), 2, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Seconds); + + WhatToIgnore = new BooleanOptionItem(Id + 22, "GamblerWhatToIgnore", true, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]); + IgnoreMedicShield = new BooleanOptionItem(Id + 23, "GamblerIgnoreMedicShield", true, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); + IgnoreCursedWolfAndJinx = new BooleanOptionItem(Id + 24, "GamblerIgnoreCursedWolfAndJinx", true, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); + IgnoreVeteranAlert = new BooleanOptionItem(Id + 25, "GamblerIgnoreVeteranAlert", false, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); + IgnorePestilence = new BooleanOptionItem(Id + 26, "GamblerIgnorePestilence", false, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); + + PositiveEffectChance = new IntegerOptionItem(Id + 27, "GamblerPositiveEffectChance", new(0, 100, 5), 70, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) + .SetValueFormat(OptionFormat.Percent); + } + + public override void Init() + { + PlayerIdList = []; + EffectID = byte.MaxValue; + WaitingDelayedKills = []; + IsSpeedChange = []; + IsVisionChange = []; + isPositiveEffect = true; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + EffectID = byte.MaxValue; + isPositiveEffect = true; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (IsVisionChange.ContainsKey(playerId)) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Gambler); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - KillDelay = new IntegerOptionItem(Id + 11, "GamblerKillDelay", new(0, 10, 1), 3, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - ShieldDur = new IntegerOptionItem(Id + 12, "GamblerShieldDur", new(1, 30, 1), 15, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - FreezeDur = new IntegerOptionItem(Id + 13, "GamblerFreezeDur", new(1, 10, 1), 3, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - LowVision = new FloatOptionItem(Id + 14, "GamblerLowVision", new(0f, 1f, 0.05f), 0.7f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Multiplier); - - LowVisionDur = new IntegerOptionItem(Id + 15, "GamblerLowVisionDur", new(1, 30, 1), 10, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - Speed = new FloatOptionItem(Id + 16, "GamblerSpeedup", new(0.1f, 3f, 0.05f), 1.5f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Multiplier); - - SpeedDur = new IntegerOptionItem(Id + 17, "GamblerSpeedupDur", new(1, 20, 1), 5, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - BsrDelay = new IntegerOptionItem(Id + 18, "GamblerBSRDelay", new(0, 10, 1), 2, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - HighKCD = new FloatOptionItem(Id + 19, "GamblerHighKCD", new(10f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - LowKCD = new FloatOptionItem(Id + 20, "GamblerLowKCD", new(10f, 60f, 2.5f), 17.5f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - TPDelay = new IntegerOptionItem(Id + 21, "GamblerTPDelay", new(0, 10, 1), 2, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Seconds); - - WhatToIgnore = new BooleanOptionItem(Id + 22, "GamblerWhatToIgnore", true, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]); - IgnoreMedicShield = new BooleanOptionItem(Id + 23, "GamblerIgnoreMedicShield", true, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); - IgnoreCursedWolfAndJinx = new BooleanOptionItem(Id + 24, "GamblerIgnoreCursedWolfAndJinx", true, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); - IgnoreVeteranAlert = new BooleanOptionItem(Id + 25, "GamblerIgnoreVeteranAlert", false, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); - IgnorePestilence = new BooleanOptionItem(Id + 26, "GamblerIgnorePestilence", false, TabGroup.ImpostorRoles).SetParent(WhatToIgnore); - - PositiveEffectChance = new IntegerOptionItem(Id + 27, "GamblerPositiveEffectChance", new(0, 100, 5), 70, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gambler]) - .SetValueFormat(OptionFormat.Percent); - } - - public override void Init() - { - PlayerIdList = []; - EffectID = byte.MaxValue; - WaitingDelayedKills = []; - IsSpeedChange = []; - IsVisionChange = []; - isPositiveEffect = true; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - EffectID = byte.MaxValue; - isPositiveEffect = true; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (IsVisionChange.ContainsKey(playerId)) - { - opt.SetFloat(FloatOptionNames.CrewLightMod, LowVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, LowVision.GetFloat()); - } - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return false; - - if (target == null) return false; - - if (EffectID != byte.MaxValue) return true; - - var rd = IRandom.Instance; - isPositiveEffect = rd.Next(1, 101) <= PositiveEffectChance.GetInt(); - - if (isPositiveEffect) - { - EffectID = (byte)rd.Next(1, 8); - - switch (EffectID) - { - case 1: // Delayed kill - killer.Notify(string.Format(GetString("GamblerGet.DelayedKill"), KillDelay.GetInt())); - WaitingDelayedKills.TryAdd(target.PlayerId, TimeStamp); - return false; - case 2: // Shield - killer.Notify(string.Format(GetString("GamblerGet.Shield"), ShieldDur.GetInt())); - IsShielded.TryAdd(killer.PlayerId, TimeStamp); - break; - case 3: // No lunge (Swift kill) - killer.Notify(GetString("GamblerGet.NoLunge")); - if (killer.RpcCheckAndMurder(target, true)) target.Kill(target); - - return false; - case 4: // Swap with random player - killer.Notify(GetString("GamblerGet.Swap")); - - LateTask.New(() => - { - if (GameStates.IsInTask && killer.IsAlive()) - { - PlayerControl[] list = Main.AllAlivePlayerControls.Where(a => !Pelican.IsEaten(a.PlayerId) && !a.inVent && a.PlayerId != killer.PlayerId).ToArray(); - TP(killer.NetTransform, list.RandomElement().Pos()); - } - }, TPDelay.GetInt(), "Gambler Swap"); - - break; - case 5: // Ignore defense - killer.Notify(GetString("GamblerGet.IgnoreDefense")); - - if ((target.Is(CustomRoles.Pestilence) && IgnorePestilence.GetBool()) - || (Veteran.VeteranInProtect.ContainsKey(target.PlayerId) && IgnoreVeteranAlert.GetBool()) - || (Medic.InProtect(target.PlayerId) && IgnoreMedicShield.GetBool()) - || ((target.Is(CustomRoles.Jinx) || target.Is(CustomRoles.CursedWolf)) && IgnoreCursedWolfAndJinx.GetBool())) - { - killer.Kill(target); - return false; - } - - if ((target.Is(CustomRoles.Pestilence) && !IgnorePestilence.GetBool()) - || (Veteran.VeteranInProtect.ContainsKey(target.PlayerId) && !IgnoreVeteranAlert.GetBool()) - || (Medic.InProtect(target.PlayerId) && !IgnoreMedicShield.GetBool()) - || ((target.Is(CustomRoles.Jinx) || target.Is(CustomRoles.CursedWolf)) && !IgnoreCursedWolfAndJinx.GetBool())) - break; - - killer.RpcCheckAndMurder(target); - return false; - case 6: // Low KCD - killer.Notify(string.Format(GetString("GamblerGet.LowKCD"), LowKCD.GetFloat())); - LateTask.New(() => { killer.SetKillCooldown(LowKCD.GetFloat()); }, 0.1f, "Gambler SetLowKCD"); - break; - case 7: // Speed - killer.Notify(string.Format(GetString("GamblerGet.Speedup"), SpeedDur.GetInt(), Speed.GetFloat())); - IsSpeedChange.TryAdd(killer.PlayerId, (Main.AllPlayerSpeed[killer.PlayerId], TimeStamp)); - Main.AllPlayerSpeed[killer.PlayerId] = Speed.GetFloat(); - killer.MarkDirtySettings(); - break; - default: - Logger.Error("Invalid Effect ID (positive)", "Gambler.OnCheckMurder"); - break; - } - } - else - { - EffectID = (byte)rd.Next(1, 5); - - switch (EffectID) - { - case 1: // BSR - float delay = Math.Max(0.15f, BsrDelay.GetFloat()); - if (delay >= 1f) killer.Notify(string.Format(GetString("GamblerGet.BSR"), BsrDelay.GetInt())); - - LateTask.New(() => - { - if (GameStates.IsInTask) killer.CmdReportDeadBody(target.Data); - }, delay, "Gambler Self Report"); - - break; - case 2: // Freeze - killer.Notify(string.Format(GetString("GamblerGet.Freeze"), FreezeDur.GetInt())); - IsSpeedChange.TryAdd(killer.PlayerId, (Main.AllPlayerSpeed[killer.PlayerId], TimeStamp)); - Main.AllPlayerSpeed[killer.PlayerId] = Main.MinSpeed; - killer.MarkDirtySettings(); - break; - case 3: // Low vision - killer.Notify(string.Format(GetString("GamblerGet.LowVision"), LowVisionDur.GetInt(), LowVision.GetFloat())); - IsVisionChange.TryAdd(killer.PlayerId, TimeStamp); - killer.MarkDirtySettings(); - break; - case 4: // High KCD - killer.Notify(string.Format(GetString("GamblerGet.HighKCD"), HighKCD.GetFloat())); - LateTask.New(() => { killer.SetKillCooldown(HighKCD.GetFloat()); }, 0.1f, "Gambler SetHighKCD"); - break; - default: - Logger.Error("Invalid Effect ID (negative)", "Gambler.OnCheckMurder"); - break; - } - } - - EffectID = byte.MaxValue; - - return true; - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask || player == null || !player.Is(CustomRoles.Gambler) || (WaitingDelayedKills.Count == 0 && IsSpeedChange.Count == 0 && IsVisionChange.Count == 0 && IsShielded.Count == 0)) return; - - var sync = false; - - foreach (KeyValuePair x in WaitingDelayedKills) - { - PlayerControl pc = GetPlayerById(x.Key); - - if (!pc.IsAlive()) - { - WaitingDelayedKills.Remove(x.Key); - continue; - } - - if (x.Value + KillDelay.GetInt() < TimeStamp) - { - pc.Suicide(PlayerState.DeathReason.Poison, player); - WaitingDelayedKills.Remove(x.Key); - } - } - - if (IsSpeedChange.TryGetValue(player.PlayerId, out (float, long) p) && p.Item2 + SpeedDur.GetInt() < TimeStamp) - { - Main.AllPlayerSpeed[player.PlayerId] = p.Item1; - IsSpeedChange.Remove(player.PlayerId); - sync = true; - } - - if (IsVisionChange.TryGetValue(player.PlayerId, out long v) && v + LowVisionDur.GetInt() < TimeStamp) - { - IsVisionChange.Remove(player.PlayerId); - sync = true; - } - - if (IsShielded.TryGetValue(player.PlayerId, out long shielded) && shielded + ShieldDur.GetInt() < TimeStamp) IsShielded.Remove(player.PlayerId); - - if (sync) player.MarkDirtySettings(); - } - - public override void OnReportDeadBody() - { - EffectID = byte.MaxValue; - isPositiveEffect = true; - - foreach (byte playerId in WaitingDelayedKills.Keys.ToArray()) - { - PlayerControl pc = GetPlayerById(playerId); - if (pc.IsAlive()) pc.Kill(pc); - } - - WaitingDelayedKills.Clear(); - IsShielded.Clear(); - IsSpeedChange.Clear(); - IsVisionChange.Clear(); + opt.SetFloat(FloatOptionNames.CrewLightMod, LowVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, LowVision.GetFloat()); } } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + if (EffectID != byte.MaxValue) return true; + + var rd = IRandom.Instance; + isPositiveEffect = rd.Next(1, 101) <= PositiveEffectChance.GetInt(); + + if (isPositiveEffect) + { + EffectID = (byte)rd.Next(1, 8); + + switch (EffectID) + { + case 1: // Delayed kill + killer.Notify(string.Format(GetString("GamblerGet.DelayedKill"), KillDelay.GetInt())); + WaitingDelayedKills.TryAdd(target.PlayerId, TimeStamp); + return false; + case 2: // Shield + killer.Notify(string.Format(GetString("GamblerGet.Shield"), ShieldDur.GetInt())); + IsShielded.TryAdd(killer.PlayerId, TimeStamp); + break; + case 3: // No lunge (Swift kill) + killer.Notify(GetString("GamblerGet.NoLunge")); + if (killer.RpcCheckAndMurder(target, true)) target.Kill(target); + + return false; + case 4: // Swap with random player + killer.Notify(GetString("GamblerGet.Swap")); + + LateTask.New(() => + { + if (GameStates.IsInTask && killer.IsAlive()) + { + PlayerControl[] list = Main.AllAlivePlayerControls.Where(a => !Pelican.IsEaten(a.PlayerId) && !a.inVent && a.PlayerId != killer.PlayerId).ToArray(); + TP(killer.NetTransform, list.RandomElement().Pos()); + } + }, TPDelay.GetInt(), "Gambler Swap"); + + break; + case 5: // Ignore defense + killer.Notify(GetString("GamblerGet.IgnoreDefense")); + + if ((target.Is(CustomRoles.Pestilence) && IgnorePestilence.GetBool()) + || (Veteran.VeteranInProtect.ContainsKey(target.PlayerId) && IgnoreVeteranAlert.GetBool()) + || (Medic.InProtect(target.PlayerId) && IgnoreMedicShield.GetBool()) + || ((target.Is(CustomRoles.Jinx) || target.Is(CustomRoles.CursedWolf)) && IgnoreCursedWolfAndJinx.GetBool())) + { + killer.Kill(target); + return false; + } + + if ((target.Is(CustomRoles.Pestilence) && !IgnorePestilence.GetBool()) + || (Veteran.VeteranInProtect.ContainsKey(target.PlayerId) && !IgnoreVeteranAlert.GetBool()) + || (Medic.InProtect(target.PlayerId) && !IgnoreMedicShield.GetBool()) + || ((target.Is(CustomRoles.Jinx) || target.Is(CustomRoles.CursedWolf)) && !IgnoreCursedWolfAndJinx.GetBool())) + break; + + killer.RpcCheckAndMurder(target); + return false; + case 6: // Low KCD + killer.Notify(string.Format(GetString("GamblerGet.LowKCD"), LowKCD.GetFloat())); + LateTask.New(() => { killer.SetKillCooldown(LowKCD.GetFloat()); }, 0.1f, "Gambler SetLowKCD"); + break; + case 7: // Speed + killer.Notify(string.Format(GetString("GamblerGet.Speedup"), SpeedDur.GetInt(), Speed.GetFloat())); + IsSpeedChange.TryAdd(killer.PlayerId, (Main.AllPlayerSpeed[killer.PlayerId], TimeStamp)); + Main.AllPlayerSpeed[killer.PlayerId] = Speed.GetFloat(); + killer.MarkDirtySettings(); + break; + default: + Logger.Error("Invalid Effect ID (positive)", "Gambler.OnCheckMurder"); + break; + } + } + else + { + EffectID = (byte)rd.Next(1, 5); + + switch (EffectID) + { + case 1: // BSR + float delay = Math.Max(0.15f, BsrDelay.GetFloat()); + if (delay >= 1f) killer.Notify(string.Format(GetString("GamblerGet.BSR"), BsrDelay.GetInt())); + + LateTask.New(() => + { + if (GameStates.IsInTask) killer.CmdReportDeadBody(target.Data); + }, delay, "Gambler Self Report"); + + break; + case 2: // Freeze + killer.Notify(string.Format(GetString("GamblerGet.Freeze"), FreezeDur.GetInt())); + IsSpeedChange.TryAdd(killer.PlayerId, (Main.AllPlayerSpeed[killer.PlayerId], TimeStamp)); + Main.AllPlayerSpeed[killer.PlayerId] = Main.MinSpeed; + killer.MarkDirtySettings(); + break; + case 3: // Low vision + killer.Notify(string.Format(GetString("GamblerGet.LowVision"), LowVisionDur.GetInt(), LowVision.GetFloat())); + IsVisionChange.TryAdd(killer.PlayerId, TimeStamp); + killer.MarkDirtySettings(); + break; + case 4: // High KCD + killer.Notify(string.Format(GetString("GamblerGet.HighKCD"), HighKCD.GetFloat())); + LateTask.New(() => { killer.SetKillCooldown(HighKCD.GetFloat()); }, 0.1f, "Gambler SetHighKCD"); + break; + default: + Logger.Error("Invalid Effect ID (negative)", "Gambler.OnCheckMurder"); + break; + } + } + + EffectID = byte.MaxValue; + + return true; + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask || player == null || !player.Is(CustomRoles.Gambler) || (WaitingDelayedKills.Count == 0 && IsSpeedChange.Count == 0 && IsVisionChange.Count == 0 && IsShielded.Count == 0)) return; + + var sync = false; + + foreach (KeyValuePair x in WaitingDelayedKills) + { + PlayerControl pc = GetPlayerById(x.Key); + + if (!pc.IsAlive()) + { + WaitingDelayedKills.Remove(x.Key); + continue; + } + + if (x.Value + KillDelay.GetInt() < TimeStamp) + { + pc.Suicide(PlayerState.DeathReason.Poison, player); + WaitingDelayedKills.Remove(x.Key); + } + } + + if (IsSpeedChange.TryGetValue(player.PlayerId, out (float, long) p) && p.Item2 + SpeedDur.GetInt() < TimeStamp) + { + Main.AllPlayerSpeed[player.PlayerId] = p.Item1; + IsSpeedChange.Remove(player.PlayerId); + sync = true; + } + + if (IsVisionChange.TryGetValue(player.PlayerId, out long v) && v + LowVisionDur.GetInt() < TimeStamp) + { + IsVisionChange.Remove(player.PlayerId); + sync = true; + } + + if (IsShielded.TryGetValue(player.PlayerId, out long shielded) && shielded + ShieldDur.GetInt() < TimeStamp) IsShielded.Remove(player.PlayerId); + + if (sync) player.MarkDirtySettings(); + } + + public override void OnReportDeadBody() + { + EffectID = byte.MaxValue; + isPositiveEffect = true; + + foreach (byte playerId in WaitingDelayedKills.Keys.ToArray()) + { + PlayerControl pc = GetPlayerById(playerId); + if (pc.IsAlive()) pc.Kill(pc); + } + + WaitingDelayedKills.Clear(); + IsShielded.Clear(); + IsSpeedChange.Clear(); + IsVisionChange.Clear(); + } } \ No newline at end of file diff --git a/Roles/Impostor/Gangster.cs b/Roles/Impostor/Gangster.cs index cc5a681c0..3779acfd2 100644 --- a/Roles/Impostor/Gangster.cs +++ b/Roles/Impostor/Gangster.cs @@ -1,126 +1,125 @@ using System.Collections.Generic; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Gangster : RoleBase { - public class Gangster : RoleBase + private const int Id = 2900; + private static List PlayerIdList = []; + + private static OptionItem RecruitLimitOpt; + public static OptionItem KillCooldown; + public static OptionItem SheriffCanBeMadmate; + public static OptionItem MayorCanBeMadmate; + public static OptionItem NGuesserCanBeMadmate; + public static OptionItem JudgeCanBeMadmate; + public static OptionItem MarshallCanBeMadmate; + public static OptionItem FarseerCanBeMadmate; + public static OptionItem PresidentCanBeMadmate; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 2900; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Gangster); - private static OptionItem RecruitLimitOpt; - public static OptionItem KillCooldown; - public static OptionItem SheriffCanBeMadmate; - public static OptionItem MayorCanBeMadmate; - public static OptionItem NGuesserCanBeMadmate; - public static OptionItem JudgeCanBeMadmate; - public static OptionItem MarshallCanBeMadmate; - public static OptionItem FarseerCanBeMadmate; - public static OptionItem PresidentCanBeMadmate; + KillCooldown = new FloatOptionItem(Id + 10, "GangsterRecruitCooldown", new(0f, 60f, 2.5f), 7.5f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + RecruitLimitOpt = new IntegerOptionItem(Id + 12, "GangsterRecruitLimit", new(1, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + SheriffCanBeMadmate = new BooleanOptionItem(Id + 14, "GanSheriffCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + MayorCanBeMadmate = new BooleanOptionItem(Id + 15, "GanMayorCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + NGuesserCanBeMadmate = new BooleanOptionItem(Id + 16, "GanNGuesserCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + JudgeCanBeMadmate = new BooleanOptionItem(Id + 17, "GanJudgeCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + MarshallCanBeMadmate = new BooleanOptionItem(Id + 18, "GanMarshallCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + FarseerCanBeMadmate = new BooleanOptionItem(Id + 19, "GanFarseerCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + PresidentCanBeMadmate = new BooleanOptionItem(Id + 20, "GanPresidentCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(RecruitLimitOpt.GetInt()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CanRecruit(id) ? KillCooldown.GetFloat() : Options.DefaultKillCooldown; + } + + public static bool CanRecruit(byte id) + { + return id.GetAbilityUseLimit() > 0; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton.OverrideText(CanRecruit(id) ? GetString("GangsterButtonText") : GetString("KillButtonText")); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return true; + + if (CanBeMadmate(target)) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Gangster); + if (!killer.GetCustomSubRoles().FindFirst(x => x.IsConverted(), out CustomRoles convertedAddon)) convertedAddon = CustomRoles.Madmate; - KillCooldown = new FloatOptionItem(Id + 10, "GangsterRecruitCooldown", new(0f, 60f, 2.5f), 7.5f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]) - .SetValueFormat(OptionFormat.Seconds); + killer.RpcRemoveAbilityUse(); + target.RpcSetCustomRole(convertedAddon); - RecruitLimitOpt = new IntegerOptionItem(Id + 12, "GangsterRecruitLimit", new(1, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]) - .SetValueFormat(OptionFormat.Times); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(convertedAddon), GetString("GangsterSuccessfullyRecruited"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(convertedAddon), GetString("BeRecruitedByGangster"))); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - SheriffCanBeMadmate = new BooleanOptionItem(Id + 14, "GanSheriffCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); - MayorCanBeMadmate = new BooleanOptionItem(Id + 15, "GanMayorCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); - NGuesserCanBeMadmate = new BooleanOptionItem(Id + 16, "GanNGuesserCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); - JudgeCanBeMadmate = new BooleanOptionItem(Id + 17, "GanJudgeCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); - MarshallCanBeMadmate = new BooleanOptionItem(Id + 18, "GanMarshallCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); - FarseerCanBeMadmate = new BooleanOptionItem(Id + 19, "GanFarseerCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); - PresidentCanBeMadmate = new BooleanOptionItem(Id + 20, "GanPresidentCanBeMadmate", false, TabGroup.ImpostorRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Gangster]); + killer.ResetKillCooldown(); + killer.SyncSettings(); + killer.SetKillCooldown(); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); + + Logger.Info($"SetRole: {target?.Data?.PlayerName} = {target.GetCustomRole()} + {convertedAddon}", $"Assign {convertedAddon}"); + if (killer.GetAbilityUseLimit() <= 0) HudManager.Instance.KillButton.OverrideText($"{GetString("KillButtonText")}"); + + return false; } - public override void Init() - { - PlayerIdList = []; - } + if (killer.GetAbilityUseLimit() < 0) HudManager.Instance.KillButton.OverrideText($"{GetString("KillButtonText")}"); - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(RecruitLimitOpt.GetInt()); - } + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Gangster), GetString("GangsterRecruitmentFailure"))); + return true; + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CanRecruit(id) ? KillCooldown.GetFloat() : Options.DefaultKillCooldown; - } - - public static bool CanRecruit(byte id) - { - return id.GetAbilityUseLimit() > 0; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton.OverrideText(CanRecruit(id) ? GetString("GangsterButtonText") : GetString("KillButtonText")); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return true; - - if (CanBeMadmate(target)) - { - if (!killer.GetCustomSubRoles().FindFirst(x => x.IsConverted(), out CustomRoles convertedAddon)) convertedAddon = CustomRoles.Madmate; - - killer.RpcRemoveAbilityUse(); - target.RpcSetCustomRole(convertedAddon); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(convertedAddon), GetString("GangsterSuccessfullyRecruited"))); - target.Notify(Utils.ColorString(Utils.GetRoleColor(convertedAddon), GetString("BeRecruitedByGangster"))); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - - killer.ResetKillCooldown(); - killer.SyncSettings(); - killer.SetKillCooldown(); - target.RpcGuardAndKill(killer); - target.RpcGuardAndKill(target); - - Logger.Info($"SetRole: {target?.Data?.PlayerName} = {target.GetCustomRole()} + {convertedAddon}", $"Assign {convertedAddon}"); - if (killer.GetAbilityUseLimit() <= 0) HudManager.Instance.KillButton.OverrideText($"{GetString("KillButtonText")}"); - - return false; - } - - if (killer.GetAbilityUseLimit() < 0) HudManager.Instance.KillButton.OverrideText($"{GetString("KillButtonText")}"); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Gangster), GetString("GangsterRecruitmentFailure"))); - return true; - } - - private static bool CanBeMadmate(PlayerControl pc) - { - return pc != null && pc.IsCrewmate() && !pc.Is(CustomRoles.Madmate) - && !( - (pc.Is(CustomRoles.Sheriff) && !SheriffCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Mayor) && !MayorCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.NiceGuesser) && !NGuesserCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Judge) && !JudgeCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Marshall) && !MarshallCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.Farseer) && !FarseerCanBeMadmate.GetBool()) || - (pc.Is(CustomRoles.President) && !PresidentCanBeMadmate.GetBool()) || - pc.Is(CustomRoles.NiceSwapper) || - pc.Is(CustomRoles.Speedrunner) || - pc.Is(CustomRoles.Snitch) || - pc.Is(CustomRoles.Needy) || - pc.Is(CustomRoles.Lazy) || - pc.Is(CustomRoles.Loyal) || - pc.Is(CustomRoles.CyberStar) || - pc.Is(CustomRoles.Egoist) - ); - } + private static bool CanBeMadmate(PlayerControl pc) + { + return pc != null && pc.IsCrewmate() && !pc.Is(CustomRoles.Madmate) + && !( + (pc.Is(CustomRoles.Sheriff) && !SheriffCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Mayor) && !MayorCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.NiceGuesser) && !NGuesserCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Judge) && !JudgeCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Marshall) && !MarshallCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.Farseer) && !FarseerCanBeMadmate.GetBool()) || + (pc.Is(CustomRoles.President) && !PresidentCanBeMadmate.GetBool()) || + pc.Is(CustomRoles.NiceSwapper) || + pc.Is(CustomRoles.Speedrunner) || + pc.Is(CustomRoles.Snitch) || + pc.Is(CustomRoles.Needy) || + pc.Is(CustomRoles.Lazy) || + pc.Is(CustomRoles.Loyal) || + pc.Is(CustomRoles.CyberStar) || + pc.Is(CustomRoles.Egoist) + ); } } \ No newline at end of file diff --git a/Roles/Impostor/Generator.cs b/Roles/Impostor/Generator.cs index 490e40c0c..dfac4757c 100644 --- a/Roles/Impostor/Generator.cs +++ b/Roles/Impostor/Generator.cs @@ -3,195 +3,194 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal static class GeneratorStatic { - internal static class GeneratorStatic + private static int GetDefaultCost(this Action action) { - private static int GetDefaultCost(this Action action) + return action switch { - return action switch - { - Action.Kill => 5, - Action.Shapeshift => 10, - Action.Vent => 20, - Action.Sabotage => 15, + Action.Kill => 5, + Action.Shapeshift => 10, + Action.Vent => 20, + Action.Sabotage => 15, - _ => 15 - }; + _ => 15 + }; + } + + private enum Action + { + Kill, + Shapeshift, + Vent, + Sabotage + } + + internal class Generator : RoleBase + { + public static bool On; + + private static OptionItem ChargesGainedPerSecond; + private static OptionItem StartingCharges; + private static OptionItem ChargesLostWhileShiftedPerSecond; + private static OptionItem MaxChargesStored; + private static readonly Dictionary ActionCostSettings = []; + + private static int Gain; + private static int ChargesLostWhileShifted; + private static Dictionary ActionCosts = []; + + private int Charges; + private long LastUpdate; + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + const int id = 11385; + Options.SetupRoleOptions(id, TabGroup.ImpostorRoles, CustomRoles.Generator); + + ChargesGainedPerSecond = new IntegerOptionItem(id + 2, "Generator.ChargesGainedEverySecond", new(1, 30, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + + StartingCharges = new IntegerOptionItem(id + 3, "Generator.StartingCharges", new(0, 100, 1), 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + + ChargesLostWhileShiftedPerSecond = new IntegerOptionItem(id + 4, "Generator.ChargesLostWhileShiftedPerSecond", new(1, 30, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + + MaxChargesStored = new IntegerOptionItem(id + 5, "Generator.MaxChargesStored", new(0, 200, 1), 100, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + + foreach (Action action in Enum.GetValues()) + { + OptionItem option = new IntegerOptionItem(id + 6 + (int)action, $"Generator.{action}.Cost", new(0, 100, 1), action.GetDefaultCost(), TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + + ActionCostSettings.Add(action, option); + } } - private enum Action + public override void Add(byte playerId) { - Kill, - Shapeshift, - Vent, - Sabotage + On = true; + Charges = StartingCharges.GetInt(); + playerId.SetAbilityUseLimit(Charges); + LastUpdate = Utils.TimeStamp + 8; } - internal class Generator : RoleBase + public override void Init() { - public static bool On; + On = false; + Gain = ChargesGainedPerSecond.GetInt(); + ChargesLostWhileShifted = ChargesLostWhileShiftedPerSecond.GetInt(); + ActionCosts = ActionCostSettings.ToDictionary(x => x.Key, x => x.Value.GetInt()); + } - private static OptionItem ChargesGainedPerSecond; - private static OptionItem StartingCharges; - private static OptionItem ChargesLostWhileShiftedPerSecond; - private static OptionItem MaxChargesStored; - private static readonly Dictionary ActionCostSettings = []; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = 1f; + AURoleOptions.ShapeshifterDuration = 300f; + } - private static int Gain; - private static int ChargesLostWhileShifted; - private static Dictionary ActionCosts = []; + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || pc == null || !pc.IsAlive()) return; - private int Charges; - private long LastUpdate; - public override bool IsEnable => On; + long now = Utils.TimeStamp; + if (now <= LastUpdate) return; - public override void SetupCustomOption() + LastUpdate = now; + + int beforeCharges = Charges; + bool shifted = pc.IsShifted(); + + if (shifted) + Charges -= ChargesLostWhileShifted; + else + Charges += Gain; + + Charges = Math.Clamp(Charges, 0, MaxChargesStored.GetInt()); + + if (Charges <= 0 && shifted) { - const int id = 11385; - Options.SetupRoleOptions(id, TabGroup.ImpostorRoles, CustomRoles.Generator); + pc.Notify(Translator.GetString("Generator.Notify.RevertShapeshift")); + pc.RpcShapeshift(pc, !Options.DisableShapeshiftAnimations.GetBool()); + pc.RpcResetAbilityCooldown(); + } - ChargesGainedPerSecond = new IntegerOptionItem(id + 2, "Generator.ChargesGainedEverySecond", new(1, 30, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + if (beforeCharges != Charges) pc.SetAbilityUseLimit(Charges, log: false); + } - StartingCharges = new IntegerOptionItem(id + 3, "Generator.StartingCharges", new(0, 100, 1), 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + int cost = ActionCosts[Action.Vent]; - ChargesLostWhileShiftedPerSecond = new IntegerOptionItem(id + 4, "Generator.ChargesLostWhileShiftedPerSecond", new(1, 30, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); - - MaxChargesStored = new IntegerOptionItem(id + 5, "Generator.MaxChargesStored", new(0, 200, 1), 100, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); - - foreach (Action action in Enum.GetValues()) + if (Charges < cost) + { + LateTask.New(() => { - OptionItem option = new IntegerOptionItem(id + 6 + (int)action, $"Generator.{action}.Cost", new(0, 100, 1), action.GetDefaultCost(), TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Generator]); + physics.RpcBootFromVent(ventId); + physics.myPlayer.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); + }, 0.5f, "Generator not enough charges to vent"); - ActionCostSettings.Add(action, option); - } + return; } - public override void Add(byte playerId) + Charges -= cost; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + int cost = ActionCosts[Action.Kill]; + + if (Charges < cost) { - On = true; - Charges = StartingCharges.GetInt(); - playerId.SetAbilityUseLimit(Charges); - LastUpdate = Utils.TimeStamp + 8; + killer.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); + return false; } - public override void Init() + return true; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + Charges -= ActionCosts[Action.Kill]; + } + + public override bool OnSabotage(PlayerControl pc) + { + if (!pc.IsAlive()) return true; + + int cost = ActionCosts[Action.Sabotage]; + + if (Charges < cost) { - On = false; - Gain = ChargesGainedPerSecond.GetInt(); - ChargesLostWhileShifted = ChargesLostWhileShiftedPerSecond.GetInt(); - ActionCosts = ActionCostSettings.ToDictionary(x => x.Key, x => x.Value.GetInt()); + pc.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); + return false; } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + Charges -= cost; + return true; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) return true; + + int cost = ActionCosts[Action.Shapeshift]; + + if (Charges < cost) { - AURoleOptions.ShapeshifterCooldown = 1f; - AURoleOptions.ShapeshifterDuration = 300f; + shapeshifter.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); + return false; } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || pc == null || !pc.IsAlive()) return; - - long now = Utils.TimeStamp; - if (now <= LastUpdate) return; - - LastUpdate = now; - - int beforeCharges = Charges; - bool shifted = pc.IsShifted(); - - if (shifted) - Charges -= ChargesLostWhileShifted; - else - Charges += Gain; - - Charges = Math.Clamp(Charges, 0, MaxChargesStored.GetInt()); - - if (Charges <= 0 && shifted) - { - pc.Notify(Translator.GetString("Generator.Notify.RevertShapeshift")); - pc.RpcShapeshift(pc, !Options.DisableShapeshiftAnimations.GetBool()); - pc.RpcResetAbilityCooldown(); - } - - if (beforeCharges != Charges) pc.SetAbilityUseLimit(Charges, log: false); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - int cost = ActionCosts[Action.Vent]; - - if (Charges < cost) - { - LateTask.New(() => - { - physics.RpcBootFromVent(ventId); - physics.myPlayer.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); - }, 0.5f, "Generator not enough charges to vent"); - - return; - } - - Charges -= cost; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - int cost = ActionCosts[Action.Kill]; - - if (Charges < cost) - { - killer.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); - return false; - } - - return true; - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - Charges -= ActionCosts[Action.Kill]; - } - - public override bool OnSabotage(PlayerControl pc) - { - if (!pc.IsAlive()) return true; - - int cost = ActionCosts[Action.Sabotage]; - - if (Charges < cost) - { - pc.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); - return false; - } - - Charges -= cost; - return true; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting) return true; - - int cost = ActionCosts[Action.Shapeshift]; - - if (Charges < cost) - { - shapeshifter.Notify(string.Format(Translator.GetString("Generator.Notify.NotEnoughCharges"), cost)); - return false; - } - - Charges -= cost; - return true; - } + Charges -= cost; + return true; } } } \ No newline at end of file diff --git a/Roles/Impostor/Godfather.cs b/Roles/Impostor/Godfather.cs index b150142ba..5c445ecbd 100644 --- a/Roles/Impostor/Godfather.cs +++ b/Roles/Impostor/Godfather.cs @@ -1,34 +1,33 @@ -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Godfather : RoleBase { - internal class Godfather : RoleBase + public static byte GodfatherTarget = byte.MaxValue; + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static byte GodfatherTarget = byte.MaxValue; - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(648400, TabGroup.ImpostorRoles, CustomRoles.Godfather); + Options.GodfatherCancelVote = Options.CreateVoteCancellingUseSetting(648402, CustomRoles.Godfather, TabGroup.ImpostorRoles); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(648400, TabGroup.ImpostorRoles, CustomRoles.Godfather); - Options.GodfatherCancelVote = Options.CreateVoteCancellingUseSetting(648402, CustomRoles.Godfather, TabGroup.ImpostorRoles); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override bool OnVote(PlayerControl voter, PlayerControl target) + { + if (voter == null || target == null || voter.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; - public override bool OnVote(PlayerControl voter, PlayerControl target) - { - if (voter == null || target == null || voter.PlayerId == target.PlayerId || Main.DontCancelVoteList.Contains(voter.PlayerId)) return false; - - GodfatherTarget = target.PlayerId; - Main.DontCancelVoteList.Add(voter.PlayerId); - return true; - } + GodfatherTarget = target.PlayerId; + Main.DontCancelVoteList.Add(voter.PlayerId); + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/Greedier.cs b/Roles/Impostor/Greedier.cs index 9683cc7c7..38328f810 100644 --- a/Roles/Impostor/Greedier.cs +++ b/Roles/Impostor/Greedier.cs @@ -5,132 +5,131 @@ using EHR.Modules; using EHR.Neutral; using Hazel; -namespace EHR.Impostor +namespace EHR.Impostor; + +// Reference: https://github.com/Yumenopai/TownOfHost_Y +public class Greedier : RoleBase // Also used for Imitator as the NK version of this { - // Reference: https://github.com/Yumenopai/TownOfHost_Y - public class Greedier : RoleBase // Also used for Imitator as the NK version of this + private const int Id = 1300; + public static List PlayerIdList = []; + + private static OptionItem OddKillCooldown; + private static OptionItem EvenKillCooldown; + private static OptionItem AfterMeetingKillCooldown; + private float AfterMeetingKCD; + private float EvenKCD; + private bool HasImpVision; + + private bool IsImitator; + + public bool IsOdd = true; + + private float OddKCD; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 1300; - public static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Greedier); - private static OptionItem OddKillCooldown; - private static OptionItem EvenKillCooldown; - private static OptionItem AfterMeetingKillCooldown; - private float AfterMeetingKCD; - private float EvenKCD; - private bool HasImpVision; + OddKillCooldown = new FloatOptionItem(Id + 10, "OddKillCooldown", new(0f, 60f, 2.5f), 27.5f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Greedier]) + .SetValueFormat(OptionFormat.Seconds); - private bool IsImitator; + EvenKillCooldown = new FloatOptionItem(Id + 11, "EvenKillCooldown", new(0f, 30f, 2.5f), 15f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Greedier]) + .SetValueFormat(OptionFormat.Seconds); - public bool IsOdd = true; + AfterMeetingKillCooldown = new FloatOptionItem(Id + 12, "AfterMeetingKillCooldown", new(0f, 30f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Greedier]) + .SetValueFormat(OptionFormat.Seconds); + } - private float OddKCD; + public override void Init() + { + PlayerIdList = []; + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + IsOdd = true; - public override void SetupCustomOption() + IsImitator = Main.PlayerStates[playerId].MainRole == CustomRoles.Imitator; + + if (IsImitator) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Greedier); - - OddKillCooldown = new FloatOptionItem(Id + 10, "OddKillCooldown", new(0f, 60f, 2.5f), 27.5f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Greedier]) - .SetValueFormat(OptionFormat.Seconds); - - EvenKillCooldown = new FloatOptionItem(Id + 11, "EvenKillCooldown", new(0f, 30f, 2.5f), 15f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Greedier]) - .SetValueFormat(OptionFormat.Seconds); - - AfterMeetingKillCooldown = new FloatOptionItem(Id + 12, "AfterMeetingKillCooldown", new(0f, 30f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Greedier]) - .SetValueFormat(OptionFormat.Seconds); + OddKCD = Imitator.OddKillCooldown.GetFloat(); + EvenKCD = Imitator.EvenKillCooldown.GetFloat(); + AfterMeetingKCD = Imitator.AfterMeetingKillCooldown.GetFloat(); + HasImpVision = Imitator.HasImpostorVision.GetBool(); } - - public override void Init() + else { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - IsOdd = true; - - IsImitator = Main.PlayerStates[playerId].MainRole == CustomRoles.Imitator; - - if (IsImitator) - { - OddKCD = Imitator.OddKillCooldown.GetFloat(); - EvenKCD = Imitator.EvenKillCooldown.GetFloat(); - AfterMeetingKCD = Imitator.AfterMeetingKillCooldown.GetFloat(); - HasImpVision = Imitator.HasImpostorVision.GetBool(); - } - else - { - OddKCD = OddKillCooldown.GetFloat(); - EvenKCD = EvenKillCooldown.GetFloat(); - AfterMeetingKCD = AfterMeetingKillCooldown.GetFloat(); - HasImpVision = true; - } - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpVision); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return !IsImitator || Imitator.CanVent.GetBool(); - } - - private void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetGreedierOe, SendOption.Reliable); - writer.Write(playerId); - writer.Write(IsOdd); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(bool isOdd) - { - IsOdd = isOdd; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = OddKCD; - } - - public override void OnReportDeadBody() - { - foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId))) - { - IsOdd = true; - SendRPC(pc.PlayerId); - Main.AllPlayerKillCooldown[pc.PlayerId] = AfterMeetingKCD; - } - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - switch (IsOdd) - { - case true: - Logger.Info($"{killer.Data?.PlayerName}: Odd-Kill", "Greedier"); - Main.AllPlayerKillCooldown[killer.PlayerId] = EvenKCD; - break; - case false: - Logger.Info($"{killer.Data?.PlayerName}: Even-Kill", "Greedier"); - Main.AllPlayerKillCooldown[killer.PlayerId] = OddKCD; - break; - } - - IsOdd = !IsOdd; - SendRPC(killer.PlayerId); - killer.SyncSettings(); - return base.OnCheckMurder(killer, target); + OddKCD = OddKillCooldown.GetFloat(); + EvenKCD = EvenKillCooldown.GetFloat(); + AfterMeetingKCD = AfterMeetingKillCooldown.GetFloat(); + HasImpVision = true; } } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpVision); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return !IsImitator || Imitator.CanVent.GetBool(); + } + + private void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetGreedierOe, SendOption.Reliable); + writer.Write(playerId); + writer.Write(IsOdd); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(bool isOdd) + { + IsOdd = isOdd; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = OddKCD; + } + + public override void OnReportDeadBody() + { + foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(x => PlayerIdList.Contains(x.PlayerId))) + { + IsOdd = true; + SendRPC(pc.PlayerId); + Main.AllPlayerKillCooldown[pc.PlayerId] = AfterMeetingKCD; + } + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + switch (IsOdd) + { + case true: + Logger.Info($"{killer.Data?.PlayerName}: Odd-Kill", "Greedier"); + Main.AllPlayerKillCooldown[killer.PlayerId] = EvenKCD; + break; + case false: + Logger.Info($"{killer.Data?.PlayerName}: Even-Kill", "Greedier"); + Main.AllPlayerKillCooldown[killer.PlayerId] = OddKCD; + break; + } + + IsOdd = !IsOdd; + SendRPC(killer.PlayerId); + killer.SyncSettings(); + return base.OnCheckMurder(killer, target); + } } \ No newline at end of file diff --git a/Roles/Impostor/Hacker.cs b/Roles/Impostor/Hacker.cs index 19fc6045e..d80ad5f8a 100644 --- a/Roles/Impostor/Hacker.cs +++ b/Roles/Impostor/Hacker.cs @@ -3,104 +3,103 @@ using AmongUs.GameOptions; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Hacker : RoleBase { - public class Hacker : RoleBase + private const int Id = 2200; + private static List PlayerIdList = []; + + private static OptionItem HackLimitOpt; + private static OptionItem KillCooldown; + public static OptionItem HackerAbilityUseGainWithEachKill; + + private static List DeadBodyList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 2200; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Hacker); - private static OptionItem HackLimitOpt; - private static OptionItem KillCooldown; - public static OptionItem HackerAbilityUseGainWithEachKill; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hacker]) + .SetValueFormat(OptionFormat.Seconds); - private static List DeadBodyList = []; + HackLimitOpt = new IntegerOptionItem(Id + 3, "HackLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hacker]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + HackerAbilityUseGainWithEachKill = new FloatOptionItem(Id + 4, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.2f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hacker]) + .SetValueFormat(OptionFormat.Times); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + DeadBodyList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(HackLimitOpt.GetInt()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = 15f; + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override void SetButtonTexts(HudManager __instance, byte playerId) + { + if (playerId.GetAbilityUseLimit() >= 1) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Hacker); - - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hacker]) - .SetValueFormat(OptionFormat.Seconds); - - HackLimitOpt = new IntegerOptionItem(Id + 3, "HackLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hacker]) - .SetValueFormat(OptionFormat.Times); - - HackerAbilityUseGainWithEachKill = new FloatOptionItem(Id + 4, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.2f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hacker]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - PlayerIdList = []; - DeadBodyList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(HackLimitOpt.GetInt()); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = 15f; - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override void SetButtonTexts(HudManager __instance, byte playerId) - { - if (playerId.GetAbilityUseLimit() >= 1) - { - __instance.AbilityButton.OverrideText(GetString("HackerShapeshiftText")); - __instance.AbilityButton.SetUsesRemaining((int)playerId.GetAbilityUseLimit()); - } - } - - public override void OnReportDeadBody() - { - DeadBodyList = []; - } - - public static void AddDeadBody(PlayerControl target) - { - if (target != null && !DeadBodyList.Contains(target.PlayerId)) DeadBodyList.Add(target.PlayerId); - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl ssTarget, bool shapeshifting) - { - if (!shapeshifting || pc.GetAbilityUseLimit() < 1 || ssTarget == null || ssTarget.Is(CustomRoles.Needy) || ssTarget.Is(CustomRoles.Lazy)) return false; - - pc.RpcRemoveAbilityUse(); - - var targetId = byte.MaxValue; - - foreach (byte db in DeadBodyList.ToArray()) - { - PlayerControl dp = Utils.GetPlayerById(db); - if (dp == null || dp.GetRealKiller() == null) continue; - - if (dp.GetRealKiller().PlayerId == pc.PlayerId) targetId = db; - } - - if (targetId == byte.MaxValue && DeadBodyList.Count > 0) targetId = DeadBodyList.RandomElement(); - - if (targetId == byte.MaxValue) - LateTask.New(() => ssTarget.NoCheckStartMeeting(ssTarget.Data), 0.15f, "Hacker Hacking Report Self"); - else - LateTask.New(() => ssTarget.NoCheckStartMeeting(Utils.GetPlayerById(targetId)?.Data), 0.15f, "Hacker Hacking Report"); - - return false; + __instance.AbilityButton.OverrideText(GetString("HackerShapeshiftText")); + __instance.AbilityButton.SetUsesRemaining((int)playerId.GetAbilityUseLimit()); } } + + public override void OnReportDeadBody() + { + DeadBodyList = []; + } + + public static void AddDeadBody(PlayerControl target) + { + if (target != null && !DeadBodyList.Contains(target.PlayerId)) DeadBodyList.Add(target.PlayerId); + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl ssTarget, bool shapeshifting) + { + if (!shapeshifting || pc.GetAbilityUseLimit() < 1 || ssTarget == null || ssTarget.Is(CustomRoles.Needy) || ssTarget.Is(CustomRoles.Lazy)) return false; + + pc.RpcRemoveAbilityUse(); + + var targetId = byte.MaxValue; + + foreach (byte db in DeadBodyList.ToArray()) + { + PlayerControl dp = Utils.GetPlayerById(db); + if (dp == null || dp.GetRealKiller() == null) continue; + + if (dp.GetRealKiller().PlayerId == pc.PlayerId) targetId = db; + } + + if (targetId == byte.MaxValue && DeadBodyList.Count > 0) targetId = DeadBodyList.RandomElement(); + + if (targetId == byte.MaxValue) + LateTask.New(() => ssTarget.NoCheckStartMeeting(ssTarget.Data), 0.15f, "Hacker Hacking Report Self"); + else + LateTask.New(() => ssTarget.NoCheckStartMeeting(Utils.GetPlayerById(targetId)?.Data), 0.15f, "Hacker Hacking Report"); + + return false; + } } \ No newline at end of file diff --git a/Roles/Impostor/Hangman.cs b/Roles/Impostor/Hangman.cs index c87597b83..2975b2a3b 100644 --- a/Roles/Impostor/Hangman.cs +++ b/Roles/Impostor/Hangman.cs @@ -3,96 +3,95 @@ using AmongUs.GameOptions; using EHR.Crewmate; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Hangman : RoleBase { - public class Hangman : RoleBase + private const int Id = 1400; + private static List PlayerIdList = []; + + private static OptionItem ShapeshiftCooldown; + public static OptionItem ShapeshiftDuration; + private static OptionItem KCD; + private static OptionItem HangmanLimitOpt; + public static OptionItem HangmanAbilityUseGainWithEachKill; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 1400; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Hangman); - private static OptionItem ShapeshiftCooldown; - public static OptionItem ShapeshiftDuration; - private static OptionItem KCD; - private static OptionItem HangmanLimitOpt; - public static OptionItem HangmanAbilityUseGainWithEachKill; + ShapeshiftCooldown = new FloatOptionItem(Id + 2, "ShapeshiftCooldown", new(1f, 60f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + ShapeshiftDuration = new FloatOptionItem(Id + 3, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + KCD = new FloatOptionItem(Id + 4, "KillCooldownOnStrangle", new(1f, 90f, 1f), 40f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) + .SetValueFormat(OptionFormat.Seconds); + + HangmanLimitOpt = new IntegerOptionItem(Id + 5, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) + .SetValueFormat(OptionFormat.Times); + + HangmanAbilityUseGainWithEachKill = new FloatOptionItem(Id + 6, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(HangmanLimitOpt.GetInt()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Medic.ProtectList.Contains(target.PlayerId)) return false; + + if (target.Is(CustomRoles.Madmate) && !ImpCanKillMadmate.GetBool()) return false; + + if (killer.GetAbilityUseLimit() < 1 && killer.IsShifted()) return false; + + if (killer.IsShifted()) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Hangman); + if (target.Is(CustomRoles.Pestilence)) return false; - ShapeshiftCooldown = new FloatOptionItem(Id + 2, "ShapeshiftCooldown", new(1f, 60f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) - .SetValueFormat(OptionFormat.Seconds); + if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return false; - ShapeshiftDuration = new FloatOptionItem(Id + 3, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) - .SetValueFormat(OptionFormat.Seconds); - - KCD = new FloatOptionItem(Id + 4, "KillCooldownOnStrangle", new(1f, 90f, 1f), 40f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) - .SetValueFormat(OptionFormat.Seconds); - - HangmanLimitOpt = new IntegerOptionItem(Id + 5, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) - .SetValueFormat(OptionFormat.Times); - - HangmanAbilityUseGainWithEachKill = new FloatOptionItem(Id + 6, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.3f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hangman]) - .SetValueFormat(OptionFormat.Times); + killer.RpcRemoveAbilityUse(); + target.Data.IsDead = true; + target.SetRealKiller(killer); + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.LossOfHead; + target.RpcExileV2(); + Main.PlayerStates[target.PlayerId].SetDead(); + target.SetRealKiller(killer); + killer.SetKillCooldown(KCD.GetFloat()); + return false; } - public override void Init() - { - PlayerIdList = []; - } + return true; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(HangmanLimitOpt.GetInt()); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + if (id.IsPlayerShifted()) hud.KillButton?.OverrideText(Translator.GetString("HangmanKillButtonTextDuringSS")); - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Medic.ProtectList.Contains(target.PlayerId)) return false; - - if (target.Is(CustomRoles.Madmate) && !ImpCanKillMadmate.GetBool()) return false; - - if (killer.GetAbilityUseLimit() < 1 && killer.IsShifted()) return false; - - if (killer.IsShifted()) - { - if (target.Is(CustomRoles.Pestilence)) return false; - - if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return false; - - killer.RpcRemoveAbilityUse(); - target.Data.IsDead = true; - target.SetRealKiller(killer); - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.LossOfHead; - target.RpcExileV2(); - Main.PlayerStates[target.PlayerId].SetDead(); - target.SetRealKiller(killer); - killer.SetKillCooldown(KCD.GetFloat()); - return false; - } - - return true; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (id.IsPlayerShifted()) hud.KillButton?.OverrideText(Translator.GetString("HangmanKillButtonTextDuringSS")); - - hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); - } + hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); } } \ No newline at end of file diff --git a/Roles/Impostor/Hitman.cs b/Roles/Impostor/Hitman.cs index 2b145e490..1aede1545 100644 --- a/Roles/Impostor/Hitman.cs +++ b/Roles/Impostor/Hitman.cs @@ -5,127 +5,126 @@ using Hazel; using static EHR.Options; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Hitman : RoleBase { - public class Hitman : RoleBase + private const int Id = 640800; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem SuccessKCD; + private static OptionItem ShapeshiftCooldown; + + private byte HitmanId = byte.MaxValue; + public byte TargetId = byte.MaxValue; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640800; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Hitman); - private static OptionItem KillCooldown; - private static OptionItem SuccessKCD; - private static OptionItem ShapeshiftCooldown; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Hitman]) + .SetValueFormat(OptionFormat.Seconds); - private byte HitmanId = byte.MaxValue; - public byte TargetId = byte.MaxValue; + SuccessKCD = new FloatOptionItem(Id + 11, "HitmanLowKCD", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Hitman]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + ShapeshiftCooldown = new FloatOptionItem(Id + 12, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Hitman]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + TargetId = byte.MaxValue; + HitmanId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + HitmanId = playerId; + TargetId = byte.MaxValue; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + private void SendRPC() + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetHitmanTarget, SendOption.Reliable); + writer.Write(HitmanId); + writer.Write(TargetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(byte id) + { + TargetId = id; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + if (target.PlayerId == TargetId) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Hitman); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Hitman]) - .SetValueFormat(OptionFormat.Seconds); - - SuccessKCD = new FloatOptionItem(Id + 11, "HitmanLowKCD", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Hitman]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftCooldown = new FloatOptionItem(Id + 12, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Hitman]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; TargetId = byte.MaxValue; - HitmanId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - HitmanId = playerId; - TargetId = byte.MaxValue; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - private void SendRPC() - { - if (!DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetHitmanTarget, SendOption.Reliable); - writer.Write(HitmanId); - writer.Write(TargetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(byte id) - { - TargetId = id; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return false; - - if (target == null) return false; - - if (target.PlayerId == TargetId) - { - TargetId = byte.MaxValue; - SendRPC(); - LateTask.New(() => { killer.SetKillCooldown(SuccessKCD.GetFloat()); }, 0.1f, "Hitman Killed Target - SetKillCooldown Task"); - } - - return true; - } - - public static void CheckAndResetTargets() - { - foreach (byte id in PlayerIdList) - if (Main.PlayerStates[id].Role is Hitman { IsEnable: true } hm) - hm.OnReportDeadBody(); - } - - public override void OnReportDeadBody() - { - PlayerControl target = GetPlayerById(TargetId); - - if (!target.IsAlive() || target.Data.Disconnected) - { - TargetId = byte.MaxValue; - SendRPC(); - } - } - - public override bool OnShapeshift(PlayerControl hitman, PlayerControl target, bool shapeshifting) - { - if (target == null || hitman == null || !shapeshifting || TargetId != byte.MaxValue || !target.IsAlive()) return false; - - TargetId = target.PlayerId; SendRPC(); - NotifyRoles(SpecifySeer: hitman, SpecifyTarget: target); - - return false; + LateTask.New(() => { killer.SetKillCooldown(SuccessKCD.GetFloat()); }, 0.1f, "Hitman Killed Target - SetKillCooldown Task"); } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId) return string.Empty; + return true; + } - byte id = (Main.PlayerStates[seer.PlayerId].Role as Hitman)?.TargetId ?? byte.MaxValue; - return id == byte.MaxValue ? string.Empty : $"Target: {GetPlayerById(id).GetRealName().RemoveHtmlTags()}"; + public static void CheckAndResetTargets() + { + foreach (byte id in PlayerIdList) + if (Main.PlayerStates[id].Role is Hitman { IsEnable: true } hm) + hm.OnReportDeadBody(); + } + + public override void OnReportDeadBody() + { + PlayerControl target = GetPlayerById(TargetId); + + if (!target.IsAlive() || target.Data.Disconnected) + { + TargetId = byte.MaxValue; + SendRPC(); } } + + public override bool OnShapeshift(PlayerControl hitman, PlayerControl target, bool shapeshifting) + { + if (target == null || hitman == null || !shapeshifting || TargetId != byte.MaxValue || !target.IsAlive()) return false; + + TargetId = target.PlayerId; + SendRPC(); + NotifyRoles(SpecifySeer: hitman, SpecifyTarget: target); + + return false; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + byte id = (Main.PlayerStates[seer.PlayerId].Role as Hitman)?.TargetId ?? byte.MaxValue; + return id == byte.MaxValue ? string.Empty : $"Target: {GetPlayerById(id).GetRealName().RemoveHtmlTags()}"; + } } \ No newline at end of file diff --git a/Roles/Impostor/Hypnotist.cs b/Roles/Impostor/Hypnotist.cs index 18d01663b..1e0e9c96d 100644 --- a/Roles/Impostor/Hypnotist.cs +++ b/Roles/Impostor/Hypnotist.cs @@ -4,115 +4,114 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Hypnotist : RoleBase { - public class Hypnotist : RoleBase + public static bool On; + private static List Instances = []; + + public static OptionItem AbilityCooldown; + public static OptionItem AbilityDuration; + public static OptionItem AbilityUseLimit; + public static OptionItem AbilityUseGainWithEachKill; + private static OptionItem DoReportAfterHypnosisEnds; + + private long ActivateTS; + private int Count; + private byte HypnotistId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(647550) + .AutoSetupOption(ref AbilityCooldown, 30, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityDuration, 15, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 5, 1), OptionFormat.Times) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times) + .AutoSetupOption(ref DoReportAfterHypnosisEnds, true); + } - public static OptionItem AbilityCooldown; - public static OptionItem AbilityDuration; - public static OptionItem AbilityUseLimit; - public static OptionItem AbilityUseGainWithEachKill; - private static OptionItem DoReportAfterHypnosisEnds; + public override void Init() + { + On = false; + Instances = []; + } - private long ActivateTS; - private int Count; - private byte HypnotistId; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + Count = 0; + ActivateTS = 0; + HypnotistId = playerId; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - public override bool IsEnable => On; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetInt(); + AURoleOptions.ShapeshifterDuration = 1f; + } - public override void SetupCustomOption() + public static bool OnAnyoneReport() + { + return Instances.All(x => x.ActivateTS == 0); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + OnPet(shapeshifter); + return false; + } + + public override void OnPet(PlayerControl pc) + { + Count = 0; + ActivateTS = Utils.TimeStamp; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + Utils.SendRPC(CustomRPC.SyncRoleData, HypnotistId, ActivateTS); + + if (DoReportAfterHypnosisEnds.GetBool()) ReportDeadBodyPatch.CanReport.SetAllValues(false); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (ActivateTS == 0) return; + + var notify = false; + var timeLeft = (int)(ActivateTS + AbilityDuration.GetInt() - Utils.TimeStamp); + + switch (timeLeft) { - StartSetup(647550) - .AutoSetupOption(ref AbilityCooldown, 30, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds) - .AutoSetupOption(ref AbilityDuration, 15, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) - .AutoSetupOption(ref AbilityUseLimit, 1, new IntegerValueRule(0, 5, 1), OptionFormat.Times) - .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.5f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times) - .AutoSetupOption(ref DoReportAfterHypnosisEnds, true); + case <= 0: + ActivateTS = 0; + notify = true; + pc.RpcResetAbilityCooldown(); + Utils.SendRPC(CustomRPC.SyncRoleData, HypnotistId, ActivateTS); + if (DoReportAfterHypnosisEnds.GetBool()) ReportDeadBodyPatch.CanReport.SetAllValues(true); + + break; + case <= 6 when Count++ >= 30: + Count = 0; + notify = true; + break; } - public override void Init() - { - On = false; - Instances = []; - } + if (notify) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - Count = 0; - ActivateTS = 0; - HypnotistId = playerId; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } + public void ReceiveRPC(MessageReader reader) + { + ActivateTS = long.Parse(reader.ReadString()); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetInt(); - AURoleOptions.ShapeshifterDuration = 1f; - } + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != HypnotistId || meeting || (seer.IsModClient() && !hud) || ActivateTS == 0) return string.Empty; - public static bool OnAnyoneReport() - { - return Instances.All(x => x.ActivateTS == 0); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - OnPet(shapeshifter); - return false; - } - - public override void OnPet(PlayerControl pc) - { - Count = 0; - ActivateTS = Utils.TimeStamp; - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - Utils.SendRPC(CustomRPC.SyncRoleData, HypnotistId, ActivateTS); - - if (DoReportAfterHypnosisEnds.GetBool()) ReportDeadBodyPatch.CanReport.SetAllValues(false); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (ActivateTS == 0) return; - - var notify = false; - var timeLeft = (int)(ActivateTS + AbilityDuration.GetInt() - Utils.TimeStamp); - - switch (timeLeft) - { - case <= 0: - ActivateTS = 0; - notify = true; - pc.RpcResetAbilityCooldown(); - Utils.SendRPC(CustomRPC.SyncRoleData, HypnotistId, ActivateTS); - if (DoReportAfterHypnosisEnds.GetBool()) ReportDeadBodyPatch.CanReport.SetAllValues(true); - - break; - case <= 6 when Count++ >= 30: - Count = 0; - notify = true; - break; - } - - if (notify) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public void ReceiveRPC(MessageReader reader) - { - ActivateTS = long.Parse(reader.ReadString()); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != HypnotistId || meeting || (seer.IsModClient() && !hud) || ActivateTS == 0) return string.Empty; - - var timeLeft = (int)(ActivateTS + AbilityDuration.GetInt() - Utils.TimeStamp); - return timeLeft <= 5 ? $"\u25a9 ({timeLeft})" : "\u25a9"; - } + var timeLeft = (int)(ActivateTS + AbilityDuration.GetInt() - Utils.TimeStamp); + return timeLeft <= 5 ? $"\u25a9 ({timeLeft})" : "\u25a9"; } } \ No newline at end of file diff --git a/Roles/Impostor/ImperiusCurse.cs b/Roles/Impostor/ImperiusCurse.cs index 7a193f811..9bf9a1ab0 100644 --- a/Roles/Impostor/ImperiusCurse.cs +++ b/Roles/Impostor/ImperiusCurse.cs @@ -1,64 +1,63 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class ImperiusCurse : RoleBase { - internal class ImperiusCurse : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + SetupRoleOptions(3700, TabGroup.ImpostorRoles, CustomRoles.ImperiusCurse); - public override void SetupCustomOption() + ShapeImperiusCurseShapeshiftDuration = new FloatOptionItem(3710, "ShapeshiftDuration", new(2.5f, 300f, 2.5f), 20f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ImperiusCurse]) + .SetValueFormat(OptionFormat.Seconds); + + ImperiusCurseShapeshiftCooldown = new FloatOptionItem(3711, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.ImperiusCurse]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = ImperiusCurseShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterLeaveSkin = false; + AURoleOptions.ShapeshifterDuration = ShapeImperiusCurseShapeshiftDuration.GetFloat(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton?.OverrideText(Translator.GetString("ImperiusCurseButtonText")); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifting) { - SetupRoleOptions(3700, TabGroup.ImpostorRoles, CustomRoles.ImperiusCurse); - - ShapeImperiusCurseShapeshiftDuration = new FloatOptionItem(3710, "ShapeshiftDuration", new(2.5f, 300f, 2.5f), 20f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ImperiusCurse]) - .SetValueFormat(OptionFormat.Seconds); - - ImperiusCurseShapeshiftCooldown = new FloatOptionItem(3711, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.ImperiusCurse]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = ImperiusCurseShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterLeaveSkin = false; - AURoleOptions.ShapeshifterDuration = ShapeImperiusCurseShapeshiftDuration.GetFloat(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton?.OverrideText(Translator.GetString("ImperiusCurseButtonText")); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifting) + LateTask.New(() => { - LateTask.New(() => + if (!(!GameStates.IsInTask || !shapeshifter.IsAlive() || !target.IsAlive() || shapeshifter.inVent || target.inVent)) { - if (!(!GameStates.IsInTask || !shapeshifter.IsAlive() || !target.IsAlive() || shapeshifter.inVent || target.inVent)) - { - Vector2 originPs = target.Pos(); - target.TP(shapeshifter.Pos()); - shapeshifter.TP(originPs); - } - }, 1.5f, "ImperiusCurse TP"); - } - - return true; + Vector2 originPs = target.Pos(); + target.TP(shapeshifter.Pos()); + shapeshifter.TP(originPs); + } + }, 1.5f, "ImperiusCurse TP"); } + + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/ImpostorVanillaRoles.cs b/Roles/Impostor/ImpostorVanillaRoles.cs index fa960ae59..03a18e4fe 100644 --- a/Roles/Impostor/ImpostorVanillaRoles.cs +++ b/Roles/Impostor/ImpostorVanillaRoles.cs @@ -1,37 +1,36 @@ using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class ImpostorVanillaRoles : IVanillaSettingHolder { - internal class ImpostorVanillaRoles : IVanillaSettingHolder + public static OptionItem PhantomCooldown; + public static OptionItem PhantomDuration; + public static OptionItem ShapeshiftCD; + public static OptionItem ShapeshiftDur; + public TabGroup Tab => TabGroup.ImpostorRoles; + + public void SetupCustomOption() { - public static OptionItem PhantomCooldown; - public static OptionItem PhantomDuration; - public static OptionItem ShapeshiftCD; - public static OptionItem ShapeshiftDur; - public TabGroup Tab => TabGroup.ImpostorRoles; + SetupRoleOptions(300, Tab, CustomRoles.ImpostorEHR); + SetupRoleOptions(350, Tab, CustomRoles.PhantomEHR); - public void SetupCustomOption() - { - SetupRoleOptions(300, Tab, CustomRoles.ImpostorEHR); - SetupRoleOptions(350, Tab, CustomRoles.PhantomEHR); + PhantomCooldown = new FloatOptionItem(352, "PhantomCooldown", new(1f, 180f, 1f), 30f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.PhantomEHR]) + .SetValueFormat(OptionFormat.Seconds); - PhantomCooldown = new FloatOptionItem(352, "PhantomCooldown", new(1f, 180f, 1f), 30f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.PhantomEHR]) - .SetValueFormat(OptionFormat.Seconds); + PhantomDuration = new FloatOptionItem(353, "PhantomDuration", new(1f, 60f, 1f), 10f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.PhantomEHR]) + .SetValueFormat(OptionFormat.Seconds); - PhantomDuration = new FloatOptionItem(353, "PhantomDuration", new(1f, 60f, 1f), 10f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.PhantomEHR]) - .SetValueFormat(OptionFormat.Seconds); + SetupRoleOptions(400, Tab, CustomRoles.ShapeshifterEHR); - SetupRoleOptions(400, Tab, CustomRoles.ShapeshifterEHR); + ShapeshiftCD = new FloatOptionItem(402, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterEHR]) + .SetValueFormat(OptionFormat.Seconds); - ShapeshiftCD = new FloatOptionItem(402, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterEHR]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftDur = new FloatOptionItem(403, "ShapeshiftDuration", new(1f, 60f, 1f), 10f, Tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterEHR]) - .SetValueFormat(OptionFormat.Seconds); - } + ShapeshiftDur = new FloatOptionItem(403, "ShapeshiftDuration", new(1f, 60f, 1f), 10f, Tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.ShapeshifterEHR]) + .SetValueFormat(OptionFormat.Seconds); } } \ No newline at end of file diff --git a/Roles/Impostor/Inhibitor.cs b/Roles/Impostor/Inhibitor.cs index d275685d8..010b571d2 100644 --- a/Roles/Impostor/Inhibitor.cs +++ b/Roles/Impostor/Inhibitor.cs @@ -1,63 +1,62 @@ using System; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Inhibitor : RoleBase { - internal class Inhibitor : RoleBase + public static bool On; + private byte InhibitorId; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private byte InhibitorId; - public override bool IsEnable => On; + SetupRoleOptions(1500, TabGroup.ImpostorRoles, CustomRoles.Inhibitor); - public override void SetupCustomOption() + InhibitorCD = new FloatOptionItem(1510, "KillCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Inhibitor]) + .SetValueFormat(OptionFormat.Seconds); + + InhibitorCDAfterMeetings = new FloatOptionItem(1511, "AfterMeetingKillCooldown", new(0f, 180f, 2.5f), 22.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Inhibitor]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + InhibitorId = playerId; + } + + public override void Init() + { + On = false; + InhibitorId = byte.MaxValue; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return base.CanUseKillButton(pc) && !Utils.IsActive(SystemTypes.Electrical) && !Utils.IsActive(SystemTypes.Comms) && !Utils.IsActive(SystemTypes.MushroomMixupSabotage) && !Utils.IsActive(SystemTypes.Laboratory) && !Utils.IsActive(SystemTypes.LifeSupp) && !Utils.IsActive(SystemTypes.Reactor) && !Utils.IsActive(SystemTypes.HeliSabotage); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = InhibitorCDAfterMeetings.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Math.Abs(Main.AllPlayerKillCooldown[killer.PlayerId] - InhibitorCD.GetFloat()) > 0.5f) { - SetupRoleOptions(1500, TabGroup.ImpostorRoles, CustomRoles.Inhibitor); - - InhibitorCD = new FloatOptionItem(1510, "KillCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Inhibitor]) - .SetValueFormat(OptionFormat.Seconds); - - InhibitorCDAfterMeetings = new FloatOptionItem(1511, "AfterMeetingKillCooldown", new(0f, 180f, 2.5f), 22.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Inhibitor]) - .SetValueFormat(OptionFormat.Seconds); + Main.AllPlayerKillCooldown[killer.PlayerId] = InhibitorCD.GetFloat(); + killer.SyncSettings(); } - public override void Add(byte playerId) - { - On = true; - InhibitorId = playerId; - } + return base.OnCheckMurder(killer, target); + } - public override void Init() - { - On = false; - InhibitorId = byte.MaxValue; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return base.CanUseKillButton(pc) && !Utils.IsActive(SystemTypes.Electrical) && !Utils.IsActive(SystemTypes.Comms) && !Utils.IsActive(SystemTypes.MushroomMixupSabotage) && !Utils.IsActive(SystemTypes.Laboratory) && !Utils.IsActive(SystemTypes.LifeSupp) && !Utils.IsActive(SystemTypes.Reactor) && !Utils.IsActive(SystemTypes.HeliSabotage); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = InhibitorCDAfterMeetings.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Math.Abs(Main.AllPlayerKillCooldown[killer.PlayerId] - InhibitorCD.GetFloat()) > 0.5f) - { - Main.AllPlayerKillCooldown[killer.PlayerId] = InhibitorCD.GetFloat(); - killer.SyncSettings(); - } - - return base.OnCheckMurder(killer, target); - } - - public override void OnReportDeadBody() - { - Main.AllPlayerKillCooldown[InhibitorId] = InhibitorCDAfterMeetings.GetFloat(); - } + public override void OnReportDeadBody() + { + Main.AllPlayerKillCooldown[InhibitorId] = InhibitorCDAfterMeetings.GetFloat(); } } \ No newline at end of file diff --git a/Roles/Impostor/Kamikaze.cs b/Roles/Impostor/Kamikaze.cs index f6858cb64..cd3244a2d 100644 --- a/Roles/Impostor/Kamikaze.cs +++ b/Roles/Impostor/Kamikaze.cs @@ -3,100 +3,99 @@ using System.Linq; using static EHR.Options; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Kamikaze : RoleBase { - internal class Kamikaze : RoleBase + private static List PlayerIdList = []; + public static bool On; + + private static OptionItem MarkCD; + private static OptionItem KamikazeLimitOpt; + public static OptionItem KamikazeAbilityUseGainWithEachKill; + private byte KamikazeId; + + public List MarkedPlayers = []; + private static int Id => 643310; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private static List PlayerIdList = []; - public static bool On; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Kamikaze); - private static OptionItem MarkCD; - private static OptionItem KamikazeLimitOpt; - public static OptionItem KamikazeAbilityUseGainWithEachKill; - private byte KamikazeId; + MarkCD = new FloatOptionItem(Id + 2, "KamikazeMarkCD", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Kamikaze]) + .SetValueFormat(OptionFormat.Seconds); - public List MarkedPlayers = []; - private static int Id => 643310; + KamikazeLimitOpt = new IntegerOptionItem(Id + 3, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Kamikaze]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => On; + KamikazeAbilityUseGainWithEachKill = new FloatOptionItem(Id + 4, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Kamikaze]) + .SetValueFormat(OptionFormat.Times); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + MarkedPlayers.Clear(); + On = false; + KamikazeId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + MarkedPlayers = []; + playerId.SetAbilityUseLimit(KamikazeLimitOpt.GetInt()); + On = true; + KamikazeId = playerId; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + + if (killer.GetAbilityUseLimit() < 1) return true; + + return killer.CheckDoubleTrigger(target, () => { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Kamikaze); + MarkedPlayers.Add(target.PlayerId); + killer.SetKillCooldown(MarkCD.GetFloat()); + killer.RpcRemoveAbilityUse(); + }); + } - MarkCD = new FloatOptionItem(Id + 2, "KamikazeMarkCD", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Kamikaze]) - .SetValueFormat(OptionFormat.Seconds); + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !On) return; - KamikazeLimitOpt = new IntegerOptionItem(Id + 3, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Kamikaze]) - .SetValueFormat(OptionFormat.Times); - - KamikazeAbilityUseGainWithEachKill = new FloatOptionItem(Id + 4, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Kamikaze]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() + foreach (byte kkId in PlayerIdList) { - PlayerIdList = []; - MarkedPlayers.Clear(); - On = false; - KamikazeId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - MarkedPlayers = []; - playerId.SetAbilityUseLimit(KamikazeLimitOpt.GetInt()); - On = true; - KamikazeId = playerId; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; - - if (killer.GetAbilityUseLimit() < 1) return true; - - return killer.CheckDoubleTrigger(target, () => + if (Main.PlayerStates[kkId].Role is Kamikaze { IsEnable: true } kk) { - MarkedPlayers.Add(target.PlayerId); - killer.SetKillCooldown(MarkCD.GetFloat()); - killer.RpcRemoveAbilityUse(); - }); - } + PlayerControl kamikazePc = GetPlayerById(kk.KamikazeId); - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !On) return; - - foreach (byte kkId in PlayerIdList) - { - if (Main.PlayerStates[kkId].Role is Kamikaze { IsEnable: true } kk) + if (kamikazePc == null) { - PlayerControl kamikazePc = GetPlayerById(kk.KamikazeId); - - if (kamikazePc == null) - { - LateTask.New(() => PlayerIdList.Remove(kkId), 0.001f, log: false); - continue; - } - - if (kamikazePc.IsAlive() || kk.MarkedPlayers.Count == 0) continue; - - foreach (byte id in kk.MarkedPlayers) - { - PlayerControl victim = GetPlayerById(id); - if (victim == null || !victim.IsAlive()) continue; - - victim.Suicide(PlayerState.DeathReason.Kamikazed, kamikazePc); - } - - kk.MarkedPlayers.Clear(); - Logger.Info($"Murder {kamikazePc.GetRealName()}'s targets: {string.Join(", ", kk.MarkedPlayers.Select(x => GetPlayerById(x).GetNameWithRole()))}", "Kamikaze"); + LateTask.New(() => PlayerIdList.Remove(kkId), 0.001f, log: false); + continue; } + + if (kamikazePc.IsAlive() || kk.MarkedPlayers.Count == 0) continue; + + foreach (byte id in kk.MarkedPlayers) + { + PlayerControl victim = GetPlayerById(id); + if (victim == null || !victim.IsAlive()) continue; + + victim.Suicide(PlayerState.DeathReason.Kamikazed, kamikazePc); + } + + kk.MarkedPlayers.Clear(); + Logger.Info($"Murder {kamikazePc.GetRealName()}'s targets: {string.Join(", ", kk.MarkedPlayers.Select(x => GetPlayerById(x).GetNameWithRole()))}", "Kamikaze"); } } } diff --git a/Roles/Impostor/Kidnapper.cs b/Roles/Impostor/Kidnapper.cs index 1dd673d6f..13cbb9c81 100644 --- a/Roles/Impostor/Kidnapper.cs +++ b/Roles/Impostor/Kidnapper.cs @@ -1,48 +1,47 @@ using AmongUs.GameOptions; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Kidnapper : RoleBase { - internal class Kidnapper : RoleBase + public static bool On; + + public static OptionItem SSCD; + private static int Id => 643300; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Kidnapper); - public static OptionItem SSCD; - private static int Id => 643300; - public override bool IsEnable => On; + SSCD = new FloatOptionItem(Id + 2, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Kidnapper]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Kidnapper); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } - SSCD = new FloatOptionItem(Id + 2, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Kidnapper]) - .SetValueFormat(OptionFormat.Seconds); - } + public override bool OnShapeshift(PlayerControl kidnapper, PlayerControl target, bool shapeshifting) + { + if (kidnapper == null || target == null || !shapeshifting) return true; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } + if (!target.TP(kidnapper)) kidnapper.Notify(Utils.ColorString(Color.yellow, Translator.GetString("TargetCannotBeTeleported"))); - public override bool OnShapeshift(PlayerControl kidnapper, PlayerControl target, bool shapeshifting) - { - if (kidnapper == null || target == null || !shapeshifting) return true; + return false; + } - if (!target.TP(kidnapper)) kidnapper.Notify(Utils.ColorString(Color.yellow, Translator.GetString("TargetCannotBeTeleported"))); + public override void Init() + { + On = false; + } - return false; - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - } + public override void Add(byte playerId) + { + On = true; } } \ No newline at end of file diff --git a/Roles/Impostor/Librarian.cs b/Roles/Impostor/Librarian.cs index 05a92e8cf..98c6c8f65 100644 --- a/Roles/Impostor/Librarian.cs +++ b/Roles/Impostor/Librarian.cs @@ -6,218 +6,217 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Librarian : RoleBase { - public class Librarian : RoleBase + private const int Id = 643150; + private static List PlayerIdList = []; + + private static List Sssh = []; + + private static OptionItem Radius; + private static OptionItem ShowSSAnimation; + private static OptionItem NameDuration; + private static OptionItem SSCD; + private static OptionItem SSDur; + private static OptionItem CanKillWhileShifted; + + private (bool SILENCING, long LAST_CHANGE) IsInSilencingMode = (false, 0); + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 643150; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Librarian); - private static List Sssh = []; + Radius = new FloatOptionItem(Id + 5, "LibrarianRadius", new(0.5f, 5f, 0.5f), 3f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]) + .SetValueFormat(OptionFormat.Multiplier); - private static OptionItem Radius; - private static OptionItem ShowSSAnimation; - private static OptionItem NameDuration; - private static OptionItem SSCD; - private static OptionItem SSDur; - private static OptionItem CanKillWhileShifted; + ShowSSAnimation = new BooleanOptionItem(Id + 6, "LibrarianShowSSAnimation", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]); - private (bool SILENCING, long LAST_CHANGE) IsInSilencingMode = (false, 0); + SSCD = new FloatOptionItem(Id + 7, "ShapeshiftCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(ShowSSAnimation) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + SSDur = new FloatOptionItem(Id + 8, "LibrarianSilenceDuration", new(2.5f, 60f, 2.5f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + CanKillWhileShifted = new BooleanOptionItem(Id + 9, "CanKillWhileShifted", false, TabGroup.ImpostorRoles) + .SetParent(ShowSSAnimation); + + NameDuration = new IntegerOptionItem(Id + 10, "LibrarianNameNotifyDuration", new(1, 30, 1), 10, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + IsInSilencingMode = (false, 0); + Sssh = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + IsInSilencingMode = (false, TimeStamp); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !pc.IsShifted() || CanKillWhileShifted.GetBool(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = SSCD.GetFloat(); + else { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Librarian); - - Radius = new FloatOptionItem(Id + 5, "LibrarianRadius", new(0.5f, 5f, 0.5f), 3f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]) - .SetValueFormat(OptionFormat.Multiplier); - - ShowSSAnimation = new BooleanOptionItem(Id + 6, "LibrarianShowSSAnimation", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]); - - SSCD = new FloatOptionItem(Id + 7, "ShapeshiftCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(ShowSSAnimation) - .SetValueFormat(OptionFormat.Seconds); - - SSDur = new FloatOptionItem(Id + 8, "LibrarianSilenceDuration", new(2.5f, 60f, 2.5f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]) - .SetValueFormat(OptionFormat.Seconds); - - CanKillWhileShifted = new BooleanOptionItem(Id + 9, "CanKillWhileShifted", false, TabGroup.ImpostorRoles) - .SetParent(ShowSSAnimation); - - NameDuration = new IntegerOptionItem(Id + 10, "LibrarianNameNotifyDuration", new(1, 30, 1), 10, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Librarian]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - IsInSilencingMode = (false, 0); - Sssh = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - IsInSilencingMode = (false, TimeStamp); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !pc.IsShifted() || CanKillWhileShifted.GetBool(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = SSCD.GetFloat(); - else - { - AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = SSDur.GetFloat(); - } - } - - private static void SendRPC(byte playerId, bool isInSilenceMode) - { - if (!DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLibrarianMode, SendOption.Reliable); - writer.Write(playerId); - writer.Write(isInSilenceMode); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - bool isInSilenceMode = reader.ReadBoolean(); - - if (Main.PlayerStates[playerId].Role is not Librarian lr) return; - - lr.IsInSilencingMode = (isInSilenceMode, TimeStamp); - } - - public static bool OnAnyoneReport(PlayerControl reporter) - { - if (reporter == null || PlayerIdList.Count == 0) return true; - - PlayerControl librarian = null; - float silenceRadius = Radius.GetFloat(); - - foreach (byte id in PlayerIdList) - { - if (Main.PlayerStates[id].Role is not Librarian lr) continue; - - if (!lr.IsInSilencingMode.SILENCING) continue; - - PlayerControl pc = GetPlayerById(id); - if (pc == null || !pc.IsAlive()) continue; - - if (Vector2.Distance(pc.Pos(), reporter.Pos()) <= silenceRadius) librarian = pc; - } - - if (librarian == null) return true; - - reporter.SetRealKiller(librarian); - - if (librarian.RpcCheckAndMurder(reporter)) - { - Logger.Info(" Counter kill (report during and in range of silence)", "Librarian"); - Sssh.Add(librarian.PlayerId); - NotifyRoles(SpecifyTarget: librarian); - - LateTask.New(() => - { - Sssh.Remove(librarian.PlayerId); - NotifyRoles(SpecifyTarget: librarian); - }, NameDuration.GetInt(), "Librarian sssh text"); - } - - return false; - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) - { - if (!IsEnable) return false; - - if (pc == null) return false; - - ChangeSilencingMode(pc); - - return (!shapeshifting && !UseUnshiftTrigger.GetBool()) || ShowSSAnimation.GetBool(); - } - - public override bool OnVanish(PlayerControl pc) - { - if (!IsEnable) return false; - - if (pc == null) return false; - - ChangeSilencingMode(pc); - - return false; - } - - private void ChangeSilencingMode(PlayerControl pc) - { - IsInSilencingMode = (!IsInSilencingMode.SILENCING, TimeStamp); - SendRPC(pc.PlayerId, IsInSilencingMode.SILENCING); - NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable) return; - - if (ShowSSAnimation.GetBool() && !UsePhantomBasis.GetBool() && !UseUnshiftTrigger.GetBool()) return; - - if (IsInSilencingMode.SILENCING && IsInSilencingMode.LAST_CHANGE + SSDur.GetInt() < TimeStamp) - { - byte id = pc.PlayerId; - IsInSilencingMode = (!IsInSilencingMode.SILENCING, TimeStamp); - SendRPC(id, IsInSilencingMode.SILENCING); - } - } - - public override void OnReportDeadBody() - { - if (!IsEnable) return; - - IsInSilencingMode = (false, TimeStamp); - Sssh.Clear(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - var result = string.Empty; - if (target.Is(CustomRoles.Librarian)) result += GetNameTextForSuffix(target.PlayerId); - - if (hud || (seer.PlayerId == target.PlayerId && !seer.IsModClient())) result += GetSelfSuffixAndHudText(target.PlayerId); - - return result; - } - - private static string GetNameTextForSuffix(byte playerId) - { - if (Main.PlayerStates[playerId].Role is not Librarian lr) return string.Empty; - - return lr.IsEnable && lr.IsInSilencingMode.SILENCING && Sssh.Contains(playerId) && GameStates.IsInTask - ? GetString("LibrarianNameText") - : string.Empty; - } - - private static string GetSelfSuffixAndHudText(byte playerId) - { - if (Main.PlayerStates[playerId].Role is not Librarian lr) return string.Empty; - - if (!lr.IsEnable || !GameStates.IsInTask) return string.Empty; - - return string.Format(GetString("LibrarianModeText"), lr.IsInSilencingMode.SILENCING ? GetString("LibrarianSilenceMode") : GetString("LibrarianNormalMode")); + AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = SSDur.GetFloat(); } } + + private static void SendRPC(byte playerId, bool isInSilenceMode) + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLibrarianMode, SendOption.Reliable); + writer.Write(playerId); + writer.Write(isInSilenceMode); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + bool isInSilenceMode = reader.ReadBoolean(); + + if (Main.PlayerStates[playerId].Role is not Librarian lr) return; + + lr.IsInSilencingMode = (isInSilenceMode, TimeStamp); + } + + public static bool OnAnyoneReport(PlayerControl reporter) + { + if (reporter == null || PlayerIdList.Count == 0) return true; + + PlayerControl librarian = null; + float silenceRadius = Radius.GetFloat(); + + foreach (byte id in PlayerIdList) + { + if (Main.PlayerStates[id].Role is not Librarian lr) continue; + + if (!lr.IsInSilencingMode.SILENCING) continue; + + PlayerControl pc = GetPlayerById(id); + if (pc == null || !pc.IsAlive()) continue; + + if (Vector2.Distance(pc.Pos(), reporter.Pos()) <= silenceRadius) librarian = pc; + } + + if (librarian == null) return true; + + reporter.SetRealKiller(librarian); + + if (librarian.RpcCheckAndMurder(reporter)) + { + Logger.Info(" Counter kill (report during and in range of silence)", "Librarian"); + Sssh.Add(librarian.PlayerId); + NotifyRoles(SpecifyTarget: librarian); + + LateTask.New(() => + { + Sssh.Remove(librarian.PlayerId); + NotifyRoles(SpecifyTarget: librarian); + }, NameDuration.GetInt(), "Librarian sssh text"); + } + + return false; + } + + public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) + { + if (!IsEnable) return false; + + if (pc == null) return false; + + ChangeSilencingMode(pc); + + return (!shapeshifting && !UseUnshiftTrigger.GetBool()) || ShowSSAnimation.GetBool(); + } + + public override bool OnVanish(PlayerControl pc) + { + if (!IsEnable) return false; + + if (pc == null) return false; + + ChangeSilencingMode(pc); + + return false; + } + + private void ChangeSilencingMode(PlayerControl pc) + { + IsInSilencingMode = (!IsInSilencingMode.SILENCING, TimeStamp); + SendRPC(pc.PlayerId, IsInSilencingMode.SILENCING); + NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable) return; + + if (ShowSSAnimation.GetBool() && !UsePhantomBasis.GetBool() && !UseUnshiftTrigger.GetBool()) return; + + if (IsInSilencingMode.SILENCING && IsInSilencingMode.LAST_CHANGE + SSDur.GetInt() < TimeStamp) + { + byte id = pc.PlayerId; + IsInSilencingMode = (!IsInSilencingMode.SILENCING, TimeStamp); + SendRPC(id, IsInSilencingMode.SILENCING); + } + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + IsInSilencingMode = (false, TimeStamp); + Sssh.Clear(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + var result = string.Empty; + if (target.Is(CustomRoles.Librarian)) result += GetNameTextForSuffix(target.PlayerId); + + if (hud || (seer.PlayerId == target.PlayerId && !seer.IsModClient())) result += GetSelfSuffixAndHudText(target.PlayerId); + + return result; + } + + private static string GetNameTextForSuffix(byte playerId) + { + if (Main.PlayerStates[playerId].Role is not Librarian lr) return string.Empty; + + return lr.IsEnable && lr.IsInSilencingMode.SILENCING && Sssh.Contains(playerId) && GameStates.IsInTask + ? GetString("LibrarianNameText") + : string.Empty; + } + + private static string GetSelfSuffixAndHudText(byte playerId) + { + if (Main.PlayerStates[playerId].Role is not Librarian lr) return string.Empty; + + if (!lr.IsEnable || !GameStates.IsInTask) return string.Empty; + + return string.Format(GetString("LibrarianModeText"), lr.IsInSilencingMode.SILENCING ? GetString("LibrarianSilenceMode") : GetString("LibrarianNormalMode")); + } } \ No newline at end of file diff --git a/Roles/Impostor/Lurker.cs b/Roles/Impostor/Lurker.cs index dab685567..4a462cc55 100644 --- a/Roles/Impostor/Lurker.cs +++ b/Roles/Impostor/Lurker.cs @@ -1,62 +1,61 @@ using System.Collections.Generic; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Lurker : RoleBase { - public class Lurker : RoleBase + private const int Id = 2100; + public static List PlayerIdList = []; + + private static OptionItem DefaultKillCooldown; + private static OptionItem ReduceKillCooldown; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 2100; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Lurker); - private static OptionItem DefaultKillCooldown; - private static OptionItem ReduceKillCooldown; + DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lurker]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 10f, 1f), 1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Lurker]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Lurker); + public override void Init() + { + PlayerIdList = []; + } - DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lurker]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 10f, 1f), 1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Lurker]) - .SetValueFormat(OptionFormat.Seconds); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = DefaultKillCooldown.GetFloat(); + } - public override void Init() - { - PlayerIdList = []; - } + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (!pc.Is(CustomRoles.Lurker)) return; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } + float newCd = Main.AllPlayerKillCooldown[pc.PlayerId] - ReduceKillCooldown.GetFloat(); + if (newCd <= 0) return; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = DefaultKillCooldown.GetFloat(); - } + Main.AllPlayerKillCooldown[pc.PlayerId] = newCd; + pc.SyncSettings(); + } - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (!pc.Is(CustomRoles.Lurker)) return; - - float newCd = Main.AllPlayerKillCooldown[pc.PlayerId] - ReduceKillCooldown.GetFloat(); - if (newCd <= 0) return; - - Main.AllPlayerKillCooldown[pc.PlayerId] = newCd; - pc.SyncSettings(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - killer.ResetKillCooldown(); - killer.SyncSettings(); - return true; - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + killer.ResetKillCooldown(); + killer.SyncSettings(); + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/Mafia.cs b/Roles/Impostor/Mafia.cs index f04b5afb1..4bd6b0294 100644 --- a/Roles/Impostor/Mafia.cs +++ b/Roles/Impostor/Mafia.cs @@ -10,227 +10,226 @@ using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Mafia : RoleBase { - internal class Mafia : RoleBase + public static bool On; + + public static Dictionary MafiaRevenged = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupRoleOptions(3100, TabGroup.ImpostorRoles, CustomRoles.Mafia); - public static Dictionary MafiaRevenged = []; - public override bool IsEnable => On; + MafiaCanKillNum = new IntegerOptionItem(3200, "MafiaCanKillNum", new(0, 15, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafia]) + .SetValueFormat(OptionFormat.Players); - public override void SetupCustomOption() + LegacyMafia = new BooleanOptionItem(3210, "LegacyMafia", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafia]); + + MafiaShapeshiftCD = new FloatOptionItem(3211, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(LegacyMafia) + .SetValueFormat(OptionFormat.Seconds); + + MafiaShapeshiftDur = new FloatOptionItem(3212, "ShapeshiftDuration", new(1f, 180f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(LegacyMafia) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return Utils.CanMafiaKill(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = MafiaShapeshiftCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = MafiaShapeshiftDur.GetFloat(); + } + + public static bool MafiaMsgCheck(PlayerControl pc, string msg, bool isUI = false) + { + if (!AmongUsClient.Instance.AmHost) return false; + + if (!GameStates.IsInGame || pc == null) return false; + + if (!pc.Is(CustomRoles.Mafia)) return false; + + msg = msg.Trim().ToLower().Replace(" ", string.Empty); + if (msg.Length < 3 || !msg.StartsWith("/rv")) return false; + + if (MafiaCanKillNum.GetInt() < 1) { - SetupRoleOptions(3100, TabGroup.ImpostorRoles, CustomRoles.Mafia); - - MafiaCanKillNum = new IntegerOptionItem(3200, "MafiaCanKillNum", new(0, 15, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafia]) - .SetValueFormat(OptionFormat.Players); - - LegacyMafia = new BooleanOptionItem(3210, "LegacyMafia", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafia]); - - MafiaShapeshiftCD = new FloatOptionItem(3211, "ShapeshiftCooldown", new(1f, 180f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(LegacyMafia) - .SetValueFormat(OptionFormat.Seconds); - - MafiaShapeshiftDur = new FloatOptionItem(3212, "ShapeshiftDuration", new(1f, 180f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(LegacyMafia) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return Utils.CanMafiaKill(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = MafiaShapeshiftCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = MafiaShapeshiftDur.GetFloat(); - } - - public static bool MafiaMsgCheck(PlayerControl pc, string msg, bool isUI = false) - { - if (!AmongUsClient.Instance.AmHost) return false; - - if (!GameStates.IsInGame || pc == null) return false; - - if (!pc.Is(CustomRoles.Mafia)) return false; - - msg = msg.Trim().ToLower().Replace(" ", string.Empty); - if (msg.Length < 3 || !msg.StartsWith("/rv")) return false; - - if (MafiaCanKillNum.GetInt() < 1) - { - if (!isUI) - Utils.SendMessage(GetString("MafiaKillDisable"), pc.PlayerId); - else - pc.ShowPopUp(GetString("MafiaKillDisable")); - - return true; - } - - if (!pc.Data.IsDead) - { - Utils.SendMessage(GetString("MafiaAliveKill"), pc.PlayerId); - return true; - } - - if (msg == "/rv") - { - string text = GetString("PlayerIdList"); - text = Main.AllAlivePlayerControls.Aggregate(text, (current, npc) => current + "\n" + npc.PlayerId + " → (" + npc.GetDisplayRoleName() + ") " + npc.GetRealName()); - - Utils.SendMessage(text, pc.PlayerId); - return true; - } - - if (!MafiaRevenged.TryAdd(pc.PlayerId, 0)) - { - if (MafiaRevenged[pc.PlayerId] >= MafiaCanKillNum.GetInt()) - { - if (!isUI) - Utils.SendMessage(GetString("MafiaKillMax"), pc.PlayerId); - else - pc.ShowPopUp(GetString("MafiaKillMax")); - - return true; - } - } - - PlayerControl target; - - try - { - int targetId = int.Parse(msg.Replace("/rv", string.Empty)); - target = Utils.GetPlayerById(targetId); - } - catch - { - if (!isUI) - Utils.SendMessage(GetString("MafiaKillDead"), pc.PlayerId); - else - pc.ShowPopUp(GetString("MafiaKillDead")); - - return true; - } - - if (target == null || target.Data.IsDead) - { - if (!isUI) - Utils.SendMessage(GetString("MafiaKillDead"), pc.PlayerId); - else - pc.ShowPopUp(GetString("MafiaKillDead")); - - return true; - } - - if (target.Is(CustomRoles.Pestilence)) - { - if (!isUI) - Utils.SendMessage(GetString("PestilenceImmune"), pc.PlayerId); - else - pc.ShowPopUp(GetString("PestilenceImmune")); - - return true; - } - - Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} 复仇了 {target.GetNameWithRole().RemoveHtmlTags()}", "Mafia"); - - string Name = target.GetRealName(); - - MafiaRevenged[pc.PlayerId]++; - - CustomSoundsManager.RPCPlayCustomSoundAll("AWP"); - - LateTask.New(() => - { - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Revenge; - target.SetRealKiller(pc); - - if (GameStates.IsMeeting) - { - target.RpcGuesserMurderPlayer(); - - //死者检查 - Utils.AfterPlayerDeathTasks(target, true); - - Utils.NotifyRoles(GameStates.IsMeeting, NoCache: true); - } - else - { - target.Kill(target); - Main.PlayerStates[target.PlayerId].SetDead(); - } - - LateTask.New(() => { Utils.SendMessage(string.Format(GetString("MafiaKillSucceed"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mafia), GetString("MafiaRevengeTitle"))); }, 0.6f, "Mafia Kill"); - }, 0.2f, "Mafia Kill"); + if (!isUI) + Utils.SendMessage(GetString("MafiaKillDisable"), pc.PlayerId); + else + pc.ShowPopUp(GetString("MafiaKillDisable")); return true; } - private static void SendRPC(byte playerId) + if (!pc.Data.IsDead) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MafiaRevenge, SendOption.Reliable); - writer.Write(playerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + Utils.SendMessage(GetString("MafiaAliveKill"), pc.PlayerId); + return true; } - public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + if (msg == "/rv") { - int PlayerId = reader.ReadByte(); - MafiaMsgCheck(pc, $"/rv {PlayerId}", true); + string text = GetString("PlayerIdList"); + text = Main.AllAlivePlayerControls.Aggregate(text, (current, npc) => current + "\n" + npc.PlayerId + " → (" + npc.GetDisplayRoleName() + ") " + npc.GetRealName()); + + Utils.SendMessage(text, pc.PlayerId); + return true; } - private static void MafiaOnClick(byte playerId /*, MeetingHud __instance*/) + if (!MafiaRevenged.TryAdd(pc.PlayerId, 0)) { - Logger.Msg($"Click: ID {playerId}", "Mafia UI"); - PlayerControl pc = Utils.GetPlayerById(playerId); - if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + if (MafiaRevenged[pc.PlayerId] >= MafiaCanKillNum.GetInt()) + { + if (!isUI) + Utils.SendMessage(GetString("MafiaKillMax"), pc.PlayerId); + else + pc.ShowPopUp(GetString("MafiaKillMax")); - if (AmongUsClient.Instance.AmHost) - MafiaMsgCheck(PlayerControl.LocalPlayer, $"/rv {playerId}", true); + return true; + } + } + + PlayerControl target; + + try + { + int targetId = int.Parse(msg.Replace("/rv", string.Empty)); + target = Utils.GetPlayerById(targetId); + } + catch + { + if (!isUI) + Utils.SendMessage(GetString("MafiaKillDead"), pc.PlayerId); else - SendRPC(playerId); + pc.ShowPopUp(GetString("MafiaKillDead")); + + return true; } - public static void CreateJudgeButton(MeetingHud __instance) + if (target == null || target.Data.IsDead) { - foreach (PlayerVoteArea pva in __instance.playerStates.ToArray()) - { - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null || !pc.IsAlive()) continue; + if (!isUI) + Utils.SendMessage(GetString("MafiaKillDead"), pc.PlayerId); + else + pc.ShowPopUp(GetString("MafiaKillDead")); - GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; - GameObject targetBox = Object.Instantiate(template, pva.transform); - targetBox.name = "ShootButton"; - targetBox.transform.localPosition = new(-0.95f, 0.03f, -1.31f); - var renderer = targetBox.GetComponent(); - renderer.sprite = CustomButton.Get("MeetingKillButton"); - var button = targetBox.GetComponent(); - button.OnClick.RemoveAllListeners(); - button.OnClick.AddListener((Action)(() => MafiaOnClick(pva.TargetPlayerId))); - } + return true; } - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] - private class StartMeetingPatch + if (target.Is(CustomRoles.Pestilence)) { - public static void Postfix(MeetingHud __instance) + if (!isUI) + Utils.SendMessage(GetString("PestilenceImmune"), pc.PlayerId); + else + pc.ShowPopUp(GetString("PestilenceImmune")); + + return true; + } + + Logger.Info($"{pc.GetNameWithRole().RemoveHtmlTags()} 复仇了 {target.GetNameWithRole().RemoveHtmlTags()}", "Mafia"); + + string Name = target.GetRealName(); + + MafiaRevenged[pc.PlayerId]++; + + CustomSoundsManager.RPCPlayCustomSoundAll("AWP"); + + LateTask.New(() => + { + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Revenge; + target.SetRealKiller(pc); + + if (GameStates.IsMeeting) { - if (PlayerControl.LocalPlayer.Is(CustomRoles.Mafia) && !PlayerControl.LocalPlayer.IsAlive()) CreateJudgeButton(__instance); + target.RpcGuesserMurderPlayer(); + + //死者检查 + Utils.AfterPlayerDeathTasks(target, true); + + Utils.NotifyRoles(GameStates.IsMeeting, NoCache: true); } + else + { + target.Kill(target); + Main.PlayerStates[target.PlayerId].SetDead(); + } + + LateTask.New(() => { Utils.SendMessage(string.Format(GetString("MafiaKillSucceed"), Name), 255, Utils.ColorString(Utils.GetRoleColor(CustomRoles.Mafia), GetString("MafiaRevengeTitle"))); }, 0.6f, "Mafia Kill"); + }, 0.2f, "Mafia Kill"); + + return true; + } + + private static void SendRPC(byte playerId) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MafiaRevenge, SendOption.Reliable); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader, PlayerControl pc) + { + int PlayerId = reader.ReadByte(); + MafiaMsgCheck(pc, $"/rv {PlayerId}", true); + } + + private static void MafiaOnClick(byte playerId /*, MeetingHud __instance*/) + { + Logger.Msg($"Click: ID {playerId}", "Mafia UI"); + PlayerControl pc = Utils.GetPlayerById(playerId); + if (pc == null || !pc.IsAlive() || !GameStates.IsVoting) return; + + if (AmongUsClient.Instance.AmHost) + MafiaMsgCheck(PlayerControl.LocalPlayer, $"/rv {playerId}", true); + else + SendRPC(playerId); + } + + public static void CreateJudgeButton(MeetingHud __instance) + { + foreach (PlayerVoteArea pva in __instance.playerStates.ToArray()) + { + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null || !pc.IsAlive()) continue; + + GameObject template = pva.Buttons.transform.Find("CancelButton").gameObject; + GameObject targetBox = Object.Instantiate(template, pva.transform); + targetBox.name = "ShootButton"; + targetBox.transform.localPosition = new(-0.95f, 0.03f, -1.31f); + var renderer = targetBox.GetComponent(); + renderer.sprite = CustomButton.Get("MeetingKillButton"); + var button = targetBox.GetComponent(); + button.OnClick.RemoveAllListeners(); + button.OnClick.AddListener((Action)(() => MafiaOnClick(pva.TargetPlayerId))); + } + } + + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + private class StartMeetingPatch + { + public static void Postfix(MeetingHud __instance) + { + if (PlayerControl.LocalPlayer.Is(CustomRoles.Mafia) && !PlayerControl.LocalPlayer.IsAlive()) CreateJudgeButton(__instance); } } } \ No newline at end of file diff --git a/Roles/Impostor/Mafioso.cs b/Roles/Impostor/Mafioso.cs index 0dc16c4f2..1b08294ec 100644 --- a/Roles/Impostor/Mafioso.cs +++ b/Roles/Impostor/Mafioso.cs @@ -7,299 +7,298 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +/* + * Tiers and their perks + * + * Tier 0: Regular Impostor, higher KCD: normal KCD * 1.25, short kill range + * Tier 1: Increased Kill Range to Long, normal KCD + * Tier 2: Lower KCD: normal KCD * 0.85 + * Tier 3: Dual Pistols (2 separate KCDs, KCDs = normal KCD * 1.5) + * Tier 4: Normal KCD for both pistols + * Tier 5: Delayed Kills + * + * XP rewards + * + * Kill, Sabotage, Unique vent usage, Non-teamed player ejected + * XP needed to level up: 100 + * + */ +public class Mafioso : RoleBase { - /* - * Tiers and their perks - * - * Tier 0: Regular Impostor, higher KCD: normal KCD * 1.25, short kill range - * Tier 1: Increased Kill Range to Long, normal KCD - * Tier 2: Lower KCD: normal KCD * 0.85 - * Tier 3: Dual Pistols (2 separate KCDs, KCDs = normal KCD * 1.5) - * Tier 4: Normal KCD for both pistols - * Tier 5: Delayed Kills - * - * XP rewards - * - * Kill, Sabotage, Unique vent usage, Non-teamed player ejected - * XP needed to level up: 100 - * - */ - public class Mafioso : RoleBase + private const int Id = 642200; + private static List PlayerIdList = []; + + private static OptionItem Delay; + private static OptionItem RewardForKilling; + private static OptionItem RewardForSabotaging; + private static OptionItem RewardForVenting; + private static OptionItem RewardForOtherPlayerEjected; + private long lastUpdate; + + private byte MafiosoId; + + private int Pistol1CD; + private int Pistol2CD; + + private List PreviouslyUsedVents = []; + + private int Tier; + private int XP; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642200; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Mafioso); - private static OptionItem Delay; - private static OptionItem RewardForKilling; - private static OptionItem RewardForSabotaging; - private static OptionItem RewardForVenting; - private static OptionItem RewardForOtherPlayerEjected; - private long lastUpdate; + Delay = new IntegerOptionItem(Id + 10, "MafiosoDelay", new(1, 10, 1), 3, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]) + .SetValueFormat(OptionFormat.Seconds); - private byte MafiosoId; + RewardForKilling = new IntegerOptionItem(Id + 11, "MafiosoRewardForKilling", new(0, 100, 5), 40, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); - private int Pistol1CD; - private int Pistol2CD; + RewardForSabotaging = new IntegerOptionItem(Id + 12, "MafiosoRewardForSabotaging", new(0, 100, 5), 25, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); - private List PreviouslyUsedVents = []; + RewardForVenting = new IntegerOptionItem(Id + 13, "MafiosoRewardForVenting", new(0, 100, 5), 10, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); - private int Tier; - private int XP; + RewardForOtherPlayerEjected = new IntegerOptionItem(Id + 14, "MafiosoRewardForOtherPlayerEjected", new(0, 100, 5), 30, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + PreviouslyUsedVents = []; + Tier = 0; + XP = 0; + Pistol1CD = 0; + Pistol2CD = 0; + lastUpdate = TimeStamp + 30; + MafiosoId = byte.MaxValue; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + lastUpdate = TimeStamp + 8; + MafiosoId = playerId; + PreviouslyUsedVents = []; + Tier = 0; + XP = 0; + Pistol1CD = 0; + Pistol2CD = 0; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetInt(Int32OptionNames.KillDistance, Tier > 0 ? 2 : 0); + } + + public override void SetKillCooldown(byte playerId) + { + Main.AllPlayerKillCooldown[playerId] = Tier switch { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Mafioso); + 0 => (float)Math.Round(DefaultKillCooldown * 1.25, 2), + 1 => DefaultKillCooldown, + 2 => (float)Math.Round(DefaultKillCooldown * 0.85, 2), + _ => 1f + }; + } - Delay = new IntegerOptionItem(Id + 10, "MafiosoDelay", new(1, 10, 1), 3, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]) - .SetValueFormat(OptionFormat.Seconds); + private void SendRPC() + { + if (!IsEnable || !DoRPC) return; - RewardForKilling = new IntegerOptionItem(Id + 11, "MafiosoRewardForKilling", new(0, 100, 5), 40, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncMafiosoData, SendOption.Reliable); + writer.Write(MafiosoId); + writer.Write(Tier); + writer.Write(XP); + writer.Write(PreviouslyUsedVents.Count); - RewardForSabotaging = new IntegerOptionItem(Id + 12, "MafiosoRewardForSabotaging", new(0, 100, 5), 25, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); + if (PreviouslyUsedVents.Count > 0) + foreach (int vent in PreviouslyUsedVents.ToArray()) + writer.Write(vent); - RewardForVenting = new IntegerOptionItem(Id + 13, "MafiosoRewardForVenting", new(0, 100, 5), 10, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - RewardForOtherPlayerEjected = new IntegerOptionItem(Id + 14, "MafiosoRewardForOtherPlayerEjected", new(0, 100, 5), 30, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mafioso]); + public void ReceiveRPC(MessageReader reader) + { + Tier = reader.ReadInt32(); + XP = reader.ReadInt32(); + PreviouslyUsedVents.Clear(); + int elements = reader.ReadInt32(); + + if (elements > 0) + for (var i = 0; i < elements; i++) + PreviouslyUsedVents.Add(reader.ReadInt32()); + } + + private void SendRPCSyncPistolCD() + { + if (!IsEnable || !DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncMafiosoPistolCD, SendOption.Reliable); + writer.Write(MafiosoId); + writer.Write(Pistol1CD); + writer.Write(Pistol2CD); + writer.Write(lastUpdate.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPCSyncPistolCD(MessageReader reader) + { + Pistol1CD = reader.ReadInt32(); + Pistol2CD = reader.ReadInt32(); + lastUpdate = long.Parse(reader.ReadString()); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !IsEnable || pc == null || !pc.IsAlive()) return; + + if (XP >= 100 && Tier < 5) + { + XP -= 100; + Tier++; + + SendRPC(); + pc.MarkDirtySettings(); + pc.ResetKillCooldown(); + pc.Notify(GetString("MafiosoLevelUp")); } - public override void Init() + if (lastUpdate >= TimeStamp) return; + + lastUpdate = TimeStamp; + + int before1CD = Pistol1CD; + int before2CD = Pistol2CD; + + if (Pistol1CD > 0) Pistol1CD--; + + if (Pistol2CD > 0) Pistol2CD--; + + if (before1CD != Pistol1CD || before2CD != Pistol2CD) { - PlayerIdList = []; - PreviouslyUsedVents = []; - Tier = 0; - XP = 0; - Pistol1CD = 0; - Pistol2CD = 0; - lastUpdate = TimeStamp + 30; - MafiosoId = byte.MaxValue; + NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + if (pc.IsNonHostModClient()) SendRPCSyncPistolCD(); } + } - public override void Add(byte playerId) + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!GameStates.IsInTask || target == null || killer == null || !killer.Is(CustomRoles.Mafioso) || Tier < 3 || !IsEnable) return true; + + if (Pistol1CD > 0 && Pistol2CD > 0) return false; + + int KCD = Tier >= 4 ? (int)Math.Round(DefaultKillCooldown) : (int)Math.Round(DefaultKillCooldown * 1.5); + KCD++; + + if (Pistol1CD <= 0) { - PlayerIdList.Add(playerId); - lastUpdate = TimeStamp + 8; - MafiosoId = playerId; - PreviouslyUsedVents = []; - Tier = 0; - XP = 0; - Pistol1CD = 0; - Pistol2CD = 0; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetInt(Int32OptionNames.KillDistance, Tier > 0 ? 2 : 0); - } - - public override void SetKillCooldown(byte playerId) - { - Main.AllPlayerKillCooldown[playerId] = Tier switch - { - 0 => (float)Math.Round(DefaultKillCooldown * 1.25, 2), - 1 => DefaultKillCooldown, - 2 => (float)Math.Round(DefaultKillCooldown * 0.85, 2), - _ => 1f - }; - } - - private void SendRPC() - { - if (!IsEnable || !DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncMafiosoData, SendOption.Reliable); - writer.Write(MafiosoId); - writer.Write(Tier); - writer.Write(XP); - writer.Write(PreviouslyUsedVents.Count); - - if (PreviouslyUsedVents.Count > 0) - foreach (int vent in PreviouslyUsedVents.ToArray()) - writer.Write(vent); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(MessageReader reader) - { - Tier = reader.ReadInt32(); - XP = reader.ReadInt32(); - PreviouslyUsedVents.Clear(); - int elements = reader.ReadInt32(); - - if (elements > 0) - for (var i = 0; i < elements; i++) - PreviouslyUsedVents.Add(reader.ReadInt32()); - } - - private void SendRPCSyncPistolCD() - { - if (!IsEnable || !DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncMafiosoPistolCD, SendOption.Reliable); - writer.Write(MafiosoId); - writer.Write(Pistol1CD); - writer.Write(Pistol2CD); - writer.Write(lastUpdate.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPCSyncPistolCD(MessageReader reader) - { - Pistol1CD = reader.ReadInt32(); - Pistol2CD = reader.ReadInt32(); - lastUpdate = long.Parse(reader.ReadString()); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !IsEnable || pc == null || !pc.IsAlive()) return; - - if (XP >= 100 && Tier < 5) - { - XP -= 100; - Tier++; - - SendRPC(); - pc.MarkDirtySettings(); - pc.ResetKillCooldown(); - pc.Notify(GetString("MafiosoLevelUp")); - } - - if (lastUpdate >= TimeStamp) return; - - lastUpdate = TimeStamp; - - int before1CD = Pistol1CD; - int before2CD = Pistol2CD; - - if (Pistol1CD > 0) Pistol1CD--; - - if (Pistol2CD > 0) Pistol2CD--; - - if (before1CD != Pistol1CD || before2CD != Pistol2CD) - { - NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - if (pc.IsNonHostModClient()) SendRPCSyncPistolCD(); - } - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!GameStates.IsInTask || target == null || killer == null || !killer.Is(CustomRoles.Mafioso) || Tier < 3 || !IsEnable) return true; - - if (Pistol1CD > 0 && Pistol2CD > 0) return false; - - int KCD = Tier >= 4 ? (int)Math.Round(DefaultKillCooldown) : (int)Math.Round(DefaultKillCooldown * 1.5); - KCD++; - - if (Pistol1CD <= 0) - { - Pistol1CD = KCD; - NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); - } - else if (Pistol2CD <= 0) - { - Pistol2CD = KCD; - NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); - } - - if (Tier >= 5) - { - LateTask.New(() => - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse ---- Can be null since it's a task that completes later - if (target != null && target.IsAlive() && GameStates.IsInTask) target.Suicide(PlayerState.DeathReason.Kill, killer); - }, Delay.GetInt(), "Mafioso Tier 5 Kill Delay"); - - return false; - } - - if (Pistol1CD > 1 && Pistol2CD > 1) LateTask.New(() => { killer.SetKillCooldown(Math.Min(Pistol1CD, Pistol2CD) - 1); }, 0.1f, "Mafioso SetKillCooldown"); - - return true; - } - - public override void OnReportDeadBody() - { - if (!IsEnable) return; - - PreviouslyUsedVents.Clear(); - int KCD = Tier >= 4 ? (int)Math.Round(DefaultKillCooldown) : (int)Math.Round(DefaultKillCooldown * 1.5); - KCD++; Pistol1CD = KCD; + NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); + } + else if (Pistol2CD <= 0) + { Pistol2CD = KCD; - lastUpdate = TimeStamp; - SendRPC(); - SendRPCSyncPistolCD(); + NotifyRoles(SpecifySeer: killer, SpecifyTarget: killer); } - public override string GetProgressText(byte id, bool comms) + if (Tier >= 5) { - return id.IsPlayerModClient() ? string.Empty : string.Format(GetString("MafiosoProgressText"), Tier, XP); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || Main.PlayerStates[seer.PlayerId].Role is not Mafioso { IsEnable: true } mo) return string.Empty; - - if (mo.Tier >= 3) + LateTask.New(() => { - string CD; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse ---- Can be null since it's a task that completes later + if (target != null && target.IsAlive() && GameStates.IsInTask) target.Suicide(PlayerState.DeathReason.Kill, killer); + }, Delay.GetInt(), "Mafioso Tier 5 Kill Delay"); - if (mo.Pistol1CD <= 0 && mo.Pistol2CD <= 0) - CD = "Can Kill"; - else - CD = $"CD: {Math.Min(mo.Pistol1CD, mo.Pistol2CD)}s"; - - return string.Format(GetString("MafiosoHUDTextWithDualPistols"), mo.Tier, mo.XP, CD); - } - - return string.Format(GetString("MafiosoHUDText"), mo.Tier, mo.XP); + return false; } - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return; + if (Pistol1CD > 1 && Pistol2CD > 1) LateTask.New(() => { killer.SetKillCooldown(Math.Min(Pistol1CD, Pistol2CD) - 1); }, 0.1f, "Mafioso SetKillCooldown"); - XP += RewardForKilling.GetInt(); - SendRPC(); + return true; + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + PreviouslyUsedVents.Clear(); + int KCD = Tier >= 4 ? (int)Math.Round(DefaultKillCooldown) : (int)Math.Round(DefaultKillCooldown * 1.5); + KCD++; + Pistol1CD = KCD; + Pistol2CD = KCD; + lastUpdate = TimeStamp; + SendRPC(); + SendRPCSyncPistolCD(); + } + + public override string GetProgressText(byte id, bool comms) + { + return id.IsPlayerModClient() ? string.Empty : string.Format(GetString("MafiosoProgressText"), Tier, XP); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || Main.PlayerStates[seer.PlayerId].Role is not Mafioso { IsEnable: true } mo) return string.Empty; + + if (mo.Tier >= 3) + { + string CD; + + if (mo.Pistol1CD <= 0 && mo.Pistol2CD <= 0) + CD = "Can Kill"; + else + CD = $"CD: {Math.Min(mo.Pistol1CD, mo.Pistol2CD)}s"; + + return string.Format(GetString("MafiosoHUDTextWithDualPistols"), mo.Tier, mo.XP, CD); } - public override void OnEnterVent(PlayerControl pc, Vent vent) + return string.Format(GetString("MafiosoHUDText"), mo.Tier, mo.XP); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return; + + XP += RewardForKilling.GetInt(); + SendRPC(); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (!IsEnable) return; + + if (PreviouslyUsedVents.Contains(vent.Id)) return; + + PreviouslyUsedVents.Add(vent.Id); + XP += RewardForVenting.GetInt(); + SendRPC(); + } + + public override bool OnSabotage(PlayerControl pc) + { + XP += RewardForSabotaging.GetInt(); + SendRPC(); + return true; + } + + public static void OnCrewmateEjected() + { + foreach (KeyValuePair state in Main.PlayerStates) { - if (!IsEnable) return; - - if (PreviouslyUsedVents.Contains(vent.Id)) return; - - PreviouslyUsedVents.Add(vent.Id); - XP += RewardForVenting.GetInt(); - SendRPC(); - } - - public override bool OnSabotage(PlayerControl pc) - { - XP += RewardForSabotaging.GetInt(); - SendRPC(); - return true; - } - - public static void OnCrewmateEjected() - { - foreach (KeyValuePair state in Main.PlayerStates) + if (state.Value.Role is Mafioso { IsEnable: true } mo) { - if (state.Value.Role is Mafioso { IsEnable: true } mo) - { - mo.XP += RewardForOtherPlayerEjected.GetInt(); - mo.SendRPC(); - } + mo.XP += RewardForOtherPlayerEjected.GetInt(); + mo.SendRPC(); } } } diff --git a/Roles/Impostor/Mastermind.cs b/Roles/Impostor/Mastermind.cs index cf67bcc30..9f7b15334 100644 --- a/Roles/Impostor/Mastermind.cs +++ b/Roles/Impostor/Mastermind.cs @@ -5,200 +5,199 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Mastermind : RoleBase { - public class Mastermind : RoleBase + private const int Id = 640600; + + private static List PlayerIdList = []; + + public static Dictionary ManipulatedPlayers = []; + public static Dictionary ManipulateDelays = []; + private static Dictionary TempKCDs = []; + + private static OptionItem KillCooldown; + private static OptionItem TimeLimit; + private static OptionItem Delay; + + private static float ManipulateCD; + private byte MastermindId = byte.MaxValue; + + private PlayerControl MastermindPC => GetPlayerById(MastermindId); + + public override bool IsEnable => MastermindId != byte.MaxValue || Randomizer.Exists; + + public override void SetupCustomOption() { - private const int Id = 640600; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Mastermind); - private static List PlayerIdList = []; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Mastermind]) + .SetValueFormat(OptionFormat.Seconds); - public static Dictionary ManipulatedPlayers = []; - public static Dictionary ManipulateDelays = []; - private static Dictionary TempKCDs = []; + // Manipulation Cooldown = Kill Cooldown + Delay + Time Limit + TimeLimit = new IntegerOptionItem(Id + 12, "MastermindTimeLimit", new(1, 60, 1), 20, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Mastermind]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem KillCooldown; - private static OptionItem TimeLimit; - private static OptionItem Delay; + Delay = new IntegerOptionItem(Id + 13, "MastermindDelay", new(0, 30, 1), 7, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Mastermind]) + .SetValueFormat(OptionFormat.Seconds); + } - private static float ManipulateCD; - private byte MastermindId = byte.MaxValue; + public override void Init() + { + MastermindId = byte.MaxValue; + ManipulatedPlayers = []; + ManipulateDelays = []; + TempKCDs = []; + PlayerIdList = []; + } - private PlayerControl MastermindPC => GetPlayerById(MastermindId); + public override void Add(byte playerId) + { + MastermindId = playerId; + ManipulateCD = KillCooldown.GetFloat() + TimeLimit.GetFloat() + Delay.GetFloat(); + PlayerIdList.Add(playerId); + } - public override bool IsEnable => MastermindId != byte.MaxValue || Randomizer.Exists; + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - public override void SetupCustomOption() + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return false; + + if (killer == null) return false; + + if (target == null) return false; + + return killer.CheckDoubleTrigger(target, () => { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Mastermind); + killer.SetKillCooldown(ManipulateCD); + ManipulateDelays.TryAdd(target.PlayerId, TimeStamp); + NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + }); + } - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Mastermind]) - .SetValueFormat(OptionFormat.Seconds); + public override void OnFixedUpdate(PlayerControl _) + { + if (!IsEnable || GameStates.IsMeeting || (ManipulatedPlayers.Count == 0 && ManipulateDelays.Count == 0)) return; - // Manipulation Cooldown = Kill Cooldown + Delay + Time Limit - TimeLimit = new IntegerOptionItem(Id + 12, "MastermindTimeLimit", new(1, 60, 1), 20, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Mastermind]) - .SetValueFormat(OptionFormat.Seconds); - - Delay = new IntegerOptionItem(Id + 13, "MastermindDelay", new(0, 30, 1), 7, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Mastermind]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() + foreach (KeyValuePair x in ManipulateDelays) { - MastermindId = byte.MaxValue; - ManipulatedPlayers = []; - ManipulateDelays = []; - TempKCDs = []; - PlayerIdList = []; - } + PlayerControl pc = GetPlayerById(x.Key); - public override void Add(byte playerId) - { - MastermindId = playerId; - ManipulateCD = KillCooldown.GetFloat() + TimeLimit.GetFloat() + Delay.GetFloat(); - PlayerIdList.Add(playerId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return false; - - if (killer == null) return false; - - if (target == null) return false; - - return killer.CheckDoubleTrigger(target, () => + if (!pc.IsAlive()) { - killer.SetKillCooldown(ManipulateCD); - ManipulateDelays.TryAdd(target.PlayerId, TimeStamp); - NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - }); - } - - public override void OnFixedUpdate(PlayerControl _) - { - if (!IsEnable || GameStates.IsMeeting || (ManipulatedPlayers.Count == 0 && ManipulateDelays.Count == 0)) return; - - foreach (KeyValuePair x in ManipulateDelays) - { - PlayerControl pc = GetPlayerById(x.Key); - - if (!pc.IsAlive()) - { - ManipulateDelays.Remove(x.Key); - continue; - } - - if (x.Value + Delay.GetInt() < TimeStamp) - { - ManipulateDelays.Remove(x.Key); - ManipulatedPlayers.TryAdd(x.Key, TimeStamp); - - if (pc.HasKillButton()) - { - TempKCDs.TryAdd(pc.PlayerId, Main.KillTimers[pc.PlayerId]); - pc.SetKillCooldown(1f); - } - else - pc.RpcChangeRoleBasis(CustomRoles.NSerialKiller); - - NotifyRoles(SpecifySeer: MastermindPC, SpecifyTarget: pc); - } + ManipulateDelays.Remove(x.Key); + continue; } - foreach (KeyValuePair x in ManipulatedPlayers) + if (x.Value + Delay.GetInt() < TimeStamp) { - PlayerControl player = GetPlayerById(x.Key); + ManipulateDelays.Remove(x.Key); + ManipulatedPlayers.TryAdd(x.Key, TimeStamp); - if (!player.IsAlive()) + if (pc.HasKillButton()) { - ManipulatedPlayers.Remove(x.Key); - TempKCDs.Remove(x.Key); - continue; + TempKCDs.TryAdd(pc.PlayerId, Main.KillTimers[pc.PlayerId]); + pc.SetKillCooldown(1f); } + else + pc.RpcChangeRoleBasis(CustomRoles.NSerialKiller); - if (x.Value + TimeLimit.GetInt() < TimeStamp) - { - ManipulatedPlayers.Remove(x.Key); - TempKCDs.Remove(x.Key); - player.RpcChangeRoleBasis(player.GetCustomRole()); - player.Suicide(realKiller: MastermindPC); - RPC.PlaySoundRPC(MastermindId, Sounds.KillSound); - - if (player.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - - long time = TimeLimit.GetInt() - (TimeStamp - x.Value); - - player.Notify(string.Format(GetString("ManipulateNotify"), time), 1.1f); + NotifyRoles(SpecifySeer: MastermindPC, SpecifyTarget: pc); } } - public override void OnReportDeadBody() + foreach (KeyValuePair x in ManipulatedPlayers) { - if (!IsEnable) return; + PlayerControl player = GetPlayerById(x.Key); - foreach (KeyValuePair x in ManipulatedPlayers) + if (!player.IsAlive()) { - PlayerControl pc = GetPlayerById(x.Key); - if (pc.IsAlive()) pc.Suicide(realKiller: MastermindPC); + ManipulatedPlayers.Remove(x.Key); + TempKCDs.Remove(x.Key); + continue; } - ManipulateDelays.Clear(); - ManipulatedPlayers.Clear(); - TempKCDs.Clear(); + if (x.Value + TimeLimit.GetInt() < TimeStamp) + { + ManipulatedPlayers.Remove(x.Key); + TempKCDs.Remove(x.Key); + player.RpcChangeRoleBasis(player.GetCustomRole()); + player.Suicide(realKiller: MastermindPC); + RPC.PlaySoundRPC(MastermindId, Sounds.KillSound); + + if (player.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + + long time = TimeLimit.GetInt() - (TimeStamp - x.Value); + + player.Notify(string.Format(GetString("ManipulateNotify"), time), 1.1f); + } + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + foreach (KeyValuePair x in ManipulatedPlayers) + { + PlayerControl pc = GetPlayerById(x.Key); + if (pc.IsAlive()) pc.Suicide(realKiller: MastermindPC); } - public static bool ForceKillForManipulatedPlayer(PlayerControl killer, PlayerControl target) + ManipulateDelays.Clear(); + ManipulatedPlayers.Clear(); + TempKCDs.Clear(); + } + + public static bool ForceKillForManipulatedPlayer(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + ManipulatedPlayers.Remove(killer.PlayerId); + + foreach (byte id in PlayerIdList) (Main.PlayerStates[id].Role as Mastermind)?.NotifyMastermindTargetSurvived(); + + if (target.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.ContainsKey(target.PlayerId) || target.Is(CustomRoles.Mastermind)) { - if (killer == null) return false; + Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Suicide; + Main.PlayerStates[killer.PlayerId].SetDead(); + target.Kill(killer); + TempKCDs.Remove(killer.PlayerId); - if (target == null) return false; - - ManipulatedPlayers.Remove(killer.PlayerId); - - foreach (byte id in PlayerIdList) (Main.PlayerStates[id].Role as Mastermind)?.NotifyMastermindTargetSurvived(); - - if (target.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.ContainsKey(target.PlayerId) || target.Is(CustomRoles.Mastermind)) - { - Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Suicide; - Main.PlayerStates[killer.PlayerId].SetDead(); - target.Kill(killer); - TempKCDs.Remove(killer.PlayerId); - - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); - - return false; - } - - killer.Kill(target); - - killer.Notify(GetString("MastermindTargetSurvived")); - killer.RpcChangeRoleBasis(killer.GetCustomRole()); - - LateTask.New(() => - { - float kcd = TempKCDs.TryGetValue(killer.PlayerId, out float cd) ? cd : Main.AllPlayerKillCooldown.GetValueOrDefault(killer.PlayerId, DefaultKillCooldown); - killer.SetKillCooldown(kcd); - TempKCDs.Remove(killer.PlayerId); - }, 0.1f, "Set KCD for Manipulated Kill"); + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); return false; } - private void NotifyMastermindTargetSurvived() - { - if (!IsEnable) return; + killer.Kill(target); - MastermindPC.Notify(GetString("ManipulatedKilled")); - if (Main.KillTimers[MastermindId] > KillCooldown.GetFloat()) MastermindPC.SetKillCooldown(KillCooldown.GetFloat()); - } + killer.Notify(GetString("MastermindTargetSurvived")); + killer.RpcChangeRoleBasis(killer.GetCustomRole()); + + LateTask.New(() => + { + float kcd = TempKCDs.TryGetValue(killer.PlayerId, out float cd) ? cd : Main.AllPlayerKillCooldown.GetValueOrDefault(killer.PlayerId, DefaultKillCooldown); + killer.SetKillCooldown(kcd); + TempKCDs.Remove(killer.PlayerId); + }, 0.1f, "Set KCD for Manipulated Kill"); + + return false; + } + + private void NotifyMastermindTargetSurvived() + { + if (!IsEnable) return; + + MastermindPC.Notify(GetString("ManipulatedKilled")); + if (Main.KillTimers[MastermindId] > KillCooldown.GetFloat()) MastermindPC.SetKillCooldown(KillCooldown.GetFloat()); } } \ No newline at end of file diff --git a/Roles/Impostor/Miner.cs b/Roles/Impostor/Miner.cs index cbbb51fff..8267689a5 100644 --- a/Roles/Impostor/Miner.cs +++ b/Roles/Impostor/Miner.cs @@ -1,79 +1,78 @@ using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Miner : RoleBase { - internal class Miner : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(3800, TabGroup.ImpostorRoles, CustomRoles.Miner); - public override void SetupCustomOption() + Options.MinerSSCD = new FloatOptionItem(3811, "ShapeshiftCooldown", new(1f, 180f, 1f), 5f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Miner]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = Options.MinerSSCD.GetFloat(); + else { - Options.SetupRoleOptions(3800, TabGroup.ImpostorRoles, CustomRoles.Miner); + if (Options.UsePets.GetBool()) return; - Options.MinerSSCD = new FloatOptionItem(3811, "ShapeshiftCooldown", new(1f, 180f, 1f), 5f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Miner]) - .SetValueFormat(OptionFormat.Seconds); + AURoleOptions.ShapeshifterCooldown = Options.MinerSSCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; } + } - public override void Add(byte playerId) + public override void SetButtonTexts(HudManager hud, byte id) + { + if (Options.UsePets.GetBool()) + hud.PetButton?.OverrideText(Translator.GetString("MinerTeleButtonText")); + else + hud.AbilityButton?.OverrideText(Translator.GetString("MinerTeleButtonText")); + } + + public override void OnPet(PlayerControl pc) + { + TeleportToVent(pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + + TeleportToVent(shapeshifter); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + TeleportToVent(pc); + return false; + } + + private static void TeleportToVent(PlayerControl pc) + { + if (Main.LastEnteredVent.ContainsKey(pc.PlayerId)) { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = Options.MinerSSCD.GetFloat(); - else - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = Options.MinerSSCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (Options.UsePets.GetBool()) - hud.PetButton?.OverrideText(Translator.GetString("MinerTeleButtonText")); - else - hud.AbilityButton?.OverrideText(Translator.GetString("MinerTeleButtonText")); - } - - public override void OnPet(PlayerControl pc) - { - TeleportToVent(pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - - TeleportToVent(shapeshifter); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - TeleportToVent(pc); - return false; - } - - private static void TeleportToVent(PlayerControl pc) - { - if (Main.LastEnteredVent.ContainsKey(pc.PlayerId)) - { - Vector2 position = Main.LastEnteredVentLocation[pc.PlayerId]; - pc.TP(new Vector2(position.x, position.y)); - } + Vector2 position = Main.LastEnteredVentLocation[pc.PlayerId]; + pc.TP(new Vector2(position.x, position.y)); } } } \ No newline at end of file diff --git a/Roles/Impostor/Minimalism.cs b/Roles/Impostor/Minimalism.cs index 5a4f173b3..f563ad7d8 100644 --- a/Roles/Impostor/Minimalism.cs +++ b/Roles/Impostor/Minimalism.cs @@ -1,70 +1,69 @@ using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Minimalism : RoleBase { - internal class Minimalism : RoleBase + public static bool On; + + private static OptionItem MnKillCooldown; + private static OptionItem BypassShields; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(16300, TabGroup.ImpostorRoles, CustomRoles.Minimalism); - private static OptionItem MnKillCooldown; - private static OptionItem BypassShields; - public override bool IsEnable => On; + MnKillCooldown = new FloatOptionItem(16310, "KillCooldown", new(2.5f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minimalism]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(16300, TabGroup.ImpostorRoles, CustomRoles.Minimalism); - - MnKillCooldown = new FloatOptionItem(16310, "KillCooldown", new(2.5f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minimalism]) - .SetValueFormat(OptionFormat.Seconds); - - BypassShields = new BooleanOptionItem(16311, "BypassShields", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minimalism]); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.SabotageButton?.ToggleVisible(false); - hud.AbilityButton?.ToggleVisible(false); - hud.ReportButton?.ToggleVisible(false); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = MnKillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) + BypassShields = new BooleanOptionItem(16311, "BypassShields", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Minimalism]); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.SabotageButton?.ToggleVisible(false); + hud.AbilityButton?.ToggleVisible(false); + hud.ReportButton?.ToggleVisible(false); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = MnKillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(true); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + if (BypassShields.GetBool()) { + killer.Kill(target); return false; } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(true); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - if (BypassShields.GetBool()) - { - killer.Kill(target); - return false; - } - - return true; - } + return true; } } \ No newline at end of file diff --git a/Roles/Impostor/Morphling.cs b/Roles/Impostor/Morphling.cs index ac47ebc64..7bc0f0289 100644 --- a/Roles/Impostor/Morphling.cs +++ b/Roles/Impostor/Morphling.cs @@ -2,60 +2,59 @@ using System.Collections.Generic; using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Morphling : RoleBase { - public class Morphling : RoleBase + private const int Id = 3000; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem ShapeshiftCD; + private static OptionItem ShapeshiftDur; + public override bool IsEnable => PlayerIdList.Count > 0; + + + public override void SetupCustomOption() { - private const int Id = 3000; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Morphling); - private static OptionItem KillCooldown; - private static OptionItem ShapeshiftCD; - private static OptionItem ShapeshiftDur; - public override bool IsEnable => PlayerIdList.Count > 0; + KillCooldown = new FloatOptionItem(Id + 14, "KillCooldown", new(0f, 60f, 2.5f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Morphling]) + .SetValueFormat(OptionFormat.Seconds); + ShapeshiftCD = new FloatOptionItem(Id + 15, "ShapeshiftCooldown", new(1f, 60f, 1f), 20f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Morphling]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Morphling); + ShapeshiftDur = new FloatOptionItem(Id + 16, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Morphling]) + .SetValueFormat(OptionFormat.Seconds); + } - KillCooldown = new FloatOptionItem(Id + 14, "KillCooldown", new(0f, 60f, 2.5f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Morphling]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + PlayerIdList = []; + } - ShapeshiftCD = new FloatOptionItem(Id + 15, "ShapeshiftCooldown", new(1f, 60f, 1f), 20f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Morphling]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - ShapeshiftDur = new FloatOptionItem(Id + 16, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Morphling]) - .SetValueFormat(OptionFormat.Seconds); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return !Main.PlayerStates[pc.PlayerId].IsDead && pc.IsShifted(); + } - public override void Init() - { - PlayerIdList = []; - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = ShapeshiftDur.GetFloat(); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !Main.PlayerStates[pc.PlayerId].IsDead && pc.IsShifted(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = ShapeshiftDur.GetFloat(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); } } \ No newline at end of file diff --git a/Roles/Impostor/Nullifier.cs b/Roles/Impostor/Nullifier.cs index ba2d8d87e..8eadebe25 100644 --- a/Roles/Impostor/Nullifier.cs +++ b/Roles/Impostor/Nullifier.cs @@ -2,98 +2,97 @@ using EHR.Crewmate; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Nullifier : RoleBase { - internal class Nullifier : RoleBase + private const int Id = 642000; + public static List PlayerIdList = []; + + public static OptionItem NullCD; + private static OptionItem KCD; + private static OptionItem Delay; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 642000; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Nullifier); - public static OptionItem NullCD; - private static OptionItem KCD; - private static OptionItem Delay; + NullCD = new FloatOptionItem(Id + 10, "NullCD", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Nullifier]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + KCD = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Nullifier]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + Delay = new IntegerOptionItem(Id + 12, "NullifierDelay", new(0, 90, 1), 5, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Nullifier]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KCD.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable || killer == null || target == null) return false; + + return killer.CheckDoubleTrigger(target, () => { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Nullifier); + killer.SetKillCooldown(NullCD.GetFloat()); + killer.Notify(Translator.GetString("NullifierUseRemoved")); - NullCD = new FloatOptionItem(Id + 10, "NullCD", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Nullifier]) - .SetValueFormat(OptionFormat.Seconds); - - KCD = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Nullifier]) - .SetValueFormat(OptionFormat.Seconds); - - Delay = new IntegerOptionItem(Id + 12, "NullifierDelay", new(0, 90, 1), 5, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Nullifier]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KCD.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable || killer == null || target == null) return false; - - return killer.CheckDoubleTrigger(target, () => + LateTask.New(() => { - killer.SetKillCooldown(NullCD.GetFloat()); - killer.Notify(Translator.GetString("NullifierUseRemoved")); - - LateTask.New(() => + switch (target.GetCustomRole()) { - switch (target.GetCustomRole()) - { - case CustomRoles.Cleanser: - if (Main.PlayerStates[target.PlayerId].Role is not Cleanser { IsEnable: true } cs) return; + case CustomRoles.Cleanser: + if (Main.PlayerStates[target.PlayerId].Role is not Cleanser { IsEnable: true } cs) return; - cs.CleanserUses++; - cs.SendRPC(target.PlayerId); - break; - case CustomRoles.NiceHacker: - if (target.IsModClient()) - { - NiceHacker.UseLimitSeconds[target.PlayerId] -= NiceHacker.ModdedClientAbilityUseSecondsMultiplier.GetInt(); - NiceHacker.SendRPC(target.PlayerId, NiceHacker.UseLimitSeconds[target.PlayerId]); - } - else - NiceHacker.UseLimit[target.PlayerId]--; + cs.CleanserUses++; + cs.SendRPC(target.PlayerId); + break; + case CustomRoles.NiceHacker: + if (target.IsModClient()) + { + NiceHacker.UseLimitSeconds[target.PlayerId] -= NiceHacker.ModdedClientAbilityUseSecondsMultiplier.GetInt(); + NiceHacker.SendRPC(target.PlayerId, NiceHacker.UseLimitSeconds[target.PlayerId]); + } + else + NiceHacker.UseLimit[target.PlayerId]--; - break; - case CustomRoles.SabotageMaster: - if (Main.PlayerStates[target.PlayerId].Role is not SabotageMaster { IsEnable: true } sm) return; + break; + case CustomRoles.SabotageMaster: + if (Main.PlayerStates[target.PlayerId].Role is not SabotageMaster { IsEnable: true } sm) return; - sm.UsedSkillCount++; - sm.SendRPC(); - break; - case CustomRoles.SwordsMan: - SwordsMan.Killed.Add(target.PlayerId); - SwordsMan.SendRPC(target.PlayerId); - break; - default: - target.RpcRemoveAbilityUse(); - break; - } + sm.UsedSkillCount++; + sm.SendRPC(); + break; + case CustomRoles.SwordsMan: + SwordsMan.Killed.Add(target.PlayerId); + SwordsMan.SendRPC(target.PlayerId); + break; + default: + target.RpcRemoveAbilityUse(); + break; + } - if (GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); - }, Delay.GetInt(), "Nullifier Remove Ability Use"); - }); - } + if (GameStates.IsInTask) Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); + }, Delay.GetInt(), "Nullifier Remove Ability Use"); + }); } } \ No newline at end of file diff --git a/Roles/Impostor/Occultist.cs b/Roles/Impostor/Occultist.cs index 73b2d6348..1e32dbe4d 100644 --- a/Roles/Impostor/Occultist.cs +++ b/Roles/Impostor/Occultist.cs @@ -1,180 +1,179 @@ using System.Collections.Generic; using EHR.Modules; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Occultist : RoleBase { - public class Occultist : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem AbilityUseLimit; + private static OptionItem ReviveTime; + private static OptionItem ArrowsToBodies; + private static OptionItem RevivedPlayersBodiesCanBeReported; + private static OptionItem RevivedPlayers; + private static OptionItem CanReviveImpostorsAndMadmates; + public static OptionItem AbilityUseGainWithEachKill; + + private static readonly string[] RevivedPlayersModes = + [ + "Occultist.RPM.Renegade", + "Occultist.RPM.Madmate" + ]; + + private static ActionSwitchModes ActionSwitchMode; + private static Dictionary Revives = []; + + private bool InRevivingMode; + + private long LastUpdate; + private PlayerControl OccultistPC; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(645250) + .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) + .AutoSetupOption(ref ReviveTime, 5, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds) + .AutoSetupOption(ref ArrowsToBodies, false) + .AutoSetupOption(ref RevivedPlayersBodiesCanBeReported, false) + .AutoSetupOption(ref RevivedPlayers, 0, RevivedPlayersModes) + .AutoSetupOption(ref CanReviveImpostorsAndMadmates, true) + .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.4f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times); + } - private static OptionItem AbilityUseLimit; - private static OptionItem ReviveTime; - private static OptionItem ArrowsToBodies; - private static OptionItem RevivedPlayersBodiesCanBeReported; - private static OptionItem RevivedPlayers; - private static OptionItem CanReviveImpostorsAndMadmates; - public static OptionItem AbilityUseGainWithEachKill; + public override void Init() + { + On = false; + ActionSwitchMode = Options.UseUnshiftTrigger.GetBool() ? ActionSwitchModes.Unshift : Options.UsePets.GetBool() ? ActionSwitchModes.Pet : ActionSwitchModes.Vent; + Instances = []; + Revives = []; + } - private static readonly string[] RevivedPlayersModes = - [ - "Occultist.RPM.Renegade", - "Occultist.RPM.Madmate" - ]; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + OccultistPC = playerId.GetPlayer(); + InRevivingMode = true; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - private static ActionSwitchModes ActionSwitchMode; - private static Dictionary Revives = []; + void SwitchAction() + { + InRevivingMode = Main.AllAlivePlayerControls.Length >= 4 && !InRevivingMode; + Utils.SendRPC(CustomRPC.SyncRoleData, OccultistPC.PlayerId, 1, InRevivingMode); + Utils.NotifyRoles(SpecifySeer: OccultistPC, SpecifyTarget: OccultistPC); + } - private bool InRevivingMode; + public override void OnExitVent(PlayerControl pc, Vent vent) + { + if (ActionSwitchMode == ActionSwitchModes.Vent) + SwitchAction(); + } - private long LastUpdate; - private PlayerControl OccultistPC; + public override void OnPet(PlayerControl pc) + { + if (ActionSwitchMode == ActionSwitchModes.Pet) + SwitchAction(); + } - public override bool IsEnable => On; + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && ActionSwitchMode == ActionSwitchModes.Unshift) + SwitchAction(); - public override void SetupCustomOption() + return false; + } + + public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) + { + if (!InRevivingMode || reporter.GetAbilityUseLimit() < 1 || target.Disconnected || target.Object.IsAlive() || target.Object.Is(CustomRoles.Unreportable) || (target.Object.Is(Team.Impostor) && !CanReviveImpostorsAndMadmates.GetBool())) return true; + + InRevivingMode = false; + Revives[target.PlayerId] = new(Utils.TimeStamp, reporter.Pos(), false); + reporter.RpcRemoveAbilityUse(); + reporter.Notify(string.Format(Translator.GetString("OccultistReviving"), target.PlayerId.ColoredPlayerName()), ReviveTime.GetInt()); + + return false; + } + + public static bool OnAnyoneReportDeadBody(NetworkedPlayerInfo target) + { + if (RevivedPlayersBodiesCanBeReported.GetBool()) return true; + return !Revives.ContainsKey(target.PlayerId); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive()) { - StartSetup(645250) - .AutoSetupOption(ref AbilityUseLimit, 0, new IntegerValueRule(0, 20, 1), OptionFormat.Times) - .AutoSetupOption(ref ReviveTime, 5, new IntegerValueRule(0, 60, 1), OptionFormat.Seconds) - .AutoSetupOption(ref ArrowsToBodies, false) - .AutoSetupOption(ref RevivedPlayersBodiesCanBeReported, false) - .AutoSetupOption(ref RevivedPlayers, 0, RevivedPlayersModes) - .AutoSetupOption(ref CanReviveImpostorsAndMadmates, true) - .AutoSetupOption(ref AbilityUseGainWithEachKill, 0.4f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times); + Revives.Clear(); + return; } - public override void Init() + long now = Utils.TimeStamp; + if (now == LastUpdate) return; + LastUpdate = now; + + foreach ((byte id, ReviveData data) in Revives) { - On = false; - ActionSwitchMode = Options.UseUnshiftTrigger.GetBool() ? ActionSwitchModes.Unshift : Options.UsePets.GetBool() ? ActionSwitchModes.Pet : ActionSwitchModes.Vent; - Instances = []; - Revives = []; - } + if (data.Done) continue; - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - OccultistPC = playerId.GetPlayer(); - InRevivingMode = true; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - void SwitchAction() - { - InRevivingMode = Main.AllAlivePlayerControls.Length >= 4 && !InRevivingMode; - Utils.SendRPC(CustomRPC.SyncRoleData, OccultistPC.PlayerId, 1, InRevivingMode); - Utils.NotifyRoles(SpecifySeer: OccultistPC, SpecifyTarget: OccultistPC); - } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - if (ActionSwitchMode == ActionSwitchModes.Vent) - SwitchAction(); - } - - public override void OnPet(PlayerControl pc) - { - if (ActionSwitchMode == ActionSwitchModes.Pet) - SwitchAction(); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && ActionSwitchMode == ActionSwitchModes.Unshift) - SwitchAction(); - - return false; - } - - public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) - { - if (!InRevivingMode || reporter.GetAbilityUseLimit() < 1 || target.Disconnected || target.Object.IsAlive() || target.Object.Is(CustomRoles.Unreportable) || (target.Object.Is(Team.Impostor) && !CanReviveImpostorsAndMadmates.GetBool())) return true; - - InRevivingMode = false; - Revives[target.PlayerId] = new(Utils.TimeStamp, reporter.Pos(), false); - reporter.RpcRemoveAbilityUse(); - reporter.Notify(string.Format(Translator.GetString("OccultistReviving"), target.PlayerId.ColoredPlayerName()), ReviveTime.GetInt()); - - return false; - } - - public static bool OnAnyoneReportDeadBody(NetworkedPlayerInfo target) - { - if (RevivedPlayersBodiesCanBeReported.GetBool()) return true; - return !Revives.ContainsKey(target.PlayerId); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive()) + if (now - data.StartTimeStamp >= ReviveTime.GetInt()) { - Revives.Clear(); - return; + var player = id.GetPlayer(); + if (player == null) continue; + + player.RpcRevive(); + player.TP(data.Position); + player.Notify(Translator.GetString("RevivedByOccultist"), 15f); + + OccultistPC.Notify(string.Format(Translator.GetString("OccultistRevived"), player.PlayerId.ColoredPlayerName())); + + if (!player.Is(Team.Impostor)) + player.RpcSetCustomRole(RevivedPlayers.GetValue() == 0 ? CustomRoles.Refugee : CustomRoles.Madmate); + + data.Done = true; } - - long now = Utils.TimeStamp; - if (now == LastUpdate) return; - LastUpdate = now; - - foreach ((byte id, ReviveData data) in Revives) - { - if (data.Done) continue; - - if (now - data.StartTimeStamp >= ReviveTime.GetInt()) - { - var player = id.GetPlayer(); - if (player == null) continue; - - player.RpcRevive(); - player.TP(data.Position); - player.Notify(Translator.GetString("RevivedByOccultist"), 15f); - - OccultistPC.Notify(string.Format(Translator.GetString("OccultistRevived"), player.PlayerId.ColoredPlayerName())); - - if (!player.Is(Team.Impostor)) - player.RpcSetCustomRole(RevivedPlayers.GetValue() == 0 ? CustomRoles.Refugee : CustomRoles.Madmate); - - data.Done = true; - } - } - - if (Main.AllAlivePlayerControls.Length < 4) SwitchAction(); } - public static void OnAnyoneDied(PlayerControl target) - { - if (!ArrowsToBodies.GetBool()) return; - var pos = target.Pos(); - Instances.Do(x => LocateArrow.Add(x.OccultistPC.PlayerId, pos)); - } + if (Main.AllAlivePlayerControls.Length < 4) SwitchAction(); + } - public override void OnReportDeadBody() - { - if (ArrowsToBodies.GetBool()) LocateArrow.RemoveAllTarget(OccultistPC.PlayerId); - } + public static void OnAnyoneDied(PlayerControl target) + { + if (!ArrowsToBodies.GetBool()) return; + var pos = target.Pos(); + Instances.Do(x => LocateArrow.Add(x.OccultistPC.PlayerId, pos)); + } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != OccultistPC.PlayerId || seer.PlayerId != target.PlayerId || meeting || (seer.IsModClient() && !hud)) return string.Empty; - var str = string.Format(Translator.GetString("OccultistSuffix"), Translator.GetString(InRevivingMode ? "OccultistMode.Revive" : "OccultistMode.Report"), Translator.GetString($"OccultistActionSwitchMode.{ActionSwitchMode}")); - if (Main.AllAlivePlayerControls.Length < 4) str = str.Split('(')[0].TrimEnd(' '); - return str; - } + public override void OnReportDeadBody() + { + if (ArrowsToBodies.GetBool()) LocateArrow.RemoveAllTarget(OccultistPC.PlayerId); + } - enum ActionSwitchModes - { - Vent, - Pet, - Unshift - } + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != OccultistPC.PlayerId || seer.PlayerId != target.PlayerId || meeting || (seer.IsModClient() && !hud)) return string.Empty; + var str = string.Format(Translator.GetString("OccultistSuffix"), Translator.GetString(InRevivingMode ? "OccultistMode.Revive" : "OccultistMode.Report"), Translator.GetString($"OccultistActionSwitchMode.{ActionSwitchMode}")); + if (Main.AllAlivePlayerControls.Length < 4) str = str.Split('(')[0].TrimEnd(' '); + return str; + } - class ReviveData(long startTimeStamp, Vector2 position, bool done) - { - public long StartTimeStamp { get; } = startTimeStamp; - public Vector2 Position { get; } = position; - public bool Done { get; set; } = done; - } + enum ActionSwitchModes + { + Vent, + Pet, + Unshift + } + + class ReviveData(long startTimeStamp, Vector2 position, bool done) + { + public long StartTimeStamp { get; } = startTimeStamp; + public Vector2 Position { get; } = position; + public bool Done { get; set; } = done; } } \ No newline at end of file diff --git a/Roles/Impostor/OverKiller.cs b/Roles/Impostor/OverKiller.cs index 00c164969..7d97b9d39 100644 --- a/Roles/Impostor/OverKiller.cs +++ b/Roles/Impostor/OverKiller.cs @@ -3,106 +3,105 @@ using System.Collections.Generic; using Hazel; using InnerNet; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class OverKiller : RoleBase { - internal class OverKiller : RoleBase + public static bool On; + + public static List OverDeadPlayerList = []; + private static OptionItem KillCooldown; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(16900, TabGroup.ImpostorRoles, CustomRoles.OverKiller); - public static List OverDeadPlayerList = []; - private static OptionItem KillCooldown; - public override bool IsEnable => On; + KillCooldown = new FloatOptionItem(16902, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.OverKiller]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("OverKillerButtonText")); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!killer.RpcCheckAndMurder(target, true)) return false; + + if (killer.PlayerId != target.PlayerId && !target.Is(CustomRoles.Unreportable)) { - Options.SetupRoleOptions(16900, TabGroup.ImpostorRoles, CustomRoles.OverKiller); + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Dismembered; - KillCooldown = new FloatOptionItem(16902, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.OverKiller]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("OverKillerButtonText")); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!killer.RpcCheckAndMurder(target, true)) return false; - - if (killer.PlayerId != target.PlayerId && !target.Is(CustomRoles.Unreportable)) + LateTask.New(() => { - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Dismembered; + if (!OverDeadPlayerList.Contains(target.PlayerId)) + OverDeadPlayerList.Add(target.PlayerId); - LateTask.New(() => + if (target.Is(CustomRoles.Avanger)) { - if (!OverDeadPlayerList.Contains(target.PlayerId)) - OverDeadPlayerList.Add(target.PlayerId); + target.Suicide(PlayerState.DeathReason.Dismembered, killer); - if (target.Is(CustomRoles.Avanger)) - { - target.Suicide(PlayerState.DeathReason.Dismembered, killer); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + pc.Suicide(PlayerState.DeathReason.Revenge, target); - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - pc.Suicide(PlayerState.DeathReason.Revenge, target); - - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); - return; - } - - Vector2 ops = target.Pos(); - Vector2 originPos = killer.Pos(); - var rd = IRandom.Instance; - - Main.Instance.StartCoroutine(SpawnFakeDeadBodies()); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.None); return; + } - IEnumerator SpawnFakeDeadBodies() + Vector2 ops = target.Pos(); + Vector2 originPos = killer.Pos(); + var rd = IRandom.Instance; + + Main.Instance.StartCoroutine(SpawnFakeDeadBodies()); + return; + + IEnumerator SpawnFakeDeadBodies() + { + for (var i = 0; i < 26; i++) { - for (var i = 0; i < 26; i++) - { - Vector2 location = new(ops.x + ((float)(rd.Next(0, 201) - 100) / 100), ops.y + ((float)(rd.Next(0, 201) - 100) / 100)); - location += new Vector2(0, 0.3636f); + Vector2 location = new(ops.x + ((float)(rd.Next(0, 201) - 100) / 100), ops.y + ((float)(rd.Next(0, 201) - 100) / 100)); + location += new Vector2(0, 0.3636f); - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.None); - NetHelpers.WriteVector2(location, writer); - writer.Write(target.NetTransform.lastSequenceId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(target.NetTransform.NetId, (byte)RpcCalls.SnapTo, SendOption.None); + NetHelpers.WriteVector2(location, writer); + writer.Write(target.NetTransform.lastSequenceId); + AmongUsClient.Instance.FinishRpcImmediately(writer); - target.NetTransform.SnapTo(location); - killer.MurderPlayer(target, ExtendedPlayerControl.ResultFlags); + target.NetTransform.SnapTo(location); + killer.MurderPlayer(target, ExtendedPlayerControl.ResultFlags); - MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.None); - messageWriter.WriteNetObject(target); - messageWriter.Write((byte)ExtendedPlayerControl.ResultFlags); - AmongUsClient.Instance.FinishRpcImmediately(messageWriter); + MessageWriter messageWriter = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)RpcCalls.MurderPlayer, SendOption.None); + messageWriter.WriteNetObject(target); + messageWriter.Write((byte)ExtendedPlayerControl.ResultFlags); + AmongUsClient.Instance.FinishRpcImmediately(messageWriter); - if (i % 3 == 0) yield return null; - } - - yield return null; - killer.TP(originPos); + if (i % 3 == 0) yield return null; } - }, 0.05f, "OverKiller Murder"); - } - return base.OnCheckMurder(killer, target); + yield return null; + killer.TP(originPos); + } + }, 0.05f, "OverKiller Murder"); } + + return base.OnCheckMurder(killer, target); } } \ No newline at end of file diff --git a/Roles/Impostor/Overheat.cs b/Roles/Impostor/Overheat.cs index c9f4b6c8f..52441f5c8 100644 --- a/Roles/Impostor/Overheat.cs +++ b/Roles/Impostor/Overheat.cs @@ -2,159 +2,158 @@ using EHR.Modules; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Overheat : RoleBase { - public class Overheat : RoleBase + private const int StartingTemperature = 35; + public static bool On; + + private static OptionItem OverheatChanceIncrease; + private static OptionItem OverheatChanceIncreaseFrequency; + private static OptionItem OverheatRollChanceFrequency; + private static OptionItem CoolDownTime; + private static OptionItem KCDDecreasePerIncreasedTemperature; + private float ChanceIncreaseTimer; + private float RollChanceTimer; + + public int Temperature; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int StartingTemperature = 35; - public static bool On; + const int id = 12332; + Options.SetupRoleOptions(id, TabGroup.ImpostorRoles, CustomRoles.Overheat); - private static OptionItem OverheatChanceIncrease; - private static OptionItem OverheatChanceIncreaseFrequency; - private static OptionItem OverheatRollChanceFrequency; - private static OptionItem CoolDownTime; - private static OptionItem KCDDecreasePerIncreasedTemperature; - private float ChanceIncreaseTimer; - private float RollChanceTimer; + OverheatChanceIncrease = new FloatOptionItem(id + 2, "Overheat.ChanceIncrease", new(1f, 10f, 1f), 1f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) + .SetValueFormat(OptionFormat.Percent); - public int Temperature; + OverheatChanceIncreaseFrequency = new FloatOptionItem(id + 3, "Overheat.ChanceIncreaseFrequency", new(0.5f, 30f, 0.5f), 5f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + OverheatRollChanceFrequency = new FloatOptionItem(id + 4, "Overheat.RollChanceFrequency", new(0.5f, 60f, 0.5f), 3f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + CoolDownTime = new FloatOptionItem(id + 5, "Overheat.CoolDownTime", new(0.5f, 30f, 0.5f), 4f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) + .SetValueFormat(OptionFormat.Seconds); + + KCDDecreasePerIncreasedTemperature = new FloatOptionItem(id + 6, "Overheat.KCDDecreasePerIncreasedTemperature", new(0.5f, 15f, 0.5f), 3f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) + .SetValueFormat(OptionFormat.Seconds); + } + + private static Color GetTemperatureColor(int temperature) + { + return temperature switch { - const int id = 12332; - Options.SetupRoleOptions(id, TabGroup.ImpostorRoles, CustomRoles.Overheat); + <= 35 => Color.blue, + 36 => Color.cyan, + 37 => Color.green, + 38 => Color.yellow, + 39 => Palette.Orange, + >= 40 => Color.red + }; + } - OverheatChanceIncrease = new FloatOptionItem(id + 2, "Overheat.ChanceIncrease", new(1f, 10f, 1f), 1f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) - .SetValueFormat(OptionFormat.Percent); + public override void Add(byte playerId) + { + On = true; + Temperature = StartingTemperature; + ChanceIncreaseTimer = -8f; + RollChanceTimer = -8f; + SendRPC(playerId); + } - OverheatChanceIncreaseFrequency = new FloatOptionItem(id + 3, "Overheat.ChanceIncreaseFrequency", new(0.5f, 30f, 0.5f), 5f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + On = false; + } - OverheatRollChanceFrequency = new FloatOptionItem(id + 4, "Overheat.RollChanceFrequency", new(0.5f, 60f, 0.5f), 3f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) - .SetValueFormat(OptionFormat.Seconds); + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePets.GetBool()) return; - CoolDownTime = new FloatOptionItem(id + 5, "Overheat.CoolDownTime", new(0.5f, 30f, 0.5f), 4f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) - .SetValueFormat(OptionFormat.Seconds); + AURoleOptions.ShapeshifterCooldown = 1f; + AURoleOptions.ShapeshifterDuration = 1f; + } - KCDDecreasePerIncreasedTemperature = new FloatOptionItem(id + 6, "Overheat.KCDDecreasePerIncreasedTemperature", new(0.5f, 15f, 0.5f), 3f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Overheat]) - .SetValueFormat(OptionFormat.Seconds); - } + private void SendRPC(byte id) + { + Utils.SendRPC(CustomRPC.SyncOverheat, id, Temperature); + } - private static Color GetTemperatureColor(int temperature) + public override void SetKillCooldown(byte id) + { + float kcd = Options.DefaultKillCooldown; + kcd -= KCDDecreasePerIncreasedTemperature.GetFloat() * (Temperature - StartingTemperature); + Main.AllPlayerKillCooldown[id] = kcd; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !pc.IsAlive() || ExileController.Instance != null) return; + + ChanceIncreaseTimer += Time.fixedDeltaTime; + RollChanceTimer += Time.fixedDeltaTime; + + if (ChanceIncreaseTimer >= OverheatChanceIncreaseFrequency.GetFloat()) { - return temperature switch - { - <= 35 => Color.blue, - 36 => Color.cyan, - 37 => Color.green, - 38 => Color.yellow, - 39 => Palette.Orange, - >= 40 => Color.red - }; - } - - public override void Add(byte playerId) - { - On = true; - Temperature = StartingTemperature; - ChanceIncreaseTimer = -8f; - RollChanceTimer = -8f; - SendRPC(playerId); - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = 1f; - AURoleOptions.ShapeshifterDuration = 1f; - } - - private void SendRPC(byte id) - { - Utils.SendRPC(CustomRPC.SyncOverheat, id, Temperature); - } - - public override void SetKillCooldown(byte id) - { - float kcd = Options.DefaultKillCooldown; - kcd -= KCDDecreasePerIncreasedTemperature.GetFloat() * (Temperature - StartingTemperature); - Main.AllPlayerKillCooldown[id] = kcd; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !pc.IsAlive() || ExileController.Instance != null) return; - - ChanceIncreaseTimer += Time.fixedDeltaTime; - RollChanceTimer += Time.fixedDeltaTime; - - if (ChanceIncreaseTimer >= OverheatChanceIncreaseFrequency.GetFloat()) - { - ChanceIncreaseTimer = 0f; - Temperature += OverheatChanceIncrease.GetInt(); - SendRPC(pc.PlayerId); - pc.ResetKillCooldown(); - pc.MarkDirtySettings(); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - if (RollChanceTimer >= OverheatRollChanceFrequency.GetFloat()) - { - RollChanceTimer = 0f; - if (IRandom.Instance.Next(100) < Temperature - StartingTemperature) pc.Suicide(); - } - } - - private void CoolDown(PlayerControl pc) - { - Temperature = StartingTemperature; + ChanceIncreaseTimer = 0f; + Temperature += OverheatChanceIncrease.GetInt(); SendRPC(pc.PlayerId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - float speed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.ResetKillCooldown(); pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[pc.PlayerId] = speed; - pc.MarkDirtySettings(); - }, CoolDownTime.GetFloat(), "Overheat Cool Down"); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + if (RollChanceTimer >= OverheatRollChanceFrequency.GetFloat()) { - CoolDown(shapeshifter); - return false; - } - - public override void OnPet(PlayerControl pc) - { - CoolDown(pc); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (Main.PlayerStates[seer.PlayerId].Role is not Overheat oh) return string.Empty; - - if (seer.PlayerId != target.PlayerId) return string.Empty; - - Color color = GetTemperatureColor(oh.Temperature); - string str = Translator.GetString("Overheat.Suffix"); - return string.Format(str, Utils.ColorString(color, $"{oh.Temperature}°C")); + RollChanceTimer = 0f; + if (IRandom.Instance.Next(100) < Temperature - StartingTemperature) pc.Suicide(); } } + + private void CoolDown(PlayerControl pc) + { + Temperature = StartingTemperature; + SendRPC(pc.PlayerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + float speed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.MarkDirtySettings(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[pc.PlayerId] = speed; + pc.MarkDirtySettings(); + }, CoolDownTime.GetFloat(), "Overheat Cool Down"); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + CoolDown(shapeshifter); + return false; + } + + public override void OnPet(PlayerControl pc) + { + CoolDown(pc); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (Main.PlayerStates[seer.PlayerId].Role is not Overheat oh) return string.Empty; + + if (seer.PlayerId != target.PlayerId) return string.Empty; + + Color color = GetTemperatureColor(oh.Temperature); + string str = Translator.GetString("Overheat.Suffix"); + return string.Format(str, Utils.ColorString(color, $"{oh.Temperature}°C")); + } } \ No newline at end of file diff --git a/Roles/Impostor/Parasite.cs b/Roles/Impostor/Parasite.cs index a8df592de..78e5d197b 100644 --- a/Roles/Impostor/Parasite.cs +++ b/Roles/Impostor/Parasite.cs @@ -1,69 +1,68 @@ using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Parasite : RoleBase { - internal class Parasite : RoleBase + public static bool On; + + private static OptionItem ParasiteCD; + private static OptionItem ShapeshiftCooldown; + private static OptionItem ShapeshiftDuration; + + public static float SSCD; + public static float SSDur; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupSingleRoleOptions(4900, TabGroup.ImpostorRoles, CustomRoles.Parasite); - private static OptionItem ParasiteCD; - private static OptionItem ShapeshiftCooldown; - private static OptionItem ShapeshiftDuration; + ParasiteCD = new FloatOptionItem(4910, "KillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Parasite]) + .SetValueFormat(OptionFormat.Seconds); - public static float SSCD; - public static float SSDur; + ShapeshiftCooldown = new FloatOptionItem(4911, "ShapeshiftCooldown", new(0f, 180f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Parasite]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + ShapeshiftDuration = new FloatOptionItem(4912, "ShapeshiftDuration", new(0f, 180f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Parasite]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() - { - Options.SetupSingleRoleOptions(4900, TabGroup.ImpostorRoles, CustomRoles.Parasite); + public override void Add(byte playerId) + { + On = true; + } - ParasiteCD = new FloatOptionItem(4910, "KillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Parasite]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + On = false; + SSCD = ShapeshiftCooldown.GetFloat(); + SSDur = ShapeshiftDuration.GetFloat(); + } - ShapeshiftCooldown = new FloatOptionItem(4911, "ShapeshiftCooldown", new(0f, 180f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Parasite]) - .SetValueFormat(OptionFormat.Seconds); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = ParasiteCD.GetFloat(); + } - ShapeshiftDuration = new FloatOptionItem(4912, "ShapeshiftDuration", new(0f, 180f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Parasite]) - .SetValueFormat(OptionFormat.Seconds); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(true); + AURoleOptions.ShapeshifterCooldown = SSCD; + AURoleOptions.ShapeshifterDuration = SSDur; + } - public override void Add(byte playerId) - { - On = true; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override void Init() - { - On = false; - SSCD = ShapeshiftCooldown.GetFloat(); - SSDur = ShapeshiftDuration.GetFloat(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = ParasiteCD.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(true); - AURoleOptions.ShapeshifterCooldown = SSCD; - AURoleOptions.ShapeshifterDuration = SSDur; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.IsAlive(); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsAlive(); } } \ No newline at end of file diff --git a/Roles/Impostor/Penguin.cs b/Roles/Impostor/Penguin.cs index a312296b9..e51b4440a 100644 --- a/Roles/Impostor/Penguin.cs +++ b/Roles/Impostor/Penguin.cs @@ -6,417 +6,416 @@ using Hazel; using UnityEngine; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Penguin : RoleBase { - public class Penguin : RoleBase + private const int Id = 641800; + + private static OptionItem OptionAbductTimerLimit; + private static OptionItem OptionMeetingKill; + private static OptionItem OptionSpeedDuringDrag; + private static OptionItem OptionVictimCanUseAbilities; + + private float AbductTimer; + private float AbductTimerLimit; + + private PlayerControl AbductVictim; + private float Cooldown; + + private int Count; + private float DefaultSpeed; + + private bool IsGoose; + private long LastNotify; + private bool MeetingKill; + + private PlayerControl Penguin_; + private byte PenguinId = byte.MaxValue; + private float SpeedDuringDrag; + + private bool stopCount; + private bool VictimCanUseAbilities; + + public override bool IsEnable => PenguinId != byte.MaxValue; + + // Measures to prevent the opponent who is about to be killed during abduction from using their abilities + public static bool IsVictim(PlayerControl pc) { - private const int Id = 641800; + foreach (KeyValuePair state in Main.PlayerStates) + if (state.Value.Role is Penguin { IsEnable: true, VictimCanUseAbilities: false } pg && pg.AbductVictim != null && pg.AbductVictim.PlayerId == pc.PlayerId) + return true; - private static OptionItem OptionAbductTimerLimit; - private static OptionItem OptionMeetingKill; - private static OptionItem OptionSpeedDuringDrag; - private static OptionItem OptionVictimCanUseAbilities; + return false; + } - private float AbductTimer; - private float AbductTimerLimit; + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Penguin); - private PlayerControl AbductVictim; - private float Cooldown; + OptionAbductTimerLimit = new FloatOptionItem(Id + 11, "PenguinAbductTimerLimit", new(1f, 20f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; - private float DefaultSpeed; + OptionMeetingKill = new BooleanOptionItem(Id + 12, "PenguinMeetingKill", false, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]); - private bool IsGoose; - private long LastNotify; - private bool MeetingKill; + OptionSpeedDuringDrag = new FloatOptionItem(Id + 13, "PenguinSpeedDuringDrag", new(0.1f, 3f, 0.1f), 1f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]) + .SetValueFormat(OptionFormat.Multiplier); - private PlayerControl Penguin_; - private byte PenguinId = byte.MaxValue; - private float SpeedDuringDrag; + OptionVictimCanUseAbilities = new BooleanOptionItem(Id + 14, "PenguinVictimCanUseAbilities", false, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]); + } - private bool stopCount; - private bool VictimCanUseAbilities; + public override void Init() + { + PenguinId = byte.MaxValue; + Penguin_ = null; + } - public override bool IsEnable => PenguinId != byte.MaxValue; + public override void Add(byte playerId) + { + IsGoose = Main.PlayerStates[playerId].MainRole == CustomRoles.Goose; - // Measures to prevent the opponent who is about to be killed during abduction from using their abilities - public static bool IsVictim(PlayerControl pc) + if (!IsGoose) { - foreach (KeyValuePair state in Main.PlayerStates) - if (state.Value.Role is Penguin { IsEnable: true, VictimCanUseAbilities: false } pg && pg.AbductVictim != null && pg.AbductVictim.PlayerId == pc.PlayerId) - return true; - - return false; + AbductTimerLimit = OptionAbductTimerLimit.GetFloat(); + MeetingKill = OptionMeetingKill.GetBool(); + SpeedDuringDrag = OptionSpeedDuringDrag.GetFloat(); + VictimCanUseAbilities = OptionVictimCanUseAbilities.GetBool(); + Cooldown = Options.DefaultKillCooldown; + } + else + { + AbductTimerLimit = Goose.OptionAbductTimerLimit.GetFloat(); + MeetingKill = false; + SpeedDuringDrag = Goose.OptionSpeedDuringDrag.GetFloat(); + VictimCanUseAbilities = Goose.OptionVictimCanUseAbilities.GetBool(); + Cooldown = Goose.Cooldown.GetFloat(); } - public override void SetupCustomOption() + LateTask.New(() => DefaultSpeed = Main.AllPlayerSpeed[playerId], 9f, log: false); + + PenguinId = playerId; + Penguin_ = Utils.GetPlayerById(playerId); + + AbductTimer = 255f; + stopCount = false; + LastNotify = 0; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Cooldown; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return AbductVictim == null && !IsGoose; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return !IsGoose && pc.IsAlive(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (IsGoose) opt.SetVision(false); + } + + private void SendRPC() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PenguinSync, SendOption.Reliable); + writer.Write(PenguinId); + writer.Write(1); + writer.Write(AbductVictim?.PlayerId ?? 255); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(byte victim) + { + if (victim == 255) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Penguin); - - OptionAbductTimerLimit = new FloatOptionItem(Id + 11, "PenguinAbductTimerLimit", new(1f, 20f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]) - .SetValueFormat(OptionFormat.Seconds); - - OptionMeetingKill = new BooleanOptionItem(Id + 12, "PenguinMeetingKill", false, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]); - - OptionSpeedDuringDrag = new FloatOptionItem(Id + 13, "PenguinSpeedDuringDrag", new(0.1f, 3f, 0.1f), 1f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]) - .SetValueFormat(OptionFormat.Multiplier); - - OptionVictimCanUseAbilities = new BooleanOptionItem(Id + 14, "PenguinVictimCanUseAbilities", false, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Penguin]); - } - - public override void Init() - { - PenguinId = byte.MaxValue; - Penguin_ = null; - } - - public override void Add(byte playerId) - { - IsGoose = Main.PlayerStates[playerId].MainRole == CustomRoles.Goose; - - if (!IsGoose) - { - AbductTimerLimit = OptionAbductTimerLimit.GetFloat(); - MeetingKill = OptionMeetingKill.GetBool(); - SpeedDuringDrag = OptionSpeedDuringDrag.GetFloat(); - VictimCanUseAbilities = OptionVictimCanUseAbilities.GetBool(); - Cooldown = Options.DefaultKillCooldown; - } - else - { - AbductTimerLimit = Goose.OptionAbductTimerLimit.GetFloat(); - MeetingKill = false; - SpeedDuringDrag = Goose.OptionSpeedDuringDrag.GetFloat(); - VictimCanUseAbilities = Goose.OptionVictimCanUseAbilities.GetBool(); - Cooldown = Goose.Cooldown.GetFloat(); - } - - LateTask.New(() => DefaultSpeed = Main.AllPlayerSpeed[playerId], 9f, log: false); - - PenguinId = playerId; - Penguin_ = Utils.GetPlayerById(playerId); - + AbductVictim = null; AbductTimer = 255f; - stopCount = false; - LastNotify = 0; } - - public override void SetKillCooldown(byte id) + else { - Main.AllPlayerKillCooldown[id] = Cooldown; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return AbductVictim == null && !IsGoose; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return !IsGoose && pc.IsAlive(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (IsGoose) opt.SetVision(false); - } - - private void SendRPC() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PenguinSync, SendOption.Reliable); - writer.Write(PenguinId); - writer.Write(1); - writer.Write(AbductVictim?.PlayerId ?? 255); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(byte victim) - { - if (victim == 255) - { - AbductVictim = null; - AbductTimer = 255f; - } - else - { - AbductVictim = Utils.GetPlayerById(victim); - AbductTimer = AbductTimerLimit; - } - } - - public void ReceiveRPC(float timer) - { - AbductTimer = timer; - } - - private void AddVictim(PlayerControl target) - { - if (!IsEnable) return; - - AbductVictim = target; + AbductVictim = Utils.GetPlayerById(victim); AbductTimer = AbductTimerLimit; - Main.AllPlayerSpeed[PenguinId] = SpeedDuringDrag; - Penguin_.MarkDirtySettings(); - LogSpeed(); - Utils.NotifyRoles(SpecifySeer: Penguin_, SpecifyTarget: Penguin_); - SendRPC(); + } + } - if (PlayerControl.LocalPlayer.PlayerId != PenguinId) return; + public void ReceiveRPC(float timer) + { + AbductTimer = timer; + } - switch (IsGoose) - { - case true when target.Is(CustomRoles.Penguin): - Achievements.Type.Awww.CompleteAfterGameEnd(); - break; - case false when target.Is(CustomRoles.Goose): - Achievements.Type.Ohhh.CompleteAfterGameEnd(); - break; - } + private void AddVictim(PlayerControl target) + { + if (!IsEnable) return; - if (IsGoose && PenguinId == PlayerControl.LocalPlayer.PlayerId) - Achievements.Type.Honk.Complete(); + AbductVictim = target; + AbductTimer = AbductTimerLimit; + Main.AllPlayerSpeed[PenguinId] = SpeedDuringDrag; + Penguin_.MarkDirtySettings(); + LogSpeed(); + Utils.NotifyRoles(SpecifySeer: Penguin_, SpecifyTarget: Penguin_); + SendRPC(); + + if (PlayerControl.LocalPlayer.PlayerId != PenguinId) return; + + switch (IsGoose) + { + case true when target.Is(CustomRoles.Penguin): + Achievements.Type.Awww.CompleteAfterGameEnd(); + break; + case false when target.Is(CustomRoles.Goose): + Achievements.Type.Ohhh.CompleteAfterGameEnd(); + break; } - private void RemoveVictim(bool allowDelay = true) - { - if (!IsEnable) return; + if (IsGoose && PenguinId == PlayerControl.LocalPlayer.PlayerId) + Achievements.Type.Honk.Complete(); + } - if (!allowDelay) + private void RemoveVictim(bool allowDelay = true) + { + if (!IsEnable) return; + + if (!allowDelay) + { + AbductVictim = null; + SendRPC(); + } + else + { + LateTask.New(() => { AbductVictim = null; SendRPC(); - } - else - { - LateTask.New(() => - { - AbductVictim = null; - SendRPC(); - }, 1f, log: false); - } - - AbductTimer = 255f; - Main.AllPlayerSpeed[PenguinId] = DefaultSpeed; - Penguin_.MarkDirtySettings(); - LogSpeed(); - Utils.NotifyRoles(SpecifySeer: Penguin_, SpecifyTarget: Penguin_); + }, 1f, log: false); } - private void LogSpeed() + AbductTimer = 255f; + Main.AllPlayerSpeed[PenguinId] = DefaultSpeed; + Penguin_.MarkDirtySettings(); + LogSpeed(); + Utils.NotifyRoles(SpecifySeer: Penguin_, SpecifyTarget: Penguin_); + } + + private void LogSpeed() + { + Logger.Info($" Speed: {Main.AllPlayerSpeed[PenguinId]}", IsGoose ? "Goose" : "Penguin"); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return true; + + bool doKill = !IsGoose; + + if (AbductVictim != null) { - Logger.Info($" Speed: {Main.AllPlayerSpeed[PenguinId]}", IsGoose ? "Goose" : "Penguin"); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return true; - - bool doKill = !IsGoose; - - if (AbductVictim != null) + if (target.PlayerId != AbductVictim.PlayerId) { - if (target.PlayerId != AbductVictim.PlayerId) + // During an abduction, only the abductee can be killed. + if (!IsGoose) { - // During an abduction, only the abductee can be killed. - if (!IsGoose) - { - Penguin_.Kill(AbductVictim); - Penguin_.ResetKillCooldown(); - } - - doKill = false; + Penguin_.Kill(AbductVictim); + Penguin_.ResetKillCooldown(); } - RemoveVictim(); - } - else - { - if (!IsGoose && !killer.RpcCheckAndMurder(target, true)) return false; - doKill = false; - AddVictim(target); } - if (doKill) killer.Kill(target); + RemoveVictim(); + } + else + { + if (!IsGoose && !killer.RpcCheckAndMurder(target, true)) return false; - return false; + doKill = false; + AddVictim(target); } - public override void SetButtonTexts(HudManager hud, byte id) + if (doKill) killer.Kill(target); + + return false; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(AbductVictim != null && !IsGoose ? GetString("KillButtonText") : GetString("PenguinKillButtonText")); + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + stopCount = true; + + // If you meet a meeting with time running out, kill it even if you're on a ladder. + if (AbductVictim != null && AbductTimer <= 0f) { - hud.KillButton?.OverrideText(AbductVictim != null && !IsGoose ? GetString("KillButtonText") : GetString("PenguinKillButtonText")); + if (!IsGoose) Penguin_.Kill(AbductVictim); + + RemoveVictim(false); } - public override void OnReportDeadBody() + if (MeetingKill) { - if (!IsEnable) return; - - stopCount = true; - - // If you meet a meeting with time running out, kill it even if you're on a ladder. - if (AbductVictim != null && AbductTimer <= 0f) - { - if (!IsGoose) Penguin_.Kill(AbductVictim); - - RemoveVictim(false); - } - - if (MeetingKill) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (AbductVictim == null) return; - - if (!IsGoose) Penguin_.Kill(AbductVictim); - - RemoveVictim(); - } - } - - public override void AfterMeetingTasks() - { - if (Main.NormalOptions.MapId == 4) return; - - //Maps other than Airship - RestartAbduct(); - } - - public static void OnSpawnAirship() - { - foreach (KeyValuePair state in Main.PlayerStates) - if (state.Value.Role is Penguin { IsEnable: true } pg) - pg.RestartAbduct(); - } - - private void RestartAbduct() - { - if (!IsEnable) return; - - if (AbductVictim != null) Penguin_.MarkDirtySettings(); - - stopCount = false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable) return; - if (!AmongUsClient.Instance.AmHost) return; - if (!GameStates.IsInTask) return; + if (AbductVictim == null) return; - if (!stopCount) - { - AbductTimer -= Time.fixedDeltaTime; + if (!IsGoose) Penguin_.Kill(AbductVictim); - Count++; - - if (Count >= 10) - { - Count = 0; - Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, AbductTimer); - } - } - - if (AbductVictim != null) - { - if (!Penguin_.IsAlive() || !AbductVictim.IsAlive()) - { - RemoveVictim(); - return; - } - - if (LastNotify != Utils.TimeStamp) - { - Utils.NotifyRoles(SpecifySeer: Penguin_, SpecifyTarget: Penguin_); - LastNotify = Utils.TimeStamp; - } - - if (AbductTimer <= 0f && !Penguin_.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) - { - // Set IsDead to true first (prevents ladder chase) - if (!IsGoose) - { - AbductVictim.Data.IsDead = true; - AbductVictim.Data.MarkDirty(); - } - - // If the penguin himself is on a ladder, kill him after getting off the ladder. - if (!AbductVictim.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) - { - PlayerControl abductVictim = AbductVictim; - - LateTask.New(() => - { - if (IsGoose) return; - - int sId = abductVictim.NetTransform.lastSequenceId + 5; - abductVictim.NetTransform.SnapTo(Penguin_.transform.position, (ushort)sId); - Penguin_.Kill(abductVictim); - - var sender = CustomRpcSender.Create("PenguinMurder"); - - { - sender.AutoStartRpc(abductVictim.NetTransform.NetId, (byte)RpcCalls.SnapTo); - - { - NetHelpers.WriteVector2(Penguin_.transform.position, sender.stream); - sender.Write(abductVictim.NetTransform.lastSequenceId); - } - - sender.EndRpc(); - sender.AutoStartRpc(Penguin_.NetId, (byte)RpcCalls.MurderPlayer); - - { - sender.WriteNetObject(abductVictim); - } - - sender.EndRpc(); - } - - sender.SendMessage(); - }, 0.3f, "PenguinMurder"); - - RemoveVictim(); - } - } - // SnapToRPC does not work for players on top of the ladder, and only the host behaves differently, so teleporting is not done uniformly. - else if (!AbductVictim.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) - { - Vector3 position = Penguin_.transform.position; - - if (!Penguin_.IsHost()) - Utils.TP(AbductVictim.NetTransform, position, log: false); - else - { - LateTask.New(() => - { - if (AbductVictim != null) Utils.TP(AbductVictim.NetTransform, position, log: false); - }, 0.25f, log: false); - } - } - } - else if (AbductTimer <= 100f) AbductTimer = 255f; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; - - if (!Main.PlayerStates.TryGetValue(seer.PlayerId, out PlayerState state) || state.Role is not Penguin pg || pg.AbductVictim == null) return string.Empty; - - return $"\u21b9 {(int)(pg.AbductTimer + 1f)}s"; + RemoveVictim(); } } + + public override void AfterMeetingTasks() + { + if (Main.NormalOptions.MapId == 4) return; + + //Maps other than Airship + RestartAbduct(); + } + + public static void OnSpawnAirship() + { + foreach (KeyValuePair state in Main.PlayerStates) + if (state.Value.Role is Penguin { IsEnable: true } pg) + pg.RestartAbduct(); + } + + private void RestartAbduct() + { + if (!IsEnable) return; + + if (AbductVictim != null) Penguin_.MarkDirtySettings(); + + stopCount = false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable) return; + + if (!AmongUsClient.Instance.AmHost) return; + + if (!GameStates.IsInTask) return; + + if (!stopCount) + { + AbductTimer -= Time.fixedDeltaTime; + + Count++; + + if (Count >= 10) + { + Count = 0; + Utils.SendRPC(CustomRPC.PenguinSync, PenguinId, 2, AbductTimer); + } + } + + if (AbductVictim != null) + { + if (!Penguin_.IsAlive() || !AbductVictim.IsAlive()) + { + RemoveVictim(); + return; + } + + if (LastNotify != Utils.TimeStamp) + { + Utils.NotifyRoles(SpecifySeer: Penguin_, SpecifyTarget: Penguin_); + LastNotify = Utils.TimeStamp; + } + + if (AbductTimer <= 0f && !Penguin_.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) + { + // Set IsDead to true first (prevents ladder chase) + if (!IsGoose) + { + AbductVictim.Data.IsDead = true; + AbductVictim.Data.MarkDirty(); + } + + // If the penguin himself is on a ladder, kill him after getting off the ladder. + if (!AbductVictim.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) + { + PlayerControl abductVictim = AbductVictim; + + LateTask.New(() => + { + if (IsGoose) return; + + int sId = abductVictim.NetTransform.lastSequenceId + 5; + abductVictim.NetTransform.SnapTo(Penguin_.transform.position, (ushort)sId); + Penguin_.Kill(abductVictim); + + var sender = CustomRpcSender.Create("PenguinMurder"); + + { + sender.AutoStartRpc(abductVictim.NetTransform.NetId, (byte)RpcCalls.SnapTo); + + { + NetHelpers.WriteVector2(Penguin_.transform.position, sender.stream); + sender.Write(abductVictim.NetTransform.lastSequenceId); + } + + sender.EndRpc(); + sender.AutoStartRpc(Penguin_.NetId, (byte)RpcCalls.MurderPlayer); + + { + sender.WriteNetObject(abductVictim); + } + + sender.EndRpc(); + } + + sender.SendMessage(); + }, 0.3f, "PenguinMurder"); + + RemoveVictim(); + } + } + // SnapToRPC does not work for players on top of the ladder, and only the host behaves differently, so teleporting is not done uniformly. + else if (!AbductVictim.MyPhysics.Animations.IsPlayingAnyLadderAnimation()) + { + Vector3 position = Penguin_.transform.position; + + if (!Penguin_.IsHost()) + Utils.TP(AbductVictim.NetTransform, position, log: false); + else + { + LateTask.New(() => + { + if (AbductVictim != null) Utils.TP(AbductVictim.NetTransform, position, log: false); + }, 0.25f, log: false); + } + } + } + else if (AbductTimer <= 100f) AbductTimer = 255f; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud)) return string.Empty; + + if (!Main.PlayerStates.TryGetValue(seer.PlayerId, out PlayerState state) || state.Role is not Penguin pg || pg.AbductVictim == null) return string.Empty; + + return $"\u21b9 {(int)(pg.AbductTimer + 1f)}s"; + } } \ No newline at end of file diff --git a/Roles/Impostor/Puppeteer.cs b/Roles/Impostor/Puppeteer.cs index 0abfcf280..8198cd087 100644 --- a/Roles/Impostor/Puppeteer.cs +++ b/Roles/Impostor/Puppeteer.cs @@ -7,224 +7,223 @@ using EHR.Neutral; using UnityEngine; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Puppeteer : RoleBase { - internal class Puppeteer : RoleBase + public static Dictionary PuppeteerList = []; + public static Dictionary PuppeteerDelayList = []; + private static Dictionary PuppeteerDelay = []; + private static Dictionary PuppeteerMaxPuppets = []; + + public static bool On; + + private static OptionItem PuppeteerKCD; + private static OptionItem PuppeteerCD; + private static OptionItem PuppeteerCanKillNormally; + private static OptionItem PuppeteerManipulationBypassesLazy; + private static OptionItem PuppeteerManipulationBypassesLazyGuy; + private static OptionItem PuppeteerPuppetCanKillPuppeteer; + private static OptionItem PuppeteerPuppetCanKillImpostors; + private static OptionItem PuppeteerMaxPuppetsOpt; + private static OptionItem PuppeteerDiesAfterMaxPuppets; + private static OptionItem PuppeteerMinDelay; + private static OptionItem PuppeteerMaxDelay; + private static OptionItem PuppeteerManipulationEndsAfterFixedTime; + private static OptionItem PuppeteerManipulationEndsAfterTime; + private static OptionItem PuppetDiesAlongWithVictim; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary PuppeteerList = []; - public static Dictionary PuppeteerDelayList = []; - private static Dictionary PuppeteerDelay = []; - private static Dictionary PuppeteerMaxPuppets = []; + SetupRoleOptions(3900, TabGroup.ImpostorRoles, CustomRoles.Puppeteer); - public static bool On; + PuppeteerCD = new FloatOptionItem(3911, "PuppeteerCD", new(2.5f, 60f, 2.5f), 22.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem PuppeteerKCD; - private static OptionItem PuppeteerCD; - private static OptionItem PuppeteerCanKillNormally; - private static OptionItem PuppeteerManipulationBypassesLazy; - private static OptionItem PuppeteerManipulationBypassesLazyGuy; - private static OptionItem PuppeteerPuppetCanKillPuppeteer; - private static OptionItem PuppeteerPuppetCanKillImpostors; - private static OptionItem PuppeteerMaxPuppetsOpt; - private static OptionItem PuppeteerDiesAfterMaxPuppets; - private static OptionItem PuppeteerMinDelay; - private static OptionItem PuppeteerMaxDelay; - private static OptionItem PuppeteerManipulationEndsAfterFixedTime; - private static OptionItem PuppeteerManipulationEndsAfterTime; - private static OptionItem PuppetDiesAlongWithVictim; - public override bool IsEnable => On; + PuppeteerCanKillNormally = new BooleanOptionItem(3917, "PuppeteerCanKillNormally", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - public override void SetupCustomOption() + PuppeteerKCD = new FloatOptionItem(3912, "PuppeteerKCD", new(2.5f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(PuppeteerCanKillNormally) + .SetValueFormat(OptionFormat.Seconds); + + PuppeteerMinDelay = new IntegerOptionItem(3913, "PuppeteerMinDelay", new(0, 90, 1), 3, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) + .SetValueFormat(OptionFormat.Seconds); + + PuppeteerMaxDelay = new IntegerOptionItem(3914, "PuppeteerMaxDelay", new(0, 90, 1), 7, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) + .SetValueFormat(OptionFormat.Seconds); + + PuppeteerManipulationEndsAfterFixedTime = new BooleanOptionItem(3915, "PuppeteerManipulationEndsAfterFixedTime", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + + PuppeteerManipulationEndsAfterTime = new IntegerOptionItem(3916, "PuppeteerManipulationEndsAfterTime", new(0, 90, 1), 30, TabGroup.ImpostorRoles) + .SetParent(PuppeteerManipulationEndsAfterFixedTime) + .SetValueFormat(OptionFormat.Seconds); + + PuppeteerManipulationBypassesLazy = new BooleanOptionItem(3918, "PuppeteerManipulationBypassesLazy", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + + PuppeteerManipulationBypassesLazyGuy = new BooleanOptionItem(3922, "PuppeteerManipulationBypassesLazyGuy", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + + PuppeteerPuppetCanKillImpostors = new BooleanOptionItem(3919, "PuppeteerPuppetCanKillImpostors", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + + PuppeteerPuppetCanKillPuppeteer = new BooleanOptionItem(3920, "PuppeteerPuppetCanKillPuppeteer", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + + PuppeteerMaxPuppetsOpt = new IntegerOptionItem(3921, "PuppeteerMaxPuppets", new(0, 30, 1), 5, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) + .SetValueFormat(OptionFormat.Times); + + PuppeteerDiesAfterMaxPuppets = new BooleanOptionItem(3923, "PuppeteerDiesAfterMaxPuppets", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + + PuppetDiesAlongWithVictim = new BooleanOptionItem(3924, "PuppetDiesAlongWithVictim", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + PuppeteerList = []; + PuppeteerDelayList = []; + PuppeteerDelay = []; + PuppeteerMaxPuppets = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = PuppeteerCanKillNormally.GetBool() ? PuppeteerKCD.GetFloat() : PuppeteerCD.GetFloat(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("PuppeteerOperateButtonText")); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (target.Is(CustomRoles.Needy) && PuppeteerManipulationBypassesLazyGuy.GetBool()) return false; + + if (target.Is(CustomRoles.Lazy) && PuppeteerManipulationBypassesLazy.GetBool()) return false; + + if (Medic.ProtectList.Contains(target.PlayerId)) return false; + + if (!PuppeteerMaxPuppets.TryGetValue(killer.PlayerId, out int usesLeft)) { - SetupRoleOptions(3900, TabGroup.ImpostorRoles, CustomRoles.Puppeteer); - - PuppeteerCD = new FloatOptionItem(3911, "PuppeteerCD", new(2.5f, 60f, 2.5f), 22.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) - .SetValueFormat(OptionFormat.Seconds); - - PuppeteerCanKillNormally = new BooleanOptionItem(3917, "PuppeteerCanKillNormally", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppeteerKCD = new FloatOptionItem(3912, "PuppeteerKCD", new(2.5f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(PuppeteerCanKillNormally) - .SetValueFormat(OptionFormat.Seconds); - - PuppeteerMinDelay = new IntegerOptionItem(3913, "PuppeteerMinDelay", new(0, 90, 1), 3, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) - .SetValueFormat(OptionFormat.Seconds); - - PuppeteerMaxDelay = new IntegerOptionItem(3914, "PuppeteerMaxDelay", new(0, 90, 1), 7, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) - .SetValueFormat(OptionFormat.Seconds); - - PuppeteerManipulationEndsAfterFixedTime = new BooleanOptionItem(3915, "PuppeteerManipulationEndsAfterFixedTime", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppeteerManipulationEndsAfterTime = new IntegerOptionItem(3916, "PuppeteerManipulationEndsAfterTime", new(0, 90, 1), 30, TabGroup.ImpostorRoles) - .SetParent(PuppeteerManipulationEndsAfterFixedTime) - .SetValueFormat(OptionFormat.Seconds); - - PuppeteerManipulationBypassesLazy = new BooleanOptionItem(3918, "PuppeteerManipulationBypassesLazy", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppeteerManipulationBypassesLazyGuy = new BooleanOptionItem(3922, "PuppeteerManipulationBypassesLazyGuy", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppeteerPuppetCanKillImpostors = new BooleanOptionItem(3919, "PuppeteerPuppetCanKillImpostors", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppeteerPuppetCanKillPuppeteer = new BooleanOptionItem(3920, "PuppeteerPuppetCanKillPuppeteer", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppeteerMaxPuppetsOpt = new IntegerOptionItem(3921, "PuppeteerMaxPuppets", new(0, 30, 1), 5, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]) - .SetValueFormat(OptionFormat.Times); - - PuppeteerDiesAfterMaxPuppets = new BooleanOptionItem(3923, "PuppeteerDiesAfterMaxPuppets", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); - - PuppetDiesAlongWithVictim = new BooleanOptionItem(3924, "PuppetDiesAlongWithVictim", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Puppeteer]); + usesLeft = PuppeteerMaxPuppetsOpt.GetInt(); + PuppeteerMaxPuppets.Add(killer.PlayerId, usesLeft); } - public override void Add(byte playerId) + if (PuppeteerCanKillNormally.GetBool()) { - On = true; - } - - public override void Init() - { - On = false; - PuppeteerList = []; - PuppeteerDelayList = []; - PuppeteerDelay = []; - PuppeteerMaxPuppets = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = PuppeteerCanKillNormally.GetBool() ? PuppeteerKCD.GetFloat() : PuppeteerCD.GetFloat(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("PuppeteerOperateButtonText")); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (target.Is(CustomRoles.Needy) && PuppeteerManipulationBypassesLazyGuy.GetBool()) return false; - - if (target.Is(CustomRoles.Lazy) && PuppeteerManipulationBypassesLazy.GetBool()) return false; - - if (Medic.ProtectList.Contains(target.PlayerId)) return false; - - if (!PuppeteerMaxPuppets.TryGetValue(killer.PlayerId, out int usesLeft)) + return killer.CheckDoubleTrigger(target, () => { - usesLeft = PuppeteerMaxPuppetsOpt.GetInt(); - PuppeteerMaxPuppets.Add(killer.PlayerId, usesLeft); + PuppeteerList[target.PlayerId] = killer.PlayerId; + PuppeteerDelayList[target.PlayerId] = Utils.TimeStamp; + PuppeteerDelay[target.PlayerId] = IRandom.Instance.Next(PuppeteerMinDelay.GetInt(), PuppeteerMaxDelay.GetInt()); + killer.SetKillCooldown(PuppeteerCD.GetFloat()); + + if (usesLeft <= 1 && PuppeteerDiesAfterMaxPuppets.GetBool()) + LateTask.New(() => { killer.Suicide(); }, 1.5f, "Puppeteer Max Uses Reached => Suicide"); + else + killer.Notify(string.Format(Translator.GetString("PuppeteerUsesRemaining"), usesLeft - 1)); + + PuppeteerMaxPuppets[killer.PlayerId]--; + killer.RPCPlayCustomSound("Line"); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + }); + } + + PuppeteerList[target.PlayerId] = killer.PlayerId; + PuppeteerDelayList[target.PlayerId] = Utils.TimeStamp; + PuppeteerDelay[target.PlayerId] = IRandom.Instance.Next(PuppeteerMinDelay.GetInt(), PuppeteerMaxDelay.GetInt()); + killer.SetKillCooldown(); + + if (usesLeft <= 1) + LateTask.New(() => { killer.Suicide(); }, 1.5f, "Puppeteer Max Uses Reached => Suicide"); + else + killer.Notify(string.Format(Translator.GetString("PuppeteerUsesRemaining"), usesLeft - 1)); + + PuppeteerMaxPuppets[killer.PlayerId]--; + killer.RPCPlayCustomSound("Line"); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + return false; + } + + public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + { + if (player == null || lowLoad) return; + + byte playerId = player.PlayerId; + long now = Utils.TimeStamp; + + if (GameStates.IsInTask && PuppeteerList.ContainsKey(playerId)) + { + if (!player.IsAlive() || Pelican.IsEaten(playerId)) + { + PuppeteerList.Remove(playerId); + PuppeteerDelayList.Remove(playerId); + PuppeteerDelay.Remove(playerId); } - - if (PuppeteerCanKillNormally.GetBool()) + else if (PuppeteerDelayList[playerId] + PuppeteerManipulationEndsAfterTime.GetInt() < now && PuppeteerManipulationEndsAfterFixedTime.GetBool()) { - return killer.CheckDoubleTrigger(target, () => - { - PuppeteerList[target.PlayerId] = killer.PlayerId; - PuppeteerDelayList[target.PlayerId] = Utils.TimeStamp; - PuppeteerDelay[target.PlayerId] = IRandom.Instance.Next(PuppeteerMinDelay.GetInt(), PuppeteerMaxDelay.GetInt()); - killer.SetKillCooldown(PuppeteerCD.GetFloat()); - - if (usesLeft <= 1 && PuppeteerDiesAfterMaxPuppets.GetBool()) - LateTask.New(() => { killer.Suicide(); }, 1.5f, "Puppeteer Max Uses Reached => Suicide"); - else - killer.Notify(string.Format(Translator.GetString("PuppeteerUsesRemaining"), usesLeft - 1)); - - PuppeteerMaxPuppets[killer.PlayerId]--; - killer.RPCPlayCustomSound("Line"); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - }); + PuppeteerList.Remove(playerId); + PuppeteerDelayList.Remove(playerId); + PuppeteerDelay.Remove(playerId); + Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Puppeteer)).Do(x => Utils.NotifyRoles(SpecifySeer: x, SpecifyTarget: player)); } - - PuppeteerList[target.PlayerId] = killer.PlayerId; - PuppeteerDelayList[target.PlayerId] = Utils.TimeStamp; - PuppeteerDelay[target.PlayerId] = IRandom.Instance.Next(PuppeteerMinDelay.GetInt(), PuppeteerMaxDelay.GetInt()); - killer.SetKillCooldown(); - - if (usesLeft <= 1) - LateTask.New(() => { killer.Suicide(); }, 1.5f, "Puppeteer Max Uses Reached => Suicide"); - else - killer.Notify(string.Format(Translator.GetString("PuppeteerUsesRemaining"), usesLeft - 1)); - - PuppeteerMaxPuppets[killer.PlayerId]--; - killer.RPCPlayCustomSound("Line"); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - return false; - } - - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) - { - if (player == null || lowLoad) return; - - byte playerId = player.PlayerId; - long now = Utils.TimeStamp; - - if (GameStates.IsInTask && PuppeteerList.ContainsKey(playerId)) + else if (PuppeteerDelayList[playerId] + PuppeteerDelay[playerId] < now) { - if (!player.IsAlive() || Pelican.IsEaten(playerId)) + Vector2 puppeteerPos = player.transform.position; + Dictionary targetDistance = []; + + foreach (PlayerControl target in Main.AllAlivePlayerControls) { - PuppeteerList.Remove(playerId); - PuppeteerDelayList.Remove(playerId); - PuppeteerDelay.Remove(playerId); + if (target.PlayerId == playerId || target.Is(CustomRoles.Pestilence)) continue; + + if (target.Is(CustomRoles.Puppeteer) && !PuppeteerPuppetCanKillPuppeteer.GetBool()) continue; + + if (target.Is(CustomRoleTypes.Impostor) && !PuppeteerPuppetCanKillImpostors.GetBool()) continue; + + float dis = Vector2.Distance(puppeteerPos, target.transform.position); + targetDistance.Add(target.PlayerId, dis); } - else if (PuppeteerDelayList[playerId] + PuppeteerManipulationEndsAfterTime.GetInt() < now && PuppeteerManipulationEndsAfterFixedTime.GetBool()) + + if (targetDistance.Count > 0) { - PuppeteerList.Remove(playerId); - PuppeteerDelayList.Remove(playerId); - PuppeteerDelay.Remove(playerId); - Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Puppeteer)).Do(x => Utils.NotifyRoles(SpecifySeer: x, SpecifyTarget: player)); - } - else if (PuppeteerDelayList[playerId] + PuppeteerDelay[playerId] < now) - { - Vector2 puppeteerPos = player.transform.position; - Dictionary targetDistance = []; + KeyValuePair min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); + PlayerControl target = Utils.GetPlayerById(min.Key); + float KillRange = NormalGameOptionsV08.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; - foreach (PlayerControl target in Main.AllAlivePlayerControls) + if (min.Value <= KillRange && player.CanMove && target.CanMove) { - if (target.PlayerId == playerId || target.Is(CustomRoles.Pestilence)) continue; - - if (target.Is(CustomRoles.Puppeteer) && !PuppeteerPuppetCanKillPuppeteer.GetBool()) continue; - - if (target.Is(CustomRoleTypes.Impostor) && !PuppeteerPuppetCanKillImpostors.GetBool()) continue; - - float dis = Vector2.Distance(puppeteerPos, target.transform.position); - targetDistance.Add(target.PlayerId, dis); - } - - if (targetDistance.Count > 0) - { - KeyValuePair min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); - PlayerControl target = Utils.GetPlayerById(min.Key); - float KillRange = NormalGameOptionsV08.KillDistances[Mathf.Clamp(Main.NormalOptions.KillDistance, 0, 2)]; - - if (min.Value <= KillRange && player.CanMove && target.CanMove) + if (player.RpcCheckAndMurder(target, true)) { - if (player.RpcCheckAndMurder(target, true)) - { - byte puppeteerId = PuppeteerList[playerId]; - RPC.PlaySoundRPC(puppeteerId, Sounds.KillSound); - PlayerControl puppeteer = Utils.GetPlayerById(puppeteerId); - target.SetRealKiller(puppeteer); - player.Kill(target); - if (PuppetDiesAlongWithVictim.GetBool()) player.Suicide(realKiller: puppeteer); + byte puppeteerId = PuppeteerList[playerId]; + RPC.PlaySoundRPC(puppeteerId, Sounds.KillSound); + PlayerControl puppeteer = Utils.GetPlayerById(puppeteerId); + target.SetRealKiller(puppeteer); + player.Kill(target); + if (PuppetDiesAlongWithVictim.GetBool()) player.Suicide(realKiller: puppeteer); - player.MarkDirtySettings(); - target.MarkDirtySettings(); - PuppeteerList.Remove(playerId); - PuppeteerDelayList.Remove(playerId); - PuppeteerDelay.Remove(playerId); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); - } + player.MarkDirtySettings(); + target.MarkDirtySettings(); + PuppeteerList.Remove(playerId); + PuppeteerDelayList.Remove(playerId); + PuppeteerDelay.Remove(playerId); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); } } } diff --git a/Roles/Impostor/QuickShooter.cs b/Roles/Impostor/QuickShooter.cs index 7d31e9097..204a3063f 100644 --- a/Roles/Impostor/QuickShooter.cs +++ b/Roles/Impostor/QuickShooter.cs @@ -5,150 +5,149 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class QuickShooter : RoleBase { - internal class QuickShooter : RoleBase + private const int Id = 1800; + public static List PlayerIdList = []; + private static OptionItem KillCooldown; + private static OptionItem MeetingReserved; + public static OptionItem ShapeshiftCooldown; + + public static Dictionary ShotLimit = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 1800; - public static List PlayerIdList = []; - private static OptionItem KillCooldown; - private static OptionItem MeetingReserved; - public static OptionItem ShapeshiftCooldown; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.QuickShooter); - public static Dictionary ShotLimit = []; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.QuickShooter]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + ShapeshiftCooldown = new FloatOptionItem(Id + 12, "QuickShooterShapeshiftCooldown", new(0f, 180f, 2.5f), 40f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.QuickShooter]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + MeetingReserved = new IntegerOptionItem(Id + 14, "MeetingReserved", new(0, 15, 1), 1, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.QuickShooter]) + .SetValueFormat(OptionFormat.Pieces); + } + + public override void Init() + { + PlayerIdList = []; + ShotLimit = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + ShotLimit.TryAdd(playerId, 0); + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetQuickShooterShotLimit, SendOption.Reliable); + writer.Write(playerId); + writer.Write(ShotLimit[playerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte QuickShooterId = reader.ReadByte(); + int Limit = reader.ReadInt32(); + ShotLimit.TryAdd(QuickShooterId, Limit); + ShotLimit[QuickShooterId] = Limit; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); + else { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.QuickShooter); + if (Options.UsePets.GetBool()) return; - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.QuickShooter]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftCooldown = new FloatOptionItem(Id + 12, "QuickShooterShapeshiftCooldown", new(0f, 180f, 2.5f), 40f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.QuickShooter]) - .SetValueFormat(OptionFormat.Seconds); - - MeetingReserved = new IntegerOptionItem(Id + 14, "MeetingReserved", new(0, 15, 1), 1, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.QuickShooter]) - .SetValueFormat(OptionFormat.Pieces); + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; } + } - public override void Init() + public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) + { + if (Main.KillTimers[pc.PlayerId] <= 0 && (shapeshifting || Options.UseUnshiftTrigger.GetBool())) Store(pc); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + if (Main.KillTimers[pc.PlayerId] <= 0) Store(pc); + + return false; + } + + public override void OnPet(PlayerControl pc) + { + if (Main.KillTimers[pc.PlayerId] <= 0) Store(pc); + } + + private static void Store(PlayerControl pc) + { + ShotLimit[pc.PlayerId]++; + SendRPC(pc.PlayerId); + pc.ResetKillCooldown(); + pc.SetKillCooldown(); + pc.Notify(Translator.GetString("QuickShooterStoraging")); + Logger.Info($"{Utils.GetPlayerById(pc.PlayerId)?.GetNameWithRole().RemoveHtmlTags()} : Remaining: {ShotLimit[pc.PlayerId]} bullets", "QuickShooter"); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void OnReportDeadBody() + { + Dictionary NewSL = []; + foreach (KeyValuePair sl in ShotLimit) NewSL.Add(sl.Key, Math.Clamp(sl.Value, 0, MeetingReserved.GetInt())); + + foreach (KeyValuePair sl in NewSL) { - PlayerIdList = []; - ShotLimit = []; + ShotLimit[sl.Key] = sl.Value; + SendRPC(sl.Key); } + } - public override void Add(byte playerId) + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + ShotLimit.TryAdd(killer.PlayerId, 0); + if (ShotLimit[killer.PlayerId] > 0) LateTask.New(() => killer.SetKillCooldown(0.01f), 0.01f, "QuickShooterKill: Set KCD to 0s"); + + ShotLimit[killer.PlayerId]--; + ShotLimit[killer.PlayerId] = Math.Max(ShotLimit[killer.PlayerId], 0); + SendRPC(killer.PlayerId); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return Utils.ColorString(ShotLimit.ContainsKey(playerId) && ShotLimit[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.QuickShooter).ShadeColor(0.25f) : Color.gray, ShotLimit.TryGetValue(playerId, out int shotLimit) ? $"({shotLimit})" : "Invalid"); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (Options.UsePets.GetBool()) + hud.PetButton?.OverrideText(Translator.GetString("QuickShooterShapeshiftText")); + else { - PlayerIdList.Add(playerId); - ShotLimit.TryAdd(playerId, 0); - } - - private static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetQuickShooterShotLimit, SendOption.Reliable); - writer.Write(playerId); - writer.Write(ShotLimit[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte QuickShooterId = reader.ReadByte(); - int Limit = reader.ReadInt32(); - ShotLimit.TryAdd(QuickShooterId, Limit); - ShotLimit[QuickShooterId] = Limit; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); - else - { - if (Options.UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - - public override bool OnShapeshift(PlayerControl pc, PlayerControl target, bool shapeshifting) - { - if (Main.KillTimers[pc.PlayerId] <= 0 && (shapeshifting || Options.UseUnshiftTrigger.GetBool())) Store(pc); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - if (Main.KillTimers[pc.PlayerId] <= 0) Store(pc); - - return false; - } - - public override void OnPet(PlayerControl pc) - { - if (Main.KillTimers[pc.PlayerId] <= 0) Store(pc); - } - - private static void Store(PlayerControl pc) - { - ShotLimit[pc.PlayerId]++; - SendRPC(pc.PlayerId); - pc.ResetKillCooldown(); - pc.SetKillCooldown(); - pc.Notify(Translator.GetString("QuickShooterStoraging")); - Logger.Info($"{Utils.GetPlayerById(pc.PlayerId)?.GetNameWithRole().RemoveHtmlTags()} : Remaining: {ShotLimit[pc.PlayerId]} bullets", "QuickShooter"); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void OnReportDeadBody() - { - Dictionary NewSL = []; - foreach (KeyValuePair sl in ShotLimit) NewSL.Add(sl.Key, Math.Clamp(sl.Value, 0, MeetingReserved.GetInt())); - - foreach (KeyValuePair sl in NewSL) - { - ShotLimit[sl.Key] = sl.Value; - SendRPC(sl.Key); - } - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - ShotLimit.TryAdd(killer.PlayerId, 0); - if (ShotLimit[killer.PlayerId] > 0) LateTask.New(() => killer.SetKillCooldown(0.01f), 0.01f, "QuickShooterKill: Set KCD to 0s"); - - ShotLimit[killer.PlayerId]--; - ShotLimit[killer.PlayerId] = Math.Max(ShotLimit[killer.PlayerId], 0); - SendRPC(killer.PlayerId); - } - - public override string GetProgressText(byte playerId, bool comms) - { - return Utils.ColorString(ShotLimit.ContainsKey(playerId) && ShotLimit[playerId] > 0 ? Utils.GetRoleColor(CustomRoles.QuickShooter).ShadeColor(0.25f) : Color.gray, ShotLimit.TryGetValue(playerId, out int shotLimit) ? $"({shotLimit})" : "Invalid"); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (Options.UsePets.GetBool()) - hud.PetButton?.OverrideText(Translator.GetString("QuickShooterShapeshiftText")); - else - { - hud.AbilityButton?.OverrideText(Translator.GetString("QuickShooterShapeshiftText")); - hud.AbilityButton?.SetUsesRemaining(ShotLimit.GetValueOrDefault(PlayerControl.LocalPlayer.PlayerId, 0)); - } + hud.AbilityButton?.OverrideText(Translator.GetString("QuickShooterShapeshiftText")); + hud.AbilityButton?.SetUsesRemaining(ShotLimit.GetValueOrDefault(PlayerControl.LocalPlayer.PlayerId, 0)); } } } \ No newline at end of file diff --git a/Roles/Impostor/Refugee.cs b/Roles/Impostor/Refugee.cs index b45011601..3c3941916 100644 --- a/Roles/Impostor/Refugee.cs +++ b/Roles/Impostor/Refugee.cs @@ -1,37 +1,36 @@ using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Refugee : RoleBase { - internal class Refugee : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { } + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void SetupCustomOption() { } + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Options.RefugeeKillCD.GetFloat(); + } - public override void Init() - { - On = false; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(true); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Options.RefugeeKillCD.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(true); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); } } \ No newline at end of file diff --git a/Roles/Impostor/RiftMaker.cs b/Roles/Impostor/RiftMaker.cs index 34edbfe83..56d3e8f9d 100644 --- a/Roles/Impostor/RiftMaker.cs +++ b/Roles/Impostor/RiftMaker.cs @@ -5,167 +5,166 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class RiftMaker : RoleBase { - public class RiftMaker : RoleBase + private const int Id = 640900; + public static List PlayerIdList = []; + + public static OptionItem KillCooldown; + public static OptionItem ShapeshiftCooldown; + public long LastTP = TimeStamp; + + public List Marks = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 640900; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.RiftMaker); - public static OptionItem KillCooldown; - public static OptionItem ShapeshiftCooldown; - public long LastTP = TimeStamp; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.RiftMaker]) + .SetValueFormat(OptionFormat.Seconds); - public List Marks = []; + ShapeshiftCooldown = new FloatOptionItem(Id + 11, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.RiftMaker]) + .SetValueFormat(OptionFormat.Seconds); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + Marks = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + LastTP = TimeStamp; + Marks = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); + else { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.RiftMaker); + if (UsePets.GetBool()) return; - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.RiftMaker]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftCooldown = new FloatOptionItem(Id + 11, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.RiftMaker]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - Marks = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - LastTP = TimeStamp; - Marks = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); - else - { - if (UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - AURoleOptions.ShapeshifterLeaveSkin = true; - } - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask) return; - - if (Pelican.IsEaten(player.PlayerId) || player.Data.IsDead) return; - - if (!player.Is(CustomRoles.RiftMaker)) return; - - if (Marks.Count != 2) return; - - if (Vector2.Distance(Marks[0], Marks[1]) <= 4f) - { - player.Notify(GetString("IncorrectMarks")); - Marks.Clear(); - return; - } - - if (LastTP + 5 > TimeStamp) return; - - Vector2 position = player.transform.position; - - var isTP = false; - Vector2 from = Marks[0]; - - foreach (Vector2 mark in Marks.ToArray()) - { - float dis = Vector2.Distance(mark, position); - if (dis > 2f) continue; - - isTP = true; - from = mark; - } - - if (isTP) - { - LastTP = TimeStamp; - - if (from == Marks[0]) - player.TP(Marks[1]); - else if (from == Marks[1]) - player.TP(Marks[0]); - else - Logger.Error($"Teleport failed - from: {from}", "RiftMakerTP"); - } - } - - public override void OnReportDeadBody() - { - LastTP = TimeStamp; - } - - public override void OnEnterVent(PlayerControl player, Vent vent) - { - Marks.Clear(); - player.Notify(GetString("MarksCleared")); - - player.MyPhysics?.RpcBootFromVent(vent.Id); - } - - public override bool OnShapeshift(PlayerControl player, PlayerControl target, bool shapeshifting) - { - if (player == null) return false; - - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - if (Marks.Count >= 2) return false; - - Mark(player); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - if (pc == null) return false; - - if (Marks.Count >= 2) return false; - - Mark(pc); - - return false; - } - - public override void OnPet(PlayerControl pc) - { - if (pc == null) return; - - if (Marks.Count >= 2) return; - - Mark(pc); - } - - private void Mark(PlayerControl player) - { - Marks.Add(player.transform.position); - if (Marks.Count == 2) LastTP = TimeStamp; - - player.Notify(GetString("MarkDone")); - } - - public override string GetProgressText(byte playerId, bool comms) - { - return $" - {(Marks.Count == 2 ? "" : "")}{Marks.Count}/2"; + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + AURoleOptions.ShapeshifterLeaveSkin = true; } } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) return; + + if (Pelican.IsEaten(player.PlayerId) || player.Data.IsDead) return; + + if (!player.Is(CustomRoles.RiftMaker)) return; + + if (Marks.Count != 2) return; + + if (Vector2.Distance(Marks[0], Marks[1]) <= 4f) + { + player.Notify(GetString("IncorrectMarks")); + Marks.Clear(); + return; + } + + if (LastTP + 5 > TimeStamp) return; + + Vector2 position = player.transform.position; + + var isTP = false; + Vector2 from = Marks[0]; + + foreach (Vector2 mark in Marks.ToArray()) + { + float dis = Vector2.Distance(mark, position); + if (dis > 2f) continue; + + isTP = true; + from = mark; + } + + if (isTP) + { + LastTP = TimeStamp; + + if (from == Marks[0]) + player.TP(Marks[1]); + else if (from == Marks[1]) + player.TP(Marks[0]); + else + Logger.Error($"Teleport failed - from: {from}", "RiftMakerTP"); + } + } + + public override void OnReportDeadBody() + { + LastTP = TimeStamp; + } + + public override void OnEnterVent(PlayerControl player, Vent vent) + { + Marks.Clear(); + player.Notify(GetString("MarksCleared")); + + player.MyPhysics?.RpcBootFromVent(vent.Id); + } + + public override bool OnShapeshift(PlayerControl player, PlayerControl target, bool shapeshifting) + { + if (player == null) return false; + + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + if (Marks.Count >= 2) return false; + + Mark(player); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + if (pc == null) return false; + + if (Marks.Count >= 2) return false; + + Mark(pc); + + return false; + } + + public override void OnPet(PlayerControl pc) + { + if (pc == null) return; + + if (Marks.Count >= 2) return; + + Mark(pc); + } + + private void Mark(PlayerControl player) + { + Marks.Add(player.transform.position); + if (Marks.Count == 2) LastTP = TimeStamp; + + player.Notify(GetString("MarkDone")); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return $" - {(Marks.Count == 2 ? "" : "")}{Marks.Count}/2"; + } } \ No newline at end of file diff --git a/Roles/Impostor/Saboteur.cs b/Roles/Impostor/Saboteur.cs index 9a69cb0d9..96a07c486 100644 --- a/Roles/Impostor/Saboteur.cs +++ b/Roles/Impostor/Saboteur.cs @@ -1,63 +1,62 @@ using System; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Saboteur : RoleBase { - internal class Saboteur : RoleBase + public static bool On; + private byte SaboteurId; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private byte SaboteurId; - public override bool IsEnable => On; + SetupRoleOptions(10005, TabGroup.ImpostorRoles, CustomRoles.Saboteur); - public override void SetupCustomOption() + SaboteurCD = new FloatOptionItem(10015, "KillCooldown", new(0f, 180f, 2.5f), 17.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Saboteur]) + .SetValueFormat(OptionFormat.Seconds); + + SaboteurCDAfterMeetings = new FloatOptionItem(10016, "AfterMeetingKillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Saboteur]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + SaboteurId = playerId; + } + + public override void Init() + { + On = false; + SaboteurId = byte.MaxValue; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return base.CanUseKillButton(pc) && (Utils.IsActive(SystemTypes.Electrical) || Utils.IsActive(SystemTypes.Comms) || Utils.IsActive(SystemTypes.MushroomMixupSabotage) || Utils.IsActive(SystemTypes.Laboratory) || Utils.IsActive(SystemTypes.LifeSupp) || Utils.IsActive(SystemTypes.Reactor) || Utils.IsActive(SystemTypes.HeliSabotage)); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = SaboteurCDAfterMeetings.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Math.Abs(Main.AllPlayerKillCooldown[killer.PlayerId] - SaboteurCD.GetFloat()) > 0.5f) { - SetupRoleOptions(10005, TabGroup.ImpostorRoles, CustomRoles.Saboteur); - - SaboteurCD = new FloatOptionItem(10015, "KillCooldown", new(0f, 180f, 2.5f), 17.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Saboteur]) - .SetValueFormat(OptionFormat.Seconds); - - SaboteurCDAfterMeetings = new FloatOptionItem(10016, "AfterMeetingKillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Saboteur]) - .SetValueFormat(OptionFormat.Seconds); + Main.AllPlayerKillCooldown[killer.PlayerId] = SaboteurCD.GetFloat(); + killer.SyncSettings(); } - public override void Add(byte playerId) - { - On = true; - SaboteurId = playerId; - } + return base.OnCheckMurder(killer, target); + } - public override void Init() - { - On = false; - SaboteurId = byte.MaxValue; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return base.CanUseKillButton(pc) && (Utils.IsActive(SystemTypes.Electrical) || Utils.IsActive(SystemTypes.Comms) || Utils.IsActive(SystemTypes.MushroomMixupSabotage) || Utils.IsActive(SystemTypes.Laboratory) || Utils.IsActive(SystemTypes.LifeSupp) || Utils.IsActive(SystemTypes.Reactor) || Utils.IsActive(SystemTypes.HeliSabotage)); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = SaboteurCDAfterMeetings.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Math.Abs(Main.AllPlayerKillCooldown[killer.PlayerId] - SaboteurCD.GetFloat()) > 0.5f) - { - Main.AllPlayerKillCooldown[killer.PlayerId] = SaboteurCD.GetFloat(); - killer.SyncSettings(); - } - - return base.OnCheckMurder(killer, target); - } - - public override void OnReportDeadBody() - { - Main.AllPlayerKillCooldown[SaboteurId] = SaboteurCDAfterMeetings.GetFloat(); - } + public override void OnReportDeadBody() + { + Main.AllPlayerKillCooldown[SaboteurId] = SaboteurCDAfterMeetings.GetFloat(); } } \ No newline at end of file diff --git a/Roles/Impostor/Sans.cs b/Roles/Impostor/Sans.cs index 280b97f60..f30b009b6 100644 --- a/Roles/Impostor/Sans.cs +++ b/Roles/Impostor/Sans.cs @@ -4,119 +4,118 @@ using AmongUs.GameOptions; using EHR.Neutral; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Sans : RoleBase { - public class Sans : RoleBase + private const int Id = 600; + public static List PlayerIdList = []; + + private static OptionItem DefaultKillCooldown; + private static OptionItem ReduceKillCooldown; + private static OptionItem MinKillCooldown; + public static OptionItem BardChance; + private bool CanVent; + + private float DefaultKCD; + private bool HasImpostorVision; + private float MinKCD; + + private float NowCooldown; + private float ReduceKCD; + private bool ResetKCDOnMeeting; + + private CustomRoles UsedRole; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 600; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sans); - private static OptionItem DefaultKillCooldown; - private static OptionItem ReduceKillCooldown; - private static OptionItem MinKillCooldown; - public static OptionItem BardChance; - private bool CanVent; + DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) + .SetValueFormat(OptionFormat.Seconds); - private float DefaultKCD; - private bool HasImpostorVision; - private float MinKCD; + ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 3.5f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) + .SetValueFormat(OptionFormat.Seconds); - private float NowCooldown; - private float ReduceKCD; - private bool ResetKCDOnMeeting; + MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) + .SetValueFormat(OptionFormat.Seconds); - private CustomRoles UsedRole; + BardChance = new IntegerOptionItem(Id + 13, "BardChance", new(0, 100, 5), 0, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) + .SetValueFormat(OptionFormat.Percent); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + + UsedRole = Main.PlayerStates[playerId].MainRole; + + switch (UsedRole) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sans); - - DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) - .SetValueFormat(OptionFormat.Seconds); - - ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 3.5f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) - .SetValueFormat(OptionFormat.Seconds); - - MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 2.5f), 10f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) - .SetValueFormat(OptionFormat.Seconds); - - BardChance = new IntegerOptionItem(Id + 13, "BardChance", new(0, 100, 5), 0, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sans]) - .SetValueFormat(OptionFormat.Percent); + case CustomRoles.Sans: + DefaultKCD = DefaultKillCooldown.GetFloat(); + ReduceKCD = ReduceKillCooldown.GetFloat(); + MinKCD = MinKillCooldown.GetFloat(); + ResetKCDOnMeeting = false; + HasImpostorVision = true; + CanVent = true; + break; + case CustomRoles.Juggernaut: + DefaultKCD = Juggernaut.DefaultKillCooldown.GetFloat(); + ReduceKCD = Juggernaut.ReduceKillCooldown.GetFloat(); + MinKCD = Juggernaut.MinKillCooldown.GetFloat(); + ResetKCDOnMeeting = false; + HasImpostorVision = Juggernaut.HasImpostorVision.GetBool(); + CanVent = Juggernaut.CanVent.GetBool(); + break; + case CustomRoles.Reckless: + DefaultKCD = Reckless.DefaultKillCooldown.GetFloat(); + ReduceKCD = Reckless.ReduceKillCooldown.GetFloat(); + MinKCD = Reckless.MinKillCooldown.GetFloat(); + ResetKCDOnMeeting = true; + HasImpostorVision = Reckless.HasImpostorVision.GetBool(); + CanVent = Reckless.CanVent.GetBool(); + break; } - public override void Init() - { - PlayerIdList = []; - } + NowCooldown = DefaultKCD; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = NowCooldown; + } - UsedRole = Main.PlayerStates[playerId].MainRole; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpostorVision); + } - switch (UsedRole) - { - case CustomRoles.Sans: - DefaultKCD = DefaultKillCooldown.GetFloat(); - ReduceKCD = ReduceKillCooldown.GetFloat(); - MinKCD = MinKillCooldown.GetFloat(); - ResetKCDOnMeeting = false; - HasImpostorVision = true; - CanVent = true; - break; - case CustomRoles.Juggernaut: - DefaultKCD = Juggernaut.DefaultKillCooldown.GetFloat(); - ReduceKCD = Juggernaut.ReduceKillCooldown.GetFloat(); - MinKCD = Juggernaut.MinKillCooldown.GetFloat(); - ResetKCDOnMeeting = false; - HasImpostorVision = Juggernaut.HasImpostorVision.GetBool(); - CanVent = Juggernaut.CanVent.GetBool(); - break; - case CustomRoles.Reckless: - DefaultKCD = Reckless.DefaultKillCooldown.GetFloat(); - ReduceKCD = Reckless.ReduceKillCooldown.GetFloat(); - MinKCD = Reckless.MinKillCooldown.GetFloat(); - ResetKCDOnMeeting = true; - HasImpostorVision = Reckless.HasImpostorVision.GetBool(); - CanVent = Reckless.CanVent.GetBool(); - break; - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent; + } - NowCooldown = DefaultKCD; - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + NowCooldown = Math.Clamp(NowCooldown - ReduceKCD, MinKCD, DefaultKCD); + killer?.ResetKillCooldown(); + killer?.SyncSettings(); + return base.OnCheckMurder(killer, target); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = NowCooldown; - } + public override void OnReportDeadBody() + { + if (!ResetKCDOnMeeting) return; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpostorVision); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - NowCooldown = Math.Clamp(NowCooldown - ReduceKCD, MinKCD, DefaultKCD); - killer?.ResetKillCooldown(); - killer?.SyncSettings(); - return base.OnCheckMurder(killer, target); - } - - public override void OnReportDeadBody() - { - if (!ResetKCDOnMeeting) return; - - NowCooldown = DefaultKCD; - } + NowCooldown = DefaultKCD; } } \ No newline at end of file diff --git a/Roles/Impostor/Sapper.cs b/Roles/Impostor/Sapper.cs index 3160d2656..431a51774 100644 --- a/Roles/Impostor/Sapper.cs +++ b/Roles/Impostor/Sapper.cs @@ -6,149 +6,148 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Sapper : RoleBase { - public class Sapper : RoleBase + private const int Id = 643000; + public static List PlayerIdList = []; + + public static OptionItem ShapeshiftCooldown; + private static OptionItem Delay; + private static OptionItem Radius; + + public static Dictionary Bombs = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 643000; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sapper); - public static OptionItem ShapeshiftCooldown; - private static OptionItem Delay; - private static OptionItem Radius; + ShapeshiftCooldown = new FloatOptionItem(Id + 11, "SapperCD", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sapper]) + .SetValueFormat(OptionFormat.Seconds); - public static Dictionary Bombs = []; + Delay = new IntegerOptionItem(Id + 12, "SapperDelay", new(1, 15, 1), 5, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sapper]) + .SetValueFormat(OptionFormat.Times); - public override bool IsEnable => PlayerIdList.Count > 0; + Radius = new FloatOptionItem(Id + 13, "SapperRadius", new(0f, 10f, 0.25f), 3f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sapper]) + .SetValueFormat(OptionFormat.Multiplier); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + Bombs = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); + else { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sapper); - - ShapeshiftCooldown = new FloatOptionItem(Id + 11, "SapperCD", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sapper]) - .SetValueFormat(OptionFormat.Seconds); - - Delay = new IntegerOptionItem(Id + 12, "SapperDelay", new(1, 15, 1), 5, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sapper]) - .SetValueFormat(OptionFormat.Times); - - Radius = new FloatOptionItem(Id + 13, "SapperRadius", new(0f, 10f, 0.25f), 3f, TabGroup.ImpostorRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Sapper]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Init() - { - PlayerIdList = []; - Bombs = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); - else - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = 300f; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - return PlaceBomb(shapeshifter); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return false; - } - - public override void OnPet(PlayerControl pc) - { - PlaceBomb(pc); - } - - public override bool OnVanish(PlayerControl pc) - { - return PlaceBomb(pc); - } - - private static bool PlaceBomb(PlayerControl pc) - { - if (pc == null) return false; - - if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; - - Bombs.TryAdd(pc.Pos(), TimeStamp); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc == null || Bombs.Count == 0 || !GameStates.IsInTask || !pc.IsAlive()) return; - - foreach (KeyValuePair bomb in Bombs.Where(bomb => bomb.Value + Delay.GetInt() < TimeStamp)) - { - var b = false; - IEnumerable players = GetPlayersInRadius(Radius.GetFloat(), bomb.Key); - - foreach (PlayerControl tg in players.ToArray()) - { - if (tg.PlayerId == pc.PlayerId) - { - b = true; - continue; - } - - tg.Suicide(PlayerState.DeathReason.Bombed, pc); - } - - Bombs.Remove(bomb.Key); - pc.Notify(GetString("MagicianBombExploded")); - - if (b) - { - LateTask.New(() => - { - if (!GameStates.IsEnded) pc.Suicide(PlayerState.DeathReason.Bombed); - }, 0.5f, "Sapper Bomb Suicide"); - } - } - - var sb = new StringBuilder(); - foreach (long x in Bombs.Values) sb.Append(string.Format(GetString("MagicianBombExlodesIn"), Delay.GetInt() - (TimeStamp - x) + 1)); - - pc.Notify(sb.ToString(), overrideAll: true); - } - - public override void OnReportDeadBody() - { - Bombs.Clear(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton?.OverrideText(GetString("BomberShapeshiftText")); - else - hud.AbilityButton?.OverrideText(GetString("BomberShapeshiftText")); + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; } } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = 300f; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + return PlaceBomb(shapeshifter); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return false; + } + + public override void OnPet(PlayerControl pc) + { + PlaceBomb(pc); + } + + public override bool OnVanish(PlayerControl pc) + { + return PlaceBomb(pc); + } + + private static bool PlaceBomb(PlayerControl pc) + { + if (pc == null) return false; + + if (!pc.IsAlive() || Pelican.IsEaten(pc.PlayerId)) return false; + + Bombs.TryAdd(pc.Pos(), TimeStamp); + + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc == null || Bombs.Count == 0 || !GameStates.IsInTask || !pc.IsAlive()) return; + + foreach (KeyValuePair bomb in Bombs.Where(bomb => bomb.Value + Delay.GetInt() < TimeStamp)) + { + var b = false; + IEnumerable players = GetPlayersInRadius(Radius.GetFloat(), bomb.Key); + + foreach (PlayerControl tg in players.ToArray()) + { + if (tg.PlayerId == pc.PlayerId) + { + b = true; + continue; + } + + tg.Suicide(PlayerState.DeathReason.Bombed, pc); + } + + Bombs.Remove(bomb.Key); + pc.Notify(GetString("MagicianBombExploded")); + + if (b) + { + LateTask.New(() => + { + if (!GameStates.IsEnded) pc.Suicide(PlayerState.DeathReason.Bombed); + }, 0.5f, "Sapper Bomb Suicide"); + } + } + + var sb = new StringBuilder(); + foreach (long x in Bombs.Values) sb.Append(string.Format(GetString("MagicianBombExlodesIn"), Delay.GetInt() - (TimeStamp - x) + 1)); + + pc.Notify(sb.ToString(), overrideAll: true); + } + + public override void OnReportDeadBody() + { + Bombs.Clear(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton?.OverrideText(GetString("BomberShapeshiftText")); + else + hud.AbilityButton?.OverrideText(GetString("BomberShapeshiftText")); + } } \ No newline at end of file diff --git a/Roles/Impostor/Scavenger.cs b/Roles/Impostor/Scavenger.cs index 1252d0f08..b1d8d65dd 100644 --- a/Roles/Impostor/Scavenger.cs +++ b/Roles/Impostor/Scavenger.cs @@ -2,71 +2,70 @@ using EHR.Neutral; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Scavenger : RoleBase { - internal class Scavenger : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + SetupRoleOptions(4000, TabGroup.ImpostorRoles, CustomRoles.Scavenger); - public override void SetupCustomOption() + ScavengerKillCooldown = new FloatOptionItem(4010, "KillCooldown", new(0f, 180f, 2.5f), 35f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scavenger]) + .SetValueFormat(OptionFormat.Seconds); + + ScavengerKillDuration = new FloatOptionItem(4011, "ScavengerKillDuration", new(0f, 90f, 0.5f), 5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Scavenger]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = ScavengerKillCooldown.GetFloat(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!target.Is(CustomRoles.Pestilence)) { - SetupRoleOptions(4000, TabGroup.ImpostorRoles, CustomRoles.Scavenger); + float dur = ScavengerKillDuration.GetFloat(); + killer.Notify("....", dur); + killer.SetKillCooldown(dur + 0.5f); - ScavengerKillCooldown = new FloatOptionItem(4010, "KillCooldown", new(0f, 180f, 2.5f), 35f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scavenger]) - .SetValueFormat(OptionFormat.Seconds); - - ScavengerKillDuration = new FloatOptionItem(4011, "ScavengerKillDuration", new(0f, 90f, 0.5f), 5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Scavenger]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = ScavengerKillCooldown.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!target.Is(CustomRoles.Pestilence)) + LateTask.New(() => { - float dur = ScavengerKillDuration.GetFloat(); - killer.Notify("....", dur); - killer.SetKillCooldown(dur + 0.5f); + if (Vector2.Distance(killer.Pos(), target.Pos()) > 2f) return; - LateTask.New(() => - { - if (Vector2.Distance(killer.Pos(), target.Pos()) > 2f) return; + target.TP(Pelican.GetBlackRoomPS()); + target.Suicide(PlayerState.DeathReason.Scavenged, killer); + if (target.Is(CustomRoles.Pelican)) Pelican.OnPelicanDied(target.PlayerId); - target.TP(Pelican.GetBlackRoomPS()); - target.Suicide(PlayerState.DeathReason.Scavenged, killer); - if (target.Is(CustomRoles.Pelican)) Pelican.OnPelicanDied(target.PlayerId); - - killer.SetKillCooldown(); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Scavenger), Translator.GetString("KilledByScavenger"))); - }, dur, "Scavenger Kill"); - - return false; - } - - killer.Suicide(PlayerState.DeathReason.Kill, target); - - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); + killer.SetKillCooldown(); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Scavenger), Translator.GetString("KilledByScavenger"))); + }, dur, "Scavenger Kill"); return false; } + + killer.Suicide(PlayerState.DeathReason.Kill, target); + + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); + + return false; } } \ No newline at end of file diff --git a/Roles/Impostor/SerialKiller.cs b/Roles/Impostor/SerialKiller.cs index 4ce9e5099..04c1feed4 100644 --- a/Roles/Impostor/SerialKiller.cs +++ b/Roles/Impostor/SerialKiller.cs @@ -2,113 +2,112 @@ using System.Collections.Generic; using EHR.Modules; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class SerialKiller : RoleBase { - public class SerialKiller : RoleBase + private const int Id = 1700; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem TimeLimit; + private static OptionItem WaitFor1Kill; + + private float SuicideTimer; + private int Timer; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 1700; - public static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.SerialKiller); - private static OptionItem KillCooldown; - private static OptionItem TimeLimit; - private static OptionItem WaitFor1Kill; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SerialKiller]) + .SetValueFormat(OptionFormat.Seconds); - private float SuicideTimer; - private int Timer; + TimeLimit = new FloatOptionItem(Id + 11, "SerialKillerLimit", new(5f, 180f, 5f), 40f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SerialKiller]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + WaitFor1Kill = new BooleanOptionItem(Id + 12, "WaitFor1Kill", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SerialKiller]); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.SerialKiller); + public override void Init() + { + PlayerIdList = []; + SuicideTimer = TimeLimit.GetFloat(); + Timer = TimeLimit.GetInt(); + } - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SerialKiller]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte serial) + { + PlayerIdList.Add(serial); + Timer = TimeLimit.GetInt(); + SuicideTimer = TimeLimit.GetFloat(); + } - TimeLimit = new FloatOptionItem(Id + 11, "SerialKillerLimit", new(5f, 180f, 5f), 40f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SerialKiller]) - .SetValueFormat(OptionFormat.Seconds); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - WaitFor1Kill = new BooleanOptionItem(Id + 12, "WaitFor1Kill", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SerialKiller]); - } + private static bool HasKilled(PlayerControl pc) + { + return pc != null && pc.Is(CustomRoles.SerialKiller) && pc.IsAlive() && (Main.PlayerStates[pc.PlayerId].GetKillCount(true) > 0 || !WaitFor1Kill.GetBool()); + } - public override void Init() - { - PlayerIdList = []; - SuicideTimer = TimeLimit.GetFloat(); - Timer = TimeLimit.GetInt(); - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!killer.Is(CustomRoles.SerialKiller)) return true; - public override void Add(byte serial) - { - PlayerIdList.Add(serial); - Timer = TimeLimit.GetInt(); - SuicideTimer = TimeLimit.GetFloat(); - } + SuicideTimer = float.NaN; + Timer = TimeLimit.GetInt(); + killer.MarkDirtySettings(); + return true; + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + public override void OnReportDeadBody() + { + SuicideTimer = float.NaN; + Timer = TimeLimit.GetInt(); + } - private static bool HasKilled(PlayerControl pc) - { - return pc != null && pc.Is(CustomRoles.SerialKiller) && pc.IsAlive() && (Main.PlayerStates[pc.PlayerId].GetKillCount(true) > 0 || !WaitFor1Kill.GetBool()); - } + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask) return; - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!killer.Is(CustomRoles.SerialKiller)) return true; - - SuicideTimer = float.NaN; - Timer = TimeLimit.GetInt(); - killer.MarkDirtySettings(); - return true; - } - - public override void OnReportDeadBody() + if (!HasKilled(player)) { SuicideTimer = float.NaN; Timer = TimeLimit.GetInt(); + return; } - public override void OnFixedUpdate(PlayerControl player) + if (float.IsNaN(SuicideTimer)) return; + + if (SuicideTimer >= TimeLimit.GetFloat()) { - if (!GameStates.IsInTask) return; - - if (!HasKilled(player)) - { - SuicideTimer = float.NaN; - Timer = TimeLimit.GetInt(); - return; - } - - if (float.IsNaN(SuicideTimer)) return; - - if (SuicideTimer >= TimeLimit.GetFloat()) - { - player.Suicide(); - SuicideTimer = float.NaN; - Timer = TimeLimit.GetInt(); - - if (player.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - else - { - SuicideTimer += Time.fixedDeltaTime; - int tempTimer = Timer; - Timer = TimeLimit.GetInt() - (int)SuicideTimer; - if (Timer != tempTimer && Timer <= 20 && !player.IsModClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - } - } - - public override void AfterMeetingTasks() - { - SuicideTimer = 0f; + player.Suicide(); + SuicideTimer = float.NaN; Timer = TimeLimit.GetInt(); + + if (player.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + else + { + SuicideTimer += Time.fixedDeltaTime; + int tempTimer = Timer; + Timer = TimeLimit.GetInt() - (int)SuicideTimer; + if (Timer != tempTimer && Timer <= 20 && !player.IsModClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); } } + + public override void AfterMeetingTasks() + { + SuicideTimer = 0f; + Timer = TimeLimit.GetInt(); + } } \ No newline at end of file diff --git a/Roles/Impostor/Silencer.cs b/Roles/Impostor/Silencer.cs index 6689b2a16..53e3f1a48 100644 --- a/Roles/Impostor/Silencer.cs +++ b/Roles/Impostor/Silencer.cs @@ -4,101 +4,100 @@ using EHR.Crewmate; using EHR.Modules; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Silencer : RoleBase { - public class Silencer : RoleBase + private const int Id = 643050; + private static List PlayerIdList = []; + + public static OptionItem SkillCooldown; + public static OptionItem SilenceMode; + + public static List ForSilencer = []; + + private static int LocalPlayerTotalSilences; + + private static readonly string[] SilenceModes = + [ + "EKill", + "Shapeshift" + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 643050; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Silencer); - public static OptionItem SkillCooldown; - public static OptionItem SilenceMode; + SkillCooldown = new FloatOptionItem(Id + 5, "SilencerSkillCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Silencer]) + .SetValueFormat(OptionFormat.Seconds); - public static List ForSilencer = []; + SilenceMode = new StringOptionItem(Id + 4, "SilenceMode", SilenceModes, 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Silencer]); + } - private static int LocalPlayerTotalSilences; + public override void Init() + { + PlayerIdList = []; + ForSilencer = []; - private static readonly string[] SilenceModes = - [ - "EKill", - "Shapeshift" - ]; + LocalPlayerTotalSilences = 0; + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - public override void SetupCustomOption() + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + AURoleOptions.ShapeshifterCooldown = SkillCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (SilenceMode.GetValue() == 1 || ForSilencer.Count >= 1) return true; + + return killer.CheckDoubleTrigger(target, () => { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Silencer); + ForSilencer.Add(target.PlayerId); + killer.SetKillCooldown(3f); - SkillCooldown = new FloatOptionItem(Id + 5, "SilencerSkillCooldown", new(2.5f, 60f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Silencer]) - .SetValueFormat(OptionFormat.Seconds); - - SilenceMode = new StringOptionItem(Id + 4, "SilenceMode", SilenceModes, 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Silencer]); - } - - public override void Init() - { - PlayerIdList = []; - ForSilencer = []; - - LocalPlayerTotalSilences = 0; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - AURoleOptions.ShapeshifterCooldown = SkillCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (SilenceMode.GetValue() == 1 || ForSilencer.Count >= 1) return true; - - return killer.CheckDoubleTrigger(target, () => + if (killer.IsLocalPlayer()) { - ForSilencer.Add(target.PlayerId); - killer.SetKillCooldown(3f); + LocalPlayerTotalSilences++; + if (LocalPlayerTotalSilences >= 5) Achievements.Type.Censorship.Complete(); - if (killer.IsLocalPlayer()) - { - LocalPlayerTotalSilences++; - if (LocalPlayerTotalSilences >= 5) Achievements.Type.Censorship.Complete(); - - if (target.Is(CustomRoles.Snitch) && Snitch.IsExposed.TryGetValue(target.PlayerId, out var exposed) && exposed) - Achievements.Type.YouWontTellAnyone.Complete(); - } - }); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (SilenceMode.GetValue() == 1 && ForSilencer.Count == 0 && shapeshifter.PlayerId != target.PlayerId) - { - ForSilencer.Add(target.PlayerId); - - if (shapeshifter.IsLocalPlayer()) - { - LocalPlayerTotalSilences++; - if (LocalPlayerTotalSilences >= 5) Achievements.Type.Censorship.Complete(); - - if (target.Is(CustomRoles.Snitch) && Snitch.IsExposed.TryGetValue(target.PlayerId, out var exposed) && exposed) - Achievements.Type.YouWontTellAnyone.Complete(); - } + if (target.Is(CustomRoles.Snitch) && Snitch.IsExposed.TryGetValue(target.PlayerId, out var exposed) && exposed) + Achievements.Type.YouWontTellAnyone.Complete(); } + }); + } - return false; - } - - public override void AfterMeetingTasks() + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (SilenceMode.GetValue() == 1 && ForSilencer.Count == 0 && shapeshifter.PlayerId != target.PlayerId) { - ForSilencer.Clear(); + ForSilencer.Add(target.PlayerId); + + if (shapeshifter.IsLocalPlayer()) + { + LocalPlayerTotalSilences++; + if (LocalPlayerTotalSilences >= 5) Achievements.Type.Censorship.Complete(); + + if (target.Is(CustomRoles.Snitch) && Snitch.IsExposed.TryGetValue(target.PlayerId, out var exposed) && exposed) + Achievements.Type.YouWontTellAnyone.Complete(); + } } + + return false; + } + + public override void AfterMeetingTasks() + { + ForSilencer.Clear(); } } \ No newline at end of file diff --git a/Roles/Impostor/Sniper.cs b/Roles/Impostor/Sniper.cs index fcff407c7..032f31b08 100644 --- a/Roles/Impostor/Sniper.cs +++ b/Roles/Impostor/Sniper.cs @@ -8,376 +8,375 @@ using Hazel; using UnityEngine; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Sniper : RoleBase { - public class Sniper : RoleBase + private const int Id = 1900; + private static List PlayerIdList = []; + + private static OptionItem SniperBulletCount; + private static OptionItem SniperPrecisionShooting; + private static OptionItem SniperAimAssist; + private static OptionItem SniperAimAssistOnshot; + private static OptionItem ShapeshiftDuration; + private static OptionItem CanKillWithBullets; + + private static bool MeetingReset; + private static int MaxBulletCount; + private static bool PrecisionShooting; + private static bool AimAssist; + private static bool AimAssistOneshot; + + public static bool On; + private float AimTime; + private int bulletCount; + public bool IsAim; + private Vector3 LastPosition; + private List shotNotify = []; + private Vector3 snipeBasePosition; + + private byte snipeTarget; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 1900; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sniper); - private static OptionItem SniperBulletCount; - private static OptionItem SniperPrecisionShooting; - private static OptionItem SniperAimAssist; - private static OptionItem SniperAimAssistOnshot; - private static OptionItem ShapeshiftDuration; - private static OptionItem CanKillWithBullets; + SniperBulletCount = new IntegerOptionItem(Id + 10, "SniperBulletCount", new(1, 10, 1), 2, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]) + .SetValueFormat(OptionFormat.Pieces); - private static bool MeetingReset; - private static int MaxBulletCount; - private static bool PrecisionShooting; - private static bool AimAssist; - private static bool AimAssistOneshot; + SniperPrecisionShooting = new BooleanOptionItem(Id + 11, "SniperPrecisionShooting", false, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]); - public static bool On; - private float AimTime; - private int bulletCount; - public bool IsAim; - private Vector3 LastPosition; - private List shotNotify = []; - private Vector3 snipeBasePosition; + SniperAimAssist = new BooleanOptionItem(Id + 12, "SniperAimAssist", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]); - private byte snipeTarget; - public override bool IsEnable => On; + SniperAimAssistOnshot = new BooleanOptionItem(Id + 13, "SniperAimAssistOneshot", false, TabGroup.ImpostorRoles) + .SetParent(SniperAimAssist); - public override void SetupCustomOption() + CanKillWithBullets = new BooleanOptionItem(Id + 14, "SniperCanKill", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]); + + ShapeshiftDuration = new FloatOptionItem(Id + 15, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + Logger.Disable("Sniper"); + + PlayerIdList = []; + On = false; + + snipeBasePosition = new(); + LastPosition = new(); + snipeTarget = 0x7F; + bulletCount = SniperBulletCount.GetInt(); + shotNotify = []; + IsAim = false; + AimTime = 0f; + MeetingReset = false; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + On = true; + + MaxBulletCount = SniperBulletCount.GetInt(); + PrecisionShooting = SniperPrecisionShooting.GetBool(); + AimAssist = SniperAimAssist.GetBool(); + AimAssistOneshot = SniperAimAssistOnshot.GetBool(); + + snipeBasePosition = new(); + LastPosition = new(); + snipeTarget = 0x7F; + bulletCount = MaxBulletCount; + shotNotify = []; + IsAim = false; + AimTime = 0f; + MeetingReset = false; + } + + private static bool IsThisRole(byte playerId) + { + return PlayerIdList.Contains(playerId); + } + + private void SendRPC(byte sniperId) + { + if (!On || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SniperSync, SendOption.Reliable); + writer.Write(sniperId); + writer.Write(shotNotify.Count); + foreach (byte sn in shotNotify) writer.Write(sn); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(MessageReader msg) + { + shotNotify.Clear(); + int count = msg.ReadInt32(); + + while (count > 0) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Sniper); - - SniperBulletCount = new IntegerOptionItem(Id + 10, "SniperBulletCount", new(1, 10, 1), 2, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]) - .SetValueFormat(OptionFormat.Pieces); - - SniperPrecisionShooting = new BooleanOptionItem(Id + 11, "SniperPrecisionShooting", false, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]); - - SniperAimAssist = new BooleanOptionItem(Id + 12, "SniperAimAssist", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]); - - SniperAimAssistOnshot = new BooleanOptionItem(Id + 13, "SniperAimAssistOneshot", false, TabGroup.ImpostorRoles) - .SetParent(SniperAimAssist); - - CanKillWithBullets = new BooleanOptionItem(Id + 14, "SniperCanKill", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]); - - ShapeshiftDuration = new FloatOptionItem(Id + 15, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Sniper]) - .SetValueFormat(OptionFormat.Seconds); + shotNotify.Add(msg.ReadByte()); + count--; } + } - public override void Init() + public override bool CanUseKillButton(PlayerControl pc) + { + if (!pc.IsAlive()) return false; + + var canUse = false; + if (pc.IsShifted()) return false; + + if (bulletCount <= 0) canUse = true; + + if (CanKillWithBullets.GetBool()) canUse = true; + + return canUse; + } + + private Dictionary GetSnipeTargets(PlayerControl sniper) + { + var targets = new Dictionary(); + Vector3 snipeBasePos = snipeBasePosition; + Vector3 snipePos = sniper.transform.position; + Vector3 dir = (snipePos - snipeBasePos).normalized; + + snipePos -= dir; + + foreach (PlayerControl target in Main.AllAlivePlayerControls) { - Logger.Disable("Sniper"); + if (target.PlayerId == sniper.PlayerId) continue; - PlayerIdList = []; - On = false; + Vector3 target_pos = target.transform.position - snipePos; + if (target_pos.magnitude < 1) continue; - snipeBasePosition = new(); - LastPosition = new(); - snipeTarget = 0x7F; - bulletCount = SniperBulletCount.GetInt(); - shotNotify = []; - IsAim = false; - AimTime = 0f; - MeetingReset = false; - } + Vector3 target_dir = target_pos.normalized; + float target_dot = Vector3.Dot(dir, target_dir); + if (target_dot < 0.995) continue; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - On = true; - - MaxBulletCount = SniperBulletCount.GetInt(); - PrecisionShooting = SniperPrecisionShooting.GetBool(); - AimAssist = SniperAimAssist.GetBool(); - AimAssistOneshot = SniperAimAssistOnshot.GetBool(); - - snipeBasePosition = new(); - LastPosition = new(); - snipeTarget = 0x7F; - bulletCount = MaxBulletCount; - shotNotify = []; - IsAim = false; - AimTime = 0f; - MeetingReset = false; - } - - private static bool IsThisRole(byte playerId) - { - return PlayerIdList.Contains(playerId); - } - - private void SendRPC(byte sniperId) - { - if (!On || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SniperSync, SendOption.Reliable); - writer.Write(sniperId); - writer.Write(shotNotify.Count); - foreach (byte sn in shotNotify) writer.Write(sn); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(MessageReader msg) - { - shotNotify.Clear(); - int count = msg.ReadInt32(); - - while (count > 0) + if (PrecisionShooting) { - shotNotify.Add(msg.ReadByte()); - count--; + float err = Vector3.Cross(dir, target_pos).magnitude; + if (err < 0.5) targets.Add(target, err); + } + else + { + float err = target_pos.magnitude; + targets.Add(target, err); } } - public override bool CanUseKillButton(PlayerControl pc) + return targets; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + return Options.UseUnshiftTrigger.GetBool() ? Snipe(shapeshifter, !IsAim) : Snipe(shapeshifter, shapeshifting); + } + + public override void OnPet(PlayerControl pc) + { + Snipe(pc, !IsAim, true); + } + + public override bool OnVanish(PlayerControl pc) + { + Snipe(pc, !IsAim, true); + return false; + } + + private bool Snipe(PlayerControl sniper, bool shapeshifting, bool isPet = false) + { + if (!IsThisRole(sniper.PlayerId) || !sniper.IsAlive()) return true; + + byte sniperId = sniper.PlayerId; + + if (bulletCount <= 0) { - if (!pc.IsAlive()) return false; + float CD = ShapeshiftDuration.GetFloat() + 1f; + if (Main.KillTimers[sniper.PlayerId] < CD && !isPet) sniper.SetKillCooldown(CD); - var canUse = false; - if (pc.IsShifted()) return false; - - if (bulletCount <= 0) canUse = true; - - if (CanKillWithBullets.GetBool()) canUse = true; - - return canUse; - } - - private Dictionary GetSnipeTargets(PlayerControl sniper) - { - var targets = new Dictionary(); - Vector3 snipeBasePos = snipeBasePosition; - Vector3 snipePos = sniper.transform.position; - Vector3 dir = (snipePos - snipeBasePos).normalized; - - snipePos -= dir; - - foreach (PlayerControl target in Main.AllAlivePlayerControls) - { - if (target.PlayerId == sniper.PlayerId) continue; - - Vector3 target_pos = target.transform.position - snipePos; - if (target_pos.magnitude < 1) continue; - - Vector3 target_dir = target_pos.normalized; - float target_dot = Vector3.Dot(dir, target_dir); - if (target_dot < 0.995) continue; - - if (PrecisionShooting) - { - float err = Vector3.Cross(dir, target_pos).magnitude; - if (err < 0.5) targets.Add(target, err); - } - else - { - float err = target_pos.magnitude; - targets.Add(target, err); - } - } - - return targets; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - return Options.UseUnshiftTrigger.GetBool() ? Snipe(shapeshifter, !IsAim) : Snipe(shapeshifter, shapeshifting); - } - - public override void OnPet(PlayerControl pc) - { - Snipe(pc, !IsAim, true); - } - - public override bool OnVanish(PlayerControl pc) - { - Snipe(pc, !IsAim, true); return false; } - private bool Snipe(PlayerControl sniper, bool shapeshifting, bool isPet = false) + if (shapeshifting) { - if (!IsThisRole(sniper.PlayerId) || !sniper.IsAlive()) return true; + MeetingReset = false; - byte sniperId = sniper.PlayerId; + snipeBasePosition = sniper.transform.position; - if (bulletCount <= 0) - { - float CD = ShapeshiftDuration.GetFloat() + 1f; - if (Main.KillTimers[sniper.PlayerId] < CD && !isPet) sniper.SetKillCooldown(CD); - - return false; - } - - if (shapeshifting) - { - MeetingReset = false; - - snipeBasePosition = sniper.transform.position; - - LastPosition = sniper.transform.position; - IsAim = true; - AimTime = 0f; - - return false; - } - - IsAim = false; + LastPosition = sniper.transform.position; + IsAim = true; AimTime = 0f; - if (MeetingReset) + return false; + } + + IsAim = false; + AimTime = 0f; + + if (MeetingReset) + { + MeetingReset = false; + return false; + } + + bulletCount--; + + if (!AmongUsClient.Instance.AmHost || Pelican.IsEaten(sniperId) || Medic.ProtectList.Contains(sniperId)) return false; + + sniper.RPCPlayCustomSound("AWP"); + + Dictionary targets = GetSnipeTargets(sniper); + + if (targets.Count > 0) + { + PlayerControl snipedTarget = targets.MinBy(c => c.Value).Key; + snipeTarget = snipedTarget.PlayerId; + snipedTarget.CheckMurder(snipedTarget); + sniper.SetKillCooldown(); + snipeTarget = 0x7F; + + targets.Remove(snipedTarget); + List snList = shotNotify; + snList.Clear(); + + foreach (PlayerControl otherPc in targets.Keys) { - MeetingReset = false; - return false; + snList.Add(otherPc.PlayerId); + Utils.NotifyRoles(SpecifySeer: otherPc); } - bulletCount--; + SendRPC(sniperId); - if (!AmongUsClient.Instance.AmHost || Pelican.IsEaten(sniperId) || Medic.ProtectList.Contains(sniperId)) return false; - - sniper.RPCPlayCustomSound("AWP"); - - Dictionary targets = GetSnipeTargets(sniper); - - if (targets.Count > 0) + LateTask.New(() => { - PlayerControl snipedTarget = targets.MinBy(c => c.Value).Key; - snipeTarget = snipedTarget.PlayerId; - snipedTarget.CheckMurder(snipedTarget); - sniper.SetKillCooldown(); - snipeTarget = 0x7F; - - targets.Remove(snipedTarget); - List snList = shotNotify; snList.Clear(); - - foreach (PlayerControl otherPc in targets.Keys) - { - snList.Add(otherPc.PlayerId); - Utils.NotifyRoles(SpecifySeer: otherPc); - } + foreach (PlayerControl otherPc in targets.Keys) Utils.NotifyRoles(SpecifySeer: otherPc); SendRPC(sniperId); + }, 0.5f, "Sniper shot Notify"); + } - LateTask.New(() => + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = bulletCount > 0 ? Options.DefaultKillCooldown : 255f; + else + { + if (Options.UsePets.GetBool()) return; + + try + { + if (bulletCount > 0) + AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat(); + else { - snList.Clear(); - foreach (PlayerControl otherPc in targets.Keys) Utils.NotifyRoles(SpecifySeer: otherPc); - - SendRPC(sniperId); - }, 0.5f, "Sniper shot Notify"); - } - - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = bulletCount > 0 ? Options.DefaultKillCooldown : 255f; - else - { - if (Options.UsePets.GetBool()) return; - - try - { - if (bulletCount > 0) - AURoleOptions.ShapeshifterDuration = ShapeshiftDuration.GetFloat(); - else - { - AURoleOptions.ShapeshifterDuration = 1f; - AURoleOptions.ShapeshifterCooldown = 255f; - } - } - catch { } - } - } - - public override void OnFixedUpdate(PlayerControl sniper) - { - if (!IsThisRole(sniper.PlayerId) || !sniper.IsAlive()) return; - - if (!AimAssist) return; - - if (!IsAim) return; - - if (!GameStates.IsInTask) - { - IsAim = false; - AimTime = 0f; - return; - } - - Vector3 pos = sniper.transform.position; - - if (pos != LastPosition) - { - AimTime = 0f; - LastPosition = pos; - } - else - { - AimTime += Time.fixedDeltaTime; - Utils.NotifyRoles(SpecifySeer: sniper, SpecifyTarget: sniper); - } - } - - public override void OnReportDeadBody() - { - MeetingReset = true; - } - - public static bool TryGetSniper(byte targetId, ref PlayerControl sniper) - { - foreach (KeyValuePair state in Main.PlayerStates) - { - if (state.Value.Role is Sniper { IsEnable: true } sp) - { - if (sp.snipeTarget == targetId) - { - sniper = Utils.GetPlayerById(state.Key); - return true; - } + AURoleOptions.ShapeshifterDuration = 1f; + AURoleOptions.ShapeshifterCooldown = 255f; } } + catch { } + } + } - return false; + public override void OnFixedUpdate(PlayerControl sniper) + { + if (!IsThisRole(sniper.PlayerId) || !sniper.IsAlive()) return; + + if (!AimAssist) return; + + if (!IsAim) return; + + if (!GameStates.IsInTask) + { + IsAim = false; + AimTime = 0f; + return; } - public static string GetShotNotify(byte seerId) + Vector3 pos = sniper.transform.position; + + if (pos != LastPosition) { - if (AimAssist && IsThisRole(seerId)) - { - if (Main.PlayerStates[seerId].Role is not Sniper sp) return string.Empty; + AimTime = 0f; + LastPosition = pos; + } + else + { + AimTime += Time.fixedDeltaTime; + Utils.NotifyRoles(SpecifySeer: sniper, SpecifyTarget: sniper); + } + } - if (0.5f < sp.AimTime && (!AimAssistOneshot || sp.AimTime < 1.0f)) - if (sp.GetSnipeTargets(Utils.GetPlayerById(seerId)).Count > 0) - return $"{Utils.ColorString(Palette.ImpostorRed, "◎")}"; - } - else + public override void OnReportDeadBody() + { + MeetingReset = true; + } + + public static bool TryGetSniper(byte targetId, ref PlayerControl sniper) + { + foreach (KeyValuePair state in Main.PlayerStates) + { + if (state.Value.Role is Sniper { IsEnable: true } sp) { - foreach (byte sniperId in PlayerIdList) + if (sp.snipeTarget == targetId) { - if (Main.PlayerStates[sniperId].Role is not Sniper sp) continue; - - List snList = sp.shotNotify; - if (snList.Count > 0 && snList.Contains(seerId)) return $"{Utils.ColorString(Palette.ImpostorRed, "!")}"; + sniper = Utils.GetPlayerById(state.Key); + return true; } } - - return string.Empty; } - public override void SetButtonTexts(HudManager hud, byte id) - { - if (Options.UsePets.GetBool()) - hud.PetButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText")); - else - { - if (IsThisRole(id)) hud.AbilityButton.SetUsesRemaining(bulletCount); + return false; + } - hud.AbilityButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText")); + public static string GetShotNotify(byte seerId) + { + if (AimAssist && IsThisRole(seerId)) + { + if (Main.PlayerStates[seerId].Role is not Sniper sp) return string.Empty; + + if (0.5f < sp.AimTime && (!AimAssistOneshot || sp.AimTime < 1.0f)) + if (sp.GetSnipeTargets(Utils.GetPlayerById(seerId)).Count > 0) + return $"{Utils.ColorString(Palette.ImpostorRed, "◎")}"; + } + else + { + foreach (byte sniperId in PlayerIdList) + { + if (Main.PlayerStates[sniperId].Role is not Sniper sp) continue; + + List snList = sp.shotNotify; + if (snList.Count > 0 && snList.Contains(seerId)) return $"{Utils.ColorString(Palette.ImpostorRed, "!")}"; } } + + return string.Empty; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (Options.UsePets.GetBool()) + hud.PetButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText")); + else + { + if (IsThisRole(id)) hud.AbilityButton.SetUsesRemaining(bulletCount); + + hud.AbilityButton.OverrideText(GetString(bulletCount <= 0 ? "DefaultShapeshiftText" : "SniperSnipeButtonText")); + } } } \ No newline at end of file diff --git a/Roles/Impostor/Stealth.cs b/Roles/Impostor/Stealth.cs index 96f8f3132..c1f586fb2 100644 --- a/Roles/Impostor/Stealth.cs +++ b/Roles/Impostor/Stealth.cs @@ -4,138 +4,137 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Impostor +namespace EHR.Impostor; + +public sealed class Stealth : RoleBase { - public sealed class Stealth : RoleBase + private static readonly LogHandler Logger = EHR.Logger.Handler(nameof(Stealth)); + + private static OptionItem OptionExcludeImpostors; + private static OptionItem OptionDarkenDuration; + + public static bool On; + private float darkenDuration; + private PlayerControl[] darkenedPlayers; + private SystemTypes? darkenedRoom; + private float darkenTimer; + + private bool excludeImpostors; + private PlayerControl StealthPC; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private static readonly LogHandler Logger = EHR.Logger.Handler(nameof(Stealth)); + StartSetup(641900) + .AutoSetupOption(ref OptionExcludeImpostors, true) + .AutoSetupOption(ref OptionDarkenDuration, 5f, new FloatValueRule(0.5f, 30f, 0.5f), OptionFormat.Seconds); + } - private static OptionItem OptionExcludeImpostors; - private static OptionItem OptionDarkenDuration; + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return true; - public static bool On; - private float darkenDuration; - private PlayerControl[] darkenedPlayers; - private SystemTypes? darkenedRoom; - private float darkenTimer; + IEnumerable playersToDarken = FindPlayersInSameRoom(target); - private bool excludeImpostors; - private PlayerControl StealthPC; - public override bool IsEnable => On; - - public override void SetupCustomOption() + if (playersToDarken == null) { - StartSetup(641900) - .AutoSetupOption(ref OptionExcludeImpostors, true) - .AutoSetupOption(ref OptionDarkenDuration, 5f, new FloatValueRule(0.5f, 30f, 0.5f), OptionFormat.Seconds); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return true; - - IEnumerable playersToDarken = FindPlayersInSameRoom(target); - - if (playersToDarken == null) - { - Logger.Info("No players to darken"); - return true; - } - - if (excludeImpostors) playersToDarken = playersToDarken.Where(player => !player.Is(CustomRoles.Impostor)); - - DarkenPlayers(playersToDarken); + Logger.Info("No players to darken"); return true; } - private IEnumerable FindPlayersInSameRoom(PlayerControl killedPlayer) + if (excludeImpostors) playersToDarken = playersToDarken.Where(player => !player.Is(CustomRoles.Impostor)); + + DarkenPlayers(playersToDarken); + return true; + } + + private IEnumerable FindPlayersInSameRoom(PlayerControl killedPlayer) + { + PlainShipRoom room = killedPlayer.GetPlainShipRoom(); + if (room == null) return null; + + Collider2D roomArea = room.roomArea; + SystemTypes roomName = room.RoomId; + RpcDarken(roomName); + return Main.AllAlivePlayerControls.Where(player => player != StealthPC && player.Collider.IsTouching(roomArea)); + } + + private void DarkenPlayers(IEnumerable playersToDarken) + { + darkenedPlayers = playersToDarken.ToArray(); + + foreach (PlayerControl player in darkenedPlayers) { - PlainShipRoom room = killedPlayer.GetPlainShipRoom(); - if (room == null) return null; - - Collider2D roomArea = room.roomArea; - SystemTypes roomName = room.RoomId; - RpcDarken(roomName); - return Main.AllAlivePlayerControls.Where(player => player != StealthPC && player.Collider.IsTouching(roomArea)); - } - - private void DarkenPlayers(IEnumerable playersToDarken) - { - darkenedPlayers = playersToDarken.ToArray(); - - foreach (PlayerControl player in darkenedPlayers) - { - Main.PlayerStates[player.PlayerId].IsBlackOut = true; - player.MarkDirtySettings(); - } - } - - public override void Init() - { - On = false; - excludeImpostors = OptionExcludeImpostors.GetBool(); - darkenDuration = OptionDarkenDuration.GetFloat(); - } - - public override void Add(byte playerId) - { - On = true; - StealthPC = playerId.GetPlayer(); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (darkenedPlayers != null) - { - darkenTimer -= Time.fixedDeltaTime; - if (darkenTimer <= 0) ResetDarkenState(); - } - } - - public override void OnReportDeadBody() - { - if (AmongUsClient.Instance.AmHost) ResetDarkenState(); - } - - private void RpcDarken(SystemTypes? roomType) - { - Logger.Info($"Darkened room set to {roomType?.ToString() ?? "null"}"); - darkenedRoom = roomType; - Utils.SendRPC(CustomRPC.SyncRoleData, StealthPC.PlayerId, (byte?)roomType ?? byte.MaxValue); - } - - public void ReceiveRPC(MessageReader reader) - { - byte roomId = reader.ReadByte(); - darkenedRoom = roomId == byte.MaxValue ? null : (SystemTypes)roomId; - } - - private void ResetDarkenState() - { - if (darkenedPlayers != null) - { - foreach (PlayerControl player in darkenedPlayers) - { - Main.PlayerStates[player.PlayerId].IsBlackOut = false; - player.MarkDirtySettings(); - } - - darkenedPlayers = null; - } - - darkenTimer = darkenDuration; - RpcDarken(null); - Utils.NotifyRoles(SpecifySeer: StealthPC); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl seen, bool isForMeeting = false, bool isForHud = false) - { - seen ??= seer; - if (isForMeeting || seer != StealthPC || seen != StealthPC || !darkenedRoom.HasValue) return base.GetSuffix(seer, seen, isForMeeting, isForHud); - - return string.Format(Translator.GetString("StealthDarkened"), DestroyableSingleton.Instance.GetString(darkenedRoom.Value)); + Main.PlayerStates[player.PlayerId].IsBlackOut = true; + player.MarkDirtySettings(); } } + + public override void Init() + { + On = false; + excludeImpostors = OptionExcludeImpostors.GetBool(); + darkenDuration = OptionDarkenDuration.GetFloat(); + } + + public override void Add(byte playerId) + { + On = true; + StealthPC = playerId.GetPlayer(); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!AmongUsClient.Instance.AmHost) return; + + if (darkenedPlayers != null) + { + darkenTimer -= Time.fixedDeltaTime; + if (darkenTimer <= 0) ResetDarkenState(); + } + } + + public override void OnReportDeadBody() + { + if (AmongUsClient.Instance.AmHost) ResetDarkenState(); + } + + private void RpcDarken(SystemTypes? roomType) + { + Logger.Info($"Darkened room set to {roomType?.ToString() ?? "null"}"); + darkenedRoom = roomType; + Utils.SendRPC(CustomRPC.SyncRoleData, StealthPC.PlayerId, (byte?)roomType ?? byte.MaxValue); + } + + public void ReceiveRPC(MessageReader reader) + { + byte roomId = reader.ReadByte(); + darkenedRoom = roomId == byte.MaxValue ? null : (SystemTypes)roomId; + } + + private void ResetDarkenState() + { + if (darkenedPlayers != null) + { + foreach (PlayerControl player in darkenedPlayers) + { + Main.PlayerStates[player.PlayerId].IsBlackOut = false; + player.MarkDirtySettings(); + } + + darkenedPlayers = null; + } + + darkenTimer = darkenDuration; + RpcDarken(null); + Utils.NotifyRoles(SpecifySeer: StealthPC); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl seen, bool isForMeeting = false, bool isForHud = false) + { + seen ??= seer; + if (isForMeeting || seer != StealthPC || seen != StealthPC || !darkenedRoom.HasValue) return base.GetSuffix(seer, seen, isForMeeting, isForHud); + + return string.Format(Translator.GetString("StealthDarkened"), DestroyableSingleton.Instance.GetString(darkenedRoom.Value)); + } } \ No newline at end of file diff --git a/Roles/Impostor/Swapster.cs b/Roles/Impostor/Swapster.cs index f00da5f1c..cbe7c3f3b 100644 --- a/Roles/Impostor/Swapster.cs +++ b/Roles/Impostor/Swapster.cs @@ -1,58 +1,57 @@ using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Swapster : RoleBase { - internal class Swapster : RoleBase + public static OptionItem SSCD; + public static readonly Dictionary FirstSwapTarget = []; + public static bool On; + private static int Id => 643320; + public override bool IsEnable => On; + + public override void Init() { - public static OptionItem SSCD; - public static readonly Dictionary FirstSwapTarget = []; - public static bool On; - private static int Id => 643320; - public override bool IsEnable => On; + FirstSwapTarget.Clear(); + On = false; + } - public override void Init() + public override void Add(byte playerId) + { + On = true; + } + + public override void SetupCustomOption() + { + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Swapster); + + SSCD = new FloatOptionItem(Id + 2, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swapster]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } + + public override bool OnShapeshift(PlayerControl swapster, PlayerControl target, bool shapeshifting) + { + if (swapster == null || target == null || swapster == target || !shapeshifting) return true; + + if (FirstSwapTarget.TryGetValue(swapster.PlayerId, out byte firstTargetId)) { - FirstSwapTarget.Clear(); - On = false; + PlayerControl firstTarget = Utils.GetPlayerById(firstTargetId); + Vector2 pos = firstTarget.Pos(); + firstTarget.TP(target); + target.TP(pos); + FirstSwapTarget.Remove(swapster.PlayerId); } + else + FirstSwapTarget[swapster.PlayerId] = target.PlayerId; - public override void Add(byte playerId) - { - On = true; - } - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Swapster); - - SSCD = new FloatOptionItem(Id + 2, "ShapeshiftCooldown", new(0f, 180f, 2.5f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swapster]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = SSCD.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - - public override bool OnShapeshift(PlayerControl swapster, PlayerControl target, bool shapeshifting) - { - if (swapster == null || target == null || swapster == target || !shapeshifting) return true; - - if (FirstSwapTarget.TryGetValue(swapster.PlayerId, out byte firstTargetId)) - { - PlayerControl firstTarget = Utils.GetPlayerById(firstTargetId); - Vector2 pos = firstTarget.Pos(); - firstTarget.TP(target); - target.TP(pos); - FirstSwapTarget.Remove(swapster.PlayerId); - } - else - FirstSwapTarget[swapster.PlayerId] = target.PlayerId; - - return false; - } + return false; } } \ No newline at end of file diff --git a/Roles/Impostor/Swiftclaw.cs b/Roles/Impostor/Swiftclaw.cs index b8a24291a..6b24cfc17 100644 --- a/Roles/Impostor/Swiftclaw.cs +++ b/Roles/Impostor/Swiftclaw.cs @@ -1,102 +1,101 @@ using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Swiftclaw : RoleBase { - internal class Swiftclaw : RoleBase + public static OptionItem DashCD; + public static OptionItem DashDuration; + public static OptionItem DashSpeed; + private static readonly Dictionary DashStart = []; + + public static bool On; + private static int Id => 643340; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static OptionItem DashCD; - public static OptionItem DashDuration; - public static OptionItem DashSpeed; - private static readonly Dictionary DashStart = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Swiftclaw); - public static bool On; - private static int Id => 643340; - public override bool IsEnable => On; + DashCD = new FloatOptionItem(Id + 2, "SwiftclawDashCD", new(0f, 180f, 2.5f), 15f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swiftclaw]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Swiftclaw); + DashDuration = new IntegerOptionItem(Id + 3, "SwiftclawDashDur", new(0, 60, 1), 4, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swiftclaw]) + .SetValueFormat(OptionFormat.Seconds); - DashCD = new FloatOptionItem(Id + 2, "SwiftclawDashCD", new(0f, 180f, 2.5f), 15f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swiftclaw]) - .SetValueFormat(OptionFormat.Seconds); + DashSpeed = new FloatOptionItem(Id + 4, "SwiftclawDashSpeed", new(0.05f, 3f, 0.05f), 2f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swiftclaw]) + .SetValueFormat(OptionFormat.Multiplier); + } - DashDuration = new IntegerOptionItem(Id + 3, "SwiftclawDashDur", new(0, 60, 1), 4, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swiftclaw]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + DashStart.Clear(); + On = false; + } - DashSpeed = new FloatOptionItem(Id + 4, "SwiftclawDashSpeed", new(0.05f, 3f, 0.05f), 2f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Swiftclaw]) - .SetValueFormat(OptionFormat.Multiplier); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Init() - { - DashStart.Clear(); - On = false; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePhantomBasis.GetBool()) AURoleOptions.PhantomCooldown = DashCD.GetFloat() + DashDuration.GetFloat(); - public override void Add(byte playerId) - { - On = true; - } + if (Options.UseUnshiftTrigger.GetBool()) AURoleOptions.ShapeshifterCooldown = DashCD.GetFloat() + DashDuration.GetFloat(); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePhantomBasis.GetBool()) AURoleOptions.PhantomCooldown = DashCD.GetFloat() + DashDuration.GetFloat(); + public override void OnPet(PlayerControl pc) + { + Dash(pc); + } - if (Options.UseUnshiftTrigger.GetBool()) AURoleOptions.ShapeshifterCooldown = DashCD.GetFloat() + DashDuration.GetFloat(); - } + public override bool OnVanish(PlayerControl pc) + { + Dash(pc); + return false; + } - public override void OnPet(PlayerControl pc) - { - Dash(pc); - } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - public override bool OnVanish(PlayerControl pc) - { - Dash(pc); - return false; - } + Dash(shapeshifter); + return false; + } - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + private static void Dash(PlayerControl pc) + { + if (pc == null || DashStart.ContainsKey(pc.PlayerId)) return; - Dash(shapeshifter); - return false; - } + DashStart[pc.PlayerId] = (Utils.TimeStamp, Main.AllPlayerSpeed[pc.PlayerId]); + Main.AllPlayerSpeed[pc.PlayerId] = DashSpeed.GetFloat(); + pc.MarkDirtySettings(); + } - private static void Dash(PlayerControl pc) - { - if (pc == null || DashStart.ContainsKey(pc.PlayerId)) return; + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || pc == null || !DashStart.TryGetValue(pc.PlayerId, out (long StartTimeStamp, float NormalSpeed) dashInfo) || dashInfo.StartTimeStamp + DashDuration.GetInt() > Utils.TimeStamp) return; - DashStart[pc.PlayerId] = (Utils.TimeStamp, Main.AllPlayerSpeed[pc.PlayerId]); - Main.AllPlayerSpeed[pc.PlayerId] = DashSpeed.GetFloat(); - pc.MarkDirtySettings(); - } + Main.AllPlayerSpeed[pc.PlayerId] = dashInfo.NormalSpeed; + pc.MarkDirtySettings(); + DashStart.Remove(pc.PlayerId); + } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || pc == null || !DashStart.TryGetValue(pc.PlayerId, out (long StartTimeStamp, float NormalSpeed) dashInfo) || dashInfo.StartTimeStamp + DashDuration.GetInt() > Utils.TimeStamp) return; + public override void OnReportDeadBody() + { + foreach (KeyValuePair item in DashStart) Main.AllPlayerSpeed[item.Key] = item.Value.NormalSpeed; - Main.AllPlayerSpeed[pc.PlayerId] = dashInfo.NormalSpeed; - pc.MarkDirtySettings(); - DashStart.Remove(pc.PlayerId); - } + DashStart.Clear(); + } - public override void OnReportDeadBody() - { - foreach (KeyValuePair item in DashStart) Main.AllPlayerSpeed[item.Key] = item.Value.NormalSpeed; - - DashStart.Clear(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - ActionButton button = Options.UseUnshiftTrigger.GetBool() ? hud.AbilityButton : hud.PetButton; - button?.OverrideText(Translator.GetString("SwiftclawKillButtonText")); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + ActionButton button = Options.UseUnshiftTrigger.GetBool() ? hud.AbilityButton : hud.PetButton; + button?.OverrideText(Translator.GetString("SwiftclawKillButtonText")); } } \ No newline at end of file diff --git a/Roles/Impostor/Swooper.cs b/Roles/Impostor/Swooper.cs index b84abb103..96c4aab45 100644 --- a/Roles/Impostor/Swooper.cs +++ b/Roles/Impostor/Swooper.cs @@ -8,293 +8,292 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Swooper : RoleBase { - public class Swooper : RoleBase + private const int Id = 4200; + private static List PlayerIdList = []; + + private static OptionItem SwooperCooldown; + private static OptionItem SwooperDuration; + private static OptionItem SwooperVentNormallyOnCooldown; + private static OptionItem SwooperLimitOpt; + public static OptionItem SwooperAbilityUseGainWithEachKill; + private int CD; + + private float Cooldown; + + private int Count; + private float Duration; + + private long InvisTime; + + private long lastFixedTime; + private long lastTime; + private byte SwooperId; + + private CustomRoles UsedRole; + private int ventedId; + private bool VentNormallyOnCooldown; + + public override bool IsEnable => PlayerIdList.Count > 0; + + private bool CanGoInvis => GameStates.IsInTask && InvisTime == -10 && lastTime == -10; + private bool IsInvis => InvisTime != -10; + + public override void SetupCustomOption() { - private const int Id = 4200; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Swooper); - private static OptionItem SwooperCooldown; - private static OptionItem SwooperDuration; - private static OptionItem SwooperVentNormallyOnCooldown; - private static OptionItem SwooperLimitOpt; - public static OptionItem SwooperAbilityUseGainWithEachKill; - private int CD; + SwooperCooldown = new FloatOptionItem(Id + 2, "SwooperCooldown", new(1f, 60f, 1f), 20f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) + .SetValueFormat(OptionFormat.Seconds); - private float Cooldown; + SwooperDuration = new FloatOptionItem(Id + 3, "SwooperDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; - private float Duration; + SwooperVentNormallyOnCooldown = new BooleanOptionItem(Id + 4, "SwooperVentNormallyOnCooldown", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]); - private long InvisTime; + SwooperLimitOpt = new IntegerOptionItem(Id + 5, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) + .SetValueFormat(OptionFormat.Times); - private long lastFixedTime; - private long lastTime; - private byte SwooperId; + SwooperAbilityUseGainWithEachKill = new FloatOptionItem(Id + 6, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) + .SetValueFormat(OptionFormat.Times); + } - private CustomRoles UsedRole; - private int ventedId; - private bool VentNormallyOnCooldown; + public override void Init() + { + PlayerIdList = []; + InvisTime = -10; + lastTime = -10; + ventedId = -10; + CD = 0; + SwooperId = byte.MaxValue; + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + SwooperId = playerId; - private bool CanGoInvis => GameStates.IsInTask && InvisTime == -10 && lastTime == -10; - private bool IsInvis => InvisTime != -10; + InvisTime = -10; + lastTime = -10; + ventedId = -10; + CD = 0; - public override void SetupCustomOption() + UsedRole = Main.PlayerStates[playerId].MainRole; + + switch (UsedRole) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Swooper); - - SwooperCooldown = new FloatOptionItem(Id + 2, "SwooperCooldown", new(1f, 60f, 1f), 20f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) - .SetValueFormat(OptionFormat.Seconds); - - SwooperDuration = new FloatOptionItem(Id + 3, "SwooperDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) - .SetValueFormat(OptionFormat.Seconds); - - SwooperVentNormallyOnCooldown = new BooleanOptionItem(Id + 4, "SwooperVentNormallyOnCooldown", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]); - - SwooperLimitOpt = new IntegerOptionItem(Id + 5, "AbilityUseLimit", new(0, 5, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) - .SetValueFormat(OptionFormat.Times); - - SwooperAbilityUseGainWithEachKill = new FloatOptionItem(Id + 6, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Swooper]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - PlayerIdList = []; - InvisTime = -10; - lastTime = -10; - ventedId = -10; - CD = 0; - SwooperId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - SwooperId = playerId; - - InvisTime = -10; - lastTime = -10; - ventedId = -10; - CD = 0; - - UsedRole = Main.PlayerStates[playerId].MainRole; - - switch (UsedRole) - { - case CustomRoles.Swooper: - playerId.SetAbilityUseLimit(SwooperLimitOpt.GetInt()); - Cooldown = SwooperCooldown.GetFloat(); - Duration = SwooperDuration.GetFloat(); - VentNormallyOnCooldown = SwooperVentNormallyOnCooldown.GetBool(); - break; - case CustomRoles.Chameleon: - playerId.SetAbilityUseLimit(Chameleon.UseLimitOpt.GetInt()); - Cooldown = Chameleon.ChameleonCooldown.GetFloat(); - Duration = Chameleon.ChameleonDuration.GetFloat(); - VentNormallyOnCooldown = true; - break; - case CustomRoles.Wraith: - Cooldown = Wraith.WraithCooldown.GetFloat(); - Duration = Wraith.WraithDuration.GetFloat(); - VentNormallyOnCooldown = Wraith.WraithVentNormallyOnCooldown.GetBool(); - break; - } - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - try - { - if (UsedRole == CustomRoles.Chameleon) - { - AURoleOptions.EngineerCooldown = Cooldown + 1f; - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.Data.RoleType != RoleTypes.Engineer; - } - - private void SendRPC() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetSwooperTimer, SendOption.Reliable); - writer.Write(SwooperId); - writer.Write(InvisTime.ToString()); - writer.Write(lastTime.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(MessageReader reader) - { - InvisTime = long.Parse(reader.ReadString()); - lastTime = long.Parse(reader.ReadString()); - } - - public override void AfterMeetingTasks() - { - InvisTime = -10; - lastTime = Utils.TimeStamp; - SendRPC(); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask || !IsEnable || Main.HasJustStarted || ExileController.Instance || player == null) return; - - if (Count++ < 30) return; - - Count = 0; - - long now = Utils.TimeStamp; - - if (lastTime != -10) - { - if (!player.IsModClient() && UsedRole != CustomRoles.Chameleon) - { - long cooldown = lastTime + (long)Cooldown - now; - if ((int)cooldown != CD) player.Notify(string.Format(GetString("CDPT"), cooldown + 1), 1.1f, overrideAll: true); - - CD = (int)cooldown; - } - - if (lastTime + (long)Cooldown < now) - { - lastTime = -10; - if (!player.IsModClient()) player.Notify(GetString("SwooperCanVent"), 10f); - - SendRPC(); - CD = 0; - } - } - - if (lastFixedTime != now && IsInvis) - { - lastFixedTime = now; - var refresh = false; - long remainTime = InvisTime + (long)Duration - now; - - switch (remainTime) - { - case < 0: - lastTime = now; - Vector2 pos = player.Pos(); - player.MyPhysics?.RpcBootFromVent(ventedId == -10 ? Main.LastEnteredVent[player.PlayerId].Id : ventedId); - player.Notify(GetString("SwooperInvisStateOut")); - InvisTime = -10; - SendRPC(); - refresh = true; - LateTask.New(() => player.TP(pos), 0.5f, log: false); - break; - case <= 10 when !player.IsModClient(): - player.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1), overrideAll: true); - break; - } - - if (refresh) SendRPC(); - } - } - - public override void OnCoEnterVent(PlayerPhysics __instance, int ventId) - { - if (!AmongUsClient.Instance.AmHost || IsInvis) return; - - PlayerControl pc = __instance.myPlayer; - - LateTask.New(() => - { - float limit = pc.GetAbilityUseLimit(); - bool wraith = UsedRole == CustomRoles.Wraith; - - if (CanGoInvis && (wraith || limit >= 1)) - { - ventedId = ventId; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, 34, SendOption.Reliable, pc.GetClientId()); - writer.WritePacked(ventId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - - InvisTime = Utils.TimeStamp; - if (!wraith) pc.RpcRemoveAbilityUse(); - - SendRPC(); - pc.Notify(GetString("SwooperInvisState"), Duration); - } - else if (!VentNormallyOnCooldown) - { - __instance.RpcBootFromVent(ventId); - pc.Notify(GetString("SwooperInvisInCooldown")); - } - }, 0.5f, "Swooper Vent"); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (!IsInvis || InvisTime == Utils.TimeStamp) return; - - InvisTime = -10; - lastTime = Utils.TimeStamp; - SendRPC(); - - pc?.MyPhysics?.RpcBootFromVent(vent.Id); - pc?.Notify(GetString("SwooperInvisStateOut")); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || seer == null || seer.PlayerId != SwooperId || !GameStates.IsInTask || ExileController.Instance || !seer.IsAlive()) return string.Empty; - - var str = new StringBuilder(); - - if (IsInvis) - { - long remainTime = InvisTime + (long)Duration - Utils.TimeStamp; - str.Append(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1)); - } - else if (lastTime != -10) - { - long cooldown = lastTime + (long)Cooldown - Utils.TimeStamp; - str.Append(string.Format(GetString("SwooperInvisCooldownRemain"), cooldown + 1)); - } - else - str.Append(GetString("SwooperCanVent")); - - return str.ToString(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Medic.ProtectList.Contains(target.PlayerId)) return false; - - if (target.Is(CustomRoles.Bait)) return true; - - if (!IsInvis) return true; - - killer.SetKillCooldown(); - target.SetRealKiller(killer); - target.RpcCheckAndMurder(target); - return false; - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return UsedRole != CustomRoles.Chameleon || pc.GetClosestVent()?.Id == ventId; + case CustomRoles.Swooper: + playerId.SetAbilityUseLimit(SwooperLimitOpt.GetInt()); + Cooldown = SwooperCooldown.GetFloat(); + Duration = SwooperDuration.GetFloat(); + VentNormallyOnCooldown = SwooperVentNormallyOnCooldown.GetBool(); + break; + case CustomRoles.Chameleon: + playerId.SetAbilityUseLimit(Chameleon.UseLimitOpt.GetInt()); + Cooldown = Chameleon.ChameleonCooldown.GetFloat(); + Duration = Chameleon.ChameleonDuration.GetFloat(); + VentNormallyOnCooldown = true; + break; + case CustomRoles.Wraith: + Cooldown = Wraith.WraithCooldown.GetFloat(); + Duration = Wraith.WraithDuration.GetFloat(); + VentNormallyOnCooldown = Wraith.WraithVentNormallyOnCooldown.GetBool(); + break; } } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + try + { + if (UsedRole == CustomRoles.Chameleon) + { + AURoleOptions.EngineerCooldown = Cooldown + 1f; + } + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.Data.RoleType != RoleTypes.Engineer; + } + + private void SendRPC() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetSwooperTimer, SendOption.Reliable); + writer.Write(SwooperId); + writer.Write(InvisTime.ToString()); + writer.Write(lastTime.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(MessageReader reader) + { + InvisTime = long.Parse(reader.ReadString()); + lastTime = long.Parse(reader.ReadString()); + } + + public override void AfterMeetingTasks() + { + InvisTime = -10; + lastTime = Utils.TimeStamp; + SendRPC(); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask || !IsEnable || Main.HasJustStarted || ExileController.Instance || player == null) return; + + if (Count++ < 30) return; + + Count = 0; + + long now = Utils.TimeStamp; + + if (lastTime != -10) + { + if (!player.IsModClient() && UsedRole != CustomRoles.Chameleon) + { + long cooldown = lastTime + (long)Cooldown - now; + if ((int)cooldown != CD) player.Notify(string.Format(GetString("CDPT"), cooldown + 1), 1.1f, overrideAll: true); + + CD = (int)cooldown; + } + + if (lastTime + (long)Cooldown < now) + { + lastTime = -10; + if (!player.IsModClient()) player.Notify(GetString("SwooperCanVent"), 10f); + + SendRPC(); + CD = 0; + } + } + + if (lastFixedTime != now && IsInvis) + { + lastFixedTime = now; + var refresh = false; + long remainTime = InvisTime + (long)Duration - now; + + switch (remainTime) + { + case < 0: + lastTime = now; + Vector2 pos = player.Pos(); + player.MyPhysics?.RpcBootFromVent(ventedId == -10 ? Main.LastEnteredVent[player.PlayerId].Id : ventedId); + player.Notify(GetString("SwooperInvisStateOut")); + InvisTime = -10; + SendRPC(); + refresh = true; + LateTask.New(() => player.TP(pos), 0.5f, log: false); + break; + case <= 10 when !player.IsModClient(): + player.Notify(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1), overrideAll: true); + break; + } + + if (refresh) SendRPC(); + } + } + + public override void OnCoEnterVent(PlayerPhysics __instance, int ventId) + { + if (!AmongUsClient.Instance.AmHost || IsInvis) return; + + PlayerControl pc = __instance.myPlayer; + + LateTask.New(() => + { + float limit = pc.GetAbilityUseLimit(); + bool wraith = UsedRole == CustomRoles.Wraith; + + if (CanGoInvis && (wraith || limit >= 1)) + { + ventedId = ventId; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(__instance.NetId, 34, SendOption.Reliable, pc.GetClientId()); + writer.WritePacked(ventId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + + InvisTime = Utils.TimeStamp; + if (!wraith) pc.RpcRemoveAbilityUse(); + + SendRPC(); + pc.Notify(GetString("SwooperInvisState"), Duration); + } + else if (!VentNormallyOnCooldown) + { + __instance.RpcBootFromVent(ventId); + pc.Notify(GetString("SwooperInvisInCooldown")); + } + }, 0.5f, "Swooper Vent"); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (!IsInvis || InvisTime == Utils.TimeStamp) return; + + InvisTime = -10; + lastTime = Utils.TimeStamp; + SendRPC(); + + pc?.MyPhysics?.RpcBootFromVent(vent.Id); + pc?.Notify(GetString("SwooperInvisStateOut")); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || seer == null || seer.PlayerId != SwooperId || !GameStates.IsInTask || ExileController.Instance || !seer.IsAlive()) return string.Empty; + + var str = new StringBuilder(); + + if (IsInvis) + { + long remainTime = InvisTime + (long)Duration - Utils.TimeStamp; + str.Append(string.Format(GetString("SwooperInvisStateCountdown"), remainTime + 1)); + } + else if (lastTime != -10) + { + long cooldown = lastTime + (long)Cooldown - Utils.TimeStamp; + str.Append(string.Format(GetString("SwooperInvisCooldownRemain"), cooldown + 1)); + } + else + str.Append(GetString("SwooperCanVent")); + + return str.ToString(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Medic.ProtectList.Contains(target.PlayerId)) return false; + + if (target.Is(CustomRoles.Bait)) return true; + + if (!IsInvis) return true; + + killer.SetKillCooldown(); + target.SetRealKiller(killer); + target.RpcCheckAndMurder(target); + return false; + } + + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return UsedRole != CustomRoles.Chameleon || pc.GetClosestVent()?.Id == ventId; + } } \ No newline at end of file diff --git a/Roles/Impostor/TimeThief.cs b/Roles/Impostor/TimeThief.cs index f4473b257..06676bacd 100644 --- a/Roles/Impostor/TimeThief.cs +++ b/Roles/Impostor/TimeThief.cs @@ -1,72 +1,71 @@ using System.Collections.Generic; using System.Linq; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class TimeThief : RoleBase { - public class TimeThief : RoleBase + private const int Id = 3300; + public static List PlayerIdList = []; + public static OptionItem KillCooldown; + public static OptionItem DecreaseMeetingTime; + public static OptionItem LowerLimitVotingTime; + public static OptionItem ReturnStolenTimeUponDeath; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 3300; - public static List PlayerIdList = []; - public static OptionItem KillCooldown; - public static OptionItem DecreaseMeetingTime; - public static OptionItem LowerLimitVotingTime; - public static OptionItem ReturnStolenTimeUponDeath; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.TimeThief); - public override bool IsEnable => PlayerIdList.Count > 0; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.TimeThief); + DecreaseMeetingTime = new IntegerOptionItem(Id + 11, "TimeThiefDecreaseMeetingTime", new(0, 100, 1), 10, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]) + .SetValueFormat(OptionFormat.Seconds); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]) - .SetValueFormat(OptionFormat.Seconds); + LowerLimitVotingTime = new IntegerOptionItem(Id + 12, "TimeThiefLowerLimitVotingTime", new(0, 300, 5), 50, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]) + .SetValueFormat(OptionFormat.Seconds); - DecreaseMeetingTime = new IntegerOptionItem(Id + 11, "TimeThiefDecreaseMeetingTime", new(0, 100, 1), 10, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]) - .SetValueFormat(OptionFormat.Seconds); + ReturnStolenTimeUponDeath = new BooleanOptionItem(Id + 13, "TimeThiefReturnStolenTimeUponDeath", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]); + } - LowerLimitVotingTime = new IntegerOptionItem(Id + 12, "TimeThiefLowerLimitVotingTime", new(0, 300, 5), 50, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + PlayerIdList = []; + } - ReturnStolenTimeUponDeath = new BooleanOptionItem(Id + 13, "TimeThiefReturnStolenTimeUponDeath", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.TimeThief]); - } + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - public override void Init() - { - PlayerIdList = []; - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } + private static int StolenTime(byte id) + { + return PlayerIdList.Contains(id) && (Utils.GetPlayerById(id).IsAlive() || !ReturnStolenTimeUponDeath.GetBool()) + ? DecreaseMeetingTime.GetInt() * Main.PlayerStates[id].GetKillCount(true) + : 0; + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + public static int TotalDecreasedMeetingTime() + { + int sec = PlayerIdList.ToArray().Aggregate(0, (current, playerId) => current - StolenTime(playerId)); - private static int StolenTime(byte id) - { - return PlayerIdList.Contains(id) && (Utils.GetPlayerById(id).IsAlive() || !ReturnStolenTimeUponDeath.GetBool()) - ? DecreaseMeetingTime.GetInt() * Main.PlayerStates[id].GetKillCount(true) - : 0; - } + Logger.Info($"{sec} second", "TimeThief.TotalDecreasedMeetingTime"); + return sec; + } - public static int TotalDecreasedMeetingTime() - { - int sec = PlayerIdList.ToArray().Aggregate(0, (current, playerId) => current - StolenTime(playerId)); - - Logger.Info($"{sec} second", "TimeThief.TotalDecreasedMeetingTime"); - return sec; - } - - public override string GetProgressText(byte playerId, bool comms) - { - return StolenTime(playerId) > 0 ? Utils.ColorString(Palette.ImpostorRed.ShadeColor(0.5f), $"{-StolenTime(playerId)}s") : string.Empty; - } + public override string GetProgressText(byte playerId, bool comms) + { + return StolenTime(playerId) > 0 ? Utils.ColorString(Palette.ImpostorRed.ShadeColor(0.5f), $"{-StolenTime(playerId)}s") : string.Empty; } } \ No newline at end of file diff --git a/Roles/Impostor/Trickster.cs b/Roles/Impostor/Trickster.cs index f81e7e0cf..103f50e60 100644 --- a/Roles/Impostor/Trickster.cs +++ b/Roles/Impostor/Trickster.cs @@ -1,16 +1,15 @@ -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Trickster : RoleBase { - internal class Trickster : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - Options.SetupRoleOptions(4300, TabGroup.ImpostorRoles, CustomRoles.Trickster); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + Options.SetupRoleOptions(4300, TabGroup.ImpostorRoles, CustomRoles.Trickster); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Impostor/Twister.cs b/Roles/Impostor/Twister.cs index 7a712bab9..29b01d7fa 100644 --- a/Roles/Impostor/Twister.cs +++ b/Roles/Impostor/Twister.cs @@ -7,118 +7,117 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Twister : RoleBase { - public class Twister : RoleBase + private const int Id = 4400; + + public static OptionItem ShapeshiftCooldown; + private static OptionItem TwisterLimitOpt; + public static OptionItem TwisterAbilityUseGainWithEachKill; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 4400; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Twister); - public static OptionItem ShapeshiftCooldown; - private static OptionItem TwisterLimitOpt; - public static OptionItem TwisterAbilityUseGainWithEachKill; + ShapeshiftCooldown = new FloatOptionItem(Id + 10, "TwisterCooldown", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Twister]) + .SetValueFormat(OptionFormat.Seconds); - public static bool On; - public override bool IsEnable => On; + TwisterLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Twister]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + TwisterAbilityUseGainWithEachKill = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.4f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Twister]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + playerId.SetAbilityUseLimit(TwisterLimitOpt.GetInt()); + On = true; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); + else { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Twister); - - ShapeshiftCooldown = new FloatOptionItem(Id + 10, "TwisterCooldown", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Twister]) - .SetValueFormat(OptionFormat.Seconds); - - TwisterLimitOpt = new IntegerOptionItem(Id + 11, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Twister]) - .SetValueFormat(OptionFormat.Times); - - TwisterAbilityUseGainWithEachKill = new FloatOptionItem(Id + 12, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 0.4f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Twister]) - .SetValueFormat(OptionFormat.Times); + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; } + } - public override void Init() + public override void OnPet(PlayerControl pc) + { + TwistPlayers(pc, true); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + TwistPlayers(shapeshifter, shapeshifting); + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + TwistPlayers(pc, true); + return false; + } + + private static void TwistPlayers(PlayerControl shapeshifter, bool shapeshifting) + { + if (shapeshifter == null) return; + + if (shapeshifter.GetAbilityUseLimit() < 1) return; + + if (!shapeshifting) return; + + List changePositionPlayers = [shapeshifter.PlayerId]; + shapeshifter.RpcRemoveAbilityUse(); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - On = false; + if (changePositionPlayers.Contains(pc.PlayerId) || Pelican.IsEaten(pc.PlayerId) || pc.onLadder || pc.inMovingPlat || pc.inVent || GameStates.IsMeeting) continue; + + PlayerControl[] filtered = Main.AllAlivePlayerControls.Where(a => !a.inVent && !Pelican.IsEaten(a.PlayerId) && !a.onLadder && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); + if (filtered.Length == 0) break; + + PlayerControl target = filtered.RandomElement(); + + changePositionPlayers.Add(target.PlayerId); + changePositionPlayers.Add(pc.PlayerId); + + pc.RPCPlayCustomSound("Teleport"); + + Vector2 originPs = target.Pos(); + target.TP(pc.Pos()); + pc.TP(originPs); + + target.Notify(ColorString(GetRoleColor(CustomRoles.Twister), string.Format(GetString("TeleportedByTwister"), pc.GetRealName()))); + pc.Notify(ColorString(GetRoleColor(CustomRoles.Twister), string.Format(GetString("TeleportedByTwister"), target.GetRealName()))); } + } - public override void Add(byte playerId) + public override void SetButtonTexts(HudManager hud, byte id) + { + if (UsePets.GetBool()) + hud.PetButton?.OverrideText(GetString("TwisterButtonText")); + else { - playerId.SetAbilityUseLimit(TwisterLimitOpt.GetInt()); - On = true; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = ShapeshiftCooldown.GetFloat(); - else - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - - public override void OnPet(PlayerControl pc) - { - TwistPlayers(pc, true); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - TwistPlayers(shapeshifter, shapeshifting); - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - TwistPlayers(pc, true); - return false; - } - - private static void TwistPlayers(PlayerControl shapeshifter, bool shapeshifting) - { - if (shapeshifter == null) return; - - if (shapeshifter.GetAbilityUseLimit() < 1) return; - - if (!shapeshifting) return; - - List changePositionPlayers = [shapeshifter.PlayerId]; - shapeshifter.RpcRemoveAbilityUse(); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (changePositionPlayers.Contains(pc.PlayerId) || Pelican.IsEaten(pc.PlayerId) || pc.onLadder || pc.inMovingPlat || pc.inVent || GameStates.IsMeeting) continue; - - PlayerControl[] filtered = Main.AllAlivePlayerControls.Where(a => !a.inVent && !Pelican.IsEaten(a.PlayerId) && !a.onLadder && a.PlayerId != pc.PlayerId && !changePositionPlayers.Contains(a.PlayerId)).ToArray(); - if (filtered.Length == 0) break; - - PlayerControl target = filtered.RandomElement(); - - changePositionPlayers.Add(target.PlayerId); - changePositionPlayers.Add(pc.PlayerId); - - pc.RPCPlayCustomSound("Teleport"); - - Vector2 originPs = target.Pos(); - target.TP(pc.Pos()); - pc.TP(originPs); - - target.Notify(ColorString(GetRoleColor(CustomRoles.Twister), string.Format(GetString("TeleportedByTwister"), pc.GetRealName()))); - pc.Notify(ColorString(GetRoleColor(CustomRoles.Twister), string.Format(GetString("TeleportedByTwister"), target.GetRealName()))); - } - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (UsePets.GetBool()) - hud.PetButton?.OverrideText(GetString("TwisterButtonText")); - else - { - hud.AbilityButton?.OverrideText(GetString("TwisterButtonText")); - hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); - } + hud.AbilityButton?.OverrideText(GetString("TwisterButtonText")); + hud.AbilityButton?.SetUsesRemaining((int)id.GetAbilityUseLimit()); } } } \ No newline at end of file diff --git a/Roles/Impostor/Underdog.cs b/Roles/Impostor/Underdog.cs index d5832eabf..1097be360 100644 --- a/Roles/Impostor/Underdog.cs +++ b/Roles/Impostor/Underdog.cs @@ -1,47 +1,46 @@ using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Underdog : RoleBase { - internal class Underdog : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + SetupRoleOptions(10025, TabGroup.ImpostorRoles, CustomRoles.Underdog); - public override void SetupCustomOption() - { - SetupRoleOptions(10025, TabGroup.ImpostorRoles, CustomRoles.Underdog); + UnderdogMaximumPlayersNeededToKill = new IntegerOptionItem(10030, "UnderdogMaximumPlayersNeededToKill", new(1, 15, 1), 5, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Underdog]) + .SetValueFormat(OptionFormat.Players); - UnderdogMaximumPlayersNeededToKill = new IntegerOptionItem(10030, "UnderdogMaximumPlayersNeededToKill", new(1, 15, 1), 5, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Underdog]) - .SetValueFormat(OptionFormat.Players); + UnderdogKillCooldown = new FloatOptionItem(10031, "KillCooldown", new(0f, 180f, 2.5f), 15f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Underdog]) + .SetValueFormat(OptionFormat.Seconds); - UnderdogKillCooldown = new FloatOptionItem(10031, "KillCooldown", new(0f, 180f, 2.5f), 15f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Underdog]) - .SetValueFormat(OptionFormat.Seconds); + UnderdogKillCooldownWithMorePlayersAlive = new FloatOptionItem(10032, "UnderdogKillCooldownWithMorePlayersAlive", new(0f, 180f, 2.5f), 35f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Underdog]) + .SetValueFormat(OptionFormat.Seconds); + } - UnderdogKillCooldownWithMorePlayersAlive = new FloatOptionItem(10032, "UnderdogKillCooldownWithMorePlayersAlive", new(0f, 180f, 2.5f), 35f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Underdog]) - .SetValueFormat(OptionFormat.Seconds); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Main.AllAlivePlayerControls.Length < UnderdogMaximumPlayersNeededToKill.GetInt() ? UnderdogKillCooldown.GetFloat() : UnderdogKillCooldownWithMorePlayersAlive.GetFloat(); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Main.AllAlivePlayerControls.Length < UnderdogMaximumPlayersNeededToKill.GetInt() ? UnderdogKillCooldown.GetFloat() : UnderdogKillCooldownWithMorePlayersAlive.GetFloat(); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - SetKillCooldown(killer.PlayerId); - } + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + SetKillCooldown(killer.PlayerId); } } \ No newline at end of file diff --git a/Roles/Impostor/Undertaker.cs b/Roles/Impostor/Undertaker.cs index b227d9800..626a7cb53 100644 --- a/Roles/Impostor/Undertaker.cs +++ b/Roles/Impostor/Undertaker.cs @@ -1,34 +1,33 @@ using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Undertaker : RoleBase { - internal class Undertaker : RoleBase + private const int Id = 720; + public static OptionItem UndertakerMarkCooldown; + public static OptionItem UndertakerAssassinateCooldown; + public static OptionItem UndertakerCanKillAfterAssassinate; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 720; - public static OptionItem UndertakerMarkCooldown; - public static OptionItem UndertakerAssassinateCooldown; - public static OptionItem UndertakerCanKillAfterAssassinate; + SetupRoleOptions(Id + 20, TabGroup.ImpostorRoles, CustomRoles.Undertaker); - public override bool IsEnable => false; + UndertakerMarkCooldown = new FloatOptionItem(Id + 30, "UndertakerMarkCooldown", new(0f, 180f, 0.5f), 1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Undertaker]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id + 20, TabGroup.ImpostorRoles, CustomRoles.Undertaker); + UndertakerAssassinateCooldown = new FloatOptionItem(Id + 31, "UndertakerAssassinateCooldown", new(0f, 180f, 0.5f), 18.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Undertaker]) + .SetValueFormat(OptionFormat.Seconds); - UndertakerMarkCooldown = new FloatOptionItem(Id + 30, "UndertakerMarkCooldown", new(0f, 180f, 0.5f), 1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Undertaker]) - .SetValueFormat(OptionFormat.Seconds); - - UndertakerAssassinateCooldown = new FloatOptionItem(Id + 31, "UndertakerAssassinateCooldown", new(0f, 180f, 0.5f), 18.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Undertaker]) - .SetValueFormat(OptionFormat.Seconds); - - UndertakerCanKillAfterAssassinate = new BooleanOptionItem(Id + 32, "UndertakerCanKillAfterAssassinate", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Undertaker]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + UndertakerCanKillAfterAssassinate = new BooleanOptionItem(Id + 32, "UndertakerCanKillAfterAssassinate", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Undertaker]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Impostor/Vampire.cs b/Roles/Impostor/Vampire.cs index 179f425bc..2404f7da8 100644 --- a/Roles/Impostor/Vampire.cs +++ b/Roles/Impostor/Vampire.cs @@ -6,181 +6,180 @@ using EHR.Neutral; using UnityEngine; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Vampire : RoleBase { - public class Vampire : RoleBase + private const int Id = 4500; + private static readonly List PlayerIdList = []; + private static readonly Dictionary BittenPlayers = []; + + private static OptionItem Cooldown; + private static OptionItem OptionKillDelay; + private static OptionItem OptionCanKillNormally; + private bool CanKillNormally; + private bool CanVent; + + private bool IsPoisoner; + private float KillCooldown; + private float KillDelay; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 4500; - private static readonly List PlayerIdList = []; - private static readonly Dictionary BittenPlayers = []; + Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Vampire); - private static OptionItem Cooldown; - private static OptionItem OptionKillDelay; - private static OptionItem OptionCanKillNormally; - private bool CanKillNormally; - private bool CanVent; + Cooldown = new FloatOptionItem(Id + 9, "VampireKillCooldown", new(1f, 90f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vampire]) + .SetValueFormat(OptionFormat.Seconds); - private bool IsPoisoner; - private float KillCooldown; - private float KillDelay; + OptionKillDelay = new FloatOptionItem(Id + 10, "VampireKillDelay", new(1f, 30f, 1f), 3f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vampire]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + OptionCanKillNormally = new BooleanOptionItem(Id + 11, "CanKillNormally", true, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vampire]); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList.Clear(); + BittenPlayers.Clear(); + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + + IsPoisoner = Main.PlayerStates[playerId].MainRole == CustomRoles.Poisoner; + + if (!IsPoisoner) { - Options.SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Vampire); - - Cooldown = new FloatOptionItem(Id + 9, "VampireKillCooldown", new(1f, 90f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vampire]) - .SetValueFormat(OptionFormat.Seconds); - - OptionKillDelay = new FloatOptionItem(Id + 10, "VampireKillDelay", new(1f, 30f, 1f), 3f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vampire]) - .SetValueFormat(OptionFormat.Seconds); - - OptionCanKillNormally = new BooleanOptionItem(Id + 11, "CanKillNormally", true, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vampire]); + KillCooldown = Cooldown.GetFloat(); + KillDelay = OptionKillDelay.GetFloat(); + CanVent = true; + CanKillNormally = OptionCanKillNormally.GetBool(); } - - public override void Init() + else { - PlayerIdList.Clear(); - BittenPlayers.Clear(); + KillCooldown = Poisoner.KillCooldown.GetFloat(); + KillDelay = Poisoner.OptionKillDelay.GetFloat(); + CanVent = Poisoner.CanVent.GetBool(); + CanKillNormally = Poisoner.CanKillNormally.GetBool(); } + } - public override void Add(byte playerId) + public static bool IsThisRole(byte playerId) + { + return PlayerIdList.Contains(playerId); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsThisRole(killer.PlayerId)) return true; + + if (target.Is(CustomRoles.Bait)) return true; + + if (target.Is(CustomRoles.Pestilence)) return true; + + if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) return true; + + if (target.Is(CustomRoles.Opportunist) && target.AllTasksCompleted() && Opportunist.OppoImmuneToAttacksWhenTasksDone.GetBool()) return true; + + if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return true; + + if (Medic.ProtectList.Contains(target.PlayerId)) return true; + + if (CanKillNormally) return killer.CheckDoubleTrigger(target, Bite); + + Bite(); + return false; + + void Bite() { - PlayerIdList.Add(playerId); + killer.SetKillCooldown(KillCooldown + KillDelay); + killer.RPCPlayCustomSound("Bite"); - IsPoisoner = Main.PlayerStates[playerId].MainRole == CustomRoles.Poisoner; + if (!BittenPlayers.ContainsKey(target.PlayerId)) BittenPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); + } + } - if (!IsPoisoner) + public override void OnFixedUpdate(PlayerControl vampire) + { + if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || ExileController.Instance) return; + + byte vampireID = vampire.PlayerId; + if (!IsThisRole(vampire.PlayerId)) return; + + List targetList = [.. BittenPlayers.Where(b => b.Value.VampireId == vampireID).Select(b => b.Key)]; + + foreach (byte targetId in targetList.ToArray()) + { + BittenInfo bitten = BittenPlayers[targetId]; + + if (bitten.KillTimer >= KillDelay) { - KillCooldown = Cooldown.GetFloat(); - KillDelay = OptionKillDelay.GetFloat(); - CanVent = true; - CanKillNormally = OptionCanKillNormally.GetBool(); + PlayerControl target = Utils.GetPlayerById(targetId); + KillBitten(vampire, target); + BittenPlayers.Remove(targetId); } else { - KillCooldown = Poisoner.KillCooldown.GetFloat(); - KillDelay = Poisoner.OptionKillDelay.GetFloat(); - CanVent = Poisoner.CanVent.GetBool(); - CanKillNormally = Poisoner.CanKillNormally.GetBool(); + bitten.KillTimer += Time.fixedDeltaTime; + BittenPlayers[targetId] = bitten; } } + } - public static bool IsThisRole(byte playerId) - { - return PlayerIdList.Contains(playerId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown; - } + private void KillBitten(PlayerControl vampire, PlayerControl target, bool isButton = false) + { + if (vampire == null || target == null || target.Data.Disconnected) return; - public override bool CanUseImpostorVentButton(PlayerControl pc) + if (target.IsAlive()) { - return CanVent; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsThisRole(killer.PlayerId)) return true; - - if (target.Is(CustomRoles.Bait)) return true; - - if (target.Is(CustomRoles.Pestilence)) return true; + target.Suicide(IsPoisoner ? PlayerState.DeathReason.Poison : PlayerState.DeathReason.Bite, vampire); - if (target.Is(CustomRoles.Guardian) && target.AllTasksCompleted()) return true; - - if (target.Is(CustomRoles.Opportunist) && target.AllTasksCompleted() && Opportunist.OppoImmuneToAttacksWhenTasksDone.GetBool()) return true; - - if (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) return true; - - if (Medic.ProtectList.Contains(target.PlayerId)) return true; - - if (CanKillNormally) return killer.CheckDoubleTrigger(target, Bite); - - Bite(); - return false; - - void Bite() + if (!isButton && vampire.IsAlive()) { - killer.SetKillCooldown(KillCooldown + KillDelay); - killer.RPCPlayCustomSound("Bite"); + RPC.PlaySoundRPC(vampire.PlayerId, Sounds.KillSound); + if (target.Is(CustomRoles.Trapper)) vampire.TrapperKilled(target); - if (!BittenPlayers.ContainsKey(target.PlayerId)) BittenPlayers.Add(target.PlayerId, new(killer.PlayerId, 0f)); + vampire.Notify(GetString("VampireTargetDead")); } } + } - public override void OnFixedUpdate(PlayerControl vampire) + public override void OnReportDeadBody() + { + foreach (byte targetId in BittenPlayers.Keys) { - if (!AmongUsClient.Instance.AmHost || !GameStates.IsInTask || ExileController.Instance) return; - - byte vampireID = vampire.PlayerId; - if (!IsThisRole(vampire.PlayerId)) return; - - List targetList = [.. BittenPlayers.Where(b => b.Value.VampireId == vampireID).Select(b => b.Key)]; - - foreach (byte targetId in targetList.ToArray()) - { - BittenInfo bitten = BittenPlayers[targetId]; - - if (bitten.KillTimer >= KillDelay) - { - PlayerControl target = Utils.GetPlayerById(targetId); - KillBitten(vampire, target); - BittenPlayers.Remove(targetId); - } - else - { - bitten.KillTimer += Time.fixedDeltaTime; - BittenPlayers[targetId] = bitten; - } - } + PlayerControl target = Utils.GetPlayerById(targetId); + PlayerControl vampire = Utils.GetPlayerById(BittenPlayers[targetId].VampireId); + KillBitten(vampire, target); } - - private void KillBitten(PlayerControl vampire, PlayerControl target, bool isButton = false) - { - if (vampire == null || target == null || target.Data.Disconnected) return; - - if (target.IsAlive()) - { - target.Suicide(IsPoisoner ? PlayerState.DeathReason.Poison : PlayerState.DeathReason.Bite, vampire); - if (!isButton && vampire.IsAlive()) - { - RPC.PlaySoundRPC(vampire.PlayerId, Sounds.KillSound); - if (target.Is(CustomRoles.Trapper)) vampire.TrapperKilled(target); + BittenPlayers.Clear(); + } - vampire.Notify(GetString("VampireTargetDead")); - } - } - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton.OverrideText(GetString(IsPoisoner ? "PoisonerKillButtonText" : "VampireBiteButtonText")); + } - public override void OnReportDeadBody() - { - foreach (byte targetId in BittenPlayers.Keys) - { - PlayerControl target = Utils.GetPlayerById(targetId); - PlayerControl vampire = Utils.GetPlayerById(BittenPlayers[targetId].VampireId); - KillBitten(vampire, target); - } - - BittenPlayers.Clear(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton.OverrideText(GetString(IsPoisoner ? "PoisonerKillButtonText" : "VampireBiteButtonText")); - } - - private class BittenInfo(byte vampierId, float killTimer) - { - public readonly byte VampireId = vampierId; - public float KillTimer = killTimer; - } + private class BittenInfo(byte vampierId, float killTimer) + { + public readonly byte VampireId = vampierId; + public float KillTimer = killTimer; } } \ No newline at end of file diff --git a/Roles/Impostor/Ventriloquist.cs b/Roles/Impostor/Ventriloquist.cs index f6b1367bc..f35ebbdb8 100644 --- a/Roles/Impostor/Ventriloquist.cs +++ b/Roles/Impostor/Ventriloquist.cs @@ -1,38 +1,37 @@ -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Ventriloquist : RoleBase { - public class Ventriloquist : RoleBase + public static bool On; + + private static OptionItem UseLimit; + private static OptionItem VentriloquistAbilityUseGainWithEachKill; + + public byte Target; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(649650, TabGroup.ImpostorRoles, CustomRoles.Ventriloquist); - private static OptionItem UseLimit; - private static OptionItem VentriloquistAbilityUseGainWithEachKill; + UseLimit = new IntegerOptionItem(649652, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventriloquist]) + .SetValueFormat(OptionFormat.Times); - public byte Target; - public override bool IsEnable => On; + VentriloquistAbilityUseGainWithEachKill = new FloatOptionItem(649653, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventriloquist]) + .SetValueFormat(OptionFormat.Times); + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(649650, TabGroup.ImpostorRoles, CustomRoles.Ventriloquist); + public override void Init() + { + On = false; + } - UseLimit = new IntegerOptionItem(649652, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventriloquist]) - .SetValueFormat(OptionFormat.Times); - - VentriloquistAbilityUseGainWithEachKill = new FloatOptionItem(649653, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Ventriloquist]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - Target = byte.MaxValue; - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } + public override void Add(byte playerId) + { + On = true; + Target = byte.MaxValue; + playerId.SetAbilityUseLimit(UseLimit.GetInt()); } } \ No newline at end of file diff --git a/Roles/Impostor/Vindicator.cs b/Roles/Impostor/Vindicator.cs index eb50eded6..9ade25007 100644 --- a/Roles/Impostor/Vindicator.cs +++ b/Roles/Impostor/Vindicator.cs @@ -1,25 +1,24 @@ using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Vindicator : RoleBase { - internal class Vindicator : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(3400, TabGroup.ImpostorRoles, CustomRoles.Vindicator); - public override void SetupCustomOption() - { - SetupRoleOptions(3400, TabGroup.ImpostorRoles, CustomRoles.Vindicator); + VindicatorAdditionalVote = new IntegerOptionItem(3410, "MayorAdditionalVote", new(1, 30, 1), 1, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vindicator]) + .SetValueFormat(OptionFormat.Votes); - VindicatorAdditionalVote = new IntegerOptionItem(3410, "MayorAdditionalVote", new(1, 30, 1), 1, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vindicator]) - .SetValueFormat(OptionFormat.Votes); - - VindicatorHideVote = new BooleanOptionItem(3411, "MayorHideVote", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vindicator]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + VindicatorHideVote = new BooleanOptionItem(3411, "MayorHideVote", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vindicator]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Impostor/Visionary.cs b/Roles/Impostor/Visionary.cs index c2a79ce1f..0655184e3 100644 --- a/Roles/Impostor/Visionary.cs +++ b/Roles/Impostor/Visionary.cs @@ -1,78 +1,77 @@ using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Visionary : RoleBase { - internal class Visionary : RoleBase + public static bool On; + + private static OptionItem UseLimit; + public static OptionItem VisionaryAbilityUseGainWithEachKill; + private static OptionItem ShapeshiftCooldown; + private static OptionItem ChanceOfSeeingAllAlignmentsOnStart; + + public List RevealedPlayerIds = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + Options.SetupRoleOptions(16150, TabGroup.ImpostorRoles, CustomRoles.Visionary); - private static OptionItem UseLimit; - public static OptionItem VisionaryAbilityUseGainWithEachKill; - private static OptionItem ShapeshiftCooldown; - private static OptionItem ChanceOfSeeingAllAlignmentsOnStart; + UseLimit = new IntegerOptionItem(16152, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) + .SetValueFormat(OptionFormat.Times); - public List RevealedPlayerIds = []; - public override bool IsEnable => On; + VisionaryAbilityUseGainWithEachKill = new FloatOptionItem(16153, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + ShapeshiftCooldown = new FloatOptionItem(16154, "ShapeshiftCooldown", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) + .SetValueFormat(OptionFormat.Seconds); + + ChanceOfSeeingAllAlignmentsOnStart = new FloatOptionItem(16155, "ChanceOfSeeingAllAlignmentsOnStart", new(0f, 100f, 1f), 0f, TabGroup.ImpostorRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) + .SetValueFormat(OptionFormat.Percent); + } + + public override void Add(byte playerId) + { + On = true; + RevealedPlayerIds = []; + + if (IRandom.Instance.Next(100) < ChanceOfSeeingAllAlignmentsOnStart.GetInt()) { - Options.SetupRoleOptions(16150, TabGroup.ImpostorRoles, CustomRoles.Visionary); - - UseLimit = new IntegerOptionItem(16152, "AbilityUseLimit", new(0, 5, 1), 0, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) - .SetValueFormat(OptionFormat.Times); - - VisionaryAbilityUseGainWithEachKill = new FloatOptionItem(16153, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) - .SetValueFormat(OptionFormat.Times); - - ShapeshiftCooldown = new FloatOptionItem(16154, "ShapeshiftCooldown", new(1f, 60f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) - .SetValueFormat(OptionFormat.Seconds); - - ChanceOfSeeingAllAlignmentsOnStart = new FloatOptionItem(16155, "ChanceOfSeeingAllAlignmentsOnStart", new(0f, 100f, 1f), 0f, TabGroup.ImpostorRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Visionary]) - .SetValueFormat(OptionFormat.Percent); + RevealedPlayerIds.AddRange(Main.PlayerStates.Keys); + playerId.SetAbilityUseLimit(0); + return; } - public override void Add(byte playerId) - { - On = true; - RevealedPlayerIds = []; + playerId.SetAbilityUseLimit(UseLimit.GetInt()); + } - if (IRandom.Instance.Next(100) < ChanceOfSeeingAllAlignmentsOnStart.GetInt()) - { - RevealedPlayerIds.AddRange(Main.PlayerStates.Keys); - playerId.SetAbilityUseLimit(0); - return; - } + public override void Init() + { + On = false; + } - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; + } - public override void Init() - { - On = false; - } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) return true; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } + if (RevealedPlayerIds.Contains(target.PlayerId) || shapeshifter.GetAbilityUseLimit() < 1) return false; - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting) return true; + RevealedPlayerIds.Add(target.PlayerId); + shapeshifter.RpcRemoveAbilityUse(); + Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: target); - if (RevealedPlayerIds.Contains(target.PlayerId) || shapeshifter.GetAbilityUseLimit() < 1) return false; - - RevealedPlayerIds.Add(target.PlayerId); - shapeshifter.RpcRemoveAbilityUse(); - Utils.NotifyRoles(SpecifySeer: shapeshifter, SpecifyTarget: target); - - return false; - } + return false; } } \ No newline at end of file diff --git a/Roles/Impostor/Warlock.cs b/Roles/Impostor/Warlock.cs index a6c17dc18..a977aaa8e 100644 --- a/Roles/Impostor/Warlock.cs +++ b/Roles/Impostor/Warlock.cs @@ -8,323 +8,322 @@ using EHR.Neutral; using UnityEngine; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Warlock : RoleBase { - internal class Warlock : RoleBase + public static bool On; + + private static OptionItem WarlockCanKillAllies; + private static OptionItem WarlockCanKillSelf; + private static OptionItem KillCooldown; + private static OptionItem CurseCooldown; + public static OptionItem ShapeshiftCooldown; + private static OptionItem FreezeAfterCurseKill; + private static OptionItem FreezeDurationAfterCurseKill; + + public static Dictionary WarlockTimer = []; + public static Dictionary CursedPlayers = []; + public static Dictionary IsCurseAndKill = []; + public static bool IsCursed; + private float CurseCD; + + private float KCD; + private long LastNotify; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupRoleOptions(4600, TabGroup.ImpostorRoles, CustomRoles.Warlock); - private static OptionItem WarlockCanKillAllies; - private static OptionItem WarlockCanKillSelf; - private static OptionItem KillCooldown; - private static OptionItem CurseCooldown; - public static OptionItem ShapeshiftCooldown; - private static OptionItem FreezeAfterCurseKill; - private static OptionItem FreezeDurationAfterCurseKill; + WarlockCanKillAllies = new BooleanOptionItem(4610, "CanKillAllies", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]); - public static Dictionary WarlockTimer = []; - public static Dictionary CursedPlayers = []; - public static Dictionary IsCurseAndKill = []; - public static bool IsCursed; - private float CurseCD; + WarlockCanKillSelf = new BooleanOptionItem(4611, "CanKillSelf", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]); - private float KCD; - private long LastNotify; - public override bool IsEnable => On; + KillCooldown = new FloatOptionItem(4613, "KillCooldown", new(0f, 180f, 1f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + CurseCooldown = new FloatOptionItem(4614, "CurseCooldown", new(0f, 180f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) + .SetValueFormat(OptionFormat.Seconds); + + ShapeshiftCooldown = new FloatOptionItem(4612, "ShapeshiftCooldown", new(0f, 180f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) + .SetValueFormat(OptionFormat.Seconds); + + FreezeAfterCurseKill = new BooleanOptionItem(4615, "FreezeAfterCurseKill", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]); + + FreezeDurationAfterCurseKill = new FloatOptionItem(4616, "FreezeDuration", new(0f, 60f, 1f), 4f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + CursedPlayers.Add(playerId, null); + IsCurseAndKill.Add(playerId, false); + KCD = KillCooldown.GetFloat(); + CurseCD = CurseCooldown.GetFloat(); + LastNotify = 0; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + try { - SetupRoleOptions(4600, TabGroup.ImpostorRoles, CustomRoles.Warlock); - - WarlockCanKillAllies = new BooleanOptionItem(4610, "CanKillAllies", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]); - - WarlockCanKillSelf = new BooleanOptionItem(4611, "CanKillSelf", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]); - - KillCooldown = new FloatOptionItem(4613, "KillCooldown", new(0f, 180f, 1f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) - .SetValueFormat(OptionFormat.Seconds); - - CurseCooldown = new FloatOptionItem(4614, "CurseCooldown", new(0f, 180f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftCooldown = new FloatOptionItem(4612, "ShapeshiftCooldown", new(0f, 180f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) - .SetValueFormat(OptionFormat.Seconds); - - FreezeAfterCurseKill = new BooleanOptionItem(4615, "FreezeAfterCurseKill", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]); - - FreezeDurationAfterCurseKill = new FloatOptionItem(4616, "FreezeDuration", new(0f, 60f, 1f), 4f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Warlock]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Add(byte playerId) - { - On = true; - CursedPlayers.Add(playerId, null); - IsCurseAndKill.Add(playerId, false); - KCD = KillCooldown.GetFloat(); - CurseCD = CurseCooldown.GetFloat(); - LastNotify = 0; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - try - { - if (UsePhantomBasis.GetBool()) - AURoleOptions.PhantomCooldown = IsCursed ? 1f : ShapeshiftCooldown.GetFloat(); - else - { - if (UsePets.GetBool()) return; - - AURoleOptions.ShapeshifterCooldown = IsCursed ? 1f : ShapeshiftCooldown.GetFloat(); - AURoleOptions.ShapeshifterDuration = 1f; - } - } - catch { } - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = 1f; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - bool curse = IsCurseAndKill.TryGetValue(id, out bool wcs) && wcs; - bool shapeshifting = id.IsPlayerShifted(); - - if (!shapeshifting && !curse) - hud.KillButton?.OverrideText(Translator.GetString("WarlockCurseButtonText")); + if (UsePhantomBasis.GetBool()) + AURoleOptions.PhantomCooldown = IsCursed ? 1f : ShapeshiftCooldown.GetFloat(); else - hud.KillButton?.OverrideText(Translator.GetString("KillButtonText")); - - if (!shapeshifting && curse) hud.AbilityButton?.OverrideText(Translator.GetString("WarlockShapeshiftButtonText")); - } - - private void ResetCooldowns(bool killCooldown = false, bool curseCooldown = false, bool shapeshiftCooldown = false, PlayerControl warlock = null) - { - if (killCooldown) KCD = KillCooldown.GetFloat(); - - if (curseCooldown) CurseCD = CurseCooldown.GetFloat(); - - if (warlock == null) return; - - if (shapeshiftCooldown) { - if (!UsePets.GetBool()) - warlock.RpcResetAbilityCooldown(); - else - warlock.AddAbilityCD(); + if (UsePets.GetBool()) return; + + AURoleOptions.ShapeshifterCooldown = IsCursed ? 1f : ShapeshiftCooldown.GetFloat(); + AURoleOptions.ShapeshifterDuration = 1f; } + } + catch { } + } - if (KCD > 0f && CurseCD > 0f) LateTask.New(() => { warlock.SetKillCooldown(Math.Min(KCD, CurseCD) - 1f); }, 0.1f, log: false); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = 1f; + } - Utils.NotifyRoles(SpecifySeer: warlock, SpecifyTarget: warlock); + public override void SetButtonTexts(HudManager hud, byte id) + { + bool curse = IsCurseAndKill.TryGetValue(id, out bool wcs) && wcs; + bool shapeshifting = id.IsPlayerShifted(); + + if (!shapeshifting && !curse) + hud.KillButton?.OverrideText(Translator.GetString("WarlockCurseButtonText")); + else + hud.KillButton?.OverrideText(Translator.GetString("KillButtonText")); + + if (!shapeshifting && curse) hud.AbilityButton?.OverrideText(Translator.GetString("WarlockShapeshiftButtonText")); + } + + private void ResetCooldowns(bool killCooldown = false, bool curseCooldown = false, bool shapeshiftCooldown = false, PlayerControl warlock = null) + { + if (killCooldown) KCD = KillCooldown.GetFloat(); + + if (curseCooldown) CurseCD = CurseCooldown.GetFloat(); + + if (warlock == null) return; + + if (shapeshiftCooldown) + { + if (!UsePets.GetBool()) + warlock.RpcResetAbilityCooldown(); + else + warlock.AddAbilityCD(); } - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.IsShifted()) return false; + if (KCD > 0f && CurseCD > 0f) LateTask.New(() => { warlock.SetKillCooldown(Math.Min(KCD, CurseCD) - 1f); }, 0.1f, log: false); - if (killer.CheckDoubleTrigger(target, () => - { - if (CurseCD > 0f) return; + Utils.NotifyRoles(SpecifySeer: warlock, SpecifyTarget: warlock); + } - IsCurseAndKill.TryAdd(killer.PlayerId, false); + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.IsShifted()) return false; - if (!killer.IsShifted() && !IsCurseAndKill[killer.PlayerId]) - { - if (target.Is(CustomRoles.Needy) || target.Is(CustomRoles.Lazy)) return; - - IsCursed = true; - killer.SetKillCooldown(); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); - killer.RPCPlayCustomSound("Line"); - CursedPlayers[killer.PlayerId] = target; - WarlockTimer.Add(killer.PlayerId, 0f); - IsCurseAndKill[killer.PlayerId] = true; - - ResetCooldowns(true, true, warlock: killer); - - return; - } - - if (IsCurseAndKill[killer.PlayerId]) killer.RpcGuardAndKill(target); - })) + if (killer.CheckDoubleTrigger(target, () => { - if (KCD > 0f) return false; + if (CurseCD > 0f) return; - ResetCooldowns(true, true, true, killer); + IsCurseAndKill.TryAdd(killer.PlayerId, false); - return true; - } - - return false; - } - - public override void OnPet(PlayerControl pc) - { - Curse(pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - Curse(shapeshifter); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - Curse(pc); - return false; - } - - private void Curse(PlayerControl pc) - { - IsCurseAndKill.TryAdd(pc.PlayerId, false); - - if (CursedPlayers[pc.PlayerId] != null) - { - if (!CursedPlayers[pc.PlayerId].Data.IsDead) + if (!killer.IsShifted() && !IsCurseAndKill[killer.PlayerId]) { - PlayerControl cp = CursedPlayers[pc.PlayerId]; - Vector2 cppos = cp.Pos(); - Dictionary cpdistance = []; + if (target.Is(CustomRoles.Needy) || target.Is(CustomRoles.Lazy)) return; - foreach (PlayerControl p in Main.AllAlivePlayerControls) + IsCursed = true; + killer.SetKillCooldown(); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + killer.RPCPlayCustomSound("Line"); + CursedPlayers[killer.PlayerId] = target; + WarlockTimer.Add(killer.PlayerId, 0f); + IsCurseAndKill[killer.PlayerId] = true; + + ResetCooldowns(true, true, warlock: killer); + + return; + } + + if (IsCurseAndKill[killer.PlayerId]) killer.RpcGuardAndKill(target); + })) + { + if (KCD > 0f) return false; + + ResetCooldowns(true, true, true, killer); + + return true; + } + + return false; + } + + public override void OnPet(PlayerControl pc) + { + Curse(pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + Curse(shapeshifter); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + Curse(pc); + return false; + } + + private void Curse(PlayerControl pc) + { + IsCurseAndKill.TryAdd(pc.PlayerId, false); + + if (CursedPlayers[pc.PlayerId] != null) + { + if (!CursedPlayers[pc.PlayerId].Data.IsDead) + { + PlayerControl cp = CursedPlayers[pc.PlayerId]; + Vector2 cppos = cp.Pos(); + Dictionary cpdistance = []; + + foreach (PlayerControl p in Main.AllAlivePlayerControls) + { + if (p.PlayerId == cp.PlayerId) continue; + + if (!WarlockCanKillSelf.GetBool() && p.PlayerId == pc.PlayerId) continue; + + if (!WarlockCanKillAllies.GetBool() && p.GetCustomRole().IsImpostor()) continue; + + if (p.Is(CustomRoles.Pestilence)) continue; + + if (Pelican.IsEaten(p.PlayerId) || Medic.ProtectList.Contains(p.PlayerId)) continue; + + float dis = Vector2.Distance(cppos, p.Pos()); + cpdistance.Add(p, dis); + Logger.Info($"{p.Data?.PlayerName}'s distance: {dis}", "Warlock"); + } + + if (cpdistance.Count > 0) + { + KeyValuePair min = cpdistance.OrderBy(c => c.Value).FirstOrDefault(); + PlayerControl targetw = min.Key; + + if (cp.RpcCheckAndMurder(targetw, true)) { - if (p.PlayerId == cp.PlayerId) continue; + ResetCooldowns(true, true, true, pc); - if (!WarlockCanKillSelf.GetBool() && p.PlayerId == pc.PlayerId) continue; + targetw.SetRealKiller(pc); + Logger.Info($"{targetw.GetNameWithRole().RemoveHtmlTags()} was killed", "Warlock"); + cp.Kill(targetw); + pc.Notify(Translator.GetString("WarlockControlKill")); + RPC.PlaySoundRPC(pc.PlayerId, Sounds.KillSound); - if (!WarlockCanKillAllies.GetBool() && p.GetCustomRole().IsImpostor()) continue; - - if (p.Is(CustomRoles.Pestilence)) continue; - - if (Pelican.IsEaten(p.PlayerId) || Medic.ProtectList.Contains(p.PlayerId)) continue; - - float dis = Vector2.Distance(cppos, p.Pos()); - cpdistance.Add(p, dis); - Logger.Info($"{p.Data?.PlayerName}'s distance: {dis}", "Warlock"); - } - - if (cpdistance.Count > 0) - { - KeyValuePair min = cpdistance.OrderBy(c => c.Value).FirstOrDefault(); - PlayerControl targetw = min.Key; - - if (cp.RpcCheckAndMurder(targetw, true)) + if (FreezeAfterCurseKill.GetBool()) { - ResetCooldowns(true, true, true, pc); + float speed = Main.AllPlayerSpeed[pc.PlayerId]; + Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + pc.MarkDirtySettings(); - targetw.SetRealKiller(pc); - Logger.Info($"{targetw.GetNameWithRole().RemoveHtmlTags()} was killed", "Warlock"); - cp.Kill(targetw); - pc.Notify(Translator.GetString("WarlockControlKill")); - RPC.PlaySoundRPC(pc.PlayerId, Sounds.KillSound); - - if (FreezeAfterCurseKill.GetBool()) + LateTask.New(() => { - float speed = Main.AllPlayerSpeed[pc.PlayerId]; - Main.AllPlayerSpeed[pc.PlayerId] = Main.MinSpeed; + Main.AllPlayerSpeed[pc.PlayerId] = speed; pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[pc.PlayerId] = speed; - pc.MarkDirtySettings(); - }, FreezeDurationAfterCurseKill.GetFloat(), "Warlock Freeze"); - } + }, FreezeDurationAfterCurseKill.GetFloat(), "Warlock Freeze"); } - - if (!UsePets.GetBool()) LateTask.New(() => { pc.RpcShapeshift(pc, false); }, 1.5f, "Warlock RpcRevertShapeshift"); } - else - pc.Notify(Translator.GetString("WarlockNoTarget")); - IsCurseAndKill[pc.PlayerId] = false; - } - - CursedPlayers[pc.PlayerId] = null; - } - } - - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) - { - byte playerId = player.PlayerId; - - if (GameStates.IsInTask && WarlockTimer.ContainsKey(playerId)) - { - if (player.IsAlive()) - { - if (WarlockTimer[playerId] >= 1f) - { - player.RpcResetAbilityCooldown(); - IsCursed = false; - player.MarkDirtySettings(); - WarlockTimer.Remove(playerId); - } - else - WarlockTimer[playerId] += Time.fixedDeltaTime; + if (!UsePets.GetBool()) LateTask.New(() => { pc.RpcShapeshift(pc, false); }, 1.5f, "Warlock RpcRevertShapeshift"); } else - WarlockTimer.Remove(playerId); + pc.Notify(Translator.GetString("WarlockNoTarget")); + + IsCurseAndKill[pc.PlayerId] = false; } - } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !pc.IsAlive() || pc.inVent) return; - - float beforeKCD = KCD; - float beforeCCD = CurseCD; - - if (KCD > 0f) KCD -= Time.fixedDeltaTime; - - if (CurseCD > 0f) CurseCD -= Time.fixedDeltaTime; - - long now = Utils.TimeStamp; - - if ((Math.Abs(KCD - beforeKCD) > 0.01f || Math.Abs(beforeCCD - CurseCD) > 0.01f) && LastNotify != now) - { - LastNotify = now; - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - } - - public override void AfterMeetingTasks() - { - ResetCooldowns(true, true); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.IsModClient() && !hud) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Warlock { IsEnable: true } wl) return string.Empty; - - if (seer.PlayerId != target.PlayerId) return string.Empty; - - var sb = new StringBuilder(); - if (wl.KCD > 0f) sb.Append($"<#ffa500>{Translator.GetString("KillCooldown")}: <#ffffff>{(int)Math.Round(wl.KCD)}s"); - - if (wl.CurseCD > 0f) sb.Append($"{(sb.Length > 0 ? "\n" : string.Empty)}<#00ffa5>{Translator.GetString("CurseCooldown")}: <#ffffff>{(int)Math.Round(wl.CurseCD)}s"); - - return hud ? sb.ToString() : $"{sb}"; + CursedPlayers[pc.PlayerId] = null; } } + + public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + { + byte playerId = player.PlayerId; + + if (GameStates.IsInTask && WarlockTimer.ContainsKey(playerId)) + { + if (player.IsAlive()) + { + if (WarlockTimer[playerId] >= 1f) + { + player.RpcResetAbilityCooldown(); + IsCursed = false; + player.MarkDirtySettings(); + WarlockTimer.Remove(playerId); + } + else + WarlockTimer[playerId] += Time.fixedDeltaTime; + } + else + WarlockTimer.Remove(playerId); + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !pc.IsAlive() || pc.inVent) return; + + float beforeKCD = KCD; + float beforeCCD = CurseCD; + + if (KCD > 0f) KCD -= Time.fixedDeltaTime; + + if (CurseCD > 0f) CurseCD -= Time.fixedDeltaTime; + + long now = Utils.TimeStamp; + + if ((Math.Abs(KCD - beforeKCD) > 0.01f || Math.Abs(beforeCCD - CurseCD) > 0.01f) && LastNotify != now) + { + LastNotify = now; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + } + + public override void AfterMeetingTasks() + { + ResetCooldowns(true, true); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.IsModClient() && !hud) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Warlock { IsEnable: true } wl) return string.Empty; + + if (seer.PlayerId != target.PlayerId) return string.Empty; + + var sb = new StringBuilder(); + if (wl.KCD > 0f) sb.Append($"<#ffa500>{Translator.GetString("KillCooldown")}: <#ffffff>{(int)Math.Round(wl.KCD)}s"); + + if (wl.CurseCD > 0f) sb.Append($"{(sb.Length > 0 ? "\n" : string.Empty)}<#00ffa5>{Translator.GetString("CurseCooldown")}: <#ffffff>{(int)Math.Round(wl.CurseCD)}s"); + + return hud ? sb.ToString() : $"{sb}"; + } } \ No newline at end of file diff --git a/Roles/Impostor/Wasp.cs b/Roles/Impostor/Wasp.cs index 02dcc762c..5787ae29b 100644 --- a/Roles/Impostor/Wasp.cs +++ b/Roles/Impostor/Wasp.cs @@ -6,225 +6,224 @@ using EHR.Modules; using EHR.Patches; using Hazel; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Wasp : RoleBase { - public class Wasp : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem StingCooldown; + private static OptionItem KillDelay; + private static OptionItem EvadeKills; + private static OptionItem SwarmModeDuration; + private static OptionItem WaspDiesAfterSwarmEnd; + private static OptionItem PestControlDuration; + private static OptionItem PestControlSpeed; + private static OptionItem PestControlVision; + + public Dictionary DelayedKills; + private bool EvadedKillThisRound; + private long LastUpdate; + public HashSet MeetingKills; + private long PestControlEnd; + private long SwarmModeEnd; + private PlayerControl WaspPC; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(645350) + .AutoSetupOption(ref StingCooldown, 30, new IntegerValueRule(1, 120, 1), OptionFormat.Seconds) + .AutoSetupOption(ref KillDelay, 5, new IntegerValueRule(1, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref EvadeKills, true) + .AutoSetupOption(ref SwarmModeDuration, 15, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds, overrideParent: EvadeKills) + .AutoSetupOption(ref WaspDiesAfterSwarmEnd, true, overrideParent: EvadeKills) + .AutoSetupOption(ref PestControlDuration, 10, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds, overrideParent: EvadeKills) + .AutoSetupOption(ref PestControlSpeed, 0.5f, new FloatValueRule(0.05f, 2f, 0.05f), OptionFormat.Multiplier, overrideParent: EvadeKills) + .AutoSetupOption(ref PestControlVision, 0.2f, new FloatValueRule(0f, 1f, 0.05f), OptionFormat.Multiplier, overrideParent: EvadeKills); + } - private static OptionItem StingCooldown; - private static OptionItem KillDelay; - private static OptionItem EvadeKills; - private static OptionItem SwarmModeDuration; - private static OptionItem WaspDiesAfterSwarmEnd; - private static OptionItem PestControlDuration; - private static OptionItem PestControlSpeed; - private static OptionItem PestControlVision; + public override void Init() + { + On = false; + Instances = []; + } - public Dictionary DelayedKills; - private bool EvadedKillThisRound; - private long LastUpdate; - public HashSet MeetingKills; - private long PestControlEnd; - private long SwarmModeEnd; - private PlayerControl WaspPC; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + WaspPC = playerId.GetPlayer(); + DelayedKills = []; + MeetingKills = []; + SwarmModeEnd = 0; + PestControlEnd = 0; + EvadedKillThisRound = false; + } - public override bool IsEnable => On; + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = SwarmModeEnd == 0 ? StingCooldown.GetInt() : 0.01f; + } - public override void SetupCustomOption() + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (PestControlEnd == 0) return; + + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, PestControlVision.GetFloat()); + opt.SetFloat(FloatOptionNames.CrewLightMod, PestControlVision.GetFloat()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (DelayedKills.ContainsKey(target.PlayerId)) return false; + + if (target.HasKillButton()) + DelayedKills[target.PlayerId] = Utils.TimeStamp + KillDelay.GetInt(); + else + MeetingKills.Add(target.PlayerId); + + killer.SetKillCooldown(StingCooldown.GetInt()); + return false; + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !GameStates.IsInTask || ExileController.Instance || !DelayedKills.TryGetValue(pc.PlayerId, out long ts) || ts > Utils.TimeStamp) return; + + pc.Suicide(PlayerState.DeathReason.Stung, WaspPC); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (SwarmModeEnd == 0 && PestControlEnd == 0) return; + + long now = Utils.TimeStamp; + if (LastUpdate == now) return; + + LastUpdate = now; + + if (SwarmModeEnd != 0) { - StartSetup(645350) - .AutoSetupOption(ref StingCooldown, 30, new IntegerValueRule(1, 120, 1), OptionFormat.Seconds) - .AutoSetupOption(ref KillDelay, 5, new IntegerValueRule(1, 30, 1), OptionFormat.Seconds) - .AutoSetupOption(ref EvadeKills, true) - .AutoSetupOption(ref SwarmModeDuration, 15, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds, overrideParent: EvadeKills) - .AutoSetupOption(ref WaspDiesAfterSwarmEnd, true, overrideParent: EvadeKills) - .AutoSetupOption(ref PestControlDuration, 10, new IntegerValueRule(1, 60, 1), OptionFormat.Seconds, overrideParent: EvadeKills) - .AutoSetupOption(ref PestControlSpeed, 0.5f, new FloatValueRule(0.05f, 2f, 0.05f), OptionFormat.Multiplier, overrideParent: EvadeKills) - .AutoSetupOption(ref PestControlVision, 0.2f, new FloatValueRule(0f, 1f, 0.05f), OptionFormat.Multiplier, overrideParent: EvadeKills); - } - - public override void Init() - { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - WaspPC = playerId.GetPlayer(); - DelayedKills = []; - MeetingKills = []; - SwarmModeEnd = 0; - PestControlEnd = 0; - EvadedKillThisRound = false; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = SwarmModeEnd == 0 ? StingCooldown.GetInt() : 0.01f; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (PestControlEnd == 0) return; - - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, PestControlVision.GetFloat()); - opt.SetFloat(FloatOptionNames.CrewLightMod, PestControlVision.GetFloat()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (DelayedKills.ContainsKey(target.PlayerId)) return false; - - if (target.HasKillButton()) - DelayedKills[target.PlayerId] = Utils.TimeStamp + KillDelay.GetInt(); - else - MeetingKills.Add(target.PlayerId); - - killer.SetKillCooldown(StingCooldown.GetInt()); - return false; - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !GameStates.IsInTask || ExileController.Instance || !DelayedKills.TryGetValue(pc.PlayerId, out long ts) || ts > Utils.TimeStamp) return; - - pc.Suicide(PlayerState.DeathReason.Stung, WaspPC); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (SwarmModeEnd == 0 && PestControlEnd == 0) return; - - long now = Utils.TimeStamp; - if (LastUpdate == now) return; - - LastUpdate = now; - - if (SwarmModeEnd != 0) + if (SwarmModeEnd <= now) { - if (SwarmModeEnd <= now) - { - SwarmModeEnd = 0; - Utils.SendRPC(CustomRPC.SyncRoleData, WaspPC.PlayerId, SwarmModeEnd); - - if (WaspDiesAfterSwarmEnd.GetBool()) pc.Suicide(); - else - { - pc.ResetKillCooldown(); - pc.SyncSettings(); - pc.SetKillCooldown(StingCooldown.GetInt()); - } - } - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - if (PestControlEnd != 0 && PestControlEnd <= now) - { - PestControlEnd = 0; - Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - pc.MarkDirtySettings(); - } - } - - public override void OnReportDeadBody() - { - foreach (byte id in DelayedKills.Keys) - { - PlayerControl player = id.GetPlayer(); - if (player == null || !player.IsAlive()) continue; - - player.Suicide(PlayerState.DeathReason.Stung, WaspPC); - } - - DelayedKills.Clear(); - - if (WaspPC == null || !WaspPC.IsAlive()) - MeetingKills.Clear(); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (!EvadeKills.GetBool() || EvadedKillThisRound) return true; - - if (IRandom.Instance.Next(2) == 0) - { - SwarmModeEnd = Utils.TimeStamp + SwarmModeDuration.GetInt(); + SwarmModeEnd = 0; Utils.SendRPC(CustomRPC.SyncRoleData, WaspPC.PlayerId, SwarmModeEnd); - target.SyncSettings(); - target.SetKillCooldown(0.01f); - } - else - { - PestControlEnd = Utils.TimeStamp + PestControlDuration.GetInt(); - Main.AllPlayerSpeed[target.PlayerId] = PestControlSpeed.GetFloat(); - target.MarkDirtySettings(); + + if (WaspDiesAfterSwarmEnd.GetBool()) pc.Suicide(); + else + { + pc.ResetKillCooldown(); + pc.SyncSettings(); + pc.SetKillCooldown(StingCooldown.GetInt()); + } } - EvadedKillThisRound = true; - return false; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } - public override bool CanUseKillButton(PlayerControl pc) + if (PestControlEnd != 0 && PestControlEnd <= now) { - return !EvadedKillThisRound || SwarmModeEnd != 0; - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return !EvadedKillThisRound; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return !EvadedKillThisRound; - } - - public override void AfterMeetingTasks() - { - EvadedKillThisRound = false; - MeetingKills.Clear(); - } - - public static void OnExile(byte[] exileIds) - { - try - { - HashSet waspDeathList = []; - - foreach (Wasp instance in Instances) waspDeathList.UnionWith(instance.GetStungPlayers(exileIds)); - waspDeathList.ExceptWith(Main.AfterMeetingDeathPlayers.Keys); - - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Stung, [.. waspDeathList]); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public static string GetStungMark(byte target) - { - return Instances.Any(x => x.MeetingKills.Contains(target)) ? Utils.ColorString(Palette.ImpostorRed, "\u25c0") : string.Empty; - } - - HashSet GetStungPlayers(byte[] exileIds) => WaspPC == null || !WaspPC.IsAlive() || Main.AfterMeetingDeathPlayers.ContainsKey(WaspPC.PlayerId) || exileIds.Any(x => x == WaspPC.PlayerId) ? [] : MeetingKills; - - public void ReceiveRPC(MessageReader reader) - { - SwarmModeEnd = long.Parse(reader.ReadString()); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != WaspPC.PlayerId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || meeting || SwarmModeEnd == 0) return string.Empty; - return string.Format(Translator.GetString("Wasp.SwarmModeSuffix"), SwarmModeEnd - Utils.TimeStamp); + PestControlEnd = 0; + Main.AllPlayerSpeed[pc.PlayerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + pc.MarkDirtySettings(); } } + + public override void OnReportDeadBody() + { + foreach (byte id in DelayedKills.Keys) + { + PlayerControl player = id.GetPlayer(); + if (player == null || !player.IsAlive()) continue; + + player.Suicide(PlayerState.DeathReason.Stung, WaspPC); + } + + DelayedKills.Clear(); + + if (WaspPC == null || !WaspPC.IsAlive()) + MeetingKills.Clear(); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (!EvadeKills.GetBool() || EvadedKillThisRound) return true; + + if (IRandom.Instance.Next(2) == 0) + { + SwarmModeEnd = Utils.TimeStamp + SwarmModeDuration.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, WaspPC.PlayerId, SwarmModeEnd); + target.SyncSettings(); + target.SetKillCooldown(0.01f); + } + else + { + PestControlEnd = Utils.TimeStamp + PestControlDuration.GetInt(); + Main.AllPlayerSpeed[target.PlayerId] = PestControlSpeed.GetFloat(); + target.MarkDirtySettings(); + } + + EvadedKillThisRound = true; + return false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !EvadedKillThisRound || SwarmModeEnd != 0; + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return !EvadedKillThisRound; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return !EvadedKillThisRound; + } + + public override void AfterMeetingTasks() + { + EvadedKillThisRound = false; + MeetingKills.Clear(); + } + + public static void OnExile(byte[] exileIds) + { + try + { + HashSet waspDeathList = []; + + foreach (Wasp instance in Instances) waspDeathList.UnionWith(instance.GetStungPlayers(exileIds)); + waspDeathList.ExceptWith(Main.AfterMeetingDeathPlayers.Keys); + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Stung, [.. waspDeathList]); + } + catch (Exception e) + { + Utils.ThrowException(e); + } + } + + public static string GetStungMark(byte target) + { + return Instances.Any(x => x.MeetingKills.Contains(target)) ? Utils.ColorString(Palette.ImpostorRed, "\u25c0") : string.Empty; + } + + HashSet GetStungPlayers(byte[] exileIds) => WaspPC == null || !WaspPC.IsAlive() || Main.AfterMeetingDeathPlayers.ContainsKey(WaspPC.PlayerId) || exileIds.Any(x => x == WaspPC.PlayerId) ? [] : MeetingKills; + + public void ReceiveRPC(MessageReader reader) + { + SwarmModeEnd = long.Parse(reader.ReadString()); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != WaspPC.PlayerId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || meeting || SwarmModeEnd == 0) return string.Empty; + return string.Format(Translator.GetString("Wasp.SwarmModeSuffix"), SwarmModeEnd - Utils.TimeStamp); + } } \ No newline at end of file diff --git a/Roles/Impostor/Wildling.cs b/Roles/Impostor/Wildling.cs index f2fc9b8c8..d01c9df7e 100644 --- a/Roles/Impostor/Wildling.cs +++ b/Roles/Impostor/Wildling.cs @@ -6,185 +6,184 @@ using EHR.Neutral; using Hazel; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Wildling : RoleBase { - public class Wildling : RoleBase + private const int Id = 4700; + public static List PlayerIdList = []; + + private static OptionItem ProtectDurationOpt; + private static OptionItem CanVentOpt; + public static OptionItem CanShapeshiftOpt; + private static OptionItem ShapeshiftCDOpt; + private static OptionItem ShapeshiftDurOpt; + + private bool CanShapeshift; + private bool CanVent; + private bool HasImpostorVision; + private float KillCooldown; + + private float ProtectionDuration; + private float ShapeshiftCD; + private float ShapeshiftDur; + + private long TimeStamp; + + private CustomRoles UsedRole; + + public override bool IsEnable => PlayerIdList.Count > 0; + + private bool InProtect => TimeStamp > Utils.TimeStamp; + + public override void SetupCustomOption() { - private const int Id = 4700; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Wildling); - private static OptionItem ProtectDurationOpt; - private static OptionItem CanVentOpt; - public static OptionItem CanShapeshiftOpt; - private static OptionItem ShapeshiftCDOpt; - private static OptionItem ShapeshiftDurOpt; + ProtectDurationOpt = new FloatOptionItem(Id + 14, "BKProtectDuration", new(1f, 30f, 1f), 15f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]) + .SetValueFormat(OptionFormat.Seconds); - private bool CanShapeshift; - private bool CanVent; - private bool HasImpostorVision; - private float KillCooldown; + CanVentOpt = new BooleanOptionItem(Id + 15, "CanVent", true, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]); - private float ProtectionDuration; - private float ShapeshiftCD; - private float ShapeshiftDur; + CanShapeshiftOpt = new BooleanOptionItem(Id + 16, "CanShapeshift", false, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]); - private long TimeStamp; + ShapeshiftCDOpt = new FloatOptionItem(Id + 17, "ShapeshiftCooldown", new(1f, 60f, 1f), 30f, TabGroup.ImpostorRoles) + .SetParent(CanShapeshiftOpt) + .SetValueFormat(OptionFormat.Seconds); - private CustomRoles UsedRole; + ShapeshiftDurOpt = new FloatOptionItem(Id + 18, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(CanShapeshiftOpt) + .SetValueFormat(OptionFormat.Seconds); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + TimeStamp = 0; + } - private bool InProtect => TimeStamp > Utils.TimeStamp; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + TimeStamp = 0; - public override void SetupCustomOption() + UsedRole = Main.PlayerStates[playerId].MainRole; + + switch (UsedRole) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Wildling); - - ProtectDurationOpt = new FloatOptionItem(Id + 14, "BKProtectDuration", new(1f, 30f, 1f), 15f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]) - .SetValueFormat(OptionFormat.Seconds); - - CanVentOpt = new BooleanOptionItem(Id + 15, "CanVent", true, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]); - - CanShapeshiftOpt = new BooleanOptionItem(Id + 16, "CanShapeshift", false, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Wildling]); - - ShapeshiftCDOpt = new FloatOptionItem(Id + 17, "ShapeshiftCooldown", new(1f, 60f, 1f), 30f, TabGroup.ImpostorRoles) - .SetParent(CanShapeshiftOpt) - .SetValueFormat(OptionFormat.Seconds); - - ShapeshiftDurOpt = new FloatOptionItem(Id + 18, "ShapeshiftDuration", new(1f, 30f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(CanShapeshiftOpt) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - PlayerIdList = []; - TimeStamp = 0; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - TimeStamp = 0; - - UsedRole = Main.PlayerStates[playerId].MainRole; - - switch (UsedRole) - { - case CustomRoles.Wildling: - ProtectionDuration = ProtectDurationOpt.GetFloat(); - CanVent = CanVentOpt.GetBool(); - CanShapeshift = CanShapeshiftOpt.GetBool(); - ShapeshiftCD = ShapeshiftCDOpt.GetFloat(); - ShapeshiftDur = ShapeshiftDurOpt.GetFloat(); - HasImpostorVision = true; - KillCooldown = DefaultKillCooldown; - break; - case CustomRoles.BloodKnight: - ProtectionDuration = BloodKnight.ProtectDuration.GetFloat(); - CanVent = BloodKnight.CanVent.GetBool(); - CanShapeshift = false; - ShapeshiftCD = 0; - ShapeshiftDur = 0; - HasImpostorVision = BloodKnight.HasImpostorVision.GetBool(); - KillCooldown = BloodKnight.KillCooldown.GetFloat(); - break; - } - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision); - - if (CanShapeshift) - { - AURoleOptions.ShapeshifterCooldown = ShapeshiftCD; - AURoleOptions.ShapeshifterDuration = ShapeshiftDur; - } - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent; - } - - private void SendRPC(byte playerId) - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetBkTimer, SendOption.Reliable); - writer.Write(playerId); - writer.Write(TimeStamp.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte PlayerId = reader.ReadByte(); - string Time = reader.ReadString(); - - if (Main.PlayerStates[PlayerId].Role is not Wildling wl) return; - - wl.TimeStamp = long.Parse(Time); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (killer.PlayerId == target.PlayerId) return; - - TimeStamp = Utils.TimeStamp + (long)ProtectionDuration; - SendRPC(killer.PlayerId); - killer.Notify(Translator.GetString("BKInProtect")); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (InProtect) - { - killer.RpcGuardAndKill(target); - target.Notify(Translator.GetString("BKOffsetKill")); - return false; - } - - return true; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask) return; - - if (TimeStamp < Utils.TimeStamp && TimeStamp != 0) - { - TimeStamp = 0; - pc.Notify(Translator.GetString("BKProtectOut")); - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || seer == null || !GameStates.IsInTask || !PlayerControl.LocalPlayer.IsAlive()) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Wildling wl) return string.Empty; - - var str = new StringBuilder(); - - if (wl.InProtect) - { - long remainTime = wl.TimeStamp - Utils.GetTimeStamp(DateTime.Now); - str.Append(string.Format(Translator.GetString("BKSkillTimeRemain"), remainTime)); - } - else - str.Append(Translator.GetString("BKSkillNotice")); - - return str.ToString(); + case CustomRoles.Wildling: + ProtectionDuration = ProtectDurationOpt.GetFloat(); + CanVent = CanVentOpt.GetBool(); + CanShapeshift = CanShapeshiftOpt.GetBool(); + ShapeshiftCD = ShapeshiftCDOpt.GetFloat(); + ShapeshiftDur = ShapeshiftDurOpt.GetFloat(); + HasImpostorVision = true; + KillCooldown = DefaultKillCooldown; + break; + case CustomRoles.BloodKnight: + ProtectionDuration = BloodKnight.ProtectDuration.GetFloat(); + CanVent = BloodKnight.CanVent.GetBool(); + CanShapeshift = false; + ShapeshiftCD = 0; + ShapeshiftDur = 0; + HasImpostorVision = BloodKnight.HasImpostorVision.GetBool(); + KillCooldown = BloodKnight.KillCooldown.GetFloat(); + break; } } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision); + + if (CanShapeshift) + { + AURoleOptions.ShapeshifterCooldown = ShapeshiftCD; + AURoleOptions.ShapeshifterDuration = ShapeshiftDur; + } + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent; + } + + private void SendRPC(byte playerId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetBkTimer, SendOption.Reliable); + writer.Write(playerId); + writer.Write(TimeStamp.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + string Time = reader.ReadString(); + + if (Main.PlayerStates[PlayerId].Role is not Wildling wl) return; + + wl.TimeStamp = long.Parse(Time); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (killer.PlayerId == target.PlayerId) return; + + TimeStamp = Utils.TimeStamp + (long)ProtectionDuration; + SendRPC(killer.PlayerId); + killer.Notify(Translator.GetString("BKInProtect")); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (InProtect) + { + killer.RpcGuardAndKill(target); + target.Notify(Translator.GetString("BKOffsetKill")); + return false; + } + + return true; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask) return; + + if (TimeStamp < Utils.TimeStamp && TimeStamp != 0) + { + TimeStamp = 0; + pc.Notify(Translator.GetString("BKProtectOut")); + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || seer == null || !GameStates.IsInTask || !PlayerControl.LocalPlayer.IsAlive()) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Wildling wl) return string.Empty; + + var str = new StringBuilder(); + + if (wl.InProtect) + { + long remainTime = wl.TimeStamp - Utils.GetTimeStamp(DateTime.Now); + str.Append(string.Format(Translator.GetString("BKSkillTimeRemain"), remainTime)); + } + else + str.Append(Translator.GetString("BKSkillNotice")); + + return str.ToString(); + } } \ No newline at end of file diff --git a/Roles/Impostor/Witch.cs b/Roles/Impostor/Witch.cs index 76990723c..c8044d325 100644 --- a/Roles/Impostor/Witch.cs +++ b/Roles/Impostor/Witch.cs @@ -8,273 +8,272 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class Witch : RoleBase { - public class Witch : RoleBase + private const int Id = 2000; + + public static readonly string[] SwitchTriggerText = + [ + "TriggerKill", + "TriggerVent", + "TriggerDouble" + ]; + + public static List PlayerIdList = []; + + private static OptionItem ModeSwitchAction; + private static SwitchTrigger NowSwitchTrigger; + + private bool IsHM; + private List SpelledPlayer = []; + + private bool SpellMode; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 2000; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Witch); - public static readonly string[] SwitchTriggerText = - [ - "TriggerKill", - "TriggerVent", - "TriggerDouble" - ]; + ModeSwitchAction = new StringOptionItem(Id + 10, "WitchModeSwitchAction", SwitchTriggerText, 2, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Witch]); + } - public static List PlayerIdList = []; + public override void Init() + { + PlayerIdList = []; + SpellMode = false; + SpelledPlayer = []; + IsHM = false; + } - private static OptionItem ModeSwitchAction; - private static SwitchTrigger NowSwitchTrigger; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + SpellMode = false; + SpelledPlayer = []; - private bool IsHM; - private List SpelledPlayer = []; + IsHM = Main.PlayerStates[playerId].MainRole == CustomRoles.HexMaster; + NowSwitchTrigger = IsHM ? (SwitchTrigger)HexMaster.ModeSwitchAction.GetValue() : (SwitchTrigger)ModeSwitchAction.GetValue(); + } - private bool SpellMode; + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return true; + } - public override bool IsEnable => PlayerIdList.Count > 0; + private static void SendRPC(bool doSpell, byte witchId, byte target = 255, bool spellMode = false) + { + if (!Utils.DoRPC) return; - public override void SetupCustomOption() + if (doSpell) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.Witch); - - ModeSwitchAction = new StringOptionItem(Id + 10, "WitchModeSwitchAction", SwitchTriggerText, 2, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Witch]); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DoSpell, SendOption.Reliable); + writer.Write(witchId); + writer.Write(target); + AmongUsClient.Instance.FinishRpcImmediately(writer); } - - public override void Init() + else { - PlayerIdList = []; - SpellMode = false; - SpelledPlayer = []; - IsHM = false; + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillOrSpell, SendOption.Reliable); + writer.Write(witchId); + writer.Write(spellMode); + AmongUsClient.Instance.FinishRpcImmediately(writer); } + } - public override void Add(byte playerId) + public static void ReceiveRPC(MessageReader reader, bool doSpell) + { + byte witch = reader.ReadByte(); + if (Main.PlayerStates[witch].Role is not Witch wc) return; + + if (doSpell) { - PlayerIdList.Add(playerId); - SpellMode = false; - SpelledPlayer = []; + byte spelledId = reader.ReadByte(); - IsHM = Main.PlayerStates[playerId].MainRole == CustomRoles.HexMaster; - NowSwitchTrigger = IsHM ? (SwitchTrigger)HexMaster.ModeSwitchAction.GetValue() : (SwitchTrigger)ModeSwitchAction.GetValue(); + if (spelledId != 255) + wc.SpelledPlayer.Add(spelledId); + else + wc.SpelledPlayer.Clear(); } + else + wc.SpellMode = reader.ReadBoolean(); + } - public override bool CanUseImpostorVentButton(PlayerControl pc) + public override void OnPet(PlayerControl pc) + { + if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) return; + + SwitchMode(pc.PlayerId); + } + + private void SwitchSpellMode(byte playerId, bool kill) + { + bool needSwitch = NowSwitchTrigger switch { + SwitchTrigger.Kill => kill, + SwitchTrigger.Vent => !kill, + _ => false + }; + + if (needSwitch) SwitchMode(playerId); + } + + private void SwitchMode(byte playerId) + { + SpellMode = !SpellMode; + SendRPC(false, playerId); + PlayerControl pc = Utils.GetPlayerById(playerId); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + private bool IsSpelled(byte target) + { + return SpelledPlayer.Contains(target); + } + + private void SetSpelled(PlayerControl killer, PlayerControl target) + { + if (!IsSpelled(target.PlayerId)) + { + SpelledPlayer.Add(target.PlayerId); + SendRPC(true, killer.PlayerId, target.PlayerId); + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("Curse"); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + } + } + + public static void RemoveSpelledPlayer() + { + foreach (byte witch in PlayerIdList) + { + if (Main.PlayerStates[witch].Role is not Witch wc) continue; + + wc.SpelledPlayer.Clear(); + SendRPC(true, witch); + } + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Medic.ProtectList.Contains(target.PlayerId)) return false; + + if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) return killer.CheckDoubleTrigger(target, () => SetSpelled(killer, target)); + + if (!SpellMode) + { + SwitchSpellMode(killer.PlayerId, true); return true; } - private static void SendRPC(bool doSpell, byte witchId, byte target = 255, bool spellMode = false) + SetSpelled(killer, target); + + SwitchSpellMode(killer.PlayerId, true); + + return false; + } + + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + { + try { - if (!Utils.DoRPC) return; + if (deathReason != PlayerState.DeathReason.Vote) return; - if (doSpell) + foreach (byte id in exileIds) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.DoSpell, SendOption.Reliable); - writer.Write(witchId); - writer.Write(target); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - else - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetKillOrSpell, SendOption.Reliable); - writer.Write(witchId); - writer.Write(spellMode); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - } + if (PlayerIdList.Contains(id)) + { + if (Main.PlayerStates[id].Role is not Witch wc) continue; - public static void ReceiveRPC(MessageReader reader, bool doSpell) - { - byte witch = reader.ReadByte(); - if (Main.PlayerStates[witch].Role is not Witch wc) return; - - if (doSpell) - { - byte spelledId = reader.ReadByte(); - - if (spelledId != 255) - wc.SpelledPlayer.Add(spelledId); - else wc.SpelledPlayer.Clear(); - } - else - wc.SpellMode = reader.ReadBoolean(); - } - - public override void OnPet(PlayerControl pc) - { - if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) return; - - SwitchMode(pc.PlayerId); - } - - private void SwitchSpellMode(byte playerId, bool kill) - { - bool needSwitch = NowSwitchTrigger switch - { - SwitchTrigger.Kill => kill, - SwitchTrigger.Vent => !kill, - _ => false - }; - - if (needSwitch) SwitchMode(playerId); - } - - private void SwitchMode(byte playerId) - { - SpellMode = !SpellMode; - SendRPC(false, playerId); - PlayerControl pc = Utils.GetPlayerById(playerId); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - private bool IsSpelled(byte target) - { - return SpelledPlayer.Contains(target); - } - - private void SetSpelled(PlayerControl killer, PlayerControl target) - { - if (!IsSpelled(target.PlayerId)) - { - SpelledPlayer.Add(target.PlayerId); - SendRPC(true, killer.PlayerId, target.PlayerId); - killer.SetKillCooldown(); - killer.RPCPlayCustomSound("Curse"); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - } - } - - public static void RemoveSpelledPlayer() - { - foreach (byte witch in PlayerIdList) - { - if (Main.PlayerStates[witch].Role is not Witch wc) continue; - - wc.SpelledPlayer.Clear(); - SendRPC(true, witch); - } - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Medic.ProtectList.Contains(target.PlayerId)) return false; - - if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) return killer.CheckDoubleTrigger(target, () => SetSpelled(killer, target)); - - if (!SpellMode) - { - SwitchSpellMode(killer.PlayerId, true); - return true; - } - - SetSpelled(killer, target); - - SwitchSpellMode(killer.PlayerId, true); - - return false; - } - - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) - { - try - { - if (deathReason != PlayerState.DeathReason.Vote) return; - - foreach (byte id in exileIds) - { - if (PlayerIdList.Contains(id)) - { - if (Main.PlayerStates[id].Role is not Witch wc) continue; - - wc.SpelledPlayer.Clear(); - } } - - var spelledIdList = new List(); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - foreach (byte witchId in PlayerIdList) - { - if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; - - if (Main.PlayerStates[witchId].Role is not Witch wc) continue; - - PlayerControl witch = Utils.GetPlayerById(witchId); - - if (wc.SpelledPlayer.Contains(pc.PlayerId) && witch != null && witch.IsAlive()) - { - pc.SetRealKiller(witch); - spelledIdList.Add(pc.PlayerId); - } - else - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } - } - - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. spelledIdList]); - RemoveSpelledPlayer(); } - catch (Exception e) + + var spelledIdList = new List(); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - Utils.ThrowException(e); + foreach (byte witchId in PlayerIdList) + { + if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; + + if (Main.PlayerStates[witchId].Role is not Witch wc) continue; + + PlayerControl witch = Utils.GetPlayerById(witchId); + + if (wc.SpelledPlayer.Contains(pc.PlayerId) && witch != null && witch.IsAlive()) + { + pc.SetRealKiller(witch); + spelledIdList.Add(pc.PlayerId); + } + else + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); + } } + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. spelledIdList]); + RemoveSpelledPlayer(); } - - public static string GetSpelledMark(byte target, bool isMeeting) + catch (Exception e) { - if (!isMeeting) return string.Empty; - - foreach (byte id in PlayerIdList) - if (Main.PlayerStates[id].Role is Witch { IsEnable: true } wc && wc.IsSpelled(target)) - return Utils.ColorString(wc.IsHM ? Utils.GetRoleColor(CustomRoles.HexMaster) : Palette.ImpostorRed, "†"); - - return string.Empty; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || meeting || seer.PlayerId != target.PlayerId || !seer.Is(CustomRoles.Witch) || (seer.IsModClient() && !hud)) return string.Empty; - - var str = new StringBuilder(); - - if (hud) - str.Append($"{GetString("WitchCurrentMode")}: "); - else - str.Append($"{GetString("Mode")}: "); - - if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) - str.Append(GetString("WitchModeDouble")); - else - str.Append(SpellMode ? GetString("WitchModeSpell") : GetString("WitchModeKill")); - - return str.ToString(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - if (SpellMode && NowSwitchTrigger != SwitchTrigger.DoubleTrigger) - hud.KillButton.OverrideText(GetString("WitchSpellButtonText")); - else - hud.KillButton.OverrideText(GetString("KillButtonText")); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (!AmongUsClient.Instance.AmHost) return; - - if (PlayerIdList.Contains(pc.PlayerId)) - if (NowSwitchTrigger is SwitchTrigger.Vent) - SwitchSpellMode(pc.PlayerId, false); - } - - private enum SwitchTrigger - { - Kill, - Vent, - DoubleTrigger + Utils.ThrowException(e); } } + + public static string GetSpelledMark(byte target, bool isMeeting) + { + if (!isMeeting) return string.Empty; + + foreach (byte id in PlayerIdList) + if (Main.PlayerStates[id].Role is Witch { IsEnable: true } wc && wc.IsSpelled(target)) + return Utils.ColorString(wc.IsHM ? Utils.GetRoleColor(CustomRoles.HexMaster) : Palette.ImpostorRed, "†"); + + return string.Empty; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || meeting || seer.PlayerId != target.PlayerId || !seer.Is(CustomRoles.Witch) || (seer.IsModClient() && !hud)) return string.Empty; + + var str = new StringBuilder(); + + if (hud) + str.Append($"{GetString("WitchCurrentMode")}: "); + else + str.Append($"{GetString("Mode")}: "); + + if (NowSwitchTrigger == SwitchTrigger.DoubleTrigger) + str.Append(GetString("WitchModeDouble")); + else + str.Append(SpellMode ? GetString("WitchModeSpell") : GetString("WitchModeKill")); + + return str.ToString(); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + if (SpellMode && NowSwitchTrigger != SwitchTrigger.DoubleTrigger) + hud.KillButton.OverrideText(GetString("WitchSpellButtonText")); + else + hud.KillButton.OverrideText(GetString("KillButtonText")); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (!AmongUsClient.Instance.AmHost) return; + + if (PlayerIdList.Contains(pc.PlayerId)) + if (NowSwitchTrigger is SwitchTrigger.Vent) + SwitchSpellMode(pc.PlayerId, false); + } + + private enum SwitchTrigger + { + Kill, + Vent, + DoubleTrigger + } } \ No newline at end of file diff --git a/Roles/Impostor/YinYanger.cs b/Roles/Impostor/YinYanger.cs index 7015c8969..5c70b9ca8 100644 --- a/Roles/Impostor/YinYanger.cs +++ b/Roles/Impostor/YinYanger.cs @@ -4,151 +4,150 @@ using Hazel; using static EHR.Options; using static EHR.Utils; -namespace EHR.Impostor +namespace EHR.Impostor; + +public class YinYanger : RoleBase { - public class YinYanger : RoleBase + private const int Id = 642870; + + private static OptionItem YinYangCD; + private static OptionItem KCD; + private List YinYangedPlayers = []; + private byte YinYangerId = byte.MaxValue; + + // ReSharper disable once InconsistentNaming + private PlayerControl YinYanger_ => GetPlayerById(YinYangerId); + + public override bool IsEnable => YinYangerId != byte.MaxValue; + + public override void SetupCustomOption() { - private const int Id = 642870; + SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.YinYanger); - private static OptionItem YinYangCD; - private static OptionItem KCD; - private List YinYangedPlayers = []; - private byte YinYangerId = byte.MaxValue; + YinYangCD = new FloatOptionItem(Id + 5, "YinYangCD", new(0f, 60f, 2.5f), 12.5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) + .SetValueFormat(OptionFormat.Seconds); - // ReSharper disable once InconsistentNaming - private PlayerControl YinYanger_ => GetPlayerById(YinYangerId); + KCD = new FloatOptionItem(Id + 6, "KillCooldown", new(0f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) + .SetValueFormat(OptionFormat.Seconds); + } - public override bool IsEnable => YinYangerId != byte.MaxValue; + public override void Init() + { + YinYangerId = byte.MaxValue; + YinYangedPlayers = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + YinYangerId = playerId; + YinYangedPlayers = []; + } + + public override void SetKillCooldown(byte playerId) + { + Main.AllPlayerKillCooldown[playerId] = YinYangedPlayers.Count == 2 ? KCD.GetFloat() : YinYangCD.GetFloat(); + } + + private void SendRPC(bool isClear, byte playerId = byte.MaxValue) + { + if (!DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncYinYanger, SendOption.Reliable); + writer.Write(YinYangerId); + writer.Write(isClear); + if (!isClear) writer.Write(playerId); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte yyId = reader.ReadByte(); + if (Main.PlayerStates[yyId].Role is not YinYanger yy) return; + + bool isClear = reader.ReadBoolean(); + + if (!isClear) { - SetupRoleOptions(Id, TabGroup.ImpostorRoles, CustomRoles.YinYanger); + byte playerId = reader.ReadByte(); + yy.YinYangedPlayers.Add(playerId); + } + else + yy.YinYangedPlayers.Clear(); + } - YinYangCD = new FloatOptionItem(Id + 5, "YinYangCD", new(0f, 60f, 2.5f), 12.5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) - .SetValueFormat(OptionFormat.Seconds); + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return false; - KCD = new FloatOptionItem(Id + 6, "KillCooldown", new(0f, 60f, 2.5f), 25f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.YinYanger]) - .SetValueFormat(OptionFormat.Seconds); + if (killer == null || target == null || !killer.Is(CustomRoles.YinYanger)) return false; + + if (YinYangedPlayers.Count == 2) return true; + + if (YinYangedPlayers.Contains(target.PlayerId)) return false; + + YinYangedPlayers.Add(target.PlayerId); + SendRPC(false, target.PlayerId); + + if (YinYangedPlayers.Count == 2) + { + killer.ResetKillCooldown(); + killer.SyncSettings(); } - public override void Init() + killer.SetKillCooldown(); + return false; + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + YinYangedPlayers.Clear(); + YinYanger_?.ResetKillCooldown(); + SendRPC(true); + } + + public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + { + if (!GameStates.IsInTask) return; + + foreach (KeyValuePair state in Main.PlayerStates) { - YinYangerId = byte.MaxValue; - YinYangedPlayers = []; - } + if (state.Value.Role is not YinYanger { IsEnable: true, YinYangedPlayers.Count: 2 } yy) continue; - public override void Add(byte playerId) - { - YinYangerId = playerId; - YinYangedPlayers = []; - } + PlayerControl yyPc = yy.YinYanger_; + PlayerControl pc1 = GetPlayerById(yy.YinYangedPlayers[0]); + PlayerControl pc2 = GetPlayerById(yy.YinYangedPlayers[1]); - public override void SetKillCooldown(byte playerId) - { - Main.AllPlayerKillCooldown[playerId] = YinYangedPlayers.Count == 2 ? KCD.GetFloat() : YinYangCD.GetFloat(); - } + if (pc1 == null || pc2 == null || yyPc == null) return; - private void SendRPC(bool isClear, byte playerId = byte.MaxValue) - { - if (!DoRPC) return; + if (!pc1.IsAlive() || !pc2.IsAlive() || !yyPc.IsAlive()) return; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncYinYanger, SendOption.Reliable); - writer.Write(YinYangerId); - writer.Write(isClear); - if (!isClear) writer.Write(playerId); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte yyId = reader.ReadByte(); - if (Main.PlayerStates[yyId].Role is not YinYanger yy) return; - - bool isClear = reader.ReadBoolean(); - - if (!isClear) + if (Vector2.Distance(pc1.Pos(), pc2.Pos()) <= 2f) { - byte playerId = reader.ReadByte(); - yy.YinYangedPlayers.Add(playerId); + if (!yyPc.RpcCheckAndMurder(pc1, true) + || !yyPc.RpcCheckAndMurder(pc2, true)) + return; + + pc1.Suicide(PlayerState.DeathReason.YinYanged, yyPc); + pc2.Suicide(PlayerState.DeathReason.YinYanged, yyPc); } - else - yy.YinYangedPlayers.Clear(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return false; - - if (killer == null || target == null || !killer.Is(CustomRoles.YinYanger)) return false; - - if (YinYangedPlayers.Count == 2) return true; - - if (YinYangedPlayers.Contains(target.PlayerId)) return false; - - YinYangedPlayers.Add(target.PlayerId); - SendRPC(false, target.PlayerId); - - if (YinYangedPlayers.Count == 2) - { - killer.ResetKillCooldown(); - killer.SyncSettings(); - } - - killer.SetKillCooldown(); - return false; - } - - public override void OnReportDeadBody() - { - if (!IsEnable) return; - - YinYangedPlayers.Clear(); - YinYanger_?.ResetKillCooldown(); - SendRPC(true); - } - - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) - { - if (!GameStates.IsInTask) return; - - foreach (KeyValuePair state in Main.PlayerStates) - { - if (state.Value.Role is not YinYanger { IsEnable: true, YinYangedPlayers.Count: 2 } yy) continue; - - PlayerControl yyPc = yy.YinYanger_; - PlayerControl pc1 = GetPlayerById(yy.YinYangedPlayers[0]); - PlayerControl pc2 = GetPlayerById(yy.YinYangedPlayers[1]); - - if (pc1 == null || pc2 == null || yyPc == null) return; - - if (!pc1.IsAlive() || !pc2.IsAlive() || !yyPc.IsAlive()) return; - - if (Vector2.Distance(pc1.Pos(), pc2.Pos()) <= 2f) - { - if (!yyPc.RpcCheckAndMurder(pc1, true) - || !yyPc.RpcCheckAndMurder(pc2, true)) - return; - - pc1.Suicide(PlayerState.DeathReason.YinYanged, yyPc); - pc2.Suicide(PlayerState.DeathReason.YinYanged, yyPc); - } - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.IsModClient() && !hud) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is YinYanger { IsEnable: true } yy) - { - if (seer.PlayerId == target.PlayerId) return yy.YinYangedPlayers.Count == 2 ? $"{Translator.GetString("Mode")}: {Translator.GetString("YinYangModeNormal")}" : $"{Translator.GetString("Mode")}: {Translator.GetString("YinYangMode")} ({yy.YinYangedPlayers.Count}/2)"; - else if (yy.YinYangedPlayers.Contains(target.PlayerId)) return "\u262f"; - } - - return string.Empty; } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.IsModClient() && !hud) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is YinYanger { IsEnable: true } yy) + { + if (seer.PlayerId == target.PlayerId) return yy.YinYangedPlayers.Count == 2 ? $"{Translator.GetString("Mode")}: {Translator.GetString("YinYangModeNormal")}" : $"{Translator.GetString("Mode")}: {Translator.GetString("YinYangMode")} ({yy.YinYangedPlayers.Count}/2)"; + else if (yy.YinYangedPlayers.Contains(target.PlayerId)) return "\u262f"; + } + + return string.Empty; + } } \ No newline at end of file diff --git a/Roles/Impostor/Zombie.cs b/Roles/Impostor/Zombie.cs index 3bad22bc7..89fb1ae5a 100644 --- a/Roles/Impostor/Zombie.cs +++ b/Roles/Impostor/Zombie.cs @@ -2,80 +2,79 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class Zombie : RoleBase { - internal class Zombie : RoleBase + public static bool On; + + private static OptionItem ZombieKillCooldown; + private static OptionItem ZombieSpeedReduce; + private static OptionItem ZombieSpeedReduceInterval; + private static OptionItem ZombieInitialSpeed; + private static OptionItem ZombieMinimumSpeed; + + private long LastReduce; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupRoleOptions(16400, TabGroup.ImpostorRoles, CustomRoles.Zombie); - private static OptionItem ZombieKillCooldown; - private static OptionItem ZombieSpeedReduce; - private static OptionItem ZombieSpeedReduceInterval; - private static OptionItem ZombieInitialSpeed; - private static OptionItem ZombieMinimumSpeed; + ZombieKillCooldown = new FloatOptionItem(16410, "KillCooldown", new(0f, 180f, 2.5f), 5f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) + .SetValueFormat(OptionFormat.Seconds); - private long LastReduce; - public override bool IsEnable => On; + ZombieSpeedReduce = new FloatOptionItem(16411, "ZombieSpeedReduce", new(0.0f, 1.0f, 0.1f), 0.1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() + ZombieSpeedReduceInterval = new FloatOptionItem(16412, "ZombieSpeedReduceInterval", new(0f, 180f, 1f), 10f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) + .SetValueFormat(OptionFormat.Seconds); + + ZombieInitialSpeed = new FloatOptionItem(16413, "ZombieInitialSpeed", new(0.1f, 3f, 0.1f), 1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) + .SetValueFormat(OptionFormat.Multiplier); + + ZombieMinimumSpeed = new FloatOptionItem(16414, "ZombieMinimumSpeed", new(0.05f, 2f, 0.05f), 0.1f, TabGroup.ImpostorRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Add(byte playerId) + { + On = true; + Main.AllPlayerSpeed[playerId] = ZombieInitialSpeed.GetFloat(); + LastReduce = Utils.TimeStamp + 8; + } + + public override void Init() + { + On = false; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = ZombieKillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0.2f); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask || Main.HasJustStarted) return; + + long now = Utils.TimeStamp; + + if (now - LastReduce > ZombieSpeedReduceInterval.GetInt()) { - SetupRoleOptions(16400, TabGroup.ImpostorRoles, CustomRoles.Zombie); - - ZombieKillCooldown = new FloatOptionItem(16410, "KillCooldown", new(0f, 180f, 2.5f), 5f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) - .SetValueFormat(OptionFormat.Seconds); - - ZombieSpeedReduce = new FloatOptionItem(16411, "ZombieSpeedReduce", new(0.0f, 1.0f, 0.1f), 0.1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) - .SetValueFormat(OptionFormat.Multiplier); - - ZombieSpeedReduceInterval = new FloatOptionItem(16412, "ZombieSpeedReduceInterval", new(0f, 180f, 1f), 10f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) - .SetValueFormat(OptionFormat.Seconds); - - ZombieInitialSpeed = new FloatOptionItem(16413, "ZombieInitialSpeed", new(0.1f, 3f, 0.1f), 1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) - .SetValueFormat(OptionFormat.Multiplier); - - ZombieMinimumSpeed = new FloatOptionItem(16414, "ZombieMinimumSpeed", new(0.05f, 2f, 0.05f), 0.1f, TabGroup.ImpostorRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Zombie]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Add(byte playerId) - { - On = true; - Main.AllPlayerSpeed[playerId] = ZombieInitialSpeed.GetFloat(); - LastReduce = Utils.TimeStamp + 8; - } - - public override void Init() - { - On = false; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = ZombieKillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetFloat(FloatOptionNames.ImpostorLightMod, 0.2f); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask || Main.HasJustStarted) return; - - long now = Utils.TimeStamp; - - if (now - LastReduce > ZombieSpeedReduceInterval.GetInt()) - { - LastReduce = now; - Main.AllPlayerSpeed[pc.PlayerId] = Math.Clamp(Main.AllPlayerSpeed[pc.PlayerId] - ZombieSpeedReduce.GetFloat(), ZombieMinimumSpeed.GetFloat(), 3f); - pc.MarkDirtySettings(); - } + LastReduce = now; + Main.AllPlayerSpeed[pc.PlayerId] = Math.Clamp(Main.AllPlayerSpeed[pc.PlayerId] - ZombieSpeedReduce.GetFloat(), ZombieMinimumSpeed.GetFloat(), 3f); + pc.MarkDirtySettings(); } } } \ No newline at end of file diff --git a/Roles/Neutral/Agitater.cs b/Roles/Neutral/Agitater.cs index b0e698fc0..cb7d4b264 100644 --- a/Roles/Neutral/Agitater.cs +++ b/Roles/Neutral/Agitater.cs @@ -7,254 +7,253 @@ using Hazel; using UnityEngine; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Agitater : RoleBase { - public class Agitater : RoleBase + private const int Id = 12420; + private static List PlayerIdList = []; + + private static OptionItem BombExplodeCooldown; + private static OptionItem PassCooldown; + private static OptionItem AgitaterCanGetBombed; + private static OptionItem AgiTaterBombCooldown; + private static OptionItem AgitaterAutoReportBait; + private static OptionItem HasImpostorVision; + private bool AgitaterHasBombed; + private byte AgitaterId; + + private byte CurrentBombedPlayer = byte.MaxValue; + private long CurrentBombedPlayerTime; + private byte LastBombedPlayer = byte.MaxValue; + + public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + + public override void SetupCustomOption() { - private const int Id = 12420; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Agitater); - private static OptionItem BombExplodeCooldown; - private static OptionItem PassCooldown; - private static OptionItem AgitaterCanGetBombed; - private static OptionItem AgiTaterBombCooldown; - private static OptionItem AgitaterAutoReportBait; - private static OptionItem HasImpostorVision; - private bool AgitaterHasBombed; - private byte AgitaterId; + AgiTaterBombCooldown = new FloatOptionItem(Id + 10, "AgitaterBombCooldown", new(10f, 180f, 0.5f), 20f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]) + .SetValueFormat(OptionFormat.Seconds); - private byte CurrentBombedPlayer = byte.MaxValue; - private long CurrentBombedPlayerTime; - private byte LastBombedPlayer = byte.MaxValue; + PassCooldown = new FloatOptionItem(Id + 11, "AgitaterPassCooldown", new(0f, 5f, 0.25f), 2f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0 || Randomizer.Exists; + BombExplodeCooldown = new FloatOptionItem(Id + 12, "BombExplodeCooldown", new(1f, 60f, 1f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + AgitaterCanGetBombed = new BooleanOptionItem(Id + 13, "AgitaterCanGetBombed", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]); + + AgitaterAutoReportBait = new BooleanOptionItem(Id + 14, "AgitaterAutoReportBait", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]); + + HasImpostorVision = new BooleanOptionItem(Id + 15, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]); + } + + public override void Init() + { + PlayerIdList = []; + CurrentBombedPlayer = byte.MaxValue; + LastBombedPlayer = byte.MaxValue; + AgitaterHasBombed = false; + CurrentBombedPlayerTime = new(); + AgitaterId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + AgitaterId = playerId; + + CurrentBombedPlayer = byte.MaxValue; + LastBombedPlayer = byte.MaxValue; + AgitaterHasBombed = false; + CurrentBombedPlayerTime = new(); + } + + private void ResetBomb() + { + CurrentBombedPlayer = byte.MaxValue; + CurrentBombedPlayerTime = new(); + LastBombedPlayer = byte.MaxValue; + AgitaterHasBombed = false; + SendRPC(CurrentBombedPlayer, LastBombedPlayer); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = AgiTaterBombCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return false; + + if (AgitaterAutoReportBait.GetBool() && target.Is(CustomRoles.Bait)) return true; + + if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId))) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Agitater); + target.Kill(killer); + ResetBomb(); - AgiTaterBombCooldown = new FloatOptionItem(Id + 10, "AgitaterBombCooldown", new(10f, 180f, 0.5f), 20f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]) - .SetValueFormat(OptionFormat.Seconds); - - PassCooldown = new FloatOptionItem(Id + 11, "AgitaterPassCooldown", new(0f, 5f, 0.25f), 2f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]) - .SetValueFormat(OptionFormat.Seconds); - - BombExplodeCooldown = new FloatOptionItem(Id + 12, "BombExplodeCooldown", new(1f, 60f, 1f), 15f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]) - .SetValueFormat(OptionFormat.Seconds); - - AgitaterCanGetBombed = new BooleanOptionItem(Id + 13, "AgitaterCanGetBombed", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]); - - AgitaterAutoReportBait = new BooleanOptionItem(Id + 14, "AgitaterAutoReportBait", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]); - - HasImpostorVision = new BooleanOptionItem(Id + 15, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Agitater]); - } - - public override void Init() - { - PlayerIdList = []; - CurrentBombedPlayer = byte.MaxValue; - LastBombedPlayer = byte.MaxValue; - AgitaterHasBombed = false; - CurrentBombedPlayerTime = new(); - AgitaterId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - AgitaterId = playerId; - - CurrentBombedPlayer = byte.MaxValue; - LastBombedPlayer = byte.MaxValue; - AgitaterHasBombed = false; - CurrentBombedPlayerTime = new(); - } - - private void ResetBomb() - { - CurrentBombedPlayer = byte.MaxValue; - CurrentBombedPlayerTime = new(); - LastBombedPlayer = byte.MaxValue; - AgitaterHasBombed = false; - SendRPC(CurrentBombedPlayer, LastBombedPlayer); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = AgiTaterBombCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return false; - - if (AgitaterAutoReportBait.GetBool() && target.Is(CustomRoles.Bait)) return true; - - if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId))) - { - target.Kill(killer); - ResetBomb(); - - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); - - return false; - } - - CurrentBombedPlayer = target.PlayerId; - LastBombedPlayer = killer.PlayerId; - CurrentBombedPlayerTime = Utils.TimeStamp; - killer.RpcGuardAndKill(killer); - killer.Notify(GetString("AgitaterPassNotify")); - target.Notify(GetString("AgitaterTargetNotify")); - AgitaterHasBombed = true; - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - - LateTask.New(() => - { - if (CurrentBombedPlayer != byte.MaxValue && GameStates.IsInTask) - { - PlayerControl pc = Utils.GetPlayerById(CurrentBombedPlayer); - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (pc != null && pc.IsAlive() && killer != null) // Can be null since it's a late task - { - pc.Suicide(PlayerState.DeathReason.Bombed, Utils.GetPlayerById(PlayerIdList[0])); - Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} bombed {pc.GetNameWithRole().RemoveHtmlTags()}, bomb cd complete", "Agitater"); - ResetBomb(); - - if (pc.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - } - }, BombExplodeCooldown.GetFloat(), "AgitaterBombKill"); + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); return false; } - public override void OnReportDeadBody() + CurrentBombedPlayer = target.PlayerId; + LastBombedPlayer = killer.PlayerId; + CurrentBombedPlayerTime = Utils.TimeStamp; + killer.RpcGuardAndKill(killer); + killer.Notify(GetString("AgitaterPassNotify")); + target.Notify(GetString("AgitaterTargetNotify")); + AgitaterHasBombed = true; + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + + LateTask.New(() => { - if (!IsEnable) return; - - if (CurrentBombedPlayer == byte.MaxValue) return; - - PlayerControl target = Utils.GetPlayerById(CurrentBombedPlayer); - PlayerControl killer = Utils.GetPlayerById(PlayerIdList[0]); - if (target == null || killer == null) return; - - target.RpcExileV2(); - target.SetRealKiller(killer); - Main.PlayerStates[CurrentBombedPlayer].deathReason = PlayerState.DeathReason.Bombed; - Main.PlayerStates[CurrentBombedPlayer].SetDead(); - Utils.AfterPlayerDeathTasks(target, true); - ResetBomb(); - Logger.Info($"{killer.GetRealName()} bombed {target.GetRealName()} on report", "Agitater"); - } - - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) - { - byte playerId = player.PlayerId; - - if (!lowLoad && GameStates.IsInTask && IsEnable && AgitaterHasBombed && CurrentBombedPlayer == playerId) + if (CurrentBombedPlayer != byte.MaxValue && GameStates.IsInTask) { - if (!player.IsAlive()) - ResetBomb(); - else + PlayerControl pc = Utils.GetPlayerById(CurrentBombedPlayer); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (pc != null && pc.IsAlive() && killer != null) // Can be null since it's a late task { - Vector2 agitaterPos = player.transform.position; - Dictionary targetDistance = []; + pc.Suicide(PlayerState.DeathReason.Bombed, Utils.GetPlayerById(PlayerIdList[0])); + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} bombed {pc.GetNameWithRole().RemoveHtmlTags()}, bomb cd complete", "Agitater"); + ResetBomb(); - foreach (PlayerControl target in PlayerControl.AllPlayerControls) + if (pc.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + } + }, BombExplodeCooldown.GetFloat(), "AgitaterBombKill"); + + return false; + } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + if (CurrentBombedPlayer == byte.MaxValue) return; + + PlayerControl target = Utils.GetPlayerById(CurrentBombedPlayer); + PlayerControl killer = Utils.GetPlayerById(PlayerIdList[0]); + if (target == null || killer == null) return; + + target.RpcExileV2(); + target.SetRealKiller(killer); + Main.PlayerStates[CurrentBombedPlayer].deathReason = PlayerState.DeathReason.Bombed; + Main.PlayerStates[CurrentBombedPlayer].SetDead(); + Utils.AfterPlayerDeathTasks(target, true); + ResetBomb(); + Logger.Info($"{killer.GetRealName()} bombed {target.GetRealName()} on report", "Agitater"); + } + + public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + { + byte playerId = player.PlayerId; + + if (!lowLoad && GameStates.IsInTask && IsEnable && AgitaterHasBombed && CurrentBombedPlayer == playerId) + { + if (!player.IsAlive()) + ResetBomb(); + else + { + Vector2 agitaterPos = player.transform.position; + Dictionary targetDistance = []; + + foreach (PlayerControl target in PlayerControl.AllPlayerControls) + { + if (!target.IsAlive()) continue; + + if (target.PlayerId != playerId && target.PlayerId != LastBombedPlayer && !target.Data.IsDead) { - if (!target.IsAlive()) continue; - - if (target.PlayerId != playerId && target.PlayerId != LastBombedPlayer && !target.Data.IsDead) - { - float dis = Vector2.Distance(agitaterPos, target.transform.position); - targetDistance.Add(target.PlayerId, dis); - } + float dis = Vector2.Distance(agitaterPos, target.transform.position); + targetDistance.Add(target.PlayerId, dis); } + } - if (targetDistance.Count > 0) - { - KeyValuePair min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); - PlayerControl target = Utils.GetPlayerById(min.Key); - float KillRange = GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; - if (min.Value <= KillRange && player.CanMove && target.CanMove) PassBomb(player, target); - } + if (targetDistance.Count > 0) + { + KeyValuePair min = targetDistance.OrderBy(c => c.Value).FirstOrDefault(); + PlayerControl target = Utils.GetPlayerById(min.Key); + float KillRange = GameOptionsData.KillDistances[Mathf.Clamp(GameOptionsManager.Instance.currentNormalGameOptions.KillDistance, 0, 2)]; + if (min.Value <= KillRange && player.CanMove && target.CanMove) PassBomb(player, target); } } } + } - private void PassBomb(PlayerControl player, PlayerControl target /*, bool IsAgitater = false*/) + private void PassBomb(PlayerControl player, PlayerControl target /*, bool IsAgitater = false*/) + { + if (!IsEnable) return; + + if (!AgitaterHasBombed) return; + + if (target.Data.IsDead) return; + + long now = Utils.TimeStamp; + if (now - CurrentBombedPlayerTime < PassCooldown.GetFloat()) return; + + if (target.PlayerId == LastBombedPlayer) return; + + if (!AgitaterCanGetBombed.GetBool() && target.Is(CustomRoles.Agitater)) return; + + + if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId))) { - if (!IsEnable) return; + target.Kill(player); + ResetBomb(); - if (!AgitaterHasBombed) return; + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); - if (target.Data.IsDead) return; - - long now = Utils.TimeStamp; - if (now - CurrentBombedPlayerTime < PassCooldown.GetFloat()) return; - - if (target.PlayerId == LastBombedPlayer) return; - - if (!AgitaterCanGetBombed.GetBool() && target.Is(CustomRoles.Agitater)) return; - - - if (target.Is(CustomRoles.Pestilence) || (target.Is(CustomRoles.Veteran) && Veteran.VeteranInProtect.ContainsKey(target.PlayerId))) - { - target.Kill(player); - ResetBomb(); - - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); - - return; - } - - LastBombedPlayer = CurrentBombedPlayer; - CurrentBombedPlayer = target.PlayerId; - CurrentBombedPlayerTime = now; - - player.MarkDirtySettings(); - target.MarkDirtySettings(); - - player.Notify(GetString("AgitaterPassNotify")); - target.Notify(GetString("AgitaterTargetNotify")); - - SendRPC(CurrentBombedPlayer, LastBombedPlayer); - Logger.Msg($"{player.GetNameWithRole().RemoveHtmlTags()} passed bomb to {target.GetNameWithRole().RemoveHtmlTags()}", "Agitater Pass"); + return; } - private void SendRPC(byte newbomb, byte oldbomb) - { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RpcPassBomb, SendOption.Reliable); - writer.Write(AgitaterId); - writer.Write(newbomb); - writer.Write(oldbomb); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + LastBombedPlayer = CurrentBombedPlayer; + CurrentBombedPlayer = target.PlayerId; + CurrentBombedPlayerTime = now; - public static void ReceiveRPC(MessageReader reader) - { - byte agitaterId = reader.ReadByte(); - if (Main.PlayerStates[agitaterId].Role is not Agitater at) return; + player.MarkDirtySettings(); + target.MarkDirtySettings(); - at.CurrentBombedPlayer = reader.ReadByte(); - at.LastBombedPlayer = reader.ReadByte(); - } + player.Notify(GetString("AgitaterPassNotify")); + target.Notify(GetString("AgitaterTargetNotify")); + + SendRPC(CurrentBombedPlayer, LastBombedPlayer); + Logger.Msg($"{player.GetNameWithRole().RemoveHtmlTags()} passed bomb to {target.GetNameWithRole().RemoveHtmlTags()}", "Agitater Pass"); + } + + private void SendRPC(byte newbomb, byte oldbomb) + { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RpcPassBomb, SendOption.Reliable); + writer.Write(AgitaterId); + writer.Write(newbomb); + writer.Write(oldbomb); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte agitaterId = reader.ReadByte(); + if (Main.PlayerStates[agitaterId].Role is not Agitater at) return; + + at.CurrentBombedPlayer = reader.ReadByte(); + at.LastBombedPlayer = reader.ReadByte(); } } \ No newline at end of file diff --git a/Roles/Neutral/Amnesiac.cs b/Roles/Neutral/Amnesiac.cs index 4a62a0100..18ec17d6a 100644 --- a/Roles/Neutral/Amnesiac.cs +++ b/Roles/Neutral/Amnesiac.cs @@ -5,270 +5,269 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Amnesiac : RoleBase { - public class Amnesiac : RoleBase + private const int Id = 35000; + private static List Instances = []; + public static HashSet WasAmnesiac = []; + + private static OptionItem RememberCooldown; + private static OptionItem CanRememberCrewPower; + private static OptionItem IncompatibleNeutralMode; + public static OptionItem RememberMode; + public static OptionItem CanVent; + private static OptionItem VentCooldown; + private static OptionItem VentDuration; + + private static readonly CustomRoles[] AmnesiacIncompatibleNeutralMode = + [ + CustomRoles.Amnesiac, + CustomRoles.Pursuer, + CustomRoles.Totocalcio, + CustomRoles.Maverick + ]; + + private static readonly string[] RememberModes = + [ + "AmnesiacRM.ByReportingBody", + "AmnesiacRM.ByKillButton" + ]; + + private byte AmnesiacId; + + public override bool IsEnable => Instances.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 35000; - private static List Instances = []; - public static HashSet WasAmnesiac = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Amnesiac); - private static OptionItem RememberCooldown; - private static OptionItem CanRememberCrewPower; - private static OptionItem IncompatibleNeutralMode; - public static OptionItem RememberMode; - public static OptionItem CanVent; - private static OptionItem VentCooldown; - private static OptionItem VentDuration; + RememberMode = new StringOptionItem(Id + 9, "RememberMode", RememberModes, 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - private static readonly CustomRoles[] AmnesiacIncompatibleNeutralMode = - [ - CustomRoles.Amnesiac, - CustomRoles.Pursuer, - CustomRoles.Totocalcio, - CustomRoles.Maverick - ]; + RememberCooldown = new FloatOptionItem(Id + 10, "RememberCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(RememberMode) + .SetValueFormat(OptionFormat.Seconds); - private static readonly string[] RememberModes = - [ - "AmnesiacRM.ByReportingBody", - "AmnesiacRM.ByKillButton" - ]; + CanRememberCrewPower = new BooleanOptionItem(Id + 11, "CanRememberCrewPower", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - private byte AmnesiacId; + IncompatibleNeutralMode = new StringOptionItem(Id + 12, "IncompatibleNeutralMode", AmnesiacIncompatibleNeutralMode.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - public override bool IsEnable => Instances.Count > 0; + CanVent = new BooleanOptionItem(Id + 13, "CanVent", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - public override void SetupCustomOption() + VentCooldown = new FloatOptionItem(Id + 14, "VentCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(CanVent) + .SetValueFormat(OptionFormat.Seconds); + + VentDuration = new FloatOptionItem(Id + 15, "MaxInVentTime", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(CanVent) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + Instances = []; + WasAmnesiac = []; + } + + public override void Add(byte playerId) + { + Instances.Add(this); + AmnesiacId = playerId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = RememberCooldown.GetFloat(); + } + + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead && RememberMode.GetValue() == 1; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + + if (CanVent.GetBool()) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Amnesiac); - - RememberMode = new StringOptionItem(Id + 9, "RememberMode", RememberModes, 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - - RememberCooldown = new FloatOptionItem(Id + 10, "RememberCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) - .SetParent(RememberMode) - .SetValueFormat(OptionFormat.Seconds); - - CanRememberCrewPower = new BooleanOptionItem(Id + 11, "CanRememberCrewPower", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - - IncompatibleNeutralMode = new StringOptionItem(Id + 12, "IncompatibleNeutralMode", AmnesiacIncompatibleNeutralMode.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true) - .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - - CanVent = new BooleanOptionItem(Id + 13, "CanVent", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Amnesiac]); - - VentCooldown = new FloatOptionItem(Id + 14, "VentCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) - .SetParent(CanVent) - .SetValueFormat(OptionFormat.Seconds); - - VentDuration = new FloatOptionItem(Id + 15, "MaxInVentTime", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) - .SetParent(CanVent) - .SetValueFormat(OptionFormat.Seconds); + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = VentDuration.GetFloat(); } + } - public override void Init() + public static void OnAnyoneDeath(PlayerControl target) + { + if (RememberMode.GetValue() == 0) { - Instances = []; - WasAmnesiac = []; - } - - public override void Add(byte playerId) - { - Instances.Add(this); - AmnesiacId = playerId; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = RememberCooldown.GetFloat(); - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead && RememberMode.GetValue() == 1; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - - if (CanVent.GetBool()) + foreach (Amnesiac instance in Instances) { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = VentDuration.GetFloat(); + LocateArrow.Add(instance.AmnesiacId, target.Pos()); + PlayerControl amne = Utils.GetPlayerById(instance.AmnesiacId); + Utils.NotifyRoles(SpecifySeer: amne, SpecifyTarget: amne); } } + } - public static void OnAnyoneDeath(PlayerControl target) + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (RememberMode.GetValue() == 1) + RememberRole(killer, target); + + return false; + } + + public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) + { + if (target.Object.Is(CustomRoles.Unreportable)) return true; + + if (RememberMode.GetValue() == 0) { - if (RememberMode.GetValue() == 0) - { - foreach (Amnesiac instance in Instances) - { - LocateArrow.Add(instance.AmnesiacId, target.Pos()); - PlayerControl amne = Utils.GetPlayerById(instance.AmnesiacId); - Utils.NotifyRoles(SpecifySeer: amne, SpecifyTarget: amne); - } - } - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (RememberMode.GetValue() == 1) - RememberRole(killer, target); - + RememberRole(reporter, target.Object); return false; } - public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) - { - if (target.Object.Is(CustomRoles.Unreportable)) return true; - - if (RememberMode.GetValue() == 0) - { - RememberRole(reporter, target.Object); - return false; - } + return true; + } - return true; + private static void RememberRole(PlayerControl amnesiac, PlayerControl target) + { + CustomRoles? RememberedRole = null; + + var amneNotifyString = string.Empty; + CustomRoles targetRole = target.GetCustomRole(); + int loversAlive = Main.LoversPlayers.Count(x => x.IsAlive()); + + switch (targetRole) + { + case CustomRoles.Jackal: + RememberedRole = CustomRoles.Sidekick; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedNeutralKiller")); + break; + case CustomRoles.Necromancer: + RememberedRole = CustomRoles.Deathknight; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedNeutralKiller")); + break; + case CustomRoles.LovingCrewmate when loversAlive > 0: + target.RpcSetCustomRole(CustomRoles.CrewmateEHR); + RememberedRole = CustomRoles.LovingCrewmate; + Main.LoversPlayers.RemoveAll(x => x.PlayerId == target.PlayerId); + Main.LoversPlayers.Add(amnesiac); + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedLover")); + break; + case CustomRoles.LovingImpostor when loversAlive > 0: + target.RpcSetCustomRole(CustomRoles.ImpostorEHR); + RememberedRole = CustomRoles.LovingImpostor; + Main.LoversPlayers.RemoveAll(x => x.PlayerId == target.PlayerId); + Main.LoversPlayers.Add(amnesiac); + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedLover")); + break; + case CustomRoles.LovingCrewmate: + RememberedRole = CustomRoles.Sheriff; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedCrewmate")); + break; + case CustomRoles.LovingImpostor: + RememberedRole = CustomRoles.Refugee; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedImpostor")); + break; + default: + switch (target.GetTeam()) + { + case Team.Impostor: + RememberedRole = CustomRoles.Refugee; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedImpostor")); + break; + case Team.Crewmate: + RememberedRole = CanRememberCrewPower.GetBool() || targetRole.GetCrewmateRoleCategory() != RoleOptionType.Crewmate_Power ? targetRole : CustomRoles.Sheriff; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedCrewmate")); + break; + case Team.Neutral: + if (!SingleRoles.Contains(targetRole)) + { + RememberedRole = targetRole; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedNeutralKiller")); + } + else + { + RememberedRole = AmnesiacIncompatibleNeutralMode[IncompatibleNeutralMode.GetValue()]; + amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString($"Remembered{RememberedRole}")); + } + + break; + } + + break; } - private static void RememberRole(PlayerControl amnesiac, PlayerControl target) + + if (!RememberedRole.HasValue) { - CustomRoles? RememberedRole = null; - - var amneNotifyString = string.Empty; - CustomRoles targetRole = target.GetCustomRole(); - int loversAlive = Main.LoversPlayers.Count(x => x.IsAlive()); - - switch (targetRole) - { - case CustomRoles.Jackal: - RememberedRole = CustomRoles.Sidekick; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedNeutralKiller")); - break; - case CustomRoles.Necromancer: - RememberedRole = CustomRoles.Deathknight; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedNeutralKiller")); - break; - case CustomRoles.LovingCrewmate when loversAlive > 0: - target.RpcSetCustomRole(CustomRoles.CrewmateEHR); - RememberedRole = CustomRoles.LovingCrewmate; - Main.LoversPlayers.RemoveAll(x => x.PlayerId == target.PlayerId); - Main.LoversPlayers.Add(amnesiac); - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedLover")); - break; - case CustomRoles.LovingImpostor when loversAlive > 0: - target.RpcSetCustomRole(CustomRoles.ImpostorEHR); - RememberedRole = CustomRoles.LovingImpostor; - Main.LoversPlayers.RemoveAll(x => x.PlayerId == target.PlayerId); - Main.LoversPlayers.Add(amnesiac); - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedLover")); - break; - case CustomRoles.LovingCrewmate: - RememberedRole = CustomRoles.Sheriff; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedCrewmate")); - break; - case CustomRoles.LovingImpostor: - RememberedRole = CustomRoles.Refugee; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedImpostor")); - break; - default: - switch (target.GetTeam()) - { - case Team.Impostor: - RememberedRole = CustomRoles.Refugee; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedImpostor")); - break; - case Team.Crewmate: - RememberedRole = CanRememberCrewPower.GetBool() || targetRole.GetCrewmateRoleCategory() != RoleOptionType.Crewmate_Power ? targetRole : CustomRoles.Sheriff; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedCrewmate")); - break; - case Team.Neutral: - if (!SingleRoles.Contains(targetRole)) - { - RememberedRole = targetRole; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("RememberedNeutralKiller")); - } - else - { - RememberedRole = AmnesiacIncompatibleNeutralMode[IncompatibleNeutralMode.GetValue()]; - amneNotifyString = Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString($"Remembered{RememberedRole}")); - } - - break; - } - - break; - } - - - if (!RememberedRole.HasValue) - { - amnesiac.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("AmnesiacInvalidTarget"))); - return; - } - - CustomRoles role = RememberedRole.Value; - - amnesiac.RpcSetCustomRole(role); - amnesiac.RpcChangeRoleBasis(role); - - amnesiac.Notify(amneNotifyString); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("AmnesiacRemembered"))); - - amnesiac.SetKillCooldown(3f); - - target.RpcGuardAndKill(amnesiac); - target.RpcGuardAndKill(target); - - if (role.IsRecruitingRole()) amnesiac.SetAbilityUseLimit(0); - - if (role.GetRoleTypes() == RoleTypes.Engineer) - WasAmnesiac.Add(amnesiac.PlayerId); - - if (!amnesiac.IsLocalPlayer()) return; - - switch (role) - { - case CustomRoles.Virus: - case CustomRoles.Succubus: - Achievements.Type.UnderNewManagement.Complete(); - break; - case CustomRoles.Deathknight: - case CustomRoles.Sidekick: - Achievements.Type.FirstDayOnTheJob.Complete(); - break; - } + amnesiac.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("AmnesiacInvalidTarget"))); + return; } - public override void OnReportDeadBody() + CustomRoles role = RememberedRole.Value; + + amnesiac.RpcSetCustomRole(role); + amnesiac.RpcChangeRoleBasis(role); + + amnesiac.Notify(amneNotifyString); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Amnesiac), GetString("AmnesiacRemembered"))); + + amnesiac.SetKillCooldown(3f); + + target.RpcGuardAndKill(amnesiac); + target.RpcGuardAndKill(target); + + if (role.IsRecruitingRole()) amnesiac.SetAbilityUseLimit(0); + + if (role.GetRoleTypes() == RoleTypes.Engineer) + WasAmnesiac.Add(amnesiac.PlayerId); + + if (!amnesiac.IsLocalPlayer()) return; + + switch (role) { - LocateArrow.RemoveAllTarget(AmnesiacId); - } - - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; - - if (player.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) return true; - - return player.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Refugee); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - ActionButton amneButton = RememberMode.GetValue() == 1 ? hud.KillButton : hud.ReportButton; - amneButton?.OverrideText(GetString("RememberButtonText")); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != AmnesiacId || meeting || hud) return string.Empty; - - return LocateArrow.GetArrows(seer); + case CustomRoles.Virus: + case CustomRoles.Succubus: + Achievements.Type.UnderNewManagement.Complete(); + break; + case CustomRoles.Deathknight: + case CustomRoles.Sidekick: + Achievements.Type.FirstDayOnTheJob.Complete(); + break; } } + + public override void OnReportDeadBody() + { + LocateArrow.RemoveAllTarget(AmnesiacId); + } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (player.Is(CustomRoles.Refugee) && target.Is(CustomRoleTypes.Impostor)) return true; + + return player.Is(CustomRoleTypes.Impostor) && target.Is(CustomRoles.Refugee); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + ActionButton amneButton = RememberMode.GetValue() == 1 ? hud.KillButton : hud.ReportButton; + amneButton?.OverrideText(GetString("RememberButtonText")); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != AmnesiacId || meeting || hud) return string.Empty; + + return LocateArrow.GetArrows(seer); + } } \ No newline at end of file diff --git a/Roles/Neutral/Arsonist.cs b/Roles/Neutral/Arsonist.cs index 7f6fd3e84..ee617712f 100644 --- a/Roles/Neutral/Arsonist.cs +++ b/Roles/Neutral/Arsonist.cs @@ -5,223 +5,222 @@ using EHR.Modules; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Arsonist : RoleBase { - internal class Arsonist : RoleBase + public static Dictionary ArsonistTimer = []; + public static Dictionary<(byte, byte), bool> IsDoused = []; + public static byte CurrentDousingTarget = byte.MaxValue; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary ArsonistTimer = []; - public static Dictionary<(byte, byte), bool> IsDoused = []; - public static byte CurrentDousingTarget = byte.MaxValue; + SetupRoleOptions(10400, TabGroup.NeutralRoles, CustomRoles.Arsonist); - public static bool On; - public override bool IsEnable => On; + ArsonistDouseTime = new FloatOptionItem(10410, "ArsonistDouseTime", new(0f, 90f, 0.5f), 3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + ArsonistCooldown = new FloatOptionItem(10411, "Cooldown", new(0f, 60f, 0.5f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]) + .SetValueFormat(OptionFormat.Seconds); + + ArsonistCanIgniteAnytime = new BooleanOptionItem(10413, "ArsonistCanIgniteAnytime", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]); + + ArsonistMinPlayersToIgnite = new IntegerOptionItem(10414, "ArsonistMinPlayersToIgnite", new(1, 14, 1), 1, TabGroup.NeutralRoles) + .SetParent(ArsonistCanIgniteAnytime); + + ArsonistMaxPlayersToIgnite = new IntegerOptionItem(10415, "ArsonistMaxPlayersToIgnite", new(1, 14, 1), 3, TabGroup.NeutralRoles) + .SetParent(ArsonistCanIgniteAnytime); + + ArsonistKeepsGameGoing = new BooleanOptionItem(10412, "ArsonistKeepsGameGoing", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]); + } + + public override void Add(byte playerId) + { + On = true; + foreach (PlayerControl ar in Main.AllPlayerControls) IsDoused.Add((playerId, ar.PlayerId), false); + } + + public override void Init() + { + On = false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return ArsonistCanIgniteAnytime.GetBool() ? Utils.GetDousedPlayerCount(pc.PlayerId).Item1 < ArsonistMaxPlayersToIgnite.GetInt() : !pc.IsDouseDone(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsDouseDone() || (ArsonistCanIgniteAnytime.GetBool() && !UsePets.GetBool() && (Utils.GetDousedPlayerCount(pc.PlayerId).Item1 >= ArsonistMinPlayersToIgnite.GetInt() || pc.inVent)); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = ArsonistCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override string GetProgressText(byte playerId, bool comms) + { + (int, int) doused = Utils.GetDousedPlayerCount(playerId); + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Arsonist).ShadeColor(0.25f), !ArsonistCanIgniteAnytime.GetBool() ? $"- {doused.Item1}/{doused.Item2}" : $"- {doused.Item1}/{ArsonistMaxPlayersToIgnite.GetInt()}"); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + killer.SetKillCooldown(ArsonistDouseTime.GetFloat()); + + if (!IsDoused[(killer.PlayerId, target.PlayerId)] && !ArsonistTimer.ContainsKey(killer.PlayerId)) { - SetupRoleOptions(10400, TabGroup.NeutralRoles, CustomRoles.Arsonist); - - ArsonistDouseTime = new FloatOptionItem(10410, "ArsonistDouseTime", new(0f, 90f, 0.5f), 3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]) - .SetValueFormat(OptionFormat.Seconds); - - ArsonistCooldown = new FloatOptionItem(10411, "Cooldown", new(0f, 60f, 0.5f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]) - .SetValueFormat(OptionFormat.Seconds); - - ArsonistCanIgniteAnytime = new BooleanOptionItem(10413, "ArsonistCanIgniteAnytime", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]); - - ArsonistMinPlayersToIgnite = new IntegerOptionItem(10414, "ArsonistMinPlayersToIgnite", new(1, 14, 1), 1, TabGroup.NeutralRoles) - .SetParent(ArsonistCanIgniteAnytime); - - ArsonistMaxPlayersToIgnite = new IntegerOptionItem(10415, "ArsonistMaxPlayersToIgnite", new(1, 14, 1), 3, TabGroup.NeutralRoles) - .SetParent(ArsonistCanIgniteAnytime); - - ArsonistKeepsGameGoing = new BooleanOptionItem(10412, "ArsonistKeepsGameGoing", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Arsonist]); + ArsonistTimer.Add(killer.PlayerId, (target, 0f)); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + RPC.SetCurrentDousingTarget(killer.PlayerId, target.PlayerId); } - public override void Add(byte playerId) - { - On = true; - foreach (PlayerControl ar in Main.AllPlayerControls) IsDoused.Add((playerId, ar.PlayerId), false); - } + return false; + } - public override void Init() - { - On = false; - } + public override void OnPet(PlayerControl pc) + { + Ignite(pc.MyPhysics); + } - public override bool CanUseKillButton(PlayerControl pc) - { - return ArsonistCanIgniteAnytime.GetBool() ? Utils.GetDousedPlayerCount(pc.PlayerId).Item1 < ArsonistMaxPlayersToIgnite.GetInt() : !pc.IsDouseDone(); - } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (AmongUsClient.Instance.IsGameStarted) + Ignite(physics); + } - public override bool CanUseImpostorVentButton(PlayerControl pc) + private static void Ignite(PlayerPhysics physics) + { + switch (ArsonistCanIgniteAnytime.GetBool()) { - return pc.IsDouseDone() || (ArsonistCanIgniteAnytime.GetBool() && !UsePets.GetBool() && (Utils.GetDousedPlayerCount(pc.PlayerId).Item1 >= ArsonistMinPlayersToIgnite.GetInt() || pc.inVent)); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = ArsonistCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override string GetProgressText(byte playerId, bool comms) - { - (int, int) doused = Utils.GetDousedPlayerCount(playerId); - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Arsonist).ShadeColor(0.25f), !ArsonistCanIgniteAnytime.GetBool() ? $"- {doused.Item1}/{doused.Item2}" : $"- {doused.Item1}/{ArsonistMaxPlayersToIgnite.GetInt()}"); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - killer.SetKillCooldown(ArsonistDouseTime.GetFloat()); - - if (!IsDoused[(killer.PlayerId, target.PlayerId)] && !ArsonistTimer.ContainsKey(killer.PlayerId)) + case false when physics.myPlayer.IsDouseDone(): { - ArsonistTimer.Add(killer.PlayerId, (target, 0f)); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - RPC.SetCurrentDousingTarget(killer.PlayerId, target.PlayerId); + CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + if (pc != physics.myPlayer) + pc.Suicide(PlayerState.DeathReason.Torched, physics.myPlayer); + + foreach (PlayerControl pc in Main.AllPlayerControls) + pc.KillFlash(); + + if (CustomWinnerHolder.WinnerTeam is CustomWinner.Crewmate or CustomWinner.Impostor) + CustomWinnerHolder.Reset(); + + CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Arsonist); + CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); + return; } - - return false; - } - - public override void OnPet(PlayerControl pc) - { - Ignite(pc.MyPhysics); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (AmongUsClient.Instance.IsGameStarted) - Ignite(physics); - } - - private static void Ignite(PlayerPhysics physics) - { - switch (ArsonistCanIgniteAnytime.GetBool()) + case true: { - case false when physics.myPlayer.IsDouseDone(): + int douseCount = Utils.GetDousedPlayerCount(physics.myPlayer.PlayerId).Doused; + + if (douseCount >= ArsonistMinPlayersToIgnite.GetInt()) // Don't check for max, since the player would not be able to ignite at all if they somehow get more players doused than the max { - CustomSoundsManager.RPCPlayCustomSoundAll("Boom"); + if (douseCount > ArsonistMaxPlayersToIgnite.GetInt()) Logger.Warn("Arsonist Ignited with more players doused than the maximum amount in the settings", "Arsonist Ignite"); foreach (PlayerControl pc in Main.AllAlivePlayerControls) - if (pc != physics.myPlayer) - pc.Suicide(PlayerState.DeathReason.Torched, physics.myPlayer); - - foreach (PlayerControl pc in Main.AllPlayerControls) - pc.KillFlash(); - - if (CustomWinnerHolder.WinnerTeam is CustomWinner.Crewmate or CustomWinner.Impostor) - CustomWinnerHolder.Reset(); - - CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Arsonist); - CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); - return; - } - case true: - { - int douseCount = Utils.GetDousedPlayerCount(physics.myPlayer.PlayerId).Doused; - - if (douseCount >= ArsonistMinPlayersToIgnite.GetInt()) // Don't check for max, since the player would not be able to ignite at all if they somehow get more players doused than the max { - if (douseCount > ArsonistMaxPlayersToIgnite.GetInt()) Logger.Warn("Arsonist Ignited with more players doused than the maximum amount in the settings", "Arsonist Ignite"); + if (!physics.myPlayer.IsDousedPlayer(pc)) continue; - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (!physics.myPlayer.IsDousedPlayer(pc)) continue; - - pc.Suicide(PlayerState.DeathReason.Torched, physics.myPlayer); - } - - physics.myPlayer.KillFlash(); - - int apc = Main.AllAlivePlayerControls.Length; - - switch (apc) - { - case 1: - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Arsonist); - CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); - break; - case 2: - if (Main.AllAlivePlayerControls.Where(x => x.PlayerId != physics.myPlayer.PlayerId).All(x => x.GetCountTypes() == CountTypes.Crew)) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Arsonist); - CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); - } - - break; - } + pc.Suicide(PlayerState.DeathReason.Torched, physics.myPlayer); } - break; + physics.myPlayer.KillFlash(); + + int apc = Main.AllAlivePlayerControls.Length; + + switch (apc) + { + case 1: + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Arsonist); + CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); + break; + case 2: + if (Main.AllAlivePlayerControls.Where(x => x.PlayerId != physics.myPlayer.PlayerId).All(x => x.GetCountTypes() == CountTypes.Crew)) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Arsonist); + CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); + } + + break; + } } + + break; } } + } - public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + public override void OnGlobalFixedUpdate(PlayerControl player, bool lowLoad) + { + byte playerId = player.PlayerId; + + if (GameStates.IsInTask && ArsonistTimer.ContainsKey(playerId)) { - byte playerId = player.PlayerId; + PlayerControl arTarget = ArsonistTimer[playerId].Player; - if (GameStates.IsInTask && ArsonistTimer.ContainsKey(playerId)) + if (!player.IsAlive() || Pelican.IsEaten(playerId)) { - PlayerControl arTarget = ArsonistTimer[playerId].Player; + ArsonistTimer.Remove(playerId); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); + RPC.ResetCurrentDousingTarget(playerId); + } + else + { + PlayerControl ar_target = ArsonistTimer[playerId].Player; + float ar_time = ArsonistTimer[playerId].Timer; - if (!player.IsAlive() || Pelican.IsEaten(playerId)) - { + if (!ar_target.IsAlive()) ArsonistTimer.Remove(playerId); + else if (ar_time >= ArsonistDouseTime.GetFloat()) + { + player.SetKillCooldown(); + ArsonistTimer.Remove(playerId); + IsDoused[(playerId, ar_target.PlayerId)] = true; + player.RpcSetDousedPlayer(ar_target, true); Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); RPC.ResetCurrentDousingTarget(playerId); } else { - PlayerControl ar_target = ArsonistTimer[playerId].Player; - float ar_time = ArsonistTimer[playerId].Timer; + float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; + float dis = Vector2.Distance(player.transform.position, ar_target.transform.position); - if (!ar_target.IsAlive()) - ArsonistTimer.Remove(playerId); - else if (ar_time >= ArsonistDouseTime.GetFloat()) - { - player.SetKillCooldown(); - ArsonistTimer.Remove(playerId); - IsDoused[(playerId, ar_target.PlayerId)] = true; - player.RpcSetDousedPlayer(ar_target, true); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); - RPC.ResetCurrentDousingTarget(playerId); - } + if (dis <= range) + ArsonistTimer[playerId] = (ar_target, ar_time + Time.fixedDeltaTime); else { - float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - float dis = Vector2.Distance(player.transform.position, ar_target.transform.position); + player.SetKillCooldown(0.01f); + ArsonistTimer.Remove(playerId); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); + RPC.ResetCurrentDousingTarget(playerId); - if (dis <= range) - ArsonistTimer[playerId] = (ar_target, ar_time + Time.fixedDeltaTime); - else - { - player.SetKillCooldown(0.01f); - ArsonistTimer.Remove(playerId); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: arTarget, ForceLoop: true); - RPC.ResetCurrentDousingTarget(playerId); - - Logger.Info($"Canceled: {player.GetNameWithRole().RemoveHtmlTags()}", "Arsonist"); - } + Logger.Info($"Canceled: {player.GetNameWithRole().RemoveHtmlTags()}", "Arsonist"); } } } } + } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("ArsonistDouseButtonText")); - ActionButton usedButton = UsePets.GetBool() ? hud.PetButton : hud.KillButton; - usedButton?.OverrideText(Translator.GetString("ArsonistVentButtonText")); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("ArsonistDouseButtonText")); + ActionButton usedButton = UsePets.GetBool() ? hud.PetButton : hud.KillButton; + usedButton?.OverrideText(Translator.GetString("ArsonistVentButtonText")); } } \ No newline at end of file diff --git a/Roles/Neutral/Backstabber.cs b/Roles/Neutral/Backstabber.cs index 3774e9522..6aa69c8be 100644 --- a/Roles/Neutral/Backstabber.cs +++ b/Roles/Neutral/Backstabber.cs @@ -1,114 +1,113 @@ using AmongUs.GameOptions; using EHR.Modules; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Backstabber : RoleBase { - public class Backstabber : RoleBase + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + public static OptionItem RevealAfterKilling; + + private WinningTeam Team; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 649150; + Options.SetupRoleOptions(id++, TabGroup.NeutralRoles, CustomRoles.Backstabber); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - public static OptionItem RevealAfterKilling; + KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]) + .SetValueFormat(OptionFormat.Seconds); - private WinningTeam Team; + CanVent = new BooleanOptionItem(++id, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); - public override bool IsEnable => On; + HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); - public override void SetupCustomOption() - { - var id = 649150; - Options.SetupRoleOptions(id++, TabGroup.NeutralRoles, CustomRoles.Backstabber); + RevealAfterKilling = new BooleanOptionItem(++id, "Backstabber.RevealAfterKilling", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); + } - KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + On = false; + } - CanVent = new BooleanOptionItem(++id, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); + public override void Add(byte playerId) + { + On = true; + Team = WinningTeam.Crew; + playerId.SetAbilityUseLimit(1); + } - HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - RevealAfterKilling = new BooleanOptionItem(++id, "Backstabber.RevealAfterKilling", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Backstabber]); - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } - public override void Init() - { - On = false; - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override void Add(byte playerId) - { - On = true; - Team = WinningTeam.Crew; - playerId.SetAbilityUseLimit(1); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() > 0f; + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() > 0f; - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (killer.PlayerId == target.PlayerId) return; + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (killer.PlayerId == target.PlayerId) return; - Team targetTeam = target.GetTeam(); + Team targetTeam = target.GetTeam(); - Team = targetTeam switch - { - EHR.Team.Impostor => WinningTeam.NK, - _ => WinningTeam.Imp - }; + Team = targetTeam switch + { + EHR.Team.Impostor => WinningTeam.NK, + _ => WinningTeam.Imp + }; - if (targetTeam == EHR.Team.Crewmate && killer.IsLocalPlayer()) - Achievements.Type.StabbingTheBack.Complete(); + if (targetTeam == EHR.Team.Crewmate && killer.IsLocalPlayer()) + Achievements.Type.StabbingTheBack.Complete(); - killer.RpcRemoveAbilityUse(); - killer.Notify(string.Format(Translator.GetString("Backstabber.MurderNotify"), Utils.ColorString(targetTeam.GetTeamColor(), Translator.GetString(targetTeam.ToString())), Translator.GetString($"BackstabberTeam.{Team}")), 10f); - } + killer.RpcRemoveAbilityUse(); + killer.Notify(string.Format(Translator.GetString("Backstabber.MurderNotify"), Utils.ColorString(targetTeam.GetTeamColor(), Translator.GetString(targetTeam.ToString())), Translator.GetString($"BackstabberTeam.{Team}")), 10f); + } - public override bool KnowRole(PlayerControl seer, PlayerControl target) + public override bool KnowRole(PlayerControl seer, PlayerControl target) + { + return target.Is(CustomRoles.Backstabber) && RevealAfterKilling.GetBool() && target.GetAbilityUseLimit() == 0f; + } + + public bool CheckWin() + { + return CustomWinnerHolder.WinnerTeam switch { - return target.Is(CustomRoles.Backstabber) && RevealAfterKilling.GetBool() && target.GetAbilityUseLimit() == 0f; - } + CustomWinner.Crewmate => Team == WinningTeam.Crew, + CustomWinner.Impostor => Team == WinningTeam.Imp, + CustomWinner.Neutrals => Team == WinningTeam.NK, + CustomWinner.None => false, + CustomWinner.Default or CustomWinner.Draw or CustomWinner.Error => true, + _ => Team == WinningTeam.NK + }; + } - public bool CheckWin() - { - return CustomWinnerHolder.WinnerTeam switch - { - CustomWinner.Crewmate => Team == WinningTeam.Crew, - CustomWinner.Impostor => Team == WinningTeam.Imp, - CustomWinner.Neutrals => Team == WinningTeam.NK, - CustomWinner.None => false, - CustomWinner.Default or CustomWinner.Draw or CustomWinner.Error => true, - _ => Team == WinningTeam.NK - }; - } - - private enum WinningTeam - { - Crew, - Imp, - NK - } + private enum WinningTeam + { + Crew, + Imp, + NK } } \ No newline at end of file diff --git a/Roles/Neutral/Bandit.cs b/Roles/Neutral/Bandit.cs index 8cc58a389..8d4df40df 100644 --- a/Roles/Neutral/Bandit.cs +++ b/Roles/Neutral/Bandit.cs @@ -5,217 +5,216 @@ using Hazel; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Bandit : RoleBase { - public class Bandit : RoleBase + private const int Id = 18420; + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem MaxSteals; + private static OptionItem StealMode; + private static OptionItem CanStealBetrayalAddon; + private static OptionItem CanStealImpOnlyAddon; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + + private static Dictionary TotalSteals = []; + private static Dictionary> Targets = []; + + private static readonly string[] BanditStealModeOpt = + [ + "BanditStealMode.OnMeeting", + "BanditStealMode.Instantly" + ]; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 18420; - public static bool On; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Bandit); - private static OptionItem KillCooldown; - private static OptionItem MaxSteals; - private static OptionItem StealMode; - private static OptionItem CanStealBetrayalAddon; - private static OptionItem CanStealImpOnlyAddon; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + MaxSteals = new IntegerOptionItem(Id + 10, "BanditMaxSteals", new(1, 20, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - private static Dictionary TotalSteals = []; - private static Dictionary> Targets = []; + KillCooldown = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]) + .SetValueFormat(OptionFormat.Seconds); - private static readonly string[] BanditStealModeOpt = - [ - "BanditStealMode.OnMeeting", - "BanditStealMode.Instantly" - ]; + StealMode = new StringOptionItem(Id + 12, "BanditStealMode", BanditStealModeOpt, 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - public override bool IsEnable => On; + CanStealBetrayalAddon = new BooleanOptionItem(Id + 13, "BanditCanStealBetrayalAddon", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - public override void SetupCustomOption() + CanStealImpOnlyAddon = new BooleanOptionItem(Id + 14, "BanditCanStealImpOnlyAddon", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); + + CanVent = new BooleanOptionItem(Id + 16, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); + + HasImpostorVision = new BooleanOptionItem(Id + 17, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); + } + + public override void Init() + { + Targets = []; + TotalSteals = []; + On = false; + } + + public override void Add(byte playerId) + { + On = true; + TotalSteals.Add(playerId, 0); + Targets[playerId] = []; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + private static void SendRPC(byte playerId /*, bool isTargetList = false*/) + { + if (!On || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetBanditStealLimit, SendOption.Reliable); + writer.Write(playerId); + writer.Write(TotalSteals[playerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + int Limit = reader.ReadInt32(); + if (!TotalSteals.TryAdd(PlayerId, 0)) TotalSteals[PlayerId] = Limit; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + private static CustomRoles? SelectRandomAddon(PlayerControl Target) + { + if (!AmongUsClient.Instance.AmHost) return null; + + List AllSubRoles = Main.PlayerStates[Target.PlayerId].SubRoles; + + for (var i = 0; i < AllSubRoles.Count; i++) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Bandit); + CustomRoles role = AllSubRoles[i]; - MaxSteals = new IntegerOptionItem(Id + 10, "BanditMaxSteals", new(1, 20, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - - KillCooldown = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]) - .SetValueFormat(OptionFormat.Seconds); - - StealMode = new StringOptionItem(Id + 12, "BanditStealMode", BanditStealModeOpt, 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - - CanStealBetrayalAddon = new BooleanOptionItem(Id + 13, "BanditCanStealBetrayalAddon", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - - CanStealImpOnlyAddon = new BooleanOptionItem(Id + 14, "BanditCanStealImpOnlyAddon", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - - CanVent = new BooleanOptionItem(Id + 16, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - - HasImpostorVision = new BooleanOptionItem(Id + 17, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bandit]); - } - - public override void Init() - { - Targets = []; - TotalSteals = []; - On = false; - } - - public override void Add(byte playerId) - { - On = true; - TotalSteals.Add(playerId, 0); - Targets[playerId] = []; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - private static void SendRPC(byte playerId /*, bool isTargetList = false*/) - { - if (!On || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetBanditStealLimit, SendOption.Reliable); - writer.Write(playerId); - writer.Write(TotalSteals[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte PlayerId = reader.ReadByte(); - int Limit = reader.ReadInt32(); - if (!TotalSteals.TryAdd(PlayerId, 0)) TotalSteals[PlayerId] = Limit; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - private static CustomRoles? SelectRandomAddon(PlayerControl Target) - { - if (!AmongUsClient.Instance.AmHost) return null; - - List AllSubRoles = Main.PlayerStates[Target.PlayerId].SubRoles; - - for (var i = 0; i < AllSubRoles.Count; i++) + if (IsBlacklistedAddon(role)) { - CustomRoles role = AllSubRoles[i]; + Logger.Info($"Removed {role} from stealable addons", "Bandit"); + AllSubRoles.Remove(role); + } + } - if (IsBlacklistedAddon(role)) - { - Logger.Info($"Removed {role} from stealable addons", "Bandit"); - AllSubRoles.Remove(role); - } + if (AllSubRoles.Count == 0) + { + Logger.Info("No stealable addons found on the target.", "Bandit"); + return null; + } + + CustomRoles addon = AllSubRoles.RandomElement(); + return addon; + } + + private static bool IsBlacklistedAddon(CustomRoles role) + { + return (role.IsImpOnlyAddon() && !CanStealImpOnlyAddon.GetBool()) || (role.IsBetrayalAddon() && !CanStealBetrayalAddon.GetBool()) || StartGameHostPatch.BasisChangingAddons.ContainsKey(role) || role.IsNotAssignableMidGame(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!On) return true; + + if (!target.HasSubRole()) return true; + + if (TotalSteals[killer.PlayerId] >= MaxSteals.GetInt()) + { + Logger.Info("Max steals reached killing the player", "Bandit"); + TotalSteals[killer.PlayerId] = MaxSteals.GetInt(); + return true; + } + + CustomRoles? SelectedAddOn = SelectRandomAddon(target); + if (SelectedAddOn == null) return true; // no stealable addons found on the target. + + killer.ResetKillCooldown(); + + return killer.CheckDoubleTrigger(target, () => + { + if (StealMode.GetValue() == 1) + { + Main.PlayerStates[target.PlayerId].RemoveSubRole((CustomRoles)SelectedAddOn); + Logger.Info($"Successfully removed {SelectedAddOn} addon from {target.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); + killer.RpcSetCustomRole((CustomRoles)SelectedAddOn); + Logger.Info($"Successfully Added {SelectedAddOn} addon to {killer.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); + } + else + { + Targets[killer.PlayerId][target.PlayerId] = (CustomRoles)SelectedAddOn; + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} will steal {SelectedAddOn} addon from {target.GetNameWithRole().RemoveHtmlTags()} after meeting starts", "Bandit"); } - if (AllSubRoles.Count == 0) - { - Logger.Info("No stealable addons found on the target.", "Bandit"); - return null; - } - - CustomRoles addon = AllSubRoles.RandomElement(); - return addon; - } - - private static bool IsBlacklistedAddon(CustomRoles role) - { - return (role.IsImpOnlyAddon() && !CanStealImpOnlyAddon.GetBool()) || (role.IsBetrayalAddon() && !CanStealBetrayalAddon.GetBool()) || StartGameHostPatch.BasisChangingAddons.ContainsKey(role) || role.IsNotAssignableMidGame(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!On) return true; - - if (!target.HasSubRole()) return true; - - if (TotalSteals[killer.PlayerId] >= MaxSteals.GetInt()) - { - Logger.Info("Max steals reached killing the player", "Bandit"); - TotalSteals[killer.PlayerId] = MaxSteals.GetInt(); - return true; - } - - CustomRoles? SelectedAddOn = SelectRandomAddon(target); - if (SelectedAddOn == null) return true; // no stealable addons found on the target. + TotalSteals[killer.PlayerId]++; + SendRPC(killer.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); + if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); killer.ResetKillCooldown(); + killer.SetKillCooldown(); + }); + } - return killer.CheckDoubleTrigger(target, () => - { - if (StealMode.GetValue() == 1) - { - Main.PlayerStates[target.PlayerId].RemoveSubRole((CustomRoles)SelectedAddOn); - Logger.Info($"Successfully removed {SelectedAddOn} addon from {target.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); - killer.RpcSetCustomRole((CustomRoles)SelectedAddOn); - Logger.Info($"Successfully Added {SelectedAddOn} addon to {killer.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); - } - else - { - Targets[killer.PlayerId][target.PlayerId] = (CustomRoles)SelectedAddOn; - Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} will steal {SelectedAddOn} addon from {target.GetNameWithRole().RemoveHtmlTags()} after meeting starts", "Bandit"); - } + public override void OnReportDeadBody() + { + if (!On) return; - TotalSteals[killer.PlayerId]++; - SendRPC(killer.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); - if (!DisableShieldAnimations.GetBool()) killer.RpcGuardAndKill(target); + if (StealMode.GetValue() == 1) return; - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - }); - } - - public override void OnReportDeadBody() + foreach (KeyValuePair> kvp1 in Targets) { - if (!On) return; + byte banditId = kvp1.Key; + PlayerControl banditpc = Utils.GetPlayerById(banditId); + if (banditpc == null || !banditpc.IsAlive()) continue; - if (StealMode.GetValue() == 1) return; + Dictionary innerDictionary = kvp1.Value; + Utils.NotifyRoles(SpecifySeer: banditpc); - foreach (KeyValuePair> kvp1 in Targets) + foreach (KeyValuePair kvp2 in innerDictionary) { - byte banditId = kvp1.Key; - PlayerControl banditpc = Utils.GetPlayerById(banditId); - if (banditpc == null || !banditpc.IsAlive()) continue; + byte targetId = kvp2.Key; + PlayerControl target = Utils.GetPlayerById(banditId); + if (target == null) continue; - Dictionary innerDictionary = kvp1.Value; - Utils.NotifyRoles(SpecifySeer: banditpc); - - foreach (KeyValuePair kvp2 in innerDictionary) - { - byte targetId = kvp2.Key; - PlayerControl target = Utils.GetPlayerById(banditId); - if (target == null) continue; - - CustomRoles role = kvp2.Value; - Main.PlayerStates[targetId].RemoveSubRole(role); - Logger.Info($"Successfully removed {role} addon from {target.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); - banditpc.RpcSetCustomRole(role); - Logger.Info($"Successfully Added {role} addon to {banditpc.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: banditpc); - } - - Targets[banditId].Clear(); + CustomRoles role = kvp2.Value; + Main.PlayerStates[targetId].RemoveSubRole(role); + Logger.Info($"Successfully removed {role} addon from {target.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); + banditpc.RpcSetCustomRole(role); + Logger.Info($"Successfully Added {role} addon to {banditpc.GetNameWithRole().RemoveHtmlTags()}", "Bandit"); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: banditpc); } - } - public override string GetProgressText(byte playerId, bool comms) - { - return Utils.ColorString(TotalSteals[playerId] < MaxSteals.GetInt() ? Utils.GetRoleColor(CustomRoles.Bandit).ShadeColor(0.25f) : Color.gray, TotalSteals.TryGetValue(playerId, out int stealLimit) ? $"({MaxSteals.GetInt() - stealLimit})" : "Invalid"); + Targets[banditId].Clear(); } } + + public override string GetProgressText(byte playerId, bool comms) + { + return Utils.ColorString(TotalSteals[playerId] < MaxSteals.GetInt() ? Utils.GetRoleColor(CustomRoles.Bandit).ShadeColor(0.25f) : Color.gray, TotalSteals.TryGetValue(playerId, out int stealLimit) ? $"({MaxSteals.GetInt() - stealLimit})" : "Invalid"); + } } \ No newline at end of file diff --git a/Roles/Neutral/Bargainer.cs b/Roles/Neutral/Bargainer.cs index c1325f242..a4534cf28 100644 --- a/Roles/Neutral/Bargainer.cs +++ b/Roles/Neutral/Bargainer.cs @@ -9,519 +9,518 @@ using static EHR.Options; // ReSharper disable PossibleMultipleEnumeration -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Bargainer : RoleBase { - internal class Bargainer : RoleBase + public enum Item { - public enum Item + None, + EnergyDrink, + LensOfTruth, + BandAid + } + + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem StartingMoney; + private static readonly Dictionary MoneySettings = []; + private static readonly Dictionary ItemSettings = []; + private static OptionItem ShieldDuration; + private static OptionItem ShieldTime; + private static OptionItem ReducedKillCooldown; + private static OptionItem AlignmentVisible; + private static OptionItem AlignmentVisibleDuration; + + private static Dictionary Gains = []; + private static Dictionary Costs = []; + + private static readonly Dictionary Icons = new() + { + [Item.EnergyDrink] = Utils.ColorString(Color.magenta, "\u2668"), + [Item.LensOfTruth] = "\u2600", + [Item.BandAid] = Utils.ColorString(Color.green, "♥") + }; + + public static HashSet PurchasedItems = []; + + private List<(Item Item, long ActivateTimeStamp, int Duration, byte Target)> ActiveItems = []; + + private byte BargainerId; + private bool InShop; + private int Money; + private IEnumerable OrderedItems; + private Item SelectedItem; + + private static int AlignmentVisibleValue => (AlignmentVisibleOptions)AlignmentVisible.GetValue() switch + { + AlignmentVisibleOptions.Forever => int.MaxValue, + AlignmentVisibleOptions.UntilNextMeeting => int.MaxValue, + AlignmentVisibleOptions.UntilNextReveal => int.MaxValue, + AlignmentVisibleOptions.ForSpecifiedTime => AlignmentVisibleDuration.GetInt(), + + _ => 0 + }; + + private static int ShieldDurationValue => (ShieldDurationOptions)ShieldDuration.GetValue() switch + { + ShieldDurationOptions.UntilNextMeeting => int.MaxValue, + ShieldDurationOptions.ForSpecifiedTime => ShieldTime.GetInt(), + + _ => 0 + }; + + private static IEnumerable ShopLocations + { + get { - None, - EnergyDrink, - LensOfTruth, - BandAid + var mapName = Main.CurrentMap.ToString(); + IEnumerable> devices = DisableDevice.DevicePos.SkipWhile(x => !x.Key.StartsWith(mapName)).TakeWhile(x => x.Key.StartsWith(mapName)); + return devices.Select(x => x.Value); } + } - public static bool On; + public override bool IsEnable => On; - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem StartingMoney; - private static readonly Dictionary MoneySettings = []; - private static readonly Dictionary ItemSettings = []; - private static OptionItem ShieldDuration; - private static OptionItem ShieldTime; - private static OptionItem ReducedKillCooldown; - private static OptionItem AlignmentVisible; - private static OptionItem AlignmentVisibleDuration; + public override void SetupCustomOption() + { + var id = 14870; + const TabGroup tab = TabGroup.NeutralRoles; - private static Dictionary Gains = []; - private static Dictionary Costs = []; + SetupRoleOptions(id++, tab, CustomRoles.Bargainer); - private static readonly Dictionary Icons = new() + KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]) + .SetValueFormat(OptionFormat.Seconds); + + CanVent = new BooleanOptionItem(++id, "CanVent", true, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); + + HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); + + StartingMoney = new IntegerOptionItem(++id, "Bargainer.StartingMoney", new(0, 100, 5), 0, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); + + foreach (MoneyGainingAction action in Enum.GetValues()) { - [Item.EnergyDrink] = Utils.ColorString(Color.magenta, "\u2668"), - [Item.LensOfTruth] = "\u2600", - [Item.BandAid] = Utils.ColorString(Color.green, "♥") - }; + OptionItem boolOpt = new BooleanOptionItem(++id, $"Bargainer.{action}.Enabled", true, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); - public static HashSet PurchasedItems = []; + OptionItem intOpt = new IntegerOptionItem(++id, $"Bargainer.{action}.Amount", new(0, 100, 5), GetDefaultValue(), tab) + .SetParent(boolOpt); - private List<(Item Item, long ActivateTimeStamp, int Duration, byte Target)> ActiveItems = []; + MoneySettings[action] = (boolOpt, intOpt); + continue; - private byte BargainerId; - private bool InShop; - private int Money; - private IEnumerable OrderedItems; - private Item SelectedItem; - - private static int AlignmentVisibleValue => (AlignmentVisibleOptions)AlignmentVisible.GetValue() switch - { - AlignmentVisibleOptions.Forever => int.MaxValue, - AlignmentVisibleOptions.UntilNextMeeting => int.MaxValue, - AlignmentVisibleOptions.UntilNextReveal => int.MaxValue, - AlignmentVisibleOptions.ForSpecifiedTime => AlignmentVisibleDuration.GetInt(), - - _ => 0 - }; - - private static int ShieldDurationValue => (ShieldDurationOptions)ShieldDuration.GetValue() switch - { - ShieldDurationOptions.UntilNextMeeting => int.MaxValue, - ShieldDurationOptions.ForSpecifiedTime => ShieldTime.GetInt(), - - _ => 0 - }; - - private static IEnumerable ShopLocations - { - get + int GetDefaultValue() { - var mapName = Main.CurrentMap.ToString(); - IEnumerable> devices = DisableDevice.DevicePos.SkipWhile(x => !x.Key.StartsWith(mapName)).TakeWhile(x => x.Key.StartsWith(mapName)); - return devices.Select(x => x.Value); + return action switch + { + MoneyGainingAction.Kill => 30, + MoneyGainingAction.Sabotage => 10, + MoneyGainingAction.SurviveMeeting => 20, + + _ => 0 + }; } } - public override bool IsEnable => On; - - public override void SetupCustomOption() + foreach (Item item in Enum.GetValues()) { - var id = 14870; - const TabGroup tab = TabGroup.NeutralRoles; + if (item == Item.None) continue; - SetupRoleOptions(id++, tab, CustomRoles.Bargainer); - - KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(++id, "CanVent", true, tab) + OptionItem boolOpt = new BooleanOptionItem(++id, $"Bargainer.{item}.Enabled", true, tab) .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); - HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); + OptionItem intOpt = new IntegerOptionItem(++id, $"Bargainer.{item}.Cost", new(0, 200, 5), GetDefaultValue(), tab) + .SetParent(boolOpt); - StartingMoney = new IntegerOptionItem(++id, "Bargainer.StartingMoney", new(0, 100, 5), 0, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); + SetupExtraSettings(); - foreach (MoneyGainingAction action in Enum.GetValues()) + ItemSettings[item] = (boolOpt, intOpt); + continue; + + int GetDefaultValue() { - OptionItem boolOpt = new BooleanOptionItem(++id, $"Bargainer.{action}.Enabled", true, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); - - OptionItem intOpt = new IntegerOptionItem(++id, $"Bargainer.{action}.Amount", new(0, 100, 5), GetDefaultValue(), tab) - .SetParent(boolOpt); - - MoneySettings[action] = (boolOpt, intOpt); - continue; - - int GetDefaultValue() + return item switch { - return action switch - { - MoneyGainingAction.Kill => 30, - MoneyGainingAction.Sabotage => 10, - MoneyGainingAction.SurviveMeeting => 20, + Item.EnergyDrink => 40, + Item.LensOfTruth => 60, + Item.BandAid => 20, - _ => 0 - }; + _ => 0 + }; + } + + void SetupExtraSettings() + { + switch (item) + { + case Item.BandAid: + ShieldDuration = new StringOptionItem(++id, $"Bargainer.{item}.DurationSwitch", Enum.GetNames(), 0, tab) + .SetParent(boolOpt); + + ShieldTime = new IntegerOptionItem(++id, $"Bargainer.{item}.Duration", new(0, 60, 1), 20, tab) + .SetParent(ShieldDuration) + .SetValueFormat(OptionFormat.Seconds); + + break; + case Item.EnergyDrink: + ReducedKillCooldown = new FloatOptionItem(++id, $"Bargainer.{item}.ReducedKCD", new(0f, 180f, 0.5f), 17.5f, tab) + .SetParent(boolOpt) + .SetValueFormat(OptionFormat.Seconds); + + break; + case Item.LensOfTruth: + AlignmentVisible = new StringOptionItem(++id, $"Bargainer.{item}.DurationSwitch", Enum.GetNames(), (int)AlignmentVisibleOptions.UntilNextReveal, tab) + .SetParent(boolOpt); + + AlignmentVisibleDuration = new IntegerOptionItem(++id, $"Bargainer.{item}.Duration", new(1, 30, 1), 10, tab) + .SetParent(AlignmentVisible) + .SetValueFormat(OptionFormat.Seconds); + + break; } } + } + } - foreach (Item item in Enum.GetValues()) + public override void Init() + { + On = false; + + Gains = []; + + foreach (KeyValuePair kvp in MoneySettings) + if (kvp.Value.Enabled.GetBool()) + Gains[kvp.Key] = kvp.Value.Amount.GetInt(); + + Costs = []; + + foreach (KeyValuePair kvp in ItemSettings) + if (kvp.Value.Enabled.GetBool()) + Costs[kvp.Key] = kvp.Value.Cost.GetInt(); + + Costs[Item.None] = 0; + } + + public override void Add(byte playerId) + { + On = true; + + BargainerId = playerId; + Money = StartingMoney.GetInt(); + Update(); + + InShop = false; + SelectedItem = Item.None; + ActiveItems = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = ActiveItems.Any(x => x.Item == Item.EnergyDrink) ? ReducedKillCooldown.GetFloat() : KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } + + private void Update() + { + BargainerId.SetAbilityUseLimit(Money); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (Gains.TryGetValue(MoneyGainingAction.Kill, out int gain)) + { + Money += gain; + Update(); + } + } + + public override bool OnSabotage(PlayerControl pc) + { + if (InShop) + { + CycleItem(pc); + return false; + } + + if (Gains.TryGetValue(MoneyGainingAction.Sabotage, out int gain)) + { + Money += gain; + Update(); + } + + return true; + } + + private void CycleItem(PlayerControl pc) + { + List list = OrderedItems.ToList(); + SelectedItem = list[(list.IndexOf(SelectedItem) + 1) % list.Count]; + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 2, (int)SelectedItem); + } + + public override void OnPet(PlayerControl pc) + { + CycleItem(pc); + } + + public override bool OnVanish(PlayerControl pc) + { + CycleItem(pc); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + CycleItem(shapeshifter); + return false; + } + + public override void AfterMeetingTasks() + { + if (Gains.TryGetValue(MoneyGainingAction.SurviveMeeting, out int gain)) + { + Money += gain; + Update(); + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !pc.IsAlive()) + { + if (InShop || ActiveItems.Count > 0 || Money != 0) { - if (item == Item.None) continue; + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 1, false); + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 5); - OptionItem boolOpt = new BooleanOptionItem(++id, $"Bargainer.{item}.Enabled", true, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bargainer]); + InShop = false; + ActiveItems.Clear(); + Money = 0; + Update(); + } - OptionItem intOpt = new IntegerOptionItem(++id, $"Bargainer.{item}.Cost", new(0, 200, 5), GetDefaultValue(), tab) - .SetParent(boolOpt); + return; + } - SetupExtraSettings(); + bool wasInShop = InShop; + InShop = ShopLocations.Any(x => Vector2.Distance(pc.Pos(), x) < DisableDevice.UsableDistance); + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 1, InShop); - ItemSettings[item] = (boolOpt, intOpt); - continue; + switch (wasInShop) + { + case true when !InShop && ActiveItems.All(x => x.Item != SelectedItem) && Costs.TryGetValue(SelectedItem, out int cost) && cost <= Money: + { + (int duration, byte target) = GetData(SelectedItem); + ActiveItems.Add((SelectedItem, Utils.TimeStamp, duration, target)); + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 3, (int)SelectedItem, Utils.TimeStamp, duration, target); + Money -= cost; + Update(); - int GetDefaultValue() + pc.Notify(string.Format(Translator.GetString("Bargainer.Notify.PurchasedItem"), Translator.GetString($"Bargainer.{SelectedItem}"))); + + switch (SelectedItem) + { + case Item.EnergyDrink: + pc.ResetKillCooldown(); + pc.SyncSettings(); + if (Main.KillTimers[pc.PlayerId] > ReducedKillCooldown.GetFloat()) pc.SetKillCooldown(ReducedKillCooldown.GetFloat()); + + break; + case Item.LensOfTruth: + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: Utils.GetPlayerById(target)); + break; + } + + if (pc.IsLocalPlayer()) + PurchasedItems.Add(SelectedItem); + + break; + + static (int Duration, byte Target) GetData(Item item) { return item switch { - Item.EnergyDrink => 40, - Item.LensOfTruth => 60, - Item.BandAid => 20, + Item.EnergyDrink => (int.MaxValue, byte.MaxValue), + Item.LensOfTruth => (AlignmentVisibleValue, Main.AllAlivePlayerControls.RandomElement().PlayerId), + Item.BandAid => (ShieldDurationValue, byte.MaxValue), - _ => 0 + _ => (0, byte.MaxValue) }; } - - void SetupExtraSettings() - { - switch (item) - { - case Item.BandAid: - ShieldDuration = new StringOptionItem(++id, $"Bargainer.{item}.DurationSwitch", Enum.GetNames(), 0, tab) - .SetParent(boolOpt); - - ShieldTime = new IntegerOptionItem(++id, $"Bargainer.{item}.Duration", new(0, 60, 1), 20, tab) - .SetParent(ShieldDuration) - .SetValueFormat(OptionFormat.Seconds); - - break; - case Item.EnergyDrink: - ReducedKillCooldown = new FloatOptionItem(++id, $"Bargainer.{item}.ReducedKCD", new(0f, 180f, 0.5f), 17.5f, tab) - .SetParent(boolOpt) - .SetValueFormat(OptionFormat.Seconds); - - break; - case Item.LensOfTruth: - AlignmentVisible = new StringOptionItem(++id, $"Bargainer.{item}.DurationSwitch", Enum.GetNames(), (int)AlignmentVisibleOptions.UntilNextReveal, tab) - .SetParent(boolOpt); - - AlignmentVisibleDuration = new IntegerOptionItem(++id, $"Bargainer.{item}.Duration", new(1, 30, 1), 10, tab) - .SetParent(AlignmentVisible) - .SetValueFormat(OptionFormat.Seconds); - - break; - } - } + } + case false when InShop: + { + Func, bool> canBuy = x => x.Value <= Money; + IOrderedEnumerable> orderedItems = Costs.OrderByDescending(canBuy); + IEnumerable unAffordableItems = orderedItems.SkipWhile(canBuy).Select(x => x.Key).Prepend(Item.None); + IEnumerable affordableItems = orderedItems.TakeWhile(canBuy).Select(x => x.Key); + OrderedItems = affordableItems.Concat(unAffordableItems); + SelectedItem = OrderedItems.FirstOrDefault(); + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 2, (int)SelectedItem); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + break; } } - public override void Init() + int rc = ActiveItems.RemoveAll(x => x.Item == Item.None); + if (rc > 0) Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 6); + + (Item Item, long ActivateTimeStamp, int Duration, byte Target)[] array = ActiveItems.Where(x => x.Duration != int.MaxValue && x.ActivateTimeStamp + x.Duration < Utils.TimeStamp).ToArray(); + + for (var i = 0; i < array.Length; i++) { - On = false; - - Gains = []; - - foreach (KeyValuePair kvp in MoneySettings) - if (kvp.Value.Enabled.GetBool()) - Gains[kvp.Key] = kvp.Value.Amount.GetInt(); - - Costs = []; - - foreach (KeyValuePair kvp in ItemSettings) - if (kvp.Value.Enabled.GetBool()) - Costs[kvp.Key] = kvp.Value.Cost.GetInt(); - - Costs[Item.None] = 0; - } - - public override void Add(byte playerId) - { - On = true; - - BargainerId = playerId; - Money = StartingMoney.GetInt(); - Update(); - - InShop = false; - SelectedItem = Item.None; - ActiveItems = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = ActiveItems.Any(x => x.Item == Item.EnergyDrink) ? ReducedKillCooldown.GetFloat() : KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } - - private void Update() - { - BargainerId.SetAbilityUseLimit(Money); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (Gains.TryGetValue(MoneyGainingAction.Kill, out int gain)) - { - Money += gain; - Update(); - } - } - - public override bool OnSabotage(PlayerControl pc) - { - if (InShop) - { - CycleItem(pc); - return false; - } - - if (Gains.TryGetValue(MoneyGainingAction.Sabotage, out int gain)) - { - Money += gain; - Update(); - } - - return true; - } - - private void CycleItem(PlayerControl pc) - { - List list = OrderedItems.ToList(); - SelectedItem = list[(list.IndexOf(SelectedItem) + 1) % list.Count]; - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 2, (int)SelectedItem); - } - - public override void OnPet(PlayerControl pc) - { - CycleItem(pc); - } - - public override bool OnVanish(PlayerControl pc) - { - CycleItem(pc); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - CycleItem(shapeshifter); - return false; - } - - public override void AfterMeetingTasks() - { - if (Gains.TryGetValue(MoneyGainingAction.SurviveMeeting, out int gain)) - { - Money += gain; - Update(); - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !pc.IsAlive()) - { - if (InShop || ActiveItems.Count > 0 || Money != 0) - { - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 1, false); - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 5); - - InShop = false; - ActiveItems.Clear(); - Money = 0; - Update(); - } - - return; - } - - bool wasInShop = InShop; - InShop = ShopLocations.Any(x => Vector2.Distance(pc.Pos(), x) < DisableDevice.UsableDistance); - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 1, InShop); - - switch (wasInShop) - { - case true when !InShop && ActiveItems.All(x => x.Item != SelectedItem) && Costs.TryGetValue(SelectedItem, out int cost) && cost <= Money: - { - (int duration, byte target) = GetData(SelectedItem); - ActiveItems.Add((SelectedItem, Utils.TimeStamp, duration, target)); - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 3, (int)SelectedItem, Utils.TimeStamp, duration, target); - Money -= cost; - Update(); - - pc.Notify(string.Format(Translator.GetString("Bargainer.Notify.PurchasedItem"), Translator.GetString($"Bargainer.{SelectedItem}"))); - - switch (SelectedItem) - { - case Item.EnergyDrink: - pc.ResetKillCooldown(); - pc.SyncSettings(); - if (Main.KillTimers[pc.PlayerId] > ReducedKillCooldown.GetFloat()) pc.SetKillCooldown(ReducedKillCooldown.GetFloat()); - - break; - case Item.LensOfTruth: - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: Utils.GetPlayerById(target)); - break; - } - - if (pc.IsLocalPlayer()) - PurchasedItems.Add(SelectedItem); - - break; - - static (int Duration, byte Target) GetData(Item item) - { - return item switch - { - Item.EnergyDrink => (int.MaxValue, byte.MaxValue), - Item.LensOfTruth => (AlignmentVisibleValue, Main.AllAlivePlayerControls.RandomElement().PlayerId), - Item.BandAid => (ShieldDurationValue, byte.MaxValue), - - _ => (0, byte.MaxValue) - }; - } - } - case false when InShop: - { - Func, bool> canBuy = x => x.Value <= Money; - IOrderedEnumerable> orderedItems = Costs.OrderByDescending(canBuy); - IEnumerable unAffordableItems = orderedItems.SkipWhile(canBuy).Select(x => x.Key).Prepend(Item.None); - IEnumerable affordableItems = orderedItems.TakeWhile(canBuy).Select(x => x.Key); - OrderedItems = affordableItems.Concat(unAffordableItems); - SelectedItem = OrderedItems.FirstOrDefault(); - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 2, (int)SelectedItem); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - break; - } - } - - int rc = ActiveItems.RemoveAll(x => x.Item == Item.None); - if (rc > 0) Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 6); - - (Item Item, long ActivateTimeStamp, int Duration, byte Target)[] array = ActiveItems.Where(x => x.Duration != int.MaxValue && x.ActivateTimeStamp + x.Duration < Utils.TimeStamp).ToArray(); - - for (var i = 0; i < array.Length; i++) - { - (Item Item, long ActivateTimeStamp, int Duration, byte Target) item = array[i]; - ActiveItems.Remove(item); - Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 4, i); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: Utils.GetPlayerById(item.Target) ?? pc); - } - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return ActiveItems.All(x => x.Item != Item.BandAid); - } - - public override void OnReportDeadBody() - { - List indexesToRemove = []; - - for (var i = 0; i < ActiveItems.Count; i++) - { - switch (ActiveItems[i].Item) - { - case Item.LensOfTruth: - if ((AlignmentVisibleOptions)AlignmentVisible.GetValue() is AlignmentVisibleOptions.ForSpecifiedTime or AlignmentVisibleOptions.UntilNextMeeting) indexesToRemove.Add(i); - - break; - default: - indexesToRemove.Add(i); - break; - } - } - - foreach (int index in indexesToRemove) - { - ActiveItems.RemoveAt(index); - Utils.SendRPC(CustomRPC.SyncBargainer, BargainerId, 4, index); - } - - Utils.GetPlayerById(BargainerId).ResetKillCooldown(); - } - - public override bool KnowRole(PlayerControl seer, PlayerControl target) - { - if (base.KnowRole(seer, target)) return true; - - return Main.PlayerStates[seer.PlayerId].Role is Bargainer bg && bg.ActiveItems.Any(x => x.Target == target.PlayerId); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - if (Main.PlayerStates[playerId].Role is not Bargainer bg) return; - - switch (reader.ReadPackedInt32()) - { - case 1: - bg.InShop = reader.ReadBoolean(); - break; - case 2: - bg.SelectedItem = (Item)reader.ReadPackedInt32(); - break; - case 3: - bg.ActiveItems.Add(((Item)reader.ReadPackedInt32(), long.Parse(reader.ReadString()), reader.ReadPackedInt32(), reader.ReadByte())); - break; - case 4: - bg.ActiveItems.RemoveAt(reader.ReadPackedInt32()); - break; - case 5: - bg.ActiveItems.Clear(); - break; - case 6: - bg.ActiveItems.RemoveAll(x => x.Item == Item.None); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Bargainer bg) return string.Empty; - - var result = string.Empty; - - if (bg.InShop) - { - result += string.Format( - Translator.GetString("Bargainer.Suffix.InShop"), - Translator.GetString($"Bargainer.{bg.SelectedItem}"), - Utils.ColorString(bg.Money >= Costs[bg.SelectedItem] ? Color.white : Color.red, $"{Costs[bg.SelectedItem]}")); - - result += "\n"; - } - - if (seer.IsModClient()) result += ""; - - result += string.Join(' ', bg.ActiveItems.Select(x => - { - long timeLeft = x.Duration - (Utils.TimeStamp - x.ActivateTimeStamp) + 1; - string icon = Icons[x.Item]; - if (x.Item == Item.LensOfTruth && x.Target != byte.MaxValue) icon = Utils.ColorString(Main.PlayerColors[x.Target], icon); - - return seer.IsModClient() && timeLeft < 10 ? $"{icon} ({timeLeft}s)" : icon; - })); - - if (seer.IsModClient()) result += ""; - - return result; - } - - private enum AlignmentVisibleOptions - { - Forever, - UntilNextMeeting, - UntilNextReveal, - ForSpecifiedTime - } - - private enum ShieldDurationOptions - { - UntilNextMeeting, - ForSpecifiedTime - } - - private enum MoneyGainingAction - { - Kill, - Sabotage, - SurviveMeeting + (Item Item, long ActivateTimeStamp, int Duration, byte Target) item = array[i]; + ActiveItems.Remove(item); + Utils.SendRPC(CustomRPC.SyncBargainer, pc.PlayerId, 4, i); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: Utils.GetPlayerById(item.Target) ?? pc); } } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return ActiveItems.All(x => x.Item != Item.BandAid); + } + + public override void OnReportDeadBody() + { + List indexesToRemove = []; + + for (var i = 0; i < ActiveItems.Count; i++) + { + switch (ActiveItems[i].Item) + { + case Item.LensOfTruth: + if ((AlignmentVisibleOptions)AlignmentVisible.GetValue() is AlignmentVisibleOptions.ForSpecifiedTime or AlignmentVisibleOptions.UntilNextMeeting) indexesToRemove.Add(i); + + break; + default: + indexesToRemove.Add(i); + break; + } + } + + foreach (int index in indexesToRemove) + { + ActiveItems.RemoveAt(index); + Utils.SendRPC(CustomRPC.SyncBargainer, BargainerId, 4, index); + } + + Utils.GetPlayerById(BargainerId).ResetKillCooldown(); + } + + public override bool KnowRole(PlayerControl seer, PlayerControl target) + { + if (base.KnowRole(seer, target)) return true; + + return Main.PlayerStates[seer.PlayerId].Role is Bargainer bg && bg.ActiveItems.Any(x => x.Target == target.PlayerId); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + if (Main.PlayerStates[playerId].Role is not Bargainer bg) return; + + switch (reader.ReadPackedInt32()) + { + case 1: + bg.InShop = reader.ReadBoolean(); + break; + case 2: + bg.SelectedItem = (Item)reader.ReadPackedInt32(); + break; + case 3: + bg.ActiveItems.Add(((Item)reader.ReadPackedInt32(), long.Parse(reader.ReadString()), reader.ReadPackedInt32(), reader.ReadByte())); + break; + case 4: + bg.ActiveItems.RemoveAt(reader.ReadPackedInt32()); + break; + case 5: + bg.ActiveItems.Clear(); + break; + case 6: + bg.ActiveItems.RemoveAll(x => x.Item == Item.None); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Bargainer bg) return string.Empty; + + var result = string.Empty; + + if (bg.InShop) + { + result += string.Format( + Translator.GetString("Bargainer.Suffix.InShop"), + Translator.GetString($"Bargainer.{bg.SelectedItem}"), + Utils.ColorString(bg.Money >= Costs[bg.SelectedItem] ? Color.white : Color.red, $"{Costs[bg.SelectedItem]}")); + + result += "\n"; + } + + if (seer.IsModClient()) result += ""; + + result += string.Join(' ', bg.ActiveItems.Select(x => + { + long timeLeft = x.Duration - (Utils.TimeStamp - x.ActivateTimeStamp) + 1; + string icon = Icons[x.Item]; + if (x.Item == Item.LensOfTruth && x.Target != byte.MaxValue) icon = Utils.ColorString(Main.PlayerColors[x.Target], icon); + + return seer.IsModClient() && timeLeft < 10 ? $"{icon} ({timeLeft}s)" : icon; + })); + + if (seer.IsModClient()) result += ""; + + return result; + } + + private enum AlignmentVisibleOptions + { + Forever, + UntilNextMeeting, + UntilNextReveal, + ForSpecifiedTime + } + + private enum ShieldDurationOptions + { + UntilNextMeeting, + ForSpecifiedTime + } + + private enum MoneyGainingAction + { + Kill, + Sabotage, + SurviveMeeting + } } \ No newline at end of file diff --git a/Roles/Neutral/Beehive.cs b/Roles/Neutral/Beehive.cs index 02a62f2cf..cf6027ea2 100644 --- a/Roles/Neutral/Beehive.cs +++ b/Roles/Neutral/Beehive.cs @@ -5,156 +5,155 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Beehive : RoleBase { - public class Beehive : RoleBase + public static bool On; + + private static OptionItem Distance; + private static OptionItem Time; + private static OptionItem StingCooldown; + private static OptionItem StungPlayersDieOnMeeting; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + + private byte BeehiveId; + public Dictionary StungPlayers = []; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + var id = 647350; + Options.SetupRoleOptions(id++, TabGroup.NeutralRoles, CustomRoles.Beehive); - private static OptionItem Distance; - private static OptionItem Time; - private static OptionItem StingCooldown; - private static OptionItem StungPlayersDieOnMeeting; - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + Distance = new FloatOptionItem(++id, "Beehive.Distance", new(0f, 20f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); - private byte BeehiveId; - public Dictionary StungPlayers = []; + Time = new FloatOptionItem(++id, "Beehive.Time", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + StingCooldown = new FloatOptionItem(++id, "Beehive.StingCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + StungPlayersDieOnMeeting = new BooleanOptionItem(++id, "Beehive.StungPlayersDieOnMeeting", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + + KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) + .SetValueFormat(OptionFormat.Seconds); + + CanVent = new BooleanOptionItem(++id, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + + HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + BeehiveId = playerId; + StungPlayers = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + return killer.CheckDoubleTrigger(target, () => { - var id = 647350; - Options.SetupRoleOptions(id++, TabGroup.NeutralRoles, CustomRoles.Beehive); + Vector2 pos = target.Pos(); + StungPlayers[target.PlayerId] = (Utils.TimeStamp, pos); + killer.SetKillCooldown(StingCooldown.GetFloat()); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.SendRPC(CustomRPC.SyncRoleData, BeehiveId, 1, target.PlayerId, pos); + target.Notify(string.Format(Translator.GetString("Beehive.Notify"), Math.Round(Distance.GetFloat(), 1), Math.Round(Time.GetFloat(), 1)), 8f); + }); + } - Distance = new FloatOptionItem(++id, "Beehive.Distance", new(0f, 20f, 0.5f), 15f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - Time = new FloatOptionItem(++id, "Beehive.Time", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) - .SetValueFormat(OptionFormat.Seconds); - - StingCooldown = new FloatOptionItem(++id, "Beehive.StingCooldown", new(0f, 180f, 0.5f), 5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) - .SetValueFormat(OptionFormat.Seconds); - - StungPlayersDieOnMeeting = new BooleanOptionItem(++id, "Beehive.StungPlayersDieOnMeeting", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); - - KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(++id, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); - - HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Beehive]); - } - - public override void Init() + if (StungPlayers.TryGetValue(pc.PlayerId, out (long TimeStamp, Vector2 InitialPosition) sp)) { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - BeehiveId = playerId; - StungPlayers = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - return killer.CheckDoubleTrigger(target, () => + if (Utils.TimeStamp - sp.TimeStamp >= Time.GetFloat()) { - Vector2 pos = target.Pos(); - StungPlayers[target.PlayerId] = (Utils.TimeStamp, pos); - killer.SetKillCooldown(StingCooldown.GetFloat()); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.SendRPC(CustomRPC.SyncRoleData, BeehiveId, 1, target.PlayerId, pos); - target.Notify(string.Format(Translator.GetString("Beehive.Notify"), Math.Round(Distance.GetFloat(), 1), Math.Round(Time.GetFloat(), 1)), 8f); - }); - } + StungPlayers.Remove(pc.PlayerId); + Utils.SendRPC(CustomRPC.SyncRoleData, BeehiveId, 2, pc.PlayerId); - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (lowLoad || !pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return; - - if (StungPlayers.TryGetValue(pc.PlayerId, out (long TimeStamp, Vector2 InitialPosition) sp)) - { - if (Utils.TimeStamp - sp.TimeStamp >= Time.GetFloat()) + if (Vector2.Distance(pc.Pos(), sp.InitialPosition) < Distance.GetFloat()) { - StungPlayers.Remove(pc.PlayerId); - Utils.SendRPC(CustomRPC.SyncRoleData, BeehiveId, 2, pc.PlayerId); + pc.Suicide(realKiller: Utils.GetPlayerById(BeehiveId)); - if (Vector2.Distance(pc.Pos(), sp.InitialPosition) < Distance.GetFloat()) - { - pc.Suicide(realKiller: Utils.GetPlayerById(BeehiveId)); - - if (pc.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } + if (pc.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); } - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - } - - public override void OnReportDeadBody() - { - if (StungPlayersDieOnMeeting.GetBool()) - { - StungPlayers.Keys.Select(x => Utils.GetPlayerById(x)).DoIf( - x => x != null && x.IsAlive(), - x => x.Suicide(realKiller: Utils.GetPlayerById(BeehiveId))); } - StungPlayers.Clear(); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - StungPlayers[reader.ReadByte()] = (Utils.TimeStamp, reader.ReadVector2()); - break; - case 2: - StungPlayers.Remove(reader.ReadByte()); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || meeting || hud || !StungPlayers.TryGetValue(seer.PlayerId, out (long TimeStamp, Vector2 InitialPosition) sp)) return string.Empty; - - double walked = Math.Round(Vector2.Distance(seer.Pos(), sp.InitialPosition), 1); - double distance = Math.Round(Distance.GetFloat(), 1); - long time = Time.GetInt() - (Utils.TimeStamp - sp.TimeStamp); - string color = walked >= distance ? "<#00ffa5>" : "<#ffa500>"; - string color2 = walked >= distance ? "<#00ffff>" : "<#ffff00>"; - return $"{color}{walked}{color2}/{distance} <#ffffff>({time}s)"; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); } } + + public override void OnReportDeadBody() + { + if (StungPlayersDieOnMeeting.GetBool()) + { + StungPlayers.Keys.Select(x => Utils.GetPlayerById(x)).DoIf( + x => x != null && x.IsAlive(), + x => x.Suicide(realKiller: Utils.GetPlayerById(BeehiveId))); + } + + StungPlayers.Clear(); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + StungPlayers[reader.ReadByte()] = (Utils.TimeStamp, reader.ReadVector2()); + break; + case 2: + StungPlayers.Remove(reader.ReadByte()); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || meeting || hud || !StungPlayers.TryGetValue(seer.PlayerId, out (long TimeStamp, Vector2 InitialPosition) sp)) return string.Empty; + + double walked = Math.Round(Vector2.Distance(seer.Pos(), sp.InitialPosition), 1); + double distance = Math.Round(Distance.GetFloat(), 1); + long time = Time.GetInt() - (Utils.TimeStamp - sp.TimeStamp); + string color = walked >= distance ? "<#00ffa5>" : "<#ffa500>"; + string color2 = walked >= distance ? "<#00ffff>" : "<#ffff00>"; + return $"{color}{walked}{color2}/{distance} <#ffffff>({time}s)"; + } } \ No newline at end of file diff --git a/Roles/Neutral/BloodKnight.cs b/Roles/Neutral/BloodKnight.cs index 89e577274..53a4f6edb 100644 --- a/Roles/Neutral/BloodKnight.cs +++ b/Roles/Neutral/BloodKnight.cs @@ -1,34 +1,33 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class BloodKnight : RoleBase { - public class BloodKnight : RoleBase + private const int Id = 11800; + + public static OptionItem KillCooldown; + public static OptionItem CanVent; + public static OptionItem HasImpostorVision; + public static OptionItem ProtectDuration; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 11800; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.BloodKnight); - public static OptionItem KillCooldown; - public static OptionItem CanVent; - public static OptionItem HasImpostorVision; - public static OptionItem ProtectDuration; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.BloodKnight); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]); - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]); - - ProtectDuration = new FloatOptionItem(Id + 14, "BKProtectDuration", new(1f, 30f, 1f), 15f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + ProtectDuration = new FloatOptionItem(Id + 14, "BKProtectDuration", new(1f, 30f, 1f), 15f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.BloodKnight]) + .SetValueFormat(OptionFormat.Seconds); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Bubble.cs b/Roles/Neutral/Bubble.cs index d68d2f9cf..e57b4a0db 100644 --- a/Roles/Neutral/Bubble.cs +++ b/Roles/Neutral/Bubble.cs @@ -6,193 +6,192 @@ using Hazel; using static EHR.Options; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Bubble : RoleBase { - internal class Bubble : RoleBase + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + public static OptionItem CanVent; + public static OptionItem NotifyDelay; + private static OptionItem ExplodeDelay; + private static OptionItem BubbleDiesIfInRange; + private static OptionItem ExplosionRadius; + + public static readonly Dictionary EncasedPlayers = []; + private static readonly Dictionary LastUpdates = []; + private byte BubbleId = byte.MaxValue; + private static int Id => 643220; + + private PlayerControl BubblePC => GetPlayerById(BubbleId); + + public override bool IsEnable => BubbleId != byte.MaxValue; + + public override void SetupCustomOption() { - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - public static OptionItem CanVent; - public static OptionItem NotifyDelay; - private static OptionItem ExplodeDelay; - private static OptionItem BubbleDiesIfInRange; - private static OptionItem ExplosionRadius; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Bubble); - public static readonly Dictionary EncasedPlayers = []; - private static readonly Dictionary LastUpdates = []; - private byte BubbleId = byte.MaxValue; - private static int Id => 643220; + KillCooldown = new FloatOptionItem(Id + 2, "BubbleCD", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) + .SetValueFormat(OptionFormat.Seconds); - private PlayerControl BubblePC => GetPlayerById(BubbleId); + HasImpostorVision = new BooleanOptionItem(Id + 6, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]); - public override bool IsEnable => BubbleId != byte.MaxValue; + NotifyDelay = new IntegerOptionItem(Id + 3, "BubbleTargetNotifyDelay", new(0, 60, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Bubble); + ExplodeDelay = new IntegerOptionItem(Id + 4, "BubbleExplosionDelay", new(0, 60, 1), 10, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) + .SetValueFormat(OptionFormat.Seconds); - KillCooldown = new FloatOptionItem(Id + 2, "BubbleCD", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) - .SetValueFormat(OptionFormat.Seconds); + BubbleDiesIfInRange = new BooleanOptionItem(Id + 5, "BubbleDiesIfInRange", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]); - HasImpostorVision = new BooleanOptionItem(Id + 6, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]); + ExplosionRadius = new FloatOptionItem(Id + 7, "BubbleExplosionRadius", new(0.1f, 5f, 0.1f), 3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) + .SetValueFormat(OptionFormat.Multiplier); - NotifyDelay = new IntegerOptionItem(Id + 3, "BubbleTargetNotifyDelay", new(0, 60, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) - .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(Id + 8, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]); + } - ExplodeDelay = new IntegerOptionItem(Id + 4, "BubbleExplosionDelay", new(0, 60, 1), 10, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + BubbleId = byte.MaxValue; + EncasedPlayers.Clear(); + LastUpdates.Clear(); + } - BubbleDiesIfInRange = new BooleanOptionItem(Id + 5, "BubbleDiesIfInRange", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]); + public override void Add(byte playerId) + { + BubbleId = playerId; + } - ExplosionRadius = new FloatOptionItem(Id + 7, "BubbleExplosionRadius", new(0.1f, 5f, 0.1f), 3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]) - .SetValueFormat(OptionFormat.Multiplier); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - CanVent = new BooleanOptionItem(Id + 8, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Bubble]); - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } - public override void Init() - { - BubbleId = byte.MaxValue; + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + private void SendRPC(byte id = byte.MaxValue, bool remove = false, bool clear = false) + { + if (!IsEnable || !DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncBubble, SendOption.Reliable); + writer.Write(remove); + writer.Write(clear); + writer.Write(id); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + bool remove = reader.ReadBoolean(); + bool clear = reader.ReadBoolean(); + byte id = reader.ReadByte(); + + if (clear) EncasedPlayers.Clear(); - LastUpdates.Clear(); - } + else if (remove) + EncasedPlayers.Remove(id); + else + EncasedPlayers.Add(id, TimeStamp); + } - public override void Add(byte playerId) + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable || target == null) return false; + + EncasedPlayers.Add(target.PlayerId, TimeStamp); + SendRPC(target.PlayerId); + BubblePC.SetKillCooldown(); + return false; + } + + public override void OnGlobalFixedUpdate(PlayerControl encasedPc, bool lowLoad) + { + if (lowLoad || !IsEnable || !GameStates.IsInTask || !EncasedPlayers.TryGetValue(encasedPc.PlayerId, out long ts)) return; + + long now = TimeStamp; + byte id = encasedPc.PlayerId; + + if (ts + ExplodeDelay.GetInt() < now) { - BubbleId = playerId; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - private void SendRPC(byte id = byte.MaxValue, bool remove = false, bool clear = false) - { - if (!IsEnable || !DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncBubble, SendOption.Reliable); - writer.Write(remove); - writer.Write(clear); - writer.Write(id); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - bool remove = reader.ReadBoolean(); - bool clear = reader.ReadBoolean(); - byte id = reader.ReadByte(); - - if (clear) - EncasedPlayers.Clear(); - else if (remove) - EncasedPlayers.Remove(id); - else - EncasedPlayers.Add(id, TimeStamp); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable || target == null) return false; - - EncasedPlayers.Add(target.PlayerId, TimeStamp); - SendRPC(target.PlayerId); - BubblePC.SetKillCooldown(); - return false; - } - - public override void OnGlobalFixedUpdate(PlayerControl encasedPc, bool lowLoad) - { - if (lowLoad || !IsEnable || !GameStates.IsInTask || !EncasedPlayers.TryGetValue(encasedPc.PlayerId, out long ts)) return; - - long now = TimeStamp; - byte id = encasedPc.PlayerId; - - if (ts + ExplodeDelay.GetInt() < now) + if (!encasedPc.IsAlive()) { - if (!encasedPc.IsAlive()) - { - EncasedPlayers.Remove(id); - SendRPC(id, true); - return; - } - - IEnumerable players = GetPlayersInRadius(ExplosionRadius.GetFloat(), encasedPc.Pos()); - - int numDied = 0; - - foreach (PlayerControl pc in players) - { - if (pc == null) continue; - - if (pc.PlayerId == BubbleId) - { - if (BubbleDiesIfInRange.GetBool()) - { - LateTask.New(() => - { - if (GameStates.IsInTask) pc.Suicide(PlayerState.DeathReason.Bombed); - }, 0.5f, log: false); - } - - continue; - } - - pc.Suicide(PlayerState.DeathReason.Bombed, BubblePC); - numDied++; - } - - if (BubbleId == PlayerControl.LocalPlayer.PlayerId && numDied >= 5) - Achievements.Type.SorryToBurstYourBubble.CompleteAfterGameEnd(); - EncasedPlayers.Remove(id); SendRPC(id, true); return; } - if (ts + NotifyDelay.GetInt() < now) + IEnumerable players = GetPlayersInRadius(ExplosionRadius.GetFloat(), encasedPc.Pos()); + + int numDied = 0; + + foreach (PlayerControl pc in players) { - Main.AllAlivePlayerControls.Where(x => (!LastUpdates.TryGetValue(x.PlayerId, out long la) || la != now) && Vector2.Distance(x.Pos(), encasedPc.Pos()) < 5f).Do(x => + if (pc == null) continue; + + if (pc.PlayerId == BubbleId) { - NotifyRoles(SpecifySeer: x, SpecifyTarget: encasedPc); - LastUpdates[x.PlayerId] = now; - }); + if (BubbleDiesIfInRange.GetBool()) + { + LateTask.New(() => + { + if (GameStates.IsInTask) pc.Suicide(PlayerState.DeathReason.Bombed); + }, 0.5f, log: false); + } + + continue; + } + + pc.Suicide(PlayerState.DeathReason.Bombed, BubblePC); + numDied++; } + + if (BubbleId == PlayerControl.LocalPlayer.PlayerId && numDied >= 5) + Achievements.Type.SorryToBurstYourBubble.CompleteAfterGameEnd(); + + EncasedPlayers.Remove(id); + SendRPC(id, true); + return; } - public override void OnReportDeadBody() + if (ts + NotifyDelay.GetInt() < now) { - if (!IsEnable) return; - - foreach (PlayerControl pc in EncasedPlayers.Keys.Select(x => GetPlayerById(x)).Where(x => x != null && x.IsAlive())) pc.Suicide(PlayerState.DeathReason.Bombed, BubblePC); - - EncasedPlayers.Clear(); - SendRPC(clear: true); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (target == null || !EncasedPlayers.TryGetValue(target.PlayerId, out long ts) || (ts + NotifyDelay.GetInt() >= TimeStamp && !seer.Is(CustomRoles.Bubble))) return string.Empty; - - return ColorString(GetRoleColor(CustomRoles.Bubble), $"⚠ {ExplodeDelay.GetInt() - (TimeStamp - ts) + 1}"); + Main.AllAlivePlayerControls.Where(x => (!LastUpdates.TryGetValue(x.PlayerId, out long la) || la != now) && Vector2.Distance(x.Pos(), encasedPc.Pos()) < 5f).Do(x => + { + NotifyRoles(SpecifySeer: x, SpecifyTarget: encasedPc); + LastUpdates[x.PlayerId] = now; + }); } } + + public override void OnReportDeadBody() + { + if (!IsEnable) return; + + foreach (PlayerControl pc in EncasedPlayers.Keys.Select(x => GetPlayerById(x)).Where(x => x != null && x.IsAlive())) pc.Suicide(PlayerState.DeathReason.Bombed, BubblePC); + + EncasedPlayers.Clear(); + SendRPC(clear: true); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (target == null || !EncasedPlayers.TryGetValue(target.PlayerId, out long ts) || (ts + NotifyDelay.GetInt() >= TimeStamp && !seer.Is(CustomRoles.Bubble))) return string.Empty; + + return ColorString(GetRoleColor(CustomRoles.Bubble), $"⚠ {ExplodeDelay.GetInt() - (TimeStamp - ts) + 1}"); + } } \ No newline at end of file diff --git a/Roles/Neutral/Chemist.cs b/Roles/Neutral/Chemist.cs index 7f70a1a98..bdfcddbc8 100644 --- a/Roles/Neutral/Chemist.cs +++ b/Roles/Neutral/Chemist.cs @@ -7,696 +7,695 @@ using Hazel; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Chemist : RoleBase { - internal class Chemist : RoleBase + public static bool On; + public static List Instances = []; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + + private static OptionItem AirGainedPerSecond; + private static OptionItem WaterGainedPerSecond; + private static OptionItem CoalGainedPerVent; + private static OptionItem IronOreGainedPerVent; + + private static Dictionary FinalProductUsageAmounts = []; + + private static OptionItem AcidPlayersDie; + private static OptionItem AcidPlayersDieAfterTime; + private static OptionItem BlindDuration; + private static OptionItem GrenadeExplodeDelay; + private static OptionItem GrenadeExplodeRadius; + + private static readonly Dictionary Ingredients, List<(int Count, Item Item)> Results)>> Processes = new() { - public static bool On; - public static List Instances = []; - - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - - private static OptionItem AirGainedPerSecond; - private static OptionItem WaterGainedPerSecond; - private static OptionItem CoalGainedPerVent; - private static OptionItem IronOreGainedPerVent; - - private static Dictionary FinalProductUsageAmounts = []; - - private static OptionItem AcidPlayersDie; - private static OptionItem AcidPlayersDieAfterTime; - private static OptionItem BlindDuration; - private static OptionItem GrenadeExplodeDelay; - private static OptionItem GrenadeExplodeRadius; - - private static readonly Dictionary Ingredients, List<(int Count, Item Item)> Results)>> Processes = new() + [Factory.None] = [], + [Factory.ChemicalPlant] = new() { - [Factory.None] = [], - [Factory.ChemicalPlant] = new() + ["Synthesis Sulfur"] = ([(60, Item.HydrogenSulfideGas), (40, Item.OxygenGas)], [(3, Item.Sulfur)]), + ["Synthesis Of Naphtha"] = ([(150, Item.SynthesisGas), (50, Item.CarbonMonoxide)], [(100, Item.Naphtha)]), + ["Reversed Water Gas Shift"] = ([(50, Item.HydrogenGas), (50, Item.CarbonDioxide)], [(50, Item.PurifiedWater), (50, Item.CarbonMonoxide)]), + ["Synthesis Sulfur Dioxide"] = ([(1, Item.Sulfur), (60, Item.OxygenGas)], [(60, Item.SulfurDioxideGas)]), + ["Synthesis Sulfuric Acid"] = ([(90, Item.SulfurDioxideGas), (40, Item.PurifiedWater)], [(60, Item.SulfuricAcid)]), + ["Synthesis to Methanol"] = ([(100, Item.SynthesisGas), (40, Item.CarbonDioxide)], [(20, Item.PurifiedWater), (80, Item.MethanolGas)]), + ["Synthesis Gas Separation"] = ([(100, Item.SynthesisGas)], [(40, Item.CarbonMonoxide), (60, Item.HydrogenGas)]), + ["Synthesis Gas Reforming"] = ([(60, Item.CarbonMonoxide), (90, Item.HydrogenGas)], [(100, Item.SynthesisGas)]), + ["Methylamine Gas"] = ([(50, Item.MethanolGas), (250, Item.AmmoniaGas)], [(200, Item.MethylamineGas), (50, Item.PurifiedWater)]), + ["Synthesis Methanol"] = ([(100, Item.CarbonDioxide), (100, Item.HydrogenGas)], [(100, Item.MethanolGas)]), + ["Air Separation"] = ([(100, Item.Air)], [(50, Item.OxygenGas), (50, Item.NitrogenGas)]) + }, + [Factory.AdvancedChemicalPlant] = new() + { + ["Cracking of Naphtha to Mineral Oil"] = ([(60, Item.Naphtha), (20, Item.ThermalWater), (20, Item.CarbonMonoxide)], [(100, Item.BaseMineralOil)]), + ["Coal Cracking Fischer Tropsch Process"] = ([(5, Item.Coal), (50, Item.Steam), (50, Item.OxygenGas)], [(100, Item.SynthesisGas), (20, Item.CarbonDioxide), (30, Item.HydrogenSulfideGas)]), + ["Explosives"] = ([(1, Item.Sulfur), (1, Item.Coal), (10, Item.Water)], [(2, Item.Explosive)]) + }, + [Factory.SteamCracker] = new() + { + ["Steam Cracking Mineral Oil to Synthesis Gas"] = ([(100, Item.BaseMineralOil), (100, Item.Steam)], [(200, Item.SynthesisGas)]) + }, + [Factory.BlastFurnace] = new() + { + ["Iron Ore Smelting"] = ([(24, Item.IronOre)], [(24, Item.IronIngot)]) + }, + [Factory.InductionFurnace] = new() + { + ["Iron Melting"] = ([(12, Item.IronIngot)], [(120, Item.MoltenIron)]) + }, + [Factory.CastingMachine] = new() + { + ["Iron Plate Casting"] = ([(40, Item.MoltenIron)], [(4, Item.IronPlate)]) + }, + [Factory.Electrolyzer] = new() + { + ["Purified Water Electrolysis"] = ([(100, Item.PurifiedWater)], [(40, Item.OxygenGas), (60, Item.HydrogenGas)]) + }, + [Factory.CoolingTower] = new() + { + ["Steam Cooling"] = ([(100, Item.Steam)], [(100, Item.PurifiedWater)]) + }, + [Factory.WaterTreatmentPlant] = new() + { + ["Water Purification"] = ([(150, Item.Water)], [(100, Item.PurifiedWater)]), + ["Water Boiling"] = ([(100, Item.Water), (1, Item.Coal)], [(60, Item.Steam)]) + }, + [Factory.Liquifier] = new() + { + ["Coal Liquefaction"] = ([(1, Item.Coal)], [(50, Item.CarbonDioxide)]) + }, + [Factory.AssemblingMachine] = new() + { + ["Grenade"] = ([(5, Item.IronPlate), (10, Item.Coal)], [(1, Item.Grenade)]) + } + }; + + private static Dictionary FactoryLocations = []; + + private Dictionary OtherAcidPlayers, long TimeStamp)> AcidPlayers; + private HashSet BombedBodies; + + public PlayerControl ChemistPC; + private Factory CurrentFactory; + private Dictionary Grenades; + public bool IsBlinding; + private Dictionary ItemCounts; + private long LastUpdate; + private string SelectedProcess; + private List SortedAvailableProcesses; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + var id = 17670; + const TabGroup tab = TabGroup.NeutralRoles; + + SetupRoleOptions(id++, tab, CustomRoles.Chemist); + + KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Seconds); + + HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]); + + AirGainedPerSecond = new IntegerOptionItem(++id, "Chemist.AirGainedPerSecond", new(5, 100, 5), 10, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Times); + + WaterGainedPerSecond = new IntegerOptionItem(++id, "Chemist.WaterGainedPerSecond", new(5, 100, 5), 10, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Times); + + CoalGainedPerVent = new IntegerOptionItem(++id, "Chemist.CoalGainedPerVent", new(1, 10, 1), 1, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Times); + + IronOreGainedPerVent = new IntegerOptionItem(++id, "Chemist.IronOreGainedPerVent", new(1, 50, 1), 4, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Times); + + FinalProductUsageAmounts = Enum.GetValues() + .Where(x => GetItemType(x) == ItemType.FinalProduct) + .ToDictionary(x => x, x => new IntegerOptionItem(++id, $"Chemist.Item.{x}.Usage", new(1, 100, 1), GetDefaultValue(x), tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Times)); + + AcidPlayersDie = new StringOptionItem(++id, "Chemist.AcidPlayersDie", Enum.GetNames(), 0, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]); + + AcidPlayersDieAfterTime = new IntegerOptionItem(++id, "Chemist.AcidPlayersDieAfterTime", new(1, 60, 1), 15, tab) + .SetParent(AcidPlayersDie) + .SetValueFormat(OptionFormat.Seconds); + + BlindDuration = new IntegerOptionItem(++id, "Chemist.BlindDuration", new(1, 60, 1), 10, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Seconds); + + GrenadeExplodeDelay = new IntegerOptionItem(++id, "Chemist.GrenadeExplodeDelay", new(1, 60, 1), 5, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Seconds); + + GrenadeExplodeRadius = new FloatOptionItem(++id, "Chemist.GrenadeExplodeRadius", new(0.25f, 10f, 0.25f), 4f, tab) + .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) + .SetValueFormat(OptionFormat.Multiplier); + + return; + + static int GetDefaultValue(Item item) + { + return item switch { - ["Synthesis Sulfur"] = ([(60, Item.HydrogenSulfideGas), (40, Item.OxygenGas)], [(3, Item.Sulfur)]), - ["Synthesis Of Naphtha"] = ([(150, Item.SynthesisGas), (50, Item.CarbonMonoxide)], [(100, Item.Naphtha)]), - ["Reversed Water Gas Shift"] = ([(50, Item.HydrogenGas), (50, Item.CarbonDioxide)], [(50, Item.PurifiedWater), (50, Item.CarbonMonoxide)]), - ["Synthesis Sulfur Dioxide"] = ([(1, Item.Sulfur), (60, Item.OxygenGas)], [(60, Item.SulfurDioxideGas)]), - ["Synthesis Sulfuric Acid"] = ([(90, Item.SulfurDioxideGas), (40, Item.PurifiedWater)], [(60, Item.SulfuricAcid)]), - ["Synthesis to Methanol"] = ([(100, Item.SynthesisGas), (40, Item.CarbonDioxide)], [(20, Item.PurifiedWater), (80, Item.MethanolGas)]), - ["Synthesis Gas Separation"] = ([(100, Item.SynthesisGas)], [(40, Item.CarbonMonoxide), (60, Item.HydrogenGas)]), - ["Synthesis Gas Reforming"] = ([(60, Item.CarbonMonoxide), (90, Item.HydrogenGas)], [(100, Item.SynthesisGas)]), - ["Methylamine Gas"] = ([(50, Item.MethanolGas), (250, Item.AmmoniaGas)], [(200, Item.MethylamineGas), (50, Item.PurifiedWater)]), - ["Synthesis Methanol"] = ([(100, Item.CarbonDioxide), (100, Item.HydrogenGas)], [(100, Item.MethanolGas)]), - ["Air Separation"] = ([(100, Item.Air)], [(50, Item.OxygenGas), (50, Item.NitrogenGas)]) - }, - [Factory.AdvancedChemicalPlant] = new() + Item.Explosive => 1, + Item.Grenade => 1, + Item.SulfuricAcid => 30, + Item.MethylamineGas => 100, + _ => 0 + }; + } + } + + public override void Init() + { + On = false; + Instances = []; + + FactoryLocations = []; + + LateTask.New(() => + { + FactoryLocations = ShipStatus.Instance.AllRooms + .Select(x => Translator.GetString($"{x.RoomId}")) + .Distinct() + .Zip(Enum.GetValues()[1..]) + .ToDictionary(x => x.First, x => x.Second); + }, 20f, log: false); + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + + ChemistPC = Utils.GetPlayerById(playerId); + LastUpdate = Utils.TimeStamp + 8; + ItemCounts = []; + CurrentFactory = Factory.None; + SelectedProcess = string.Empty; + SortedAvailableProcesses = []; + + AcidPlayers = []; + IsBlinding = false; + BombedBodies = []; + Grenades = []; + + ItemCounts = Enum.GetValues().ToDictionary(x => x, _ => 0); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } + + private static ItemType GetItemType(Item item) + { + return item switch + { + Item.Air or Item.Coal or Item.IronOre or Item.Water => ItemType.BasicResource, + Item.Explosive or Item.Grenade or Item.SulfuricAcid or Item.MethylamineGas => ItemType.FinalProduct, + _ => ItemType.IntermediateProduct + }; + } + + private static string GetChemicalForm(Item item) + { + return item switch + { + Item.AmmoniaGas => "NH3", + Item.CarbonDioxide => "CO2", + Item.CarbonMonoxide => "CO", + Item.HydrogenGas => "H2", + Item.HydrogenSulfideGas => "H2S", + Item.MethanolGas => "CH3OH", + Item.MethylamineGas => "CH3NH2", + Item.NitrogenGas => "N2", + Item.OxygenGas => "O2", + Item.PurifiedWater => "H2O", + Item.Sulfur => "S", + Item.SulfurDioxideGas => "SO2", + Item.SulfuricAcid => "H2SO4", + Item.SynthesisGas => "H2+CO", + + _ => Translator.GetString($"Chemist.Item.{item}") switch { - ["Cracking of Naphtha to Mineral Oil"] = ([(60, Item.Naphtha), (20, Item.ThermalWater), (20, Item.CarbonMonoxide)], [(100, Item.BaseMineralOil)]), - ["Coal Cracking Fischer Tropsch Process"] = ([(5, Item.Coal), (50, Item.Steam), (50, Item.OxygenGas)], [(100, Item.SynthesisGas), (20, Item.CarbonDioxide), (30, Item.HydrogenSulfideGas)]), - ["Explosives"] = ([(1, Item.Sulfur), (1, Item.Coal), (10, Item.Water)], [(2, Item.Explosive)]) - }, - [Factory.SteamCracker] = new() - { - ["Steam Cracking Mineral Oil to Synthesis Gas"] = ([(100, Item.BaseMineralOil), (100, Item.Steam)], [(200, Item.SynthesisGas)]) - }, - [Factory.BlastFurnace] = new() - { - ["Iron Ore Smelting"] = ([(24, Item.IronOre)], [(24, Item.IronIngot)]) - }, - [Factory.InductionFurnace] = new() - { - ["Iron Melting"] = ([(12, Item.IronIngot)], [(120, Item.MoltenIron)]) - }, - [Factory.CastingMachine] = new() - { - ["Iron Plate Casting"] = ([(40, Item.MoltenIron)], [(4, Item.IronPlate)]) - }, - [Factory.Electrolyzer] = new() - { - ["Purified Water Electrolysis"] = ([(100, Item.PurifiedWater)], [(40, Item.OxygenGas), (60, Item.HydrogenGas)]) - }, - [Factory.CoolingTower] = new() - { - ["Steam Cooling"] = ([(100, Item.Steam)], [(100, Item.PurifiedWater)]) - }, - [Factory.WaterTreatmentPlant] = new() - { - ["Water Purification"] = ([(150, Item.Water)], [(100, Item.PurifiedWater)]), - ["Water Boiling"] = ([(100, Item.Water), (1, Item.Coal)], [(60, Item.Steam)]) - }, - [Factory.Liquifier] = new() - { - ["Coal Liquefaction"] = ([(1, Item.Coal)], [(50, Item.CarbonDioxide)]) - }, - [Factory.AssemblingMachine] = new() - { - ["Grenade"] = ([(5, Item.IronPlate), (10, Item.Coal)], [(1, Item.Grenade)]) + { } str when str.Count(char.IsUpper) == 1 => str, + { } str => string.Join(string.Empty, str.Where(char.IsUpper)) } }; + } - private static Dictionary FactoryLocations = []; - - private Dictionary OtherAcidPlayers, long TimeStamp)> AcidPlayers; - private HashSet BombedBodies; - - public PlayerControl ChemistPC; - private Factory CurrentFactory; - private Dictionary Grenades; - public bool IsBlinding; - private Dictionary ItemCounts; - private long LastUpdate; - private string SelectedProcess; - private List SortedAvailableProcesses; - - public override bool IsEnable => On; - - public override void SetupCustomOption() + private static Color GetItemColor(Item item) + { + return item switch { - var id = 17670; - const TabGroup tab = TabGroup.NeutralRoles; + Item.Air => Palette.HalfWhite, + Item.AmmoniaGas => Color.blue, + Item.BaseMineralOil => Color.green, + Item.CarbonDioxide => Palette.Brown, + Item.CarbonMonoxide => Palette.Purple, + Item.Coal => Color.black, + Item.Explosive => Color.red, + Item.Grenade => Color.red, + Item.HydrogenGas => Color.white, + Item.HydrogenSulfideGas => Color.yellow, + Item.IronIngot => Color.gray, + Item.IronOre => Color.gray, + Item.IronPlate => Color.gray, + Item.MethanolGas => Palette.Brown, + Item.MethylamineGas => Color.blue, + Item.MoltenIron => Color.gray, + Item.Naphtha => Palette.ImpostorRed, + Item.NitrogenGas => Color.blue, + Item.OxygenGas => Color.red, + Item.PurifiedWater => Color.cyan, + Item.Steam => Palette.White_75Alpha, + Item.Sulfur => Color.yellow, + Item.SulfurDioxideGas => Color.yellow, + Item.SulfuricAcid => Color.yellow, + Item.SynthesisGas => Color.magenta, + Item.ThermalWater => Palette.Orange, + Item.Water => Palette.LightBlue, - SetupRoleOptions(id++, tab, CustomRoles.Chemist); + _ => Color.white + }; + } - KillCooldown = new FloatOptionItem(++id, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Seconds); + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; - HasImpostorVision = new BooleanOptionItem(++id, "ImpostorVision", true, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]); + int need = FinalProductUsageAmounts[Item.SulfuricAcid].GetInt(); + int need2 = FinalProductUsageAmounts[Item.Grenade].GetInt(); + bool canUseAcid = ItemCounts[Item.SulfuricAcid] >= need && !AcidPlayers.ContainsKey(target.PlayerId) && !AcidPlayers.Any(x => x.Value.OtherAcidPlayers.Contains(target.PlayerId)); + bool canUseGrenade = ItemCounts[Item.Grenade] >= need2; - AirGainedPerSecond = new IntegerOptionItem(++id, "Chemist.AirGainedPerSecond", new(5, 100, 5), 10, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Times); + if (!canUseAcid && !canUseGrenade) return true; - WaterGainedPerSecond = new IntegerOptionItem(++id, "Chemist.WaterGainedPerSecond", new(5, 100, 5), 10, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Times); - - CoalGainedPerVent = new IntegerOptionItem(++id, "Chemist.CoalGainedPerVent", new(1, 10, 1), 1, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Times); - - IronOreGainedPerVent = new IntegerOptionItem(++id, "Chemist.IronOreGainedPerVent", new(1, 50, 1), 4, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Times); - - FinalProductUsageAmounts = Enum.GetValues() - .Where(x => GetItemType(x) == ItemType.FinalProduct) - .ToDictionary(x => x, x => new IntegerOptionItem(++id, $"Chemist.Item.{x}.Usage", new(1, 100, 1), GetDefaultValue(x), tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Times)); - - AcidPlayersDie = new StringOptionItem(++id, "Chemist.AcidPlayersDie", Enum.GetNames(), 0, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]); - - AcidPlayersDieAfterTime = new IntegerOptionItem(++id, "Chemist.AcidPlayersDieAfterTime", new(1, 60, 1), 15, tab) - .SetParent(AcidPlayersDie) - .SetValueFormat(OptionFormat.Seconds); - - BlindDuration = new IntegerOptionItem(++id, "Chemist.BlindDuration", new(1, 60, 1), 10, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Seconds); - - GrenadeExplodeDelay = new IntegerOptionItem(++id, "Chemist.GrenadeExplodeDelay", new(1, 60, 1), 5, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Seconds); - - GrenadeExplodeRadius = new FloatOptionItem(++id, "Chemist.GrenadeExplodeRadius", new(0.25f, 10f, 0.25f), 4f, tab) - .SetParent(CustomRoleSpawnChances[CustomRoles.Chemist]) - .SetValueFormat(OptionFormat.Multiplier); - - return; - - static int GetDefaultValue(Item item) + return killer.CheckDoubleTrigger(target, () => + { + if (canUseAcid) { - return item switch - { - Item.Explosive => 1, - Item.Grenade => 1, - Item.SulfuricAcid => 30, - Item.MethylamineGas => 100, - _ => 0 - }; + AcidPlayers[target.PlayerId] = ([], Utils.TimeStamp); + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 4, target.PlayerId); + ItemCounts[Item.SulfuricAcid] -= need; + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, (int)Item.SulfuricAcid, -need); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); } + else if (canUseGrenade) + { + ItemCounts[Item.Grenade] -= need2; + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, (int)Item.Grenade, -need2); + Grenades[target.PlayerId] = Utils.TimeStamp; + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + } + }); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + int need = FinalProductUsageAmounts[Item.Explosive].GetInt(); + + if (ItemCounts[Item.Explosive] >= need) + { + ItemCounts[Item.Explosive] -= need; + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, (int)Item.Explosive, -need); + BombedBodies.Add(target.PlayerId); + } + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (!GameStates.IsInTask || !pc.IsAlive()) return; + + Vector2 pos = pc.Pos(); + + if (AcidPlayers.TryGetValue(pc.PlayerId, out (HashSet OtherAcidPlayers, long TimeStamp) acidPlayers)) + { + Main.AllAlivePlayerControls + .ExceptBy(acidPlayers.OtherAcidPlayers, x => x.PlayerId) + .Where(x => x.PlayerId != pc.PlayerId && x.PlayerId != ChemistPC.PlayerId && Vector2.Distance(x.Pos(), pos) < 1.5f) + .Do(x => acidPlayers.OtherAcidPlayers.Add(x.PlayerId)); } - public override void Init() + if (Grenades.TryGetValue(pc.PlayerId, out long ts) && ts + GrenadeExplodeDelay.GetInt() <= Utils.TimeStamp) { - On = false; - Instances = []; + Grenades.Remove(pc.PlayerId); - FactoryLocations = []; + float radius = GrenadeExplodeRadius.GetFloat(); + + Main.AllAlivePlayerControls + .Where(x => x.PlayerId != ChemistPC.PlayerId && Vector2.Distance(x.Pos(), pos) < radius && ChemistPC.RpcCheckAndMurder(x, true)) + .Do(x => x.Suicide(realKiller: ChemistPC)); + } + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (IsBlinding) return; + + int need = FinalProductUsageAmounts[Item.MethylamineGas].GetInt(); + + if (ItemCounts[Item.MethylamineGas] >= need) + { + ItemCounts[Item.MethylamineGas] -= need; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.MethylamineGas, -need); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + IsBlinding = true; + Utils.MarkEveryoneDirtySettings(); LateTask.New(() => { - FactoryLocations = ShipStatus.Instance.AllRooms - .Select(x => Translator.GetString($"{x.RoomId}")) - .Distinct() - .Zip(Enum.GetValues()[1..]) - .ToDictionary(x => x.First, x => x.Second); - }, 20f, log: false); - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - - ChemistPC = Utils.GetPlayerById(playerId); - LastUpdate = Utils.TimeStamp + 8; - ItemCounts = []; - CurrentFactory = Factory.None; - SelectedProcess = string.Empty; - SortedAvailableProcesses = []; - - AcidPlayers = []; - IsBlinding = false; - BombedBodies = []; - Grenades = []; - - ItemCounts = Enum.GetValues().ToDictionary(x => x, _ => 0); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } - - private static ItemType GetItemType(Item item) - { - return item switch - { - Item.Air or Item.Coal or Item.IronOre or Item.Water => ItemType.BasicResource, - Item.Explosive or Item.Grenade or Item.SulfuricAcid or Item.MethylamineGas => ItemType.FinalProduct, - _ => ItemType.IntermediateProduct - }; - } - - private static string GetChemicalForm(Item item) - { - return item switch - { - Item.AmmoniaGas => "NH3", - Item.CarbonDioxide => "CO2", - Item.CarbonMonoxide => "CO", - Item.HydrogenGas => "H2", - Item.HydrogenSulfideGas => "H2S", - Item.MethanolGas => "CH3OH", - Item.MethylamineGas => "CH3NH2", - Item.NitrogenGas => "N2", - Item.OxygenGas => "O2", - Item.PurifiedWater => "H2O", - Item.Sulfur => "S", - Item.SulfurDioxideGas => "SO2", - Item.SulfuricAcid => "H2SO4", - Item.SynthesisGas => "H2+CO", - - _ => Translator.GetString($"Chemist.Item.{item}") switch + if (IsBlinding) { - { } str when str.Count(char.IsUpper) == 1 => str, - { } str => string.Join(string.Empty, str.Where(char.IsUpper)) + IsBlinding = false; + Utils.MarkEveryoneDirtySettings(); } - }; - } - - private static Color GetItemColor(Item item) - { - return item switch - { - Item.Air => Palette.HalfWhite, - Item.AmmoniaGas => Color.blue, - Item.BaseMineralOil => Color.green, - Item.CarbonDioxide => Palette.Brown, - Item.CarbonMonoxide => Palette.Purple, - Item.Coal => Color.black, - Item.Explosive => Color.red, - Item.Grenade => Color.red, - Item.HydrogenGas => Color.white, - Item.HydrogenSulfideGas => Color.yellow, - Item.IronIngot => Color.gray, - Item.IronOre => Color.gray, - Item.IronPlate => Color.gray, - Item.MethanolGas => Palette.Brown, - Item.MethylamineGas => Color.blue, - Item.MoltenIron => Color.gray, - Item.Naphtha => Palette.ImpostorRed, - Item.NitrogenGas => Color.blue, - Item.OxygenGas => Color.red, - Item.PurifiedWater => Color.cyan, - Item.Steam => Palette.White_75Alpha, - Item.Sulfur => Color.yellow, - Item.SulfurDioxideGas => Color.yellow, - Item.SulfuricAcid => Color.yellow, - Item.SynthesisGas => Color.magenta, - Item.ThermalWater => Palette.Orange, - Item.Water => Palette.LightBlue, - - _ => Color.white - }; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - int need = FinalProductUsageAmounts[Item.SulfuricAcid].GetInt(); - int need2 = FinalProductUsageAmounts[Item.Grenade].GetInt(); - bool canUseAcid = ItemCounts[Item.SulfuricAcid] >= need && !AcidPlayers.ContainsKey(target.PlayerId) && !AcidPlayers.Any(x => x.Value.OtherAcidPlayers.Contains(target.PlayerId)); - bool canUseGrenade = ItemCounts[Item.Grenade] >= need2; - - if (!canUseAcid && !canUseGrenade) return true; - - return killer.CheckDoubleTrigger(target, () => - { - if (canUseAcid) - { - AcidPlayers[target.PlayerId] = ([], Utils.TimeStamp); - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 4, target.PlayerId); - ItemCounts[Item.SulfuricAcid] -= need; - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, (int)Item.SulfuricAcid, -need); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - } - else if (canUseGrenade) - { - ItemCounts[Item.Grenade] -= need2; - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, (int)Item.Grenade, -need2); - Grenades[target.PlayerId] = Utils.TimeStamp; - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - } - }); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - int need = FinalProductUsageAmounts[Item.Explosive].GetInt(); - - if (ItemCounts[Item.Explosive] >= need) - { - ItemCounts[Item.Explosive] -= need; - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 1, (int)Item.Explosive, -need); - BombedBodies.Add(target.PlayerId); - } - } - - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) - { - if (!GameStates.IsInTask || !pc.IsAlive()) return; - - Vector2 pos = pc.Pos(); - - if (AcidPlayers.TryGetValue(pc.PlayerId, out (HashSet OtherAcidPlayers, long TimeStamp) acidPlayers)) - { - Main.AllAlivePlayerControls - .ExceptBy(acidPlayers.OtherAcidPlayers, x => x.PlayerId) - .Where(x => x.PlayerId != pc.PlayerId && x.PlayerId != ChemistPC.PlayerId && Vector2.Distance(x.Pos(), pos) < 1.5f) - .Do(x => acidPlayers.OtherAcidPlayers.Add(x.PlayerId)); - } - - if (Grenades.TryGetValue(pc.PlayerId, out long ts) && ts + GrenadeExplodeDelay.GetInt() <= Utils.TimeStamp) - { - Grenades.Remove(pc.PlayerId); - - float radius = GrenadeExplodeRadius.GetFloat(); - - Main.AllAlivePlayerControls - .Where(x => x.PlayerId != ChemistPC.PlayerId && Vector2.Distance(x.Pos(), pos) < radius && ChemistPC.RpcCheckAndMurder(x, true)) - .Do(x => x.Suicide(realKiller: ChemistPC)); - } - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (IsBlinding) return; - - int need = FinalProductUsageAmounts[Item.MethylamineGas].GetInt(); - - if (ItemCounts[Item.MethylamineGas] >= need) - { - ItemCounts[Item.MethylamineGas] -= need; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.MethylamineGas, -need); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - IsBlinding = true; - Utils.MarkEveryoneDirtySettings(); - - LateTask.New(() => - { - if (IsBlinding) - { - IsBlinding = false; - Utils.MarkEveryoneDirtySettings(); - } - }, BlindDuration.GetInt(), log: false); - } - } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - ItemCounts[Item.Coal] += CoalGainedPerVent.GetInt(); - ItemCounts[Item.IronOre] += IronOreGainedPerVent.GetInt(); - - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.Coal, CoalGainedPerVent.GetInt()); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.IronOre, IronOreGainedPerVent.GetInt()); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) - { - if (BombedBodies.Contains(target.PlayerId)) - { - if (ChemistPC.RpcCheckAndMurder(reporter, true)) reporter.Suicide(realKiller: ChemistPC); - - return false; - } - - return true; - } - - public override void OnReportDeadBody() - { - CheckAndKillAcidPlayers(true); - IsBlinding = false; - BombedBodies.Clear(); - } - - private void CheckAndKillAcidPlayers(bool force = false) - { - foreach (KeyValuePair OtherAcidPlayers, long TimeStamp)> kvp in AcidPlayers) - { - if (force || kvp.Value.TimeStamp + AcidPlayersDieAfterTime.GetInt() <= Utils.TimeStamp) - { - PlayerControl srcPlayer = Utils.GetPlayerById(kvp.Key); - if (srcPlayer != null && srcPlayer.IsAlive() && ChemistPC.RpcCheckAndMurder(srcPlayer, true)) srcPlayer.Suicide(realKiller: ChemistPC); - - foreach (byte id in kvp.Value.OtherAcidPlayers) - { - PlayerControl player = Utils.GetPlayerById(id); - if (player == null || !player.IsAlive() || !ChemistPC.RpcCheckAndMurder(player, true)) continue; - - player.Suicide(realKiller: ChemistPC); - } - } - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !pc.IsAlive() || LastUpdate >= Utils.TimeStamp) return; - - LastUpdate = Utils.TimeStamp; - - if (ItemCounts[Item.Air] < 900) - { - ItemCounts[Item.Air] += AirGainedPerSecond.GetInt(); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.Air, AirGainedPerSecond.GetInt()); - } - - if (ItemCounts[Item.Water] < 900) - { - ItemCounts[Item.Water] += WaterGainedPerSecond.GetInt(); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.Water, WaterGainedPerSecond.GetInt()); - } - - Factory beforeFactory = CurrentFactory; - PlainShipRoom room = pc.GetPlainShipRoom(); - - if (room != null) - { - CurrentFactory = FactoryLocations.GetValueOrDefault(Translator.GetString($"{room.RoomId}")); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)CurrentFactory); - - if (CurrentFactory != beforeFactory) - { - SortedAvailableProcesses = Processes[CurrentFactory] - .OrderByDescending(x => x.Value.Ingredients.TrueForAll(y => ItemCounts[y.Item] >= y.Count)) - .Select(x => x.Key) - .ToList(); - - SelectedProcess = SortedAvailableProcesses.FirstOrDefault() ?? string.Empty; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, SelectedProcess); - } - } - - if ((AcidPlayersDieOptions)AcidPlayersDie.GetValue() == AcidPlayersDieOptions.AfterTime) CheckAndKillAcidPlayers(); - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool OnSabotage(PlayerControl pc) - { - if (SortedAvailableProcesses.Count == 0) return false; - - Cycle(pc); - - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - if (SortedAvailableProcesses.Count == 0) return false; - - Cycle(pc); - - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - if (SortedAvailableProcesses.Count == 0) return false; - - Cycle(shapeshifter); - - return false; - } - - private void Cycle(PlayerControl pc) - { - int index = SortedAvailableProcesses.IndexOf(SelectedProcess); - SelectedProcess = SortedAvailableProcesses[(index + 1) % SortedAvailableProcesses.Count]; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, SelectedProcess); - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override void OnPet(PlayerControl pc) - { - if (SelectedProcess == string.Empty) return; - - (List<(int Count, Item Item)> Ingredients, List<(int Count, Item Item)> Results) = Processes[CurrentFactory][SelectedProcess]; - - if (!Ingredients.TrueForAll(x => ItemCounts[x.Item] >= x.Count)) return; - - Ingredients.ForEach(x => - { - ItemCounts[x.Item] -= x.Count; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)x.Item, -x.Count); - }); - - Results.ForEach(x => - { - ItemCounts[x.Item] += x.Count; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)x.Item, x.Count); - }); - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - var item = (Item)reader.ReadPackedInt32(); - ItemCounts.TryAdd(item, 0); - ItemCounts[item] += reader.ReadPackedInt32(); - break; - case 2: - SelectedProcess = reader.ReadString(); - break; - case 3: - CurrentFactory = (Factory)reader.ReadPackedInt32(); - break; - case 4: - AcidPlayers[reader.ReadByte()] = ([], Utils.TimeStamp); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Chemist cm) return string.Empty; - - bool self = seer.PlayerId == target.PlayerId; - if (self && seer.IsModClient() && !hud) return string.Empty; - - StringBuilder sb = new StringBuilder().Append(""); - - if (self) - { - Dictionary> grouped = cm.ItemCounts - .Where(x => x.Value > 0) - .GroupBy(x => GetItemType(x.Key)) - .OrderBy(x => (int)x.Key) - .ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Key, y => y.Value)); - - foreach ((ItemType type, Dictionary items) in grouped) - { - if (items.Count == 0) continue; - - sb.Append($"{type.ToString()[0]}: "); - - foreach ((Item item, int count) in items) sb.Append(Utils.ColorString(GetItemColor(item), $"{Utils.ColorString(FinalProductUsageAmounts.TryGetValue(item, out OptionItem opt) && opt.GetInt() <= count ? Color.green : Color.white, $"{count}")} {GetChemicalForm(item)}") + ", "); - - sb.Length -= 2; - sb.AppendLine(); - } - - if (cm.SelectedProcess == string.Empty) return sb.ToString().TrimEnd(); - - (List<(int Count, Item Item)> Ingredients, List<(int Count, Item Item)> Results) = Processes[cm.CurrentFactory][cm.SelectedProcess]; - - Func<(int Count, Item Item), string> selector = x => $"{x.Count} {Utils.ColorString(GetItemColor(x.Item), $"{GetChemicalForm(x.Item)}")}"; - sb.Append(string.Join(", ", Ingredients.Select(selector))); - sb.Append(Ingredients.Count + Results.Count > 2 ? "\n\u2192 " : " → "); - sb.Append(string.Join(", ", Results.Select(selector))); - } - - if ((AcidPlayersDieOptions)AcidPlayersDie.GetValue() == AcidPlayersDieOptions.AfterTime) - { - int time = AcidPlayersDieAfterTime.GetInt(); - long now = Utils.TimeStamp; - - foreach (KeyValuePair OtherAcidPlayers, long TimeStamp)> kvp in cm.AcidPlayers) - { - if (kvp.Key == target.PlayerId || kvp.Value.OtherAcidPlayers.Contains(target.PlayerId)) - { - sb.Append(Utils.ColorString(Color.yellow, $"\u26a0 {time - (now - kvp.Value.TimeStamp):N0}")); - break; - } - } - } - - return sb.Append("").ToString(); - } - - private enum AcidPlayersDieOptions - { - AfterMeeting, - AfterTime - } - - private enum Item - { - Air, - AmmoniaGas, - BaseMineralOil, - CarbonDioxide, - CarbonMonoxide, - Coal, - Explosive, - Grenade, - HydrogenGas, - HydrogenSulfideGas, - IronIngot, - IronOre, - IronPlate, - MethanolGas, - MethylamineGas, - MoltenIron, - Naphtha, - NitrogenGas, - OxygenGas, - PurifiedWater, - Steam, - Sulfur, - SulfurDioxideGas, - SulfuricAcid, - SynthesisGas, - ThermalWater, - Water - } - - private enum ItemType - { - BasicResource, - IntermediateProduct, - FinalProduct - } - - private enum Factory - { - None, - ChemicalPlant, // up to 2 ingredients - AdvancedChemicalPlant, // 3 or more ingredients - SteamCracker, - BlastFurnace, - InductionFurnace, - CastingMachine, - Electrolyzer, - CoolingTower, - WaterTreatmentPlant, - Liquifier, - AssemblingMachine + }, BlindDuration.GetInt(), log: false); } } + + public override void OnExitVent(PlayerControl pc, Vent vent) + { + ItemCounts[Item.Coal] += CoalGainedPerVent.GetInt(); + ItemCounts[Item.IronOre] += IronOreGainedPerVent.GetInt(); + + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.Coal, CoalGainedPerVent.GetInt()); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.IronOre, IronOreGainedPerVent.GetInt()); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) + { + if (BombedBodies.Contains(target.PlayerId)) + { + if (ChemistPC.RpcCheckAndMurder(reporter, true)) reporter.Suicide(realKiller: ChemistPC); + + return false; + } + + return true; + } + + public override void OnReportDeadBody() + { + CheckAndKillAcidPlayers(true); + IsBlinding = false; + BombedBodies.Clear(); + } + + private void CheckAndKillAcidPlayers(bool force = false) + { + foreach (KeyValuePair OtherAcidPlayers, long TimeStamp)> kvp in AcidPlayers) + { + if (force || kvp.Value.TimeStamp + AcidPlayersDieAfterTime.GetInt() <= Utils.TimeStamp) + { + PlayerControl srcPlayer = Utils.GetPlayerById(kvp.Key); + if (srcPlayer != null && srcPlayer.IsAlive() && ChemistPC.RpcCheckAndMurder(srcPlayer, true)) srcPlayer.Suicide(realKiller: ChemistPC); + + foreach (byte id in kvp.Value.OtherAcidPlayers) + { + PlayerControl player = Utils.GetPlayerById(id); + if (player == null || !player.IsAlive() || !ChemistPC.RpcCheckAndMurder(player, true)) continue; + + player.Suicide(realKiller: ChemistPC); + } + } + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !pc.IsAlive() || LastUpdate >= Utils.TimeStamp) return; + + LastUpdate = Utils.TimeStamp; + + if (ItemCounts[Item.Air] < 900) + { + ItemCounts[Item.Air] += AirGainedPerSecond.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.Air, AirGainedPerSecond.GetInt()); + } + + if (ItemCounts[Item.Water] < 900) + { + ItemCounts[Item.Water] += WaterGainedPerSecond.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)Item.Water, WaterGainedPerSecond.GetInt()); + } + + Factory beforeFactory = CurrentFactory; + PlainShipRoom room = pc.GetPlainShipRoom(); + + if (room != null) + { + CurrentFactory = FactoryLocations.GetValueOrDefault(Translator.GetString($"{room.RoomId}")); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 3, (int)CurrentFactory); + + if (CurrentFactory != beforeFactory) + { + SortedAvailableProcesses = Processes[CurrentFactory] + .OrderByDescending(x => x.Value.Ingredients.TrueForAll(y => ItemCounts[y.Item] >= y.Count)) + .Select(x => x.Key) + .ToList(); + + SelectedProcess = SortedAvailableProcesses.FirstOrDefault() ?? string.Empty; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, SelectedProcess); + } + } + + if ((AcidPlayersDieOptions)AcidPlayersDie.GetValue() == AcidPlayersDieOptions.AfterTime) CheckAndKillAcidPlayers(); + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool OnSabotage(PlayerControl pc) + { + if (SortedAvailableProcesses.Count == 0) return false; + + Cycle(pc); + + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + if (SortedAvailableProcesses.Count == 0) return false; + + Cycle(pc); + + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + if (SortedAvailableProcesses.Count == 0) return false; + + Cycle(shapeshifter); + + return false; + } + + private void Cycle(PlayerControl pc) + { + int index = SortedAvailableProcesses.IndexOf(SelectedProcess); + SelectedProcess = SortedAvailableProcesses[(index + 1) % SortedAvailableProcesses.Count]; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, SelectedProcess); + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override void OnPet(PlayerControl pc) + { + if (SelectedProcess == string.Empty) return; + + (List<(int Count, Item Item)> Ingredients, List<(int Count, Item Item)> Results) = Processes[CurrentFactory][SelectedProcess]; + + if (!Ingredients.TrueForAll(x => ItemCounts[x.Item] >= x.Count)) return; + + Ingredients.ForEach(x => + { + ItemCounts[x.Item] -= x.Count; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)x.Item, -x.Count); + }); + + Results.ForEach(x => + { + ItemCounts[x.Item] += x.Count; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, (int)x.Item, x.Count); + }); + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + var item = (Item)reader.ReadPackedInt32(); + ItemCounts.TryAdd(item, 0); + ItemCounts[item] += reader.ReadPackedInt32(); + break; + case 2: + SelectedProcess = reader.ReadString(); + break; + case 3: + CurrentFactory = (Factory)reader.ReadPackedInt32(); + break; + case 4: + AcidPlayers[reader.ReadByte()] = ([], Utils.TimeStamp); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Chemist cm) return string.Empty; + + bool self = seer.PlayerId == target.PlayerId; + if (self && seer.IsModClient() && !hud) return string.Empty; + + StringBuilder sb = new StringBuilder().Append(""); + + if (self) + { + Dictionary> grouped = cm.ItemCounts + .Where(x => x.Value > 0) + .GroupBy(x => GetItemType(x.Key)) + .OrderBy(x => (int)x.Key) + .ToDictionary(x => x.Key, x => x.ToDictionary(y => y.Key, y => y.Value)); + + foreach ((ItemType type, Dictionary items) in grouped) + { + if (items.Count == 0) continue; + + sb.Append($"{type.ToString()[0]}: "); + + foreach ((Item item, int count) in items) sb.Append(Utils.ColorString(GetItemColor(item), $"{Utils.ColorString(FinalProductUsageAmounts.TryGetValue(item, out OptionItem opt) && opt.GetInt() <= count ? Color.green : Color.white, $"{count}")} {GetChemicalForm(item)}") + ", "); + + sb.Length -= 2; + sb.AppendLine(); + } + + if (cm.SelectedProcess == string.Empty) return sb.ToString().TrimEnd(); + + (List<(int Count, Item Item)> Ingredients, List<(int Count, Item Item)> Results) = Processes[cm.CurrentFactory][cm.SelectedProcess]; + + Func<(int Count, Item Item), string> selector = x => $"{x.Count} {Utils.ColorString(GetItemColor(x.Item), $"{GetChemicalForm(x.Item)}")}"; + sb.Append(string.Join(", ", Ingredients.Select(selector))); + sb.Append(Ingredients.Count + Results.Count > 2 ? "\n\u2192 " : " → "); + sb.Append(string.Join(", ", Results.Select(selector))); + } + + if ((AcidPlayersDieOptions)AcidPlayersDie.GetValue() == AcidPlayersDieOptions.AfterTime) + { + int time = AcidPlayersDieAfterTime.GetInt(); + long now = Utils.TimeStamp; + + foreach (KeyValuePair OtherAcidPlayers, long TimeStamp)> kvp in cm.AcidPlayers) + { + if (kvp.Key == target.PlayerId || kvp.Value.OtherAcidPlayers.Contains(target.PlayerId)) + { + sb.Append(Utils.ColorString(Color.yellow, $"\u26a0 {time - (now - kvp.Value.TimeStamp):N0}")); + break; + } + } + } + + return sb.Append("").ToString(); + } + + private enum AcidPlayersDieOptions + { + AfterMeeting, + AfterTime + } + + private enum Item + { + Air, + AmmoniaGas, + BaseMineralOil, + CarbonDioxide, + CarbonMonoxide, + Coal, + Explosive, + Grenade, + HydrogenGas, + HydrogenSulfideGas, + IronIngot, + IronOre, + IronPlate, + MethanolGas, + MethylamineGas, + MoltenIron, + Naphtha, + NitrogenGas, + OxygenGas, + PurifiedWater, + Steam, + Sulfur, + SulfurDioxideGas, + SulfuricAcid, + SynthesisGas, + ThermalWater, + Water + } + + private enum ItemType + { + BasicResource, + IntermediateProduct, + FinalProduct + } + + private enum Factory + { + None, + ChemicalPlant, // up to 2 ingredients + AdvancedChemicalPlant, // 3 or more ingredients + SteamCracker, + BlastFurnace, + InductionFurnace, + CastingMachine, + Electrolyzer, + CoolingTower, + WaterTreatmentPlant, + Liquifier, + AssemblingMachine + } } \ No newline at end of file diff --git a/Roles/Neutral/Cherokious.cs b/Roles/Neutral/Cherokious.cs index 3140e3d2f..13e5f3f57 100644 --- a/Roles/Neutral/Cherokious.cs +++ b/Roles/Neutral/Cherokious.cs @@ -1,54 +1,53 @@ using EHR.Patches; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Cherokious : RoleBase { - internal class Cherokious : RoleBase + public static bool On; + + public static OptionItem KillCooldown; + public static Options.OverrideTasksData Tasks; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + const int id = 13860; + Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Cherokious); - public static OptionItem KillCooldown; - public static Options.OverrideTasksData Tasks; - public override bool IsEnable => On; + KillCooldown = new IntegerOptionItem(id + 2, "KillCooldown", new(0, 60, 1), 15, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cherokious]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - const int id = 13860; - Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Cherokious); + Tasks = Options.OverrideTasksData.Create(id + 3, TabGroup.NeutralRoles, CustomRoles.Cherokious); + } - KillCooldown = new IntegerOptionItem(id + 2, "KillCooldown", new(0, 60, 1), 15, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Cherokious]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(0); + } - Tasks = Options.OverrideTasksData.Create(id + 3, TabGroup.NeutralRoles, CustomRoles.Cherokious); - } + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(0); - } + public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) + { + pc.RpcIncreaseAbilityUseLimitBy(1); + } - public override void Init() - { - On = false; - } + public override void OnPet(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1) return; - public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) - { - pc.RpcIncreaseAbilityUseLimitBy(1); - } + PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); + if (target != null && pc.RpcCheckAndMurder(target)) pc.RpcRemoveAbilityUse(); + } - public override void OnPet(PlayerControl pc) - { - if (pc.GetAbilityUseLimit() < 1) return; - - PlayerControl target = ExternalRpcPetPatch.SelectKillButtonTarget(pc); - if (target != null && pc.RpcCheckAndMurder(target)) pc.RpcRemoveAbilityUse(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return false; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Collector.cs b/Roles/Neutral/Collector.cs index 4340bd2e4..84f50bc4b 100644 --- a/Roles/Neutral/Collector.cs +++ b/Roles/Neutral/Collector.cs @@ -4,118 +4,117 @@ using EHR.Modules; using EHR.Patches; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Collector : RoleBase { - public class Collector : RoleBase + private const int Id = 11100; + public static OptionItem CollectorCollectAmount; + private static List PlayerIdList = []; + public static Dictionary CollectorVoteFor = []; + public static Dictionary CollectVote = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 11100; - public static OptionItem CollectorCollectAmount; - private static List PlayerIdList = []; - public static Dictionary CollectorVoteFor = []; - public static Dictionary CollectVote = []; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Collector); - public override bool IsEnable => PlayerIdList.Count > 0; + CollectorCollectAmount = new IntegerOptionItem(Id + 13, "CollectorCollectAmount", new(1, 60, 1), 30, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Collector]) + .SetValueFormat(OptionFormat.Votes); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + CollectorVoteFor = []; + CollectVote = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + CollectVote.TryAdd(playerId, 0); + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCollectorVotes, SendOption.Reliable); + writer.Write(playerId); + writer.Write(CollectVote[playerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + int Num = reader.ReadInt32(); + CollectVote.TryAdd(PlayerId, 0); + CollectVote[PlayerId] = Num; + } + + public override string GetProgressText(byte playerId, bool comms) + { + if (!CollectVote.TryGetValue(playerId, out int VoteAmount)) return string.Empty; + + int CollectNum = CollectorCollectAmount.GetInt(); + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Collector).ShadeColor(0.25f), $"({VoteAmount}/{CollectNum})"); + } + + public static bool CollectorWin(bool check = true) + { + PlayerControl[] pc = Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Collector) && x.IsAlive() && CollectDone(x)).ToArray(); + + if (pc.Length > 0) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Collector); + if (check) return true; - CollectorCollectAmount = new IntegerOptionItem(Id + 13, "CollectorCollectAmount", new(1, 60, 1), 30, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Collector]) - .SetValueFormat(OptionFormat.Votes); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Collector); + foreach (PlayerControl winner in pc) CustomWinnerHolder.WinnerIds.Add(winner.PlayerId); + + return true; } - public override void Init() + return false; + } + + public static bool CollectDone(PlayerControl player) + { + if (player.Is(CustomRoles.Collector)) { - PlayerIdList = []; - CollectorVoteFor = []; - CollectVote = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - CollectVote.TryAdd(playerId, 0); - } - - private static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetCollectorVotes, SendOption.Reliable); - writer.Write(playerId); - writer.Write(CollectVote[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte PlayerId = reader.ReadByte(); - int Num = reader.ReadInt32(); - CollectVote.TryAdd(PlayerId, 0); - CollectVote[PlayerId] = Num; - } - - public override string GetProgressText(byte playerId, bool comms) - { - if (!CollectVote.TryGetValue(playerId, out int VoteAmount)) return string.Empty; - + byte pcid = player.PlayerId; + int VoteAmount = CollectVote[pcid]; int CollectNum = CollectorCollectAmount.GetInt(); - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Collector).ShadeColor(0.25f), $"({VoteAmount}/{CollectNum})"); + if (VoteAmount >= CollectNum) return true; } - public static bool CollectorWin(bool check = true) - { - PlayerControl[] pc = Main.AllPlayerControls.Where(x => x.Is(CustomRoles.Collector) && x.IsAlive() && CollectDone(x)).ToArray(); + return false; + } - if (pc.Length > 0) + public static void CollectorVotes(PlayerControl target, PlayerVoteArea ps) //集票者投票给谁 + { + if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Collector)) CollectorVoteFor.TryAdd(target.PlayerId, ps.TargetPlayerId); + } + + public static void CollectAmount(Dictionary VotingData, MeetingHud __instance) //得到集票者收集到的票 + { + foreach (PlayerVoteArea pva in __instance.playerStates.ToArray()) + { + if (pva == null) continue; + + PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); + if (pc == null) continue; + + foreach ((byte key, int value) in VotingData) { - if (check) return true; - - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Collector); - foreach (PlayerControl winner in pc) CustomWinnerHolder.WinnerIds.Add(winner.PlayerId); - - return true; - } - - return false; - } - - public static bool CollectDone(PlayerControl player) - { - if (player.Is(CustomRoles.Collector)) - { - byte pcid = player.PlayerId; - int VoteAmount = CollectVote[pcid]; - int CollectNum = CollectorCollectAmount.GetInt(); - if (VoteAmount >= CollectNum) return true; - } - - return false; - } - - public static void CollectorVotes(PlayerControl target, PlayerVoteArea ps) //集票者投票给谁 - { - if (CheckForEndVotingPatch.CheckRole(ps.TargetPlayerId, CustomRoles.Collector)) CollectorVoteFor.TryAdd(target.PlayerId, ps.TargetPlayerId); - } - - public static void CollectAmount(Dictionary VotingData, MeetingHud __instance) //得到集票者收集到的票 - { - foreach (PlayerVoteArea pva in __instance.playerStates.ToArray()) - { - if (pva == null) continue; - - PlayerControl pc = Utils.GetPlayerById(pva.TargetPlayerId); - if (pc == null) continue; - - foreach ((byte key, int value) in VotingData) + if (CollectorVoteFor.ContainsKey(key) && pc.PlayerId == CollectorVoteFor[key] && pc.Is(CustomRoles.Collector)) { - if (CollectorVoteFor.ContainsKey(key) && pc.PlayerId == CollectorVoteFor[key] && pc.Is(CustomRoles.Collector)) - { - CollectVote.TryAdd(pc.PlayerId, 0); - CollectVote[pc.PlayerId] += value; - SendRPC(pc.PlayerId); - } + CollectVote.TryAdd(pc.PlayerId, 0); + CollectVote[pc.PlayerId] += value; + SendRPC(pc.PlayerId); } } } diff --git a/Roles/Neutral/Curser.cs b/Roles/Neutral/Curser.cs index aaac47260..bd870f718 100644 --- a/Roles/Neutral/Curser.cs +++ b/Roles/Neutral/Curser.cs @@ -2,136 +2,135 @@ using System.Linq; using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Curser : RoleBase { - public class Curser : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem AbilityUseLimit; + private static OptionItem AbilityCooldown; + private static OptionItem ImpostorVision; + private static OptionItem CanVent; + private static OptionItem LowerVision; + private static OptionItem LowerSpeed; + + public HashSet KnownFactionPlayers = []; + private HashSet LowerSpeedPlayers = []; + + private HashSet LowerVisionPlayers = []; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(645450) + .AutoSetupOption(ref AbilityUseLimit, 3, new IntegerValueRule(1, 20, 1), OptionFormat.Times) + .AutoSetupOption(ref AbilityCooldown, 15f, new FloatValueRule(0.5f, 90f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref ImpostorVision, false) + .AutoSetupOption(ref CanVent, false) + .AutoSetupOption(ref LowerVision, 0.3f, new FloatValueRule(0f, 1.5f, 0.05f), OptionFormat.Multiplier) + .AutoSetupOption(ref LowerSpeed, 0.7f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier); + } - private static OptionItem AbilityUseLimit; - private static OptionItem AbilityCooldown; - private static OptionItem ImpostorVision; - private static OptionItem CanVent; - private static OptionItem LowerVision; - private static OptionItem LowerSpeed; + public override void Init() + { + On = false; + Instances = []; + } - public HashSet KnownFactionPlayers = []; - private HashSet LowerSpeedPlayers = []; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + LowerVisionPlayers = []; + LowerSpeedPlayers = []; + KnownFactionPlayers = []; + playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); + } - private HashSet LowerVisionPlayers = []; + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override bool IsEnable => On; + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat(); + } - public override void SetupCustomOption() - { - StartSetup(645450) - .AutoSetupOption(ref AbilityUseLimit, 3, new IntegerValueRule(1, 20, 1), OptionFormat.Times) - .AutoSetupOption(ref AbilityCooldown, 15f, new FloatValueRule(0.5f, 90f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref ImpostorVision, false) - .AutoSetupOption(ref CanVent, false) - .AutoSetupOption(ref LowerVision, 0.3f, new FloatValueRule(0f, 1.5f, 0.05f), OptionFormat.Multiplier) - .AutoSetupOption(ref LowerSpeed, 0.7f, new FloatValueRule(0.05f, 3f, 0.05f), OptionFormat.Multiplier); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override void Init() - { - On = false; - Instances = []; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - LowerVisionPlayers = []; - LowerSpeedPlayers = []; - KnownFactionPlayers = []; - playerId.SetAbilityUseLimit(AbilityUseLimit.GetInt()); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = AbilityCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(ImpostorVision.GetBool()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.CheckDoubleTrigger(target, () => - { - KnownFactionPlayers.Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - })) + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.CheckDoubleTrigger(target, () => { - int random = IRandom.Instance.Next(4); + KnownFactionPlayers.Add(target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + })) + { + int random = IRandom.Instance.Next(4); - switch (random) - { - case 0: - target.RpcRemoveAbilityUse(); - break; - case 1: - var addons = Options.GroupedAddons[AddonTypes.Harmful].Where(x => !target.Is(x) && CustomRolesHelper.CheckAddonConflict(x, target)); - target.RpcSetCustomRole(addons.RandomElement()); - break; - case 2: - LowerVisionPlayers.Add(target.PlayerId); - target.MarkDirtySettings(); - break; - case 3: - LowerSpeedPlayers.Add(target.PlayerId); - target.MarkDirtySettings(); - break; - } - - killer.Notify(Translator.GetString($"Curser.Notify.{random}")); - killer.SetKillCooldown(); + switch (random) + { + case 0: + target.RpcRemoveAbilityUse(); + break; + case 1: + var addons = Options.GroupedAddons[AddonTypes.Harmful].Where(x => !target.Is(x) && CustomRolesHelper.CheckAddonConflict(x, target)); + target.RpcSetCustomRole(addons.RandomElement()); + break; + case 2: + LowerVisionPlayers.Add(target.PlayerId); + target.MarkDirtySettings(); + break; + case 3: + LowerSpeedPlayers.Add(target.PlayerId); + target.MarkDirtySettings(); + break; } - return false; + killer.Notify(Translator.GetString($"Curser.Notify.{random}")); + killer.SetKillCooldown(); } - public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte playerId) + return false; + } + + public static void OnAnyoneApplyGameOptions(IGameOptions opt, byte playerId) + { + foreach (Curser instance in Instances) { - foreach (Curser instance in Instances) + if (instance.LowerVisionPlayers.Contains(playerId)) { - if (instance.LowerVisionPlayers.Contains(playerId)) - { - opt.SetVision(false); - float vision = LowerVision.GetFloat(); - opt.SetFloat(FloatOptionNames.CrewLightMod, vision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); - } - - if (instance.LowerSpeedPlayers.Contains(playerId)) - { - Main.AllPlayerSpeed[playerId] = LowerSpeed.GetFloat(); - } + opt.SetVision(false); + float vision = LowerVision.GetFloat(); + opt.SetFloat(FloatOptionNames.CrewLightMod, vision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, vision); } - } - public void OnDeath() - { - LowerVisionPlayers.UnionWith(LowerSpeedPlayers); - var players = LowerVisionPlayers.ToValidPlayers().ToArray(); - LowerVisionPlayers.Clear(); - LowerSpeedPlayers.Clear(); - players.Do(x => x.MarkDirtySettings()); + if (instance.LowerSpeedPlayers.Contains(playerId)) + { + Main.AllPlayerSpeed[playerId] = LowerSpeed.GetFloat(); + } } } + + public void OnDeath() + { + LowerVisionPlayers.UnionWith(LowerSpeedPlayers); + var players = LowerVisionPlayers.ToValidPlayers().ToArray(); + LowerVisionPlayers.Clear(); + LowerSpeedPlayers.Clear(); + players.Do(x => x.MarkDirtySettings()); + } } \ No newline at end of file diff --git a/Roles/Neutral/DarkHide.cs b/Roles/Neutral/DarkHide.cs index f70e68a74..be6bb06ab 100644 --- a/Roles/Neutral/DarkHide.cs +++ b/Roles/Neutral/DarkHide.cs @@ -4,126 +4,125 @@ using EHR.Modules; using Hazel; using InnerNet; -namespace EHR.Neutral +namespace EHR.Neutral; + +// Credit: https://github.com/Yumenopai/TownOfHost_Y +public class DarkHide : RoleBase { - // Credit: https://github.com/Yumenopai/TownOfHost_Y - public class DarkHide : RoleBase + private const int Id = 12900; + private static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + private static OptionItem CanCountNeutralKiller; + private static OptionItem CanVent; + public static OptionItem SnatchesWin; + + private float CurrentKillCooldown = Options.DefaultKillCooldown; + public bool IsWinKill; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 12900; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.DarkHide); - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - private static OptionItem CanCountNeutralKiller; - private static OptionItem CanVent; - public static OptionItem SnatchesWin; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]) + .SetValueFormat(OptionFormat.Seconds); - private float CurrentKillCooldown = Options.DefaultKillCooldown; - public bool IsWinKill; + CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); - public override bool IsEnable => PlayerIdList.Count > 0; + HasImpostorVision = new BooleanOptionItem(Id + 11, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); - public override void SetupCustomOption() + CanCountNeutralKiller = new BooleanOptionItem(Id + 12, "CanCountNeutralKiller", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); + + SnatchesWin = new BooleanOptionItem(Id + 13, "SnatchesWin", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); + } + + public override void Init() + { + PlayerIdList = []; + CurrentKillCooldown = KillCooldown.GetFloat(); + IsWinKill = false; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + CurrentKillCooldown = KillCooldown.GetFloat(); + IsWinKill = false; + + DRpcSetKillCount(Utils.GetPlayerById(playerId)); + } + + public static void ReceiveRPC(MessageReader msg) + { + byte DarkHiderId = msg.ReadByte(); + if (Main.PlayerStates[DarkHiderId].Role is not DarkHide { IsEnable: true } dh) return; + + bool IsKillerKill = msg.ReadBoolean(); + dh.IsWinKill = IsKillerKill; + } + + private void DRpcSetKillCount(PlayerControl player) + { + if (!IsEnable || !Utils.DoRPC || !AmongUsClient.Instance.AmHost) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDarkHiderKillCount, SendOption.Reliable); + writer.Write(player.PlayerId); + writer.Write(IsWinKill); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CurrentKillCooldown; + } + + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl Ktarget) + { + CustomRoles targetRole = Ktarget.GetCustomRole(); + bool succeeded = targetRole.IsImpostor(); + if (CanCountNeutralKiller.GetBool() && !Ktarget.Is(CustomRoles.Arsonist) && !Ktarget.Is(CustomRoles.Revolutionist)) succeeded = succeeded || Ktarget.IsNeutralKiller(); + + if (succeeded && SnatchesWin.GetBool()) IsWinKill = true; + + DRpcSetKillCount(killer); + MessageWriter SabotageFixWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, killer.GetClientId()); + SabotageFixWriter.Write((byte)SystemTypes.Electrical); + SabotageFixWriter.WriteNetObject(killer); + AmongUsClient.Instance.FinishRpcImmediately(SabotageFixWriter); + + foreach (PlayerControl target in Main.AllPlayerControls) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.DarkHide); + if (target.PlayerId == killer.PlayerId || target.Data.Disconnected) continue; - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); - - HasImpostorVision = new BooleanOptionItem(Id + 11, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); - - CanCountNeutralKiller = new BooleanOptionItem(Id + 12, "CanCountNeutralKiller", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); - - SnatchesWin = new BooleanOptionItem(Id + 13, "SnatchesWin", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.DarkHide]); - } - - public override void Init() - { - PlayerIdList = []; - CurrentKillCooldown = KillCooldown.GetFloat(); - IsWinKill = false; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - CurrentKillCooldown = KillCooldown.GetFloat(); - IsWinKill = false; - - DRpcSetKillCount(Utils.GetPlayerById(playerId)); - } - - public static void ReceiveRPC(MessageReader msg) - { - byte DarkHiderId = msg.ReadByte(); - if (Main.PlayerStates[DarkHiderId].Role is not DarkHide { IsEnable: true } dh) return; - - bool IsKillerKill = msg.ReadBoolean(); - dh.IsWinKill = IsKillerKill; - } - - private void DRpcSetKillCount(PlayerControl player) - { - if (!IsEnable || !Utils.DoRPC || !AmongUsClient.Instance.AmHost) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDarkHiderKillCount, SendOption.Reliable); - writer.Write(player.PlayerId); - writer.Write(IsWinKill); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CurrentKillCooldown; - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl Ktarget) - { - CustomRoles targetRole = Ktarget.GetCustomRole(); - bool succeeded = targetRole.IsImpostor(); - if (CanCountNeutralKiller.GetBool() && !Ktarget.Is(CustomRoles.Arsonist) && !Ktarget.Is(CustomRoles.Revolutionist)) succeeded = succeeded || Ktarget.IsNeutralKiller(); - - if (succeeded && SnatchesWin.GetBool()) IsWinKill = true; - - DRpcSetKillCount(killer); - MessageWriter SabotageFixWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, killer.GetClientId()); + SabotageFixWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, target.GetClientId()); SabotageFixWriter.Write((byte)SystemTypes.Electrical); - SabotageFixWriter.WriteNetObject(killer); + SabotageFixWriter.WriteNetObject(target); AmongUsClient.Instance.FinishRpcImmediately(SabotageFixWriter); - - foreach (PlayerControl target in Main.AllPlayerControls) - { - if (target.PlayerId == killer.PlayerId || target.Data.Disconnected) continue; - - SabotageFixWriter = AmongUsClient.Instance.StartRpcImmediately(ShipStatus.Instance.NetId, (byte)RpcCalls.UpdateSystem, SendOption.Reliable, target.GetClientId()); - SabotageFixWriter.Write((byte)SystemTypes.Electrical); - SabotageFixWriter.WriteNetObject(target); - AmongUsClient.Instance.FinishRpcImmediately(SabotageFixWriter); - } - - return true; } + + return true; } } \ No newline at end of file diff --git a/Roles/Neutral/Doomsayer.cs b/Roles/Neutral/Doomsayer.cs index 727a16294..1ad3e1555 100644 --- a/Roles/Neutral/Doomsayer.cs +++ b/Roles/Neutral/Doomsayer.cs @@ -3,124 +3,123 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Doomsayer : RoleBase { - public class Doomsayer : RoleBase + private const int Id = 27000; + private static List PlayerIdList = []; + public static List GuessedRoles = []; + public static Dictionary GuessingToWin = []; + + private static int GuessesCount; + public static int GuessesCountPerMeeting; + public static bool CantGuess; + + private static OptionItem DoomsayerAmountOfGuessesToWin; + public static OptionItem DCanGuessImpostors; + public static OptionItem DCanGuessCrewmates; + public static OptionItem DCanGuessNeutrals; + public static OptionItem DCanGuessAdt; + public static OptionItem AdvancedSettings; + public static OptionItem MaxNumberOfGuessesPerMeeting; + public static OptionItem KillCorrectlyGuessedPlayers; + public static OptionItem DoesNotSuicideWhenMisguessing; + public static OptionItem MisguessRolePrevGuessRoleUntilNextMeeting; + public static OptionItem DoomsayerTryHideMsg; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 27000; - private static List PlayerIdList = []; - public static List GuessedRoles = []; - public static Dictionary GuessingToWin = []; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doomsayer); - private static int GuessesCount; - public static int GuessesCountPerMeeting; - public static bool CantGuess; + DoomsayerAmountOfGuessesToWin = new IntegerOptionItem(Id + 10, "DoomsayerAmountOfGuessesToWin", new(1, 10, 1), 3, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]) + .SetValueFormat(OptionFormat.Times); - private static OptionItem DoomsayerAmountOfGuessesToWin; - public static OptionItem DCanGuessImpostors; - public static OptionItem DCanGuessCrewmates; - public static OptionItem DCanGuessNeutrals; - public static OptionItem DCanGuessAdt; - public static OptionItem AdvancedSettings; - public static OptionItem MaxNumberOfGuessesPerMeeting; - public static OptionItem KillCorrectlyGuessedPlayers; - public static OptionItem DoesNotSuicideWhenMisguessing; - public static OptionItem MisguessRolePrevGuessRoleUntilNextMeeting; - public static OptionItem DoomsayerTryHideMsg; + DCanGuessImpostors = new BooleanOptionItem(Id + 12, "DCanGuessImpostors", true, TabGroup.NeutralRoles, true) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); - public override bool IsEnable => PlayerIdList.Count > 0; + DCanGuessCrewmates = new BooleanOptionItem(Id + 13, "DCanGuessCrewmates", true, TabGroup.NeutralRoles, true) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doomsayer); + DCanGuessNeutrals = new BooleanOptionItem(Id + 14, "DCanGuessNeutrals", true, TabGroup.NeutralRoles, true) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); - DoomsayerAmountOfGuessesToWin = new IntegerOptionItem(Id + 10, "DoomsayerAmountOfGuessesToWin", new(1, 10, 1), 3, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]) - .SetValueFormat(OptionFormat.Times); + DCanGuessAdt = new BooleanOptionItem(Id + 15, "DCanGuessAdt", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); - DCanGuessImpostors = new BooleanOptionItem(Id + 12, "DCanGuessImpostors", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + AdvancedSettings = new BooleanOptionItem(Id + 16, "DoomsayerAdvancedSettings", true, TabGroup.NeutralRoles, true) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); - DCanGuessCrewmates = new BooleanOptionItem(Id + 13, "DCanGuessCrewmates", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + MaxNumberOfGuessesPerMeeting = new IntegerOptionItem(Id + 17, "DoomsayerMaxNumberOfGuessesPerMeeting", new(1, 10, 1), 2, TabGroup.NeutralRoles) + .SetParent(AdvancedSettings); - DCanGuessNeutrals = new BooleanOptionItem(Id + 14, "DCanGuessNeutrals", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + KillCorrectlyGuessedPlayers = new BooleanOptionItem(Id + 18, "DoomsayerKillCorrectlyGuessedPlayers", true, TabGroup.NeutralRoles, true) + .SetParent(AdvancedSettings); - DCanGuessAdt = new BooleanOptionItem(Id + 15, "DCanGuessAdt", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + DoesNotSuicideWhenMisguessing = new BooleanOptionItem(Id + 19, "DoomsayerDoesNotSuicideWhenMisguessing", true, TabGroup.NeutralRoles) + .SetParent(AdvancedSettings); - AdvancedSettings = new BooleanOptionItem(Id + 16, "DoomsayerAdvancedSettings", true, TabGroup.NeutralRoles, true) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + MisguessRolePrevGuessRoleUntilNextMeeting = new BooleanOptionItem(Id + 20, "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting", true, TabGroup.NeutralRoles, true) + .SetParent(DoesNotSuicideWhenMisguessing); - MaxNumberOfGuessesPerMeeting = new IntegerOptionItem(Id + 17, "DoomsayerMaxNumberOfGuessesPerMeeting", new(1, 10, 1), 2, TabGroup.NeutralRoles) - .SetParent(AdvancedSettings); + DoomsayerTryHideMsg = new BooleanOptionItem(Id + 21, "DoomsayerTryHideMsg", true, TabGroup.NeutralRoles, true) + .SetColor(Color.green) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); + } - KillCorrectlyGuessedPlayers = new BooleanOptionItem(Id + 18, "DoomsayerKillCorrectlyGuessedPlayers", true, TabGroup.NeutralRoles, true) - .SetParent(AdvancedSettings); + public override void Init() + { + PlayerIdList = []; + GuessedRoles = []; + GuessingToWin = []; + GuessesCount = 0; + GuessesCountPerMeeting = 0; + CantGuess = false; + } - DoesNotSuicideWhenMisguessing = new BooleanOptionItem(Id + 19, "DoomsayerDoesNotSuicideWhenMisguessing", true, TabGroup.NeutralRoles) - .SetParent(AdvancedSettings); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + GuessingToWin.TryAdd(playerId, GuessesCount); - MisguessRolePrevGuessRoleUntilNextMeeting = new BooleanOptionItem(Id + 20, "DoomsayerMisguessRolePrevGuessRoleUntilNextMeeting", true, TabGroup.NeutralRoles, true) - .SetParent(DoesNotSuicideWhenMisguessing); + GuessesCount = 0; + GuessesCountPerMeeting = 0; + CantGuess = false; + } - DoomsayerTryHideMsg = new BooleanOptionItem(Id + 21, "DoomsayerTryHideMsg", true, TabGroup.NeutralRoles, true) - .SetColor(Color.green) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Doomsayer]); - } + public static void SendRPC(PlayerControl player) + { + if (!Utils.DoRPC) return; - public override void Init() - { - PlayerIdList = []; - GuessedRoles = []; - GuessingToWin = []; - GuessesCount = 0; - GuessesCountPerMeeting = 0; - CantGuess = false; - } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDoomsayerProgress, SendOption.Reliable); + writer.Write(player.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - GuessingToWin.TryAdd(playerId, GuessesCount); + public static void ReceiveRPC(MessageReader reader) + { + byte DoomsayerId = reader.ReadByte(); + GuessingToWin[DoomsayerId]++; + } - GuessesCount = 0; - GuessesCountPerMeeting = 0; - CantGuess = false; - } + public static void CheckCountGuess(PlayerControl doomsayer) + { + if (!(GuessingToWin[doomsayer.PlayerId] >= DoomsayerAmountOfGuessesToWin.GetInt())) return; - public static void SendRPC(PlayerControl player) - { - if (!Utils.DoRPC) return; + GuessingToWin[doomsayer.PlayerId] = DoomsayerAmountOfGuessesToWin.GetInt(); + GuessesCount = DoomsayerAmountOfGuessesToWin.GetInt(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Doomsayer); + CustomWinnerHolder.WinnerIds.Add(doomsayer.PlayerId); + } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDoomsayerProgress, SendOption.Reliable); - writer.Write(player.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + public override void OnReportDeadBody() + { + if (!(IsEnable && AdvancedSettings.GetBool())) return; - public static void ReceiveRPC(MessageReader reader) - { - byte DoomsayerId = reader.ReadByte(); - GuessingToWin[DoomsayerId]++; - } - - public static void CheckCountGuess(PlayerControl doomsayer) - { - if (!(GuessingToWin[doomsayer.PlayerId] >= DoomsayerAmountOfGuessesToWin.GetInt())) return; - - GuessingToWin[doomsayer.PlayerId] = DoomsayerAmountOfGuessesToWin.GetInt(); - GuessesCount = DoomsayerAmountOfGuessesToWin.GetInt(); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Doomsayer); - CustomWinnerHolder.WinnerIds.Add(doomsayer.PlayerId); - } - - public override void OnReportDeadBody() - { - if (!(IsEnable && AdvancedSettings.GetBool())) return; - - CantGuess = false; - GuessesCountPerMeeting = 0; - } + CantGuess = false; + GuessesCountPerMeeting = 0; } } \ No newline at end of file diff --git a/Roles/Neutral/Doppelganger.cs b/Roles/Neutral/Doppelganger.cs index d82ea7885..1f53d9f76 100644 --- a/Roles/Neutral/Doppelganger.cs +++ b/Roles/Neutral/Doppelganger.cs @@ -8,308 +8,307 @@ using Hazel; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Doppelganger : RoleBase { - public class Doppelganger : RoleBase + private const int Id = 648100; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem ImpostorVision; + private static OptionItem MaxSteals; + private static OptionItem ResetMode; + private static OptionItem ResetTimer; + + public static int LocalPlayerChangeSkinTimes; + + public static Dictionary DoppelVictim = []; + public static Dictionary DoppelPresentSkin = []; + private static Dictionary TotalSteals = []; + private static Dictionary DoppelDefaultSkin = []; + + private static readonly string[] ResetModes = + [ + "DGRM.None", + "DGRM.OnMeeting", + "DGRM.AfterTime" + ]; + + private byte DGId; + private long StealTimeStamp; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 648100; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doppelganger); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem ImpostorVision; - private static OptionItem MaxSteals; - private static OptionItem ResetMode; - private static OptionItem ResetTimer; + MaxSteals = new IntegerOptionItem(Id + 10, "DoppelMaxSteals", new(1, 14, 1), 9, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - public static int LocalPlayerChangeSkinTimes; + KillCooldown = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]) + .SetValueFormat(OptionFormat.Seconds); - public static Dictionary DoppelVictim = []; - public static Dictionary DoppelPresentSkin = []; - private static Dictionary TotalSteals = []; - private static Dictionary DoppelDefaultSkin = []; + CanVent = new BooleanOptionItem(Id + 14, "CanVent", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - private static readonly string[] ResetModes = - [ - "DGRM.None", - "DGRM.OnMeeting", - "DGRM.AfterTime" - ]; + ImpostorVision = new BooleanOptionItem(Id + 15, "ImpostorVision", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - private byte DGId; - private long StealTimeStamp; + ResetMode = new StringOptionItem(Id + 12, "DGResetMode", ResetModes, 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - public override bool IsEnable => PlayerIdList.Count > 0; + ResetTimer = new FloatOptionItem(Id + 13, "DGResetTimer", new(0f, 60f, 1f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + DoppelVictim = []; + TotalSteals = []; + DoppelPresentSkin = []; + DoppelDefaultSkin = []; + DGId = byte.MaxValue; + StealTimeStamp = 0; + + LocalPlayerChangeSkinTimes = 0; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + DGId = playerId; + TotalSteals.Add(playerId, 0); + PlayerControl pc = Utils.GetPlayerById(playerId); + + if (playerId == PlayerControl.LocalPlayer.PlayerId && Main.NickName.Length != 0) + DoppelVictim[playerId] = Main.NickName; + else + DoppelVictim[playerId] = pc.Data.PlayerName; + + DoppelDefaultSkin[playerId] = pc.CurrentOutfit; + StealTimeStamp = 0; + } + + private void SendRPC(byte playerId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDoppelgangerStealLimit, SendOption.Reliable); + writer.Write(playerId); + writer.Write(TotalSteals[playerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + int Limit = reader.ReadInt32(); + if (!TotalSteals.TryAdd(PlayerId, 0)) TotalSteals[PlayerId] = Limit; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } + + private static NetworkedPlayerInfo.PlayerOutfit Set(NetworkedPlayerInfo.PlayerOutfit instance, string playerName, int colorId, string hatId, string skinId, string visorId, string petId, string nameplateId) + { + instance.PlayerName = playerName; + instance.ColorId = colorId; + instance.HatId = hatId; + instance.SkinId = skinId; + instance.VisorId = visorId; + instance.PetId = petId; + instance.NamePlateId = nameplateId; + return instance; + } + + private static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit) + { + var sender = CustomRpcSender.Create($"Doppelganger.RpcChangeSkin({pc.Data.PlayerName})"); + + pc.SetName(newOutfit.PlayerName); + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetName) + .Write(pc.Data.NetId) + .Write(newOutfit.PlayerName) + .EndRpc(); + + Main.AllPlayerNames[pc.PlayerId] = newOutfit.PlayerName; + + pc.SetColor(newOutfit.ColorId); + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) + .Write(pc.Data.NetId) + .Write((byte)newOutfit.ColorId) + .EndRpc(); + + pc.SetHat(newOutfit.HatId, newOutfit.ColorId); + pc.Data.DefaultOutfit.HatSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetHatStr) + .Write(newOutfit.HatId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetHatStr)) + .EndRpc(); + + pc.SetSkin(newOutfit.SkinId, newOutfit.ColorId); + pc.Data.DefaultOutfit.SkinSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetSkinStr) + .Write(newOutfit.SkinId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) + .EndRpc(); + + pc.SetVisor(newOutfit.VisorId, newOutfit.ColorId); + pc.Data.DefaultOutfit.VisorSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetVisorStr) + .Write(newOutfit.VisorId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) + .EndRpc(); + + pc.SetPet(newOutfit.PetId); + pc.Data.DefaultOutfit.PetSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) + .Write(newOutfit.PetId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) + .EndRpc(); + + pc.SetNamePlate(newOutfit.NamePlateId); + pc.Data.DefaultOutfit.NamePlateSequenceId += 10; + + sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetNamePlateStr) + .Write(newOutfit.NamePlateId) + .Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) + .EndRpc(); + + sender.SendMessage(); + DoppelPresentSkin[pc.PlayerId] = newOutfit; + + if (pc.IsLocalPlayer()) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Doppelganger); + LocalPlayerChangeSkinTimes++; + if (LocalPlayerChangeSkinTimes >= 2) Achievements.Type.Mimicry.Complete(); + } + } - MaxSteals = new IntegerOptionItem(Id + 10, "DoppelMaxSteals", new(1, 14, 1), 9, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); + public static void OnCheckMurderEnd(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || Camouflage.IsCamouflage || Camouflager.IsActive || Main.PlayerStates[killer.PlayerId].Role is not Doppelganger { IsEnable: true } dg) return; - KillCooldown = new FloatOptionItem(Id + 11, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 14, "CanVent", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - - ImpostorVision = new BooleanOptionItem(Id + 15, "ImpostorVision", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - - ResetMode = new StringOptionItem(Id + 12, "DGResetMode", ResetModes, 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]); - - ResetTimer = new FloatOptionItem(Id + 13, "DGResetTimer", new(0f, 60f, 1f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Doppelganger]) - .SetValueFormat(OptionFormat.Seconds); + if (target.IsShifted()) + { + Logger.Info("Target was shapeshifting", "Doppelganger"); + return; } - public override void Init() + if (TotalSteals[killer.PlayerId] >= MaxSteals.GetInt()) { - PlayerIdList = []; - DoppelVictim = []; - TotalSteals = []; - DoppelPresentSkin = []; - DoppelDefaultSkin = []; - DGId = byte.MaxValue; - StealTimeStamp = 0; - - LocalPlayerChangeSkinTimes = 0; + TotalSteals[killer.PlayerId] = MaxSteals.GetInt(); + return; } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - DGId = playerId; - TotalSteals.Add(playerId, 0); - PlayerControl pc = Utils.GetPlayerById(playerId); + TotalSteals[killer.PlayerId]++; - if (playerId == PlayerControl.LocalPlayer.PlayerId && Main.NickName.Length != 0) - DoppelVictim[playerId] = Main.NickName; - else - DoppelVictim[playerId] = pc.Data.PlayerName; + dg.StealTimeStamp = Utils.TimeStamp; - DoppelDefaultSkin[playerId] = pc.CurrentOutfit; - StealTimeStamp = 0; - } + string kname; - private void SendRPC(byte playerId) - { - if (!IsEnable || !Utils.DoRPC) return; + if (killer.IsLocalPlayer() && Main.NickName.Length != 0) + kname = Main.NickName; + else + kname = killer.Data.PlayerName; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetDoppelgangerStealLimit, SendOption.Reliable); - writer.Write(playerId); - writer.Write(TotalSteals[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + string tname; - public static void ReceiveRPC(MessageReader reader) - { - byte PlayerId = reader.ReadByte(); - int Limit = reader.ReadInt32(); - if (!TotalSteals.TryAdd(PlayerId, 0)) TotalSteals[PlayerId] = Limit; - } + if (target.IsLocalPlayer() && Main.NickName.Length != 0) + tname = Main.NickName; + else + tname = target.Data.PlayerName; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + NetworkedPlayerInfo.PlayerOutfit killerSkin = Set(new(), kname, killer.CurrentOutfit.ColorId, killer.CurrentOutfit.HatId, killer.CurrentOutfit.SkinId, killer.CurrentOutfit.VisorId, killer.CurrentOutfit.PetId, killer.CurrentOutfit.NamePlateId); - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + NetworkedPlayerInfo.PlayerOutfit targetSkin = Set(new(), tname, target.CurrentOutfit.ColorId, target.CurrentOutfit.HatId, target.CurrentOutfit.SkinId, target.CurrentOutfit.VisorId, target.CurrentOutfit.PetId, target.CurrentOutfit.NamePlateId); - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(ImpostorVision.GetBool()); - } - - private static NetworkedPlayerInfo.PlayerOutfit Set(NetworkedPlayerInfo.PlayerOutfit instance, string playerName, int colorId, string hatId, string skinId, string visorId, string petId, string nameplateId) - { - instance.PlayerName = playerName; - instance.ColorId = colorId; - instance.HatId = hatId; - instance.SkinId = skinId; - instance.VisorId = visorId; - instance.PetId = petId; - instance.NamePlateId = nameplateId; - return instance; - } - - private static void RpcChangeSkin(PlayerControl pc, NetworkedPlayerInfo.PlayerOutfit newOutfit) - { - var sender = CustomRpcSender.Create($"Doppelganger.RpcChangeSkin({pc.Data.PlayerName})"); - - pc.SetName(newOutfit.PlayerName); - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetName) - .Write(pc.Data.NetId) - .Write(newOutfit.PlayerName) - .EndRpc(); - - Main.AllPlayerNames[pc.PlayerId] = newOutfit.PlayerName; - - pc.SetColor(newOutfit.ColorId); - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetColor) - .Write(pc.Data.NetId) - .Write((byte)newOutfit.ColorId) - .EndRpc(); - - pc.SetHat(newOutfit.HatId, newOutfit.ColorId); - pc.Data.DefaultOutfit.HatSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetHatStr) - .Write(newOutfit.HatId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetHatStr)) - .EndRpc(); - - pc.SetSkin(newOutfit.SkinId, newOutfit.ColorId); - pc.Data.DefaultOutfit.SkinSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetSkinStr) - .Write(newOutfit.SkinId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetSkinStr)) - .EndRpc(); - - pc.SetVisor(newOutfit.VisorId, newOutfit.ColorId); - pc.Data.DefaultOutfit.VisorSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetVisorStr) - .Write(newOutfit.VisorId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetVisorStr)) - .EndRpc(); - - pc.SetPet(newOutfit.PetId); - pc.Data.DefaultOutfit.PetSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetPetStr) - .Write(newOutfit.PetId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetPetStr)) - .EndRpc(); - - pc.SetNamePlate(newOutfit.NamePlateId); - pc.Data.DefaultOutfit.NamePlateSequenceId += 10; - - sender.AutoStartRpc(pc.NetId, (byte)RpcCalls.SetNamePlateStr) - .Write(newOutfit.NamePlateId) - .Write(pc.GetNextRpcSequenceId(RpcCalls.SetNamePlateStr)) - .EndRpc(); - - sender.SendMessage(); - DoppelPresentSkin[pc.PlayerId] = newOutfit; - - if (pc.IsLocalPlayer()) - { - LocalPlayerChangeSkinTimes++; - if (LocalPlayerChangeSkinTimes >= 2) Achievements.Type.Mimicry.Complete(); - } - } - - public static void OnCheckMurderEnd(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null || Camouflage.IsCamouflage || Camouflager.IsActive || Main.PlayerStates[killer.PlayerId].Role is not Doppelganger { IsEnable: true } dg) return; - - if (target.IsShifted()) - { - Logger.Info("Target was shapeshifting", "Doppelganger"); - return; - } - - if (TotalSteals[killer.PlayerId] >= MaxSteals.GetInt()) - { - TotalSteals[killer.PlayerId] = MaxSteals.GetInt(); - return; - } - - TotalSteals[killer.PlayerId]++; - - dg.StealTimeStamp = Utils.TimeStamp; - - string kname; - - if (killer.IsLocalPlayer() && Main.NickName.Length != 0) - kname = Main.NickName; - else - kname = killer.Data.PlayerName; - - string tname; - - if (target.IsLocalPlayer() && Main.NickName.Length != 0) - tname = Main.NickName; - else - tname = target.Data.PlayerName; - - NetworkedPlayerInfo.PlayerOutfit killerSkin = Set(new(), kname, killer.CurrentOutfit.ColorId, killer.CurrentOutfit.HatId, killer.CurrentOutfit.SkinId, killer.CurrentOutfit.VisorId, killer.CurrentOutfit.PetId, killer.CurrentOutfit.NamePlateId); - - NetworkedPlayerInfo.PlayerOutfit targetSkin = Set(new(), tname, target.CurrentOutfit.ColorId, target.CurrentOutfit.HatId, target.CurrentOutfit.SkinId, target.CurrentOutfit.VisorId, target.CurrentOutfit.PetId, target.CurrentOutfit.NamePlateId); - - DoppelVictim[target.PlayerId] = tname; + DoppelVictim[target.PlayerId] = tname; - RpcChangeSkin(target, killerSkin); - Logger.Info("Changed target skin", "Doppelganger"); - RpcChangeSkin(killer, targetSkin); - Logger.Info("Changed killer skin", "Doppelganger"); + RpcChangeSkin(target, killerSkin); + Logger.Info("Changed target skin", "Doppelganger"); + RpcChangeSkin(killer, targetSkin); + Logger.Info("Changed killer skin", "Doppelganger"); - target.Notify(Translator.GetString("DoppelgangerWarning")); + target.Notify(Translator.GetString("DoppelgangerWarning")); - dg.SendRPC(killer.PlayerId); - Utils.NotifyRoles(); - killer.ResetKillCooldown(); - killer.SetKillCooldown(); + dg.SendRPC(killer.PlayerId); + Utils.NotifyRoles(); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); - target.SetRealKiller(killer); - } + target.SetRealKiller(killer); + } - public override void OnReportDeadBody() + public override void OnReportDeadBody() + { + try { - try + if (ResetMode.GetValue() == 1 && TotalSteals[DGId] > 0) { - if (ResetMode.GetValue() == 1 && TotalSteals[DGId] > 0) - { - PlayerControl pc = Utils.GetPlayerById(DGId); - if (pc == null) return; + PlayerControl pc = Utils.GetPlayerById(DGId); + if (pc == null) return; - PlayerControl currentTarget = Main.AllPlayerControls.FirstOrDefault(x => x?.GetRealName() == DoppelVictim[pc.PlayerId]); - - if (currentTarget != null) - { - RpcChangeSkin(currentTarget, DoppelPresentSkin[currentTarget.PlayerId]); - RpcChangeSkin(pc, DoppelDefaultSkin[pc.PlayerId]); - DoppelVictim[pc.PlayerId] = string.Empty; - } - } - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (ResetMode.GetValue() == 2 && TotalSteals[pc.PlayerId] > 0 && Utils.TimeStamp - StealTimeStamp > ResetTimer.GetInt()) - { - PlayerControl currentTarget = Main.AllPlayerControls.FirstOrDefault(x => x.GetRealName() == DoppelVictim[pc.PlayerId]); + PlayerControl currentTarget = Main.AllPlayerControls.FirstOrDefault(x => x?.GetRealName() == DoppelVictim[pc.PlayerId]); if (currentTarget != null) { RpcChangeSkin(currentTarget, DoppelPresentSkin[currentTarget.PlayerId]); RpcChangeSkin(pc, DoppelDefaultSkin[pc.PlayerId]); DoppelVictim[pc.PlayerId] = string.Empty; - - if (GameStates.IsInTask) Utils.NotifyRoles(); } } } - - public override string GetProgressText(byte playerId, bool comms) + catch (Exception e) { - return Utils.ColorString(TotalSteals[playerId] < MaxSteals.GetInt() ? Utils.GetRoleColor(CustomRoles.Doppelganger).ShadeColor(0.25f) : Color.gray, TotalSteals.TryGetValue(playerId, out int stealLimit) ? $"({MaxSteals.GetInt() - stealLimit})" : "Invalid"); + Utils.ThrowException(e); } } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (ResetMode.GetValue() == 2 && TotalSteals[pc.PlayerId] > 0 && Utils.TimeStamp - StealTimeStamp > ResetTimer.GetInt()) + { + PlayerControl currentTarget = Main.AllPlayerControls.FirstOrDefault(x => x.GetRealName() == DoppelVictim[pc.PlayerId]); + + if (currentTarget != null) + { + RpcChangeSkin(currentTarget, DoppelPresentSkin[currentTarget.PlayerId]); + RpcChangeSkin(pc, DoppelDefaultSkin[pc.PlayerId]); + DoppelVictim[pc.PlayerId] = string.Empty; + + if (GameStates.IsInTask) Utils.NotifyRoles(); + } + } + } + + public override string GetProgressText(byte playerId, bool comms) + { + return Utils.ColorString(TotalSteals[playerId] < MaxSteals.GetInt() ? Utils.GetRoleColor(CustomRoles.Doppelganger).ShadeColor(0.25f) : Color.gray, TotalSteals.TryGetValue(playerId, out int stealLimit) ? $"({MaxSteals.GetInt() - stealLimit})" : "Invalid"); + } } \ No newline at end of file diff --git a/Roles/Neutral/Eclipse.cs b/Roles/Neutral/Eclipse.cs index bc440d3b5..722a06cd4 100644 --- a/Roles/Neutral/Eclipse.cs +++ b/Roles/Neutral/Eclipse.cs @@ -3,82 +3,81 @@ using System.Collections.Generic; using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Eclipse : RoleBase { - public class Eclipse : RoleBase + private const int Id = 648200; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + public static OptionItem CanVent; + private static OptionItem VisionIncrease; + private static OptionItem StartVision; + private static OptionItem MaxVision; + + private float Vision; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 648200; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Eclipse); - private static OptionItem KillCooldown; - public static OptionItem CanVent; - private static OptionItem VisionIncrease; - private static OptionItem StartVision; - private static OptionItem MaxVision; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) + .SetValueFormat(OptionFormat.Seconds); - private float Vision; + StartVision = new FloatOptionItem(Id + 11, "EclipseStartVision", new(0.1f, 5f, 0.1f), 0.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) + .SetValueFormat(OptionFormat.Multiplier); - public override bool IsEnable => PlayerIdList.Count > 0; + VisionIncrease = new FloatOptionItem(Id + 12, "EclipseVisionIncrease", new(0.05f, 5f, 0.05f), 0.1f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Eclipse); + MaxVision = new FloatOptionItem(Id + 13, "EclipseMaxVision", new(0.25f, 5f, 0.25f), 1.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) + .SetValueFormat(OptionFormat.Multiplier); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) - .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]); + } - StartVision = new FloatOptionItem(Id + 11, "EclipseStartVision", new(0.1f, 5f, 0.1f), 0.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) - .SetValueFormat(OptionFormat.Multiplier); + public override void Init() + { + PlayerIdList = []; + } - VisionIncrease = new FloatOptionItem(Id + 12, "EclipseVisionIncrease", new(0.05f, 5f, 0.05f), 0.1f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) - .SetValueFormat(OptionFormat.Multiplier); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + Vision = StartVision.GetFloat(); + } - MaxVision = new FloatOptionItem(Id + 13, "EclipseMaxVision", new(0.25f, 5f, 0.25f), 1.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]) - .SetValueFormat(OptionFormat.Multiplier); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Eclipse]); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override void Init() - { - PlayerIdList = []; - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(true); + opt.SetFloat(FloatOptionNames.CrewLightMod, Vision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - Vision = StartVision.GetFloat(); - } + public override void OnMurder(PlayerControl pc, PlayerControl target) + { + if (pc == null) return; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + float currentVision = Vision; + Vision += VisionIncrease.GetFloat(); + if (Vision > MaxVision.GetFloat()) Vision = MaxVision.GetFloat(); - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(true); - opt.SetFloat(FloatOptionNames.CrewLightMod, Vision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Vision); - } - - public override void OnMurder(PlayerControl pc, PlayerControl target) - { - if (pc == null) return; - - float currentVision = Vision; - Vision += VisionIncrease.GetFloat(); - if (Vision > MaxVision.GetFloat()) Vision = MaxVision.GetFloat(); - - if (Math.Abs(Vision - currentVision) > 0.1f) pc.MarkDirtySettings(); - } + if (Math.Abs(Vision - currentVision) > 0.1f) pc.MarkDirtySettings(); } } \ No newline at end of file diff --git a/Roles/Neutral/Enderman.cs b/Roles/Neutral/Enderman.cs index 31a764746..77a8363c3 100644 --- a/Roles/Neutral/Enderman.cs +++ b/Roles/Neutral/Enderman.cs @@ -3,123 +3,122 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Enderman : RoleBase { - internal class Enderman : RoleBase + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem Time; + private byte EndermanId = byte.MaxValue; + + private (Vector2 Position, long MarkTimeStamp, bool TP) MarkedPosition = (Vector2.zero, 0, false); + private static int Id => 643200; + + private PlayerControl EndermanPC => GetPlayerById(EndermanId); + + public override bool IsEnable => EndermanId != byte.MaxValue; + + public override void SetupCustomOption() { - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem Time; - private byte EndermanId = byte.MaxValue; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Enderman); - private (Vector2 Position, long MarkTimeStamp, bool TP) MarkedPosition = (Vector2.zero, 0, false); - private static int Id => 643200; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]) + .SetValueFormat(OptionFormat.Seconds); - private PlayerControl EndermanPC => GetPlayerById(EndermanId); + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]); - public override bool IsEnable => EndermanId != byte.MaxValue; + Time = new IntegerOptionItem(Id + 4, "EndermanSecondsBeforeTP", new(1, 60, 1), 7, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]) + .SetValueFormat(OptionFormat.Seconds); + } - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Enderman); + public override void Init() + { + EndermanId = byte.MaxValue; + MarkedPosition.TP = false; + } - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]) - .SetValueFormat(OptionFormat.Seconds); + public override void Add(byte playerId) + { + EndermanId = playerId; + MarkedPosition = (Vector2.zero, 0, false); + } - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - Time = new IntegerOptionItem(Id + 4, "EndermanSecondsBeforeTP", new(1, 60, 1), 7, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Enderman]) - .SetValueFormat(OptionFormat.Seconds); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override void Init() - { - EndermanId = byte.MaxValue; - MarkedPosition.TP = false; - } + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); + } - public override void Add(byte playerId) - { - EndermanId = playerId; - MarkedPosition = (Vector2.zero, 0, false); - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(true); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = Time.GetInt() + 2f; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(true); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = Time.GetInt() + 2f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = Time.GetInt() + 2f; - } + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = Time.GetInt() + 2f; + } - public override void OnPet(PlayerControl pc) - { - MarkPosition(); - } + public override void OnPet(PlayerControl pc) + { + MarkPosition(); + } - public override bool OnSabotage(PlayerControl pc) - { - MarkPosition(); - return false; - } + public override bool OnSabotage(PlayerControl pc) + { + MarkPosition(); + return false; + } - public override bool OnVanish(PlayerControl pc) - { - MarkPosition(); - return false; - } + public override bool OnVanish(PlayerControl pc) + { + MarkPosition(); + return false; + } - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - MarkPosition(); - return false; - } + MarkPosition(); + return false; + } - private void MarkPosition() - { - if (!IsEnable || EndermanPC.HasAbilityCD()) return; + private void MarkPosition() + { + if (!IsEnable || EndermanPC.HasAbilityCD()) return; - EndermanPC.AddAbilityCD(Time.GetInt() + 2); - MarkedPosition.MarkTimeStamp = TimeStamp; - MarkedPosition.Position = EndermanPC.Pos(); - MarkedPosition.TP = true; - EndermanPC.Notify(GetString("MarkDone")); - } + EndermanPC.AddAbilityCD(Time.GetInt() + 2); + MarkedPosition.MarkTimeStamp = TimeStamp; + MarkedPosition.Position = EndermanPC.Pos(); + MarkedPosition.TP = true; + EndermanPC.Notify(GetString("MarkDone")); + } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable || !GameStates.IsInTask || !MarkedPosition.TP || !EndermanPC.IsAlive() || MarkedPosition.MarkTimeStamp + Time.GetInt() >= TimeStamp) return; + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable || !GameStates.IsInTask || !MarkedPosition.TP || !EndermanPC.IsAlive() || MarkedPosition.MarkTimeStamp + Time.GetInt() >= TimeStamp) return; - EndermanPC.TP(MarkedPosition.Position); - MarkedPosition.TP = false; - } + EndermanPC.TP(MarkedPosition.Position); + MarkedPosition.TP = false; + } - public override void OnReportDeadBody() - { - if (!IsEnable) return; + public override void OnReportDeadBody() + { + if (!IsEnable) return; - MarkedPosition.TP = false; - } + MarkedPosition.TP = false; } } \ No newline at end of file diff --git a/Roles/Neutral/Evolver.cs b/Roles/Neutral/Evolver.cs index 2125d07c2..bde7cae26 100644 --- a/Roles/Neutral/Evolver.cs +++ b/Roles/Neutral/Evolver.cs @@ -5,303 +5,302 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Evolver : RoleBase { - public class Evolver : RoleBase + private const int Id = 644500; + public static bool On; + + private static OptionItem KillCooldown; + private int ChooseTimer; + private PlayerControl EvolverPC; + + private long LastUpdate = Utils.TimeStamp; + private int SelectedUpgradeIndex; + + private (float KillCooldown, bool ImpostorVision, float Vision, float Speed, int KillDistance, bool CanVent, int VentUseLimit, bool CanSabotage, int SabotageUseLimit, bool Shielded) Stats; + private List Upgrades; + + public override bool IsEnable => On; + private (float MinKillCooldown, float MaxVision, float MaxSpeed, int MaxKillDistance) Limits => (1f, Stats.ImpostorVision ? 1.5f : 5f, 3f, 2); + + public override void SetupCustomOption() { - private const int Id = 644500; - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Evolver); - private static OptionItem KillCooldown; - private int ChooseTimer; - private PlayerControl EvolverPC; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evolver]) + .SetValueFormat(OptionFormat.Seconds); + } - private long LastUpdate = Utils.TimeStamp; - private int SelectedUpgradeIndex; + public override void Init() + { + On = false; + } - private (float KillCooldown, bool ImpostorVision, float Vision, float Speed, int KillDistance, bool CanVent, int VentUseLimit, bool CanSabotage, int SabotageUseLimit, bool Shielded) Stats; - private List Upgrades; + public override void Add(byte playerId) + { + On = true; - public override bool IsEnable => On; - private (float MinKillCooldown, float MaxVision, float MaxSpeed, int MaxKillDistance) Limits => (1f, Stats.ImpostorVision ? 1.5f : 5f, 3f, 2); + EvolverPC = Utils.GetPlayerById(playerId); + ChooseTimer = 0; + Upgrades = []; + SelectedUpgradeIndex = -1; - public override void SetupCustomOption() + OptionBackupData opts = Main.RealOptionsData; + + Stats = ( + KillCooldown.GetFloat(), + false, + opts.GetFloat(FloatOptionNames.CrewLightMod), + opts.GetFloat(FloatOptionNames.PlayerSpeedMod), + Math.Clamp(opts.GetInt(Int32OptionNames.KillDistance), 0, 2), + false, 0, false, 0, false + ); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Stats.KillCooldown; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.inVent || Stats is { CanVent: true, VentUseLimit: > 0 }; + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || Stats is { CanSabotage: true, SabotageUseLimit: > 0 }; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetFloat(FloatOptionNames.CrewLightMod, Stats.Vision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Stats.Vision); + opt.SetVision(Stats.ImpostorVision); + Main.AllPlayerSpeed[playerId] = Stats.Speed; + opt.SetInt(Int32OptionNames.KillDistance, Stats.KillDistance); + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + Stats.VentUseLimit--; + EnsureStatLimits(); + } + + public override bool OnSabotage(PlayerControl pc) + { + Stats.SabotageUseLimit--; + EnsureStatLimits(); + return true; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !Stats.Shielded; + } + + public override void OnReportDeadBody() + { + Stats.Shielded = false; + + if (ChooseTimer > 0) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Evolver); - - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Evolver]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - - EvolverPC = Utils.GetPlayerById(playerId); + ApplySelectedUpgradeAndReset(); ChooseTimer = 0; - Upgrades = []; - SelectedUpgradeIndex = -1; - - OptionBackupData opts = Main.RealOptionsData; - - Stats = ( - KillCooldown.GetFloat(), - false, - opts.GetFloat(FloatOptionNames.CrewLightMod), - opts.GetFloat(FloatOptionNames.PlayerSpeedMod), - Math.Clamp(opts.GetInt(Int32OptionNames.KillDistance), 0, 2), - false, 0, false, 0, false - ); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Stats.KillCooldown; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.inVent || Stats is { CanVent: true, VentUseLimit: > 0 }; - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || Stats is { CanSabotage: true, SabotageUseLimit: > 0 }; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetFloat(FloatOptionNames.CrewLightMod, Stats.Vision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Stats.Vision); - opt.SetVision(Stats.ImpostorVision); - Main.AllPlayerSpeed[playerId] = Stats.Speed; - opt.SetInt(Int32OptionNames.KillDistance, Stats.KillDistance); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - Stats.VentUseLimit--; - EnsureStatLimits(); - } - - public override bool OnSabotage(PlayerControl pc) - { - Stats.SabotageUseLimit--; - EnsureStatLimits(); - return true; - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !Stats.Shielded; - } - - public override void OnReportDeadBody() - { - Stats.Shielded = false; - - if (ChooseTimer > 0) - { - ApplySelectedUpgradeAndReset(); - ChooseTimer = 0; - } - } - - public override void AfterMeetingTasks() - { - if (!EvolverPC.IsAlive()) return; - - Upgrades = Enum.GetValues().Except(GetBannedUpgradeList()).Shuffle().GetRange(0, 3); - SelectedUpgradeIndex = 0; - ChooseTimer = 15; - - SendRPC(); - Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 2, SelectedUpgradeIndex); - Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 3, ChooseTimer); - } - - private IEnumerable GetBannedUpgradeList() - { - var banned = new List(); - if (Stats.KillCooldown <= Limits.MinKillCooldown) banned.Add(Upgrade.DecreaseKillCooldown); - - if (Stats.ImpostorVision) banned.Add(Upgrade.GainImpostorVision); - - if (Stats.Vision >= Limits.MaxVision) banned.Add(Upgrade.IncreaseVision); - - if (Stats.Speed >= Limits.MaxSpeed) banned.Add(Upgrade.IncreaseSpeed); - - if (Stats.KillDistance >= Limits.MaxKillDistance) banned.Add(Upgrade.IncreaseKillDistance); - - banned.Add(Stats.CanVent ? Upgrade.GainVent : Upgrade.IncreaseVentUseLimit); - banned.Add(Stats.CanSabotage ? Upgrade.GainSabotage : Upgrade.IncreaseSabotageUseLimit); - return banned; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !pc.IsAlive() || ChooseTimer == 0 || SelectedUpgradeIndex == -1 || Upgrades.Count == 0 || LastUpdate == Utils.TimeStamp) return; - - LastUpdate = Utils.TimeStamp; - - ChooseTimer--; - Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 3, ChooseTimer); - - if (ChooseTimer == 0) ApplySelectedUpgradeAndReset(); - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - private void ApplySelectedUpgradeAndReset() - { - switch (Upgrades[SelectedUpgradeIndex]) - { - case Upgrade.DecreaseKillCooldown: - Stats.KillCooldown -= 2.5f; - break; - case Upgrade.GainImpostorVision: - Stats.ImpostorVision = true; - break; - case Upgrade.IncreaseVision: - Stats.Vision += 0.4f; - break; - case Upgrade.IncreaseSpeed: - Stats.Speed += 0.25f; - break; - case Upgrade.IncreaseKillDistance: - Stats.KillDistance++; - break; - case Upgrade.GainVent: - Stats.CanVent = true; - Stats.VentUseLimit = 1; - break; - case Upgrade.IncreaseVentUseLimit: - Stats.VentUseLimit += 3; - break; - case Upgrade.GainSabotage: - Stats.CanSabotage = true; - Stats.SabotageUseLimit = 1; - break; - case Upgrade.IncreaseSabotageUseLimit: - Stats.SabotageUseLimit += 2; - break; - case Upgrade.GainShield: - Stats.Shielded = true; - break; - } - - EnsureStatLimits(); - - if (GameStates.IsInTask) - { - EvolverPC.ResetKillCooldown(); - EvolverPC.SyncSettings(); - if (Main.KillTimers[EvolverPC.PlayerId] > Stats.KillCooldown) EvolverPC.SetKillCooldown(); - - if (PlayerControl.LocalPlayer.PlayerId == EvolverPC.PlayerId) HudManager.Instance.SetHudActive(EvolverPC, EvolverPC.Data.Role, true); - } - - Upgrades = []; - SelectedUpgradeIndex = -1; - Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 2, SelectedUpgradeIndex); - } - - private void EnsureStatLimits() - { - Stats.KillCooldown = Math.Max(Stats.KillCooldown, Limits.MinKillCooldown); - Stats.Vision = Math.Min(Stats.Vision, Limits.MaxVision); - Stats.Speed = Math.Min(Stats.Speed, Limits.MaxSpeed); - Stats.KillDistance = Math.Min(Stats.KillDistance, Limits.MaxKillDistance); - Stats.VentUseLimit = Math.Max(Stats.VentUseLimit, 0); - Stats.SabotageUseLimit = Math.Max(Stats.SabotageUseLimit, 0); - - Utils.NotifyRoles(SpecifySeer: EvolverPC, SpecifyTarget: EvolverPC); - Logger.Info($" KCD: {Stats.KillCooldown}, Vision: {Stats.Vision}, Speed: {Stats.Speed}, KDis: {Stats.KillDistance}", "Evolver Stats"); - } - - public override void OnPet(PlayerControl pc) - { - if (Upgrades.Count == 0 || SelectedUpgradeIndex == -1) return; - - SelectedUpgradeIndex = (SelectedUpgradeIndex + 1) % Upgrades.Count; - ChooseTimer = 8; - - Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 2, SelectedUpgradeIndex); - Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 3, ChooseTimer); - } - - private void SendRPC() - { - MessageWriter w = Utils.CreateRPC(CustomRPC.SyncRoleData); - w.Write(EvolverPC.PlayerId); - w.WritePacked(1); - w.Write(Upgrades.Count); - Upgrades.ForEach(x => w.WritePacked((int)x)); - Utils.EndRPC(w); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - Upgrades = []; - for (var i = 0; i < Upgrades.Count; i++) Upgrades.Add((Upgrade)reader.ReadPackedInt32()); - - break; - case 2: - SelectedUpgradeIndex = reader.ReadPackedInt32(); - if (SelectedUpgradeIndex == -1) Upgrades = []; - - break; - case 3: - ChooseTimer = reader.ReadPackedInt32(); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != EvolverPC.PlayerId || (seer.IsModClient() && !hud) || meeting || ChooseTimer == 0 || Upgrades.Count == 0 || SelectedUpgradeIndex == -1) return string.Empty; - - return string.Format(Translator.GetString("EvolverSuffix"), ChooseTimer, Translator.GetString($"EvolverUpgrade.{Upgrades[SelectedUpgradeIndex]}"), string.Join(", ", Upgrades.ConvertAll(x => Translator.GetString($"EvolverUpgrade.{x}")))); - } - - public override string GetProgressText(byte playerId, bool comms) - { - var sb = new StringBuilder(); - - if (Stats.CanVent) sb.Append(string.Format(Translator.GetString("EvolverProgress.Vent"), Stats.VentUseLimit)); - - if (Stats.CanSabotage) sb.Append(string.Format(Translator.GetString("EvolverProgress.Sabotage"), Stats.SabotageUseLimit)); - - if (sb.Length > 0) - { - sb.Insert(0, "<#ffffff>"); - sb.Append(""); - } - - return sb.ToString(); - } - - private enum Upgrade - { - DecreaseKillCooldown, - GainImpostorVision, - IncreaseVision, - IncreaseSpeed, - IncreaseKillDistance, - GainVent, - IncreaseVentUseLimit, - GainSabotage, - IncreaseSabotageUseLimit, - GainShield } } + + public override void AfterMeetingTasks() + { + if (!EvolverPC.IsAlive()) return; + + Upgrades = Enum.GetValues().Except(GetBannedUpgradeList()).Shuffle().GetRange(0, 3); + SelectedUpgradeIndex = 0; + ChooseTimer = 15; + + SendRPC(); + Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 2, SelectedUpgradeIndex); + Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 3, ChooseTimer); + } + + private IEnumerable GetBannedUpgradeList() + { + var banned = new List(); + if (Stats.KillCooldown <= Limits.MinKillCooldown) banned.Add(Upgrade.DecreaseKillCooldown); + + if (Stats.ImpostorVision) banned.Add(Upgrade.GainImpostorVision); + + if (Stats.Vision >= Limits.MaxVision) banned.Add(Upgrade.IncreaseVision); + + if (Stats.Speed >= Limits.MaxSpeed) banned.Add(Upgrade.IncreaseSpeed); + + if (Stats.KillDistance >= Limits.MaxKillDistance) banned.Add(Upgrade.IncreaseKillDistance); + + banned.Add(Stats.CanVent ? Upgrade.GainVent : Upgrade.IncreaseVentUseLimit); + banned.Add(Stats.CanSabotage ? Upgrade.GainSabotage : Upgrade.IncreaseSabotageUseLimit); + return banned; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !pc.IsAlive() || ChooseTimer == 0 || SelectedUpgradeIndex == -1 || Upgrades.Count == 0 || LastUpdate == Utils.TimeStamp) return; + + LastUpdate = Utils.TimeStamp; + + ChooseTimer--; + Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 3, ChooseTimer); + + if (ChooseTimer == 0) ApplySelectedUpgradeAndReset(); + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + private void ApplySelectedUpgradeAndReset() + { + switch (Upgrades[SelectedUpgradeIndex]) + { + case Upgrade.DecreaseKillCooldown: + Stats.KillCooldown -= 2.5f; + break; + case Upgrade.GainImpostorVision: + Stats.ImpostorVision = true; + break; + case Upgrade.IncreaseVision: + Stats.Vision += 0.4f; + break; + case Upgrade.IncreaseSpeed: + Stats.Speed += 0.25f; + break; + case Upgrade.IncreaseKillDistance: + Stats.KillDistance++; + break; + case Upgrade.GainVent: + Stats.CanVent = true; + Stats.VentUseLimit = 1; + break; + case Upgrade.IncreaseVentUseLimit: + Stats.VentUseLimit += 3; + break; + case Upgrade.GainSabotage: + Stats.CanSabotage = true; + Stats.SabotageUseLimit = 1; + break; + case Upgrade.IncreaseSabotageUseLimit: + Stats.SabotageUseLimit += 2; + break; + case Upgrade.GainShield: + Stats.Shielded = true; + break; + } + + EnsureStatLimits(); + + if (GameStates.IsInTask) + { + EvolverPC.ResetKillCooldown(); + EvolverPC.SyncSettings(); + if (Main.KillTimers[EvolverPC.PlayerId] > Stats.KillCooldown) EvolverPC.SetKillCooldown(); + + if (PlayerControl.LocalPlayer.PlayerId == EvolverPC.PlayerId) HudManager.Instance.SetHudActive(EvolverPC, EvolverPC.Data.Role, true); + } + + Upgrades = []; + SelectedUpgradeIndex = -1; + Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 2, SelectedUpgradeIndex); + } + + private void EnsureStatLimits() + { + Stats.KillCooldown = Math.Max(Stats.KillCooldown, Limits.MinKillCooldown); + Stats.Vision = Math.Min(Stats.Vision, Limits.MaxVision); + Stats.Speed = Math.Min(Stats.Speed, Limits.MaxSpeed); + Stats.KillDistance = Math.Min(Stats.KillDistance, Limits.MaxKillDistance); + Stats.VentUseLimit = Math.Max(Stats.VentUseLimit, 0); + Stats.SabotageUseLimit = Math.Max(Stats.SabotageUseLimit, 0); + + Utils.NotifyRoles(SpecifySeer: EvolverPC, SpecifyTarget: EvolverPC); + Logger.Info($" KCD: {Stats.KillCooldown}, Vision: {Stats.Vision}, Speed: {Stats.Speed}, KDis: {Stats.KillDistance}", "Evolver Stats"); + } + + public override void OnPet(PlayerControl pc) + { + if (Upgrades.Count == 0 || SelectedUpgradeIndex == -1) return; + + SelectedUpgradeIndex = (SelectedUpgradeIndex + 1) % Upgrades.Count; + ChooseTimer = 8; + + Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 2, SelectedUpgradeIndex); + Utils.SendRPC(CustomRPC.SyncRoleData, EvolverPC.PlayerId, 3, ChooseTimer); + } + + private void SendRPC() + { + MessageWriter w = Utils.CreateRPC(CustomRPC.SyncRoleData); + w.Write(EvolverPC.PlayerId); + w.WritePacked(1); + w.Write(Upgrades.Count); + Upgrades.ForEach(x => w.WritePacked((int)x)); + Utils.EndRPC(w); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + Upgrades = []; + for (var i = 0; i < Upgrades.Count; i++) Upgrades.Add((Upgrade)reader.ReadPackedInt32()); + + break; + case 2: + SelectedUpgradeIndex = reader.ReadPackedInt32(); + if (SelectedUpgradeIndex == -1) Upgrades = []; + + break; + case 3: + ChooseTimer = reader.ReadPackedInt32(); + break; + } + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != EvolverPC.PlayerId || (seer.IsModClient() && !hud) || meeting || ChooseTimer == 0 || Upgrades.Count == 0 || SelectedUpgradeIndex == -1) return string.Empty; + + return string.Format(Translator.GetString("EvolverSuffix"), ChooseTimer, Translator.GetString($"EvolverUpgrade.{Upgrades[SelectedUpgradeIndex]}"), string.Join(", ", Upgrades.ConvertAll(x => Translator.GetString($"EvolverUpgrade.{x}")))); + } + + public override string GetProgressText(byte playerId, bool comms) + { + var sb = new StringBuilder(); + + if (Stats.CanVent) sb.Append(string.Format(Translator.GetString("EvolverProgress.Vent"), Stats.VentUseLimit)); + + if (Stats.CanSabotage) sb.Append(string.Format(Translator.GetString("EvolverProgress.Sabotage"), Stats.SabotageUseLimit)); + + if (sb.Length > 0) + { + sb.Insert(0, "<#ffffff>"); + sb.Append(""); + } + + return sb.ToString(); + } + + private enum Upgrade + { + DecreaseKillCooldown, + GainImpostorVision, + IncreaseVision, + IncreaseSpeed, + IncreaseKillDistance, + GainVent, + IncreaseVentUseLimit, + GainSabotage, + IncreaseSabotageUseLimit, + GainShield + } } \ No newline at end of file diff --git a/Roles/Neutral/Executioner.cs b/Roles/Neutral/Executioner.cs index e985be461..63bb5ed03 100644 --- a/Roles/Neutral/Executioner.cs +++ b/Roles/Neutral/Executioner.cs @@ -5,182 +5,181 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Executioner : RoleBase { - public class Executioner : RoleBase + private const int Id = 10700; + private static List PlayerIdList = []; + + private static OptionItem CanTargetImpostor; + private static OptionItem CanTargetNeutralKiller; + private static OptionItem CanTargetNeutralBenign; + private static OptionItem CanTargetNeutralEvil; + public static OptionItem KnowTargetRole; + public static OptionItem CanGuessTarget; + public static OptionItem ChangeRolesAfterTargetKilled; + + public static Dictionary Target = []; + + public static readonly CustomRoles[] CRoleChangeRoles = + [ + CustomRoles.Amnesiac, + CustomRoles.Maverick, + CustomRoles.CrewmateEHR, + CustomRoles.Jester, + CustomRoles.Opportunist, + CustomRoles.Convict, + CustomRoles.CyberStar, + CustomRoles.Bodyguard, + CustomRoles.Dictator, + CustomRoles.Doctor + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 10700; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Executioner); + CanTargetImpostor = new BooleanOptionItem(Id + 10, "ExecutionerCanTargetImpostor", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanTargetNeutralKiller = new BooleanOptionItem(Id + 12, "ExecutionerCanTargetNeutralKiller", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanTargetNeutralBenign = new BooleanOptionItem(Id + 14, "CanTargetNeutralBenign", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanTargetNeutralEvil = new BooleanOptionItem(Id + 15, "CanTargetNeutralEvil", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + KnowTargetRole = new BooleanOptionItem(Id + 13, "KnowTargetRole", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + CanGuessTarget = new BooleanOptionItem(Id + 17, "CanGuessTarget", false, TabGroup.NeutralRoles).SetParent(KnowTargetRole); + ChangeRolesAfterTargetKilled = new StringOptionItem(Id + 11, "ExecutionerChangeRolesAfterTargetKilled", CRoleChangeRoles.Select(x => x.ToColoredString()).ToArray(), 1, TabGroup.NeutralRoles, noTranslation: true).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); + } - private static OptionItem CanTargetImpostor; - private static OptionItem CanTargetNeutralKiller; - private static OptionItem CanTargetNeutralBenign; - private static OptionItem CanTargetNeutralEvil; - public static OptionItem KnowTargetRole; - public static OptionItem CanGuessTarget; - public static OptionItem ChangeRolesAfterTargetKilled; + public override void Init() + { + PlayerIdList = []; + Target = []; + } - public static Dictionary Target = []; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); - public static readonly CustomRoles[] CRoleChangeRoles = - [ - CustomRoles.Amnesiac, - CustomRoles.Maverick, - CustomRoles.CrewmateEHR, - CustomRoles.Jester, - CustomRoles.Opportunist, - CustomRoles.Convict, - CustomRoles.CyberStar, - CustomRoles.Bodyguard, - CustomRoles.Dictator, - CustomRoles.Doctor - ]; - - public override bool IsEnable => PlayerIdList.Count > 0; - - public override void SetupCustomOption() + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Executioner); - CanTargetImpostor = new BooleanOptionItem(Id + 10, "ExecutionerCanTargetImpostor", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - CanTargetNeutralKiller = new BooleanOptionItem(Id + 12, "ExecutionerCanTargetNeutralKiller", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - CanTargetNeutralBenign = new BooleanOptionItem(Id + 14, "CanTargetNeutralBenign", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - CanTargetNeutralEvil = new BooleanOptionItem(Id + 15, "CanTargetNeutralEvil", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - KnowTargetRole = new BooleanOptionItem(Id + 13, "KnowTargetRole", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - CanGuessTarget = new BooleanOptionItem(Id + 17, "CanGuessTarget", false, TabGroup.NeutralRoles).SetParent(KnowTargetRole); - ChangeRolesAfterTargetKilled = new StringOptionItem(Id + 11, "ExecutionerChangeRolesAfterTargetKilled", CRoleChangeRoles.Select(x => x.ToColoredString()).ToArray(), 1, TabGroup.NeutralRoles, noTranslation: true).SetParent(CustomRoleSpawnChances[CustomRoles.Executioner]); - } - - public override void Init() - { - PlayerIdList = []; - Target = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - - LateTask.New(() => + try { - try + List targetList = []; + targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetNeutralBenign.GetBool() || !target.IsNeutralBenign() where CanTargetNeutralEvil.GetBool() || !target.IsNeutralEvil() where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); + targetList.AddRange(Main.AllPlayerControls.Where(x => x.IsCrewmate())); + if (!CanTargetNeutralBenign.GetBool() && !CanTargetNeutralEvil.GetBool() && !CanTargetNeutralKiller.GetBool()) targetList.RemoveAll(x => x.GetCustomRole().IsNeutral() || x.Is(CustomRoles.Bloodlust)); + + if (targetList.Count == 0) { - List targetList = []; - targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetNeutralBenign.GetBool() || !target.IsNeutralBenign() where CanTargetNeutralEvil.GetBool() || !target.IsNeutralEvil() where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); - targetList.AddRange(Main.AllPlayerControls.Where(x => x.IsCrewmate())); - if (!CanTargetNeutralBenign.GetBool() && !CanTargetNeutralEvil.GetBool() && !CanTargetNeutralKiller.GetBool()) targetList.RemoveAll(x => x.GetCustomRole().IsNeutral() || x.Is(CustomRoles.Bloodlust)); - - if (targetList.Count == 0) - { - ChangeRole(Utils.GetPlayerById(playerId)); - return; - } - - PlayerControl SelectedTarget = targetList.RandomElement(); - Target[playerId] = SelectedTarget.PlayerId; - SendRPC(playerId, SelectedTarget.PlayerId, "SetTarget"); - Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()}'s target: {SelectedTarget.GetNameWithRole().RemoveHtmlTags()}", "Executioner"); + ChangeRole(Utils.GetPlayerById(playerId)); + return; } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "Executioner.Add"); - } - }, 3f, log: false); - } - public static void SendRPC(byte executionerId, byte targetId = 0x73, string Progress = "") - { - if (!AmongUsClient.Instance.AmHost || !Utils.DoRPC) return; - - MessageWriter writer; - - switch (Progress) - { - case "SetTarget": - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetExecutionerTarget, SendOption.Reliable); - writer.Write(executionerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - break; - case "": - if (!AmongUsClient.Instance.AmHost) return; - - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveExecutionerTarget, SendOption.Reliable); - writer.Write(executionerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - break; + PlayerControl SelectedTarget = targetList.RandomElement(); + Target[playerId] = SelectedTarget.PlayerId; + SendRPC(playerId, SelectedTarget.PlayerId, "SetTarget"); + Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()}'s target: {SelectedTarget.GetNameWithRole().RemoveHtmlTags()}", "Executioner"); } - } - - public static void ReceiveRPC(MessageReader reader, bool SetTarget) - { - if (SetTarget) + catch (Exception ex) { - byte ExecutionerId = reader.ReadByte(); - byte TargetId = reader.ReadByte(); - Target[ExecutionerId] = TargetId; + Logger.Error(ex.ToString(), "Executioner.Add"); } - else - Target.Remove(reader.ReadByte()); - } + }, 3f, log: false); + } - public static void ChangeRoleByTarget(PlayerControl target) + public static void SendRPC(byte executionerId, byte targetId = 0x73, string Progress = "") + { + if (!AmongUsClient.Instance.AmHost || !Utils.DoRPC) return; + + MessageWriter writer; + + switch (Progress) { - byte Executioner = Target.GetKeyByValue(target.PlayerId); - PlayerControl ExePC = Utils.GetPlayerById(Executioner); - CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; - ExePC.RpcChangeRoleBasis(newRole); - ExePC.RpcSetCustomRole(newRole); - Target.Remove(Executioner); - SendRPC(Executioner); - ExePC.Notify(Translator.GetString("ExecutionerRoleChanged")); - Utils.NotifyRoles(SpecifySeer: ExePC, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: ExePC); - } + case "SetTarget": + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetExecutionerTarget, SendOption.Reliable); + writer.Write(executionerId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + break; + case "": + if (!AmongUsClient.Instance.AmHost) return; - private static void ChangeRole(PlayerControl executioner) - { - CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; - executioner.RpcChangeRoleBasis(newRole); - executioner.RpcSetCustomRole(newRole); - Target.Remove(executioner.PlayerId); - SendRPC(executioner.PlayerId); - executioner.Notify(Translator.GetString("ExecutionerRoleChanged")); - } - - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; - - if (!KnowTargetRole.GetBool()) return false; - - return player.Is(CustomRoles.Executioner) && Target.TryGetValue(player.PlayerId, out byte tar) && tar == target.PlayerId; - } - - public static string TargetMark(PlayerControl seer, PlayerControl target) - { - bool GetValue = Target.TryGetValue(seer.PlayerId, out byte targetId) || (!seer.IsAlive() && Target.ContainsValue(target.PlayerId)); - return GetValue && targetId == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), "♦") : string.Empty; - } - - public static bool CheckExileTarget(NetworkedPlayerInfo exiled, bool Check = false) - { - foreach (KeyValuePair kvp in Target.Where(x => x.Value == exiled.PlayerId)) - { - PlayerControl executioner = Utils.GetPlayerById(kvp.Key); - if (executioner == null || !executioner.IsAlive() || executioner.Data.Disconnected) continue; - - if (!Check) ExeWin(kvp.Key); - - return true; - } - - return false; - } - - private static void ExeWin(byte playerId) - { - CustomWinnerHolder.SetWinnerOrAdditonalWinner(CustomWinner.Executioner); - CustomWinnerHolder.WinnerIds.Add(playerId); + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveExecutionerTarget, SendOption.Reliable); + writer.Write(executionerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + break; } } + + public static void ReceiveRPC(MessageReader reader, bool SetTarget) + { + if (SetTarget) + { + byte ExecutionerId = reader.ReadByte(); + byte TargetId = reader.ReadByte(); + Target[ExecutionerId] = TargetId; + } + else + Target.Remove(reader.ReadByte()); + } + + public static void ChangeRoleByTarget(PlayerControl target) + { + byte Executioner = Target.GetKeyByValue(target.PlayerId); + PlayerControl ExePC = Utils.GetPlayerById(Executioner); + CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; + ExePC.RpcChangeRoleBasis(newRole); + ExePC.RpcSetCustomRole(newRole); + Target.Remove(Executioner); + SendRPC(Executioner); + ExePC.Notify(Translator.GetString("ExecutionerRoleChanged")); + Utils.NotifyRoles(SpecifySeer: ExePC, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: ExePC); + } + + private static void ChangeRole(PlayerControl executioner) + { + CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; + executioner.RpcChangeRoleBasis(newRole); + executioner.RpcSetCustomRole(newRole); + Target.Remove(executioner.PlayerId); + SendRPC(executioner.PlayerId); + executioner.Notify(Translator.GetString("ExecutionerRoleChanged")); + } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (!KnowTargetRole.GetBool()) return false; + + return player.Is(CustomRoles.Executioner) && Target.TryGetValue(player.PlayerId, out byte tar) && tar == target.PlayerId; + } + + public static string TargetMark(PlayerControl seer, PlayerControl target) + { + bool GetValue = Target.TryGetValue(seer.PlayerId, out byte targetId) || (!seer.IsAlive() && Target.ContainsValue(target.PlayerId)); + return GetValue && targetId == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Executioner), "♦") : string.Empty; + } + + public static bool CheckExileTarget(NetworkedPlayerInfo exiled, bool Check = false) + { + foreach (KeyValuePair kvp in Target.Where(x => x.Value == exiled.PlayerId)) + { + PlayerControl executioner = Utils.GetPlayerById(kvp.Key); + if (executioner == null || !executioner.IsAlive() || executioner.Data.Disconnected) continue; + + if (!Check) ExeWin(kvp.Key); + + return true; + } + + return false; + } + + private static void ExeWin(byte playerId) + { + CustomWinnerHolder.SetWinnerOrAdditonalWinner(CustomWinner.Executioner); + CustomWinnerHolder.WinnerIds.Add(playerId); + } } \ No newline at end of file diff --git a/Roles/Neutral/FFF.cs b/Roles/Neutral/FFF.cs index 4784eeae5..398342bd0 100644 --- a/Roles/Neutral/FFF.cs +++ b/Roles/Neutral/FFF.cs @@ -2,165 +2,164 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class FFF : RoleBase { - public class FFF : RoleBase + private const int Id = 11300; + private static bool On; + + private static OptionItem ChooseConverted; + private static OptionItem MisFireKillTarget; + + private static OptionItem CanKillLovers; + private static OptionItem CanKillMadmate; + private static OptionItem CanKillCharmed; + private static OptionItem CanKillSidekicks; + private static OptionItem CanKillEgoists; + private static OptionItem CanKillContagious; + private static OptionItem CanKillUndead; + + private static OptionItem ChangeRoleWhenCantWin; + private static OptionItem ChangeRole; + + private static readonly CustomRoles[] ChangeRoles = + [ + CustomRoles.Amnesiac, + CustomRoles.Pursuer, + CustomRoles.Maverick, + CustomRoles.Totocalcio, + CustomRoles.Opportunist, + CustomRoles.Crewmate, + CustomRoles.Jester, + CustomRoles.Convict + ]; + + public bool IsWon; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 11300; - private static bool On; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.FFF, zeroOne: false); + MisFireKillTarget = new BooleanOptionItem(Id + 11, "FFFMisFireKillTarget", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.FFF]); + ChooseConverted = new BooleanOptionItem(Id + 12, "FFFChooseConverted", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.FFF]); + CanKillMadmate = new BooleanOptionItem(Id + 13, "FFFCanKillMadmate", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + CanKillCharmed = new BooleanOptionItem(Id + 14, "FFFCanKillCharmed", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + CanKillLovers = new BooleanOptionItem(Id + 15, "FFFCanKillLovers", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + CanKillSidekicks = new BooleanOptionItem(Id + 16, "FFFCanKillSidekick", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + CanKillEgoists = new BooleanOptionItem(Id + 17, "FFFCanKillEgoist", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + CanKillContagious = new BooleanOptionItem(Id + 19, "FFFCanKillContagious", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + CanKillUndead = new BooleanOptionItem(Id + 21, "FFFCanKillUndead", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); + ChangeRoleWhenCantWin = new BooleanOptionItem(Id + 18, "VultureChangeRoleWhenCantWin", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.FFF]); + ChangeRole = new StringOptionItem(Id + 20, "VultureChangeRole", ChangeRoles.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true).SetParent(ChangeRoleWhenCantWin); + } - private static OptionItem ChooseConverted; - private static OptionItem MisFireKillTarget; + public override void Init() + { + On = false; + IsWon = false; + } - private static OptionItem CanKillLovers; - private static OptionItem CanKillMadmate; - private static OptionItem CanKillCharmed; - private static OptionItem CanKillSidekicks; - private static OptionItem CanKillEgoists; - private static OptionItem CanKillContagious; - private static OptionItem CanKillUndead; + public override void Add(byte playerId) + { + On = true; + IsWon = false; + } - private static OptionItem ChangeRoleWhenCantWin; - private static OptionItem ChangeRole; + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } - private static readonly CustomRoles[] ChangeRoles = - [ - CustomRoles.Amnesiac, - CustomRoles.Pursuer, - CustomRoles.Maverick, - CustomRoles.Totocalcio, - CustomRoles.Opportunist, - CustomRoles.Crewmate, - CustomRoles.Jester, - CustomRoles.Convict - ]; + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive() && !IsWon; + } - public bool IsWon; + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(true); + } - public override bool IsEnable => On; + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; - public override void SetupCustomOption() + if (killer.PlayerId == target.PlayerId) return true; + + if (target.GetCustomSubRoles().Any(x => x.IsConverted() || x == CustomRoles.Madmate) + || IsConvertedMainRole(target.GetCustomRole())) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.FFF, zeroOne: false); - MisFireKillTarget = new BooleanOptionItem(Id + 11, "FFFMisFireKillTarget", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.FFF]); - ChooseConverted = new BooleanOptionItem(Id + 12, "FFFChooseConverted", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.FFF]); - CanKillMadmate = new BooleanOptionItem(Id + 13, "FFFCanKillMadmate", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - CanKillCharmed = new BooleanOptionItem(Id + 14, "FFFCanKillCharmed", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - CanKillLovers = new BooleanOptionItem(Id + 15, "FFFCanKillLovers", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - CanKillSidekicks = new BooleanOptionItem(Id + 16, "FFFCanKillSidekick", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - CanKillEgoists = new BooleanOptionItem(Id + 17, "FFFCanKillEgoist", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - CanKillContagious = new BooleanOptionItem(Id + 19, "FFFCanKillContagious", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - CanKillUndead = new BooleanOptionItem(Id + 21, "FFFCanKillUndead", true, TabGroup.NeutralRoles).SetParent(ChooseConverted); - ChangeRoleWhenCantWin = new BooleanOptionItem(Id + 18, "VultureChangeRoleWhenCantWin", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.FFF]); - ChangeRole = new StringOptionItem(Id + 20, "VultureChangeRole", ChangeRoles.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true).SetParent(ChangeRoleWhenCantWin); - } - - public override void Init() - { - On = false; - IsWon = false; - } - - public override void Add(byte playerId) - { - On = true; - IsWon = false; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive() && !IsWon; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(true); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; - - if (killer.PlayerId == target.PlayerId) return true; - - if (target.GetCustomSubRoles().Any(x => x.IsConverted() || x == CustomRoles.Madmate) - || IsConvertedMainRole(target.GetCustomRole())) + if (!ChooseConverted.GetBool()) { - if (!ChooseConverted.GetBool()) - { - if (killer.RpcCheckAndMurder(target)) IsWon = true; + if (killer.RpcCheckAndMurder(target)) IsWon = true; - Logger.Info($"{killer.GetRealName()} killed right target case 1", "FFF"); - return false; - } - - if ( - ((target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Gangster)) && CanKillMadmate.GetBool()) - || ((target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Succubus)) && CanKillCharmed.GetBool()) - || ((target.Is(CustomRoles.Undead) || target.Is(CustomRoles.Necromancer) || target.Is(CustomRoles.Deathknight)) && CanKillUndead.GetBool()) - || (Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId) && CanKillLovers.GetBool()) - || ((target.Is(CustomRoles.Romantic) || target.Is(CustomRoles.RuthlessRomantic) || target.Is(CustomRoles.VengefulRomantic) - || Romantic.PartnerId == target.PlayerId) && CanKillLovers.GetBool()) - || ((target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Jackal) || target.Is(CustomRoles.Recruit)) && CanKillSidekicks.GetBool()) - || (target.Is(CustomRoles.Egoist) && CanKillEgoists.GetBool()) - || ((target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Virus)) && CanKillContagious.GetBool()) - ) - { - if (killer.RpcCheckAndMurder(target)) IsWon = true; - - Logger.Info($"{killer.GetRealName()} killed right target case 2", "FFF"); - return false; - } + Logger.Info($"{killer.GetRealName()} killed right target case 1", "FFF"); + return false; } - if (MisFireKillTarget.GetBool() && killer.RpcCheckAndMurder(target, true)) + if ( + ((target.Is(CustomRoles.Madmate) || target.Is(CustomRoles.Gangster)) && CanKillMadmate.GetBool()) + || ((target.Is(CustomRoles.Charmed) || target.Is(CustomRoles.Succubus)) && CanKillCharmed.GetBool()) + || ((target.Is(CustomRoles.Undead) || target.Is(CustomRoles.Necromancer) || target.Is(CustomRoles.Deathknight)) && CanKillUndead.GetBool()) + || (Main.LoversPlayers.Exists(x => x.PlayerId == target.PlayerId) && CanKillLovers.GetBool()) + || ((target.Is(CustomRoles.Romantic) || target.Is(CustomRoles.RuthlessRomantic) || target.Is(CustomRoles.VengefulRomantic) + || Romantic.PartnerId == target.PlayerId) && CanKillLovers.GetBool()) + || ((target.Is(CustomRoles.Sidekick) || target.Is(CustomRoles.Jackal) || target.Is(CustomRoles.Recruit)) && CanKillSidekicks.GetBool()) + || (target.Is(CustomRoles.Egoist) && CanKillEgoists.GetBool()) + || ((target.Is(CustomRoles.Contagious) || target.Is(CustomRoles.Virus)) && CanKillContagious.GetBool()) + ) { - target.SetRealKiller(killer); - killer.Kill(target); - target.Data.IsDead = true; - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Misfire; - } + if (killer.RpcCheckAndMurder(target)) IsWon = true; - killer.Suicide(PlayerState.DeathReason.Sacrifice); - Logger.Info($"{killer.GetRealName()} killed incorrect target => misfire", "FFF"); - return false; - } - - private static bool IsConvertedMainRole(CustomRoles role) - { - return role is - CustomRoles.Gangster or - CustomRoles.Succubus or - CustomRoles.Deathknight or - CustomRoles.Necromancer or - CustomRoles.Refugee or - CustomRoles.Romantic or - CustomRoles.RuthlessRomantic or - CustomRoles.VengefulRomantic or - CustomRoles.Sidekick or - CustomRoles.Jackal or - CustomRoles.Virus; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive()) return; - - if (ChangeRoleWhenCantWin.GetBool() && !IsWon && Main.AllAlivePlayerControls.All(x => Main.LoversPlayers.TrueForAll(l => l.PlayerId != x.PlayerId) && !x.GetCustomRole().IsRecruitingRole() && !x.GetCustomSubRoles().Any(p => p.IsConverted()))) - { - CustomRoles role = ChangeRoles[ChangeRole.GetValue()]; - pc.RpcSetCustomRole(role); + Logger.Info($"{killer.GetRealName()} killed right target case 2", "FFF"); + return false; } } - public override void SetButtonTexts(HudManager hud, byte id) + if (MisFireKillTarget.GetBool() && killer.RpcCheckAndMurder(target, true)) { - hud.KillButton?.OverrideText(Translator.GetString("FFFButtonText")); + target.SetRealKiller(killer); + killer.Kill(target); + target.Data.IsDead = true; + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Misfire; + } + + killer.Suicide(PlayerState.DeathReason.Sacrifice); + Logger.Info($"{killer.GetRealName()} killed incorrect target => misfire", "FFF"); + return false; + } + + private static bool IsConvertedMainRole(CustomRoles role) + { + return role is + CustomRoles.Gangster or + CustomRoles.Succubus or + CustomRoles.Deathknight or + CustomRoles.Necromancer or + CustomRoles.Refugee or + CustomRoles.Romantic or + CustomRoles.RuthlessRomantic or + CustomRoles.VengefulRomantic or + CustomRoles.Sidekick or + CustomRoles.Jackal or + CustomRoles.Virus; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive()) return; + + if (ChangeRoleWhenCantWin.GetBool() && !IsWon && Main.AllAlivePlayerControls.All(x => Main.LoversPlayers.TrueForAll(l => l.PlayerId != x.PlayerId) && !x.GetCustomRole().IsRecruitingRole() && !x.GetCustomSubRoles().Any(p => p.IsConverted()))) + { + CustomRoles role = ChangeRoles[ChangeRole.GetValue()]; + pc.RpcSetCustomRole(role); } } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("FFFButtonText")); + } } \ No newline at end of file diff --git a/Roles/Neutral/Gamer.cs b/Roles/Neutral/Gamer.cs index 2f5292441..6e611d411 100644 --- a/Roles/Neutral/Gamer.cs +++ b/Roles/Neutral/Gamer.cs @@ -5,196 +5,195 @@ using Hazel; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Gamer : RoleBase { - public class Gamer : RoleBase + private const int Id = 10600; + public static List PlayerIdList = []; + + private static Dictionary PlayerHealth = []; + private static Dictionary GamerHealth = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem HealthMax; + private static OptionItem Damage; + private static OptionItem SelfHealthMax; + private static OptionItem SelfDamage; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 10600; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Gamer); - private static Dictionary PlayerHealth = []; - private static Dictionary GamerHealth = []; + KillCooldown = new FloatOptionItem(Id + 10, "GamerKillCooldown", new(1f, 180f, 1f), 2f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem HealthMax; - private static OptionItem Damage; - private static OptionItem SelfHealthMax; - private static OptionItem SelfDamage; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]); - public override bool IsEnable => PlayerIdList.Count > 0; + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]); - public override void SetupCustomOption() + HealthMax = new IntegerOptionItem(Id + 15, "GamerHealthMax", new(5, 300, 5), 100, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) + .SetValueFormat(OptionFormat.Health); + + Damage = new IntegerOptionItem(Id + 16, "GamerDamage", new(1, 100, 1), 15, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) + .SetValueFormat(OptionFormat.Health); + + SelfHealthMax = new IntegerOptionItem(Id + 17, "GamerSelfHealthMax", new(100, 100, 5), 100, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) + .SetValueFormat(OptionFormat.Health); + + SelfDamage = new IntegerOptionItem(Id + 18, "GamerSelfDamage", new(1, 100, 1), 35, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) + .SetValueFormat(OptionFormat.Health); + } + + public override void Init() + { + PlayerIdList = []; + GamerHealth = []; + PlayerHealth = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + GamerHealth[playerId] = SelfHealthMax.GetInt(); + foreach (PlayerControl pc in Main.AllAlivePlayerControls) PlayerHealth[pc.PlayerId] = HealthMax.GetInt(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + private void SendRPC(byte playerId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetGamerHealth, SendOption.Reliable); + writer.Write(playerId); + writer.Write(GamerHealth.TryGetValue(playerId, out int value) ? value : PlayerHealth[playerId]); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + int Health = reader.ReadInt32(); + + if (GamerHealth.ContainsKey(PlayerId)) + GamerHealth[PlayerId] = Health; + else + PlayerHealth[PlayerId] = Health; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || target.Is(CustomRoles.Gamer) || !PlayerHealth.ContainsKey(target.PlayerId)) return false; + + killer.SetKillCooldown(); + + if (PlayerHealth[target.PlayerId] - Damage.GetInt() < 1) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Gamer); - - KillCooldown = new FloatOptionItem(Id + 10, "GamerKillCooldown", new(1f, 180f, 1f), 2f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]); - - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]); - - HealthMax = new IntegerOptionItem(Id + 15, "GamerHealthMax", new(5, 300, 5), 100, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) - .SetValueFormat(OptionFormat.Health); - - Damage = new IntegerOptionItem(Id + 16, "GamerDamage", new(1, 100, 1), 15, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) - .SetValueFormat(OptionFormat.Health); - - SelfHealthMax = new IntegerOptionItem(Id + 17, "GamerSelfHealthMax", new(100, 100, 5), 100, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) - .SetValueFormat(OptionFormat.Health); - - SelfDamage = new IntegerOptionItem(Id + 18, "GamerSelfDamage", new(1, 100, 1), 35, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Gamer]) - .SetValueFormat(OptionFormat.Health); - } - - public override void Init() - { - PlayerIdList = []; - GamerHealth = []; - PlayerHealth = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - GamerHealth[playerId] = SelfHealthMax.GetInt(); - foreach (PlayerControl pc in Main.AllAlivePlayerControls) PlayerHealth[pc.PlayerId] = HealthMax.GetInt(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - private void SendRPC(byte playerId) - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetGamerHealth, SendOption.Reliable); - writer.Write(playerId); - writer.Write(GamerHealth.TryGetValue(playerId, out int value) ? value : PlayerHealth[playerId]); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte PlayerId = reader.ReadByte(); - int Health = reader.ReadInt32(); - - if (GamerHealth.ContainsKey(PlayerId)) - GamerHealth[PlayerId] = Health; - else - PlayerHealth[PlayerId] = Health; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null || target.Is(CustomRoles.Gamer) || !PlayerHealth.ContainsKey(target.PlayerId)) return false; - - killer.SetKillCooldown(); - - if (PlayerHealth[target.PlayerId] - Damage.GetInt() < 1) + if (target.Is(CustomRoles.Pestilence)) { - if (target.Is(CustomRoles.Pestilence)) - { - target.Kill(killer); + target.Kill(killer); - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); - return false; - } - - PlayerHealth.Remove(target.PlayerId); - killer.Kill(target); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); return false; } - PlayerHealth[target.PlayerId] -= Damage.GetInt(); - SendRPC(target.PlayerId); - RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + PlayerHealth.Remove(target.PlayerId); + killer.Kill(target); Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - - Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} attacked {target.GetNameWithRole().RemoveHtmlTags()}, did {Damage.GetInt()} damage", "Gamer"); return false; } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + PlayerHealth[target.PlayerId] -= Damage.GetInt(); + SendRPC(target.PlayerId); + RPC.PlaySoundRPC(killer.PlayerId, Sounds.KillSound); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} attacked {target.GetNameWithRole().RemoveHtmlTags()}, did {Damage.GetInt()} damage", "Gamer"); + return false; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || killer.Is(CustomRoles.Gamer)) return true; + + if (GamerHealth[target.PlayerId] - SelfDamage.GetInt() < 1) { - if (killer == null || target == null || killer.Is(CustomRoles.Gamer)) return true; - - if (GamerHealth[target.PlayerId] - SelfDamage.GetInt() < 1) - { - GamerHealth.Remove(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - return true; - } - - killer.SetKillCooldown(); - - GamerHealth[target.PlayerId] -= SelfDamage.GetInt(); - SendRPC(target.PlayerId); - RPC.PlaySoundRPC(target.PlayerId, Sounds.KillSound); + GamerHealth.Remove(target.PlayerId); Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); - - Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} attacked {target.GetNameWithRole().RemoveHtmlTags()}, did {SelfDamage.GetInt()} damage", "Gamer"); - return false; + return true; } - public static string TargetMark(PlayerControl seer, PlayerControl target) - { - if (!seer.IsAlive() || !PlayerIdList.Contains(seer.PlayerId)) return string.Empty; + killer.SetKillCooldown(); - if (seer.PlayerId == target.PlayerId) - { - bool GetValue = GamerHealth.TryGetValue(target.PlayerId, out int value); - return GetValue && value > 0 ? Utils.ColorString(GetColor(value, true), $"【{value}/{SelfHealthMax.GetInt()}】") : string.Empty; - } - else - { - bool GetValue = PlayerHealth.TryGetValue(target.PlayerId, out int value); - return GetValue && value > 0 ? Utils.ColorString(GetColor(value), $"【{value}/{HealthMax.GetInt()}】") : string.Empty; - } + GamerHealth[target.PlayerId] -= SelfDamage.GetInt(); + SendRPC(target.PlayerId); + RPC.PlaySoundRPC(target.PlayerId, Sounds.KillSound); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + + Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} attacked {target.GetNameWithRole().RemoveHtmlTags()}, did {SelfDamage.GetInt()} damage", "Gamer"); + return false; + } + + public static string TargetMark(PlayerControl seer, PlayerControl target) + { + if (!seer.IsAlive() || !PlayerIdList.Contains(seer.PlayerId)) return string.Empty; + + if (seer.PlayerId == target.PlayerId) + { + bool GetValue = GamerHealth.TryGetValue(target.PlayerId, out int value); + return GetValue && value > 0 ? Utils.ColorString(GetColor(value, true), $"【{value}/{SelfHealthMax.GetInt()}】") : string.Empty; } - - private static Color32 GetColor(float Health, bool self = false) + else { - var x = (int)(Health / (self ? SelfHealthMax.GetInt() : HealthMax.GetInt()) * 10 * 50); - var R = 255; - var G = 255; - var B = 0; - - if (x > 255) - R -= x - 255; - else - G = x; - - return new((byte)R, (byte)G, (byte)B, byte.MaxValue); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("GamerButtonText")); + bool GetValue = PlayerHealth.TryGetValue(target.PlayerId, out int value); + return GetValue && value > 0 ? Utils.ColorString(GetColor(value), $"【{value}/{HealthMax.GetInt()}】") : string.Empty; } } + + private static Color32 GetColor(float Health, bool self = false) + { + var x = (int)(Health / (self ? SelfHealthMax.GetInt() : HealthMax.GetInt()) * 10 * 50); + var R = 255; + var G = 255; + var B = 0; + + if (x > 255) + R -= x - 255; + else + G = x; + + return new((byte)R, (byte)G, (byte)B, byte.MaxValue); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("GamerButtonText")); + } } \ No newline at end of file diff --git a/Roles/Neutral/Gaslighter.cs b/Roles/Neutral/Gaslighter.cs index b8d2c26b1..4e08a6098 100644 --- a/Roles/Neutral/Gaslighter.cs +++ b/Roles/Neutral/Gaslighter.cs @@ -4,214 +4,213 @@ using AmongUs.GameOptions; using EHR.Crewmate; using EHR.Patches; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Gaslighter : RoleBase { - public class Gaslighter : RoleBase + public static bool On; + private static List Instances = []; + + private static OptionItem KillCooldown; + public static OptionItem WinCondition; + private static OptionItem CycleRepeats; + + private static readonly string[] WinConditionOptions = + [ + "GaslighterWinCondition.CrewLoses", + "GaslighterWinCondition.IfAlive", + "GaslighterWinCondition.LastStanding" + ]; + + private Round CurrentRound; + private HashSet CursedPlayers; + private bool CycleFinished; + + private byte GaslighterId; + private HashSet ShieldedPlayers; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - private static List Instances = []; + StartSetup(648350) + .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref WinCondition, 0, WinConditionOptions) + .AutoSetupOption(ref CycleRepeats, false); + } - private static OptionItem KillCooldown; - public static OptionItem WinCondition; - private static OptionItem CycleRepeats; + public override void Init() + { + On = false; + Instances = []; + } - private static readonly string[] WinConditionOptions = - [ - "GaslighterWinCondition.CrewLoses", - "GaslighterWinCondition.IfAlive", - "GaslighterWinCondition.LastStanding" - ]; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + GaslighterId = playerId; + CurrentRound = default; + CursedPlayers = []; + ShieldedPlayers = []; + CycleFinished = false; + } - private Round CurrentRound; - private HashSet CursedPlayers; - private bool CycleFinished; + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - private byte GaslighterId; - private HashSet ShieldedPlayers; - - public override bool IsEnable => On; - - public override void SetupCustomOption() + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CurrentRound switch { - StartSetup(648350) - .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref WinCondition, 0, WinConditionOptions) - .AutoSetupOption(ref CycleRepeats, false); - } + Round.Kill => KillCooldown.GetFloat(), + Round.Knight => Monarch.KnightCooldown.GetFloat(), + Round.Curse => Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown), + Round.Shield => Medic.CD.GetFloat(), + _ => Options.DefaultKillCooldown + }; + } - public override void Init() + public static void OnExile(byte[] exileIds) + { + try { - On = false; - Instances = []; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - GaslighterId = playerId; - CurrentRound = default; - CursedPlayers = []; - ShieldedPlayers = []; - CycleFinished = false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CurrentRound switch + foreach (Gaslighter instance in Instances) { - Round.Kill => KillCooldown.GetFloat(), - Round.Knight => Monarch.KnightCooldown.GetFloat(), - Round.Curse => Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown), - Round.Shield => Medic.CD.GetFloat(), - _ => Options.DefaultKillCooldown - }; - } + foreach (byte id in exileIds) + if (id == instance.GaslighterId) + instance.CursedPlayers.Clear(); + } - public static void OnExile(byte[] exileIds) - { - try + List curseDeathList = []; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { foreach (Gaslighter instance in Instances) { - foreach (byte id in exileIds) - if (id == instance.GaslighterId) - instance.CursedPlayers.Clear(); - } + if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; - List curseDeathList = []; + PlayerControl gaslighter = instance.GaslighterId.GetPlayer(); - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - foreach (Gaslighter instance in Instances) + if (instance.CursedPlayers.Contains(pc.PlayerId) && gaslighter != null && gaslighter.IsAlive()) { - if (Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) continue; - - PlayerControl gaslighter = instance.GaslighterId.GetPlayer(); - - if (instance.CursedPlayers.Contains(pc.PlayerId) && gaslighter != null && gaslighter.IsAlive()) - { - pc.SetRealKiller(gaslighter); - curseDeathList.Add(pc.PlayerId); - } + pc.SetRealKiller(gaslighter); + curseDeathList.Add(pc.PlayerId); } } - - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. curseDeathList]); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public override void AfterMeetingTasks() - { - ShieldedPlayers.Clear(); - CursedPlayers.Clear(); - - if (CurrentRound == Round.Shield) - { - CycleFinished = true; - CurrentRound = Round.Kill; - } - else if (!CycleFinished || CycleRepeats.GetBool()) CurrentRound++; - - float limit = CurrentRound switch - { - Round.Knight => Monarch.KnightMax.GetFloat(), - Round.Shield => Medic.SkillLimit, - _ => 0 - }; - - GaslighterId.SetAbilityUseLimit(limit); - - PlayerControl pc = GaslighterId.GetPlayer(); - pc?.ResetKillCooldown(); - pc?.Notify(Translator.GetString($"Gaslighter.{CurrentRound}")); - - LateTask.New(() => pc?.SetKillCooldown(), 1.5f, log: false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - switch (CurrentRound) - { - case Round.Kill: - return true; - case Round.Knight when killer.GetAbilityUseLimit() > 0 && !target.Is(CustomRoles.Knighted): - target.RpcSetCustomRole(CustomRoles.Knighted); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); - killer.RpcRemoveAbilityUse(); - killer.SetKillCooldown(); - return false; - case Round.Curse: - CursedPlayers.Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - killer.SetKillCooldown(); - return false; - case Round.Shield when killer.GetAbilityUseLimit() > 0: - ShieldedPlayers.Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); - killer.RpcRemoveAbilityUse(); - killer.SetKillCooldown(); - return false; } - return false; + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Spell, [.. curseDeathList]); } - - public static bool IsShielded(PlayerControl target) + catch (Exception e) { - return On && Instances.Exists(i => i.ShieldedPlayers.Contains(target.PlayerId)); - } - - private static bool IsCursed(PlayerControl target) - { - return On && Instances.Exists(i => i.CursedPlayers.Contains(target.PlayerId)); - } - - public override string GetProgressText(byte playerId, bool comms) - { - return CurrentRound is Round.Knight or Round.Shield - ? base.GetProgressText(playerId, comms) - : Utils.GetTaskCount(playerId, comms); - } - - public static string GetMark(PlayerControl seer, PlayerControl target, bool meeting = false) - { - bool seerIsGaslighter = seer.Is(CustomRoles.Gaslighter); - var sb = new StringBuilder(); - if (IsShielded(target) && (seerIsGaslighter || seer.PlayerId == target.PlayerId)) sb.Append($" ●"); - - if (IsCursed(target) && (meeting || seerIsGaslighter)) sb.Append(Utils.ColorString(Palette.ImpostorRed, "†")); - - return sb.ToString(); - } - - public bool AddAsAdditionalWinner() - { - return WinCondition.GetValue() switch - { - 0 => CustomWinnerHolder.WinnerTeam != CustomWinner.Crewmate, - 1 => GaslighterId.GetPlayer()?.IsAlive() == true, - _ => false - }; - } - - private enum Round - { - Kill, - Knight, - Curse, - Shield + Utils.ThrowException(e); } } + + public override void AfterMeetingTasks() + { + ShieldedPlayers.Clear(); + CursedPlayers.Clear(); + + if (CurrentRound == Round.Shield) + { + CycleFinished = true; + CurrentRound = Round.Kill; + } + else if (!CycleFinished || CycleRepeats.GetBool()) CurrentRound++; + + float limit = CurrentRound switch + { + Round.Knight => Monarch.KnightMax.GetFloat(), + Round.Shield => Medic.SkillLimit, + _ => 0 + }; + + GaslighterId.SetAbilityUseLimit(limit); + + PlayerControl pc = GaslighterId.GetPlayer(); + pc?.ResetKillCooldown(); + pc?.Notify(Translator.GetString($"Gaslighter.{CurrentRound}")); + + LateTask.New(() => pc?.SetKillCooldown(), 1.5f, log: false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + switch (CurrentRound) + { + case Round.Kill: + return true; + case Round.Knight when killer.GetAbilityUseLimit() > 0 && !target.Is(CustomRoles.Knighted): + target.RpcSetCustomRole(CustomRoles.Knighted); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); + killer.RpcRemoveAbilityUse(); + killer.SetKillCooldown(); + return false; + case Round.Curse: + CursedPlayers.Add(target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + killer.SetKillCooldown(); + return false; + case Round.Shield when killer.GetAbilityUseLimit() > 0: + ShieldedPlayers.Add(target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target); + killer.RpcRemoveAbilityUse(); + killer.SetKillCooldown(); + return false; + } + + return false; + } + + public static bool IsShielded(PlayerControl target) + { + return On && Instances.Exists(i => i.ShieldedPlayers.Contains(target.PlayerId)); + } + + private static bool IsCursed(PlayerControl target) + { + return On && Instances.Exists(i => i.CursedPlayers.Contains(target.PlayerId)); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return CurrentRound is Round.Knight or Round.Shield + ? base.GetProgressText(playerId, comms) + : Utils.GetTaskCount(playerId, comms); + } + + public static string GetMark(PlayerControl seer, PlayerControl target, bool meeting = false) + { + bool seerIsGaslighter = seer.Is(CustomRoles.Gaslighter); + var sb = new StringBuilder(); + if (IsShielded(target) && (seerIsGaslighter || seer.PlayerId == target.PlayerId)) sb.Append($" ●"); + + if (IsCursed(target) && (meeting || seerIsGaslighter)) sb.Append(Utils.ColorString(Palette.ImpostorRed, "†")); + + return sb.ToString(); + } + + public bool AddAsAdditionalWinner() + { + return WinCondition.GetValue() switch + { + 0 => CustomWinnerHolder.WinnerTeam != CustomWinner.Crewmate, + 1 => GaslighterId.GetPlayer()?.IsAlive() == true, + _ => false + }; + } + + private enum Round + { + Kill, + Knight, + Curse, + Shield + } } \ No newline at end of file diff --git a/Roles/Neutral/Glitch.cs b/Roles/Neutral/Glitch.cs index 658b5f5db..cff2baa15 100644 --- a/Roles/Neutral/Glitch.cs +++ b/Roles/Neutral/Glitch.cs @@ -6,326 +6,325 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Glitch : RoleBase { - public class Glitch : RoleBase + private const int Id = 18125; + public static List PlayerIdList = []; + + public static OptionItem KillCooldown; + public static OptionItem HackCooldown; + public static OptionItem HackDuration; + public static OptionItem MimicCooldown; + public static OptionItem MimicDuration; + public static OptionItem CanVent; + public static OptionItem CanVote; + private static OptionItem HasImpostorVision; + + private byte GlitchId; + + public int HackCDTimer; + private bool HasMimiced; + private bool IsShifted; + public int KCDTimer; + public long LastHack; + public long LastKill; + public long LastMimic; + private long LastUpdate; + public int MimicCDTimer; + public int MimicDurTimer; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 18125; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Glitch); - public static OptionItem KillCooldown; - public static OptionItem HackCooldown; - public static OptionItem HackDuration; - public static OptionItem MimicCooldown; - public static OptionItem MimicDuration; - public static OptionItem CanVent; - public static OptionItem CanVote; - private static OptionItem HasImpostorVision; + KillCooldown = new IntegerOptionItem(Id + 10, "KillCooldown", new(0, 180, 1), 25, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) + .SetValueFormat(OptionFormat.Seconds); - private byte GlitchId; + HackCooldown = new IntegerOptionItem(Id + 11, "HackCooldown", new(0, 180, 1), 20, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) + .SetValueFormat(OptionFormat.Seconds); - public int HackCDTimer; - private bool HasMimiced; - private bool IsShifted; - public int KCDTimer; - public long LastHack; - public long LastKill; - public long LastMimic; - private long LastUpdate; - public int MimicCDTimer; - public int MimicDurTimer; + HackDuration = new FloatOptionItem(Id + 14, "HackDuration", new(0f, 60f, 1f), 15f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + MimicCooldown = new IntegerOptionItem(Id + 15, "MimicCooldown", new(0, 180, 1), 30, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + MimicDuration = new FloatOptionItem(Id + 16, "MimicDuration", new(0f, 60f, 1f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) + .SetValueFormat(OptionFormat.Seconds); + + CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]); + CanVote = new BooleanOptionItem(Id + 17, "CanVote", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]); + } + + public override void Init() + { + PlayerIdList = []; + GlitchId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + GlitchId = playerId; + + HackCDTimer = 10; + KCDTimer = 10; + MimicCDTimer = 10; + MimicDurTimer = 0; + + IsShifted = false; + + long ts = Utils.TimeStamp; + + LastKill = ts; + LastHack = ts; + LastMimic = ts; + + LastUpdate = ts; + + HasMimiced = false; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.SabotageButton?.ToggleVisible(!Main.PlayerStates[id].IsDead); + hud.SabotageButton?.OverrideText(Translator.GetString("HackButtonText")); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.IsAlive(); + } + + private void SendRPCSyncTimers() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGlitchTimers, SendOption.Reliable); + writer.Write(GlitchId); + writer.Write(MimicCDTimer); + writer.Write(MimicDurTimer); + writer.Write(HackCDTimer); + writer.Write(KCDTimer); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPCSyncTimers(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not Glitch gc) return; + + gc.MimicCDTimer = reader.ReadInt32(); + gc.MimicDurTimer = reader.ReadInt32(); + gc.HackCDTimer = reader.ReadInt32(); + gc.KCDTimer = reader.ReadInt32(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = 1f; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + private void Mimic(PlayerControl pc) + { + if (pc == null || !pc.Is(CustomRoles.Glitch) || !pc.IsAlive() || MimicCDTimer > 0 || IsShifted) return; + + PlayerControl[] playerlist = Main.AllAlivePlayerControls.Where(a => a.PlayerId != pc.PlayerId).ToArray(); + + try { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Glitch); + pc.RpcShapeshift(playerlist.RandomElement(), false); - KillCooldown = new IntegerOptionItem(Id + 10, "KillCooldown", new(0, 180, 1), 25, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) - .SetValueFormat(OptionFormat.Seconds); + IsShifted = true; + HasMimiced = true; + LastMimic = Utils.TimeStamp; + MimicCDTimer = MimicCooldown.GetInt(); + MimicDurTimer = MimicDuration.GetInt(); + SendRPCSyncTimers(); + } + catch (Exception ex) + { + Logger.Error(ex.ToString(), "Glitch.Mimic.RpcShapeshift"); + } + } - HackCooldown = new IntegerOptionItem(Id + 11, "HackCooldown", new(0, 180, 1), 20, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) - .SetValueFormat(OptionFormat.Seconds); + public override void OnPet(PlayerControl pc) + { + Mimic(pc); + } - HackDuration = new FloatOptionItem(Id + 14, "HackDuration", new(0f, 60f, 1f), 15f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) - .SetValueFormat(OptionFormat.Seconds); + public override bool OnSabotage(PlayerControl pc) + { + Mimic(pc); + return false; + } - MimicCooldown = new IntegerOptionItem(Id + 15, "MimicCooldown", new(0, 180, 1), 30, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) - .SetValueFormat(OptionFormat.Seconds); + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || (KCDTimer > 0 && HackCDTimer > 0)) return false; - MimicDuration = new FloatOptionItem(Id + 16, "MimicDuration", new(0f, 60f, 1f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]) - .SetValueFormat(OptionFormat.Seconds); + if (killer.CheckDoubleTrigger(target, () => + { + if (HackCDTimer <= 0) + { + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + HackCDTimer = HackCooldown.GetInt(); + target.BlockRole(HackDuration.GetFloat()); + LastHack = Utils.TimeStamp; + SendRPCSyncTimers(); + } + })) + { + if (KCDTimer > 0) return false; - CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]); - CanVote = new BooleanOptionItem(Id + 17, "CanVote", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]); - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Glitch]); + LastKill = Utils.TimeStamp; + KCDTimer = KillCooldown.GetInt(); + SendRPCSyncTimers(); + return true; } - public override void Init() - { - PlayerIdList = []; - GlitchId = byte.MaxValue; - } + return false; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - GlitchId = playerId; + public override void OnFixedUpdate(PlayerControl player) + { + long now = Utils.TimeStamp; + if (LastUpdate == now) return; + LastUpdate = now; - HackCDTimer = 10; - KCDTimer = 10; - MimicCDTimer = 10; + if (HackCDTimer is > 180 or < 0) HackCDTimer = 0; + if (KCDTimer is > 180 or < 0) KCDTimer = 0; + if (MimicCDTimer is > 180 or < 0) MimicCDTimer = 0; + if (MimicDurTimer is > 180 or < 0) MimicDurTimer = 0; + + if (player == null) return; + + if (!player.IsAlive()) + { + HackCDTimer = 0; + KCDTimer = 0; + MimicCDTimer = 0; MimicDurTimer = 0; - - IsShifted = false; - - long ts = Utils.TimeStamp; - - LastKill = ts; - LastHack = ts; - LastMimic = ts; - - LastUpdate = ts; - - HasMimiced = false; + return; } - public override void SetButtonTexts(HudManager hud, byte id) + if (MimicDurTimer > 0) { - hud.SabotageButton?.ToggleVisible(!Main.PlayerStates[id].IsDead); - hud.SabotageButton?.OverrideText(Translator.GetString("HackButtonText")); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.IsAlive(); - } - - private void SendRPCSyncTimers() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncGlitchTimers, SendOption.Reliable); - writer.Write(GlitchId); - writer.Write(MimicCDTimer); - writer.Write(MimicDurTimer); - writer.Write(HackCDTimer); - writer.Write(KCDTimer); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPCSyncTimers(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not Glitch gc) return; - - gc.MimicCDTimer = reader.ReadInt32(); - gc.MimicDurTimer = reader.ReadInt32(); - gc.HackCDTimer = reader.ReadInt32(); - gc.KCDTimer = reader.ReadInt32(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = 1f; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - private void Mimic(PlayerControl pc) - { - if (pc == null || !pc.Is(CustomRoles.Glitch) || !pc.IsAlive() || MimicCDTimer > 0 || IsShifted) return; - - PlayerControl[] playerlist = Main.AllAlivePlayerControls.Where(a => a.PlayerId != pc.PlayerId).ToArray(); - try { - pc.RpcShapeshift(playerlist.RandomElement(), false); + MimicDurTimer = (int)(MimicDuration.GetInt() - (now - LastMimic)); + } + catch + { + MimicDurTimer = 0; + } - IsShifted = true; - HasMimiced = true; - LastMimic = Utils.TimeStamp; - MimicCDTimer = MimicCooldown.GetInt(); - MimicDurTimer = MimicDuration.GetInt(); - SendRPCSyncTimers(); + if (MimicDurTimer > 180) MimicDurTimer = 0; + } + + if ((MimicDurTimer <= 0 || !GameStates.IsInTask) && IsShifted) + { + try + { + player.RpcShapeshift(player, false); + IsShifted = false; } catch (Exception ex) { - Logger.Error(ex.ToString(), "Glitch.Mimic.RpcShapeshift"); + Logger.Error(ex.ToString(), "Glitch.Mimic.RpcRevertShapeshift"); } + + if (!GameStates.IsInTask) MimicDurTimer = 0; } - public override void OnPet(PlayerControl pc) + if (HackCDTimer <= 0 && KCDTimer <= 0 && MimicCDTimer <= 0 && MimicDurTimer <= 0) return; + + try { - Mimic(pc); + HackCDTimer = (int)(HackCooldown.GetInt() - (now - LastHack)); + } + catch + { + HackCDTimer = 0; } - public override bool OnSabotage(PlayerControl pc) + if (HackCDTimer is > 180 or < 0) HackCDTimer = 0; + + try { - Mimic(pc); - return false; + KCDTimer = (int)(KillCooldown.GetInt() - (now - LastKill)); + } + catch + { + KCDTimer = 0; } - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + if (KCDTimer is > 180 or < 0) KCDTimer = 0; + + try { - if (killer == null || target == null || (KCDTimer > 0 && HackCDTimer > 0)) return false; - - if (killer.CheckDoubleTrigger(target, () => - { - if (HackCDTimer <= 0) - { - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - HackCDTimer = HackCooldown.GetInt(); - target.BlockRole(HackDuration.GetFloat()); - LastHack = Utils.TimeStamp; - SendRPCSyncTimers(); - } - })) - { - if (KCDTimer > 0) return false; - - LastKill = Utils.TimeStamp; - KCDTimer = KillCooldown.GetInt(); - SendRPCSyncTimers(); - return true; - } - - return false; + MimicCDTimer = (int)(MimicCooldown.GetInt() + (HasMimiced ? MimicDuration.GetInt() : 0) - (now - LastMimic)); + } + catch + { + MimicCDTimer = 0; } - public override void OnFixedUpdate(PlayerControl player) - { - long now = Utils.TimeStamp; - if (LastUpdate == now) return; - LastUpdate = now; + if (MimicCDTimer is > 180 or < 0) MimicCDTimer = 0; - if (HackCDTimer is > 180 or < 0) HackCDTimer = 0; - if (KCDTimer is > 180 or < 0) KCDTimer = 0; - if (MimicCDTimer is > 180 or < 0) MimicCDTimer = 0; - if (MimicDurTimer is > 180 or < 0) MimicDurTimer = 0; - - if (player == null) return; - - if (!player.IsAlive()) - { - HackCDTimer = 0; - KCDTimer = 0; - MimicCDTimer = 0; - MimicDurTimer = 0; - return; - } - - if (MimicDurTimer > 0) - { - try - { - MimicDurTimer = (int)(MimicDuration.GetInt() - (now - LastMimic)); - } - catch - { - MimicDurTimer = 0; - } - - if (MimicDurTimer > 180) MimicDurTimer = 0; - } - - if ((MimicDurTimer <= 0 || !GameStates.IsInTask) && IsShifted) - { - try - { - player.RpcShapeshift(player, false); - IsShifted = false; - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "Glitch.Mimic.RpcRevertShapeshift"); - } - - if (!GameStates.IsInTask) MimicDurTimer = 0; - } - - if (HackCDTimer <= 0 && KCDTimer <= 0 && MimicCDTimer <= 0 && MimicDurTimer <= 0) return; - - try - { - HackCDTimer = (int)(HackCooldown.GetInt() - (now - LastHack)); - } - catch - { - HackCDTimer = 0; - } - - if (HackCDTimer is > 180 or < 0) HackCDTimer = 0; - - try - { - KCDTimer = (int)(KillCooldown.GetInt() - (now - LastKill)); - } - catch - { - KCDTimer = 0; - } - - if (KCDTimer is > 180 or < 0) KCDTimer = 0; - - try - { - MimicCDTimer = (int)(MimicCooldown.GetInt() + (HasMimiced ? MimicDuration.GetInt() : 0) - (now - LastMimic)); - } - catch - { - MimicCDTimer = 0; - } - - if (MimicCDTimer is > 180 or < 0) MimicCDTimer = 0; - - if (player.IsNonHostModClient()) - SendRPCSyncTimers(); - - if (!player.IsModClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer == null || seer.PlayerId != GlitchId || seer.PlayerId != target.PlayerId || !seer.IsAlive() || (seer.IsModClient() && !hud) || meeting) return string.Empty; - - var sb = new StringBuilder(); - - if (!hud) sb.Append(""); - - if (MimicDurTimer > 0) sb.Append($"{string.Format(Translator.GetString("MimicDur"), MimicDurTimer)}\n"); - if (MimicCDTimer > 0 && MimicDurTimer <= 0) sb.Append($"{string.Format(Translator.GetString("MimicCD"), MimicCDTimer)}\n"); - if (HackCDTimer > 0) sb.Append($"{string.Format(Translator.GetString("HackCD"), HackCDTimer)}\n"); - if (KCDTimer > 0) sb.Append($"{string.Format(Translator.GetString("KCD"), KCDTimer)}\n"); - - if (!hud) sb.Append(""); - - return sb.ToString(); - } - - public override void AfterMeetingTasks() - { - long timestamp = Utils.TimeStamp; - LastKill = timestamp; - LastHack = timestamp; - LastMimic = timestamp; - KCDTimer = 10; - HackCDTimer = 10; - MimicCDTimer = 10; + if (player.IsNonHostModClient()) SendRPCSyncTimers(); - } - public override void OnReportDeadBody() - { - HasMimiced = false; - } + if (!player.IsModClient()) Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer == null || seer.PlayerId != GlitchId || seer.PlayerId != target.PlayerId || !seer.IsAlive() || (seer.IsModClient() && !hud) || meeting) return string.Empty; + + var sb = new StringBuilder(); + + if (!hud) sb.Append(""); + + if (MimicDurTimer > 0) sb.Append($"{string.Format(Translator.GetString("MimicDur"), MimicDurTimer)}\n"); + if (MimicCDTimer > 0 && MimicDurTimer <= 0) sb.Append($"{string.Format(Translator.GetString("MimicCD"), MimicCDTimer)}\n"); + if (HackCDTimer > 0) sb.Append($"{string.Format(Translator.GetString("HackCD"), HackCDTimer)}\n"); + if (KCDTimer > 0) sb.Append($"{string.Format(Translator.GetString("KCD"), KCDTimer)}\n"); + + if (!hud) sb.Append(""); + + return sb.ToString(); + } + + public override void AfterMeetingTasks() + { + long timestamp = Utils.TimeStamp; + LastKill = timestamp; + LastHack = timestamp; + LastMimic = timestamp; + KCDTimer = 10; + HackCDTimer = 10; + MimicCDTimer = 10; + SendRPCSyncTimers(); + } + + public override void OnReportDeadBody() + { + HasMimiced = false; } } \ No newline at end of file diff --git a/Roles/Neutral/God.cs b/Roles/Neutral/God.cs index 5d630c87e..eef6425cc 100644 --- a/Roles/Neutral/God.cs +++ b/Roles/Neutral/God.cs @@ -1,38 +1,37 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class God : RoleBase { - internal class God : RoleBase + public static OptionItem NotifyGodAlive; + public static OptionItem GodCanGuess; + public static OptionItem KnowInfo; + + private static readonly string[] KnowInfoMode = + [ + "None", + "Alignments", + "Roles" + ]; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public static OptionItem NotifyGodAlive; - public static OptionItem GodCanGuess; - public static OptionItem KnowInfo; + SetupRoleOptions(18200, TabGroup.NeutralRoles, CustomRoles.God); - private static readonly string[] KnowInfoMode = - [ - "None", - "Alignments", - "Roles" - ]; + NotifyGodAlive = new BooleanOptionItem(18210, "NotifyGodAlive", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.God]); - public override bool IsEnable => false; + GodCanGuess = new BooleanOptionItem(18211, "CanGuess", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.God]); - public override void SetupCustomOption() - { - SetupRoleOptions(18200, TabGroup.NeutralRoles, CustomRoles.God); - - NotifyGodAlive = new BooleanOptionItem(18210, "NotifyGodAlive", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.God]); - - GodCanGuess = new BooleanOptionItem(18211, "CanGuess", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.God]); - - KnowInfo = new StringOptionItem(18212, "God.KnowInfo", KnowInfoMode, 2, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.God]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + KnowInfo = new StringOptionItem(18212, "God.KnowInfo", KnowInfoMode, 2, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.God]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/HeadHunter.cs b/Roles/Neutral/HeadHunter.cs index 55d867834..08ea113b3 100644 --- a/Roles/Neutral/HeadHunter.cs +++ b/Roles/Neutral/HeadHunter.cs @@ -6,180 +6,179 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class HeadHunter : RoleBase { - public class HeadHunter : RoleBase + private const int Id = 12870; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + public static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem SuccessKillCooldown; + private static OptionItem FailureKillCooldown; + private static OptionItem NumOfTargets; + private static OptionItem MinKCD; + private static OptionItem MaxKCD; + private byte HeadHunterId; + public float KCD = DefaultKillCooldown; + + public List Targets = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 12870; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HeadHunter); - private static OptionItem KillCooldown; - public static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem SuccessKillCooldown; - private static OptionItem FailureKillCooldown; - private static OptionItem NumOfTargets; - private static OptionItem MinKCD; - private static OptionItem MaxKCD; - private byte HeadHunterId; - public float KCD = DefaultKillCooldown; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 27.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) + .SetValueFormat(OptionFormat.Seconds); - public List Targets = []; + SuccessKillCooldown = new FloatOptionItem(Id + 11, "HHSuccessKCDDecrease", new(0f, 180f, 0.5f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + FailureKillCooldown = new FloatOptionItem(Id + 12, "HHFailureKCDIncrease", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + CanVent = new BooleanOptionItem(Id + 13, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]); + HasImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]); + + NumOfTargets = new IntegerOptionItem(Id + 15, "HHNumOfTargets", new(0, 10, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) + .SetValueFormat(OptionFormat.Times); + + MaxKCD = new FloatOptionItem(Id + 16, "HHMaxKCD", new(0f, 180f, 0.5f), 40f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) + .SetValueFormat(OptionFormat.Seconds); + + MinKCD = new FloatOptionItem(Id + 17, "HHMinKCD", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + Targets = []; + HeadHunterId = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + HeadHunterId = playerId; + Targets = []; + LateTask.New(ResetTargets, 8f, log: false); + KCD = KillCooldown.GetFloat(); + } + + private void SendRPC() + { + MessageWriter writer = Utils.CreateRPC(CustomRPC.SyncHeadHunter); + writer.Write(HeadHunterId); + writer.Write(Targets.Count); + foreach (byte target in Targets.ToArray()) writer.Write(target); + + Utils.EndRPC(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + if (Main.PlayerStates[playerId].Role is not HeadHunter hh) return; + + hh.Targets.Clear(); + int count = reader.ReadInt32(); + for (var i = 0; i < count; i++) hh.Targets.Add(reader.ReadByte()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KCD; + } + + public override void OnReportDeadBody() + { + ResetTargets(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + float tempkcd = KCD; + + if (Targets.Contains(target.PlayerId)) + Math.Clamp(KCD -= SuccessKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); + else + Math.Clamp(KCD += FailureKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); + + if (Math.Abs(KCD - tempkcd) > 0.1f) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HeadHunter); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 27.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) - .SetValueFormat(OptionFormat.Seconds); - - SuccessKillCooldown = new FloatOptionItem(Id + 11, "HHSuccessKCDDecrease", new(0f, 180f, 0.5f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) - .SetValueFormat(OptionFormat.Seconds); - - FailureKillCooldown = new FloatOptionItem(Id + 12, "HHFailureKCDIncrease", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 13, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]); - HasImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]); - - NumOfTargets = new IntegerOptionItem(Id + 15, "HHNumOfTargets", new(0, 10, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) - .SetValueFormat(OptionFormat.Times); - - MaxKCD = new FloatOptionItem(Id + 16, "HHMaxKCD", new(0f, 180f, 0.5f), 40f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) - .SetValueFormat(OptionFormat.Seconds); - - MinKCD = new FloatOptionItem(Id + 17, "HHMinKCD", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.HeadHunter]) - .SetValueFormat(OptionFormat.Seconds); + killer.ResetKillCooldown(); + killer.SyncSettings(); } - public override void Init() + return true; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + killer.SetKillCooldown(KCD); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud) return string.Empty; + + byte targetId = seer.PlayerId; + var output = string.Empty; + if (Main.PlayerStates[targetId].Role is not HeadHunter hh) return output; + + for (var i = 0; i < hh.Targets.Count; i++) { - PlayerIdList = []; - Targets = []; - HeadHunterId = byte.MaxValue; + byte playerId = hh.Targets[i]; + if (i != 0) output += ", "; + + output += playerId.ColoredPlayerName(); } - public override void Add(byte playerId) + return targetId != 0xff ? $"Targets: {output}" : string.Empty; + } + + private void ResetTargets() + { + if (!AmongUsClient.Instance.AmHost) return; + + Targets.Clear(); + + for (var i = 0; i < NumOfTargets.GetInt(); i++) { - PlayerIdList.Add(playerId); - HeadHunterId = playerId; - Targets = []; - LateTask.New(ResetTargets, 8f, log: false); - KCD = KillCooldown.GetFloat(); - } - - private void SendRPC() - { - MessageWriter writer = Utils.CreateRPC(CustomRPC.SyncHeadHunter); - writer.Write(HeadHunterId); - writer.Write(Targets.Count); - foreach (byte target in Targets.ToArray()) writer.Write(target); - - Utils.EndRPC(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - if (Main.PlayerStates[playerId].Role is not HeadHunter hh) return; - - hh.Targets.Clear(); - int count = reader.ReadInt32(); - for (var i = 0; i < count; i++) hh.Targets.Add(reader.ReadByte()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KCD; - } - - public override void OnReportDeadBody() - { - ResetTargets(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - float tempkcd = KCD; - - if (Targets.Contains(target.PlayerId)) - Math.Clamp(KCD -= SuccessKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); - else - Math.Clamp(KCD += FailureKillCooldown.GetFloat(), MinKCD.GetFloat(), MaxKCD.GetFloat()); - - if (Math.Abs(KCD - tempkcd) > 0.1f) + try { - killer.ResetKillCooldown(); - killer.SyncSettings(); + var cTargets = new List(Main.AllAlivePlayerControls.Where(pc => !Targets.Contains(pc.PlayerId) && pc.GetCustomRole() != CustomRoles.HeadHunter)); + if (cTargets.Count == 0) break; + + PlayerControl target = cTargets.RandomElement(); + Targets.Add(target.PlayerId); } - - return true; - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - killer.SetKillCooldown(KCD); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud) return string.Empty; - - byte targetId = seer.PlayerId; - var output = string.Empty; - if (Main.PlayerStates[targetId].Role is not HeadHunter hh) return output; - - for (var i = 0; i < hh.Targets.Count; i++) + catch (Exception ex) { - byte playerId = hh.Targets[i]; - if (i != 0) output += ", "; - - output += playerId.ColoredPlayerName(); + Logger.Warn($"Not enough targets for Head Hunter could be assigned. This may be due to a low player count or the following error:\n\n{ex}", "HeadHunterAssignTargets"); + SendRPC(); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(HeadHunterId)); + break; } - - return targetId != 0xff ? $"Targets: {output}" : string.Empty; } - private void ResetTargets() - { - if (!AmongUsClient.Instance.AmHost) return; - - Targets.Clear(); - - for (var i = 0; i < NumOfTargets.GetInt(); i++) - { - try - { - var cTargets = new List(Main.AllAlivePlayerControls.Where(pc => !Targets.Contains(pc.PlayerId) && pc.GetCustomRole() != CustomRoles.HeadHunter)); - if (cTargets.Count == 0) break; - - PlayerControl target = cTargets.RandomElement(); - Targets.Add(target.PlayerId); - } - catch (Exception ex) - { - Logger.Warn($"Not enough targets for Head Hunter could be assigned. This may be due to a low player count or the following error:\n\n{ex}", "HeadHunterAssignTargets"); - SendRPC(); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(HeadHunterId)); - break; - } - } - - SendRPC(); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(HeadHunterId)); - } + SendRPC(); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(HeadHunterId)); } } \ No newline at end of file diff --git a/Roles/Neutral/HexMaster.cs b/Roles/Neutral/HexMaster.cs index 414476a6c..10e49f06a 100644 --- a/Roles/Neutral/HexMaster.cs +++ b/Roles/Neutral/HexMaster.cs @@ -1,25 +1,24 @@ using EHR.Impostor; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class HexMaster : RoleBase { - public class HexMaster : RoleBase + private const int Id = 11900; + public static OptionItem ModeSwitchAction; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 11900; - public static OptionItem ModeSwitchAction; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HexMaster); - public override bool IsEnable => false; - - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.HexMaster); - - ModeSwitchAction = new StringOptionItem(Id + 10, "WitchModeSwitchAction", Witch.SwitchTriggerText, 2, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + ModeSwitchAction = new StringOptionItem(Id + 10, "WitchModeSwitchAction", Witch.SwitchTriggerText, 2, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.HexMaster]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Hookshot.cs b/Roles/Neutral/Hookshot.cs index b9bea8007..de08a73b6 100644 --- a/Roles/Neutral/Hookshot.cs +++ b/Roles/Neutral/Hookshot.cs @@ -4,173 +4,172 @@ using Hazel; using static EHR.Options; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Hookshot : RoleBase { - internal class Hookshot : RoleBase + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + private static OptionItem CanVent; + + private byte HookshotId = byte.MaxValue; + public byte MarkedPlayerId = byte.MaxValue; + + private bool ToTargetTP = true; + private static int Id => 643230; + + private PlayerControl HookshotPC => GetPlayerById(HookshotId); + + public override bool IsEnable => HookshotId != byte.MaxValue; + + public override void SetupCustomOption() { - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - private static OptionItem CanVent; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Hookshot); - private byte HookshotId = byte.MaxValue; - public byte MarkedPlayerId = byte.MaxValue; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hookshot]) + .SetValueFormat(OptionFormat.Seconds); - private bool ToTargetTP = true; - private static int Id => 643230; + HasImpostorVision = new BooleanOptionItem(Id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hookshot]); - private PlayerControl HookshotPC => GetPlayerById(HookshotId); + CanVent = new BooleanOptionItem(Id + 4, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Hookshot]); + } - public override bool IsEnable => HookshotId != byte.MaxValue; + public override void Init() + { + HookshotId = byte.MaxValue; + MarkedPlayerId = byte.MaxValue; + } - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Hookshot); + public override void Add(byte playerId) + { + HookshotId = playerId; + ToTargetTP = true; + MarkedPlayerId = byte.MaxValue; + } - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hookshot]) - .SetValueFormat(OptionFormat.Seconds); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - HasImpostorVision = new BooleanOptionItem(Id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hookshot]); + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - CanVent = new BooleanOptionItem(Id + 4, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Hookshot]); - } + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); + } - public override void Init() - { - HookshotId = byte.MaxValue; - MarkedPlayerId = byte.MaxValue; - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - public override void Add(byte playerId) - { - HookshotId = playerId; - ToTargetTP = true; - MarkedPlayerId = byte.MaxValue; - } + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + private void SendRPC() + { + if (!IsEnable || !DoRPC) return; - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncHookshot, SendOption.Reliable); + writer.Write(HookshotId); + writer.Write(ToTargetTP); + writer.Write(MarkedPlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); - } + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + if (Main.PlayerStates[playerId].Role is not Hookshot hs) return; - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + hs.ToTargetTP = reader.ReadBoolean(); + hs.MarkedPlayerId = reader.ReadByte(); + } - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } + public override void OnPet(PlayerControl pc) + { + ExecuteAction(); + } - private void SendRPC() - { - if (!IsEnable || !DoRPC) return; + public override bool OnSabotage(PlayerControl pc) + { + ExecuteAction(); + return false; + } - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncHookshot, SendOption.Reliable); - writer.Write(HookshotId); - writer.Write(ToTargetTP); - writer.Write(MarkedPlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + public override bool OnVanish(PlayerControl pc) + { + ExecuteAction(); + return false; + } - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - if (Main.PlayerStates[playerId].Role is not Hookshot hs) return; + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - hs.ToTargetTP = reader.ReadBoolean(); - hs.MarkedPlayerId = reader.ReadByte(); - } + ExecuteAction(); + return false; + } - public override void OnPet(PlayerControl pc) - { - ExecuteAction(); - } + private void ExecuteAction() + { + if (MarkedPlayerId == byte.MaxValue) return; - public override bool OnSabotage(PlayerControl pc) - { - ExecuteAction(); - return false; - } + PlayerControl markedPlayer = GetPlayerById(MarkedPlayerId); - public override bool OnVanish(PlayerControl pc) - { - ExecuteAction(); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - ExecuteAction(); - return false; - } - - private void ExecuteAction() - { - if (MarkedPlayerId == byte.MaxValue) return; - - PlayerControl markedPlayer = GetPlayerById(MarkedPlayerId); - - if (markedPlayer == null) - { - MarkedPlayerId = byte.MaxValue; - SendRPC(); - return; - } - - bool isTPsuccess = ToTargetTP ? HookshotPC.TP(markedPlayer) : markedPlayer.TP(HookshotPC); - - if (isTPsuccess) - { - MarkedPlayerId = byte.MaxValue; - SendRPC(); - } - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - ToTargetTP = !ToTargetTP; - SendRPC(); - NotifyRoles(SpecifySeer: HookshotPC, SpecifyTarget: HookshotPC); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (target == null) return false; - - return HookshotPC.CheckDoubleTrigger(target, () => - { - MarkedPlayerId = target.PlayerId; - SendRPC(); - HookshotPC.SetKillCooldown(5f); - - if (killer.IsLocalPlayer()) - Achievements.Type.WellMeetAgainSomeSunnyDay.Complete(); - }); - } - - public override void OnReportDeadBody() + if (markedPlayer == null) { MarkedPlayerId = byte.MaxValue; SendRPC(); + return; } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + bool isTPsuccess = ToTargetTP ? HookshotPC.TP(markedPlayer) : markedPlayer.TP(HookshotPC); + + if (isTPsuccess) { - return seer.PlayerId == target.PlayerId && seer.PlayerId == HookshotId ? $"<#00ffa5>{Translator.GetString("Mode")}: <#ffffff>{(ToTargetTP ? Translator.GetString("HookshotTpToTarget") : Translator.GetString("HookshotPullTarget"))}" : string.Empty; + MarkedPlayerId = byte.MaxValue; + SendRPC(); } } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + ToTargetTP = !ToTargetTP; + SendRPC(); + NotifyRoles(SpecifySeer: HookshotPC, SpecifyTarget: HookshotPC); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (target == null) return false; + + return HookshotPC.CheckDoubleTrigger(target, () => + { + MarkedPlayerId = target.PlayerId; + SendRPC(); + HookshotPC.SetKillCooldown(5f); + + if (killer.IsLocalPlayer()) + Achievements.Type.WellMeetAgainSomeSunnyDay.Complete(); + }); + } + + public override void OnReportDeadBody() + { + MarkedPlayerId = byte.MaxValue; + SendRPC(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return seer.PlayerId == target.PlayerId && seer.PlayerId == HookshotId ? $"<#00ffa5>{Translator.GetString("Mode")}: <#ffffff>{(ToTargetTP ? Translator.GetString("HookshotTpToTarget") : Translator.GetString("HookshotPullTarget"))}" : string.Empty; + } } \ No newline at end of file diff --git a/Roles/Neutral/Imitator.cs b/Roles/Neutral/Imitator.cs index 24f7e82bb..8d5a9e7fb 100644 --- a/Roles/Neutral/Imitator.cs +++ b/Roles/Neutral/Imitator.cs @@ -1,37 +1,36 @@ -namespace EHR.Neutral +namespace EHR.Neutral; + +// Credit: https://github.com/Yumenopai/TownOfHost_Y +public class Imitator : RoleBase { - // Credit: https://github.com/Yumenopai/TownOfHost_Y - public class Imitator : RoleBase + private const int Id = 11950; + + public static OptionItem OddKillCooldown; + public static OptionItem EvenKillCooldown; + public static OptionItem AfterMeetingKillCooldown; + public static OptionItem CanVent; + public static OptionItem HasImpostorVision; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 11950; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Imitator); - public static OptionItem OddKillCooldown; - public static OptionItem EvenKillCooldown; - public static OptionItem AfterMeetingKillCooldown; - public static OptionItem CanVent; - public static OptionItem HasImpostorVision; + OddKillCooldown = new FloatOptionItem(Id + 10, "OddKillCooldown", new(0f, 60f, 0.5f), 27.5f, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + EvenKillCooldown = new FloatOptionItem(Id + 11, "EvenKillCooldown", new(0f, 30f, 0.5f), 15f, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Imitator); + AfterMeetingKillCooldown = new FloatOptionItem(Id + 12, "AfterMeetingKillCooldown", new(0f, 30f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]) + .SetValueFormat(OptionFormat.Seconds); - OddKillCooldown = new FloatOptionItem(Id + 10, "OddKillCooldown", new(0f, 60f, 0.5f), 27.5f, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]) - .SetValueFormat(OptionFormat.Seconds); - - EvenKillCooldown = new FloatOptionItem(Id + 11, "EvenKillCooldown", new(0f, 30f, 0.5f), 15f, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]) - .SetValueFormat(OptionFormat.Seconds); - - AfterMeetingKillCooldown = new FloatOptionItem(Id + 12, "AfterMeetingKillCooldown", new(0f, 30f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 13, "CanVent", true, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]); - HasImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + CanVent = new BooleanOptionItem(Id + 13, "CanVent", true, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]); + HasImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(Options.CustomRoleSpawnChances[CustomRoles.Imitator]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Impartial.cs b/Roles/Neutral/Impartial.cs index 909df304c..91cac81ea 100644 --- a/Roles/Neutral/Impartial.cs +++ b/Roles/Neutral/Impartial.cs @@ -3,157 +3,156 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Impartial : RoleBase { - internal class Impartial : RoleBase + public static bool On; + + private static readonly Dictionary ReplacementDictionary = new() { { "Minimum", "Minimum" }, { "Maximum", "Maximum" } }; + + private static OptionItem ImpMinOpt; + private static OptionItem ImpMaxOpt; + private static OptionItem NeutralMinOpt; + private static OptionItem NeutralMaxOpt; + private static OptionItem CrewMinOpt; + private static OptionItem CrewMaxOpt; + private static OptionItem CanVent; + private static OptionItem CanVentAfterWinning; + private static OptionItem HasImpVision; + private static OptionItem HasImpVisionAfterWinning; + private static OptionItem CanWinWhenKillingMore; + private (int Killed, int Limit) CrewKillCount = (0, 0); + + private (int Killed, int Limit) ImpKillCount = (0, 0); + private (int Killed, int Limit) NeutralKillCount = (0, 0); + public override bool IsEnable => On; + + public bool IsWon { - public static bool On; - - private static readonly Dictionary ReplacementDictionary = new() { { "Minimum", "Minimum" }, { "Maximum", "Maximum" } }; - - private static OptionItem ImpMinOpt; - private static OptionItem ImpMaxOpt; - private static OptionItem NeutralMinOpt; - private static OptionItem NeutralMaxOpt; - private static OptionItem CrewMinOpt; - private static OptionItem CrewMaxOpt; - private static OptionItem CanVent; - private static OptionItem CanVentAfterWinning; - private static OptionItem HasImpVision; - private static OptionItem HasImpVisionAfterWinning; - private static OptionItem CanWinWhenKillingMore; - private (int Killed, int Limit) CrewKillCount = (0, 0); - - private (int Killed, int Limit) ImpKillCount = (0, 0); - private (int Killed, int Limit) NeutralKillCount = (0, 0); - public override bool IsEnable => On; - - public bool IsWon + get { - get - { - if (CanWinWhenKillingMore.GetBool()) return ImpKillCount.Killed >= ImpKillCount.Limit && NeutralKillCount.Killed >= NeutralKillCount.Limit && CrewKillCount.Killed >= CrewKillCount.Limit; + if (CanWinWhenKillingMore.GetBool()) return ImpKillCount.Killed >= ImpKillCount.Limit && NeutralKillCount.Killed >= NeutralKillCount.Limit && CrewKillCount.Killed >= CrewKillCount.Limit; - return ImpKillCount.Killed == ImpKillCount.Limit && NeutralKillCount.Killed == NeutralKillCount.Limit && CrewKillCount.Killed == CrewKillCount.Limit; - } - } - - public override void SetupCustomOption() - { - const int id = 10490; - Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Impartial); - ImpMinOpt = CreateSetting(id + 2, true, "Imp"); - ImpMaxOpt = CreateSetting(id + 3, false, "Imp"); - NeutralMinOpt = CreateSetting(id + 4, true, "Neutral"); - NeutralMaxOpt = CreateSetting(id + 5, false, "Neutral"); - CrewMinOpt = CreateSetting(id + 6, true, "Crew"); - CrewMaxOpt = CreateSetting(id + 7, false, "Crew"); - - CanVent = new BooleanOptionItem(id + 8, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); - - CanVentAfterWinning = new BooleanOptionItem(id + 9, "EvenAfterWinning", false, TabGroup.NeutralRoles) - .SetParent(CanVent); - - HasImpVision = new BooleanOptionItem(id + 10, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); - - HasImpVisionAfterWinning = new BooleanOptionItem(id + 11, "EvenAfterWinning", false, TabGroup.NeutralRoles) - .SetParent(HasImpVision); - - CanWinWhenKillingMore = new BooleanOptionItem(id + 12, "ImpartialCanWinWhenKillingMore", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); - } - - private static OptionItem CreateSetting(int id, bool min, string roleType) - { - OptionItem opt = new IntegerOptionItem(id, $"Impartial{roleType}{(min ? "min" : "max")}", new(0, 14, 1), 1, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); - - opt.ReplacementDictionary = ReplacementDictionary; - return opt; - } - - public override void Add(byte playerId) - { - On = true; - var r = IRandom.Instance; - ImpKillCount = (0, r.Next(ImpMinOpt.GetInt(), ImpMaxOpt.GetInt() + 1)); - NeutralKillCount = (0, r.Next(NeutralMinOpt.GetInt(), NeutralMaxOpt.GetInt() + 1)); - CrewKillCount = (0, r.Next(CrewMinOpt.GetInt(), CrewMaxOpt.GetInt() + 1)); - - LateTask.New(() => { Utils.SendRPC(CustomRPC.SyncRoleData, playerId, 1, ImpKillCount.Killed, ImpKillCount.Limit, NeutralKillCount.Killed, NeutralKillCount.Limit, CrewKillCount.Killed, CrewKillCount.Limit); }, 5f, log: false); - } - - public override void Init() - { - On = false; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !IsWon; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool() && (!IsWon || CanVentAfterWinning.GetBool()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpVision.GetBool() && (!IsWon || HasImpVisionAfterWinning.GetBool())); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - ImpKillCount = (reader.ReadInt32(), reader.ReadInt32()); - NeutralKillCount = (reader.ReadInt32(), reader.ReadInt32()); - CrewKillCount = (reader.ReadInt32(), reader.ReadInt32()); - break; - case 2: - ImpKillCount.Killed++; - break; - case 3: - NeutralKillCount.Killed++; - break; - case 4: - CrewKillCount.Killed++; - break; - } - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - switch (target.GetTeam()) - { - case Team.Impostor: - ImpKillCount.Killed++; - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 2); - break; - case Team.Neutral: - NeutralKillCount.Killed++; - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 3); - break; - case Team.Crewmate: - CrewKillCount.Killed++; - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 4); - break; - } - } - - public override string GetProgressText(byte playerId, bool comms) - { - if (IsWon) return " \u2713"; - - var sb = new StringBuilder(); - sb.Append($" <{Main.ImpostorColor}>{ImpKillCount.Killed}/{ImpKillCount.Limit}"); - sb.Append($" <{Main.NeutralColor}>{NeutralKillCount.Killed}/{NeutralKillCount.Limit}"); - sb.Append($" <{Main.CrewmateColor}>{CrewKillCount.Killed}/{CrewKillCount.Limit}"); - return sb.ToString(); + return ImpKillCount.Killed == ImpKillCount.Limit && NeutralKillCount.Killed == NeutralKillCount.Limit && CrewKillCount.Killed == CrewKillCount.Limit; } } + + public override void SetupCustomOption() + { + const int id = 10490; + Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Impartial); + ImpMinOpt = CreateSetting(id + 2, true, "Imp"); + ImpMaxOpt = CreateSetting(id + 3, false, "Imp"); + NeutralMinOpt = CreateSetting(id + 4, true, "Neutral"); + NeutralMaxOpt = CreateSetting(id + 5, false, "Neutral"); + CrewMinOpt = CreateSetting(id + 6, true, "Crew"); + CrewMaxOpt = CreateSetting(id + 7, false, "Crew"); + + CanVent = new BooleanOptionItem(id + 8, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); + + CanVentAfterWinning = new BooleanOptionItem(id + 9, "EvenAfterWinning", false, TabGroup.NeutralRoles) + .SetParent(CanVent); + + HasImpVision = new BooleanOptionItem(id + 10, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); + + HasImpVisionAfterWinning = new BooleanOptionItem(id + 11, "EvenAfterWinning", false, TabGroup.NeutralRoles) + .SetParent(HasImpVision); + + CanWinWhenKillingMore = new BooleanOptionItem(id + 12, "ImpartialCanWinWhenKillingMore", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); + } + + private static OptionItem CreateSetting(int id, bool min, string roleType) + { + OptionItem opt = new IntegerOptionItem(id, $"Impartial{roleType}{(min ? "min" : "max")}", new(0, 14, 1), 1, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Impartial]); + + opt.ReplacementDictionary = ReplacementDictionary; + return opt; + } + + public override void Add(byte playerId) + { + On = true; + var r = IRandom.Instance; + ImpKillCount = (0, r.Next(ImpMinOpt.GetInt(), ImpMaxOpt.GetInt() + 1)); + NeutralKillCount = (0, r.Next(NeutralMinOpt.GetInt(), NeutralMaxOpt.GetInt() + 1)); + CrewKillCount = (0, r.Next(CrewMinOpt.GetInt(), CrewMaxOpt.GetInt() + 1)); + + LateTask.New(() => { Utils.SendRPC(CustomRPC.SyncRoleData, playerId, 1, ImpKillCount.Killed, ImpKillCount.Limit, NeutralKillCount.Killed, NeutralKillCount.Limit, CrewKillCount.Killed, CrewKillCount.Limit); }, 5f, log: false); + } + + public override void Init() + { + On = false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !IsWon; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool() && (!IsWon || CanVentAfterWinning.GetBool()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpVision.GetBool() && (!IsWon || HasImpVisionAfterWinning.GetBool())); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) + { + case 1: + ImpKillCount = (reader.ReadInt32(), reader.ReadInt32()); + NeutralKillCount = (reader.ReadInt32(), reader.ReadInt32()); + CrewKillCount = (reader.ReadInt32(), reader.ReadInt32()); + break; + case 2: + ImpKillCount.Killed++; + break; + case 3: + NeutralKillCount.Killed++; + break; + case 4: + CrewKillCount.Killed++; + break; + } + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + switch (target.GetTeam()) + { + case Team.Impostor: + ImpKillCount.Killed++; + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 2); + break; + case Team.Neutral: + NeutralKillCount.Killed++; + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 3); + break; + case Team.Crewmate: + CrewKillCount.Killed++; + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 4); + break; + } + } + + public override string GetProgressText(byte playerId, bool comms) + { + if (IsWon) return " \u2713"; + + var sb = new StringBuilder(); + sb.Append($" <{Main.ImpostorColor}>{ImpKillCount.Killed}/{ImpKillCount.Limit}"); + sb.Append($" <{Main.NeutralColor}>{NeutralKillCount.Killed}/{NeutralKillCount.Limit}"); + sb.Append($" <{Main.CrewmateColor}>{CrewKillCount.Killed}/{CrewKillCount.Limit}"); + return sb.ToString(); + } } \ No newline at end of file diff --git a/Roles/Neutral/Innocent.cs b/Roles/Neutral/Innocent.cs index 2ca786140..e2b025725 100644 --- a/Roles/Neutral/Innocent.cs +++ b/Roles/Neutral/Innocent.cs @@ -1,62 +1,61 @@ using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Innocent : RoleBase { - internal class Innocent : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; - public override bool IsEnable => On; + Options.SetupRoleOptions(10800, TabGroup.NeutralRoles, CustomRoles.Innocent); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(10800, TabGroup.NeutralRoles, CustomRoles.Innocent); + Options.InnocentCanWinByImp = new BooleanOptionItem(10810, "InnocentCanWinByImp", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Innocent]); + } - Options.InnocentCanWinByImp = new BooleanOptionItem(10810, "InnocentCanWinByImp", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Innocent]); - } + public override void Add(byte playerId) + { + On = true; + } - public override void Add(byte playerId) - { - On = true; - } + public override void Init() + { + On = false; + } - public override void Init() - { - On = false; - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.Is(CustomRoles.Mischievous); + } - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.Is(CustomRoles.Mischievous); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("InnocentButtonText")); + hud.SabotageButton?.ToggleVisible(false); + hud.AbilityButton?.ToggleVisible(false); + hud.ImpostorVentButton?.ToggleVisible(false); + } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("InnocentButtonText")); - hud.SabotageButton?.ToggleVisible(false); - hud.AbilityButton?.ToggleVisible(false); - hud.ImpostorVentButton?.ToggleVisible(false); - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - target.Kill(killer); - return false; - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + target.Kill(killer); + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Jackal.cs b/Roles/Neutral/Jackal.cs index 20250bc14..8690737ea 100644 --- a/Roles/Neutral/Jackal.cs +++ b/Roles/Neutral/Jackal.cs @@ -6,263 +6,262 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Jackal : RoleBase { - public class Jackal : RoleBase + private const int Id = 12100; + public static List Instances = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + public static OptionItem CanSabotage; + public static OptionItem HasImpostorVision; + public static OptionItem ResetKillCooldownWhenSbGetKilled; + private static OptionItem ResetKillCooldownOn; + private static OptionItem CanRecruitImpostors; + private static OptionItem CanRecruitMadmates; + public static OptionItem SidekickCountMode; + public static OptionItem SKCanKill; + public static OptionItem KillCooldownSK; + public static OptionItem CanVentSK; + public static OptionItem CanSabotageSK; + private static OptionItem SKPromotesToJackal; + private static OptionItem PromotedSKCanRecruit; + + public static bool On; + + public byte SidekickId; + + public override bool IsEnable => Instances.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 12100; - public static List Instances = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jackal); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - public static OptionItem CanSabotage; - public static OptionItem HasImpostorVision; - public static OptionItem ResetKillCooldownWhenSbGetKilled; - private static OptionItem ResetKillCooldownOn; - private static OptionItem CanRecruitImpostors; - private static OptionItem CanRecruitMadmates; - public static OptionItem SidekickCountMode; - public static OptionItem SKCanKill; - public static OptionItem KillCooldownSK; - public static OptionItem CanVentSK; - public static OptionItem CanSabotageSK; - private static OptionItem SKPromotesToJackal; - private static OptionItem PromotedSKCanRecruit; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]) + .SetValueFormat(OptionFormat.Seconds); - public static bool On; + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); - public byte SidekickId; + CanSabotage = new BooleanOptionItem(Id + 4, "CanSabotage", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); - public override bool IsEnable => Instances.Count > 0; + HasImpostorVision = new BooleanOptionItem(Id + 6, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jackal); + ResetKillCooldownWhenSbGetKilled = new BooleanOptionItem(Id + 7, "ResetKillCooldownWhenPlayerGetKilled", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]) - .SetValueFormat(OptionFormat.Seconds); + ResetKillCooldownOn = new FloatOptionItem(Id + 8, "ResetKillCooldownOn", new(0f, 180f, 2.5f), 15f, TabGroup.NeutralRoles) + .SetParent(ResetKillCooldownWhenSbGetKilled) + .SetValueFormat(OptionFormat.Seconds); - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); + OptionItem SKOpts = new BooleanOptionItem(Id + 9, "SidekickSettings", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); - CanSabotage = new BooleanOptionItem(Id + 4, "CanSabotage", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); + CanRecruitImpostors = new BooleanOptionItem(Id + 10, "JackalCanRecruitImpostors", true, TabGroup.NeutralRoles) + .SetParent(SKOpts); - HasImpostorVision = new BooleanOptionItem(Id + 6, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); + CanRecruitMadmates = new BooleanOptionItem(Id + 11, "JackalCanRecruitMadmates", true, TabGroup.NeutralRoles) + .SetParent(CanRecruitImpostors); - ResetKillCooldownWhenSbGetKilled = new BooleanOptionItem(Id + 7, "ResetKillCooldownWhenPlayerGetKilled", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); + JackalCanKillSidekick = new BooleanOptionItem(Id + 12, "JackalCanKillSidekick", false, TabGroup.NeutralRoles) + .SetParent(SKOpts); - ResetKillCooldownOn = new FloatOptionItem(Id + 8, "ResetKillCooldownOn", new(0f, 180f, 2.5f), 15f, TabGroup.NeutralRoles) - .SetParent(ResetKillCooldownWhenSbGetKilled) - .SetValueFormat(OptionFormat.Seconds); + SKCanKill = new BooleanOptionItem(Id + 13, "SKCanKill", true, TabGroup.NeutralRoles) + .SetParent(SKOpts); - OptionItem SKOpts = new BooleanOptionItem(Id + 9, "SidekickSettings", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]); + KillCooldownSK = new FloatOptionItem(Id + 14, "KillCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles) + .SetParent(SKCanKill) + .SetValueFormat(OptionFormat.Seconds); - CanRecruitImpostors = new BooleanOptionItem(Id + 10, "JackalCanRecruitImpostors", true, TabGroup.NeutralRoles) - .SetParent(SKOpts); + SidekickCanKillJackal = new BooleanOptionItem(Id + 15, "SidekickCanKillJackal", false, TabGroup.NeutralRoles) + .SetParent(SKCanKill); - CanRecruitMadmates = new BooleanOptionItem(Id + 11, "JackalCanRecruitMadmates", true, TabGroup.NeutralRoles) - .SetParent(CanRecruitImpostors); + SidekickCanKillSidekick = new BooleanOptionItem(Id + 16, "SidekickCanKillSidekick", false, TabGroup.NeutralRoles) + .SetParent(SKCanKill); - JackalCanKillSidekick = new BooleanOptionItem(Id + 12, "JackalCanKillSidekick", false, TabGroup.NeutralRoles) - .SetParent(SKOpts); + CanVentSK = new BooleanOptionItem(Id + 17, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(SKOpts); - SKCanKill = new BooleanOptionItem(Id + 13, "SKCanKill", true, TabGroup.NeutralRoles) - .SetParent(SKOpts); + CanSabotageSK = new BooleanOptionItem(Id + 18, "CanSabotage", true, TabGroup.NeutralRoles) + .SetParent(SKOpts); - KillCooldownSK = new FloatOptionItem(Id + 14, "KillCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles) - .SetParent(SKCanKill) - .SetValueFormat(OptionFormat.Seconds); + SKPromotesToJackal = new BooleanOptionItem(Id + 19, "SKPromotesToJackal", true, TabGroup.NeutralRoles) + .SetParent(SKOpts); - SidekickCanKillJackal = new BooleanOptionItem(Id + 15, "SidekickCanKillJackal", false, TabGroup.NeutralRoles) - .SetParent(SKCanKill); + PromotedSKCanRecruit = new BooleanOptionItem(Id + 20, "PromotedSKCanRecruit", true, TabGroup.NeutralRoles) + .SetParent(SKPromotesToJackal); - SidekickCanKillSidekick = new BooleanOptionItem(Id + 16, "SidekickCanKillSidekick", false, TabGroup.NeutralRoles) - .SetParent(SKCanKill); - - CanVentSK = new BooleanOptionItem(Id + 17, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(SKOpts); - - CanSabotageSK = new BooleanOptionItem(Id + 18, "CanSabotage", true, TabGroup.NeutralRoles) - .SetParent(SKOpts); - - SKPromotesToJackal = new BooleanOptionItem(Id + 19, "SKPromotesToJackal", true, TabGroup.NeutralRoles) - .SetParent(SKOpts); - - PromotedSKCanRecruit = new BooleanOptionItem(Id + 20, "PromotedSKCanRecruit", true, TabGroup.NeutralRoles) - .SetParent(SKPromotesToJackal); - - SidekickCountMode = new StringOptionItem(Id + 21, "SidekickCountMode", Options.SidekickCountMode, 0, TabGroup.NeutralRoles) - .SetParent(SKOpts); - } - - public override void Init() - { - Instances = []; - On = false; - } - - public override void Add(byte playerId) - { - On = true; - Instances.Add(this); - playerId.SetAbilityUseLimit(1); - SidekickId = byte.MaxValue; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (CanSabotage.GetBool() && pc.IsAlive()); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton.OverrideText(id.GetAbilityUseLimit() > 0 ? $"{GetString("GangsterButtonText")}" : $"{GetString("KillButtonText")}"); - hud.SabotageButton.ToggleVisible(CanSabotage.GetBool()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public static void AfterPlayerDiedTask(PlayerControl target) - { - if (target.Is(CustomRoles.Jackal)) return; - - Main.AllAlivePlayerControls - .Where(x => x.Is(CustomRoles.Jackal)) - .Do(x => x.SetKillCooldown(ResetKillCooldownOn.GetFloat())); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1 || !CanBeSidekick(target)) return true; - - killer.RpcRemoveAbilityUse(); - target.RpcSetCustomRole(CustomRoles.Sidekick); - target.RpcChangeRoleBasis(CustomRoles.Sidekick); - SidekickId = target.PlayerId; - - Main.ResetCamPlayerList.Add(target.PlayerId); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), GetString("GangsterSuccessfullyRecruited"))); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), GetString("BeRecruitedByJackal"))); - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); - - killer.SetKillCooldown(3f); - target.RpcGuardAndKill(killer); - target.RpcGuardAndKill(target); - - Logger.Info($" {target.Data?.PlayerName} = {target.GetCustomRole()} + {CustomRoles.Sidekick}", $"Assign {CustomRoles.Sidekick}"); - - if (killer.IsLocalPlayer()) - Achievements.Type.YoureMyFriendNow.Complete(); - - return false; - } - - private static bool CanBeSidekick(PlayerControl pc) - { - if (!CanRecruitImpostors.GetBool() && pc.Is(CustomRoleTypes.Impostor)) return false; - - if (!CanRecruitMadmates.GetBool() && pc.IsMadmate()) return false; - - return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Curser) && !pc.Is(CustomRoles.Loyal) && !pc.IsConverted() && pc.GetCustomRole().IsAbleToBeSidekicked(); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc.IsAlive()) return; - - PromoteSidekick(); - } - - public void PromoteSidekick() - { - try - { - if (!SKPromotesToJackal.GetBool()) return; - - PlayerControl sk = SidekickId.GetPlayer(); - if (sk == null || !sk.Is(CustomRoles.Sidekick)) return; - - sk.RpcSetCustomRole(CustomRoles.Jackal); - if (!PromotedSKCanRecruit.GetBool()) sk.SetAbilityUseLimit(0); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } + SidekickCountMode = new StringOptionItem(Id + 21, "SidekickCountMode", Options.SidekickCountMode, 0, TabGroup.NeutralRoles) + .SetParent(SKOpts); } - public class Sidekick : RoleBase + public override void Init() { - private static List PlayerIdList = []; + Instances = []; + On = false; + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + playerId.SetAbilityUseLimit(1); + SidekickId = byte.MaxValue; + } - public override void SetupCustomOption() { } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - public override void Init() + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (CanSabotage.GetBool() && pc.IsAlive()); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton.OverrideText(id.GetAbilityUseLimit() > 0 ? $"{GetString("GangsterButtonText")}" : $"{GetString("KillButtonText")}"); + hud.SabotageButton.ToggleVisible(CanSabotage.GetBool()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public static void AfterPlayerDiedTask(PlayerControl target) + { + if (target.Is(CustomRoles.Jackal)) return; + + Main.AllAlivePlayerControls + .Where(x => x.Is(CustomRoles.Jackal)) + .Do(x => x.SetKillCooldown(ResetKillCooldownOn.GetFloat())); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1 || !CanBeSidekick(target)) return true; + + killer.RpcRemoveAbilityUse(); + target.RpcSetCustomRole(CustomRoles.Sidekick); + target.RpcChangeRoleBasis(CustomRoles.Sidekick); + SidekickId = target.PlayerId; + + Main.ResetCamPlayerList.Add(target.PlayerId); + + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), GetString("GangsterSuccessfullyRecruited"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Jackal), GetString("BeRecruitedByJackal"))); + + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); + + killer.SetKillCooldown(3f); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); + + Logger.Info($" {target.Data?.PlayerName} = {target.GetCustomRole()} + {CustomRoles.Sidekick}", $"Assign {CustomRoles.Sidekick}"); + + if (killer.IsLocalPlayer()) + Achievements.Type.YoureMyFriendNow.Complete(); + + return false; + } + + private static bool CanBeSidekick(PlayerControl pc) + { + if (!CanRecruitImpostors.GetBool() && pc.Is(CustomRoleTypes.Impostor)) return false; + + if (!CanRecruitMadmates.GetBool() && pc.IsMadmate()) return false; + + return pc != null && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Curser) && !pc.Is(CustomRoles.Loyal) && !pc.IsConverted() && pc.GetCustomRole().IsAbleToBeSidekicked(); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc.IsAlive()) return; + + PromoteSidekick(); + } + + public void PromoteSidekick() + { + try { - PlayerIdList = []; - } + if (!SKPromotesToJackal.GetBool()) return; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - new[] { CustomRoles.Damocles, CustomRoles.Stressed }.Do(x => Main.PlayerStates[playerId].RemoveSubRole(x)); - } + PlayerControl sk = SidekickId.GetPlayer(); + if (sk == null || !sk.Is(CustomRoles.Sidekick)) return; - public override bool CanUseKillButton(PlayerControl pc) - { - return Jackal.SKCanKill.GetBool(); + sk.RpcSetCustomRole(CustomRoles.Jackal); + if (!PromotedSKCanRecruit.GetBool()) sk.SetAbilityUseLimit(0); } + catch (Exception e) + { + Utils.ThrowException(e); + } + } +} - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return Jackal.CanVentSK.GetBool(); - } +public class Sidekick : RoleBase +{ + private static List PlayerIdList = []; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Jackal.KillCooldownSK.GetFloat(); - } + public override bool IsEnable => PlayerIdList.Count > 0; - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(Jackal.HasImpostorVision.GetBool()); - } + public override void SetupCustomOption() { } - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (Jackal.CanSabotageSK.GetBool() && pc.IsAlive()); - } + public override void Init() + { + PlayerIdList = []; + } - public override void SetButtonTexts(HudManager __instance, byte id) - { - __instance.SabotageButton.ToggleVisible(Jackal.CanSabotageSK.GetBool()); - __instance.KillButton?.OverrideText(GetString("KillButtonText")); - __instance.ImpostorVentButton?.OverrideText(GetString("ReportButtonText")); - __instance.SabotageButton?.OverrideText(GetString("SabotageButtonText")); - } + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + new[] { CustomRoles.Damocles, CustomRoles.Stressed }.Do(x => Main.PlayerStates[playerId].RemoveSubRole(x)); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return Jackal.SKCanKill.GetBool(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return Jackal.CanVentSK.GetBool(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Jackal.KillCooldownSK.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(Jackal.HasImpostorVision.GetBool()); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (Jackal.CanSabotageSK.GetBool() && pc.IsAlive()); + } + + public override void SetButtonTexts(HudManager __instance, byte id) + { + __instance.SabotageButton.ToggleVisible(Jackal.CanSabotageSK.GetBool()); + __instance.KillButton?.OverrideText(GetString("KillButtonText")); + __instance.ImpostorVentButton?.OverrideText(GetString("ReportButtonText")); + __instance.SabotageButton?.OverrideText(GetString("SabotageButtonText")); } } \ No newline at end of file diff --git a/Roles/Neutral/Jester.cs b/Roles/Neutral/Jester.cs index d94f0c97b..ad9641dcc 100644 --- a/Roles/Neutral/Jester.cs +++ b/Roles/Neutral/Jester.cs @@ -1,70 +1,69 @@ using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Jester : RoleBase { - internal class Jester : RoleBase + public static bool On; + + public static OptionItem JesterCanUseButton; + public static OptionItem JesterCanVent; + public static OptionItem JesterHasImpostorVision; + public static OptionItem SunnyboyChance; + public static OptionItem VentCooldown; + public static OptionItem MaxInVentTime; + public static OptionItem BlockVentMovement; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupRoleOptions(10900, TabGroup.NeutralRoles, CustomRoles.Jester); - public static OptionItem JesterCanUseButton; - public static OptionItem JesterCanVent; - public static OptionItem JesterHasImpostorVision; - public static OptionItem SunnyboyChance; - public static OptionItem VentCooldown; - public static OptionItem MaxInVentTime; - public static OptionItem BlockVentMovement; - public override bool IsEnable => On; + JesterCanUseButton = new BooleanOptionItem(10910, "JesterCanUseButton", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - public override void SetupCustomOption() - { - SetupRoleOptions(10900, TabGroup.NeutralRoles, CustomRoles.Jester); + JesterCanVent = new BooleanOptionItem(10911, "CanVent", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - JesterCanUseButton = new BooleanOptionItem(10910, "JesterCanUseButton", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); + VentCooldown = new FloatOptionItem(10913, "VentCooldown", new(0f, 60f, 0.5f), 0f, TabGroup.NeutralRoles) + .SetParent(JesterCanVent) + .SetValueFormat(OptionFormat.Seconds); - JesterCanVent = new BooleanOptionItem(10911, "CanVent", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); + MaxInVentTime = new FloatOptionItem(10914, "MaxInVentTime", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(JesterCanVent) + .SetValueFormat(OptionFormat.Seconds); - VentCooldown = new FloatOptionItem(10913, "VentCooldown", new(0f, 60f, 0.5f), 0f, TabGroup.NeutralRoles) - .SetParent(JesterCanVent) - .SetValueFormat(OptionFormat.Seconds); + BlockVentMovement = new BooleanOptionItem(10916, "BlockVentMovement", false, TabGroup.NeutralRoles) + .SetParent(JesterCanVent); - MaxInVentTime = new FloatOptionItem(10914, "MaxInVentTime", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(JesterCanVent) - .SetValueFormat(OptionFormat.Seconds); + JesterHasImpostorVision = new BooleanOptionItem(10912, "ImpostorVision", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); - BlockVentMovement = new BooleanOptionItem(10916, "BlockVentMovement", false, TabGroup.NeutralRoles) - .SetParent(JesterCanVent); + SunnyboyChance = new IntegerOptionItem(10915, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) + .SetValueFormat(OptionFormat.Percent); + } - JesterHasImpostorVision = new BooleanOptionItem(10912, "ImpostorVision", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]); + public override void Add(byte playerId) + { + On = true; + } - SunnyboyChance = new IntegerOptionItem(10915, "SunnyboyChance", new(0, 100, 5), 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jester]) - .SetValueFormat(OptionFormat.Percent); - } + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); + opt.SetVision(JesterHasImpostorVision.GetBool()); + } - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); - opt.SetVision(JesterHasImpostorVision.GetBool()); - } - - public override bool CanUseVent(PlayerControl pc, int ventId) - { - return !BlockVentMovement.GetBool() || pc.GetClosestVent()?.Id == ventId; - } + public override bool CanUseVent(PlayerControl pc, int ventId) + { + return !BlockVentMovement.GetBool() || pc.GetClosestVent()?.Id == ventId; } } \ No newline at end of file diff --git a/Roles/Neutral/Jinx.cs b/Roles/Neutral/Jinx.cs index c3eba1735..cedf89d00 100644 --- a/Roles/Neutral/Jinx.cs +++ b/Roles/Neutral/Jinx.cs @@ -1,38 +1,37 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Jinx : RoleBase { - public class Jinx : RoleBase + private const int Id = 12200; + + public static OptionItem KillCooldown; + public static OptionItem CanVent; + public static OptionItem HasImpostorVision; + public static OptionItem JinxSpellTimes; + public static OptionItem KillAttacker; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 12200; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jinx); - public static OptionItem KillCooldown; - public static OptionItem CanVent; - public static OptionItem HasImpostorVision; - public static OptionItem JinxSpellTimes; - public static OptionItem KillAttacker; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jinx); + JinxSpellTimes = new IntegerOptionItem(Id + 14, "JinxSpellTimes", new(0, 15, 1), 1, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) + .SetValueFormat(OptionFormat.Times); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - - JinxSpellTimes = new IntegerOptionItem(Id + 14, "JinxSpellTimes", new(0, 15, 1), 1, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]) - .SetValueFormat(OptionFormat.Times); - - KillAttacker = new BooleanOptionItem(Id + 12, "killAttacker", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + KillAttacker = new BooleanOptionItem(Id + 12, "killAttacker", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Jinx]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Juggernaut.cs b/Roles/Neutral/Juggernaut.cs index 4ffe3e7c8..99feab02d 100644 --- a/Roles/Neutral/Juggernaut.cs +++ b/Roles/Neutral/Juggernaut.cs @@ -1,38 +1,37 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Juggernaut : RoleBase { - public class Juggernaut : RoleBase + private const int Id = 12300; + + public static OptionItem DefaultKillCooldown; + public static OptionItem ReduceKillCooldown; + public static OptionItem MinKillCooldown; + public static OptionItem HasImpostorVision; + public static OptionItem CanVent; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 12300; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Juggernaut); - public static OptionItem DefaultKillCooldown; - public static OptionItem ReduceKillCooldown; - public static OptionItem MinKillCooldown; - public static OptionItem HasImpostorVision; - public static OptionItem CanVent; + DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 4f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Juggernaut); + MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]) + .SetValueFormat(OptionFormat.Seconds); - DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]) - .SetValueFormat(OptionFormat.Seconds); - - ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 4f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]) - .SetValueFormat(OptionFormat.Seconds); - - MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]) - .SetValueFormat(OptionFormat.Seconds); - - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]); - CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]); + CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Juggernaut]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Lawyer.cs b/Roles/Neutral/Lawyer.cs index fe936cbab..58e374b21 100644 --- a/Roles/Neutral/Lawyer.cs +++ b/Roles/Neutral/Lawyer.cs @@ -5,185 +5,184 @@ using EHR.Modules; using Hazel; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Lawyer : RoleBase { - public class Lawyer : RoleBase + private const int Id = 9900; + private static List PlayerIdList = []; + + private static OptionItem CanTargetImpostor; + private static OptionItem CanTargetNeutralKiller; + private static OptionItem CanTargetCrewmate; + private static OptionItem CanTargetJester; + private static OptionItem ChangeRolesAfterTargetKilled; + private static OptionItem KnowTargetRole; + private static OptionItem TargetKnowsLawyer; + + public static Dictionary Target = []; + + private static readonly CustomRoles[] CRoleChangeRoles = + [ + CustomRoles.CrewmateEHR, + CustomRoles.Jester, + CustomRoles.Opportunist, + CustomRoles.Convict, + CustomRoles.CyberStar, + CustomRoles.Bodyguard, + CustomRoles.Dictator, + CustomRoles.Mayor, + CustomRoles.Doctor, + CustomRoles.Amnesiac + ]; + + private byte LawyerId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 9900; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Lawyer); + CanTargetImpostor = new BooleanOptionItem(Id + 10, "LawyerCanTargetImpostor", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + CanTargetNeutralKiller = new BooleanOptionItem(Id + 11, "LawyerCanTargetNeutralKiller", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + CanTargetCrewmate = new BooleanOptionItem(Id + 12, "LawyerCanTargetCrewmate", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + CanTargetJester = new BooleanOptionItem(Id + 13, "LawyerCanTargetJester", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + KnowTargetRole = new BooleanOptionItem(Id + 14, "KnowTargetRole", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + TargetKnowsLawyer = new BooleanOptionItem(Id + 15, "TargetKnowsLawyer", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + ChangeRolesAfterTargetKilled = new StringOptionItem(Id + 16, "LawyerChangeRolesAfterTargetKilled", CRoleChangeRoles.Select(x => x.ToColoredString()).ToArray(), 2, TabGroup.NeutralRoles, noTranslation: true).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); + } - private static OptionItem CanTargetImpostor; - private static OptionItem CanTargetNeutralKiller; - private static OptionItem CanTargetCrewmate; - private static OptionItem CanTargetJester; - private static OptionItem ChangeRolesAfterTargetKilled; - private static OptionItem KnowTargetRole; - private static OptionItem TargetKnowsLawyer; + public override void Init() + { + PlayerIdList = []; + Target = []; + } - public static Dictionary Target = []; + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + LawyerId = playerId; - private static readonly CustomRoles[] CRoleChangeRoles = - [ - CustomRoles.CrewmateEHR, - CustomRoles.Jester, - CustomRoles.Opportunist, - CustomRoles.Convict, - CustomRoles.CyberStar, - CustomRoles.Bodyguard, - CustomRoles.Dictator, - CustomRoles.Mayor, - CustomRoles.Doctor, - CustomRoles.Amnesiac - ]; - - private byte LawyerId; - - public override bool IsEnable => PlayerIdList.Count > 0; - - public override void SetupCustomOption() + try { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Lawyer); - CanTargetImpostor = new BooleanOptionItem(Id + 10, "LawyerCanTargetImpostor", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - CanTargetNeutralKiller = new BooleanOptionItem(Id + 11, "LawyerCanTargetNeutralKiller", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - CanTargetCrewmate = new BooleanOptionItem(Id + 12, "LawyerCanTargetCrewmate", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - CanTargetJester = new BooleanOptionItem(Id + 13, "LawyerCanTargetJester", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - KnowTargetRole = new BooleanOptionItem(Id + 14, "KnowTargetRole", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - TargetKnowsLawyer = new BooleanOptionItem(Id + 15, "TargetKnowsLawyer", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - ChangeRolesAfterTargetKilled = new StringOptionItem(Id + 16, "LawyerChangeRolesAfterTargetKilled", CRoleChangeRoles.Select(x => x.ToColoredString()).ToArray(), 2, TabGroup.NeutralRoles, noTranslation: true).SetParent(CustomRoleSpawnChances[CustomRoles.Lawyer]); - } - - public override void Init() - { - PlayerIdList = []; - Target = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - LawyerId = playerId; - - try + if (AmongUsClient.Instance.AmHost) { - if (AmongUsClient.Instance.AmHost) + List targetList = []; + targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetCrewmate.GetBool() || !target.Is(CustomRoleTypes.Crewmate) where CanTargetJester.GetBool() || !target.Is(CustomRoles.Jester) where !target.Is(CustomRoleTypes.Neutral) || target.IsNeutralKiller() || target.Is(CustomRoles.Jester) where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.Curser) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); + + if (targetList.Count == 0) { - List targetList = []; - targetList.AddRange(from target in Main.AllPlayerControls where playerId != target.PlayerId where CanTargetImpostor.GetBool() || !target.Is(CustomRoleTypes.Impostor) where CanTargetNeutralKiller.GetBool() || !target.IsNeutralKiller() where CanTargetCrewmate.GetBool() || !target.Is(CustomRoleTypes.Crewmate) where CanTargetJester.GetBool() || !target.Is(CustomRoles.Jester) where !target.Is(CustomRoleTypes.Neutral) || target.IsNeutralKiller() || target.Is(CustomRoles.Jester) where target.GetCustomRole() is not (CustomRoles.GM or CustomRoles.SuperStar or CustomRoles.Curser) where Main.LoversPlayers.TrueForAll(x => x.PlayerId != playerId) select target); - - if (targetList.Count == 0) - { - ChangeRole(Utils.GetPlayerById(playerId)); - return; - } - - PlayerControl SelectedTarget = targetList.RandomElement(); - Target.Add(playerId, SelectedTarget.PlayerId); - SendRPC(playerId, SelectedTarget.PlayerId, "SetTarget"); - Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()}:{SelectedTarget.GetNameWithRole().RemoveHtmlTags()}", "Lawyer"); + ChangeRole(Utils.GetPlayerById(playerId)); + return; } - } - catch (Exception ex) - { - Logger.Error(ex.ToString(), "Lawyer.Add"); + + PlayerControl SelectedTarget = targetList.RandomElement(); + Target.Add(playerId, SelectedTarget.PlayerId); + SendRPC(playerId, SelectedTarget.PlayerId, "SetTarget"); + Logger.Info($"{Utils.GetPlayerById(playerId)?.GetNameWithRole().RemoveHtmlTags()}:{SelectedTarget.GetNameWithRole().RemoveHtmlTags()}", "Lawyer"); } } - - public static void SendRPC(byte lawyerId, byte targetId = 0x73, string Progress = "") + catch (Exception ex) { - if (!Utils.DoRPC) return; - - MessageWriter writer; - - switch (Progress) - { - case "SetTarget": - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLawyerTarget, SendOption.Reliable); - writer.Write(lawyerId); - writer.Write(targetId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - break; - case "": - if (!AmongUsClient.Instance.AmHost) return; - - writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveLawyerTarget, SendOption.Reliable); - writer.Write(lawyerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - break; - } - } - - public static void ReceiveRPC(MessageReader reader, bool SetTarget) - { - if (SetTarget) - { - byte LawyerId = reader.ReadByte(); - byte TargetId = reader.ReadByte(); - Target[LawyerId] = TargetId; - } - else - Target.Remove(reader.ReadByte()); - } - - public static void ChangeRoleByTarget(PlayerControl target) - { - byte Lawyer = 0x73; - - Target.Do(x => - { - if (x.Value == target.PlayerId) Lawyer = x.Key; - }); - - PlayerControl lawyer = Utils.GetPlayerById(Lawyer); - CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; - lawyer.RpcChangeRoleBasis(newRole); - lawyer.RpcSetCustomRole(newRole); - Target.Remove(Lawyer); - SendRPC(Lawyer); - lawyer.Notify(Translator.GetString("LawyerChangeRole")); - Utils.NotifyRoles(SpecifySeer: lawyer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: lawyer); - } - - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; - - if (!KnowTargetRole.GetBool()) return false; - - return player.Is(CustomRoles.Lawyer) && Target.TryGetValue(player.PlayerId, out byte tar) && tar == target.PlayerId; - } - - public static string LawyerMark(PlayerControl seer, PlayerControl target) - { - if (!seer.Is(CustomRoles.Lawyer)) - { - if (!TargetKnowsLawyer.GetBool()) return string.Empty; - - return Target.TryGetValue(target.PlayerId, out byte x) && seer.PlayerId == x ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "§") : string.Empty; - } - - bool GetValue = Target.TryGetValue(seer.PlayerId, out byte targetId); - return GetValue && targetId == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "§") : string.Empty; - } - - private static void ChangeRole(PlayerControl lawyer) - { - CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; - lawyer.RpcChangeRoleBasis(newRole); - lawyer.RpcSetCustomRole(newRole); - Target.Remove(lawyer.PlayerId); - SendRPC(lawyer.PlayerId); - lawyer.Notify(Translator.GetString("LawyerChangeRole")); - } - - public static bool CheckExileTarget(NetworkedPlayerInfo exiled /*, bool DecidedWinner, bool Check = false*/) - { - return Target.Where(x => x.Value == exiled.PlayerId).Select(kvp => Utils.GetPlayerById(kvp.Key)).Any(lawyer => lawyer != null && !lawyer.Data.Disconnected); - } - - public override void OnReportDeadBody() - { - if (MeetingStates.FirstMeeting && TargetKnowsLawyer.GetBool() && Target.TryGetValue(LawyerId, out byte target)) LateTask.New(() => Utils.SendMessage("\n", target, string.Format(Translator.GetString("YourLawyerIsNotify"), LawyerId.ColoredPlayerName())), 10f, log: false); + Logger.Error(ex.ToString(), "Lawyer.Add"); } } + + public static void SendRPC(byte lawyerId, byte targetId = 0x73, string Progress = "") + { + if (!Utils.DoRPC) return; + + MessageWriter writer; + + switch (Progress) + { + case "SetTarget": + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetLawyerTarget, SendOption.Reliable); + writer.Write(lawyerId); + writer.Write(targetId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + break; + case "": + if (!AmongUsClient.Instance.AmHost) return; + + writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.RemoveLawyerTarget, SendOption.Reliable); + writer.Write(lawyerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + break; + } + } + + public static void ReceiveRPC(MessageReader reader, bool SetTarget) + { + if (SetTarget) + { + byte LawyerId = reader.ReadByte(); + byte TargetId = reader.ReadByte(); + Target[LawyerId] = TargetId; + } + else + Target.Remove(reader.ReadByte()); + } + + public static void ChangeRoleByTarget(PlayerControl target) + { + byte Lawyer = 0x73; + + Target.Do(x => + { + if (x.Value == target.PlayerId) Lawyer = x.Key; + }); + + PlayerControl lawyer = Utils.GetPlayerById(Lawyer); + CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; + lawyer.RpcChangeRoleBasis(newRole); + lawyer.RpcSetCustomRole(newRole); + Target.Remove(Lawyer); + SendRPC(Lawyer); + lawyer.Notify(Translator.GetString("LawyerChangeRole")); + Utils.NotifyRoles(SpecifySeer: lawyer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: lawyer); + } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (!KnowTargetRole.GetBool()) return false; + + return player.Is(CustomRoles.Lawyer) && Target.TryGetValue(player.PlayerId, out byte tar) && tar == target.PlayerId; + } + + public static string LawyerMark(PlayerControl seer, PlayerControl target) + { + if (!seer.Is(CustomRoles.Lawyer)) + { + if (!TargetKnowsLawyer.GetBool()) return string.Empty; + + return Target.TryGetValue(target.PlayerId, out byte x) && seer.PlayerId == x ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "§") : string.Empty; + } + + bool GetValue = Target.TryGetValue(seer.PlayerId, out byte targetId); + return GetValue && targetId == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Lawyer), "§") : string.Empty; + } + + private static void ChangeRole(PlayerControl lawyer) + { + CustomRoles newRole = CRoleChangeRoles[ChangeRolesAfterTargetKilled.GetValue()]; + lawyer.RpcChangeRoleBasis(newRole); + lawyer.RpcSetCustomRole(newRole); + Target.Remove(lawyer.PlayerId); + SendRPC(lawyer.PlayerId); + lawyer.Notify(Translator.GetString("LawyerChangeRole")); + } + + public static bool CheckExileTarget(NetworkedPlayerInfo exiled /*, bool DecidedWinner, bool Check = false*/) + { + return Target.Where(x => x.Value == exiled.PlayerId).Select(kvp => Utils.GetPlayerById(kvp.Key)).Any(lawyer => lawyer != null && !lawyer.Data.Disconnected); + } + + public override void OnReportDeadBody() + { + if (MeetingStates.FirstMeeting && TargetKnowsLawyer.GetBool() && Target.TryGetValue(LawyerId, out byte target)) LateTask.New(() => Utils.SendMessage("\n", target, string.Format(Translator.GetString("YourLawyerIsNotify"), LawyerId.ColoredPlayerName())), 10f, log: false); + } } \ No newline at end of file diff --git a/Roles/Neutral/Magician.cs b/Roles/Neutral/Magician.cs index 1236e2c2a..55e1351f0 100644 --- a/Roles/Neutral/Magician.cs +++ b/Roles/Neutral/Magician.cs @@ -8,453 +8,452 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Magician : RoleBase { - public class Magician : RoleBase + private const int Id = 641300; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem Speed; + private static OptionItem SpeedDur; + private static OptionItem LowKCD; + private static OptionItem BlindDur; + private static OptionItem BlindRadius; + private static OptionItem SlownessRadius; + private static OptionItem SlownessDur; + private static OptionItem SlownessValue; + private static OptionItem BombRadius; + private static OptionItem BombDelay; + private static OptionItem ClearPortalAfterMeeting; + + private static byte CardId = byte.MaxValue; + + public static Dictionary SlowPpl = []; + public static Dictionary TempSpeeds = []; + public static Dictionary BlindPpl = []; + public static Dictionary Bombs = []; + private static List PortalMarks = []; + private static bool IsSniping; + private static Vector3 SnipeBasePosition; + private static bool IsSpeedup; + private static float OriginalSpeed; + private static long LastTP = TimeStamp; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 641300; - public static List PlayerIdList = []; + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Magician); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem Speed; - private static OptionItem SpeedDur; - private static OptionItem LowKCD; - private static OptionItem BlindDur; - private static OptionItem BlindRadius; - private static OptionItem SlownessRadius; - private static OptionItem SlownessDur; - private static OptionItem SlownessValue; - private static OptionItem BombRadius; - private static OptionItem BombDelay; - private static OptionItem ClearPortalAfterMeeting; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Seconds); - private static byte CardId = byte.MaxValue; + SlownessValue = new FloatOptionItem(Id + 11, "MagicianSlownessValue", new(0f, 1f, 0.05f), 1f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Multiplier); - public static Dictionary SlowPpl = []; - public static Dictionary TempSpeeds = []; - public static Dictionary BlindPpl = []; - public static Dictionary Bombs = []; - private static List PortalMarks = []; - private static bool IsSniping; - private static Vector3 SnipeBasePosition; - private static bool IsSpeedup; - private static float OriginalSpeed; - private static long LastTP = TimeStamp; + SlownessRadius = new FloatOptionItem(Id + 12, "MagicianSlownessRadius", new(0f, 10f, 0.25f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Multiplier); - public override bool IsEnable => PlayerIdList.Count > 0; + SlownessDur = new IntegerOptionItem(Id + 13, "MagicianSlownessDur", new(1, 30, 1), 10, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + Speed = new FloatOptionItem(Id + 14, "MagicianSpeedup", new(0.1f, 3f, 0.05f), 1.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Multiplier); + + SpeedDur = new IntegerOptionItem(Id + 15, "MagicianSpeedupDur", new(1, 20, 1), 10, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Seconds); + + LowKCD = new FloatOptionItem(Id + 16, "MagicianLowKCD", new(1f, 20f, 1f), 5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Seconds); + + BlindDur = new IntegerOptionItem(Id + 17, "MagicianBlindDur", new(1, 20, 1), 5, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Seconds); + + BlindRadius = new FloatOptionItem(Id + 18, "MagicianBlindRadius", new(0f, 10f, 0.25f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Multiplier); + + ClearPortalAfterMeeting = new BooleanOptionItem(Id + 19, "MagicianClearPortalAfterMeeting", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]); + + BombRadius = new FloatOptionItem(Id + 20, "MagicianBombRadius", new(0f, 10f, 0.25f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Multiplier); + + BombDelay = new IntegerOptionItem(Id + 21, "MagicianBombDelay", new(0, 10, 1), 3, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) + .SetValueFormat(OptionFormat.Seconds); + + CanVent = new BooleanOptionItem(Id + 22, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]); + HasImpostorVision = new BooleanOptionItem(Id + 23, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]); + } + + public override void Init() + { + PlayerIdList = []; + CardId = byte.MaxValue; + SlowPpl = []; + BlindPpl = []; + Bombs = []; + PortalMarks = []; + IsSniping = false; + IsSpeedup = false; + LastTP = TimeStamp; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + OriginalSpeed = Main.AllPlayerSpeed[playerId]; + + IsSniping = false; + IsSpeedup = false; + LastTP = TimeStamp; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return; + + CardId = (byte)IRandom.Instance.Next(1, 11); + + var sb = new StringBuilder(); + + sb.Append("\n\n"); + sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Card name: {GetString($"Magician-GetIdToName-{CardId}")}")); + sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Description: {GetString($"Magician-GetIdToDesc-{CardId}")}")); + sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Trigger by: {(UsePets.GetBool() ? "Pet button" : UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool() ? "Vanish button" : UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool() ? "Shapeshift button" : "Sabotage")}")); + + killer.Notify(sb.ToString(), 15f); + } + + public override void OnPet(PlayerControl pc) + { + UseCard(pc); + } + + public override bool OnSabotage(PlayerControl pc) + { + UseCard(pc); + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + UseCard(pc); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (shapeshifter == null) return false; + + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return false; + + UseCard(shapeshifter); + return false; + } + + public static void UseCard(PlayerControl pc) + { + if (pc == null) return; + + if (CardId == byte.MaxValue) return; + + var sync = false; + + switch (CardId) { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Magician); + case 1: // Slowness for everyone nearby + if (TempSpeeds.Count > 0) RevertSpeedChanges(true); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Seconds); + IEnumerable list = GetPlayersInRadius(SlownessRadius.GetFloat(), pc.Pos()); - SlownessValue = new FloatOptionItem(Id + 11, "MagicianSlownessValue", new(0f, 1f, 0.05f), 1f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Multiplier); + foreach (PlayerControl x in list) + { + if (x.PlayerId == pc.PlayerId) continue; - SlownessRadius = new FloatOptionItem(Id + 12, "MagicianSlownessRadius", new(0f, 10f, 0.25f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Multiplier); + TempSpeeds.TryAdd(x.PlayerId, Main.AllPlayerSpeed[x.PlayerId]); + SlowPpl.TryAdd(x.PlayerId, TimeStamp); + Main.AllPlayerSpeed[x.PlayerId] = SlownessValue.GetFloat(); + x.MarkDirtySettings(); + } - SlownessDur = new IntegerOptionItem(Id + 13, "MagicianSlownessDur", new(1, 30, 1), 10, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Seconds); + CardId = byte.MaxValue; + pc.Notify(GetString("MagicianCardUsed")); + break; + case 2: // Set KCD to x seconds + pc.SetKillCooldown(LowKCD.GetFloat()); + CardId = byte.MaxValue; + break; + case 3: // TP to random vent + pc.TPToRandomVent(); + CardId = byte.MaxValue; + break; + case 4: // Create Rift Maker portal + if (PortalMarks.Count == 2) PortalMarks.Clear(); - Speed = new FloatOptionItem(Id + 14, "MagicianSpeedup", new(0.1f, 3f, 0.05f), 1.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Multiplier); + PortalMarks.Add(pc.Pos()); + if (PortalMarks.Count == 2) CardId = byte.MaxValue; - SpeedDur = new IntegerOptionItem(Id + 15, "MagicianSpeedupDur", new(1, 20, 1), 10, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Seconds); + break; + case 5: // Snipe + if (!IsSniping) + { + SnipeBasePosition = pc.transform.position; + IsSniping = true; + pc.Notify(GetString("MarkDone")); + return; + } - LowKCD = new FloatOptionItem(Id + 16, "MagicianLowKCD", new(1f, 20f, 1f), 5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Seconds); + IsSniping = false; + CardId = byte.MaxValue; + pc.Notify(GetString("MagicianCardUsed")); - BlindDur = new IntegerOptionItem(Id + 17, "MagicianBlindDur", new(1, 20, 1), 5, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Seconds); + if (!AmongUsClient.Instance.AmHost || Pelican.IsEaten(pc.PlayerId) || Medic.ProtectList.Contains(pc.PlayerId)) return; - BlindRadius = new FloatOptionItem(Id + 18, "MagicianBlindRadius", new(0f, 10f, 0.25f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Multiplier); + pc.RPCPlayCustomSound("AWP"); - ClearPortalAfterMeeting = new BooleanOptionItem(Id + 19, "MagicianClearPortalAfterMeeting", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]); + Dictionary targets = GetSnipeTargets(pc); - BombRadius = new FloatOptionItem(Id + 20, "MagicianBombRadius", new(0f, 10f, 0.25f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Multiplier); + if (targets.Count > 0) + { + PlayerControl snipedTarget = targets.MinBy(c => c.Value).Key; + snipedTarget.CheckMurder(snipedTarget); + float temp = Main.KillTimers[pc.PlayerId]; + pc.SetKillCooldown(Main.AllPlayerKillCooldown[pc.PlayerId] + temp); - BombDelay = new IntegerOptionItem(Id + 21, "MagicianBombDelay", new(0, 10, 1), 3, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 22, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]); - HasImpostorVision = new BooleanOptionItem(Id + 23, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Magician]); - } - - public override void Init() - { - PlayerIdList = []; - CardId = byte.MaxValue; - SlowPpl = []; - BlindPpl = []; - Bombs = []; - PortalMarks = []; - IsSniping = false; - IsSpeedup = false; - LastTP = TimeStamp; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - OriginalSpeed = Main.AllPlayerSpeed[playerId]; - - IsSniping = false; - IsSpeedup = false; - LastTP = TimeStamp; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return; - - CardId = (byte)IRandom.Instance.Next(1, 11); - - var sb = new StringBuilder(); - - sb.Append("\n\n"); - sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Card name: {GetString($"Magician-GetIdToName-{CardId}")}")); - sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Description: {GetString($"Magician-GetIdToDesc-{CardId}")}")); - sb.AppendLine(ColorString(GetRoleColor(CustomRoles.Magician), $"Trigger by: {(UsePets.GetBool() ? "Pet button" : UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool() ? "Vanish button" : UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool() ? "Shapeshift button" : "Sabotage")}")); - - killer.Notify(sb.ToString(), 15f); - } - - public override void OnPet(PlayerControl pc) - { - UseCard(pc); - } - - public override bool OnSabotage(PlayerControl pc) - { - UseCard(pc); - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - UseCard(pc); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (shapeshifter == null) return false; - - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return false; - - UseCard(shapeshifter); - return false; - } - - public static void UseCard(PlayerControl pc) - { - if (pc == null) return; - - if (CardId == byte.MaxValue) return; - - var sync = false; - - switch (CardId) - { - case 1: // Slowness for everyone nearby - if (TempSpeeds.Count > 0) RevertSpeedChanges(true); - - IEnumerable list = GetPlayersInRadius(SlownessRadius.GetFloat(), pc.Pos()); - - foreach (PlayerControl x in list) - { - if (x.PlayerId == pc.PlayerId) continue; - - TempSpeeds.TryAdd(x.PlayerId, Main.AllPlayerSpeed[x.PlayerId]); - SlowPpl.TryAdd(x.PlayerId, TimeStamp); - Main.AllPlayerSpeed[x.PlayerId] = SlownessValue.GetFloat(); - x.MarkDirtySettings(); - } - - CardId = byte.MaxValue; - pc.Notify(GetString("MagicianCardUsed")); - break; - case 2: // Set KCD to x seconds - pc.SetKillCooldown(LowKCD.GetFloat()); - CardId = byte.MaxValue; - break; - case 3: // TP to random vent - pc.TPToRandomVent(); - CardId = byte.MaxValue; - break; - case 4: // Create Rift Maker portal - if (PortalMarks.Count == 2) PortalMarks.Clear(); - - PortalMarks.Add(pc.Pos()); - if (PortalMarks.Count == 2) CardId = byte.MaxValue; - - break; - case 5: // Snipe - if (!IsSniping) - { - SnipeBasePosition = pc.transform.position; - IsSniping = true; - pc.Notify(GetString("MarkDone")); - return; - } - - IsSniping = false; - CardId = byte.MaxValue; - pc.Notify(GetString("MagicianCardUsed")); - - if (!AmongUsClient.Instance.AmHost || Pelican.IsEaten(pc.PlayerId) || Medic.ProtectList.Contains(pc.PlayerId)) return; - - pc.RPCPlayCustomSound("AWP"); - - Dictionary targets = GetSnipeTargets(pc); - - if (targets.Count > 0) - { - PlayerControl snipedTarget = targets.MinBy(c => c.Value).Key; - snipedTarget.CheckMurder(snipedTarget); - float temp = Main.KillTimers[pc.PlayerId]; - pc.SetKillCooldown(Main.AllPlayerKillCooldown[pc.PlayerId] + temp); - - targets.Remove(snipedTarget); - PlayerControl[] list1 = [.. targets.Keys]; - foreach (PlayerControl x in list1) NotifyRoles(SpecifySeer: x); - - LateTask.New(() => - { - foreach (PlayerControl x in list1) NotifyRoles(SpecifySeer: x); - }, 0.5f, "Sniper shot Notify"); - } - - break; - case 6: // Blind everyone nearby - IEnumerable players = GetPlayersInRadius(BlindRadius.GetFloat(), pc.Pos()); - - foreach (PlayerControl x in players) - { - if (x.PlayerId == pc.PlayerId) continue; - - BlindPpl.TryAdd(x.PlayerId, TimeStamp); - x.MarkDirtySettings(); - } - - CardId = byte.MaxValue; - pc.Notify(GetString("MagicianCardUsed")); - break; - case 7: // Time bomb: Place, explodes after x seconds, kills everyone nearby - Bombs.TryAdd(pc.Pos(), TimeStamp); - CardId = byte.MaxValue; - pc.Notify(GetString("MagicianCardUsed")); - break; - case 8: // Speed up - Main.AllPlayerSpeed[pc.PlayerId] = Speed.GetFloat(); - IsSpeedup = true; - sync = true; + targets.Remove(snipedTarget); + PlayerControl[] list1 = [.. targets.Keys]; + foreach (PlayerControl x in list1) NotifyRoles(SpecifySeer: x); LateTask.New(() => { - Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed; - pc.MarkDirtySettings(); - IsSpeedup = false; - }, SpeedDur.GetInt(), "Revert Magician Speed"); - - CardId = byte.MaxValue; - break; - case 9: // Call meeting - pc.ReportDeadBody(null); - CardId = byte.MaxValue; - break; - case 10: // Admin map - Dictionary rooms = GetAllPlayerLocationsCount(); - var sb = new StringBuilder(); - foreach (KeyValuePair location in rooms) sb.Append($"\n{location.Key}: {location.Value}"); - - pc.Notify(sb.ToString(), 10f); - break; - default: - Logger.Error("Invalid Card ID", "Magician"); - break; - } - - if (sync) pc.MarkDirtySettings(); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (pc == null) return; - - if (!GameStates.IsInTask) return; - - if (Pelican.IsEaten(pc.PlayerId) || pc.Data.IsDead) return; - - if (TempSpeeds.Count > 0) RevertSpeedChanges(false); - - if (PortalMarks.Count == 2 && LastTP + 5 < TimeStamp) - { - if (Vector2.Distance(PortalMarks[0], PortalMarks[1]) <= 4f) - { - pc.Notify(GetString("IncorrectMarks")); - PortalMarks.Clear(); - } - else - { - Vector2 position = pc.Pos(); - - var isTP = false; - Vector2 from = PortalMarks[0]; - - foreach (Vector2 mark in PortalMarks.ToArray()) - { - float dis = Vector2.Distance(mark, position); - if (dis > 2f) continue; - - isTP = true; - from = mark; - } - - if (isTP) - { - LastTP = TimeStamp; - - if (from == PortalMarks[0]) - pc.TP(PortalMarks[1]); - else if (from == PortalMarks[1]) - pc.TP(PortalMarks[0]); - else - Logger.Error($"Teleport failed - from: {from}", "MagicianTP"); - } - } - } - - if (BlindPpl.Count > 0) - { - foreach (KeyValuePair x in BlindPpl.Where(x => x.Value + BlindDur.GetInt() < TimeStamp)) - { - BlindPpl.Remove(x.Key); - GetPlayerById(x.Key).MarkDirtySettings(); - } - } - - if (Bombs.Count > 0) - { - foreach (KeyValuePair bomb in Bombs.Where(bomb => bomb.Value + BombDelay.GetInt() < TimeStamp)) - { - var b = false; - IEnumerable players = GetPlayersInRadius(BombRadius.GetFloat(), bomb.Key); - - foreach (PlayerControl tg in players) - { - if (tg.PlayerId == pc.PlayerId) - { - b = true; - continue; - } - - tg.Suicide(PlayerState.DeathReason.Bombed, pc); - } - - Bombs.Remove(bomb.Key); - pc.Notify(GetString("MagicianBombExploded")); - - if (b) - { - LateTask.New(() => - { - if (!GameStates.IsEnded) pc.Suicide(PlayerState.DeathReason.Bombed); - }, 0.5f, "Magician Bomb Suicide"); - } + foreach (PlayerControl x in list1) NotifyRoles(SpecifySeer: x); + }, 0.5f, "Sniper shot Notify"); } + break; + case 6: // Blind everyone nearby + IEnumerable players = GetPlayersInRadius(BlindRadius.GetFloat(), pc.Pos()); + + foreach (PlayerControl x in players) + { + if (x.PlayerId == pc.PlayerId) continue; + + BlindPpl.TryAdd(x.PlayerId, TimeStamp); + x.MarkDirtySettings(); + } + + CardId = byte.MaxValue; + pc.Notify(GetString("MagicianCardUsed")); + break; + case 7: // Time bomb: Place, explodes after x seconds, kills everyone nearby + Bombs.TryAdd(pc.Pos(), TimeStamp); + CardId = byte.MaxValue; + pc.Notify(GetString("MagicianCardUsed")); + break; + case 8: // Speed up + Main.AllPlayerSpeed[pc.PlayerId] = Speed.GetFloat(); + IsSpeedup = true; + sync = true; + + LateTask.New(() => + { + Main.AllPlayerSpeed[pc.PlayerId] = OriginalSpeed; + pc.MarkDirtySettings(); + IsSpeedup = false; + }, SpeedDur.GetInt(), "Revert Magician Speed"); + + CardId = byte.MaxValue; + break; + case 9: // Call meeting + pc.ReportDeadBody(null); + CardId = byte.MaxValue; + break; + case 10: // Admin map + Dictionary rooms = GetAllPlayerLocationsCount(); var sb = new StringBuilder(); - long[] list = [.. Bombs.Values]; - foreach (long x in list) sb.Append(string.Format(GetString("MagicianBombExlodesIn"), BombDelay.GetInt() - (TimeStamp - x) + 1)); + foreach (KeyValuePair location in rooms) sb.Append($"\n{location.Key}: {location.Value}"); - pc.Notify(sb.ToString()); + pc.Notify(sb.ToString(), 10f); + break; + default: + Logger.Error("Invalid Card ID", "Magician"); + break; + } + + if (sync) pc.MarkDirtySettings(); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (pc == null) return; + + if (!GameStates.IsInTask) return; + + if (Pelican.IsEaten(pc.PlayerId) || pc.Data.IsDead) return; + + if (TempSpeeds.Count > 0) RevertSpeedChanges(false); + + if (PortalMarks.Count == 2 && LastTP + 5 < TimeStamp) + { + if (Vector2.Distance(PortalMarks[0], PortalMarks[1]) <= 4f) + { + pc.Notify(GetString("IncorrectMarks")); + PortalMarks.Clear(); + } + else + { + Vector2 position = pc.Pos(); + + var isTP = false; + Vector2 from = PortalMarks[0]; + + foreach (Vector2 mark in PortalMarks.ToArray()) + { + float dis = Vector2.Distance(mark, position); + if (dis > 2f) continue; + + isTP = true; + from = mark; + } + + if (isTP) + { + LastTP = TimeStamp; + + if (from == PortalMarks[0]) + pc.TP(PortalMarks[1]); + else if (from == PortalMarks[1]) + pc.TP(PortalMarks[0]); + else + Logger.Error($"Teleport failed - from: {from}", "MagicianTP"); + } } } - public override void OnReportDeadBody() + if (BlindPpl.Count > 0) { - SlowPpl.Clear(); - BlindPpl.Clear(); - Bombs.Clear(); - IsSniping = false; - if (ClearPortalAfterMeeting.GetBool()) PortalMarks.Clear(); - - if (IsSpeedup) + foreach (KeyValuePair x in BlindPpl.Where(x => x.Value + BlindDur.GetInt() < TimeStamp)) { - IsSpeedup = false; - Main.AllPlayerSpeed[PlayerIdList[0]] = OriginalSpeed; - } - } - - private static void RevertSpeedChanges(bool force) - { - foreach (KeyValuePair x in TempSpeeds.Where(x => SlowPpl[x.Key] + SlownessDur.GetInt() < TimeStamp || force)) - { - Main.AllPlayerSpeed[x.Key] = x.Value; - SlowPpl.Remove(x.Key); - TempSpeeds.Remove(x.Key); + BlindPpl.Remove(x.Key); GetPlayerById(x.Key).MarkDirtySettings(); } } - private static Dictionary GetSnipeTargets(PlayerControl sniper) + if (Bombs.Count > 0) { - var targets = new Dictionary(); - Vector3 snipeBasePos = SnipeBasePosition; - Vector3 snipePos = sniper.transform.position; - Vector3 dir = (snipePos - snipeBasePos).normalized; - - snipePos -= dir; - - foreach (PlayerControl target in Main.AllAlivePlayerControls) + foreach (KeyValuePair bomb in Bombs.Where(bomb => bomb.Value + BombDelay.GetInt() < TimeStamp)) { - if (target.PlayerId == sniper.PlayerId) continue; + var b = false; + IEnumerable players = GetPlayersInRadius(BombRadius.GetFloat(), bomb.Key); - Vector3 target_pos = target.transform.position - snipePos; - if (target_pos.magnitude < 1) continue; + foreach (PlayerControl tg in players) + { + if (tg.PlayerId == pc.PlayerId) + { + b = true; + continue; + } - Vector3 target_dir = target_pos.normalized; - float target_dot = Vector3.Dot(dir, target_dir); + tg.Suicide(PlayerState.DeathReason.Bombed, pc); + } - if (target_dot < 0.995) continue; + Bombs.Remove(bomb.Key); + pc.Notify(GetString("MagicianBombExploded")); - float err = target_pos.magnitude; - targets.Add(target, err); + if (b) + { + LateTask.New(() => + { + if (!GameStates.IsEnded) pc.Suicide(PlayerState.DeathReason.Bombed); + }, 0.5f, "Magician Bomb Suicide"); + } } - return targets; + var sb = new StringBuilder(); + long[] list = [.. Bombs.Values]; + foreach (long x in list) sb.Append(string.Format(GetString("MagicianBombExlodesIn"), BombDelay.GetInt() - (TimeStamp - x) + 1)); + + pc.Notify(sb.ToString()); } } + + public override void OnReportDeadBody() + { + SlowPpl.Clear(); + BlindPpl.Clear(); + Bombs.Clear(); + IsSniping = false; + if (ClearPortalAfterMeeting.GetBool()) PortalMarks.Clear(); + + if (IsSpeedup) + { + IsSpeedup = false; + Main.AllPlayerSpeed[PlayerIdList[0]] = OriginalSpeed; + } + } + + private static void RevertSpeedChanges(bool force) + { + foreach (KeyValuePair x in TempSpeeds.Where(x => SlowPpl[x.Key] + SlownessDur.GetInt() < TimeStamp || force)) + { + Main.AllPlayerSpeed[x.Key] = x.Value; + SlowPpl.Remove(x.Key); + TempSpeeds.Remove(x.Key); + GetPlayerById(x.Key).MarkDirtySettings(); + } + } + + private static Dictionary GetSnipeTargets(PlayerControl sniper) + { + var targets = new Dictionary(); + Vector3 snipeBasePos = SnipeBasePosition; + Vector3 snipePos = sniper.transform.position; + Vector3 dir = (snipePos - snipeBasePos).normalized; + + snipePos -= dir; + + foreach (PlayerControl target in Main.AllAlivePlayerControls) + { + if (target.PlayerId == sniper.PlayerId) continue; + + Vector3 target_pos = target.transform.position - snipePos; + if (target_pos.magnitude < 1) continue; + + Vector3 target_dir = target_pos.normalized; + float target_dot = Vector3.Dot(dir, target_dir); + + if (target_dot < 0.995) continue; + + float err = target_pos.magnitude; + targets.Add(target, err); + } + + return targets; + } } \ No newline at end of file diff --git a/Roles/Neutral/Mario.cs b/Roles/Neutral/Mario.cs index c219c1ebf..acf5aa92d 100644 --- a/Roles/Neutral/Mario.cs +++ b/Roles/Neutral/Mario.cs @@ -3,79 +3,78 @@ using AmongUs.GameOptions; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Mario : RoleBase { - internal class Mario : RoleBase + public static Dictionary MarioVentCount = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary MarioVentCount = []; + SetupRoleOptions(18300, TabGroup.NeutralRoles, CustomRoles.Mario); - public static bool On; - public override bool IsEnable => On; + MarioVentNumWin = new IntegerOptionItem(18310, "MarioVentNumWin", new(0, 900, 5), 80, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mario]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + MarioVentCD = new FloatOptionItem(18311, "VentCooldown", new(0f, 180f, 1f), 0f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mario]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Add(byte playerId) + { + On = true; + MarioVentCount[playerId] = 0; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = MarioVentCD.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + } + + public override string GetProgressText(byte playerId, bool comms) + { + return Utils.ColorString(Color.white, $"- {MarioVentCount.GetValueOrDefault(playerId, 0)}/{MarioVentNumWin.GetInt()}"); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.AbilityButton.buttonLabelText.text = Translator.GetString("MarioVentButtonText"); + hud.AbilityButton?.SetUsesRemaining(MarioVentNumWin.GetInt() - MarioVentCount.GetValueOrDefault(id, 0)); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + byte playerId = pc.PlayerId; + + if (MarioVentCount[playerId] > MarioVentNumWin.GetInt() && GameStates.IsInTask) { - SetupRoleOptions(18300, TabGroup.NeutralRoles, CustomRoles.Mario); - - MarioVentNumWin = new IntegerOptionItem(18310, "MarioVentNumWin", new(0, 900, 5), 80, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mario]) - .SetValueFormat(OptionFormat.Times); - - MarioVentCD = new FloatOptionItem(18311, "VentCooldown", new(0f, 180f, 1f), 0f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mario]) - .SetValueFormat(OptionFormat.Seconds); + MarioVentCount[playerId] = MarioVentNumWin.GetInt(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Mario); + CustomWinnerHolder.WinnerIds.Add(playerId); } + } - public override void Add(byte playerId) + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + MarioVentCount.TryAdd(pc.PlayerId, 0); + MarioVentCount[pc.PlayerId]++; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + if (AmongUsClient.Instance.AmHost && MarioVentCount[pc.PlayerId] >= MarioVentNumWin.GetInt()) { - On = true; - MarioVentCount[playerId] = 0; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = MarioVentCD.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - } - - public override string GetProgressText(byte playerId, bool comms) - { - return Utils.ColorString(Color.white, $"- {MarioVentCount.GetValueOrDefault(playerId, 0)}/{MarioVentNumWin.GetInt()}"); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.AbilityButton.buttonLabelText.text = Translator.GetString("MarioVentButtonText"); - hud.AbilityButton?.SetUsesRemaining(MarioVentNumWin.GetInt() - MarioVentCount.GetValueOrDefault(id, 0)); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - byte playerId = pc.PlayerId; - - if (MarioVentCount[playerId] > MarioVentNumWin.GetInt() && GameStates.IsInTask) - { - MarioVentCount[playerId] = MarioVentNumWin.GetInt(); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Mario); - CustomWinnerHolder.WinnerIds.Add(playerId); - } - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - MarioVentCount.TryAdd(pc.PlayerId, 0); - MarioVentCount[pc.PlayerId]++; - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - if (AmongUsClient.Instance.AmHost && MarioVentCount[pc.PlayerId] >= MarioVentNumWin.GetInt()) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Mario); - CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); - } + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Mario); + CustomWinnerHolder.WinnerIds.Add(pc.PlayerId); } } } \ No newline at end of file diff --git a/Roles/Neutral/Maverick.cs b/Roles/Neutral/Maverick.cs index 94029267c..3f23f9022 100644 --- a/Roles/Neutral/Maverick.cs +++ b/Roles/Neutral/Maverick.cs @@ -2,90 +2,89 @@ using AmongUs.GameOptions; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Maverick : RoleBase { - public class Maverick : RoleBase + private const int Id = 10000; + + private static OptionItem KillCooldown; + public static OptionItem CanVent; + private static OptionItem HasImpostorVision; + public static OptionItem MinKillsToWin; + + public byte MaverickId = byte.MaxValue; + public int NumOfKills; + + public override bool IsEnable => MaverickId != byte.MaxValue; + + public override void SetupCustomOption() { - private const int Id = 10000; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Maverick); - private static OptionItem KillCooldown; - public static OptionItem CanVent; - private static OptionItem HasImpostorVision; - public static OptionItem MinKillsToWin; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 35f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]) + .SetValueFormat(OptionFormat.Seconds); - public byte MaverickId = byte.MaxValue; - public int NumOfKills; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]); - public override bool IsEnable => MaverickId != byte.MaxValue; + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Maverick); + MinKillsToWin = new IntegerOptionItem(Id + 12, "DQNumOfKillsNeeded", new(0, 14, 1), 2, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]); + } - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 35f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + MaverickId = byte.MaxValue; + NumOfKills = 0; + } - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]); + public override void Add(byte playerId) + { + MaverickId = playerId; + NumOfKills = 0; + } - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - MinKillsToWin = new IntegerOptionItem(Id + 12, "DQNumOfKillsNeeded", new(0, 14, 1), 2, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Maverick]); - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } - public override void Init() - { - MaverickId = byte.MaxValue; - NumOfKills = 0; - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override void Add(byte playerId) - { - MaverickId = playerId; - NumOfKills = 0; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.Is(CustomRoles.Mischievous); + } - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } + public override string GetProgressText(byte playerId, bool comms) + { + if (Main.PlayerStates[playerId].Role is not Maverick mr) return string.Empty; - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + int kills = mr.NumOfKills; + int min = MinKillsToWin.GetInt(); + Color color = kills >= min ? Color.green : Color.red; + return Utils.ColorString(color, $"{kills}/{min}"); + } - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.Is(CustomRoles.Mischievous); - } - - public override string GetProgressText(byte playerId, bool comms) - { - if (Main.PlayerStates[playerId].Role is not Maverick mr) return string.Empty; - - int kills = mr.NumOfKills; - int min = MinKillsToWin.GetInt(); - Color color = kills >= min ? Color.green : Color.red; - return Utils.ColorString(color, $"{kills}/{min}"); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - NumOfKills++; - } + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + NumOfKills++; } } \ No newline at end of file diff --git a/Roles/Neutral/Medusa.cs b/Roles/Neutral/Medusa.cs index 8c064bad7..21d4d302f 100644 --- a/Roles/Neutral/Medusa.cs +++ b/Roles/Neutral/Medusa.cs @@ -1,43 +1,42 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Medusa : RoleBase { - public class Medusa : RoleBase + private const int Id = 12400; + + public static OptionItem KillCooldown; + public static OptionItem KillCooldownAfterStoneGazing; + public static OptionItem CanVent; + public static OptionItem HasImpostorVision; + public static OptionItem CannotStoneGazeWhenKCDIsntUp; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 12400; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Medusa); - public static OptionItem KillCooldown; - public static OptionItem KillCooldownAfterStoneGazing; - public static OptionItem CanVent; - public static OptionItem HasImpostorVision; - public static OptionItem CannotStoneGazeWhenKCDIsntUp; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + KillCooldownAfterStoneGazing = new FloatOptionItem(Id + 14, "KillCooldownAfterStoneGazing", new(0f, 180f, 0.5f), 60f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Medusa); + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) - .SetValueFormat(OptionFormat.Seconds); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - KillCooldownAfterStoneGazing = new FloatOptionItem(Id + 14, "KillCooldownAfterStoneGazing", new(0f, 180f, 0.5f), 60f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - - CannotStoneGazeWhenKCDIsntUp = new BooleanOptionItem(Id + 12, "CannotStoneGazeWhenKCDIsntUp", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + CannotStoneGazeWhenKCDIsntUp = new BooleanOptionItem(Id + 12, "CannotStoneGazeWhenKCDIsntUp", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Medusa]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Mycologist.cs b/Roles/Neutral/Mycologist.cs index 8f0a02600..b1a37a1ab 100644 --- a/Roles/Neutral/Mycologist.cs +++ b/Roles/Neutral/Mycologist.cs @@ -7,178 +7,177 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Mycologist : RoleBase { - internal class Mycologist : RoleBase + private static readonly string[] SpreadMode = + [ + "VentButtonText", // 0 + "SabotageButtonText", // 1 + "PetButtonText", // 2 + "AbilityButtonText.Phantom", // 3 + "AbilityButtonText.Shapeshifter" // 4 + ]; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + private static OptionItem SpreadAction; + private static OptionItem CD; + private static OptionItem InfectRadius; + private static OptionItem InfectTime; + + public readonly List InfectedPlayers = []; + private byte MycologistId = byte.MaxValue; + private static int Id => 643210; + + private PlayerControl MycologistPC => GetPlayerById(MycologistId); + + public override bool IsEnable => MycologistId != byte.MaxValue; + + public override void SetupCustomOption() { - private static readonly string[] SpreadMode = - [ - "VentButtonText", // 0 - "SabotageButtonText", // 1 - "PetButtonText", // 2 - "AbilityButtonText.Phantom", // 3 - "AbilityButtonText.Shapeshifter" // 4 - ]; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Mycologist); - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - private static OptionItem SpreadAction; - private static OptionItem CD; - private static OptionItem InfectRadius; - private static OptionItem InfectTime; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) + .SetValueFormat(OptionFormat.Seconds); - public readonly List InfectedPlayers = []; - private byte MycologistId = byte.MaxValue; - private static int Id => 643210; + HasImpostorVision = new BooleanOptionItem(Id + 7, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]); - private PlayerControl MycologistPC => GetPlayerById(MycologistId); + SpreadAction = new StringOptionItem(Id + 3, "MycologistAction", SpreadMode, 1, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]); - public override bool IsEnable => MycologistId != byte.MaxValue; + CD = new IntegerOptionItem(Id + 4, "AbilityCooldown", new(1, 90, 1), 15, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + InfectRadius = new FloatOptionItem(Id + 5, "InfectRadius", new(0.1f, 5f, 0.1f), 3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) + .SetValueFormat(OptionFormat.Multiplier); + + InfectTime = new IntegerOptionItem(Id + 6, "InfectDelay", new(0, 60, 1), 5, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + MycologistId = byte.MaxValue; + InfectedPlayers.Clear(); + } + + public override void Add(byte playerId) + { + MycologistId = playerId; + InfectedPlayers.Clear(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = CD.GetInt(); + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = CD.GetInt(); + } + + private void SendRPC() + { + if (!IsEnable || !DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncMycologist, SendOption.Reliable); + writer.Write(MycologistId); + writer.Write(InfectedPlayers.Count); + + if (InfectedPlayers.Count > 0) + foreach (byte x in InfectedPlayers) + writer.Write(x); + + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + if (Main.PlayerStates[playerId].Role is not Mycologist mg) return; + + mg.InfectedPlayers.Clear(); + int length = reader.ReadInt32(); + for (var i = 0; i < length; i++) mg.InfectedPlayers.Add(reader.ReadByte()); + } + + public override void OnPet(PlayerControl pc) + { + if (SpreadAction.GetValue() == 2) SpreadSpores(); + } + + public override bool OnSabotage(PlayerControl pc) + { + if (SpreadAction.GetValue() == 1) SpreadSpores(); + + return false; + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (SpreadAction.GetValue() == 0 || (SpreadAction.GetValue() == 2 && !UsePets.GetBool())) SpreadSpores(); + } + + public override bool OnVanish(PlayerControl pc) + { + if (SpreadAction.GetValue() == 3) SpreadSpores(); + + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + if (SpreadAction.GetValue() == 4) SpreadSpores(); + + return false; + } + + private void SpreadSpores() + { + if (!IsEnable || MycologistPC.HasAbilityCD()) return; + + MycologistPC.AddAbilityCD(CD.GetInt()); + + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Mycologist); + InfectedPlayers.AddRange(GetPlayersInRadius(InfectRadius.GetFloat(), MycologistPC.Pos()).Select(x => x.PlayerId)); + SendRPC(); + NotifyRoles(SpecifySeer: MycologistPC); + }, InfectTime.GetFloat(), "Mycologist Infect Time"); - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) - .SetValueFormat(OptionFormat.Seconds); + MycologistPC.Notify(GetString("MycologistNotify")); + } - HasImpostorVision = new BooleanOptionItem(Id + 7, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]); + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return IsEnable && target != null && InfectedPlayers.Contains(target.PlayerId); + } - SpreadAction = new StringOptionItem(Id + 3, "MycologistAction", SpreadMode, 1, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]); + public override void AfterMeetingTasks() + { + MycologistPC.AddAbilityCD(CD.GetInt()); + } - CD = new IntegerOptionItem(Id + 4, "AbilityCooldown", new(1, 90, 1), 15, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) - .SetValueFormat(OptionFormat.Seconds); + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return true; + } - InfectRadius = new FloatOptionItem(Id + 5, "InfectRadius", new(0.1f, 5f, 0.1f), 3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) - .SetValueFormat(OptionFormat.Multiplier); - - InfectTime = new IntegerOptionItem(Id + 6, "InfectDelay", new(0, 60, 1), 5, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Mycologist]) - .SetValueFormat(OptionFormat.Seconds); - } - - public override void Init() - { - MycologistId = byte.MaxValue; - InfectedPlayers.Clear(); - } - - public override void Add(byte playerId) - { - MycologistId = playerId; - InfectedPlayers.Clear(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = CD.GetInt(); - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = CD.GetInt(); - } - - private void SendRPC() - { - if (!IsEnable || !DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncMycologist, SendOption.Reliable); - writer.Write(MycologistId); - writer.Write(InfectedPlayers.Count); - - if (InfectedPlayers.Count > 0) - foreach (byte x in InfectedPlayers) - writer.Write(x); - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - if (Main.PlayerStates[playerId].Role is not Mycologist mg) return; - - mg.InfectedPlayers.Clear(); - int length = reader.ReadInt32(); - for (var i = 0; i < length; i++) mg.InfectedPlayers.Add(reader.ReadByte()); - } - - public override void OnPet(PlayerControl pc) - { - if (SpreadAction.GetValue() == 2) SpreadSpores(); - } - - public override bool OnSabotage(PlayerControl pc) - { - if (SpreadAction.GetValue() == 1) SpreadSpores(); - - return false; - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (SpreadAction.GetValue() == 0 || (SpreadAction.GetValue() == 2 && !UsePets.GetBool())) SpreadSpores(); - } - - public override bool OnVanish(PlayerControl pc) - { - if (SpreadAction.GetValue() == 3) SpreadSpores(); - - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - if (SpreadAction.GetValue() == 4) SpreadSpores(); - - return false; - } - - private void SpreadSpores() - { - if (!IsEnable || MycologistPC.HasAbilityCD()) return; - - MycologistPC.AddAbilityCD(CD.GetInt()); - - LateTask.New(() => - { - InfectedPlayers.AddRange(GetPlayersInRadius(InfectRadius.GetFloat(), MycologistPC.Pos()).Select(x => x.PlayerId)); - SendRPC(); - NotifyRoles(SpecifySeer: MycologistPC); - }, InfectTime.GetFloat(), "Mycologist Infect Time"); - - MycologistPC.Notify(GetString("MycologistNotify")); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return IsEnable && target != null && InfectedPlayers.Contains(target.PlayerId); - } - - public override void AfterMeetingTasks() - { - MycologistPC.AddAbilityCD(CD.GetInt()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return true; - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (SpreadAction.GetValue() == 1 && pc.IsAlive()); - } + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (SpreadAction.GetValue() == 1 && pc.IsAlive()); } } \ No newline at end of file diff --git a/Roles/Neutral/NSerialKiller.cs b/Roles/Neutral/NSerialKiller.cs index f430cc224..202159f74 100644 --- a/Roles/Neutral/NSerialKiller.cs +++ b/Roles/Neutral/NSerialKiller.cs @@ -1,48 +1,47 @@ using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class NSerialKiller : RoleBase { - public class NSerialKiller : RoleBase + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(12800) + .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref CanVent, true) + .AutoSetupOption(ref HasImpostorVision, true); + } - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + public override void Init() + { + On = false; + } - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + } - public override void SetupCustomOption() - { - StartSetup(12800) - .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref CanVent, true) - .AutoSetupOption(ref HasImpostorVision, true); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - public override void Init() - { - On = false; - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } - public override void Add(byte playerId) - { - On = true; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); } } \ No newline at end of file diff --git a/Roles/Neutral/NecroGuesser.cs b/Roles/Neutral/NecroGuesser.cs index f93ae0efb..16fa7bd8b 100644 --- a/Roles/Neutral/NecroGuesser.cs +++ b/Roles/Neutral/NecroGuesser.cs @@ -1,30 +1,29 @@ -namespace EHR.Neutral +namespace EHR.Neutral; + +public class NecroGuesser : RoleBase { - public class NecroGuesser : RoleBase + public static bool On; + + public override bool IsEnable => On; + + private static OptionItem NumGuessesToWin; + + public int GuessedPlayers; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(647125) + .AutoSetupOption(ref NumGuessesToWin, 2, new IntegerValueRule(1, 14, 1), OptionFormat.Players); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - private static OptionItem NumGuessesToWin; - - public int GuessedPlayers; - - public override void SetupCustomOption() - { - StartSetup(647125) - .AutoSetupOption(ref NumGuessesToWin, 2, new IntegerValueRule(1, 14, 1), OptionFormat.Players); - } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - GuessedPlayers = 0; - } + public override void Add(byte playerId) + { + On = true; + GuessedPlayers = 0; } } \ No newline at end of file diff --git a/Roles/Neutral/Necromancer.cs b/Roles/Neutral/Necromancer.cs index c1e4d729c..a4d82f3b9 100644 --- a/Roles/Neutral/Necromancer.cs +++ b/Roles/Neutral/Necromancer.cs @@ -4,236 +4,235 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Necromancer : RoleBase { - internal class Necromancer : RoleBase + private static byte NecromancerId = byte.MaxValue; + private static PlayerControl NecromancerPC; + + private static OptionItem CD; + public static OptionItem Dkcd; + private static OptionItem KnowTargetRole; + public static OptionItem UndeadCountMode; + + private static readonly string[] UndeadCountModeStrings = + [ + "UndeadCountMode.None", + "UndeadCountMode.Necromancer", + "UndeadCountMode.Original" + ]; + + public static readonly List PartiallyRecruitedIds = []; + private static int Id => 643450; + + public override bool IsEnable => NecromancerId != byte.MaxValue; + + public override void SetupCustomOption() { - private static byte NecromancerId = byte.MaxValue; - private static PlayerControl NecromancerPC; + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Necromancer); - private static OptionItem CD; - public static OptionItem Dkcd; - private static OptionItem KnowTargetRole; - public static OptionItem UndeadCountMode; + CD = new FloatOptionItem(Id + 2, "NecromancerCD", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) + .SetValueFormat(OptionFormat.Seconds); - private static readonly string[] UndeadCountModeStrings = - [ - "UndeadCountMode.None", - "UndeadCountMode.Necromancer", - "UndeadCountMode.Original" - ]; + Dkcd = new FloatOptionItem(Id + 10, "DKCD", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) + .SetValueFormat(OptionFormat.Seconds); - public static readonly List PartiallyRecruitedIds = []; - private static int Id => 643450; + KnowTargetRole = new BooleanOptionItem(Id + 13, "NecromancerKnowTargetRole", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); - public override bool IsEnable => NecromancerId != byte.MaxValue; - - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Necromancer); - - CD = new FloatOptionItem(Id + 2, "NecromancerCD", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) - .SetValueFormat(OptionFormat.Seconds); - - Dkcd = new FloatOptionItem(Id + 10, "DKCD", new(0f, 180f, 2.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]) - .SetValueFormat(OptionFormat.Seconds); - - KnowTargetRole = new BooleanOptionItem(Id + 13, "NecromancerKnowTargetRole", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); - - UndeadCountMode = new StringOptionItem(Id + 15, "UndeadCountMode", UndeadCountModeStrings, 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); - } - - public override void Init() - { - NecromancerId = byte.MaxValue; - NecromancerPC = null; - - PartiallyRecruitedIds.Clear(); - - Deathknight.DeathknightId = byte.MaxValue; - Deathknight.DeathknightPC = null; - } - - public override void Add(byte playerId) - { - NecromancerId = playerId; - NecromancerPC = Utils.GetPlayerById(playerId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CD.GetFloat(); - } - - public override bool CanUseKillButton(PlayerControl player) - { - return player.IsAlive(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultCrewmateVision); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Deathknight.DeathknightId == byte.MaxValue && !target.Is(CustomRoles.Loyal) && !target.Is(CustomRoles.Curser) && !target.IsConverted()) - { - target.RpcChangeRoleBasis(CustomRoles.Deathknight); - target.RpcSetCustomRole(CustomRoles.Deathknight); - - killer.SetKillCooldown(); - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target); - - target.MarkDirtySettings(); - target.ResetKillCooldown(); - target.SetKillCooldown(); - - target.Notify(GetString("RecruitedToDeathknight")); - - new[] { CustomRoles.Damocles, CustomRoles.Stressed }.Do(x => Main.PlayerStates[target.PlayerId].RemoveSubRole(x)); - - if (killer.IsLocalPlayer()) - Achievements.Type.YoureMyFriendNow.Complete(); - - return false; - } - - if (CanBeUndead(target) && !PartiallyRecruitedIds.Contains(target.PlayerId)) - { - PartiallyRecruitedIds.Add(target.PlayerId); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("NecromancerRecruitedPlayer"))); - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: Deathknight.DeathknightPC, SpecifyTarget: target); - - killer.SetKillCooldown(); - - Logger.Info($"Partial Recruit: {target.GetRealName()}", "Necromancer"); - - return false; - } - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("InvalidUndeadTarget"))); - - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || !IsEnable || NecromancerPC.IsAlive() || !Deathknight.DeathknightPC.IsAlive()) return; - - Deathknight.DeathknightPC.RpcSetCustomRole(CustomRoles.Necromancer); - Add(Deathknight.DeathknightId); - - Deathknight.DeathknightPC = null; - Deathknight.DeathknightId = byte.MaxValue; - } - - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; - - if (player.Is(CustomRoles.Undead) && (target.Is(CustomRoles.Necromancer) || target.Is(CustomRoles.Deathknight))) return true; - - if (KnowTargetRole.GetBool() && (player.Is(CustomRoles.Necromancer) || player.Is(CustomRoles.Deathknight)) && target.Is(CustomRoles.Undead)) return true; - - if (player.Is(CustomRoles.Deathknight) && target.Is(CustomRoles.Necromancer)) return true; - - return player.Is(CustomRoles.Necromancer) && target.Is(CustomRoles.Deathknight); - } - - public static bool CanBeUndead(PlayerControl pc) - { - return pc != null && !pc.Is(CustomRoles.Deathknight) && !pc.Is(CustomRoles.Necromancer) && !pc.Is(CustomRoles.Undead) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Curser) && !pc.IsConverted(); - } + UndeadCountMode = new StringOptionItem(Id + 15, "UndeadCountMode", UndeadCountModeStrings, 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Necromancer]); } - internal class Deathknight : RoleBase + public override void Init() { - public static byte DeathknightId = byte.MaxValue; - public static PlayerControl DeathknightPC; + NecromancerId = byte.MaxValue; + NecromancerPC = null; - public override bool IsEnable => DeathknightId != byte.MaxValue; + PartiallyRecruitedIds.Clear(); - public override void SetupCustomOption() { } + Deathknight.DeathknightId = byte.MaxValue; + Deathknight.DeathknightPC = null; + } - public override void Init() + public override void Add(byte playerId) + { + NecromancerId = playerId; + NecromancerPC = Utils.GetPlayerById(playerId); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CD.GetFloat(); + } + + public override bool CanUseKillButton(PlayerControl player) + { + return player.IsAlive(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultCrewmateVision); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Deathknight.DeathknightId == byte.MaxValue && !target.Is(CustomRoles.Loyal) && !target.Is(CustomRoles.Curser) && !target.IsConverted()) { - DeathknightId = byte.MaxValue; - DeathknightPC = null; - } + target.RpcChangeRoleBasis(CustomRoles.Deathknight); + target.RpcSetCustomRole(CustomRoles.Deathknight); - public override void Add(byte playerId) - { - DeathknightId = playerId; - DeathknightPC = Utils.GetPlayerById(playerId); - // if (!UsePets.GetBool()) Deathknight_.ChangeRoleBasis(RoleTypes.Impostor); - } + killer.SetKillCooldown(); - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Necromancer.Dkcd.GetFloat(); - } + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target); - public override bool CanUseKillButton(PlayerControl player) - { - return player.IsAlive(); - } + target.MarkDirtySettings(); + target.ResetKillCooldown(); + target.SetKillCooldown(); + + target.Notify(GetString("RecruitedToDeathknight")); + + new[] { CustomRoles.Damocles, CustomRoles.Stressed }.Do(x => Main.PlayerStates[target.PlayerId].RemoveSubRole(x)); + + if (killer.IsLocalPlayer()) + Achievements.Type.YoureMyFriendNow.Complete(); - public override bool CanUseImpostorVentButton(PlayerControl pc) - { return false; } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + if (CanBeUndead(target) && !PartiallyRecruitedIds.Contains(target.PlayerId)) { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultCrewmateVision); - } + PartiallyRecruitedIds.Add(target.PlayerId); - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Necromancer.CanBeUndead(target) && Necromancer.PartiallyRecruitedIds.Contains(target.PlayerId)) - { - target.RpcSetCustomRole(CustomRoles.Undead); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("NecromancerRecruitedPlayer"))); - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("DeathknightRecruitedPlayer"))); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("RecruitedByDeathknight"))); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: Deathknight.DeathknightPC, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + killer.SetKillCooldown(); - killer.SetKillCooldown(); - target.RpcGuardAndKill(killer); - target.RpcGuardAndKill(target); - - Logger.Info($"Recruit: {target.Data?.PlayerName} = {target.GetCustomRole()} + {CustomRoles.Undead}", $"Assign {CustomRoles.Undead}"); - - if (killer.IsLocalPlayer()) - Achievements.Type.YoureMyFriendNow.Complete(); - - return false; - } - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("InvalidUndeadTarget"))); + Logger.Info($"Partial Recruit: {target.GetRealName()}", "Necromancer"); return false; } + + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("InvalidUndeadTarget"))); + + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || !IsEnable || NecromancerPC.IsAlive() || !Deathknight.DeathknightPC.IsAlive()) return; + + Deathknight.DeathknightPC.RpcSetCustomRole(CustomRoles.Necromancer); + Add(Deathknight.DeathknightId); + + Deathknight.DeathknightPC = null; + Deathknight.DeathknightId = byte.MaxValue; + } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (player.Is(CustomRoles.Undead) && (target.Is(CustomRoles.Necromancer) || target.Is(CustomRoles.Deathknight))) return true; + + if (KnowTargetRole.GetBool() && (player.Is(CustomRoles.Necromancer) || player.Is(CustomRoles.Deathknight)) && target.Is(CustomRoles.Undead)) return true; + + if (player.Is(CustomRoles.Deathknight) && target.Is(CustomRoles.Necromancer)) return true; + + return player.Is(CustomRoles.Necromancer) && target.Is(CustomRoles.Deathknight); + } + + public static bool CanBeUndead(PlayerControl pc) + { + return pc != null && !pc.Is(CustomRoles.Deathknight) && !pc.Is(CustomRoles.Necromancer) && !pc.Is(CustomRoles.Undead) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Curser) && !pc.IsConverted(); + } +} + +internal class Deathknight : RoleBase +{ + public static byte DeathknightId = byte.MaxValue; + public static PlayerControl DeathknightPC; + + public override bool IsEnable => DeathknightId != byte.MaxValue; + + public override void SetupCustomOption() { } + + public override void Init() + { + DeathknightId = byte.MaxValue; + DeathknightPC = null; + } + + public override void Add(byte playerId) + { + DeathknightId = playerId; + DeathknightPC = Utils.GetPlayerById(playerId); + // if (!UsePets.GetBool()) Deathknight_.ChangeRoleBasis(RoleTypes.Impostor); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Necromancer.Dkcd.GetFloat(); + } + + public override bool CanUseKillButton(PlayerControl player) + { + return player.IsAlive(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, Main.DefaultCrewmateVision); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, Main.DefaultCrewmateVision); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Necromancer.CanBeUndead(target) && Necromancer.PartiallyRecruitedIds.Contains(target.PlayerId)) + { + target.RpcSetCustomRole(CustomRoles.Undead); + + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("DeathknightRecruitedPlayer"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("RecruitedByDeathknight"))); + + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer); + + killer.SetKillCooldown(); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); + + Logger.Info($"Recruit: {target.Data?.PlayerName} = {target.GetCustomRole()} + {CustomRoles.Undead}", $"Assign {CustomRoles.Undead}"); + + if (killer.IsLocalPlayer()) + Achievements.Type.YoureMyFriendNow.Complete(); + + return false; + } + + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Necromancer), GetString("InvalidUndeadTarget"))); + + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Nonplus.cs b/Roles/Neutral/Nonplus.cs index 6742a33f3..2740b4e92 100644 --- a/Roles/Neutral/Nonplus.cs +++ b/Roles/Neutral/Nonplus.cs @@ -1,88 +1,87 @@ -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Nonplus : RoleBase { - public class Nonplus : RoleBase + public static bool On; + + public static OptionItem BlindCooldown; + public static OptionItem BlindDuration; + public static OptionItem UseLimit; + public static OptionItem NonplusAbilityUseGainWithEachKill; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + const int id = 649500; + const TabGroup tab = TabGroup.NeutralRoles; + const CustomRoles role = CustomRoles.Nonplus; - public static OptionItem BlindCooldown; - public static OptionItem BlindDuration; - public static OptionItem UseLimit; - public static OptionItem NonplusAbilityUseGainWithEachKill; - public override bool IsEnable => On; + Options.SetupRoleOptions(id, tab, role); - public override void SetupCustomOption() + BlindCooldown = new IntegerOptionItem(id + 2, "Nonplus.BlindCooldown", new(0, 60, 1), 30, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); + + BlindDuration = new IntegerOptionItem(id + 3, "Nonplus.BlindDuration", new(0, 60, 1), 10, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Seconds); + + UseLimit = new IntegerOptionItem(id + 4, "AbilityUseLimit", new(0, 5, 1), 0, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Times); + + NonplusAbilityUseGainWithEachKill = new FloatOptionItem(id + 5, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1f, tab) + .SetParent(Options.CustomRoleSpawnChances[role]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + playerId.SetAbilityUseLimit(UseLimit.GetInt()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return true; + } + + public override void OnPet(PlayerControl pc) + { + Blind(pc); + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (Options.UsePets.GetBool()) return; + + Blind(pc); + } + + private static void Blind(PlayerControl pc) + { + if (pc.GetAbilityUseLimit() < 1) return; + + pc.RpcRemoveAbilityUse(); + + Main.AllAlivePlayerControls.Without(pc).Do(x => { - const int id = 649500; - const TabGroup tab = TabGroup.NeutralRoles; - const CustomRoles role = CustomRoles.Nonplus; + Main.PlayerStates[x.PlayerId].IsBlackOut = true; + x.MarkDirtySettings(); + }); - Options.SetupRoleOptions(id, tab, role); - - BlindCooldown = new IntegerOptionItem(id + 2, "Nonplus.BlindCooldown", new(0, 60, 1), 30, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - BlindDuration = new IntegerOptionItem(id + 3, "Nonplus.BlindDuration", new(0, 60, 1), 10, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimit = new IntegerOptionItem(id + 4, "AbilityUseLimit", new(0, 5, 1), 0, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Times); - - NonplusAbilityUseGainWithEachKill = new FloatOptionItem(id + 5, "AbilityUseGainWithEachKill", new(0f, 5f, 0.1f), 1f, tab) - .SetParent(Options.CustomRoleSpawnChances[role]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() + LateTask.New(() => { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - playerId.SetAbilityUseLimit(UseLimit.GetInt()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return true; - } - - public override void OnPet(PlayerControl pc) - { - Blind(pc); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (Options.UsePets.GetBool()) return; - - Blind(pc); - } - - private static void Blind(PlayerControl pc) - { - if (pc.GetAbilityUseLimit() < 1) return; - - pc.RpcRemoveAbilityUse(); - Main.AllAlivePlayerControls.Without(pc).Do(x => { - Main.PlayerStates[x.PlayerId].IsBlackOut = true; + Main.PlayerStates[x.PlayerId].IsBlackOut = false; x.MarkDirtySettings(); }); - - LateTask.New(() => - { - Main.AllAlivePlayerControls.Without(pc).Do(x => - { - Main.PlayerStates[x.PlayerId].IsBlackOut = false; - x.MarkDirtySettings(); - }); - }, BlindDuration.GetInt(), "Nonplus.Blind"); - } + }, BlindDuration.GetInt(), "Nonplus.Blind"); } } \ No newline at end of file diff --git a/Roles/Neutral/NoteKiller.cs b/Roles/Neutral/NoteKiller.cs index b5fa1d033..635e5de73 100644 --- a/Roles/Neutral/NoteKiller.cs +++ b/Roles/Neutral/NoteKiller.cs @@ -4,197 +4,196 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class NoteKiller : RoleBase { - public class NoteKiller : RoleBase + public static bool On; + + public static OptionItem AbilityCooldown; + private static OptionItem NumLettersRevealed; + private static OptionItem ClueShowDuration; + private static OptionItem WinCondition; + private static OptionItem NumPlayersToKill; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + + private static readonly string[] WinConditions = + [ + "NKWC.LastStanding", + "NKWC.XKills" + ]; + + private static readonly string[] Names = + [ + "John", "Jane", "Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hank", + "Ivy", "Jack", "Kate", "Liam", "Mia", "Nina", "Oliver", "Penny", "Quinn", "Ryan" + ]; + + public static Dictionary RealNames = []; + private static Dictionary ShownClues = []; + private static long ShowClueEndTimeStamp; + public static int Kills; + + private static byte NoteKillerID; + + public static bool CountsAsNeutralKiller => WinCondition?.GetValue() == 0; + public static int NumKillsNeededToWin => NumPlayersToKill.GetInt(); + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(645950, single: true) + .AutoSetupOption(ref AbilityCooldown, 15f, new FloatValueRule(0f, 90f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref NumLettersRevealed, 2, new IntegerValueRule(1, Names.Max(x => x.Length), 1)) + .AutoSetupOption(ref ClueShowDuration, 5, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) + .AutoSetupOption(ref WinCondition, 0, WinConditions) + .AutoSetupOption(ref NumPlayersToKill, 2, new IntegerValueRule(0, 14, 1)) + .AutoSetupOption(ref CanVent, true) + .AutoSetupOption(ref HasImpostorVision, true); + } - public static OptionItem AbilityCooldown; - private static OptionItem NumLettersRevealed; - private static OptionItem ClueShowDuration; - private static OptionItem WinCondition; - private static OptionItem NumPlayersToKill; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + public override void Init() + { + On = false; - private static readonly string[] WinConditions = - [ - "NKWC.LastStanding", - "NKWC.XKills" - ]; + RealNames = []; + ShownClues = []; + ShowClueEndTimeStamp = 0; + Kills = 0; - private static readonly string[] Names = - [ - "John", "Jane", "Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Hank", - "Ivy", "Jack", "Kate", "Liam", "Mia", "Nina", "Oliver", "Penny", "Quinn", "Ryan" - ]; - - public static Dictionary RealNames = []; - private static Dictionary ShownClues = []; - private static long ShowClueEndTimeStamp; - public static int Kills; - - private static byte NoteKillerID; - - public static bool CountsAsNeutralKiller => WinCondition?.GetValue() == 0; - public static int NumKillsNeededToWin => NumPlayersToKill.GetInt(); - - public override bool IsEnable => On; - - public override void SetupCustomOption() + LateTask.New(() => { - StartSetup(645950, single: true) - .AutoSetupOption(ref AbilityCooldown, 15f, new FloatValueRule(0f, 90f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref NumLettersRevealed, 2, new IntegerValueRule(1, Names.Max(x => x.Length), 1)) - .AutoSetupOption(ref ClueShowDuration, 5, new IntegerValueRule(0, 30, 1), OptionFormat.Seconds) - .AutoSetupOption(ref WinCondition, 0, WinConditions) - .AutoSetupOption(ref NumPlayersToKill, 2, new IntegerValueRule(0, 14, 1)) - .AutoSetupOption(ref CanVent, true) - .AutoSetupOption(ref HasImpostorVision, true); + var names = Names.ToList(); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) + { + if (pc.Is(CustomRoles.NoteKiller)) continue; + var name = names.RandomElement(); + RealNames[pc.PlayerId] = name; + names.Remove(name); + Logger.Info($"{pc.GetRealName()}'s real name is {name}", "NoteKiller.Init"); + } + }, 10f, log: false); + } + + public override void Add(byte playerId) + { + On = true; + NoteKillerID = playerId; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + + if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void OnPet(PlayerControl pc) + { + Dictionary> revealedPositions = []; + + for (int i = 0; i < NumLettersRevealed.GetInt(); i++) + { + foreach (KeyValuePair kvp in RealNames) + { + var range = Enumerable.Range(0, Names.Max(x => x.Length) - 1); + var hasExceptions = revealedPositions.TryGetValue(kvp.Key, out var exceptions); + if (hasExceptions) range = range.Except(exceptions); + + var position = range.RandomElement(); + + if (!hasExceptions) revealedPositions[kvp.Key] = [position]; + else exceptions.Add(position); + } } - public override void Init() - { - On = false; + IEnumerable<(byte ID, List Positions, string Name, PlayerControl Player)> datas = revealedPositions.Join( + RealNames, x => x.Key, x => x.Key, (x, y) => ( + ID: x.Key, + Positions: x.Value, + Name: y.Value, + Player: y.Key.GetPlayer())); - RealNames = []; - ShownClues = []; + foreach ((byte ID, List Positions, string Name, PlayerControl Player) data in datas) + { + if (data.Player == null || !data.Player.IsAlive()) + { + RealNames.Remove(data.ID); + continue; + } + + var clue = new char[data.Name.Length]; + Loop.Times(data.Name.Length, i => clue[i] = data.Positions.Contains(i) ? data.Name[i] : '_'); + + string shownClue = new(clue); + ShownClues[data.ID] = shownClue; + Utils.SendRPC(CustomRPC.SyncRoleData, NoteKillerID, 1, data.ID, shownClue); + } + + ShowClueEndTimeStamp = Utils.TimeStamp + ClueShowDuration.GetInt(); + Utils.SendRPC(CustomRPC.SyncRoleData, NoteKillerID, 2, ShowClueEndTimeStamp); + Utils.NotifyRoles(SpecifySeer: pc); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance || pc == null || !pc.IsAlive() || ShowClueEndTimeStamp == 0 || ShownClues.Count == 0) return; + + if (Utils.TimeStamp >= ShowClueEndTimeStamp) + { ShowClueEndTimeStamp = 0; - Kills = 0; - - LateTask.New(() => - { - var names = Names.ToList(); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.Is(CustomRoles.NoteKiller)) continue; - var name = names.RandomElement(); - RealNames[pc.PlayerId] = name; - names.Remove(name); - Logger.Info($"{pc.GetRealName()}'s real name is {name}", "NoteKiller.Init"); - } - }, 10f, log: false); - } - - public override void Add(byte playerId) - { - On = true; - NoteKillerID = playerId; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - - if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) - AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void OnPet(PlayerControl pc) - { - Dictionary> revealedPositions = []; - - for (int i = 0; i < NumLettersRevealed.GetInt(); i++) - { - foreach (KeyValuePair kvp in RealNames) - { - var range = Enumerable.Range(0, Names.Max(x => x.Length) - 1); - var hasExceptions = revealedPositions.TryGetValue(kvp.Key, out var exceptions); - if (hasExceptions) range = range.Except(exceptions); - - var position = range.RandomElement(); - - if (!hasExceptions) revealedPositions[kvp.Key] = [position]; - else exceptions.Add(position); - } - } - - IEnumerable<(byte ID, List Positions, string Name, PlayerControl Player)> datas = revealedPositions.Join( - RealNames, x => x.Key, x => x.Key, (x, y) => ( - ID: x.Key, - Positions: x.Value, - Name: y.Value, - Player: y.Key.GetPlayer())); - - foreach ((byte ID, List Positions, string Name, PlayerControl Player) data in datas) - { - if (data.Player == null || !data.Player.IsAlive()) - { - RealNames.Remove(data.ID); - continue; - } - - var clue = new char[data.Name.Length]; - Loop.Times(data.Name.Length, i => clue[i] = data.Positions.Contains(i) ? data.Name[i] : '_'); - - string shownClue = new(clue); - ShownClues[data.ID] = shownClue; - Utils.SendRPC(CustomRPC.SyncRoleData, NoteKillerID, 1, data.ID, shownClue); - } - - ShowClueEndTimeStamp = Utils.TimeStamp + ClueShowDuration.GetInt(); - Utils.SendRPC(CustomRPC.SyncRoleData, NoteKillerID, 2, ShowClueEndTimeStamp); + ShownClues.Clear(); + Utils.SendRPC(CustomRPC.SyncRoleData, NoteKillerID, 3); Utils.NotifyRoles(SpecifySeer: pc); } + } - public override void OnFixedUpdate(PlayerControl pc) + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool() && !shapeshifting) + OnPet(shapeshifter); + + return false; + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - if (!GameStates.IsInTask || ExileController.Instance || pc == null || !pc.IsAlive() || ShowClueEndTimeStamp == 0 || ShownClues.Count == 0) return; - - if (Utils.TimeStamp >= ShowClueEndTimeStamp) - { + case 1: + ShownClues[reader.ReadByte()] = reader.ReadString(); + break; + case 2: + ShowClueEndTimeStamp = long.Parse(reader.ReadString()); + break; + case 3: ShowClueEndTimeStamp = 0; ShownClues.Clear(); - Utils.SendRPC(CustomRPC.SyncRoleData, NoteKillerID, 3); - Utils.NotifyRoles(SpecifySeer: pc); - } - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool() && !shapeshifting) - OnPet(shapeshifter); - - return false; - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - ShownClues[reader.ReadByte()] = reader.ReadString(); - break; - case 2: - ShowClueEndTimeStamp = long.Parse(reader.ReadString()); - break; - case 3: - ShowClueEndTimeStamp = 0; - ShownClues.Clear(); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId == target.PlayerId && CustomRoles.NoteKiller.RoleExist() && seer.PlayerId != NoteKillerID && !meeting && RealNames.TryGetValue(seer.PlayerId, out var ownName)) - return string.Format(Translator.GetString("NoteKiller.OthersSelfSuffix"), CustomRoles.NoteKiller.ToColoredString(), ownName); - - if (seer.PlayerId != NoteKillerID || meeting || ShowClueEndTimeStamp == 0 || ShownClues.Count == 0) return string.Empty; - if (ShownClues.TryGetValue(target.PlayerId, out var clue)) return clue; - return seer.PlayerId == target.PlayerId ? $"\u25a9 ({ShowClueEndTimeStamp - Utils.TimeStamp}s)" : string.Empty; + break; } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId == target.PlayerId && CustomRoles.NoteKiller.RoleExist() && seer.PlayerId != NoteKillerID && !meeting && RealNames.TryGetValue(seer.PlayerId, out var ownName)) + return string.Format(Translator.GetString("NoteKiller.OthersSelfSuffix"), CustomRoles.NoteKiller.ToColoredString(), ownName); + + if (seer.PlayerId != NoteKillerID || meeting || ShowClueEndTimeStamp == 0 || ShownClues.Count == 0) return string.Empty; + if (ShownClues.TryGetValue(target.PlayerId, out var clue)) return clue; + return seer.PlayerId == target.PlayerId ? $"\u25a9 ({ShowClueEndTimeStamp - Utils.TimeStamp}s)" : string.Empty; + } } \ No newline at end of file diff --git a/Roles/Neutral/Opportunist.cs b/Roles/Neutral/Opportunist.cs index a049efd3b..227dd8d7f 100644 --- a/Roles/Neutral/Opportunist.cs +++ b/Roles/Neutral/Opportunist.cs @@ -1,59 +1,58 @@ using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Opportunist : RoleBase { - internal class Opportunist : RoleBase + public static bool On; + + public static OptionItem OppoImmuneToAttacksWhenTasksDone; + public static OptionItem CanVent; + private static OptionItem VentCooldown; + private static OptionItem MaxInVentTime; + public override bool IsEnable => On; + + public override void Add(byte playerId) { - public static bool On; + On = true; + } - public static OptionItem OppoImmuneToAttacksWhenTasksDone; - public static OptionItem CanVent; - private static OptionItem VentCooldown; - private static OptionItem MaxInVentTime; - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (!CanVent.GetBool()) return; - public override void Init() - { - On = false; - } + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (!CanVent.GetBool()) return; + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !OppoImmuneToAttacksWhenTasksDone.GetBool() || !target.Is(CustomRoles.Opportunist) || !target.AllTasksCompleted(); + } - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); - } + public override void SetupCustomOption() + { + Options.SetupRoleOptions(10100, TabGroup.NeutralRoles, CustomRoles.Opportunist); - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !OppoImmuneToAttacksWhenTasksDone.GetBool() || !target.Is(CustomRoles.Opportunist) || !target.AllTasksCompleted(); - } + OppoImmuneToAttacksWhenTasksDone = new BooleanOptionItem(10110, "ImmuneToAttacksWhenTasksDone", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Opportunist]); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(10100, TabGroup.NeutralRoles, CustomRoles.Opportunist); + CanVent = new BooleanOptionItem(10111, "CanVent", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Opportunist]); - OppoImmuneToAttacksWhenTasksDone = new BooleanOptionItem(10110, "ImmuneToAttacksWhenTasksDone", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Opportunist]); + VentCooldown = new FloatOptionItem(10112, "VentCooldown", new(0f, 120f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CanVent) + .SetValueFormat(OptionFormat.Seconds); - CanVent = new BooleanOptionItem(10111, "CanVent", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Opportunist]); + MaxInVentTime = new FloatOptionItem(10113, "MaxInVentTime", new(0f, 120f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(CanVent) + .SetValueFormat(OptionFormat.Seconds); - VentCooldown = new FloatOptionItem(10112, "VentCooldown", new(0f, 120f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CanVent) - .SetValueFormat(OptionFormat.Seconds); - - MaxInVentTime = new FloatOptionItem(10113, "MaxInVentTime", new(0f, 120f, 0.5f), 15f, TabGroup.NeutralRoles) - .SetParent(CanVent) - .SetValueFormat(OptionFormat.Seconds); - - Options.OverrideTasksData.Create(10114, TabGroup.NeutralRoles, CustomRoles.Opportunist); - } + Options.OverrideTasksData.Create(10114, TabGroup.NeutralRoles, CustomRoles.Opportunist); } } \ No newline at end of file diff --git a/Roles/Neutral/Patroller.cs b/Roles/Neutral/Patroller.cs index 3e14539ec..49e861082 100644 --- a/Roles/Neutral/Patroller.cs +++ b/Roles/Neutral/Patroller.cs @@ -3,159 +3,158 @@ using System.Collections.Generic; using System.Linq; using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Patroller : RoleBase { - public class Patroller : RoleBase + private const int Id = 645000; + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem DecreasedKillCooldown; + private static OptionItem IncreasedSpeed; + + private int Count; + private PlainShipRoom LastRoom; + private byte PatrollerId; + + private Dictionary RoomBoosts = []; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 645000; - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Patroller); - private static OptionItem KillCooldown; - private static OptionItem DecreasedKillCooldown; - private static OptionItem IncreasedSpeed; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Patroller]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; - private PlainShipRoom LastRoom; - private byte PatrollerId; + DecreasedKillCooldown = new FloatOptionItem(Id + 3, "DecreasedKillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Patroller]) + .SetValueFormat(OptionFormat.Seconds); - private Dictionary RoomBoosts = []; + IncreasedSpeed = new FloatOptionItem(Id + 4, "GamblerSpeedup", new(0.05f, 5f, 0.05f), 1.75f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Patroller]) + .SetValueFormat(OptionFormat.Multiplier); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + PatrollerId = playerId; + + LastRoom = null; + + RoomBoosts = ShipStatus.Instance.AllRooms + .Shuffle() + .Zip(Enum.GetValues()) + .ToDictionary(x => x.Second, x => x.First); + + playerId.SetAbilityUseLimit(1); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).GetPlainShipRoom() == RoomBoosts[Boost.Cooldown] ? DecreasedKillCooldown.GetFloat() : KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.inVent || pc.GetAbilityUseLimit() > 0 || pc.GetPlainShipRoom() == RoomBoosts[Boost.Vent]; + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || pc.GetPlainShipRoom() == RoomBoosts[Boost.Sabotage]; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + + PlainShipRoom room = Utils.GetPlayerById(id)?.GetPlainShipRoom(); + if (room == null) return; + + opt.SetVision(room == RoomBoosts[Boost.Vision]); + opt.SetInt(Int32OptionNames.KillDistance, room == RoomBoosts[Boost.Range] ? 2 : 0); + Main.AllPlayerSpeed[id] = room == RoomBoosts[Boost.Speed] ? IncreasedSpeed.GetFloat() : Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask) return; + + Count++; + if (Count < 20) return; + + Count = 0; + + PlainShipRoom room = pc.GetPlainShipRoom(); + if ((LastRoom != null && room != null && room == LastRoom) || (LastRoom == null && room == null)) return; + + LastRoom = room; + + if (room != null) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Patroller); + string roomName = Translator.GetString(room.RoomId.ToString()); - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Patroller]) - .SetValueFormat(OptionFormat.Seconds); - - DecreasedKillCooldown = new FloatOptionItem(Id + 3, "DecreasedKillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Patroller]) - .SetValueFormat(OptionFormat.Seconds); - - IncreasedSpeed = new FloatOptionItem(Id + 4, "GamblerSpeedup", new(0.05f, 5f, 0.05f), 1.75f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Patroller]) - .SetValueFormat(OptionFormat.Multiplier); + pc.Notify(RoomBoosts.Any(x => x.Value == room) + ? string.Format(Translator.GetString("PatrollerNotify"), roomName, Translator.GetString($"PatrollerBoost.{RoomBoosts.First(x => x.Value == room).Key}")) + : string.Format(Translator.GetString("PatrollerNotifyNoBoost"), roomName), 300f); } - public override void Init() - { - On = false; - } + pc.MarkDirtySettings(); - public override void Add(byte playerId) - { - On = true; - PatrollerId = playerId; + if (pc.IsLocalPlayer()) HudManager.Instance.SetHudActive(pc, pc.Data.Role, true); + } - LastRoom = null; + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (pc.GetPlainShipRoom() == RoomBoosts[Boost.Vent]) return; - RoomBoosts = ShipStatus.Instance.AllRooms - .Shuffle() - .Zip(Enum.GetValues()) - .ToDictionary(x => x.Second, x => x.First); + pc.RpcRemoveAbilityUse(); + } - playerId.SetAbilityUseLimit(1); - } + public override void AfterMeetingTasks() + { + PatrollerId.SetAbilityUseLimit(1); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Utils.GetPlayerById(id).GetPlainShipRoom() == RoomBoosts[Boost.Cooldown] ? DecreasedKillCooldown.GetFloat() : KillCooldown.GetFloat(); - } + public override void OnPet(PlayerControl pc) + { + IEnumerable s = RoomBoosts.Select(x => $"{Translator.GetString(x.Value.RoomId.ToString())} \u21e8 {Translator.GetString($"PatrollerBoost.{x.Key}")}"); + pc.Notify(string.Join('\n', s)); + } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.inVent || pc.GetAbilityUseLimit() > 0 || pc.GetPlainShipRoom() == RoomBoosts[Boost.Vent]; - } + public override bool OnVanish(PlayerControl pc) + { + OnPet(pc); + return false; + } - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || pc.GetPlainShipRoom() == RoomBoosts[Boost.Sabotage]; - } + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + OnPet(shapeshifter); + return false; + } - if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - - PlainShipRoom room = Utils.GetPlayerById(id)?.GetPlainShipRoom(); - if (room == null) return; - - opt.SetVision(room == RoomBoosts[Boost.Vision]); - opt.SetInt(Int32OptionNames.KillDistance, room == RoomBoosts[Boost.Range] ? 2 : 0); - Main.AllPlayerSpeed[id] = room == RoomBoosts[Boost.Speed] ? IncreasedSpeed.GetFloat() : Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask) return; - - Count++; - if (Count < 20) return; - - Count = 0; - - PlainShipRoom room = pc.GetPlainShipRoom(); - if ((LastRoom != null && room != null && room == LastRoom) || (LastRoom == null && room == null)) return; - - LastRoom = room; - - if (room != null) - { - string roomName = Translator.GetString(room.RoomId.ToString()); - - pc.Notify(RoomBoosts.Any(x => x.Value == room) - ? string.Format(Translator.GetString("PatrollerNotify"), roomName, Translator.GetString($"PatrollerBoost.{RoomBoosts.First(x => x.Value == room).Key}")) - : string.Format(Translator.GetString("PatrollerNotifyNoBoost"), roomName), 300f); - } - - pc.MarkDirtySettings(); - - if (pc.IsLocalPlayer()) HudManager.Instance.SetHudActive(pc, pc.Data.Role, true); - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (pc.GetPlainShipRoom() == RoomBoosts[Boost.Vent]) return; - - pc.RpcRemoveAbilityUse(); - } - - public override void AfterMeetingTasks() - { - PatrollerId.SetAbilityUseLimit(1); - } - - public override void OnPet(PlayerControl pc) - { - IEnumerable s = RoomBoosts.Select(x => $"{Translator.GetString(x.Value.RoomId.ToString())} \u21e8 {Translator.GetString($"PatrollerBoost.{x.Key}")}"); - pc.Notify(string.Join('\n', s)); - } - - public override bool OnVanish(PlayerControl pc) - { - OnPet(pc); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - - OnPet(shapeshifter); - return false; - } - - private enum Boost - { - Speed, - Range, - Cooldown, - Vision, - Vent, - Sabotage - } + private enum Boost + { + Speed, + Range, + Cooldown, + Vision, + Vent, + Sabotage } } \ No newline at end of file diff --git a/Roles/Neutral/Pelican.cs b/Roles/Neutral/Pelican.cs index e1864c868..52bea827c 100644 --- a/Roles/Neutral/Pelican.cs +++ b/Roles/Neutral/Pelican.cs @@ -6,272 +6,271 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Pelican : RoleBase { - public class Pelican : RoleBase + private const int Id = 12500; + private static List PlayerIdList = []; + private static Dictionary> EatenList = []; + private static readonly Dictionary OriginalSpeed = []; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem ImpostorVision; + + private int Count; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 12500; - private static List PlayerIdList = []; - private static Dictionary> EatenList = []; - private static readonly Dictionary OriginalSpeed = []; - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem ImpostorVision; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pelican); - private int Count; + KillCooldown = new FloatOptionItem(Id + 10, "PelicanKillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pelican]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pelican]); - public override void SetupCustomOption() + ImpostorVision = new BooleanOptionItem(Id + 12, "ImpostorVision", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pelican]); + } + + public override void Init() + { + PlayerIdList = []; + EatenList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + private static void SyncEatenList( /*byte playerId*/) + { + SendRPC(byte.MaxValue); + foreach (KeyValuePair> el in EatenList) SendRPC(el.Key); + } + + private static void SendRPC(byte playerId) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetPelicanEtenNum, SendOption.Reliable); + writer.Write(playerId); + + if (playerId != byte.MaxValue) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pelican); - - KillCooldown = new FloatOptionItem(Id + 10, "PelicanKillCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pelican]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pelican]); - - ImpostorVision = new BooleanOptionItem(Id + 12, "ImpostorVision", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pelican]); + writer.Write(EatenList[playerId].Count); + foreach (byte el in EatenList[playerId].ToArray()) writer.Write(el); } - public override void Init() - { - PlayerIdList = []; - EatenList = []; - } + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(ImpostorVision.GetBool()); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - private static void SyncEatenList( /*byte playerId*/) - { - SendRPC(byte.MaxValue); - foreach (KeyValuePair> el in EatenList) SendRPC(el.Key); - } - - private static void SendRPC(byte playerId) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetPelicanEtenNum, SendOption.Reliable); - writer.Write(playerId); - - if (playerId != byte.MaxValue) - { - writer.Write(EatenList[playerId].Count); - foreach (byte el in EatenList[playerId].ToArray()) writer.Write(el); - } - - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte playerId = reader.ReadByte(); - - if (playerId == byte.MaxValue) - EatenList.Clear(); - else - { - int eatenNum = reader.ReadInt32(); - EatenList.Remove(playerId); - List list = []; - for (var i = 0; i < eatenNum; i++) list.Add(reader.ReadByte()); - - EatenList.Add(playerId, list); - } - } - - public static bool IsEaten(PlayerControl pc, byte id) - { - return EatenList.ContainsKey(pc.PlayerId) && EatenList[pc.PlayerId].Contains(id); - } - - public static bool IsEaten(byte id) - { - foreach (KeyValuePair> el in EatenList) - if (el.Value.Contains(id)) - return true; - - return false; - } - - public static bool CanEat(PlayerControl pc, byte id) - { - if (!pc.Is(CustomRoles.Pelican) || GameStates.IsMeeting) return false; - - PlayerControl target = Utils.GetPlayerById(id); - return target != null && target.IsAlive() && !target.inVent && !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); - } - - public static Vector2 GetBlackRoomPS() - { - return Main.NormalOptions.MapId switch - { - 0 => new(-27f, 3.3f), // The Skeld - 1 => new(-11.4f, 8.2f), // MIRA HQ - 2 => new(42.6f, -19.9f), // Polus - 3 => new(27f, 3.3f), // dlekS ehT - 4 => new(-16.8f, -6.2f), // Airship - 5 => new(9.6f, 23.2f), // The Fungle - _ => throw new ArgumentOutOfRangeException(Main.NormalOptions.MapId.ToString(), "Unsupported map") - }; - } - - public override string GetProgressText(byte playerId, bool comms) - { - PlayerControl player = Utils.GetPlayerById(playerId); - if (player == null) return "Invalid"; - - var eatenNum = 0; - if (EatenList.TryGetValue(playerId, out List value)) eatenNum = value.Count; - - return Utils.ColorString(eatenNum < 1 ? Color.gray : Utils.GetRoleColor(CustomRoles.Pelican), $"({eatenNum})"); - } - - public static void EatPlayer(PlayerControl pc, PlayerControl target) - { - if (pc == null || target == null || !CanEat(pc, target.PlayerId)) return; - - if (!EatenList.ContainsKey(pc.PlayerId)) EatenList.Add(pc.PlayerId, []); - - EatenList[pc.PlayerId].Add(target.PlayerId); - - SyncEatenList( /*pc.PlayerId*/); - - OriginalSpeed.Remove(target.PlayerId); - OriginalSpeed.Add(target.PlayerId, Main.AllPlayerSpeed[target.PlayerId]); - - target.TP(GetBlackRoomPS()); - Main.AllPlayerSpeed[target.PlayerId] = 0.5f; - ReportDeadBodyPatch.CanReport[target.PlayerId] = false; - target.MarkDirtySettings(); - - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: pc); - Logger.Info($"{pc.GetRealName()} ate {target.GetRealName()}", "Pelican"); - } - - public override void OnReportDeadBody() - { - foreach (KeyValuePair> pc in EatenList) - { - foreach (byte tar in pc.Value) - { - PlayerControl target = Utils.GetPlayerById(tar); - PlayerControl killer = Utils.GetPlayerById(pc.Key); - if (killer == null || target == null) continue; - - Main.AllPlayerSpeed[tar] = Main.AllPlayerSpeed[tar] - 0.5f + OriginalSpeed[tar]; - ReportDeadBodyPatch.CanReport[tar] = true; - target.RpcExileV2(); - target.SetRealKiller(killer); - Main.PlayerStates[tar].deathReason = PlayerState.DeathReason.Eaten; - Main.PlayerStates[tar].SetDead(); - Utils.AfterPlayerDeathTasks(target, true); - Logger.Info($"{killer.GetRealName()} killed {target.GetRealName()}", "Pelican"); - } - } + public static void ReceiveRPC(MessageReader reader) + { + byte playerId = reader.ReadByte(); + if (playerId == byte.MaxValue) EatenList.Clear(); - SyncEatenList( /*byte.MaxValue*/); - } - - public static void OnPelicanDied(byte pc) + else { - if (!EatenList.TryGetValue(pc, out List value)) return; + int eatenNum = reader.ReadInt32(); + EatenList.Remove(playerId); + List list = []; + for (var i = 0; i < eatenNum; i++) list.Add(reader.ReadByte()); - foreach (byte tar in value) - { - PlayerControl target = Utils.GetPlayerById(tar); - PlayerControl player = Utils.GetPlayerById(pc); - if (player == null || target == null) continue; - - target.TP(player); - Main.AllPlayerSpeed[tar] = Main.AllPlayerSpeed[tar] - 0.5f + OriginalSpeed[tar]; - ReportDeadBodyPatch.CanReport[tar] = true; - target.MarkDirtySettings(); - RPC.PlaySoundRPC(tar, Sounds.TaskComplete); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: player); - Logger.Info($"{Utils.GetPlayerById(pc).GetRealName()} died, {target.GetRealName()} is back in-game", "Pelican"); - } - - EatenList.Remove(pc); - SyncEatenList( /*pc*/); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask) - { - if (EatenList.Count > 0) - { - EatenList.Clear(); - SyncEatenList( /*byte.MaxValue*/); - } - - return; - } - - if (!IsEnable) return; - - Count--; - if (Count > 0) return; - - Count = 20; - - if (!EatenList.TryGetValue(pc.PlayerId, out List list)) return; - - foreach (byte tar in list) - { - PlayerControl target = Utils.GetPlayerById(tar); - if (target == null) continue; - - Vector2 pos = GetBlackRoomPS(); - float dis = Vector2.Distance(pos, target.Pos()); - if (dis < 2f) continue; - - target.TP(pos, log: false); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: pc); - } - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (CanEat(killer, target.PlayerId)) - { - EatPlayer(killer, target); - killer.SetKillCooldown(); - killer.RPCPlayCustomSound("Eat"); - target.RPCPlayCustomSound("Eat"); - } - - return false; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("PelicanButtonText")); + EatenList.Add(playerId, list); } } + + public static bool IsEaten(PlayerControl pc, byte id) + { + return EatenList.ContainsKey(pc.PlayerId) && EatenList[pc.PlayerId].Contains(id); + } + + public static bool IsEaten(byte id) + { + foreach (KeyValuePair> el in EatenList) + if (el.Value.Contains(id)) + return true; + + return false; + } + + public static bool CanEat(PlayerControl pc, byte id) + { + if (!pc.Is(CustomRoles.Pelican) || GameStates.IsMeeting) return false; + + PlayerControl target = Utils.GetPlayerById(id); + return target != null && target.IsAlive() && !target.inVent && !Medic.ProtectList.Contains(target.PlayerId) && !target.Is(CustomRoles.GM) && !IsEaten(pc, id) && !IsEaten(id); + } + + public static Vector2 GetBlackRoomPS() + { + return Main.NormalOptions.MapId switch + { + 0 => new(-27f, 3.3f), // The Skeld + 1 => new(-11.4f, 8.2f), // MIRA HQ + 2 => new(42.6f, -19.9f), // Polus + 3 => new(27f, 3.3f), // dlekS ehT + 4 => new(-16.8f, -6.2f), // Airship + 5 => new(9.6f, 23.2f), // The Fungle + _ => throw new ArgumentOutOfRangeException(Main.NormalOptions.MapId.ToString(), "Unsupported map") + }; + } + + public override string GetProgressText(byte playerId, bool comms) + { + PlayerControl player = Utils.GetPlayerById(playerId); + if (player == null) return "Invalid"; + + var eatenNum = 0; + if (EatenList.TryGetValue(playerId, out List value)) eatenNum = value.Count; + + return Utils.ColorString(eatenNum < 1 ? Color.gray : Utils.GetRoleColor(CustomRoles.Pelican), $"({eatenNum})"); + } + + public static void EatPlayer(PlayerControl pc, PlayerControl target) + { + if (pc == null || target == null || !CanEat(pc, target.PlayerId)) return; + + if (!EatenList.ContainsKey(pc.PlayerId)) EatenList.Add(pc.PlayerId, []); + + EatenList[pc.PlayerId].Add(target.PlayerId); + + SyncEatenList( /*pc.PlayerId*/); + + OriginalSpeed.Remove(target.PlayerId); + OriginalSpeed.Add(target.PlayerId, Main.AllPlayerSpeed[target.PlayerId]); + + target.TP(GetBlackRoomPS()); + Main.AllPlayerSpeed[target.PlayerId] = 0.5f; + ReportDeadBodyPatch.CanReport[target.PlayerId] = false; + target.MarkDirtySettings(); + + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: pc); + Logger.Info($"{pc.GetRealName()} ate {target.GetRealName()}", "Pelican"); + } + + public override void OnReportDeadBody() + { + foreach (KeyValuePair> pc in EatenList) + { + foreach (byte tar in pc.Value) + { + PlayerControl target = Utils.GetPlayerById(tar); + PlayerControl killer = Utils.GetPlayerById(pc.Key); + if (killer == null || target == null) continue; + + Main.AllPlayerSpeed[tar] = Main.AllPlayerSpeed[tar] - 0.5f + OriginalSpeed[tar]; + ReportDeadBodyPatch.CanReport[tar] = true; + target.RpcExileV2(); + target.SetRealKiller(killer); + Main.PlayerStates[tar].deathReason = PlayerState.DeathReason.Eaten; + Main.PlayerStates[tar].SetDead(); + Utils.AfterPlayerDeathTasks(target, true); + Logger.Info($"{killer.GetRealName()} killed {target.GetRealName()}", "Pelican"); + } + } + + EatenList.Clear(); + SyncEatenList( /*byte.MaxValue*/); + } + + public static void OnPelicanDied(byte pc) + { + if (!EatenList.TryGetValue(pc, out List value)) return; + + foreach (byte tar in value) + { + PlayerControl target = Utils.GetPlayerById(tar); + PlayerControl player = Utils.GetPlayerById(pc); + if (player == null || target == null) continue; + + target.TP(player); + Main.AllPlayerSpeed[tar] = Main.AllPlayerSpeed[tar] - 0.5f + OriginalSpeed[tar]; + ReportDeadBodyPatch.CanReport[tar] = true; + target.MarkDirtySettings(); + RPC.PlaySoundRPC(tar, Sounds.TaskComplete); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: player); + Logger.Info($"{Utils.GetPlayerById(pc).GetRealName()} died, {target.GetRealName()} is back in-game", "Pelican"); + } + + EatenList.Remove(pc); + SyncEatenList( /*pc*/); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask) + { + if (EatenList.Count > 0) + { + EatenList.Clear(); + SyncEatenList( /*byte.MaxValue*/); + } + + return; + } + + if (!IsEnable) return; + + Count--; + if (Count > 0) return; + + Count = 20; + + if (!EatenList.TryGetValue(pc.PlayerId, out List list)) return; + + foreach (byte tar in list) + { + PlayerControl target = Utils.GetPlayerById(tar); + if (target == null) continue; + + Vector2 pos = GetBlackRoomPS(); + float dis = Vector2.Distance(pos, target.Pos()); + if (dis < 2f) continue; + + target.TP(pos, log: false); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: pc); + } + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (CanEat(killer, target.PlayerId)) + { + EatPlayer(killer, target); + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("Eat"); + target.RPCPlayCustomSound("Eat"); + } + + return false; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("PelicanButtonText")); + } } \ No newline at end of file diff --git a/Roles/Neutral/Phantasm.cs b/Roles/Neutral/Phantasm.cs index a515536b7..9e497f7ea 100644 --- a/Roles/Neutral/Phantasm.cs +++ b/Roles/Neutral/Phantasm.cs @@ -1,29 +1,28 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Phantasm : RoleBase { - internal class Phantasm : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(11400, TabGroup.NeutralRoles, CustomRoles.Phantasm); - public override void SetupCustomOption() - { - SetupRoleOptions(11400, TabGroup.NeutralRoles, CustomRoles.Phantasm); + PhantomCanVent = new BooleanOptionItem(11410, "CanVent", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Phantasm]); - PhantomCanVent = new BooleanOptionItem(11410, "CanVent", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Phantasm]); + PhantomSnatchesWin = new BooleanOptionItem(11411, "SnatchesWin", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Phantasm]); - PhantomSnatchesWin = new BooleanOptionItem(11411, "SnatchesWin", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Phantasm]); + PhantomCanGuess = new BooleanOptionItem(11412, "CanGuess", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Phantasm]); - PhantomCanGuess = new BooleanOptionItem(11412, "CanGuess", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Phantasm]); - - OverrideTasksData.Create(11413, TabGroup.NeutralRoles, CustomRoles.Phantasm); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + OverrideTasksData.Create(11413, TabGroup.NeutralRoles, CustomRoles.Phantasm); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Pickpocket.cs b/Roles/Neutral/Pickpocket.cs index 145bd4eae..2041619a2 100644 --- a/Roles/Neutral/Pickpocket.cs +++ b/Roles/Neutral/Pickpocket.cs @@ -2,61 +2,60 @@ using System.Collections.Generic; using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Pickpocket : RoleBase { - public class Pickpocket : RoleBase + private const int Id = 12600; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + public static OptionItem VotesPerKill; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 12600; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pickpocket); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - public static OptionItem VotesPerKill; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pickpocket); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]) - .SetValueFormat(OptionFormat.Seconds); + VotesPerKill = new FloatOptionItem(Id + 12, "VotesPerKill", new(0.1f, 10f, 0.1f), 0.3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); + } - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); + public override void Init() + { + PlayerIdList = []; + } - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - VotesPerKill = new FloatOptionItem(Id + 12, "VotesPerKill", new(0.1f, 10f, 0.1f), 0.3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Pickpocket]); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - public override void Init() - { - PlayerIdList = []; - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); } } \ No newline at end of file diff --git a/Roles/Neutral/PlagueBearer.cs b/Roles/Neutral/PlagueBearer.cs index a460ef80a..33311cab7 100644 --- a/Roles/Neutral/PlagueBearer.cs +++ b/Roles/Neutral/PlagueBearer.cs @@ -5,184 +5,183 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class PlagueBearer : RoleBase { - public class PlagueBearer : RoleBase + private const int Id = 26000; + public static List PlayerIdList = []; + public static Dictionary> PlaguedList = []; + public static Dictionary PlagueBearerCD = []; + public static List PestilenceList = []; + + public static OptionItem PlagueBearerCDOpt; + public static OptionItem PestilenceCDOpt; + public static OptionItem PestilenceCanVent; + public static OptionItem PestilenceHasImpostorVision; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 26000; - public static List PlayerIdList = []; - public static Dictionary> PlaguedList = []; - public static Dictionary PlagueBearerCD = []; - public static List PestilenceList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.PlagueBearer); - public static OptionItem PlagueBearerCDOpt; - public static OptionItem PestilenceCDOpt; - public static OptionItem PestilenceCanVent; - public static OptionItem PestilenceHasImpostorVision; + PlagueBearerCDOpt = new FloatOptionItem(Id + 10, "PlagueBearerCD", new(0f, 180f, 0.5f), 17.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + PestilenceCDOpt = new FloatOptionItem(Id + 11, "PestilenceCD", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.PlagueBearer); + PestilenceCanVent = new BooleanOptionItem(Id + 12, "PestilenceCanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]); - PlagueBearerCDOpt = new FloatOptionItem(Id + 10, "PlagueBearerCD", new(0f, 180f, 0.5f), 17.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]) - .SetValueFormat(OptionFormat.Seconds); - - PestilenceCDOpt = new FloatOptionItem(Id + 11, "PestilenceCD", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]) - .SetValueFormat(OptionFormat.Seconds); - - PestilenceCanVent = new BooleanOptionItem(Id + 12, "PestilenceCanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]); - - PestilenceHasImpostorVision = new BooleanOptionItem(Id + 13, "PestilenceHasImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]); - } - - public override void Init() - { - PlayerIdList = []; - PlaguedList = []; - PlagueBearerCD = []; - PestilenceList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - PlagueBearerCD.Add(playerId, PlagueBearerCDOpt.GetFloat()); - PlaguedList[playerId] = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = PlagueBearerCD[id]; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public static bool IsPlagued(byte pc, byte target) - { - return PlaguedList.TryGetValue(pc, out List x) && x.Contains(target); - } - - public static void SendRPC(PlayerControl player, PlayerControl target) - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetPlaguedPlayer, SendOption.Reliable); //RPCによる同期 - writer.Write(player.PlayerId); - writer.Write(target.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte PlagueBearerId = reader.ReadByte(); - byte PlaguedId = reader.ReadByte(); - PlaguedList[PlagueBearerId].Add(PlaguedId); - } - - public static (int Plagued, int All) PlaguedPlayerCount(byte playerId) - { - int plagued = 0, all = 0; - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (pc.PlayerId == playerId) continue; - - all++; - if (IsPlagued(playerId, pc.PlayerId)) plagued++; - } - - return (plagued, all); - } - - public static bool IsPlaguedAll(PlayerControl player) - { - if (!player.Is(CustomRoles.PlagueBearer)) return false; - - (int plagued, int all) = PlaguedPlayerCount(player.PlayerId); - return plagued >= all; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (IsPlagued(killer.PlayerId, target.PlayerId)) - { - killer.Notify(GetString("PlagueBearerAlreadyPlagued")); - return false; - } - - PlaguedList[killer.PlayerId].Add(target.PlayerId); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - Logger.Msg($"kill cooldown {PlagueBearerCD[killer.PlayerId]}", "PlagueBearer"); - return false; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(GetString("InfectiousKillButtonText")); - } + PestilenceHasImpostorVision = new BooleanOptionItem(Id + 13, "PestilenceHasImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.PlagueBearer]); } - public class Pestilence : RoleBase + public override void Init() { - public static bool On; - public override bool IsEnable => On; + PlayerIdList = []; + PlaguedList = []; + PlagueBearerCD = []; + PestilenceList = []; + } - public override void SetupCustomOption() { } + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + PlagueBearerCD.Add(playerId, PlagueBearerCDOpt.GetFloat()); + PlaguedList[playerId] = []; + } - public override void Add(byte playerId) + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = PlagueBearerCD[id]; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public static bool IsPlagued(byte pc, byte target) + { + return PlaguedList.TryGetValue(pc, out List x) && x.Contains(target); + } + + public static void SendRPC(PlayerControl player, PlayerControl target) + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetPlaguedPlayer, SendOption.Reliable); //RPCによる同期 + writer.Write(player.PlayerId); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlagueBearerId = reader.ReadByte(); + byte PlaguedId = reader.ReadByte(); + PlaguedList[PlagueBearerId].Add(PlaguedId); + } + + public static (int Plagued, int All) PlaguedPlayerCount(byte playerId) + { + int plagued = 0, all = 0; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - On = true; + if (pc.PlayerId == playerId) continue; + + all++; + if (IsPlagued(playerId, pc.PlayerId)) plagued++; } - public override void Init() - { - On = false; - } + return (plagued, all); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = PlagueBearer.PestilenceCDOpt.GetFloat(); - } + public static bool IsPlaguedAll(PlayerControl player) + { + if (!player.Is(CustomRoles.PlagueBearer)) return false; - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return PlagueBearer.PestilenceCanVent.GetBool(); - } + (int plagued, int all) = PlaguedPlayerCount(player.PlayerId); + return plagued >= all; + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (IsPlagued(killer.PlayerId, target.PlayerId)) { - opt.SetVision(PlagueBearer.PestilenceHasImpostorVision.GetBool()); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (base.OnCheckMurder(killer, target)) killer.Kill(target); - + killer.Notify(GetString("PlagueBearerAlreadyPlagued")); return false; } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null) return false; + PlaguedList[killer.PlayerId].Add(target.PlayerId); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + Logger.Msg($"kill cooldown {PlagueBearerCD[killer.PlayerId]}", "PlagueBearer"); + return false; + } - killer.SetRealKiller(target); - target.Kill(killer); + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(GetString("InfectiousKillButtonText")); + } +} - if (target.IsLocalPlayer()) - Achievements.Type.YoureTooLate.Complete(); +public class Pestilence : RoleBase +{ + public static bool On; + public override bool IsEnable => On; - return false; - } + public override void SetupCustomOption() { } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = PlagueBearer.PestilenceCDOpt.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return PlagueBearer.PestilenceCanVent.GetBool(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(PlagueBearer.PestilenceHasImpostorVision.GetBool()); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (base.OnCheckMurder(killer, target)) killer.Kill(target); + + return false; + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null) return false; + + killer.SetRealKiller(target); + target.Kill(killer); + + if (target.IsLocalPlayer()) + Achievements.Type.YoureTooLate.Complete(); + + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/PlagueDoctor.cs b/Roles/Neutral/PlagueDoctor.cs index 53dc7ae09..4f27065a3 100644 --- a/Roles/Neutral/PlagueDoctor.cs +++ b/Roles/Neutral/PlagueDoctor.cs @@ -6,346 +6,345 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class PlagueDoctor : RoleBase { - public class PlagueDoctor : RoleBase + private const int Id = 641700; + private static List PlayerIdList = []; + + private static OptionItem OptionInfectLimit; + private static OptionItem OptionInfectWhenKilled; + private static OptionItem OptionInfectTime; + private static OptionItem OptionInfectDistance; + private static OptionItem OptionInfectInactiveTime; + private static OptionItem OptionInfectCanInfectSelf; + private static OptionItem OptionInfectCanInfectVent; + + private static int InfectLimit; + private static bool InfectWhenKilled; + private static float InfectTime; + private static float InfectDistance; + private static float InfectInactiveTime; + private static bool CanInfectSelf; + private static bool CanInfectVent; + + private static Dictionary InfectInfos; + private static bool InfectActive; + private static bool LateCheckWin; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 641700; - private static List PlayerIdList = []; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.PlagueDoctor); - private static OptionItem OptionInfectLimit; - private static OptionItem OptionInfectWhenKilled; - private static OptionItem OptionInfectTime; - private static OptionItem OptionInfectDistance; - private static OptionItem OptionInfectInactiveTime; - private static OptionItem OptionInfectCanInfectSelf; - private static OptionItem OptionInfectCanInfectVent; + OptionInfectLimit = new IntegerOptionItem(Id + 10, "PlagueDoctorInfectLimit", new(1, 3, 1), 1, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]) + .SetValueFormat(OptionFormat.Times); - private static int InfectLimit; - private static bool InfectWhenKilled; - private static float InfectTime; - private static float InfectDistance; - private static float InfectInactiveTime; - private static bool CanInfectSelf; - private static bool CanInfectVent; + OptionInfectWhenKilled = new BooleanOptionItem(Id + 11, "PlagueDoctorInfectWhenKilled", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); - private static Dictionary InfectInfos; - private static bool InfectActive; - private static bool LateCheckWin; + OptionInfectTime = new FloatOptionItem(Id + 12, "PlagueDoctorInfectTime", new(3f, 20f, 1f), 8f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + OptionInfectDistance = new FloatOptionItem(Id + 13, "PlagueDoctorInfectDistance", new(0.5f, 2f, 0.25f), 1.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); - public override void SetupCustomOption() + OptionInfectInactiveTime = new FloatOptionItem(Id + 14, "PlagueDoctorInfectInactiveTime", new(0.5f, 10f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]) + .SetValueFormat(OptionFormat.Seconds); + + OptionInfectCanInfectSelf = new BooleanOptionItem(Id + 15, "PlagueDoctorCanInfectSelf", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); + + OptionInfectCanInfectVent = new BooleanOptionItem(Id + 16, "PlagueDoctorCanInfectVent", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); + } + + public override void Init() + { + PlayerIdList = []; + InfectInfos = []; + } + + public override void Add(byte playerId) + { + InfectLimit = OptionInfectLimit.GetInt(); + InfectWhenKilled = OptionInfectWhenKilled.GetBool(); + InfectTime = OptionInfectTime.GetFloat(); + InfectDistance = OptionInfectDistance.GetFloat(); + InfectInactiveTime = OptionInfectInactiveTime.GetFloat(); + CanInfectSelf = OptionInfectCanInfectSelf.GetBool(); + CanInfectVent = OptionInfectCanInfectVent.GetBool(); + + playerId.SetAbilityUseLimit(InfectLimit); + + InfectActive = true; + + if (Main.NormalOptions.MapId == 4) + // Fixed airship respawn selection delay + InfectInactiveTime += 5f; + + PlayerIdList.Add(playerId); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.GetAbilityUseLimit() != 0; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override string GetProgressText(byte id, bool comms) + { + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor).ShadeColor(0.25f), $"({id.GetAbilityUseLimit()})"); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(false); + } + + private bool CanInfect(PlayerControl player) + { + if (!IsEnable) return false; + + // Not a plague doctor, or capable of self-infection and infected person created + return player.PlayerId != PlayerIdList[0] || (CanInfectSelf && player.GetAbilityUseLimit() == 0); + } + + private void SendRPC(byte targetId, float rate) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncPlagueDoctor, SendOption.Reliable); + writer.Write(targetId); + writer.Write(rate); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte targetId = reader.ReadByte(); + float rate = reader.ReadSingle(); + InfectInfos[targetId] = rate; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() > 0) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.PlagueDoctor); - - OptionInfectLimit = new IntegerOptionItem(Id + 10, "PlagueDoctorInfectLimit", new(1, 3, 1), 1, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]) - .SetValueFormat(OptionFormat.Times); - - OptionInfectWhenKilled = new BooleanOptionItem(Id + 11, "PlagueDoctorInfectWhenKilled", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); - - OptionInfectTime = new FloatOptionItem(Id + 12, "PlagueDoctorInfectTime", new(3f, 20f, 1f), 8f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]) - .SetValueFormat(OptionFormat.Seconds); - - OptionInfectDistance = new FloatOptionItem(Id + 13, "PlagueDoctorInfectDistance", new(0.5f, 2f, 0.25f), 1.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); - - OptionInfectInactiveTime = new FloatOptionItem(Id + 14, "PlagueDoctorInfectInactiveTime", new(0.5f, 10f, 0.5f), 5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]) - .SetValueFormat(OptionFormat.Seconds); - - OptionInfectCanInfectSelf = new BooleanOptionItem(Id + 15, "PlagueDoctorCanInfectSelf", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); - - OptionInfectCanInfectVent = new BooleanOptionItem(Id + 16, "PlagueDoctorCanInfectVent", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.PlagueDoctor]); + killer.RpcRemoveAbilityUse(); + killer.RpcGuardAndKill(target); + DirectInfect(target); } - public override void Init() + return false; + } + + public static void OnPDdeath(PlayerControl killer, PlayerControl target) + { + if (Main.PlayerStates[target.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return; + + if (InfectWhenKilled && target.GetAbilityUseLimit() > 0) { - PlayerIdList = []; - InfectInfos = []; + target.SetAbilityUseLimit(0); + pd.DirectInfect(killer); } + } - public override void Add(byte playerId) + public static void OnAnyMurder() + { + // You may win if an uninfected person dies. + LateCheckWin = true; + } + + public override void OnReportDeadBody() + { + InfectActive = false; + } + + public override void OnCheckPlayerPosition(PlayerControl player) + { + if (!IsEnable) return; + + if (!AmongUsClient.Instance.AmHost) return; + + if (!GameStates.IsInTask) return; + + if (LateCheckWin) { - InfectLimit = OptionInfectLimit.GetInt(); - InfectWhenKilled = OptionInfectWhenKilled.GetBool(); - InfectTime = OptionInfectTime.GetFloat(); - InfectDistance = OptionInfectDistance.GetFloat(); - InfectInactiveTime = OptionInfectInactiveTime.GetFloat(); - CanInfectSelf = OptionInfectCanInfectSelf.GetBool(); - CanInfectVent = OptionInfectCanInfectVent.GetBool(); - - playerId.SetAbilityUseLimit(InfectLimit); - - InfectActive = true; - - if (Main.NormalOptions.MapId == 4) - // Fixed airship respawn selection delay - InfectInactiveTime += 5f; - - PlayerIdList.Add(playerId); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.GetAbilityUseLimit() != 0; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override string GetProgressText(byte id, bool comms) - { - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor).ShadeColor(0.25f), $"({id.GetAbilityUseLimit()})"); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(false); - } - - private bool CanInfect(PlayerControl player) - { - if (!IsEnable) return false; - - // Not a plague doctor, or capable of self-infection and infected person created - return player.PlayerId != PlayerIdList[0] || (CanInfectSelf && player.GetAbilityUseLimit() == 0); - } - - private void SendRPC(byte targetId, float rate) - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncPlagueDoctor, SendOption.Reliable); - writer.Write(targetId); - writer.Write(rate); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte targetId = reader.ReadByte(); - float rate = reader.ReadSingle(); - InfectInfos[targetId] = rate; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() > 0) - { - killer.RpcRemoveAbilityUse(); - killer.RpcGuardAndKill(target); - DirectInfect(target); - } - - return false; - } - - public static void OnPDdeath(PlayerControl killer, PlayerControl target) - { - if (Main.PlayerStates[target.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return; - - if (InfectWhenKilled && target.GetAbilityUseLimit() > 0) - { - target.SetAbilityUseLimit(0); - pd.DirectInfect(killer); - } - } - - public static void OnAnyMurder() - { - // You may win if an uninfected person dies. - LateCheckWin = true; - } - - public override void OnReportDeadBody() - { - InfectActive = false; - } - - public override void OnCheckPlayerPosition(PlayerControl player) - { - if (!IsEnable) return; - - if (!AmongUsClient.Instance.AmHost) return; - - if (!GameStates.IsInTask) return; - - if (LateCheckWin) - { - // After hanging/killing, check the victory conditions just to be sure. - LateCheckWin = false; - CheckWin(); - } - - if (!player.IsAlive() || !InfectActive) return; - - if (InfectInfos.TryGetValue(player.PlayerId, out float rate) && rate >= 100) - { - // In case of an infected person - var changed = false; - bool inVent = player.inVent; - List updates = []; - - foreach (PlayerControl target in Main.AllAlivePlayerControls) - { - // Plague doctors are excluded if they cannot infect themselves. - if (!CanInfect(target)) continue; - - // Excluded if inside or outside the vent - if (!CanInfectVent && target.inVent != inVent) continue; - - InfectInfos.TryGetValue(target.PlayerId, out float oldRate); - // Exclude infected people - if (oldRate >= 100) continue; - - // Exclude players outside the range - float distance = Vector3.Distance(player.transform.position, target.transform.position); - if (distance > InfectDistance) continue; - - float newRate = oldRate + (Time.fixedDeltaTime / InfectTime * 100); - newRate = Math.Clamp(newRate, 0, 100); - InfectInfos[target.PlayerId] = newRate; - - if ((oldRate < 50 && newRate >= 50) || newRate >= 100) - { - changed = true; - updates.Add(target); - Logger.Info($"InfectRate [{target.GetNameWithRole()}]: {newRate}%", "PlagueDoctor"); - SendRPC(target.PlayerId, newRate); - } - } - - if (changed) - { - //If someone is infected - CheckWin(); - foreach (PlayerControl x in updates) Utils.NotifyRoles(SpecifyTarget: x); - } - } - } - - public override void AfterMeetingTasks() - { - // You may win if a non-infected person is hanged. - LateCheckWin = true; - - LateTask.New(() => - { - Logger.Info("Infect Active", "PlagueDoctor"); - InfectActive = true; - }, - InfectInactiveTime, "ResetInfectInactiveTime"); - } - - public static string GetMarkOthers(PlayerControl seer, PlayerControl seen = null) - { - if (Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; - - seen ??= seer; - if (!pd.CanInfect(seen)) return string.Empty; - - if (!seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; - - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor), GetInfectRateCharactor(seen, pd)); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId && seer.IsAlive()) return string.Empty; - - if (!seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; - - if (!hud && seer.IsModClient()) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; - - var str = new StringBuilder(40); - - foreach (PlayerControl player in Main.AllAlivePlayerControls) - if (!player.Is(CustomRoles.PlagueDoctor)) - str.Append(GetInfectRateCharactor(player, pd)); - - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor), str.ToString()); - } - - public static bool IsInfected(byte playerId) - { - InfectInfos.TryGetValue(playerId, out float rate); - return rate >= 100; - } - - public static string GetInfectRateCharactor(PlayerControl player, PlagueDoctor pd) - { - if (!pd.IsEnable) return string.Empty; - - if (!pd.CanInfect(player) || !player.IsAlive()) return string.Empty; - - if (!InfectInfos.TryGetValue(player.PlayerId, out float rate)) return string.Empty; - - return rate switch - { - < 50 => "\u2581", - >= 50 and < 100 => "\u2584", - >= 100 => "\u2588", - _ => string.Empty - }; - } - - private void DirectInfect(PlayerControl player) - { - if (PlayerIdList.Count == 0 || player == null) return; - - Logger.Info($"InfectRate [{player.GetNameWithRole()}]: 100%", "PlagueDoctor"); - InfectInfos[player.PlayerId] = 100; - SendRPC(player.PlayerId, 100); - Utils.NotifyRoles(SpecifySeer: player); - Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(PlayerIdList[0])); + // After hanging/killing, check the victory conditions just to be sure. + LateCheckWin = false; CheckWin(); } - private void CheckWin() + if (!player.IsAlive() || !InfectActive) return; + + if (InfectInfos.TryGetValue(player.PlayerId, out float rate) && rate >= 100) { - if (!IsEnable) return; + // In case of an infected person + var changed = false; + bool inVent = player.inVent; + List updates = []; - if (!AmongUsClient.Instance.AmHost) return; - - // Invalid if someone's victory is being processed - if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; - - if (Main.AllAlivePlayerControls.All(p => p.Is(CustomRoles.PlagueDoctor) || IsInfected(p.PlayerId))) + foreach (PlayerControl target in Main.AllAlivePlayerControls) { - InfectActive = false; + // Plague doctors are excluded if they cannot infect themselves. + if (!CanInfect(target)) continue; - PlayerControl pd = Main.AllPlayerControls.FirstOrDefault(x => x.Is(CustomRoles.PlagueDoctor)); + // Excluded if inside or outside the vent + if (!CanInfectVent && target.inVent != inVent) continue; - foreach (PlayerControl player in Main.AllAlivePlayerControls) + InfectInfos.TryGetValue(target.PlayerId, out float oldRate); + // Exclude infected people + if (oldRate >= 100) continue; + + // Exclude players outside the range + float distance = Vector3.Distance(player.transform.position, target.transform.position); + if (distance > InfectDistance) continue; + + float newRate = oldRate + (Time.fixedDeltaTime / InfectTime * 100); + newRate = Math.Clamp(newRate, 0, 100); + InfectInfos[target.PlayerId] = newRate; + + if ((oldRate < 50 && newRate >= 50) || newRate >= 100) { - if (player.Is(CustomRoles.PlagueDoctor)) continue; - - player.Suicide(PlayerState.DeathReason.Curse, pd); + changed = true; + updates.Add(target); + Logger.Info($"InfectRate [{target.GetNameWithRole()}]: {newRate}%", "PlagueDoctor"); + SendRPC(target.PlayerId, newRate); } + } - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.PlagueDoctor); - foreach (PlayerControl plagueDoctor in Main.AllPlayerControls.Where(p => p.Is(CustomRoles.PlagueDoctor)).ToArray()) CustomWinnerHolder.WinnerIds.Add(plagueDoctor.PlayerId); + if (changed) + { + //If someone is infected + CheckWin(); + foreach (PlayerControl x in updates) Utils.NotifyRoles(SpecifyTarget: x); } } + } - public override void SetButtonTexts(HudManager hud, byte id) + public override void AfterMeetingTasks() + { + // You may win if a non-infected person is hanged. + LateCheckWin = true; + + LateTask.New(() => + { + Logger.Info("Infect Active", "PlagueDoctor"); + InfectActive = true; + }, + InfectInactiveTime, "ResetInfectInactiveTime"); + } + + public static string GetMarkOthers(PlayerControl seer, PlayerControl seen = null) + { + if (Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; + + seen ??= seer; + if (!pd.CanInfect(seen)) return string.Empty; + + if (!seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; + + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor), GetInfectRateCharactor(seen, pd)); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId && seer.IsAlive()) return string.Empty; + + if (!seer.Is(CustomRoles.PlagueDoctor) && seer.IsAlive()) return string.Empty; + + if (!hud && seer.IsModClient()) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not PlagueDoctor { IsEnable: true } pd) return string.Empty; + + var str = new StringBuilder(40); + + foreach (PlayerControl player in Main.AllAlivePlayerControls) + if (!player.Is(CustomRoles.PlagueDoctor)) + str.Append(GetInfectRateCharactor(player, pd)); + + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.PlagueDoctor), str.ToString()); + } + + public static bool IsInfected(byte playerId) + { + InfectInfos.TryGetValue(playerId, out float rate); + return rate >= 100; + } + + public static string GetInfectRateCharactor(PlayerControl player, PlagueDoctor pd) + { + if (!pd.IsEnable) return string.Empty; + + if (!pd.CanInfect(player) || !player.IsAlive()) return string.Empty; + + if (!InfectInfos.TryGetValue(player.PlayerId, out float rate)) return string.Empty; + + return rate switch { - hud.KillButton?.OverrideText(Translator.GetString("InfectiousKillButtonText")); + < 50 => "\u2581", + >= 50 and < 100 => "\u2584", + >= 100 => "\u2588", + _ => string.Empty + }; + } + + private void DirectInfect(PlayerControl player) + { + if (PlayerIdList.Count == 0 || player == null) return; + + Logger.Info($"InfectRate [{player.GetNameWithRole()}]: 100%", "PlagueDoctor"); + InfectInfos[player.PlayerId] = 100; + SendRPC(player.PlayerId, 100); + Utils.NotifyRoles(SpecifySeer: player); + Utils.NotifyRoles(SpecifySeer: Utils.GetPlayerById(PlayerIdList[0])); + CheckWin(); + } + + private void CheckWin() + { + if (!IsEnable) return; + + if (!AmongUsClient.Instance.AmHost) return; + + // Invalid if someone's victory is being processed + if (CustomWinnerHolder.WinnerTeam != CustomWinner.Default) return; + + if (Main.AllAlivePlayerControls.All(p => p.Is(CustomRoles.PlagueDoctor) || IsInfected(p.PlayerId))) + { + InfectActive = false; + + PlayerControl pd = Main.AllPlayerControls.FirstOrDefault(x => x.Is(CustomRoles.PlagueDoctor)); + + foreach (PlayerControl player in Main.AllAlivePlayerControls) + { + if (player.Is(CustomRoles.PlagueDoctor)) continue; + + player.Suicide(PlayerState.DeathReason.Curse, pd); + } + + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.PlagueDoctor); + foreach (PlayerControl plagueDoctor in Main.AllPlayerControls.Where(p => p.Is(CustomRoles.PlagueDoctor)).ToArray()) CustomWinnerHolder.WinnerIds.Add(plagueDoctor.PlayerId); } } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("InfectiousKillButtonText")); + } } \ No newline at end of file diff --git a/Roles/Neutral/Poisoner.cs b/Roles/Neutral/Poisoner.cs index a0ab4067d..9f49f9bc6 100644 --- a/Roles/Neutral/Poisoner.cs +++ b/Roles/Neutral/Poisoner.cs @@ -1,36 +1,35 @@ -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Poisoner : RoleBase { - public class Poisoner : RoleBase + private const int Id = 12700; + public static OptionItem OptionKillDelay; + public static OptionItem CanVent; + public static OptionItem KillCooldown; + public static OptionItem CanKillNormally; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 12700; - public static OptionItem OptionKillDelay; - public static OptionItem CanVent; - public static OptionItem KillCooldown; - public static OptionItem CanKillNormally; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Poisoner); - public override bool IsEnable => false; + KillCooldown = new FloatOptionItem(Id + 10, "PoisonCooldown", new(0f, 180f, 0.5f), 25f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Poisoner); + OptionKillDelay = new FloatOptionItem(Id + 11, "PoisonerKillDelay", new(1f, 30f, 1f), 3f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) + .SetValueFormat(OptionFormat.Seconds); - KillCooldown = new FloatOptionItem(Id + 10, "PoisonCooldown", new(0f, 180f, 0.5f), 25f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) - .SetValueFormat(OptionFormat.Seconds); + CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); - OptionKillDelay = new FloatOptionItem(Id + 11, "PoisonerKillDelay", new(1f, 30f, 1f), 3f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); - - CanKillNormally = new BooleanOptionItem(Id + 13, "CanKillNormally", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + CanKillNormally = new BooleanOptionItem(Id + 13, "CanKillNormally", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Poisoner]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Postman.cs b/Roles/Neutral/Postman.cs index d0f2b9541..c710627ba 100644 --- a/Roles/Neutral/Postman.cs +++ b/Roles/Neutral/Postman.cs @@ -4,190 +4,189 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Postman : RoleBase { - public class Postman : RoleBase + private const int Id = 641400; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + public static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem DieWhenTargetDies; + public bool IsFinished; + + private byte PostmanId; + public byte Target; + private List wereTargets = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 641400; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Postman); - private static OptionItem KillCooldown; - public static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem DieWhenTargetDies; - public bool IsFinished; + KillCooldown = new FloatOptionItem(Id + 10, "DeliverCooldown", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]) + .SetValueFormat(OptionFormat.Seconds); - private byte PostmanId; - public byte Target; - private List wereTargets = []; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]); + DieWhenTargetDies = new BooleanOptionItem(Id + 12, "PostmanDiesWhenTargetDies", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + Target = byte.MaxValue; + IsFinished = false; + wereTargets = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + PostmanId = playerId; + LateTask.New(SetNewTarget, 8f, "Set Postman First Target"); + + Target = byte.MaxValue; + IsFinished = false; + wereTargets = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !IsFinished; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public static void CheckAndResetTargets(PlayerControl deadPc, bool isDeath = false) + { + foreach (byte id in PlayerIdList) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Postman); + PlayerControl pc = Utils.GetPlayerById(id); + if (pc == null || !pc.IsAlive()) continue; - KillCooldown = new FloatOptionItem(Id + 10, "DeliverCooldown", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "CanVent", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]); - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]); - DieWhenTargetDies = new BooleanOptionItem(Id + 12, "PostmanDiesWhenTargetDies", false, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Postman]); - } - - public override void Init() - { - PlayerIdList = []; - Target = byte.MaxValue; - IsFinished = false; - wereTargets = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - PostmanId = playerId; - LateTask.New(SetNewTarget, 8f, "Set Postman First Target"); - - Target = byte.MaxValue; - IsFinished = false; - wereTargets = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !IsFinished; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public static void CheckAndResetTargets(PlayerControl deadPc, bool isDeath = false) - { - foreach (byte id in PlayerIdList) + if (Main.PlayerStates[id].Role is Postman { IsEnable: true } pm && pm.Target == deadPc.PlayerId) { - PlayerControl pc = Utils.GetPlayerById(id); - if (pc == null || !pc.IsAlive()) continue; - - if (Main.PlayerStates[id].Role is Postman { IsEnable: true } pm && pm.Target == deadPc.PlayerId) + if (isDeath && DieWhenTargetDies.GetBool()) + pc.Suicide(); + else { - if (isDeath && DieWhenTargetDies.GetBool()) - pc.Suicide(); - else - { - pm.SetNewTarget(); - if (!isDeath) continue; + pm.SetNewTarget(); + if (!isDeath) continue; - pm.NotifyPostman(Utils.GetPlayerById(id), GetString("PostmanTargetDied")); - } + pm.NotifyPostman(Utils.GetPlayerById(id), GetString("PostmanTargetDied")); } } } + } - private void SetNewTarget() + private void SetNewTarget() + { + if (!IsEnable) return; + + var tempTarget = byte.MaxValue; + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - if (!IsEnable) return; + if (wereTargets.Contains(pc.PlayerId) || pc.Is(CustomRoles.Postman)) continue; - var tempTarget = byte.MaxValue; + tempTarget = pc.PlayerId; + break; + } - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - if (wereTargets.Contains(pc.PlayerId) || pc.Is(CustomRoles.Postman)) continue; - - tempTarget = pc.PlayerId; - break; - } - - if (tempTarget == byte.MaxValue) - { - IsFinished = true; - Target = byte.MaxValue; - SendRPC(); - return; - } - - Target = tempTarget; - wereTargets.Add(Target); + if (tempTarget == byte.MaxValue) + { + IsFinished = true; + Target = byte.MaxValue; SendRPC(); + return; } - private void SendRPC() + Target = tempTarget; + wereTargets.Add(Target); + SendRPC(); + } + + private void SendRPC() + { + Utils.SendRPC(CustomRPC.SyncPostman, PostmanId, Target, IsFinished); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!IsEnable) return false; + + if (killer == null) return false; + + if (target == null) return false; + + if (IsFinished) return false; + + if (Target == byte.MaxValue) { - Utils.SendRPC(CustomRPC.SyncPostman, PostmanId, Target, IsFinished); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!IsEnable) return false; - - if (killer == null) return false; - - if (target == null) return false; - - if (IsFinished) return false; - - if (Target == byte.MaxValue) - { - SetNewTarget(); - return false; - } - - if (target.PlayerId == Target) - { - SetNewTarget(); - killer.SetKillCooldown(); - NotifyPostman(killer, GetString("PostmanCorrectDeliver")); - } - else - killer.Suicide(); - + SetNewTarget(); return false; } - private void NotifyPostman(PlayerControl pc, string baseText) + if (target.PlayerId == Target) { - if (!IsEnable) return; - - var sb = new StringBuilder(); - - sb.Append("\r\n\r\n"); - sb.AppendLine(baseText); - sb.Append(!IsFinished ? string.Format(GetString("PostmanGetNewTarget"), Utils.GetPlayerById(Target).GetRealName()) : GetString("PostmanDone")); - - pc.Notify(sb.ToString()); + SetNewTarget(); + killer.SetKillCooldown(); + NotifyPostman(killer, GetString("PostmanCorrectDeliver")); } + else + killer.Suicide(); - private static string GetHudText(PlayerControl pc) - { - if (Main.PlayerStates[pc.PlayerId].Role is not Postman { IsEnable: true } pm) return string.Empty; + return false; + } - return !pm.IsFinished ? string.Format(GetString("PostmanTarget"), Utils.GetPlayerById(pm.Target).GetRealName()) : GetString("PostmanDone"); - } + private void NotifyPostman(PlayerControl pc, string baseText) + { + if (!IsEnable) return; - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (hud) return GetHudText(seer); + var sb = new StringBuilder(); - if (seer.IsModClient() || seer.PlayerId != target.PlayerId || Main.PlayerStates[seer.PlayerId].Role is not Postman { IsEnable: true } pm) return string.Empty; + sb.Append("\r\n\r\n"); + sb.AppendLine(baseText); + sb.Append(!IsFinished ? string.Format(GetString("PostmanGetNewTarget"), Utils.GetPlayerById(Target).GetRealName()) : GetString("PostmanDone")); - return !pm.IsFinished ? string.Format(GetString("PostmanTarget"), Utils.GetPlayerById(pm.Target).GetRealName()) : "✓"; - } + pc.Notify(sb.ToString()); + } - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(GetString("PostmanKillButtonText")); - } + private static string GetHudText(PlayerControl pc) + { + if (Main.PlayerStates[pc.PlayerId].Role is not Postman { IsEnable: true } pm) return string.Empty; + + return !pm.IsFinished ? string.Format(GetString("PostmanTarget"), Utils.GetPlayerById(pm.Target).GetRealName()) : GetString("PostmanDone"); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (hud) return GetHudText(seer); + + if (seer.IsModClient() || seer.PlayerId != target.PlayerId || Main.PlayerStates[seer.PlayerId].Role is not Postman { IsEnable: true } pm) return string.Empty; + + return !pm.IsFinished ? string.Format(GetString("PostmanTarget"), Utils.GetPlayerById(pm.Target).GetRealName()) : "✓"; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(GetString("PostmanKillButtonText")); } } \ No newline at end of file diff --git a/Roles/Neutral/Predator.cs b/Roles/Neutral/Predator.cs index 31b8d7f19..be4a82b64 100644 --- a/Roles/Neutral/Predator.cs +++ b/Roles/Neutral/Predator.cs @@ -6,161 +6,160 @@ using EHR.Modules; using HarmonyLib; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Predator : RoleBase { - internal class Predator : RoleBase + private const int Id = 643540; + public static bool On; + private static OptionItem NumOfRolesToKill; + private static OptionItem MaxImpRolePicks; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpVision; + public bool IsWon; + + private List RolesToKill = []; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 643540; - public static bool On; - private static OptionItem NumOfRolesToKill; - private static OptionItem MaxImpRolePicks; - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpVision; - public bool IsWon; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Predator); - private List RolesToKill = []; - public override bool IsEnable => On; + NumOfRolesToKill = new IntegerOptionItem(Id + 2, "NumOfRolesToKill", new(1, 10, 1), 3, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); - public override void SetupCustomOption() + MaxImpRolePicks = new IntegerOptionItem(Id + 3, "MaxImpRolePicks", new(1, 10, 1), 1, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); + + KillCooldown = new FloatOptionItem(Id + 4, "KillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]) + .SetValueFormat(OptionFormat.Seconds); + + CanVent = new BooleanOptionItem(Id + 5, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); + + HasImpVision = new BooleanOptionItem(Id + 6, "ImpostorVision", false, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); + } + + public override void Add(byte playerId) + { + On = true; + IsWon = false; + + LateTask.New(() => { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Predator); + RolesToKill = []; - NumOfRolesToKill = new IntegerOptionItem(Id + 2, "NumOfRolesToKill", new(1, 10, 1), 3, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); + List allRoles = Enum.GetValues().ToList(); + allRoles.RemoveAll(x => x == CustomRoles.Predator || x >= CustomRoles.NotAssigned || !x.RoleExist(true)); - MaxImpRolePicks = new IntegerOptionItem(Id + 3, "MaxImpRolePicks", new(1, 10, 1), 1, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); + var r = IRandom.Instance; + var impRoles = 0; - KillCooldown = new FloatOptionItem(Id + 4, "KillCooldown", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 5, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); - - HasImpVision = new BooleanOptionItem(Id + 6, "ImpostorVision", false, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Predator]); - } - - public override void Add(byte playerId) - { - On = true; - IsWon = false; - - LateTask.New(() => + for (var i = 0; i < NumOfRolesToKill.GetInt(); i++) { - RolesToKill = []; + int index = r.Next(allRoles.Count); + CustomRoles role = allRoles[index]; + allRoles.RemoveAt(index); - List allRoles = Enum.GetValues().ToList(); - allRoles.RemoveAll(x => x == CustomRoles.Predator || x >= CustomRoles.NotAssigned || !x.RoleExist(true)); - - var r = IRandom.Instance; - var impRoles = 0; - - for (var i = 0; i < NumOfRolesToKill.GetInt(); i++) + if (role.Is(Team.Impostor)) { - int index = r.Next(allRoles.Count); - CustomRoles role = allRoles[index]; - allRoles.RemoveAt(index); - - if (role.Is(Team.Impostor)) + if (impRoles >= MaxImpRolePicks.GetInt()) { - if (impRoles >= MaxImpRolePicks.GetInt()) - { - i--; - continue; - } - - impRoles++; + i--; + continue; } - RolesToKill.Add(role); + impRoles++; } - Logger.Info($"Predator Roles: {RolesToKill.Join()}", "Predator"); - - MessageWriter w = Utils.CreateRPC(CustomRPC.SyncRoleData); - w.WritePacked(1); - w.WritePacked(RolesToKill.Count); - foreach (CustomRoles role in RolesToKill) w.WritePacked((int)role); - - Utils.EndRPC(w); - Utils.SendRPC(CustomRPC.SyncRoleData, playerId, 2, IsWon); - }, 3f, "Select Predator Roles"); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - RolesToKill = []; - int count = reader.ReadPackedInt32(); - for (var i = 0; i < count; i++) RolesToKill.Add((CustomRoles)reader.ReadPackedInt32()); - - break; - case 2: - IsWon = reader.ReadBoolean(); - break; - } - } - - public override void Init() - { - On = false; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(HasImpVision.GetBool()); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !IsWon; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (IsWon) return false; - - CustomRoles targetRole = target.GetCustomRole(); - - if (RolesToKill.Contains(targetRole)) - { - IsWon = true; - if (!killer.IsModClient()) killer.Notify(string.Format(Translator.GetString("PredatorCorrectKill"), Translator.GetString($"{targetRole}"))); - - return true; + RolesToKill.Add(role); } - killer.Suicide(); - return false; - } + Logger.Info($"Predator Roles: {RolesToKill.Join()}", "Predator"); - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + MessageWriter w = Utils.CreateRPC(CustomRPC.SyncRoleData); + w.WritePacked(1); + w.WritePacked(RolesToKill.Count); + foreach (CustomRoles role in RolesToKill) w.WritePacked((int)role); + + Utils.EndRPC(w); + Utils.SendRPC(CustomRPC.SyncRoleData, playerId, 2, IsWon); + }, 3f, "Select Predator Roles"); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - if (seer.PlayerId != target.PlayerId) return string.Empty; + case 1: + RolesToKill = []; + int count = reader.ReadPackedInt32(); + for (var i = 0; i < count; i++) RolesToKill.Add((CustomRoles)reader.ReadPackedInt32()); - if (seer.IsModClient() && !hud) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Predator { IsEnable: true } pt) return string.Empty; - - if (pt.IsWon) return !hud ? "<#00ff00>\u2713" : Translator.GetString("PredatorDone"); - - string text = pt.RolesToKill.Join(x => x.ToColoredString()); - return hud ? text : $"{text}"; + break; + case 2: + IsWon = reader.ReadBoolean(); + break; } } + + public override void Init() + { + On = false; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(HasImpVision.GetBool()); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !IsWon; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (IsWon) return false; + + CustomRoles targetRole = target.GetCustomRole(); + + if (RolesToKill.Contains(targetRole)) + { + IsWon = true; + if (!killer.IsModClient()) killer.Notify(string.Format(Translator.GetString("PredatorCorrectKill"), Translator.GetString($"{targetRole}"))); + + return true; + } + + killer.Suicide(); + return false; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + if (seer.IsModClient() && !hud) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Predator { IsEnable: true } pt) return string.Empty; + + if (pt.IsWon) return !hud ? "<#00ff00>\u2713" : Translator.GetString("PredatorDone"); + + string text = pt.RolesToKill.Join(x => x.ToColoredString()); + return hud ? text : $"{text}"; + } } \ No newline at end of file diff --git a/Roles/Neutral/Provocateur.cs b/Roles/Neutral/Provocateur.cs index 73b437f06..0c66fccb0 100644 --- a/Roles/Neutral/Provocateur.cs +++ b/Roles/Neutral/Provocateur.cs @@ -1,69 +1,68 @@ using System.Collections.Generic; using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Provocateur : RoleBase { - internal class Provocateur : RoleBase + public static Dictionary Provoked = []; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary Provoked = []; + Options.SetupRoleOptions(18500, TabGroup.NeutralRoles, CustomRoles.Provocateur); + } - public static bool On; - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + } - public override void SetupCustomOption() - { - Options.SetupRoleOptions(18500, TabGroup.NeutralRoles, CustomRoles.Provocateur); - } + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - } + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive() && !Provoked.ContainsKey(pc.PlayerId); + } - public override void Init() - { - On = false; - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive() && !Provoked.ContainsKey(pc.PlayerId); - } + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.Is(CustomRoles.Mischievous); + } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; + } - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.Is(CustomRoles.Mischievous); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("ProvocateurButtonText")); + hud.SabotageButton?.ToggleVisible(false); + hud.AbilityButton?.ToggleVisible(false); + hud.ImpostorVentButton?.ToggleVisible(false); + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("ProvocateurButtonText")); - hud.SabotageButton?.ToggleVisible(false); - hud.AbilityButton?.ToggleVisible(false); - hud.ImpostorVentButton?.ToggleVisible(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; - killer.Kill(target); - Provoked.TryAdd(killer.PlayerId, target.PlayerId); - return false; - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.PissedOff; + killer.Kill(target); + Provoked.TryAdd(killer.PlayerId, target.PlayerId); + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Pursuer.cs b/Roles/Neutral/Pursuer.cs index d0ed3a35b..eb4a434f6 100644 --- a/Roles/Neutral/Pursuer.cs +++ b/Roles/Neutral/Pursuer.cs @@ -1,120 +1,119 @@ using System.Collections.Generic; using EHR.Modules; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Pursuer : RoleBase { - public class Pursuer : RoleBase + private const int Id = 10200; + private static List PlayerIdList = []; + private static List NotActiveList = []; + + public static OptionItem PursuerSkillCooldown; + public static OptionItem PursuerSkillLimitTimes; + + private List clientList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 10200; - private static List PlayerIdList = []; - private static List NotActiveList = []; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pursuer); - public static OptionItem PursuerSkillCooldown; - public static OptionItem PursuerSkillLimitTimes; + PursuerSkillCooldown = new FloatOptionItem(Id + 10, "PursuerSkillCooldown", new(0.5f, 60f, 0.5f), 15f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) + .SetValueFormat(OptionFormat.Seconds); - private List clientList = []; + PursuerSkillLimitTimes = new IntegerOptionItem(Id + 11, "PursuerSkillLimitTimes", new(1, 99, 1), 2, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) + .SetValueFormat(OptionFormat.Times); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override void Init() + { + PlayerIdList = []; + clientList = []; + NotActiveList = []; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(PursuerSkillLimitTimes.GetInt()); + clientList = []; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !Main.PlayerStates[pc.PlayerId].IsDead + && pc.GetAbilityUseLimit() >= 1; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? PursuerSkillCooldown.GetFloat() : 0f; + } + + private bool CanBeClient(PlayerControl pc) + { + return pc != null && pc.IsAlive() && !GameStates.IsMeeting && !clientList.Contains(pc.PlayerId); + } + + private static bool CanSeel(byte playerId) + { + return PlayerIdList.Contains(playerId) && playerId.GetAbilityUseLimit() > 0; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (CanBeClient(target) && CanSeel(killer.PlayerId)) SeelToClient(killer, target); + + return false; + } + + private void SeelToClient(PlayerControl pc, PlayerControl target) + { + if (pc == null || target == null || !pc.Is(CustomRoles.Pursuer)) return; + + pc.RpcRemoveAbilityUse(); + clientList.Add(target.PlayerId); + NotActiveList.Add(pc.PlayerId); + pc.SetKillCooldown(); + pc.RPCPlayCustomSound("Bet"); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); + } + + public static bool OnClientMurder(PlayerControl pc) + { + foreach (byte id in PlayerIdList) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pursuer); + if (!Main.PlayerStates.TryGetValue(id, out PlayerState state)) continue; - PursuerSkillCooldown = new FloatOptionItem(Id + 10, "PursuerSkillCooldown", new(0.5f, 60f, 0.5f), 15f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) - .SetValueFormat(OptionFormat.Seconds); + if (state.Role is not Pursuer { IsEnable: true } ps) continue; - PursuerSkillLimitTimes = new IntegerOptionItem(Id + 11, "PursuerSkillLimitTimes", new(1, 99, 1), 2, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Pursuer]) - .SetValueFormat(OptionFormat.Times); - } + if (!ps.clientList.Contains(pc.PlayerId) || NotActiveList.Contains(pc.PlayerId)) continue; - public override void Init() - { - PlayerIdList = []; - clientList = []; - NotActiveList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(PursuerSkillLimitTimes.GetInt()); - clientList = []; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return !Main.PlayerStates[pc.PlayerId].IsDead - && pc.GetAbilityUseLimit() >= 1; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CanUseKillButton(Utils.GetPlayerById(id)) ? PursuerSkillCooldown.GetFloat() : 0f; - } - - private bool CanBeClient(PlayerControl pc) - { - return pc != null && pc.IsAlive() && !GameStates.IsMeeting && !clientList.Contains(pc.PlayerId); - } - - private static bool CanSeel(byte playerId) - { - return PlayerIdList.Contains(playerId) && playerId.GetAbilityUseLimit() > 0; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (CanBeClient(target) && CanSeel(killer.PlayerId)) SeelToClient(killer, target); - - return false; - } - - private void SeelToClient(PlayerControl pc, PlayerControl target) - { - if (pc == null || target == null || !pc.Is(CustomRoles.Pursuer)) return; - - pc.RpcRemoveAbilityUse(); - clientList.Add(target.PlayerId); - NotActiveList.Add(pc.PlayerId); + // Get rid of this nonsense of killing the player for no reason + // Just reset their KCD instead pc.SetKillCooldown(); - pc.RPCPlayCustomSound("Bet"); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: target); + + pc.Notify(Translator.GetString("ShotBlank")); + ps.clientList.Remove(pc.PlayerId); + NotActiveList.Add(pc.PlayerId); + + return true; } - public static bool OnClientMurder(PlayerControl pc) - { - foreach (byte id in PlayerIdList) - { - if (!Main.PlayerStates.TryGetValue(id, out PlayerState state)) continue; + return false; + } - if (state.Role is not Pursuer { IsEnable: true } ps) continue; + public override void OnReportDeadBody() + { + NotActiveList.Clear(); + } - if (!ps.clientList.Contains(pc.PlayerId) || NotActiveList.Contains(pc.PlayerId)) continue; - - // Get rid of this nonsense of killing the player for no reason - // Just reset their KCD instead - pc.SetKillCooldown(); - - pc.Notify(Translator.GetString("ShotBlank")); - ps.clientList.Remove(pc.PlayerId); - NotActiveList.Add(pc.PlayerId); - - return true; - } - - return false; - } - - public override void OnReportDeadBody() - { - NotActiveList.Clear(); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("PursuerButtonText")); - } + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("PursuerButtonText")); } } \ No newline at end of file diff --git a/Roles/Neutral/Pyromaniac.cs b/Roles/Neutral/Pyromaniac.cs index 9362b861a..22a624fae 100644 --- a/Roles/Neutral/Pyromaniac.cs +++ b/Roles/Neutral/Pyromaniac.cs @@ -2,85 +2,84 @@ using System.Collections.Generic; using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Pyromaniac : RoleBase { - public class Pyromaniac : RoleBase + private const int Id = 648000; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem DouseCooldown; + private static OptionItem BurnCooldown; + public static OptionItem CanVent; + private static OptionItem HasImpostorVision; + + public List DousedList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 648000; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pyromaniac); - private static OptionItem KillCooldown; - private static OptionItem DouseCooldown; - private static OptionItem BurnCooldown; - public static OptionItem CanVent; - private static OptionItem HasImpostorVision; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]) + .SetValueFormat(OptionFormat.Seconds); - public List DousedList = []; + DouseCooldown = new FloatOptionItem(Id + 11, "PyroDouseCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + BurnCooldown = new FloatOptionItem(Id + 12, "PyroBurnCooldown", new(0f, 180f, 2.5f), 5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + CanVent = new BooleanOptionItem(Id + 13, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]); + HasImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]); + } + + public override void Init() + { + PlayerIdList = []; + DousedList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + DousedList = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return true; + + if (target == null) return true; + + if (DousedList.Contains(target.PlayerId)) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Pyromaniac); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]) - .SetValueFormat(OptionFormat.Seconds); - - DouseCooldown = new FloatOptionItem(Id + 11, "PyroDouseCooldown", new(0f, 180f, 2.5f), 20f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]) - .SetValueFormat(OptionFormat.Seconds); - - BurnCooldown = new FloatOptionItem(Id + 12, "PyroBurnCooldown", new(0f, 180f, 2.5f), 5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 13, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]); - HasImpostorVision = new BooleanOptionItem(Id + 14, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Pyromaniac]); + LateTask.New(() => { killer.SetKillCooldown(BurnCooldown.GetFloat()); }, 0.1f, log: false); + return true; } - public override void Init() + return killer.CheckDoubleTrigger(target, () => { - PlayerIdList = []; - DousedList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - DousedList = []; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return true; - - if (target == null) return true; - - if (DousedList.Contains(target.PlayerId)) - { - LateTask.New(() => { killer.SetKillCooldown(BurnCooldown.GetFloat()); }, 0.1f, log: false); - return true; - } - - return killer.CheckDoubleTrigger(target, () => - { - DousedList.Add(target.PlayerId); - killer.SetKillCooldown(DouseCooldown.GetFloat()); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - }); - } + DousedList.Add(target.PlayerId); + killer.SetKillCooldown(DouseCooldown.GetFloat()); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + }); } } \ No newline at end of file diff --git a/Roles/Neutral/QuizMaster.cs b/Roles/Neutral/QuizMaster.cs index e9e1cbf5d..c30e974f9 100644 --- a/Roles/Neutral/QuizMaster.cs +++ b/Roles/Neutral/QuizMaster.cs @@ -6,334 +6,333 @@ using EHR.Modules; using EHR.Patches; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class QuizMaster : RoleBase { - internal class QuizMaster : RoleBase + private const int Id = 10890; + public static bool On; + + private static OptionItem MarkCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem CanKillWithDoubleClick; + private static OptionItem KillCooldown; + private static OptionItem EnableCustomQuestionsOpt; + private static OptionItem CustomQuestionChance; + + private static bool EnableCustomQuestions; + private static Question[] CustomQuestions = []; + + public static Dictionary MessagesToSend = []; + public static List QuizMasters = []; + + private static List AllColors = []; + + public static readonly SystemTypes[] AllSabotages = + [ + SystemTypes.Comms, + SystemTypes.Reactor, + SystemTypes.Electrical, + SystemTypes.LifeSupp, + SystemTypes.MushroomMixupSabotage, + SystemTypes.Laboratory, + SystemTypes.HeliSabotage + ]; + + public static ((string ColorString, PlayerControl Player) LastReportedPlayer, string LastPlayerPressedButtonName, SystemTypes LastSabotage, string LastReporterName, int NumPlayersVotedLastMeeting, string FirstReportedBodyPlayerName, int NumEmergencyMeetings, int NumPlayersDeadThisRound, int NumPlayersDeadFirstRound, int NumSabotages, int NumMeetings) Data = ((string.Empty, null), string.Empty, default, string.Empty, 0, string.Empty, 0, 0, 0, 0, 0); + + private Question CurrentQuestion; + public byte QuizMasterId; + public byte Target; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 10890; - public static bool On; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.QuizMaster); - private static OptionItem MarkCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem CanKillWithDoubleClick; - private static OptionItem KillCooldown; - private static OptionItem EnableCustomQuestionsOpt; - private static OptionItem CustomQuestionChance; + MarkCooldown = new FloatOptionItem(Id + 2, "QuizMaster.MarkCooldown", new(0f, 180f, 1f), 1f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]) + .SetValueFormat(OptionFormat.Seconds); - private static bool EnableCustomQuestions; - private static Question[] CustomQuestions = []; + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - public static Dictionary MessagesToSend = []; - public static List QuizMasters = []; + HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - private static List AllColors = []; + CanKillWithDoubleClick = new BooleanOptionItem(Id + 5, "CanKillWithDoubleClick", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - public static readonly SystemTypes[] AllSabotages = - [ - SystemTypes.Comms, - SystemTypes.Reactor, - SystemTypes.Electrical, - SystemTypes.LifeSupp, - SystemTypes.MushroomMixupSabotage, - SystemTypes.Laboratory, - SystemTypes.HeliSabotage - ]; + KillCooldown = new FloatOptionItem(Id + 6, "KillCooldown", new(0f, 60f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CanKillWithDoubleClick) + .SetValueFormat(OptionFormat.Seconds); - public static ((string ColorString, PlayerControl Player) LastReportedPlayer, string LastPlayerPressedButtonName, SystemTypes LastSabotage, string LastReporterName, int NumPlayersVotedLastMeeting, string FirstReportedBodyPlayerName, int NumEmergencyMeetings, int NumPlayersDeadThisRound, int NumPlayersDeadFirstRound, int NumSabotages, int NumMeetings) Data = ((string.Empty, null), string.Empty, default, string.Empty, 0, string.Empty, 0, 0, 0, 0, 0); + EnableCustomQuestionsOpt = new BooleanOptionItem(Id + 7, "QuizMaster.EnableCustomQuestions", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - private Question CurrentQuestion; - public byte QuizMasterId; - public byte Target; + CustomQuestionChance = new FloatOptionItem(Id + 8, "QuizMaster.CustomQuestionChance", new(0f, 100f, 5f), 50f, TabGroup.NeutralRoles) + .SetParent(EnableCustomQuestionsOpt) + .SetValueFormat(OptionFormat.Percent); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + Target = byte.MaxValue; + CurrentQuestion = default; - public override void SetupCustomOption() + QuizMasters = []; + + Data = ((string.Empty, null), string.Empty, default, string.Empty, 0, string.Empty, 0, 0, 0, 0, 0); + + AllColors = []; + + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.QuizMaster); - - MarkCooldown = new FloatOptionItem(Id + 2, "QuizMaster.MarkCooldown", new(0f, 180f, 1f), 1f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - - HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - - CanKillWithDoubleClick = new BooleanOptionItem(Id + 5, "CanKillWithDoubleClick", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - - KillCooldown = new FloatOptionItem(Id + 6, "KillCooldown", new(0f, 60f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CanKillWithDoubleClick) - .SetValueFormat(OptionFormat.Seconds); - - EnableCustomQuestionsOpt = new BooleanOptionItem(Id + 7, "QuizMaster.EnableCustomQuestions", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.QuizMaster]); - - CustomQuestionChance = new FloatOptionItem(Id + 8, "QuizMaster.CustomQuestionChance", new(0f, 100f, 5f), 50f, TabGroup.NeutralRoles) - .SetParent(EnableCustomQuestionsOpt) - .SetValueFormat(OptionFormat.Percent); - } - - public override void Init() - { - On = false; - Target = byte.MaxValue; - CurrentQuestion = default; - - QuizMasters = []; - - Data = ((string.Empty, null), string.Empty, default, string.Empty, 0, string.Empty, 0, 0, 0, 0, 0); - - AllColors = []; - - LateTask.New(() => + foreach (PlayerControl pc in Main.AllPlayerControls) { - foreach (PlayerControl pc in Main.AllPlayerControls) + int colorId = pc.Data.DefaultOutfit.ColorId; + AllColors.Add(Palette.GetColorName(colorId)); + } + }, 10f, log: false); + + CustomQuestions = []; + EnableCustomQuestions = EnableCustomQuestionsOpt.GetBool() && File.Exists("./EHR_DATA/QuizMasterQuestions.txt"); + + if (EnableCustomQuestions) + { + string[] lines = File.ReadAllLines("./EHR_DATA/QuizMasterQuestions.txt"); + + IEnumerable questions = lines.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => + { + try { - int colorId = pc.Data.DefaultOutfit.ColorId; - AllColors.Add(Palette.GetColorName(colorId)); + string[] line = x.Split(';'); + return new Question(line[0], line[1].Split(','), int.Parse(line[2])); } - }, 10f, log: false); - - CustomQuestions = []; - EnableCustomQuestions = EnableCustomQuestionsOpt.GetBool() && File.Exists("./EHR_DATA/QuizMasterQuestions.txt"); - - if (EnableCustomQuestions) - { - string[] lines = File.ReadAllLines("./EHR_DATA/QuizMasterQuestions.txt"); - - IEnumerable questions = lines.Where(x => !string.IsNullOrWhiteSpace(x)).Select(x => + catch (Exception e) { - try - { - string[] line = x.Split(';'); - return new Question(line[0], line[1].Split(','), int.Parse(line[2])); - } - catch (Exception e) - { - Utils.ThrowException(e); - return null; - } - }).Where(x => x != null); - - CustomQuestions = questions.ToArray(); - - EnableCustomQuestions &= CustomQuestions.Length > 0; - } - } - - public override void Add(byte playerId) - { - On = true; - QuizMasters.Add(this); - QuizMasterId = playerId; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = CanKillWithDoubleClick.GetBool() ? KillCooldown.GetFloat() : MarkCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void AfterMeetingTasks() - { - Data.NumPlayersDeadThisRound = 0; - - if (Target != byte.MaxValue) - { - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.WrongAnswer, Target); - Target = byte.MaxValue; - } - - MessagesToSend = []; - CurrentQuestion = default; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - if (!CanKillWithDoubleClick.GetBool()) - { - Target = target.PlayerId; - return false; - } - - return killer.CheckDoubleTrigger(target, () => - { - Target = target.PlayerId; - killer.SetKillCooldown(MarkCooldown.GetFloat()); - }); - } - - private static Question GetQuestion() - { - List allowedIndexes = []; - List allowedABCIndexes = []; - - for (var i = 1; i <= 14; i++) - { - switch (i) - { - case 1 when Data.LastReportedPlayer.ColorString != string.Empty: - case 2 when Data.LastPlayerPressedButtonName != string.Empty: - case 3 when Data.LastSabotage != default: - case 4 when Main.LastVotedPlayerInfo != null: - case 5 when Data.LastReporterName != string.Empty: - case 6 when Data.NumPlayersVotedLastMeeting != 0: - case 7: - case 8 when Data.FirstReportedBodyPlayerName != string.Empty: - case 9 or 10 or 11 or 12 or 13 or 14: - allowedIndexes.Add(i); - break; + Utils.ThrowException(e); + return null; } - } + }).Where(x => x != null); - for (var i = 1; i <= 6; i++) - { - switch (i) - { - case 1 or 2 when Data.LastReportedPlayer.Player != null: - case 3 or 4 or 5 or 6: - allowedABCIndexes.Add(i); - break; - } - } + CustomQuestions = questions.ToArray(); - var random = IRandom.Instance; - - if (EnableCustomQuestions && random.Next(100) < CustomQuestionChance.GetInt()) return CustomQuestions.RandomElement(); - - bool abc = random.Next(4) == 0; - List indexes = abc ? allowedABCIndexes : allowedIndexes; - int index = indexes.RandomElement(); - - CustomRoles randomRole = Enum.GetValues().Where(x => x.IsEnable() && !x.IsAdditionRole() && !HnSManager.AllHnSRoles.Contains(x) && !x.IsForOtherGameMode()).Shuffle()[0]; - - string title = index switch - { - 14 => string.Format(Translator.GetString("QuizMaster.Question.14"), randomRole.ToColoredString()), - 5 or 6 when abc => string.Format(Translator.GetString($"QuizMaster.Question.ABC.{index}"), randomRole.ToColoredString()), - _ when abc => Translator.GetString($"QuizMaster.Question.ABC.{index}"), - _ => Translator.GetString($"QuizMaster.Question.{index}") - }; - - (IEnumerable WrongAnswers, string CorrectAnswer) answers = index switch - { - 1 when abc => (Enum.GetValues().Skip(1).Where(x => x != Data.LastReportedPlayer.Player.GetTeam()).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{Data.LastReportedPlayer.Player.GetTeam()}")), - 1 => (AllColors.Without(Data.LastReportedPlayer.ColorString).Shuffle().Take(2), Data.LastReportedPlayer.ColorString), - 2 when abc => (new[] { PlayerState.DeathReason.Suicide, PlayerState.DeathReason.Kill, PlayerState.DeathReason.etc }.Without(Main.PlayerStates[Data.LastReportedPlayer.Player!.PlayerId].deathReason).Select(x => Translator.GetString($"DeathReason.{x}")), Translator.GetString($"DeathReason.{Main.PlayerStates[Data.LastReportedPlayer.Player!.PlayerId].deathReason}")), - 2 => (GetTwoRandomNames(Data.LastPlayerPressedButtonName), Data.LastPlayerPressedButtonName), - 3 when abc => (["Town Of Us", "Town Of Host Enhanced"], "Endless Host Roles"), - 3 => (AllSabotages.Take(2).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{Data.LastSabotage}")), - 4 when abc => (["tukasa0001", "0xDrMoe"], "Gurge44"), - 4 => (CustomRoleSelector.RoleResult.Values.Without(Main.LastVotedPlayerInfo!.Object.GetCustomRole()).Shuffle().Take(2).Select(x => x.ToColoredString()), Main.LastVotedPlayerInfo!.Object.GetCustomRole().ToColoredString()), - 5 when abc => (Enum.GetValues().Skip(1).Where(x => x != randomRole.GetTeam()).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{randomRole.GetTeam()}")), - 5 => (GetTwoRandomNames(Data.LastReporterName), Data.LastReporterName), - 6 when abc => (Enum.GetValues().Where(x => x != randomRole.GetCustomRoleTypes()).Shuffle().Take(2).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{randomRole.GetCustomRoleTypes()}")), - 6 => (GetTwoRandomNumbers(Data.NumPlayersVotedLastMeeting, 0, 15), Data.NumPlayersVotedLastMeeting.ToString()), - 7 => (GetTwoRandomNumbers(Data.NumMeetings, Math.Max(Data.NumMeetings - 3, 0), Data.NumMeetings + 3), Data.NumMeetings.ToString()), - 8 => (GetTwoRandomNames(Data.FirstReportedBodyPlayerName), Data.FirstReportedBodyPlayerName), - 9 => (GetTwoRandomNumbers(Data.NumEmergencyMeetings, Math.Max(Data.NumEmergencyMeetings - 3, 0), Data.NumEmergencyMeetings + 3), Data.NumEmergencyMeetings.ToString()), - 10 => (GetTwoRandomNumbers(Data.NumPlayersDeadThisRound, Math.Max(Data.NumPlayersDeadThisRound - 3, 0), Data.NumPlayersDeadThisRound + 3), Data.NumPlayersDeadThisRound.ToString()), - 11 => (GetTwoRandomNumbers(Data.NumPlayersDeadFirstRound, Math.Max(Data.NumPlayersDeadFirstRound - 3, 0), Data.NumPlayersDeadFirstRound + 3), Data.NumPlayersDeadFirstRound.ToString()), - 12 => (GetTwoRandomNumbers(Data.NumSabotages, Math.Max(Data.NumSabotages - 3, 0), Data.NumSabotages + 3), Data.NumSabotages.ToString()), - 13 => (GetTwoRandomNumbers(GameData.Instance.CompletedTasks, 0, GameData.Instance.TotalTasks), GameData.Instance.CompletedTasks.ToString()), - 14 => (Enum.GetValues().Where(x => x != randomRole.GetRoleTypes()).Shuffle().Take(2).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{randomRole.GetRoleTypes()}")), - - _ => ([], string.Empty) - }; - - List allAnswersList = answers.WrongAnswers.Append(answers.CorrectAnswer).Shuffle(); - int correctIndex = allAnswersList.IndexOf(answers.CorrectAnswer); - - return new(title, allAnswersList.ToArray(), correctIndex); - - IEnumerable GetTwoRandomNames(string except) - { - return Main.AllPlayerControls.Select(x => x?.GetRealName()).Without(except).Shuffle().TakeLast(2); - } - - IEnumerable GetTwoRandomNumbers(params int[] nums) - { - return IRandom.SequenceUnique(3, nums[1], nums[2] + 1).Without(nums[0]).Take(2).Select(x => x.ToString()); - } - } - - public override void OnReportDeadBody() - { - if (Target == byte.MaxValue) return; - - CurrentQuestion = GetQuestion(); - - string msgTitle = CurrentQuestion.QuestionText; - string answers = string.Join('\n', CurrentQuestion.Answers.Select((x, i) => $"{(char)(65 + i)}: {x}")); - string sample = string.Format(Translator.GetString("QuizMaster.QuestionSample"), msgTitle, answers); - - MessagesToSend[Target] = sample; - - Logger.Info($"Question for {Utils.GetPlayerById(Target)?.GetNameWithRole()}: {msgTitle} - Choices: {string.Join('/', CurrentQuestion.Answers)} - Correct Answer: {CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]}", "QuizMaster"); - } - - public void Answer(string answer) - { - int index = -1; - - try - { - index = answer[0] - 'A'; - - string name = Utils.GetPlayerById(Target)?.GetNameWithRole(); - if (index != -1) Logger.Info($"Player {name} answered {CurrentQuestion.Answers[index]}", "QuizMaster"); - - if (CurrentQuestion.CorrectAnswerIndex == index) - { - Utils.SendMessage(Translator.GetString("QuizMaster.AnswerCorrect"), Target, Translator.GetString("QuizMaster.Title")); - Utils.SendMessage(string.Format(Translator.GetString("QuizMaster.AnswerCorrect.Self"), CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]), QuizMasterId, Translator.GetString("QuizMaster.Title")); - - Logger.Info($"Player {name} answered correctly", "QuizMaster"); - } - else if (index != -1) - { - Utils.SendMessage(string.Format(Translator.GetString("QuizMaster.AnswerIncorrect"), CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]), Target, Translator.GetString("QuizMaster.Title")); - Utils.SendMessage(string.Format(Translator.GetString("QuizMaster.AnswerIncorrect.Self"), CurrentQuestion.Answers[index], CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]), QuizMasterId, Translator.GetString("QuizMaster.Title")); - - Main.PlayerStates[Target].deathReason = PlayerState.DeathReason.WrongAnswer; - Main.PlayerStates[Target].SetDead(); - Utils.GetPlayerById(Target).RpcExileV2(); - - Logger.Info($"Player {name} was killed for answering incorrectly", "QuizMaster"); - } - } - catch (IndexOutOfRangeException) { } - catch (Exception e) - { - Utils.ThrowException(e); - } - - if (index == -1) return; - - CurrentQuestion = default; - Target = byte.MaxValue; - } - - private class Question(string questionText, string[] answers, int correctAnswerIndex) - { - public readonly string[] Answers = answers; - public readonly int CorrectAnswerIndex = correctAnswerIndex; - public readonly string QuestionText = questionText; + EnableCustomQuestions &= CustomQuestions.Length > 0; } } + + public override void Add(byte playerId) + { + On = true; + QuizMasters.Add(this); + QuizMasterId = playerId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = CanKillWithDoubleClick.GetBool() ? KillCooldown.GetFloat() : MarkCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void AfterMeetingTasks() + { + Data.NumPlayersDeadThisRound = 0; + + if (Target != byte.MaxValue) + { + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.WrongAnswer, Target); + Target = byte.MaxValue; + } + + MessagesToSend = []; + CurrentQuestion = default; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + if (!CanKillWithDoubleClick.GetBool()) + { + Target = target.PlayerId; + return false; + } + + return killer.CheckDoubleTrigger(target, () => + { + Target = target.PlayerId; + killer.SetKillCooldown(MarkCooldown.GetFloat()); + }); + } + + private static Question GetQuestion() + { + List allowedIndexes = []; + List allowedABCIndexes = []; + + for (var i = 1; i <= 14; i++) + { + switch (i) + { + case 1 when Data.LastReportedPlayer.ColorString != string.Empty: + case 2 when Data.LastPlayerPressedButtonName != string.Empty: + case 3 when Data.LastSabotage != default: + case 4 when Main.LastVotedPlayerInfo != null: + case 5 when Data.LastReporterName != string.Empty: + case 6 when Data.NumPlayersVotedLastMeeting != 0: + case 7: + case 8 when Data.FirstReportedBodyPlayerName != string.Empty: + case 9 or 10 or 11 or 12 or 13 or 14: + allowedIndexes.Add(i); + break; + } + } + + for (var i = 1; i <= 6; i++) + { + switch (i) + { + case 1 or 2 when Data.LastReportedPlayer.Player != null: + case 3 or 4 or 5 or 6: + allowedABCIndexes.Add(i); + break; + } + } + + var random = IRandom.Instance; + + if (EnableCustomQuestions && random.Next(100) < CustomQuestionChance.GetInt()) return CustomQuestions.RandomElement(); + + bool abc = random.Next(4) == 0; + List indexes = abc ? allowedABCIndexes : allowedIndexes; + int index = indexes.RandomElement(); + + CustomRoles randomRole = Enum.GetValues().Where(x => x.IsEnable() && !x.IsAdditionRole() && !HnSManager.AllHnSRoles.Contains(x) && !x.IsForOtherGameMode()).Shuffle()[0]; + + string title = index switch + { + 14 => string.Format(Translator.GetString("QuizMaster.Question.14"), randomRole.ToColoredString()), + 5 or 6 when abc => string.Format(Translator.GetString($"QuizMaster.Question.ABC.{index}"), randomRole.ToColoredString()), + _ when abc => Translator.GetString($"QuizMaster.Question.ABC.{index}"), + _ => Translator.GetString($"QuizMaster.Question.{index}") + }; + + (IEnumerable WrongAnswers, string CorrectAnswer) answers = index switch + { + 1 when abc => (Enum.GetValues().Skip(1).Where(x => x != Data.LastReportedPlayer.Player.GetTeam()).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{Data.LastReportedPlayer.Player.GetTeam()}")), + 1 => (AllColors.Without(Data.LastReportedPlayer.ColorString).Shuffle().Take(2), Data.LastReportedPlayer.ColorString), + 2 when abc => (new[] { PlayerState.DeathReason.Suicide, PlayerState.DeathReason.Kill, PlayerState.DeathReason.etc }.Without(Main.PlayerStates[Data.LastReportedPlayer.Player!.PlayerId].deathReason).Select(x => Translator.GetString($"DeathReason.{x}")), Translator.GetString($"DeathReason.{Main.PlayerStates[Data.LastReportedPlayer.Player!.PlayerId].deathReason}")), + 2 => (GetTwoRandomNames(Data.LastPlayerPressedButtonName), Data.LastPlayerPressedButtonName), + 3 when abc => (["Town Of Us", "Town Of Host Enhanced"], "Endless Host Roles"), + 3 => (AllSabotages.Take(2).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{Data.LastSabotage}")), + 4 when abc => (["tukasa0001", "0xDrMoe"], "Gurge44"), + 4 => (CustomRoleSelector.RoleResult.Values.Without(Main.LastVotedPlayerInfo!.Object.GetCustomRole()).Shuffle().Take(2).Select(x => x.ToColoredString()), Main.LastVotedPlayerInfo!.Object.GetCustomRole().ToColoredString()), + 5 when abc => (Enum.GetValues().Skip(1).Where(x => x != randomRole.GetTeam()).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{randomRole.GetTeam()}")), + 5 => (GetTwoRandomNames(Data.LastReporterName), Data.LastReporterName), + 6 when abc => (Enum.GetValues().Where(x => x != randomRole.GetCustomRoleTypes()).Shuffle().Take(2).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{randomRole.GetCustomRoleTypes()}")), + 6 => (GetTwoRandomNumbers(Data.NumPlayersVotedLastMeeting, 0, 15), Data.NumPlayersVotedLastMeeting.ToString()), + 7 => (GetTwoRandomNumbers(Data.NumMeetings, Math.Max(Data.NumMeetings - 3, 0), Data.NumMeetings + 3), Data.NumMeetings.ToString()), + 8 => (GetTwoRandomNames(Data.FirstReportedBodyPlayerName), Data.FirstReportedBodyPlayerName), + 9 => (GetTwoRandomNumbers(Data.NumEmergencyMeetings, Math.Max(Data.NumEmergencyMeetings - 3, 0), Data.NumEmergencyMeetings + 3), Data.NumEmergencyMeetings.ToString()), + 10 => (GetTwoRandomNumbers(Data.NumPlayersDeadThisRound, Math.Max(Data.NumPlayersDeadThisRound - 3, 0), Data.NumPlayersDeadThisRound + 3), Data.NumPlayersDeadThisRound.ToString()), + 11 => (GetTwoRandomNumbers(Data.NumPlayersDeadFirstRound, Math.Max(Data.NumPlayersDeadFirstRound - 3, 0), Data.NumPlayersDeadFirstRound + 3), Data.NumPlayersDeadFirstRound.ToString()), + 12 => (GetTwoRandomNumbers(Data.NumSabotages, Math.Max(Data.NumSabotages - 3, 0), Data.NumSabotages + 3), Data.NumSabotages.ToString()), + 13 => (GetTwoRandomNumbers(GameData.Instance.CompletedTasks, 0, GameData.Instance.TotalTasks), GameData.Instance.CompletedTasks.ToString()), + 14 => (Enum.GetValues().Where(x => x != randomRole.GetRoleTypes()).Shuffle().Take(2).Select(x => Translator.GetString($"{x}")), Translator.GetString($"{randomRole.GetRoleTypes()}")), + + _ => ([], string.Empty) + }; + + List allAnswersList = answers.WrongAnswers.Append(answers.CorrectAnswer).Shuffle(); + int correctIndex = allAnswersList.IndexOf(answers.CorrectAnswer); + + return new(title, allAnswersList.ToArray(), correctIndex); + + IEnumerable GetTwoRandomNames(string except) + { + return Main.AllPlayerControls.Select(x => x?.GetRealName()).Without(except).Shuffle().TakeLast(2); + } + + IEnumerable GetTwoRandomNumbers(params int[] nums) + { + return IRandom.SequenceUnique(3, nums[1], nums[2] + 1).Without(nums[0]).Take(2).Select(x => x.ToString()); + } + } + + public override void OnReportDeadBody() + { + if (Target == byte.MaxValue) return; + + CurrentQuestion = GetQuestion(); + + string msgTitle = CurrentQuestion.QuestionText; + string answers = string.Join('\n', CurrentQuestion.Answers.Select((x, i) => $"{(char)(65 + i)}: {x}")); + string sample = string.Format(Translator.GetString("QuizMaster.QuestionSample"), msgTitle, answers); + + MessagesToSend[Target] = sample; + + Logger.Info($"Question for {Utils.GetPlayerById(Target)?.GetNameWithRole()}: {msgTitle} - Choices: {string.Join('/', CurrentQuestion.Answers)} - Correct Answer: {CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]}", "QuizMaster"); + } + + public void Answer(string answer) + { + int index = -1; + + try + { + index = answer[0] - 'A'; + + string name = Utils.GetPlayerById(Target)?.GetNameWithRole(); + if (index != -1) Logger.Info($"Player {name} answered {CurrentQuestion.Answers[index]}", "QuizMaster"); + + if (CurrentQuestion.CorrectAnswerIndex == index) + { + Utils.SendMessage(Translator.GetString("QuizMaster.AnswerCorrect"), Target, Translator.GetString("QuizMaster.Title")); + Utils.SendMessage(string.Format(Translator.GetString("QuizMaster.AnswerCorrect.Self"), CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]), QuizMasterId, Translator.GetString("QuizMaster.Title")); + + Logger.Info($"Player {name} answered correctly", "QuizMaster"); + } + else if (index != -1) + { + Utils.SendMessage(string.Format(Translator.GetString("QuizMaster.AnswerIncorrect"), CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]), Target, Translator.GetString("QuizMaster.Title")); + Utils.SendMessage(string.Format(Translator.GetString("QuizMaster.AnswerIncorrect.Self"), CurrentQuestion.Answers[index], CurrentQuestion.Answers[CurrentQuestion.CorrectAnswerIndex]), QuizMasterId, Translator.GetString("QuizMaster.Title")); + + Main.PlayerStates[Target].deathReason = PlayerState.DeathReason.WrongAnswer; + Main.PlayerStates[Target].SetDead(); + Utils.GetPlayerById(Target).RpcExileV2(); + + Logger.Info($"Player {name} was killed for answering incorrectly", "QuizMaster"); + } + } + catch (IndexOutOfRangeException) { } + catch (Exception e) + { + Utils.ThrowException(e); + } + + if (index == -1) return; + + CurrentQuestion = default; + Target = byte.MaxValue; + } + + private class Question(string questionText, string[] answers, int correctAnswerIndex) + { + public readonly string[] Answers = answers; + public readonly int CorrectAnswerIndex = correctAnswerIndex; + public readonly string QuestionText = questionText; + } } \ No newline at end of file diff --git a/Roles/Neutral/Reckless.cs b/Roles/Neutral/Reckless.cs index ce7f56b8f..07e79eaa8 100644 --- a/Roles/Neutral/Reckless.cs +++ b/Roles/Neutral/Reckless.cs @@ -1,38 +1,37 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Reckless : RoleBase { - public class Reckless : RoleBase + private const int Id = 640500; + + public static OptionItem DefaultKillCooldown; + public static OptionItem ReduceKillCooldown; + public static OptionItem MinKillCooldown; + public static OptionItem HasImpostorVision; + public static OptionItem CanVent; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 640500; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Reckless); - public static OptionItem DefaultKillCooldown; - public static OptionItem ReduceKillCooldown; - public static OptionItem MinKillCooldown; - public static OptionItem HasImpostorVision; - public static OptionItem CanVent; + DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Reckless); + MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]) + .SetValueFormat(OptionFormat.Seconds); - DefaultKillCooldown = new FloatOptionItem(Id + 10, "SansDefaultKillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]) - .SetValueFormat(OptionFormat.Seconds); - - ReduceKillCooldown = new FloatOptionItem(Id + 11, "SansReduceKillCooldown", new(0f, 30f, 0.5f), 5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]) - .SetValueFormat(OptionFormat.Seconds); - - MinKillCooldown = new FloatOptionItem(Id + 12, "SansMinKillCooldown", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]) - .SetValueFormat(OptionFormat.Seconds); - - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]); - CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]); + CanVent = new BooleanOptionItem(Id + 14, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Reckless]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Revolutionist.cs b/Roles/Neutral/Revolutionist.cs index 7b8fc0dc8..015f0568b 100644 --- a/Roles/Neutral/Revolutionist.cs +++ b/Roles/Neutral/Revolutionist.cs @@ -4,235 +4,234 @@ using EHR.Modules; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Revolutionist : RoleBase { - internal class Revolutionist : RoleBase + public static Dictionary<(byte, byte), bool> IsDraw = []; + public static Dictionary RevolutionistTimer = []; + public static Dictionary RevolutionistStart = []; + public static Dictionary RevolutionistLastTime = []; + public static Dictionary RevolutionistCountdown = []; + public static byte CurrentDrawTarget = byte.MaxValue; + + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static Dictionary<(byte, byte), bool> IsDraw = []; - public static Dictionary RevolutionistTimer = []; - public static Dictionary RevolutionistStart = []; - public static Dictionary RevolutionistLastTime = []; - public static Dictionary RevolutionistCountdown = []; - public static byte CurrentDrawTarget = byte.MaxValue; + SetupRoleOptions(18400, TabGroup.NeutralRoles, CustomRoles.Revolutionist); - public static bool On; - public override bool IsEnable => On; + RevolutionistDrawTime = new FloatOptionItem(18410, "RevolutionistDrawTime", new(0f, 90f, 1f), 3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + RevolutionistCooldown = new FloatOptionItem(18411, "RevolutionistCooldown", new(0f, 100f, 1f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) + .SetValueFormat(OptionFormat.Seconds); + + RevolutionistDrawCount = new IntegerOptionItem(18412, "RevolutionistDrawCount", new(0, 14, 1), 6, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) + .SetValueFormat(OptionFormat.Players); + + RevolutionistKillProbability = new IntegerOptionItem(18413, "RevolutionistKillProbability", new(0, 100, 5), 15, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) + .SetValueFormat(OptionFormat.Percent); + + RevolutionistVentCountDown = new FloatOptionItem(18414, "RevolutionistVentCountDown", new(0f, 180f, 1f), 15f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) + .SetValueFormat(OptionFormat.Seconds); + } + + + public override void Add(byte playerId) + { + On = true; + foreach (PlayerControl ar in Main.AllPlayerControls) IsDraw.Add((playerId, ar.PlayerId), false); + } + + public override void Init() + { + On = false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return !pc.IsDrawDone(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsDrawDone(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return pc.Is(CustomRoles.Mischievous); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = RevolutionistCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override string GetProgressText(byte playerId, bool comms) + { + (int, int) draw = Utils.GetDrawPlayerCount(playerId, out _); + return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Revolutionist).ShadeColor(0.25f), $"- {draw.Item1}/{draw.Item2}"); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("RevolutionistDrawButtonText")); + hud.ImpostorVentButton.buttonLabelText.text = Translator.GetString("RevolutionistVentButtonText"); + + hud.SabotageButton?.ToggleVisible(false); + hud.AbilityButton?.ToggleVisible(false); + hud.ImpostorVentButton?.ToggleVisible(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + killer.SetKillCooldown(RevolutionistDrawTime.GetFloat()); + + if (!IsDraw[(killer.PlayerId, target.PlayerId)] && !RevolutionistTimer.ContainsKey(killer.PlayerId)) { - SetupRoleOptions(18400, TabGroup.NeutralRoles, CustomRoles.Revolutionist); - - RevolutionistDrawTime = new FloatOptionItem(18410, "RevolutionistDrawTime", new(0f, 90f, 1f), 3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) - .SetValueFormat(OptionFormat.Seconds); - - RevolutionistCooldown = new FloatOptionItem(18411, "RevolutionistCooldown", new(0f, 100f, 1f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) - .SetValueFormat(OptionFormat.Seconds); - - RevolutionistDrawCount = new IntegerOptionItem(18412, "RevolutionistDrawCount", new(0, 14, 1), 6, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) - .SetValueFormat(OptionFormat.Players); - - RevolutionistKillProbability = new IntegerOptionItem(18413, "RevolutionistKillProbability", new(0, 100, 5), 15, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) - .SetValueFormat(OptionFormat.Percent); - - RevolutionistVentCountDown = new FloatOptionItem(18414, "RevolutionistVentCountDown", new(0f, 180f, 1f), 15f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Revolutionist]) - .SetValueFormat(OptionFormat.Seconds); + RevolutionistTimer.TryAdd(killer.PlayerId, (target, 0f)); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + RPC.SetCurrentDrawTarget(killer.PlayerId, target.PlayerId); } + return false; + } - public override void Add(byte playerId) + public override void OnReportDeadBody() + { + foreach (KeyValuePair x in RevolutionistStart) { - On = true; - foreach (PlayerControl ar in Main.AllPlayerControls) IsDraw.Add((playerId, ar.PlayerId), false); + PlayerControl tar = Utils.GetPlayerById(x.Key); + if (tar == null) continue; + + tar.Data.IsDead = true; + Main.PlayerStates[tar.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + tar.RpcExileV2(); + Main.PlayerStates[tar.PlayerId].SetDead(); } - public override void Init() + RevolutionistTimer.Clear(); + RevolutionistStart.Clear(); + RevolutionistLastTime.Clear(); + } + + public override void OnFixedUpdate(PlayerControl player) + { + byte playerId = player.PlayerId; + + if (GameStates.IsInTask && RevolutionistTimer.ContainsKey(playerId)) { - On = false; - } + PlayerControl rvTarget = RevolutionistTimer[playerId].PLAYER; - public override bool CanUseKillButton(PlayerControl pc) - { - return !pc.IsDrawDone(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.IsDrawDone(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return pc.Is(CustomRoles.Mischievous); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = RevolutionistCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override string GetProgressText(byte playerId, bool comms) - { - (int, int) draw = Utils.GetDrawPlayerCount(playerId, out _); - return Utils.ColorString(Utils.GetRoleColor(CustomRoles.Revolutionist).ShadeColor(0.25f), $"- {draw.Item1}/{draw.Item2}"); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("RevolutionistDrawButtonText")); - hud.ImpostorVentButton.buttonLabelText.text = Translator.GetString("RevolutionistVentButtonText"); - - hud.SabotageButton?.ToggleVisible(false); - hud.AbilityButton?.ToggleVisible(false); - hud.ImpostorVentButton?.ToggleVisible(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - killer.SetKillCooldown(RevolutionistDrawTime.GetFloat()); - - if (!IsDraw[(killer.PlayerId, target.PlayerId)] && !RevolutionistTimer.ContainsKey(killer.PlayerId)) + if (!player.IsAlive() || Pelican.IsEaten(playerId)) { - RevolutionistTimer.TryAdd(killer.PlayerId, (target, 0f)); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - RPC.SetCurrentDrawTarget(killer.PlayerId, target.PlayerId); + RevolutionistTimer.Remove(playerId); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: rvTarget, ForceLoop: true); + RPC.ResetCurrentDrawTarget(playerId); } - - return false; - } - - public override void OnReportDeadBody() - { - foreach (KeyValuePair x in RevolutionistStart) + else { - PlayerControl tar = Utils.GetPlayerById(x.Key); - if (tar == null) continue; + PlayerControl rv_target = RevolutionistTimer[playerId].PLAYER; + float rv_time = RevolutionistTimer[playerId].TIMER; - tar.Data.IsDead = true; - Main.PlayerStates[tar.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; - tar.RpcExileV2(); - Main.PlayerStates[tar.PlayerId].SetDead(); - } - - RevolutionistTimer.Clear(); - RevolutionistStart.Clear(); - RevolutionistLastTime.Clear(); - } - - public override void OnFixedUpdate(PlayerControl player) - { - byte playerId = player.PlayerId; - - if (GameStates.IsInTask && RevolutionistTimer.ContainsKey(playerId)) - { - PlayerControl rvTarget = RevolutionistTimer[playerId].PLAYER; - - if (!player.IsAlive() || Pelican.IsEaten(playerId)) - { + if (!rv_target.IsAlive()) RevolutionistTimer.Remove(playerId); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: rvTarget, ForceLoop: true); + else if (rv_time >= RevolutionistDrawTime.GetFloat()) + { + player.SetKillCooldown(); + RevolutionistTimer.Remove(playerId); + IsDraw[(playerId, rv_target.PlayerId)] = true; + player.RpcSetDrawPlayer(rv_target, true); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: rv_target, ForceLoop: true); RPC.ResetCurrentDrawTarget(playerId); + + if (IRandom.Instance.Next(1, 100) <= RevolutionistKillProbability.GetInt()) + { + rv_target.SetRealKiller(player); + Main.PlayerStates[rv_target.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; + player.Kill(rv_target); + Main.PlayerStates[rv_target.PlayerId].SetDead(); + Logger.Info($"Revolutionist: {player.GetNameWithRole().RemoveHtmlTags()} killed {rv_target.GetNameWithRole().RemoveHtmlTags()}", "Revolutionist"); + } } else { - PlayerControl rv_target = RevolutionistTimer[playerId].PLAYER; - float rv_time = RevolutionistTimer[playerId].TIMER; + float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; + float dis = Vector2.Distance(player.transform.position, rv_target.transform.position); - if (!rv_target.IsAlive()) - RevolutionistTimer.Remove(playerId); - else if (rv_time >= RevolutionistDrawTime.GetFloat()) - { - player.SetKillCooldown(); - RevolutionistTimer.Remove(playerId); - IsDraw[(playerId, rv_target.PlayerId)] = true; - player.RpcSetDrawPlayer(rv_target, true); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: rv_target, ForceLoop: true); - RPC.ResetCurrentDrawTarget(playerId); - - if (IRandom.Instance.Next(1, 100) <= RevolutionistKillProbability.GetInt()) - { - rv_target.SetRealKiller(player); - Main.PlayerStates[rv_target.PlayerId].deathReason = PlayerState.DeathReason.Sacrifice; - player.Kill(rv_target); - Main.PlayerStates[rv_target.PlayerId].SetDead(); - Logger.Info($"Revolutionist: {player.GetNameWithRole().RemoveHtmlTags()} killed {rv_target.GetNameWithRole().RemoveHtmlTags()}", "Revolutionist"); - } - } + if (dis <= range) + RevolutionistTimer[playerId] = (rv_target, rv_time + Time.fixedDeltaTime); else { - float range = NormalGameOptionsV08.KillDistances[Mathf.Clamp(player.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f; - float dis = Vector2.Distance(player.transform.position, rv_target.transform.position); + RevolutionistTimer.Remove(playerId); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: rv_target); + RPC.ResetCurrentDrawTarget(playerId); - if (dis <= range) - RevolutionistTimer[playerId] = (rv_target, rv_time + Time.fixedDeltaTime); - else - { - RevolutionistTimer.Remove(playerId); - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: rv_target); - RPC.ResetCurrentDrawTarget(playerId); - - Logger.Info($"Canceled: {player.GetNameWithRole().RemoveHtmlTags()}", "Revolutionist"); - } + Logger.Info($"Canceled: {player.GetNameWithRole().RemoveHtmlTags()}", "Revolutionist"); } } } - - if (GameStates.IsInTask && player.IsDrawDone() && player.IsAlive()) - { - if (RevolutionistStart.ContainsKey(playerId)) - { - if (RevolutionistLastTime.ContainsKey(playerId)) - { - long nowtime = Utils.TimeStamp; - RevolutionistLastTime[playerId] = nowtime; - var time = (int)(RevolutionistLastTime[playerId] - RevolutionistStart[playerId]); - int countdown = RevolutionistVentCountDown.GetInt() - time; - RevolutionistCountdown.Clear(); - - if (countdown <= 0) - { - Utils.GetDrawPlayerCount(playerId, out List y); - - foreach (PlayerControl pc in y) - { - if (pc != null && pc.IsAlive()) - { - pc.Suicide(PlayerState.DeathReason.Sacrifice); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - } - - player.Suicide(PlayerState.DeathReason.Sacrifice); - - if (player.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - } - else RevolutionistCountdown.Add(playerId, countdown); - } - else RevolutionistLastTime.TryAdd(playerId, RevolutionistStart[playerId]); - } - else RevolutionistStart.TryAdd(playerId, Utils.TimeStamp); - } } - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + if (GameStates.IsInTask && player.IsDrawDone() && player.IsAlive()) { - if (AmongUsClient.Instance.IsGameStarted && physics.myPlayer.IsDrawDone()) + if (RevolutionistStart.ContainsKey(playerId)) { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Revolutionist); - Utils.GetDrawPlayerCount(physics.myPlayer.PlayerId, out List x); - CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); - foreach (PlayerControl apc in x) CustomWinnerHolder.WinnerIds.Add(apc.PlayerId); + if (RevolutionistLastTime.ContainsKey(playerId)) + { + long nowtime = Utils.TimeStamp; + RevolutionistLastTime[playerId] = nowtime; + var time = (int)(RevolutionistLastTime[playerId] - RevolutionistStart[playerId]); + int countdown = RevolutionistVentCountDown.GetInt() - time; + RevolutionistCountdown.Clear(); + + if (countdown <= 0) + { + Utils.GetDrawPlayerCount(playerId, out List y); + + foreach (PlayerControl pc in y) + { + if (pc != null && pc.IsAlive()) + { + pc.Suicide(PlayerState.DeathReason.Sacrifice); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + } + + player.Suicide(PlayerState.DeathReason.Sacrifice); + + if (player.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + } + else RevolutionistCountdown.Add(playerId, countdown); + } + else RevolutionistLastTime.TryAdd(playerId, RevolutionistStart[playerId]); } + else RevolutionistStart.TryAdd(playerId, Utils.TimeStamp); + } + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (AmongUsClient.Instance.IsGameStarted && physics.myPlayer.IsDrawDone()) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Revolutionist); + Utils.GetDrawPlayerCount(physics.myPlayer.PlayerId, out List x); + CustomWinnerHolder.WinnerIds.Add(physics.myPlayer.PlayerId); + foreach (PlayerControl apc in x) CustomWinnerHolder.WinnerIds.Add(apc.PlayerId); } } } \ No newline at end of file diff --git a/Roles/Neutral/Ritualist.cs b/Roles/Neutral/Ritualist.cs index 0cbe1df7e..a21e1b23d 100644 --- a/Roles/Neutral/Ritualist.cs +++ b/Roles/Neutral/Ritualist.cs @@ -1,39 +1,38 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Ritualist : RoleBase { - public class Ritualist : RoleBase + private const int Id = 13000; + + public static OptionItem KillCooldown; + public static OptionItem RitualMaxCount; + public static OptionItem CanVent; + public static OptionItem HasImpostorVision; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 13000; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Ritualist); - public static OptionItem KillCooldown; - public static OptionItem RitualMaxCount; - public static OptionItem CanVent; - public static OptionItem HasImpostorVision; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + RitualMaxCount = new IntegerOptionItem(Id + 11, "RitualMaxCount", new(0, 15, 1), 1, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Ritualist); + CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) - .SetValueFormat(OptionFormat.Seconds); - - RitualMaxCount = new IntegerOptionItem(Id + 11, "RitualMaxCount", new(0, 15, 1), 1, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]) - .SetValueFormat(OptionFormat.Times); - - CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]); - - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Ritualist]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Rogue.cs b/Roles/Neutral/Rogue.cs index 4b3dddc06..1e63acf4e 100644 --- a/Roles/Neutral/Rogue.cs +++ b/Roles/Neutral/Rogue.cs @@ -6,348 +6,347 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Rogue : RoleBase { - public class Rogue : RoleBase + private const int Id = 644300; + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private bool AllTasksCompleted; + + private int Count; + private (Objective Objective, Reward Reward, object Data, bool IsCompleted) CurrentTask; + private bool DoCheck; + private List GotObjectives; + private List GotRewards; + private Vector2? LastPos; + + private int MorphCooldown; + private bool Moving = true; + + private PlayerControl RoguePC; + + public override bool IsEnable => On; + public bool DisableDevices => GotRewards.Contains(Reward.DisableDevices); + + public override void SetupCustomOption() { - private const int Id = 644300; - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Rogue); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private bool AllTasksCompleted; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rogue]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; - private (Objective Objective, Reward Reward, object Data, bool IsCompleted) CurrentTask; - private bool DoCheck; - private List GotObjectives; - private List GotRewards; - private Vector2? LastPos; + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rogue]); + } - private int MorphCooldown; - private bool Moving = true; + public override void Init() + { + On = false; + } - private PlayerControl RoguePC; + public override void Add(byte playerId) + { + On = true; - public override bool IsEnable => On; - public bool DisableDevices => GotRewards.Contains(Reward.DisableDevices); + RoguePC = Utils.GetPlayerById(playerId); + GotObjectives = []; + GotRewards = []; + CurrentTask = (default, default, null, false); + AllTasksCompleted = false; + SendRPC(); - public override void SetupCustomOption() + MorphCooldown = 0; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = GotRewards.Contains(Reward.DecreasedKillCooldown) ? KillCooldown.GetFloat() / 2f : KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CurrentTask.Objective == Objective.VentXTimes || CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || GotRewards.Contains(Reward.Sabotage) || GotRewards.Contains(Reward.Morph); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(GotRewards.Contains(Reward.ImpostorVision)); + opt.SetInt(Int32OptionNames.KillDistance, GotRewards.Contains(Reward.LongReach) ? 2 : 0); + float defSpeed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); + Main.AllPlayerSpeed[id] = GotRewards.Contains(Reward.IncreasedSpeed) ? defSpeed + 0.5f : defSpeed; + } + + public override bool OnSabotage(PlayerControl pc) + { + if (GotRewards.Contains(Reward.Morph) && MorphCooldown <= 0) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Rogue); - - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rogue]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Rogue]); + MorphCooldown = 15 + (int)Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, MorphCooldown); + PlayerControl target = Main.AllAlivePlayerControls.Except([pc]).Shuffle()[0]; + pc.RpcShapeshift(target, !Options.DisableAllShapeshiftAnimations.GetBool()); + return false; } - public override void Init() + return GotRewards.Contains(Reward.Sabotage); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return !GotRewards.Contains(Reward.Shield); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (MeetingStates.FirstMeeting || CurrentTask.Data == null) return; + + switch (CurrentTask.Objective) { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - - RoguePC = Utils.GetPlayerById(playerId); - GotObjectives = []; - GotRewards = []; - CurrentTask = (default, default, null, false); - AllTasksCompleted = false; - SendRPC(); - - MorphCooldown = 0; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = GotRewards.Contains(Reward.DecreasedKillCooldown) ? KillCooldown.GetFloat() / 2f : KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CurrentTask.Objective == Objective.VentXTimes || CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || GotRewards.Contains(Reward.Sabotage) || GotRewards.Contains(Reward.Morph); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(GotRewards.Contains(Reward.ImpostorVision)); - opt.SetInt(Int32OptionNames.KillDistance, GotRewards.Contains(Reward.LongReach) ? 2 : 0); - float defSpeed = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod); - Main.AllPlayerSpeed[id] = GotRewards.Contains(Reward.IncreasedSpeed) ? defSpeed + 0.5f : defSpeed; - } - - public override bool OnSabotage(PlayerControl pc) - { - if (GotRewards.Contains(Reward.Morph) && MorphCooldown <= 0) - { - MorphCooldown = 15 + (int)Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, MorphCooldown); - PlayerControl target = Main.AllAlivePlayerControls.Except([pc]).Shuffle()[0]; - pc.RpcShapeshift(target, !Options.DisableAllShapeshiftAnimations.GetBool()); - return false; - } - - return GotRewards.Contains(Reward.Sabotage); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return !GotRewards.Contains(Reward.Shield); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (MeetingStates.FirstMeeting || CurrentTask.Data == null) return; - - switch (CurrentTask.Objective) - { - case Objective.KillSpecificPlayer when target.PlayerId == (byte)CurrentTask.Data: - SetTaskCompleted(); - break; - case Objective.KillXTimes: - CurrentTask.Data = (int)CurrentTask.Data - 1; - if ((int)CurrentTask.Data <= 0) SetTaskCompleted(); - - break; - case Objective.KillInSpecificRoom when Translator.GetString(target.GetPlainShipRoom().RoomId.ToString()) == (string)CurrentTask.Data: - SetTaskCompleted(); - break; - } - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (CurrentTask.Objective == Objective.VentXTimes) - { + case Objective.KillSpecificPlayer when target.PlayerId == (byte)CurrentTask.Data: + SetTaskCompleted(); + break; + case Objective.KillXTimes: CurrentTask.Data = (int)CurrentTask.Data - 1; if ((int)CurrentTask.Data <= 0) SetTaskCompleted(); - } - } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || !GameStates.IsInTask) return; - - Count++; - if (Count < 30) return; - - Count = 0; - - if (DoCheck && Moving) - { - if (LastPos is null) - { - LastPos = pc.Pos(); - return; - } - - Moving = Vector2.Distance(pc.Pos(), LastPos.Value) >= 0.1f; - LastPos = pc.Pos(); - if (!Moving) pc.Notify(Utils.ColorString(Color.red, "x")); - } - - if (MorphCooldown > 0) - { - MorphCooldown--; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, MorphCooldown); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - - if (pc.IsShifted() && MorphCooldown <= Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown)) pc.RpcShapeshift(pc, !Options.DisableAllShapeshiftAnimations.GetBool()); - } - } - - public void OnButtonPressed() - { - if (CurrentTask.Objective == Objective.CallEmergencyMeeting) SetTaskCompleted(true); - } - - public void OnFixSabotage() - { - if (CurrentTask.Objective == Objective.FixSabotage) SetTaskCompleted(); - } - - private void SetTaskCompleted(bool chatMessage = false) - { - CurrentTask.IsCompleted = true; - SendRPC(); - - if (chatMessage) - LateTask.New(() => Utils.SendMessage("\n", RoguePC.PlayerId, Translator.GetString("Rogue.TaskCompleted")), 8f, log: false); - else - Utils.NotifyRoles(SpecifySeer: RoguePC, SpecifyTarget: RoguePC); - } - - public override void OnReportDeadBody() - { - DoCheck = false; - - if (CurrentTask.Objective == Objective.DontStopWalking && Moving) SetTaskCompleted(true); - } - - public override void AfterMeetingTasks() - { - try - { - if (AllTasksCompleted || !RoguePC.IsAlive()) return; - - if (CurrentTask.IsCompleted) - { - GotObjectives.Add(CurrentTask.Objective); - GotRewards.Add(CurrentTask.Reward); - } - - switch (CurrentTask.Reward) - { - case Reward.HelpfulAddon: - CustomRoles addon = Options.GroupedAddons[AddonTypes.Helpful].RandomElement(); - RoguePC.RpcSetCustomRole(addon); - break; - case Reward.DecreasedKillCooldown: - RoguePC.ResetKillCooldown(); - RoguePC.SyncSettings(); - RoguePC.SetKillCooldown(); - break; - case Reward.Sabotage when RoguePC.IsHost(): - HudManager.Instance.SetHudActive(RoguePC, RoguePC.Data.Role, true); - break; - } - - Objective objective = Enum.GetValues().Except(GotObjectives).Shuffle()[0]; - Reward reward = Enum.GetValues().Except(GotRewards).Shuffle()[0]; - - object data = objective switch - { - Objective.KillInSpecificRoom => Translator.GetString(ShipStatus.Instance.AllRooms.RandomElement().RoomId.ToString()), - Objective.KillSpecificPlayer => Main.AllAlivePlayerControls.Select(x => x.PlayerId).Without(RoguePC.PlayerId).Shuffle()[0], - Objective.VentXTimes => IRandom.Instance.Next(2, 20), - Objective.KillXTimes => IRandom.Instance.Next(2, 5), - _ => null - }; - - CurrentTask = (objective, reward, data, false); - SendRPC(); - - if (objective == Objective.DontStopWalking) - { - LateTask.New(() => - { - Moving = true; - DoCheck = true; - }, 5f, log: false); - } - - Utils.NotifyRoles(SpecifySeer: RoguePC, SpecifyTarget: RoguePC); - Logger.Info($" Objective: {Translator.GetString("Rogue.Objective." + objective)} - Reward: {Translator.GetString("Rogue.Reward." + reward)} - Data: {data}", "Rogue"); - } - catch (Exception e) - { - if (e is IndexOutOfRangeException) - { - AllTasksCompleted = true; - return; - } - - Utils.ThrowException(e); - } - } - - private void SendRPC() - { - Utils.SendRPC(CustomRPC.SyncRoleData, RoguePC.PlayerId, 1, (int)CurrentTask.Objective, (int)CurrentTask.Reward, CurrentTask.IsCompleted, AllTasksCompleted, DataType(), CurrentTask.Data); - } - - private int DataType() - { - return CurrentTask.Data switch - { - string => 0, - byte => 1, - int => 2, - null => 3, - _ => -1 - }; - } - - public void ReceiveRPC(MessageReader reader) - { - if (reader.ReadPackedInt32() == 1) - { - var objective = (Objective)reader.ReadPackedInt32(); - var reward = (Reward)reader.ReadPackedInt32(); - bool isCompleted = reader.ReadBoolean(); - AllTasksCompleted = reader.ReadBoolean(); - - object data = reader.ReadPackedInt32() switch - { - 0 => reader.ReadString(), - 1 => reader.ReadByte(), - 2 => reader.ReadPackedInt32(), - 3 => null, - _ => default - }; - - CurrentTask = (objective, reward, data, isCompleted); - } - else - MorphCooldown = reader.ReadPackedInt32(); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != RoguePC.PlayerId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || MeetingStates.FirstMeeting) return string.Empty; - - if (AllTasksCompleted) return Translator.GetString("Rogue.AllTasksCompleted"); - - if (CurrentTask.IsCompleted) return Translator.GetString("Rogue.TaskCompleted"); - - float d = Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); - - string c = GotRewards.Contains(Reward.Morph) && MorphCooldown > 0 - ? MorphCooldown <= d - ? string.Format(Translator.GetString("CDPT"), MorphCooldown) + "\n" - : $"\u21b9 ({MorphCooldown - d}s)" - : string.Empty; - - string o = Translator.GetString("Rogue.Objective." + CurrentTask.Objective); - if (CurrentTask.Objective is Objective.KillInSpecificRoom or Objective.VentXTimes or Objective.KillSpecificPlayer or Objective.KillXTimes) o = string.Format(o, CurrentTask.Data is byte id ? Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), Utils.GetPlayerById(id).GetRealName()) : CurrentTask.Data); - - string r = Translator.GetString("Rogue.Reward." + CurrentTask.Reward); - string s = string.Format(Translator.GetString("Rogue.Task"), o, r); - return $"{c}{s}"; - } - - private enum Objective - { - KillInSpecificRoom, - VentXTimes, - CallEmergencyMeeting, - KillSpecificPlayer, - KillXTimes, - DontStopWalking, - FixSabotage - } - - private enum Reward - { - DisableDevices, - Morph, - IncreasedSpeed, - DecreasedKillCooldown, - Sabotage, - Shield, - LongReach, - HelpfulAddon, - ImpostorVision + break; + case Objective.KillInSpecificRoom when Translator.GetString(target.GetPlainShipRoom().RoomId.ToString()) == (string)CurrentTask.Data: + SetTaskCompleted(); + break; } } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (CurrentTask.Objective == Objective.VentXTimes) + { + CurrentTask.Data = (int)CurrentTask.Data - 1; + if ((int)CurrentTask.Data <= 0) SetTaskCompleted(); + } + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || !GameStates.IsInTask) return; + + Count++; + if (Count < 30) return; + + Count = 0; + + if (DoCheck && Moving) + { + if (LastPos is null) + { + LastPos = pc.Pos(); + return; + } + + Moving = Vector2.Distance(pc.Pos(), LastPos.Value) >= 0.1f; + LastPos = pc.Pos(); + if (!Moving) pc.Notify(Utils.ColorString(Color.red, "x")); + } + + if (MorphCooldown > 0) + { + MorphCooldown--; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 2, MorphCooldown); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + + if (pc.IsShifted() && MorphCooldown <= Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown)) pc.RpcShapeshift(pc, !Options.DisableAllShapeshiftAnimations.GetBool()); + } + } + + public void OnButtonPressed() + { + if (CurrentTask.Objective == Objective.CallEmergencyMeeting) SetTaskCompleted(true); + } + + public void OnFixSabotage() + { + if (CurrentTask.Objective == Objective.FixSabotage) SetTaskCompleted(); + } + + private void SetTaskCompleted(bool chatMessage = false) + { + CurrentTask.IsCompleted = true; + SendRPC(); + + if (chatMessage) + LateTask.New(() => Utils.SendMessage("\n", RoguePC.PlayerId, Translator.GetString("Rogue.TaskCompleted")), 8f, log: false); + else + Utils.NotifyRoles(SpecifySeer: RoguePC, SpecifyTarget: RoguePC); + } + + public override void OnReportDeadBody() + { + DoCheck = false; + + if (CurrentTask.Objective == Objective.DontStopWalking && Moving) SetTaskCompleted(true); + } + + public override void AfterMeetingTasks() + { + try + { + if (AllTasksCompleted || !RoguePC.IsAlive()) return; + + if (CurrentTask.IsCompleted) + { + GotObjectives.Add(CurrentTask.Objective); + GotRewards.Add(CurrentTask.Reward); + } + + switch (CurrentTask.Reward) + { + case Reward.HelpfulAddon: + CustomRoles addon = Options.GroupedAddons[AddonTypes.Helpful].RandomElement(); + RoguePC.RpcSetCustomRole(addon); + break; + case Reward.DecreasedKillCooldown: + RoguePC.ResetKillCooldown(); + RoguePC.SyncSettings(); + RoguePC.SetKillCooldown(); + break; + case Reward.Sabotage when RoguePC.IsHost(): + HudManager.Instance.SetHudActive(RoguePC, RoguePC.Data.Role, true); + break; + } + + Objective objective = Enum.GetValues().Except(GotObjectives).Shuffle()[0]; + Reward reward = Enum.GetValues().Except(GotRewards).Shuffle()[0]; + + object data = objective switch + { + Objective.KillInSpecificRoom => Translator.GetString(ShipStatus.Instance.AllRooms.RandomElement().RoomId.ToString()), + Objective.KillSpecificPlayer => Main.AllAlivePlayerControls.Select(x => x.PlayerId).Without(RoguePC.PlayerId).Shuffle()[0], + Objective.VentXTimes => IRandom.Instance.Next(2, 20), + Objective.KillXTimes => IRandom.Instance.Next(2, 5), + _ => null + }; + + CurrentTask = (objective, reward, data, false); + SendRPC(); + + if (objective == Objective.DontStopWalking) + { + LateTask.New(() => + { + Moving = true; + DoCheck = true; + }, 5f, log: false); + } + + Utils.NotifyRoles(SpecifySeer: RoguePC, SpecifyTarget: RoguePC); + Logger.Info($" Objective: {Translator.GetString("Rogue.Objective." + objective)} - Reward: {Translator.GetString("Rogue.Reward." + reward)} - Data: {data}", "Rogue"); + } + catch (Exception e) + { + if (e is IndexOutOfRangeException) + { + AllTasksCompleted = true; + return; + } + + Utils.ThrowException(e); + } + } + + private void SendRPC() + { + Utils.SendRPC(CustomRPC.SyncRoleData, RoguePC.PlayerId, 1, (int)CurrentTask.Objective, (int)CurrentTask.Reward, CurrentTask.IsCompleted, AllTasksCompleted, DataType(), CurrentTask.Data); + } + + private int DataType() + { + return CurrentTask.Data switch + { + string => 0, + byte => 1, + int => 2, + null => 3, + _ => -1 + }; + } + + public void ReceiveRPC(MessageReader reader) + { + if (reader.ReadPackedInt32() == 1) + { + var objective = (Objective)reader.ReadPackedInt32(); + var reward = (Reward)reader.ReadPackedInt32(); + bool isCompleted = reader.ReadBoolean(); + AllTasksCompleted = reader.ReadBoolean(); + + object data = reader.ReadPackedInt32() switch + { + 0 => reader.ReadString(), + 1 => reader.ReadByte(), + 2 => reader.ReadPackedInt32(), + 3 => null, + _ => default + }; + + CurrentTask = (objective, reward, data, isCompleted); + } + else + MorphCooldown = reader.ReadPackedInt32(); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != RoguePC.PlayerId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || MeetingStates.FirstMeeting) return string.Empty; + + if (AllTasksCompleted) return Translator.GetString("Rogue.AllTasksCompleted"); + + if (CurrentTask.IsCompleted) return Translator.GetString("Rogue.TaskCompleted"); + + float d = Main.RealOptionsData.GetFloat(FloatOptionNames.KillCooldown); + + string c = GotRewards.Contains(Reward.Morph) && MorphCooldown > 0 + ? MorphCooldown <= d + ? string.Format(Translator.GetString("CDPT"), MorphCooldown) + "\n" + : $"\u21b9 ({MorphCooldown - d}s)" + : string.Empty; + + string o = Translator.GetString("Rogue.Objective." + CurrentTask.Objective); + if (CurrentTask.Objective is Objective.KillInSpecificRoom or Objective.VentXTimes or Objective.KillSpecificPlayer or Objective.KillXTimes) o = string.Format(o, CurrentTask.Data is byte id ? Utils.ColorString(Main.PlayerColors.GetValueOrDefault(id, Color.white), Utils.GetPlayerById(id).GetRealName()) : CurrentTask.Data); + + string r = Translator.GetString("Rogue.Reward." + CurrentTask.Reward); + string s = string.Format(Translator.GetString("Rogue.Task"), o, r); + return $"{c}{s}"; + } + + private enum Objective + { + KillInSpecificRoom, + VentXTimes, + CallEmergencyMeeting, + KillSpecificPlayer, + KillXTimes, + DontStopWalking, + FixSabotage + } + + private enum Reward + { + DisableDevices, + Morph, + IncreasedSpeed, + DecreasedKillCooldown, + Sabotage, + Shield, + LongReach, + HelpfulAddon, + ImpostorVision + } } \ No newline at end of file diff --git a/Roles/Neutral/Romantic.cs b/Roles/Neutral/Romantic.cs index 2c1623397..2e93f2305 100644 --- a/Roles/Neutral/Romantic.cs +++ b/Roles/Neutral/Romantic.cs @@ -7,444 +7,443 @@ using UnityEngine; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Romantic : RoleBase { - public class Romantic : RoleBase + private const int Id = 9850; + + public static byte RomanticId = byte.MaxValue; + public static PlayerControl RomanticPC; + public static byte PartnerId = byte.MaxValue; + public static PlayerControl Partner; + + public static bool IsPartnerProtected; + + public static OptionItem BetCooldown; + private static OptionItem ProtectCooldown; + private static OptionItem ProtectDuration; + private static OptionItem KnowTargetRole; + private static OptionItem BetTargetKnowRomantic; + private static OptionItem RomanticGetsPartnerConvertedAddons; + private static OptionItem Arrows; + private static OptionItem PartnerHasArrows; + public static OptionItem VengefulKCD; + public static OptionItem VengefulCanVent; + public static OptionItem VengefulHasImpVision; + public static OptionItem RuthlessKCD; + public static OptionItem RuthlessCanVent; + public static OptionItem RuthlessHasImpVision; + + private static readonly Dictionary ConvertingRolesAndAddons = new() { - private const int Id = 9850; + [CustomRoles.Succubus] = CustomRoles.Charmed, + [CustomRoles.Jackal] = CustomRoles.Sidekick, + [CustomRoles.Virus] = CustomRoles.Contagious, + [CustomRoles.Deathknight] = CustomRoles.Undead, + [CustomRoles.Necromancer] = CustomRoles.Undead + }; - public static byte RomanticId = byte.MaxValue; - public static PlayerControl RomanticPC; - public static byte PartnerId = byte.MaxValue; - public static PlayerControl Partner; + public static bool HasPickedPartner => PartnerId != byte.MaxValue; - public static bool IsPartnerProtected; + public override bool IsEnable => RomanticId != byte.MaxValue; - public static OptionItem BetCooldown; - private static OptionItem ProtectCooldown; - private static OptionItem ProtectDuration; - private static OptionItem KnowTargetRole; - private static OptionItem BetTargetKnowRomantic; - private static OptionItem RomanticGetsPartnerConvertedAddons; - private static OptionItem Arrows; - private static OptionItem PartnerHasArrows; - public static OptionItem VengefulKCD; - public static OptionItem VengefulCanVent; - public static OptionItem VengefulHasImpVision; - public static OptionItem RuthlessKCD; - public static OptionItem RuthlessCanVent; - public static OptionItem RuthlessHasImpVision; + public override void SetupCustomOption() + { + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Romantic); - private static readonly Dictionary ConvertingRolesAndAddons = new() + BetCooldown = new FloatOptionItem(Id + 10, "RomanticBetCooldown", new(0f, 60f, 1f), 7f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) + .SetValueFormat(OptionFormat.Seconds); + + ProtectCooldown = new FloatOptionItem(Id + 11, "RomanticProtectCooldown", new(0f, 60f, 2.5f), 25f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) + .SetValueFormat(OptionFormat.Seconds); + + ProtectDuration = new FloatOptionItem(Id + 12, "RomanticProtectDuration", new(0f, 60f, 2.5f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) + .SetValueFormat(OptionFormat.Seconds); + + KnowTargetRole = new BooleanOptionItem(Id + 13, "RomanticKnowTargetRole", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + BetTargetKnowRomantic = new BooleanOptionItem(Id + 14, "RomanticBetTargetKnowRomantic", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + RomanticGetsPartnerConvertedAddons = new BooleanOptionItem(Id + 15, "RomanticGetsPartnerConvertedAddons", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + Arrows = new BooleanOptionItem(Id + 16, "RomanticArrows", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + PartnerHasArrows = new BooleanOptionItem(Id + 17, "RomanticPartnerHasArrows", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + VengefulKCD = new FloatOptionItem(Id + 18, "VengefulKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) + .SetValueFormat(OptionFormat.Seconds); + + VengefulCanVent = new BooleanOptionItem(Id + 19, "VengefulCanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + VengefulHasImpVision = new BooleanOptionItem(Id + 20, "VengefulHasImpVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + RuthlessKCD = new FloatOptionItem(Id + 21, "RuthlessKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) + .SetValueFormat(OptionFormat.Seconds); + + RuthlessCanVent = new BooleanOptionItem(Id + 22, "RuthlessCanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + + RuthlessHasImpVision = new BooleanOptionItem(Id + 23, "RuthlessHasImpVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); + } + + public override void Init() + { + RomanticId = byte.MaxValue; + RomanticPC = null; + PartnerId = byte.MaxValue; + Partner = null; + IsPartnerProtected = false; + } + + public override void Add(byte playerId) + { + RomanticId = playerId; + RomanticPC = Utils.GetPlayerById(playerId); + PartnerId = byte.MaxValue; + Partner = null; + IsPartnerProtected = false; + } + + private static void SendRPC() + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRomanticTarget, SendOption.Reliable); + writer.Write(PartnerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + PartnerId = reader.ReadByte(); + Partner = Utils.GetPlayerById(PartnerId); + } + + public override void SetKillCooldown(byte id) + { + float beforeCD = Main.AllPlayerKillCooldown.GetValueOrDefault(RomanticId, 0f); + + if (HasPickedPartner) + Main.AllPlayerKillCooldown[RomanticId] = ProtectCooldown.GetFloat(); + else + Main.AllPlayerKillCooldown[RomanticId] = BetCooldown.GetFloat(); + + if (Math.Abs(beforeCD - Main.AllPlayerKillCooldown[RomanticId]) > 0.5f) RomanticPC?.SyncSettings(); + } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (!KnowTargetRole.GetBool()) return false; + + return (player.Is(CustomRoles.Romantic) && PartnerId == target.PlayerId) || (BetTargetKnowRomantic.GetBool() && target.Is(CustomRoles.Romantic) && player.PlayerId == PartnerId); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null || target == null || killer.PlayerId == target.PlayerId || killer.PlayerId != RomanticId) return true; + + if (!HasPickedPartner) { - [CustomRoles.Succubus] = CustomRoles.Charmed, - [CustomRoles.Jackal] = CustomRoles.Sidekick, - [CustomRoles.Virus] = CustomRoles.Contagious, - [CustomRoles.Deathknight] = CustomRoles.Undead, - [CustomRoles.Necromancer] = CustomRoles.Undead - }; + PartnerId = target.PlayerId; + Partner = target; - public static bool HasPickedPartner => PartnerId != byte.MaxValue; + SendRPC(); - public override bool IsEnable => RomanticId != byte.MaxValue; + RomanticPC.ResetKillCooldown(); + RomanticPC.SetKillCooldown(); + RomanticPC.RPCPlayCustomSound("Bet"); - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Romantic); + RomanticPC.Notify(GetString("RomanticBetPlayer")); + if (BetTargetKnowRomantic.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Romantic), GetString("RomanticBetOnYou"))); - BetCooldown = new FloatOptionItem(Id + 10, "RomanticBetCooldown", new(0f, 60f, 1f), 7f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) - .SetValueFormat(OptionFormat.Seconds); + if (RomanticGetsPartnerConvertedAddons.GetBool() && Partner.IsConverted()) Partner.GetCustomSubRoles().DoIf(x => x.IsConverted() && !RomanticPC.Is(x), x => RomanticPC.RpcSetCustomRole(x)); - ProtectCooldown = new FloatOptionItem(Id + 11, "RomanticProtectCooldown", new(0f, 60f, 2.5f), 25f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) - .SetValueFormat(OptionFormat.Seconds); - - ProtectDuration = new FloatOptionItem(Id + 12, "RomanticProtectDuration", new(0f, 60f, 2.5f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) - .SetValueFormat(OptionFormat.Seconds); - - KnowTargetRole = new BooleanOptionItem(Id + 13, "RomanticKnowTargetRole", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - BetTargetKnowRomantic = new BooleanOptionItem(Id + 14, "RomanticBetTargetKnowRomantic", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - RomanticGetsPartnerConvertedAddons = new BooleanOptionItem(Id + 15, "RomanticGetsPartnerConvertedAddons", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - Arrows = new BooleanOptionItem(Id + 16, "RomanticArrows", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - PartnerHasArrows = new BooleanOptionItem(Id + 17, "RomanticPartnerHasArrows", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - VengefulKCD = new FloatOptionItem(Id + 18, "VengefulKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) - .SetValueFormat(OptionFormat.Seconds); - - VengefulCanVent = new BooleanOptionItem(Id + 19, "VengefulCanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - VengefulHasImpVision = new BooleanOptionItem(Id + 20, "VengefulHasImpVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - RuthlessKCD = new FloatOptionItem(Id + 21, "RuthlessKCD", new(0f, 60f, 2.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]) - .SetValueFormat(OptionFormat.Seconds); - - RuthlessCanVent = new BooleanOptionItem(Id + 22, "RuthlessCanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - - RuthlessHasImpVision = new BooleanOptionItem(Id + 23, "RuthlessHasImpVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Romantic]); - } - - public override void Init() - { - RomanticId = byte.MaxValue; - RomanticPC = null; - PartnerId = byte.MaxValue; - Partner = null; - IsPartnerProtected = false; - } - - public override void Add(byte playerId) - { - RomanticId = playerId; - RomanticPC = Utils.GetPlayerById(playerId); - PartnerId = byte.MaxValue; - Partner = null; - IsPartnerProtected = false; - } - - private static void SendRPC() - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncRomanticTarget, SendOption.Reliable); - writer.Write(PartnerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - PartnerId = reader.ReadByte(); - Partner = Utils.GetPlayerById(PartnerId); - } - - public override void SetKillCooldown(byte id) - { - float beforeCD = Main.AllPlayerKillCooldown.GetValueOrDefault(RomanticId, 0f); - - if (HasPickedPartner) - Main.AllPlayerKillCooldown[RomanticId] = ProtectCooldown.GetFloat(); - else - Main.AllPlayerKillCooldown[RomanticId] = BetCooldown.GetFloat(); - - if (Math.Abs(beforeCD - Main.AllPlayerKillCooldown[RomanticId]) > 0.5f) RomanticPC?.SyncSettings(); - } - - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; - - if (!KnowTargetRole.GetBool()) return false; - - return (player.Is(CustomRoles.Romantic) && PartnerId == target.PlayerId) || (BetTargetKnowRomantic.GetBool() && target.Is(CustomRoles.Romantic) && player.PlayerId == PartnerId); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null || target == null || killer.PlayerId == target.PlayerId || killer.PlayerId != RomanticId) return true; - - if (!HasPickedPartner) + if (Arrows.GetBool()) { - PartnerId = target.PlayerId; - Partner = target; + TargetArrow.Add(RomanticId, PartnerId); + if (PartnerHasArrows.GetBool() && BetTargetKnowRomantic.GetBool()) TargetArrow.Add(PartnerId, RomanticId); + } - SendRPC(); + Logger.Info($"Partner picked: {RomanticPC.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Romantic"); + } + else if (!IsPartnerProtected) + { + IsPartnerProtected = true; + + RomanticPC.ResetKillCooldown(); + RomanticPC.SetKillCooldown(); + RomanticPC.RPCPlayCustomSound("Shield"); + + RomanticPC.Notify(GetString("RomanticProtectPartner")); + Partner.Notify(GetString("RomanticIsProtectingYou")); + + LateTask.New(() => + { + if (!Partner.IsAlive()) return; + + IsPartnerProtected = false; + + if (!GameStates.IsInTask) return; + + RomanticPC.Notify(GetString("ProtectingOver")); + Partner.Notify(GetString("ProtectingOver")); - RomanticPC.ResetKillCooldown(); RomanticPC.SetKillCooldown(); - RomanticPC.RPCPlayCustomSound("Bet"); - - RomanticPC.Notify(GetString("RomanticBetPlayer")); - if (BetTargetKnowRomantic.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Romantic), GetString("RomanticBetOnYou"))); - - if (RomanticGetsPartnerConvertedAddons.GetBool() && Partner.IsConverted()) Partner.GetCustomSubRoles().DoIf(x => x.IsConverted() && !RomanticPC.Is(x), x => RomanticPC.RpcSetCustomRole(x)); - - if (Arrows.GetBool()) - { - TargetArrow.Add(RomanticId, PartnerId); - if (PartnerHasArrows.GetBool() && BetTargetKnowRomantic.GetBool()) TargetArrow.Add(PartnerId, RomanticId); - } - - Logger.Info($"Partner picked: {RomanticPC.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Romantic"); - } - else if (!IsPartnerProtected) - { - IsPartnerProtected = true; - - RomanticPC.ResetKillCooldown(); - RomanticPC.SetKillCooldown(); - RomanticPC.RPCPlayCustomSound("Shield"); - - RomanticPC.Notify(GetString("RomanticProtectPartner")); - Partner.Notify(GetString("RomanticIsProtectingYou")); - - LateTask.New(() => - { - if (!Partner.IsAlive()) return; - - IsPartnerProtected = false; - - if (!GameStates.IsInTask) return; - - RomanticPC.Notify(GetString("ProtectingOver")); - Partner.Notify(GetString("ProtectingOver")); - - RomanticPC.SetKillCooldown(); - }, ProtectDuration.GetFloat(), "RomanticProtecting"); - } - - return false; + }, ProtectDuration.GetFloat(), "RomanticProtecting"); } - public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + return false; + } + + public override void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) + { + if (!lowLoad && (Partner == null || Partner.Data == null || Partner.Data.Disconnected || !Partner.IsAlive()) && RomanticPC.IsAlive() && RomanticPC.Is(CustomRoles.Romantic)) ChangeRole(); + } + + public static string TargetMark(PlayerControl seer, PlayerControl target) + { + if (!seer.Is(CustomRoles.Romantic)) { - if (!lowLoad && (Partner == null || Partner.Data == null || Partner.Data.Disconnected || !Partner.IsAlive()) && RomanticPC.IsAlive() && RomanticPC.Is(CustomRoles.Romantic)) ChangeRole(); - } + if (!BetTargetKnowRomantic.GetBool()) return string.Empty; - public static string TargetMark(PlayerControl seer, PlayerControl target) - { - if (!seer.Is(CustomRoles.Romantic)) - { - if (!BetTargetKnowRomantic.GetBool()) return string.Empty; - - return target.Is(CustomRoles.Romantic) && RomanticId == target.PlayerId && PartnerId == seer.PlayerId - ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Romantic), "♥") - : string.Empty; - } - - return PartnerId == target.PlayerId + return target.Is(CustomRoles.Romantic) && RomanticId == target.PlayerId && PartnerId == seer.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Romantic), "♥") : string.Empty; } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + return PartnerId == target.PlayerId + ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Romantic), "♥") + : string.Empty; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + if (seer.PlayerId == PartnerId && HasPickedPartner) return TargetArrow.GetArrows(seer, RomanticId); + + if (seer.PlayerId != RomanticId || !seer.Is(CustomRoles.Romantic)) return string.Empty; + + Color color = !HasPickedPartner ? Color.white : Utils.GetRoleColor(CustomRoles.Romantic); + string text = !HasPickedPartner ? "PICK PARTNER" : "♥"; + if (Arrows.GetBool()) text += TargetArrow.GetArrows(seer, PartnerId); + + return Utils.ColorString(color, text); + } + + public override void OnReportDeadBody() + { + IsPartnerProtected = false; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + private static void ChangeRole() + { + if (Partner == null || RomanticPC == null || !RomanticPC.Is(CustomRoles.Romantic)) return; + + CustomRoles partnerRole = Partner.GetCustomRole(); + PlayerControl killer = Partner.GetRealKiller(); + + if (killer?.PlayerId == RomanticId) // If the Romantic killed their Partner, they also die { - if (seer.PlayerId != target.PlayerId) return string.Empty; + if (!RomanticPC.IsAlive()) return; - if (seer.PlayerId == PartnerId && HasPickedPartner) return TargetArrow.GetArrows(seer, RomanticId); - - if (seer.PlayerId != RomanticId || !seer.Is(CustomRoles.Romantic)) return string.Empty; - - Color color = !HasPickedPartner ? Color.white : Utils.GetRoleColor(CustomRoles.Romantic); - string text = !HasPickedPartner ? "PICK PARTNER" : "♥"; - if (Arrows.GetBool()) text += TargetArrow.GetArrows(seer, PartnerId); - - return Utils.ColorString(color, text); + Logger.Info("Romantic killed their own Partner, Romantic suicides", "Romantic"); + RomanticPC.Suicide(PlayerState.DeathReason.FollowingSuicide, Partner); + return; } - public override void OnReportDeadBody() + if ((partnerRole.IsNonNK() && partnerRole is not CustomRoles.Romantic and not CustomRoles.VengefulRomantic and not CustomRoles.RuthlessRomantic) || killer == null || !killer.IsAlive() || Main.PlayerStates[PartnerId].IsSuicide) // If Partner is NNK or died by themselves, Romantic becomes Ruthless Romantic { - IsPartnerProtected = false; + Logger.Info($"NNK Romantic Partner Died ({partnerRole.IsNonNK()}) / Partner killer is null ({killer == null}) / Partner killer is dead ({!killer.IsAlive()}) / Partner commited Suicide ({Main.PlayerStates[PartnerId].IsSuicide}) => Changing {RomanticPC.GetNameWithRole().RemoveHtmlTags()} to Ruthless Romantic", "Romantic"); + RomanticPC.RpcSetCustomRole(CustomRoles.RuthlessRomantic); } - - public override bool CanUseImpostorVentButton(PlayerControl pc) + else if (ConvertingRolesAndAddons.TryGetValue(partnerRole, out CustomRoles convertedRole)) { - return false; + RomanticPC.RpcSetCustomRole(convertedRole); + if (convertedRole.IsAdditionRole()) RomanticPC.RpcSetCustomRole(CustomRoles.RuthlessRomantic); + + Logger.Info($"Converting Romantic Partner Died ({Partner.GetNameWithRole()}) => Romantic becomes their ally ({RomanticPC.GetNameWithRole()})", "Romantic"); } - - public override bool CanUseKillButton(PlayerControl pc) + else if (Partner.Is(Team.Impostor)) // If Partner is Imp, Romantic joins the imp team as Refugee { - return pc.IsAlive(); + Logger.Info($"Impostor Romantic Partner Died => Changing {RomanticPC.GetNameWithRole()} to Refugee", "Romantic"); + RomanticPC.RpcSetCustomRole(CustomRoles.Refugee); } - - private static void ChangeRole() + else if (Partner.HasKillButton() || partnerRole.IsNK() || partnerRole.GetDYRole() == RoleTypes.Impostor) // If the Partner has a kill button (NK or CK), Romantic becomes the role they were { - if (Partner == null || RomanticPC == null || !RomanticPC.Is(CustomRoles.Romantic)) return; - - CustomRoles partnerRole = Partner.GetCustomRole(); - PlayerControl killer = Partner.GetRealKiller(); - - if (killer?.PlayerId == RomanticId) // If the Romantic killed their Partner, they also die + try { - if (!RomanticPC.IsAlive()) return; - - Logger.Info("Romantic killed their own Partner, Romantic suicides", "Romantic"); - RomanticPC.Suicide(PlayerState.DeathReason.FollowingSuicide, Partner); - return; + RomanticPC.RpcSetCustomRole(partnerRole); + Main.PlayerStates[RomanticId].RemoveSubRole(CustomRoles.NotAssigned); + Logger.Info($"Romantic Partner With Kill Button Died => Changing {RomanticPC.GetNameWithRole()} to {Partner.GetAllRoleName().RemoveHtmlTags()}", "Romantic"); } - - if ((partnerRole.IsNonNK() && partnerRole is not CustomRoles.Romantic and not CustomRoles.VengefulRomantic and not CustomRoles.RuthlessRomantic) || killer == null || !killer.IsAlive() || Main.PlayerStates[PartnerId].IsSuicide) // If Partner is NNK or died by themselves, Romantic becomes Ruthless Romantic + catch { - Logger.Info($"NNK Romantic Partner Died ({partnerRole.IsNonNK()}) / Partner killer is null ({killer == null}) / Partner killer is dead ({!killer.IsAlive()}) / Partner commited Suicide ({Main.PlayerStates[PartnerId].IsSuicide}) => Changing {RomanticPC.GetNameWithRole().RemoveHtmlTags()} to Ruthless Romantic", "Romantic"); + Logger.Error($"Romantic Partner With Kill Button Died => Changing {RomanticPC.GetNameWithRole()} to {Partner.GetAllRoleName().RemoveHtmlTags()} : FAILED ----> changing to Ruthless Romantic instead", "Romantic"); RomanticPC.RpcSetCustomRole(CustomRoles.RuthlessRomantic); } - else if (ConvertingRolesAndAddons.TryGetValue(partnerRole, out CustomRoles convertedRole)) - { - RomanticPC.RpcSetCustomRole(convertedRole); - if (convertedRole.IsAdditionRole()) RomanticPC.RpcSetCustomRole(CustomRoles.RuthlessRomantic); - - Logger.Info($"Converting Romantic Partner Died ({Partner.GetNameWithRole()}) => Romantic becomes their ally ({RomanticPC.GetNameWithRole()})", "Romantic"); - } - else if (Partner.Is(Team.Impostor)) // If Partner is Imp, Romantic joins the imp team as Refugee - { - Logger.Info($"Impostor Romantic Partner Died => Changing {RomanticPC.GetNameWithRole()} to Refugee", "Romantic"); - RomanticPC.RpcSetCustomRole(CustomRoles.Refugee); - } - else if (Partner.HasKillButton() || partnerRole.IsNK() || partnerRole.GetDYRole() == RoleTypes.Impostor) // If the Partner has a kill button (NK or CK), Romantic becomes the role they were - { - try - { - RomanticPC.RpcSetCustomRole(partnerRole); - Main.PlayerStates[RomanticId].RemoveSubRole(CustomRoles.NotAssigned); - Logger.Info($"Romantic Partner With Kill Button Died => Changing {RomanticPC.GetNameWithRole()} to {Partner.GetAllRoleName().RemoveHtmlTags()}", "Romantic"); - } - catch - { - Logger.Error($"Romantic Partner With Kill Button Died => Changing {RomanticPC.GetNameWithRole()} to {Partner.GetAllRoleName().RemoveHtmlTags()} : FAILED ----> changing to Ruthless Romantic instead", "Romantic"); - RomanticPC.RpcSetCustomRole(CustomRoles.RuthlessRomantic); - } - } - else // In every other scenario, Romantic becomes Vengeful Romantic and must kill the killer of their Partner - { - Logger.Info($"Non-Killing Crew Romantic Partner Died => Changing {RomanticPC.GetNameWithRole()} to Vengeful Romantic", "Romantic"); - - RomanticPC.RpcSetCustomRole(CustomRoles.VengefulRomantic); - VengefulRomantic.Target = killer.PlayerId; - VengefulRomantic.SendRPC(); - } - - if (RomanticGetsPartnerConvertedAddons.GetBool() && Partner.IsConverted()) Partner.GetCustomSubRoles().DoIf(x => x.IsConverted() && !RomanticPC.Is(x), x => RomanticPC.RpcSetCustomRole(x)); - - RomanticPC.SetKillCooldown(); } - - public override void SetButtonTexts(HudManager hud, byte id) + else // In every other scenario, Romantic becomes Vengeful Romantic and must kill the killer of their Partner { - hud.KillButton?.OverrideText(!HasPickedPartner ? GetString("RomanticKillButtonText") : GetString("MedicalerButtonText")); + Logger.Info($"Non-Killing Crew Romantic Partner Died => Changing {RomanticPC.GetNameWithRole()} to Vengeful Romantic", "Romantic"); + + RomanticPC.RpcSetCustomRole(CustomRoles.VengefulRomantic); + VengefulRomantic.Target = killer.PlayerId; + VengefulRomantic.SendRPC(); } + + if (RomanticGetsPartnerConvertedAddons.GetBool() && Partner.IsConverted()) Partner.GetCustomSubRoles().DoIf(x => x.IsConverted() && !RomanticPC.Is(x), x => RomanticPC.RpcSetCustomRole(x)); + + RomanticPC.SetKillCooldown(); } - public class VengefulRomantic : RoleBase + public override void SetButtonTexts(HudManager hud, byte id) { - private static byte VengefulRomanticId = byte.MaxValue; + hud.KillButton?.OverrideText(!HasPickedPartner ? GetString("RomanticKillButtonText") : GetString("MedicalerButtonText")); + } +} - public static bool HasKilledKiller; - public static byte Target = byte.MaxValue; +public class VengefulRomantic : RoleBase +{ + private static byte VengefulRomanticId = byte.MaxValue; - public override bool IsEnable => VengefulRomanticId != byte.MaxValue; + public static bool HasKilledKiller; + public static byte Target = byte.MaxValue; - public override void SetupCustomOption() { } + public override bool IsEnable => VengefulRomanticId != byte.MaxValue; - public override void Init() - { - VengefulRomanticId = byte.MaxValue; - Target = byte.MaxValue; - HasKilledKiller = false; - } + public override void SetupCustomOption() { } - public override void Add(byte playerId) - { - VengefulRomanticId = playerId; - Utils.GetPlayerById(playerId); - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead && !HasKilledKiller; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return Romantic.VengefulCanVent.GetBool(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Romantic.VengefulKCD.GetFloat(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.PlayerId == target.PlayerId || killer.PlayerId != VengefulRomanticId || target.PlayerId == Target) return true; - - killer.Suicide(PlayerState.DeathReason.Misfire); - return false; - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (target.PlayerId == Target) HasKilledKiller = true; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != VengefulRomanticId) return string.Empty; - - return seer == null ? string.Empty : Utils.ColorString(HasKilledKiller ? Color.green : Utils.GetRoleColor(CustomRoles.VengefulRomantic), $"{(HasKilledKiller ? "✓" : "☹")}"); - } - - public static void SendRPC() - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncVengefulRomanticTarget, SendOption.Reliable); - writer.Write(Target); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte target = reader.ReadByte(); - Target = target; - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(GetString("VengefulRomanticKillButtonText")); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(Romantic.VengefulHasImpVision.GetBool()); - } + public override void Init() + { + VengefulRomanticId = byte.MaxValue; + Target = byte.MaxValue; + HasKilledKiller = false; } - public class RuthlessRomantic : RoleBase + public override void Add(byte playerId) { - public static List PlayerIdList = []; + VengefulRomanticId = playerId; + Utils.GetPlayerById(playerId); + } - public override bool IsEnable => PlayerIdList.Count > 0; + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead && !HasKilledKiller; + } - public override void SetupCustomOption() { } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return Romantic.VengefulCanVent.GetBool(); + } - public override void Init() - { - PlayerIdList = []; - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Romantic.VengefulKCD.GetFloat(); + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.PlayerId == target.PlayerId || killer.PlayerId != VengefulRomanticId || target.PlayerId == Target) return true; - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return Romantic.RuthlessCanVent.GetBool(); - } + killer.Suicide(PlayerState.DeathReason.Misfire); + return false; + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = Romantic.RuthlessKCD.GetFloat(); - } + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (target.PlayerId == Target) HasKilledKiller = true; + } - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(Romantic.RuthlessHasImpVision.GetBool()); - } + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != VengefulRomanticId) return string.Empty; + + return seer == null ? string.Empty : Utils.ColorString(HasKilledKiller ? Color.green : Utils.GetRoleColor(CustomRoles.VengefulRomantic), $"{(HasKilledKiller ? "✓" : "☹")}"); + } + + public static void SendRPC() + { + if (!Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncVengefulRomanticTarget, SendOption.Reliable); + writer.Write(Target); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte target = reader.ReadByte(); + Target = target; + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(GetString("VengefulRomanticKillButtonText")); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(Romantic.VengefulHasImpVision.GetBool()); + } +} + +public class RuthlessRomantic : RoleBase +{ + public static List PlayerIdList = []; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return Romantic.RuthlessCanVent.GetBool(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = Romantic.RuthlessKCD.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(Romantic.RuthlessHasImpVision.GetBool()); } } \ No newline at end of file diff --git a/Roles/Neutral/RouleteGrandeur.cs b/Roles/Neutral/RouleteGrandeur.cs index 1256b2008..66479d690 100644 --- a/Roles/Neutral/RouleteGrandeur.cs +++ b/Roles/Neutral/RouleteGrandeur.cs @@ -4,179 +4,178 @@ using AmongUs.GameOptions; using EHR.Modules; using Hazel; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class RouleteGrandeur : RoleBase { - public class RouleteGrandeur : RoleBase + private const int Id = 647500; + private const int BulletCount = 5; + private const char HitIcon = '\u29bf'; + private const char NoHitIcon = '\u25ef'; + + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + private static OptionItem KCDReduction; + + private int Bullets; + private float KCD; + private long LastRoll; + private byte RouleteGrandeurId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 647500; - private const int BulletCount = 5; - private const char HitIcon = '\u29bf'; - private const char NoHitIcon = '\u25ef'; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.RouleteGrandeur); - public static bool On; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.RouleteGrandeur]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - private static OptionItem KCDReduction; + HasImpostorVision = new BooleanOptionItem(Id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.RouleteGrandeur]); - private int Bullets; - private float KCD; - private long LastRoll; - private byte RouleteGrandeurId; + KCDReduction = new FloatOptionItem(Id + 4, "KCDReduction", new(0f, 100f, 0.5f), 3.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.RouleteGrandeur]) + .SetValueFormat(OptionFormat.Seconds); + } - public override bool IsEnable => On; + public override void Init() + { + On = false; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + On = true; + Bullets = 1; + LastRoll = 0; + KCD = KillCooldown.GetFloat(); + RouleteGrandeurId = playerId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KCD; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return true; + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } + + public override void OnExitVent(PlayerControl pc, Vent vent) + { + Bullets = Bullets >= BulletCount - 1 ? 1 : Bullets + 1; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 0, Bullets); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool OnSabotage(PlayerControl pc) + { + Roll(pc); + return false; + } + + public override void OnPet(PlayerControl pc) + { + Roll(pc); + } + + public override bool OnVanish(PlayerControl pc) + { + Roll(pc); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + + Roll(shapeshifter); + return false; + } + + private void Roll(PlayerControl pc) + { + long now = Utils.TimeStamp; + if (now - LastRoll < 5) return; + + LastRoll = now; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, LastRoll); + + string result = new(NoHitIcon, BulletCount); + result = $"<#ffffff>{result}"; + HashSet takenSlots = []; + + for (var i = 0; i < Bullets; i++) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.RouleteGrandeur); + int slot; - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.RouleteGrandeur]) - .SetValueFormat(OptionFormat.Seconds); + do + slot = IRandom.Instance.Next(BulletCount); + while (!takenSlots.Add(slot)); - HasImpostorVision = new BooleanOptionItem(Id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.RouleteGrandeur]); - - KCDReduction = new FloatOptionItem(Id + 4, "KCDReduction", new(0f, 100f, 0.5f), 3.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.RouleteGrandeur]) - .SetValueFormat(OptionFormat.Seconds); + result = result.Remove(slot, 1).Insert(slot, HitIcon.ToString()); } - public override void Init() + int hitSlot = IRandom.Instance.Next(BulletCount); + char icon = result[hitSlot]; + result = result.Remove(hitSlot, 1).Insert(hitSlot, $"{icon}"); + + bool hit = icon == HitIcon; + + string str; + + if (hit) { - On = false; + pc.Suicide(PlayerState.DeathReason.BadLuck); + str = Translator.GetString("RG.Hit"); + } + else + { + var reduction = (float)Math.Round(Bullets * KCDReduction.GetFloat(), 1); + str = string.Format(Translator.GetString("RG.NoHit"), Bullets, reduction); + KCD -= reduction; + pc.ResetKillCooldown(); + pc.MarkDirtySettings(); } - public override void Add(byte playerId) + pc.Notify($"{str}\n{result}", 15f); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - On = true; - Bullets = 1; - LastRoll = 0; - KCD = KillCooldown.GetFloat(); - RouleteGrandeurId = playerId; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KCD; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return true; - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - Bullets = Bullets >= BulletCount - 1 ? 1 : Bullets + 1; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 0, Bullets); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool OnSabotage(PlayerControl pc) - { - Roll(pc); - return false; - } - - public override void OnPet(PlayerControl pc) - { - Roll(pc); - } - - public override bool OnVanish(PlayerControl pc) - { - Roll(pc); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - - Roll(shapeshifter); - return false; - } - - private void Roll(PlayerControl pc) - { - long now = Utils.TimeStamp; - if (now - LastRoll < 5) return; - - LastRoll = now; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, LastRoll); - - string result = new(NoHitIcon, BulletCount); - result = $"<#ffffff>{result}"; - HashSet takenSlots = []; - - for (var i = 0; i < Bullets; i++) - { - int slot; - - do - slot = IRandom.Instance.Next(BulletCount); - while (!takenSlots.Add(slot)); - - result = result.Remove(slot, 1).Insert(slot, HitIcon.ToString()); - } - - int hitSlot = IRandom.Instance.Next(BulletCount); - char icon = result[hitSlot]; - result = result.Remove(hitSlot, 1).Insert(hitSlot, $"{icon}"); - - bool hit = icon == HitIcon; - - string str; - - if (hit) - { - pc.Suicide(PlayerState.DeathReason.BadLuck); - str = Translator.GetString("RG.Hit"); - } - else - { - var reduction = (float)Math.Round(Bullets * KCDReduction.GetFloat(), 1); - str = string.Format(Translator.GetString("RG.NoHit"), Bullets, reduction); - KCD -= reduction; - pc.ResetKillCooldown(); - pc.MarkDirtySettings(); - } - - pc.Notify($"{str}\n{result}", 15f); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 0: - Bullets = reader.ReadPackedInt32(); - break; - case 1: - LastRoll = long.Parse(reader.ReadString()); - break; - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != RouleteGrandeurId || meeting || (seer.IsModClient() && !hud) || (!hud && Utils.TimeStamp - LastRoll < 15)) return string.Empty; - - return string.Format(Translator.GetString("RG.Suffix"), Bullets); + case 0: + Bullets = reader.ReadPackedInt32(); + break; + case 1: + LastRoll = long.Parse(reader.ReadString()); + break; } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != RouleteGrandeurId || meeting || (seer.IsModClient() && !hud) || (!hud && Utils.TimeStamp - LastRoll < 15)) return string.Empty; + + return string.Format(Translator.GetString("RG.Suffix"), Bullets); + } } \ No newline at end of file diff --git a/Roles/Neutral/Samurai.cs b/Roles/Neutral/Samurai.cs index 9c4c33eaf..bbf0a83e1 100644 --- a/Roles/Neutral/Samurai.cs +++ b/Roles/Neutral/Samurai.cs @@ -3,141 +3,140 @@ using AmongUs.GameOptions; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Samurai : RoleBase { - internal class Samurai : RoleBase + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem NearbyDuration; + private static OptionItem SuccessKCD; + private static OptionItem KillDelay; + private Dictionary Delays; + + private PlayerControl SamuraiPC; + public (byte Id, long TimeStamp) Target; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + const int id = 16880; + SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Samurai); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem NearbyDuration; - private static OptionItem SuccessKCD; - private static OptionItem KillDelay; - private Dictionary Delays; + KillCooldown = new FloatOptionItem(id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) + .SetValueFormat(OptionFormat.Seconds); - private PlayerControl SamuraiPC; - public (byte Id, long TimeStamp) Target; + CanVent = new BooleanOptionItem(id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]); - public override bool IsEnable => On; + HasImpostorVision = new BooleanOptionItem(id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]); - public override void SetupCustomOption() + NearbyDuration = new FloatOptionItem(id + 5, "Samurai.NearbyDuration", new(0f, 30f, 0.5f), 3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) + .SetValueFormat(OptionFormat.Seconds); + + SuccessKCD = new FloatOptionItem(id + 6, "Samurai.SuccessKCD", new(0f, 180f, 0.5f), 17.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) + .SetValueFormat(OptionFormat.Seconds); + + KillDelay = new FloatOptionItem(id + 7, "Samurai.KillDelay", new(0f, 60f, 0.5f), 5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + SamuraiPC = Utils.GetPlayerById(playerId); + Target = (byte.MaxValue, 0); + Delays = []; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return Target.Id == byte.MaxValue && pc.IsAlive(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (Target.Id != byte.MaxValue) return false; + + Target = (target.PlayerId, Utils.TimeStamp); + return false; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance != null) return; + + long now = Utils.TimeStamp; + + foreach (KeyValuePair kvp in Delays) { - const int id = 16880; - SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.Samurai); + PlayerControl player = Utils.GetPlayerById(kvp.Key); + if (player == null || !player.IsAlive()) continue; - KillCooldown = new FloatOptionItem(id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]); - - HasImpostorVision = new BooleanOptionItem(id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]); - - NearbyDuration = new FloatOptionItem(id + 5, "Samurai.NearbyDuration", new(0f, 30f, 0.5f), 3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) - .SetValueFormat(OptionFormat.Seconds); - - SuccessKCD = new FloatOptionItem(id + 6, "Samurai.SuccessKCD", new(0f, 180f, 0.5f), 17.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) - .SetValueFormat(OptionFormat.Seconds); - - KillDelay = new FloatOptionItem(id + 7, "Samurai.KillDelay", new(0f, 60f, 0.5f), 5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Samurai]) - .SetValueFormat(OptionFormat.Seconds); + if (kvp.Value + KillDelay.GetInt() <= now) + if (pc.RpcCheckAndMurder(player, true)) + player.Suicide(realKiller: pc); } - public override void Init() - { - On = false; - } + if (Target.Id == byte.MaxValue || !pc.IsAlive()) return; - public override void Add(byte playerId) + PlayerControl target = Utils.GetPlayerById(Target.Id); + if (target == null) return; + + if (Vector2.Distance(target.Pos(), pc.Pos()) > NormalGameOptionsV08.KillDistances[Mathf.Clamp(pc.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f) { - On = true; - SamuraiPC = Utils.GetPlayerById(playerId); Target = (byte.MaxValue, 0); - Delays = []; + pc.RpcCheckAndMurder(target); + return; } - public override void SetKillCooldown(byte id) + if (Target.TimeStamp + NearbyDuration.GetInt() <= now) { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return Target.Id == byte.MaxValue && pc.IsAlive(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (Target.Id != byte.MaxValue) return false; - - Target = (target.PlayerId, Utils.TimeStamp); - return false; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || ExileController.Instance != null) return; - - long now = Utils.TimeStamp; - - foreach (KeyValuePair kvp in Delays) - { - PlayerControl player = Utils.GetPlayerById(kvp.Key); - if (player == null || !player.IsAlive()) continue; - - if (kvp.Value + KillDelay.GetInt() <= now) - if (pc.RpcCheckAndMurder(player, true)) - player.Suicide(realKiller: pc); - } - - if (Target.Id == byte.MaxValue || !pc.IsAlive()) return; - - PlayerControl target = Utils.GetPlayerById(Target.Id); - if (target == null) return; - - if (Vector2.Distance(target.Pos(), pc.Pos()) > NormalGameOptionsV08.KillDistances[Mathf.Clamp(pc.Is(CustomRoles.Reach) ? 2 : Main.NormalOptions.KillDistance, 0, 2)] + 0.5f) - { - Target = (byte.MaxValue, 0); - pc.RpcCheckAndMurder(target); - return; - } - - if (Target.TimeStamp + NearbyDuration.GetInt() <= now) - { - Delays[Target.Id] = now; - Target = (byte.MaxValue, 0); - pc.SetKillCooldown(SuccessKCD.GetFloat()); - } - } - - public override void OnReportDeadBody() - { - foreach (byte id in Delays.Keys) - { - PlayerControl player = Utils.GetPlayerById(id); - if (player == null || !player.IsAlive()) continue; - - if (SamuraiPC.RpcCheckAndMurder(player, true)) player.Suicide(realKiller: SamuraiPC); - } - - Delays.Clear(); + Delays[Target.Id] = now; + Target = (byte.MaxValue, 0); + pc.SetKillCooldown(SuccessKCD.GetFloat()); } } + + public override void OnReportDeadBody() + { + foreach (byte id in Delays.Keys) + { + PlayerControl player = Utils.GetPlayerById(id); + if (player == null || !player.IsAlive()) continue; + + if (SamuraiPC.RpcCheckAndMurder(player, true)) player.Suicide(realKiller: SamuraiPC); + } + + Delays.Clear(); + } } \ No newline at end of file diff --git a/Roles/Neutral/SchrodingersCat.cs b/Roles/Neutral/SchrodingersCat.cs index edd698a8a..b5fcd107a 100644 --- a/Roles/Neutral/SchrodingersCat.cs +++ b/Roles/Neutral/SchrodingersCat.cs @@ -1,52 +1,51 @@ -namespace EHR.Impostor +namespace EHR.Impostor; + +internal class SchrodingersCat : RoleBase { - internal class SchrodingersCat : RoleBase + public static bool On; + + public static OptionItem WinsWithCrewIfNotAttacked; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + const int id = 13840; + Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.SchrodingersCat); - public static OptionItem WinsWithCrewIfNotAttacked; - public override bool IsEnable => On; + WinsWithCrewIfNotAttacked = new BooleanOptionItem(id + 2, "SchrodingersCat.WinsWithCrewIfNotAttacked", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SchrodingersCat]); + } - public override void SetupCustomOption() - { - const int id = 13840; - Options.SetupRoleOptions(id, TabGroup.NeutralRoles, CustomRoles.SchrodingersCat); + public override void Add(byte playerId) + { + On = true; + } - WinsWithCrewIfNotAttacked = new BooleanOptionItem(id + 2, "SchrodingersCat.WinsWithCrewIfNotAttacked", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.SchrodingersCat]); - } + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + CustomRoles killerRole = killer.GetCustomRole(); + if (killerRole.IsImpostor() || killerRole.IsMadmate()) killerRole = CustomRoles.Refugee; - public override void Init() - { - On = false; - } + if (killerRole == CustomRoles.Jackal) killerRole = CustomRoles.Sidekick; - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - CustomRoles killerRole = killer.GetCustomRole(); - if (killerRole.IsImpostor() || killerRole.IsMadmate()) killerRole = CustomRoles.Refugee; + if (Options.SingleRoles.Contains(killerRole)) killerRole = CustomRoles.Amnesiac; - if (killerRole == CustomRoles.Jackal) killerRole = CustomRoles.Sidekick; + target.RpcSetCustomRole(killerRole); + target.RpcChangeRoleBasis(killerRole); - if (Options.SingleRoles.Contains(killerRole)) killerRole = CustomRoles.Amnesiac; + killer.SetKillCooldown(5f); - target.RpcSetCustomRole(killerRole); - target.RpcChangeRoleBasis(killerRole); + killer.Notify(string.Format(Translator.GetString("SchrodingersCat.Notify.KillerRecruited"), target.GetRealName(), CustomRoles.SchrodingersCat.ToColoredString()), 10f); + target.Notify(string.Format(Translator.GetString("SchrodingersCat.Notify.RecruitedByKiller"), killer.GetRealName(), killerRole.ToColoredString())); - killer.SetKillCooldown(5f); + Utils.NotifyRoles(SpecifySeer: killer, ForceLoop: true); + Utils.NotifyRoles(SpecifySeer: target, ForceLoop: true); - killer.Notify(string.Format(Translator.GetString("SchrodingersCat.Notify.KillerRecruited"), target.GetRealName(), CustomRoles.SchrodingersCat.ToColoredString()), 10f); - target.Notify(string.Format(Translator.GetString("SchrodingersCat.Notify.RecruitedByKiller"), killer.GetRealName(), killerRole.ToColoredString())); - - Utils.NotifyRoles(SpecifySeer: killer, ForceLoop: true); - Utils.NotifyRoles(SpecifySeer: target, ForceLoop: true); - - return false; - } + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Shifter.cs b/Roles/Neutral/Shifter.cs index ca970e1f4..1f3abae7e 100644 --- a/Roles/Neutral/Shifter.cs +++ b/Roles/Neutral/Shifter.cs @@ -3,141 +3,140 @@ using AmongUs.GameOptions; using EHR.Modules; using EHR.Patches; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Shifter : RoleBase { - public class Shifter : RoleBase + public static bool On; + + public static HashSet WasShifter = []; + private static int ShifterInteractionsCount; + + public static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + public static OptionItem CanGuess; + private static OptionItem CanBeKilled; + public static OptionItem CanBeVoted; + public static OptionItem CanVote; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(644400) + .AutoSetupOption(ref KillCooldown, 15f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds, "AbilityCooldown") + .AutoSetupOption(ref CanVent, true) + .AutoSetupOption(ref HasImpostorVision, true, "ImpostorVision") + .AutoSetupOption(ref CanGuess, false) + .AutoSetupOption(ref CanBeKilled, true) + .AutoSetupOption(ref CanBeVoted, true) + .AutoSetupOption(ref CanVote, true); + } - public static HashSet WasShifter = []; - private static int ShifterInteractionsCount; + public override void Init() + { + if (GameStates.InGame && !Main.HasJustStarted) return; - public static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - public static OptionItem CanGuess; - private static OptionItem CanBeKilled; - public static OptionItem CanBeVoted; - public static OptionItem CanVote; + On = false; - public override bool IsEnable => On; + WasShifter = []; - public override void SetupCustomOption() + ShifterInteractionsCount = 0; + } + + public override void Add(byte playerId) + { + On = true; + + PlayerControl pc = playerId.GetPlayer(); + if (pc == null) return; + + pc.AddAbilityCD(); + pc.ResetKillCooldown(); + pc.SyncSettings(); + pc.SetKillCooldown(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return true; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + CustomRoles targetRole = target.GetCustomRole(); + + Utils.RemovePlayerFromPreviousRoleData(target); + + killer.RpcSetCustomRole(targetRole); + killer.RpcChangeRoleBasis(targetRole); + + RoleBase targetRoleBase = Main.PlayerStates[target.PlayerId].Role; + LateTask.New(() => Main.PlayerStates[killer.PlayerId].Role = targetRoleBase, 0.5f, "Change RoleBase"); + + killer.SetAbilityUseLimit(target.GetAbilityUseLimit()); + + TaskState taskState = target.GetTaskState(); + if (taskState.HasTasks) Main.PlayerStates[killer.PlayerId].TaskState = taskState; + + killer.RemoveAbilityCD(); + killer.SyncSettings(); + + // ------------------------------------------------------------------------------------------ + + target.RpcSetCustomRole(CustomRoles.Shifter); + target.RpcChangeRoleBasis(CustomRoles.Shifter); + Main.AbilityUseLimit.Remove(target.PlayerId); + Utils.SendRPC(CustomRPC.RemoveAbilityUseLimit, target.PlayerId); + target.SyncSettings(); + LateTask.New(() => target.SetKillCooldown(), 0.2f, log: false); + + // ------------------------------------------------------------------------------------------ + + Utils.NotifyRoles(SpecifyTarget: killer); + Utils.NotifyRoles(SpecifyTarget: target); + + WasShifter.Add(killer.PlayerId); + + if (killer.IsLocalPlayer() || target.IsLocalPlayer()) { - StartSetup(644400) - .AutoSetupOption(ref KillCooldown, 15f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds, "AbilityCooldown") - .AutoSetupOption(ref CanVent, true) - .AutoSetupOption(ref HasImpostorVision, true, "ImpostorVision") - .AutoSetupOption(ref CanGuess, false) - .AutoSetupOption(ref CanBeKilled, true) - .AutoSetupOption(ref CanBeVoted, true) - .AutoSetupOption(ref CanVote, true); + ShifterInteractionsCount++; + if (ShifterInteractionsCount >= 3) Achievements.Type.TheresThisGameMyDadTaughtMeItsCalledSwitch.Complete(); } - public override void Init() - { - if (GameStates.InGame && !Main.HasJustStarted) return; + return false; + } - On = false; + public override void OnPet(PlayerControl pc) + { + OnCheckMurder(pc, ExternalRpcPetPatch.SelectKillButtonTarget(pc)); + } - WasShifter = []; + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(Translator.GetString("ShifterKillButtonText")); + } - ShifterInteractionsCount = 0; - } - - public override void Add(byte playerId) - { - On = true; - - PlayerControl pc = playerId.GetPlayer(); - if (pc == null) return; - - pc.AddAbilityCD(); - pc.ResetKillCooldown(); - pc.SyncSettings(); - pc.SetKillCooldown(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return true; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - CustomRoles targetRole = target.GetCustomRole(); - - Utils.RemovePlayerFromPreviousRoleData(target); - - killer.RpcSetCustomRole(targetRole); - killer.RpcChangeRoleBasis(targetRole); - - RoleBase targetRoleBase = Main.PlayerStates[target.PlayerId].Role; - LateTask.New(() => Main.PlayerStates[killer.PlayerId].Role = targetRoleBase, 0.5f, "Change RoleBase"); - - killer.SetAbilityUseLimit(target.GetAbilityUseLimit()); - - TaskState taskState = target.GetTaskState(); - if (taskState.HasTasks) Main.PlayerStates[killer.PlayerId].TaskState = taskState; - - killer.RemoveAbilityCD(); - killer.SyncSettings(); - - // ------------------------------------------------------------------------------------------ - - target.RpcSetCustomRole(CustomRoles.Shifter); - target.RpcChangeRoleBasis(CustomRoles.Shifter); - Main.AbilityUseLimit.Remove(target.PlayerId); - Utils.SendRPC(CustomRPC.RemoveAbilityUseLimit, target.PlayerId); - target.SyncSettings(); - LateTask.New(() => target.SetKillCooldown(), 0.2f, log: false); - - // ------------------------------------------------------------------------------------------ - - Utils.NotifyRoles(SpecifyTarget: killer); - Utils.NotifyRoles(SpecifyTarget: target); - - WasShifter.Add(killer.PlayerId); - - if (killer.IsLocalPlayer() || target.IsLocalPlayer()) - { - ShifterInteractionsCount++; - if (ShifterInteractionsCount >= 3) Achievements.Type.TheresThisGameMyDadTaughtMeItsCalledSwitch.Complete(); - } - - return false; - } - - public override void OnPet(PlayerControl pc) - { - OnCheckMurder(pc, ExternalRpcPetPatch.SelectKillButtonTarget(pc)); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(Translator.GetString("ShifterKillButtonText")); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return CanBeKilled.GetBool(); - } + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return CanBeKilled.GetBool(); } } \ No newline at end of file diff --git a/Roles/Neutral/Simon.cs b/Roles/Neutral/Simon.cs index 8f366a199..031eb38eb 100644 --- a/Roles/Neutral/Simon.cs +++ b/Roles/Neutral/Simon.cs @@ -6,231 +6,230 @@ using Hazel; using UnityEngine; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Simon : RoleBase { - public class Simon : RoleBase + public enum Instruction { - public enum Instruction + None, + Kill, + Task + } + + private const int Id = 12845; + public static bool On; + public static List Instances = []; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + + private bool DoMode; + private bool Executed; + private Dictionary MarkedPlayers; + private byte SimonId; + + public override bool IsEnable => On; + + public override void SetupCustomOption() + { + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Simon); + + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Simon]) + .SetValueFormat(OptionFormat.Seconds); + + HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Simon]); + } + + public override void Init() + { + On = false; + Instances = []; + } + + public override void Add(byte playerId) + { + On = true; + Instances.Add(this); + MarkedPlayers = []; + DoMode = true; + Executed = false; + SimonId = playerId; + Utils.SendRPC(CustomRPC.SyncRoleData, playerId, 1, DoMode); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!base.OnCheckMurder(killer, target)) return false; + + return killer.CheckDoubleTrigger(target, () => { - None, - Kill, - Task + MarkedPlayers[target.PlayerId] = (DoMode, Main.PlayerStates[target.PlayerId].Role.CanUseKillButton(target) ? Instruction.Kill : target.GetTaskState().HasTasks ? Instruction.Task : Instruction.None); + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 3, target.PlayerId, DoMode, (int)MarkedPlayers[target.PlayerId].Instruction); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); + }); + } + + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + if (Executed || MarkedPlayers.Count == 0) return; + + int size = MarkedPlayers.Count; + MarkedPlayers.Where(x => x.Value.Instruction == Instruction.None).ToList().ForEach(x => MarkedPlayers.Remove(x.Key)); + if (size != MarkedPlayers.Count) Utils.SendRPC(CustomRPC.SyncRoleData, physics.myPlayer.PlayerId, 2); + + Executed = true; + + foreach (KeyValuePair kvp in MarkedPlayers) + { + PlayerControl pc = Utils.GetPlayerById(kvp.Key); + if (pc == null || !pc.IsAlive()) continue; + + pc.Notify(Translator.GetString(GetNotify(kvp.Value.Instruction, kvp.Value.DoAction, false)), 300f); } - private const int Id = 12845; - public static bool On; - public static List Instances = []; + Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); + } - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - - private bool DoMode; - private bool Executed; - private Dictionary MarkedPlayers; - private byte SimonId; - - public override bool IsEnable => On; - - public override void SetupCustomOption() + private static string GetNotify(Instruction instruction, bool doAction, bool forSimon) + { + if (!forSimon) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Simon); + if (instruction == Instruction.Kill) return doAction ? "SimonKill" : "SimonDontKill"; - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Simon]) - .SetValueFormat(OptionFormat.Seconds); - - HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Simon]); + return doAction ? "SimonTask" : "SimonDontTask"; } - public override void Init() - { - On = false; - Instances = []; - } + return doAction ? "SimonDoShort" : "SimonDontShort"; + } - public override void Add(byte playerId) + public override void OnPet(PlayerControl pc) + { + DoMode = !DoMode; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, DoMode); + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + } + + public override bool OnSabotage(PlayerControl pc) + { + OnPet(pc); + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + OnPet(pc); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + OnPet(shapeshifter); + return false; + } + + public override void OnReportDeadBody() + { + if (Executed) { - On = true; - Instances.Add(this); - MarkedPlayers = []; - DoMode = true; Executed = false; - SimonId = playerId; - Utils.SendRPC(CustomRPC.SyncRoleData, playerId, 1, DoMode); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!base.OnCheckMurder(killer, target)) return false; - - return killer.CheckDoubleTrigger(target, () => - { - MarkedPlayers[target.PlayerId] = (DoMode, Main.PlayerStates[target.PlayerId].Role.CanUseKillButton(target) ? Instruction.Kill : target.GetTaskState().HasTasks ? Instruction.Task : Instruction.None); - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, 3, target.PlayerId, DoMode, (int)MarkedPlayers[target.PlayerId].Instruction); - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target); - }); - } - - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - if (Executed || MarkedPlayers.Count == 0) return; - - int size = MarkedPlayers.Count; - MarkedPlayers.Where(x => x.Value.Instruction == Instruction.None).ToList().ForEach(x => MarkedPlayers.Remove(x.Key)); - if (size != MarkedPlayers.Count) Utils.SendRPC(CustomRPC.SyncRoleData, physics.myPlayer.PlayerId, 2); - - Executed = true; foreach (KeyValuePair kvp in MarkedPlayers) { + if (!kvp.Value.DoAction || kvp.Value.Instruction == Instruction.None) continue; + PlayerControl pc = Utils.GetPlayerById(kvp.Key); if (pc == null || !pc.IsAlive()) continue; - pc.Notify(Translator.GetString(GetNotify(kvp.Value.Instruction, kvp.Value.DoAction, false)), 300f); - } - - Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); - } - - private static string GetNotify(Instruction instruction, bool doAction, bool forSimon) - { - if (!forSimon) - { - if (instruction == Instruction.Kill) return doAction ? "SimonKill" : "SimonDontKill"; - - return doAction ? "SimonTask" : "SimonDontTask"; - } - - return doAction ? "SimonDoShort" : "SimonDontShort"; - } - - public override void OnPet(PlayerControl pc) - { - DoMode = !DoMode; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, 1, DoMode); - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - } - - public override bool OnSabotage(PlayerControl pc) - { - OnPet(pc); - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - OnPet(pc); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - OnPet(shapeshifter); - return false; - } - - public override void OnReportDeadBody() - { - if (Executed) - { - Executed = false; - - foreach (KeyValuePair kvp in MarkedPlayers) - { - if (!kvp.Value.DoAction || kvp.Value.Instruction == Instruction.None) continue; - - PlayerControl pc = Utils.GetPlayerById(kvp.Key); - if (pc == null || !pc.IsAlive()) continue; - - pc.Suicide(); - } - } - - MarkedPlayers.Clear(); - Utils.SendRPC(CustomRPC.SyncRoleData, SimonId, 5); - } - - public void ReceiveRPC(MessageReader reader) - { - switch (reader.ReadPackedInt32()) - { - case 1: - DoMode = reader.ReadBoolean(); - break; - case 2: - MarkedPlayers.Where(x => x.Value.Instruction == Instruction.None).ToList().ForEach(x => MarkedPlayers.Remove(x.Key)); - break; - case 3: - MarkedPlayers[reader.ReadByte()] = (reader.ReadBoolean(), (Instruction)reader.ReadPackedInt32()); - break; - case 4: - MarkedPlayers.Remove(reader.ReadByte()); - break; - case 5: - MarkedPlayers.Clear(); - break; + pc.Suicide(); } } - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + MarkedPlayers.Clear(); + Utils.SendRPC(CustomRPC.SyncRoleData, SimonId, 5); + } + + public void ReceiveRPC(MessageReader reader) + { + switch (reader.ReadPackedInt32()) { - if (Main.PlayerStates[seer.PlayerId].Role is not Simon simon) return string.Empty; - - bool self = seer.PlayerId == target.PlayerId; - if (seer.IsModClient() && !hud && self) return string.Empty; - - if (self) return Translator.GetString(simon.DoMode ? "SimonDoMode" : "SimonDontMode"); - - if (simon.MarkedPlayers.TryGetValue(target.PlayerId, out (bool DoAction, Instruction Instruction) value)) return Translator.GetString(GetNotify(value.Instruction, value.DoAction, true)); - - return string.Empty; + case 1: + DoMode = reader.ReadBoolean(); + break; + case 2: + MarkedPlayers.Where(x => x.Value.Instruction == Instruction.None).ToList().ForEach(x => MarkedPlayers.Remove(x.Key)); + break; + case 3: + MarkedPlayers[reader.ReadByte()] = (reader.ReadBoolean(), (Instruction)reader.ReadPackedInt32()); + break; + case 4: + MarkedPlayers.Remove(reader.ReadByte()); + break; + case 5: + MarkedPlayers.Clear(); + break; } + } - public static void RemoveTarget(PlayerControl pc, Instruction instruction) + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (Main.PlayerStates[seer.PlayerId].Role is not Simon simon) return string.Empty; + + bool self = seer.PlayerId == target.PlayerId; + if (seer.IsModClient() && !hud && self) return string.Empty; + + if (self) return Translator.GetString(simon.DoMode ? "SimonDoMode" : "SimonDontMode"); + + if (simon.MarkedPlayers.TryGetValue(target.PlayerId, out (bool DoAction, Instruction Instruction) value)) return Translator.GetString(GetNotify(value.Instruction, value.DoAction, true)); + + return string.Empty; + } + + public static void RemoveTarget(PlayerControl pc, Instruction instruction) + { + foreach (Simon simon in Instances) { - foreach (Simon simon in Instances) + if (!simon.Executed) continue; + + if (simon.MarkedPlayers.TryGetValue(pc.PlayerId, out (bool DoAction, Instruction Instruction) value)) { - if (!simon.Executed) continue; + if (value.Instruction != instruction) continue; - if (simon.MarkedPlayers.TryGetValue(pc.PlayerId, out (bool DoAction, Instruction Instruction) value)) - { - if (value.Instruction != instruction) continue; + if (value.DoAction) pc.Notify(Utils.ColorString(Color.green, "\u2713")); + else pc.Suicide(); - if (value.DoAction) pc.Notify(Utils.ColorString(Color.green, "\u2713")); - else pc.Suicide(); + simon.MarkedPlayers.Remove(pc.PlayerId); + Utils.SendRPC(CustomRPC.SyncRoleData, simon.SimonId, 4, pc.PlayerId); - simon.MarkedPlayers.Remove(pc.PlayerId); - Utils.SendRPC(CustomRPC.SyncRoleData, simon.SimonId, 4, pc.PlayerId); - - if (pc.IsLocalPlayer()) - Achievements.Type.SimonSays.Complete(); - } + if (pc.IsLocalPlayer()) + Achievements.Type.SimonSays.Complete(); } } } diff --git a/Roles/Neutral/SoulHunter.cs b/Roles/Neutral/SoulHunter.cs index 2d2f5570f..b6b0e7b88 100644 --- a/Roles/Neutral/SoulHunter.cs +++ b/Roles/Neutral/SoulHunter.cs @@ -7,268 +7,267 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class SoulHunter : RoleBase { - internal class SoulHunter : RoleBase + public static OptionItem CanVent; + private static OptionItem HasImpostorVision; + public static OptionItem NumOfSoulsToWin; + private static OptionItem WaitingTimeAfterMeeting; + private static OptionItem TimeToKillTarget; + private static OptionItem GetSoulForSuicide; + public (byte ID, long StartTimeStamp, bool Frozen) CurrentTarget = (byte.MaxValue, 0, false); + private long LastUpdate; + private float NormalSpeed; + public PlayerControl SoulHunter_; + + private byte SoulHunterId; + public int Souls; + private static int Id => 643400; + + public override bool IsEnable => SoulHunterId != byte.MaxValue; + + private bool IsTargetBlocked => IsEnable && CurrentTarget.ID != byte.MaxValue && CurrentTarget.StartTimeStamp != 0; + + public override void SetupCustomOption() { - public static OptionItem CanVent; - private static OptionItem HasImpostorVision; - public static OptionItem NumOfSoulsToWin; - private static OptionItem WaitingTimeAfterMeeting; - private static OptionItem TimeToKillTarget; - private static OptionItem GetSoulForSuicide; - public (byte ID, long StartTimeStamp, bool Frozen) CurrentTarget = (byte.MaxValue, 0, false); - private long LastUpdate; - private float NormalSpeed; - public PlayerControl SoulHunter_; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulHunter); - private byte SoulHunterId; - public int Souls; - private static int Id => 643400; + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - public override bool IsEnable => SoulHunterId != byte.MaxValue; + HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - private bool IsTargetBlocked => IsEnable && CurrentTarget.ID != byte.MaxValue && CurrentTarget.StartTimeStamp != 0; + NumOfSoulsToWin = new IntegerOptionItem(Id + 5, "SoulHunterNumOfSoulsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - public override void SetupCustomOption() + WaitingTimeAfterMeeting = new IntegerOptionItem(Id + 6, "SoulHunterFreezeTimeAfterMeeting", new(0, 90, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]) + .SetValueFormat(OptionFormat.Seconds); + + TimeToKillTarget = new IntegerOptionItem(Id + 7, "SoulHunterTimeToKillTarget", new(1, 90, 1), 30, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]) + .SetValueFormat(OptionFormat.Seconds); + + GetSoulForSuicide = new BooleanOptionItem(Id + 8, "SoulHunterGetSoulForSuicide", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); + } + + public override void Init() + { + SoulHunterId = byte.MaxValue; + SoulHunter_ = null; + Souls = 0; + CurrentTarget = (byte.MaxValue, 0, false); + LastUpdate = 0; + NormalSpeed = Main.NormalOptions.PlayerSpeedMod; + } + + public override void Add(byte playerId) + { + SoulHunterId = playerId; + LateTask.New(() => { SoulHunter_ = GetPlayerById(playerId); }, 3f, log: false); + Souls = 0; + CurrentTarget = (byte.MaxValue, 0, false); + LastUpdate = 0; + NormalSpeed = Main.AllPlayerSpeed[playerId]; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = WaitingTimeAfterMeeting.GetFloat() + 1.5f; + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public static bool IsSoulHunterTarget(byte id) + { + return Main.PlayerStates.Any(x => x.Value.Role is SoulHunter { IsEnable: true, IsTargetBlocked: true } sh && sh.CurrentTarget.ID == id); + } + + private void SendRPC() + { + MessageWriter writer = CreateRPC(CustomRPC.SyncSoulHunter); + writer.Write(SoulHunterId); + writer.Write(Souls); + writer.Write(CurrentTarget.ID); + writer.Write(CurrentTarget.StartTimeStamp.ToString()); + writer.Write(CurrentTarget.Frozen); + EndRPC(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not SoulHunter sh) return; + + sh.Souls = reader.ReadInt32(); + sh.CurrentTarget.ID = reader.ReadByte(); + sh.CurrentTarget.StartTimeStamp = long.Parse(reader.ReadString()); + sh.CurrentTarget.Frozen = reader.ReadBoolean(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (SoulHunter_ == null || target == null) return false; + + if (CurrentTarget.ID == byte.MaxValue || CurrentTarget.StartTimeStamp == 0) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.SoulHunter); - - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - - HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - - NumOfSoulsToWin = new IntegerOptionItem(Id + 5, "SoulHunterNumOfSoulsToWin", new(1, 14, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - - WaitingTimeAfterMeeting = new IntegerOptionItem(Id + 6, "SoulHunterFreezeTimeAfterMeeting", new(0, 90, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]) - .SetValueFormat(OptionFormat.Seconds); - - TimeToKillTarget = new IntegerOptionItem(Id + 7, "SoulHunterTimeToKillTarget", new(1, 90, 1), 30, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]) - .SetValueFormat(OptionFormat.Seconds); - - GetSoulForSuicide = new BooleanOptionItem(Id + 8, "SoulHunterGetSoulForSuicide", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.SoulHunter]); - } - - public override void Init() - { - SoulHunterId = byte.MaxValue; - SoulHunter_ = null; - Souls = 0; - CurrentTarget = (byte.MaxValue, 0, false); - LastUpdate = 0; - NormalSpeed = Main.NormalOptions.PlayerSpeedMod; - } - - public override void Add(byte playerId) - { - SoulHunterId = playerId; - LateTask.New(() => { SoulHunter_ = GetPlayerById(playerId); }, 3f, log: false); - Souls = 0; - CurrentTarget = (byte.MaxValue, 0, false); - LastUpdate = 0; - NormalSpeed = Main.AllPlayerSpeed[playerId]; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = WaitingTimeAfterMeeting.GetFloat() + 1.5f; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public static bool IsSoulHunterTarget(byte id) - { - return Main.PlayerStates.Any(x => x.Value.Role is SoulHunter { IsEnable: true, IsTargetBlocked: true } sh && sh.CurrentTarget.ID == id); - } - - private void SendRPC() - { - MessageWriter writer = CreateRPC(CustomRPC.SyncSoulHunter); - writer.Write(SoulHunterId); - writer.Write(Souls); - writer.Write(CurrentTarget.ID); - writer.Write(CurrentTarget.StartTimeStamp.ToString()); - writer.Write(CurrentTarget.Frozen); - EndRPC(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not SoulHunter sh) return; - - sh.Souls = reader.ReadInt32(); - sh.CurrentTarget.ID = reader.ReadByte(); - sh.CurrentTarget.StartTimeStamp = long.Parse(reader.ReadString()); - sh.CurrentTarget.Frozen = reader.ReadBoolean(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (SoulHunter_ == null || target == null) return false; - - if (CurrentTarget.ID == byte.MaxValue || CurrentTarget.StartTimeStamp == 0) - { - CurrentTarget.ID = target.PlayerId; - CurrentTarget.StartTimeStamp = 0; - SoulHunter_.SetKillCooldown(5f); - Logger.Info($"Marked Next Target: {target.GetNameWithRole()}", "SoulHunter"); - SendRPC(); - return false; - } - - if (CurrentTarget.ID == target.PlayerId && CurrentTarget.StartTimeStamp != 0) - { - CurrentTarget.ID = byte.MaxValue; - CurrentTarget.StartTimeStamp = 0; - LastUpdate = 0; - Souls++; - SoulHunter_.Notify(GetString("SoulHunterNotifySuccess")); - LateTask.New(() => { SoulHunter_.SetKillCooldown(1f); }, 0.1f, log: false); - Logger.Info("Killed Target", "SoulHunter"); - SendRPC(); - return true; - } - - CurrentTarget.ID = byte.MaxValue; + CurrentTarget.ID = target.PlayerId; CurrentTarget.StartTimeStamp = 0; - LastUpdate = TimeStamp; - SoulHunter_.Suicide(); - target.Notify(GetString("SoulHunterTargetNotifySurvived")); - Logger.Info("Killed Incorrect Player => Suicide", "SoulHunter"); + SoulHunter_.SetKillCooldown(5f); + Logger.Info($"Marked Next Target: {target.GetNameWithRole()}", "SoulHunter"); SendRPC(); return false; } - public override void AfterMeetingTasks() + if (CurrentTarget.ID == target.PlayerId && CurrentTarget.StartTimeStamp != 0) { - if (!IsEnable || CurrentTarget.ID == byte.MaxValue) return; - - long now = TimeStamp; - - CurrentTarget.Frozen = true; - NormalSpeed = Main.AllPlayerSpeed[SoulHunterId]; - Main.AllPlayerSpeed[SoulHunterId] = Main.MinSpeed; - SoulHunter_.MarkDirtySettings(); - - CurrentTarget.StartTimeStamp = now; - - PlayerControl target = GetPlayerById(CurrentTarget.ID); - int waitingTime = WaitingTimeAfterMeeting.GetInt(); - - if (!SoulHunter_.IsModClient()) SoulHunter_.Notify(string.Format(GetString("SoulHunterNotifyFreeze"), target.GetRealName(), waitingTime + 1)); - - target.Notify(string.Format(GetString("SoulHunterTargetNotify"), SoulHunter_.GetRealName()), 300f); - LastUpdate = now; - + CurrentTarget.ID = byte.MaxValue; + CurrentTarget.StartTimeStamp = 0; + LastUpdate = 0; + Souls++; + SoulHunter_.Notify(GetString("SoulHunterNotifySuccess")); + LateTask.New(() => { SoulHunter_.SetKillCooldown(1f); }, 0.1f, log: false); + Logger.Info("Killed Target", "SoulHunter"); SendRPC(); - Logger.Info($"Waiting to being hunting (in {waitingTime}s)", "SoulHunter"); + return true; } - public override void OnFixedUpdate(PlayerControl pc) - { - if (!IsEnable || !GameStates.IsInTask || !SoulHunter_.IsAlive() || CurrentTarget.ID == byte.MaxValue) return; + CurrentTarget.ID = byte.MaxValue; + CurrentTarget.StartTimeStamp = 0; + LastUpdate = TimeStamp; + SoulHunter_.Suicide(); + target.Notify(GetString("SoulHunterTargetNotifySurvived")); + Logger.Info("Killed Incorrect Player => Suicide", "SoulHunter"); + SendRPC(); + return false; + } - if (!CurrentTarget.Frozen && Math.Abs(Main.AllPlayerSpeed[SoulHunterId] - NormalSpeed) > 0.1f) + public override void AfterMeetingTasks() + { + if (!IsEnable || CurrentTarget.ID == byte.MaxValue) return; + + long now = TimeStamp; + + CurrentTarget.Frozen = true; + NormalSpeed = Main.AllPlayerSpeed[SoulHunterId]; + Main.AllPlayerSpeed[SoulHunterId] = Main.MinSpeed; + SoulHunter_.MarkDirtySettings(); + + CurrentTarget.StartTimeStamp = now; + + PlayerControl target = GetPlayerById(CurrentTarget.ID); + int waitingTime = WaitingTimeAfterMeeting.GetInt(); + + if (!SoulHunter_.IsModClient()) SoulHunter_.Notify(string.Format(GetString("SoulHunterNotifyFreeze"), target.GetRealName(), waitingTime + 1)); + + target.Notify(string.Format(GetString("SoulHunterTargetNotify"), SoulHunter_.GetRealName()), 300f); + LastUpdate = now; + + SendRPC(); + Logger.Info($"Waiting to being hunting (in {waitingTime}s)", "SoulHunter"); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!IsEnable || !GameStates.IsInTask || !SoulHunter_.IsAlive() || CurrentTarget.ID == byte.MaxValue) return; + + if (!CurrentTarget.Frozen && Math.Abs(Main.AllPlayerSpeed[SoulHunterId] - NormalSpeed) > 0.1f) + { + Main.AllPlayerSpeed[SoulHunterId] = NormalSpeed; + SoulHunter_.MarkDirtySettings(); + } + + if (CurrentTarget.StartTimeStamp == 0) return; + + long now = TimeStamp; + if (LastUpdate >= now) return; + + LastUpdate = now; + + PlayerControl target = GetPlayerById(CurrentTarget.ID); + string targetName = target.GetRealName(); + int waitingTime = WaitingTimeAfterMeeting.GetInt(); + int timeToKill = TimeToKillTarget.GetInt(); + + if (!target.IsAlive() || target.Data.Disconnected) + { + CurrentTarget.ID = byte.MaxValue; + CurrentTarget.StartTimeStamp = 0; + CurrentTarget.Frozen = false; + + Main.AllPlayerSpeed[SoulHunterId] = NormalSpeed; + SoulHunter_.MarkDirtySettings(); + + if (GetSoulForSuicide.GetBool()) Souls++; + + SoulHunter_.Notify(GetString("SoulHunterNotifySuccess")); + Logger.Info("Target Died/Disconnected", "SoulHunter"); + SendRPC(); + + return; + } + + if (CurrentTarget.Frozen) + { + if (CurrentTarget.StartTimeStamp + waitingTime < now) { + CurrentTarget.Frozen = false; Main.AllPlayerSpeed[SoulHunterId] = NormalSpeed; SoulHunter_.MarkDirtySettings(); + + CurrentTarget.StartTimeStamp = now; + SendRPC(); } - - if (CurrentTarget.StartTimeStamp == 0) return; - - long now = TimeStamp; - if (LastUpdate >= now) return; - - LastUpdate = now; - - PlayerControl target = GetPlayerById(CurrentTarget.ID); - string targetName = target.GetRealName(); - int waitingTime = WaitingTimeAfterMeeting.GetInt(); - int timeToKill = TimeToKillTarget.GetInt(); - - if (!target.IsAlive() || target.Data.Disconnected) + else if (!SoulHunter_.IsModClient()) SoulHunter_.Notify(string.Format(GetString("SoulHunterNotifyFreeze"), targetName, waitingTime - (now - CurrentTarget.StartTimeStamp) + 1)); + } + else + { + if (CurrentTarget.StartTimeStamp + timeToKill < now) { CurrentTarget.ID = byte.MaxValue; CurrentTarget.StartTimeStamp = 0; - CurrentTarget.Frozen = false; - - Main.AllPlayerSpeed[SoulHunterId] = NormalSpeed; - SoulHunter_.MarkDirtySettings(); - - if (GetSoulForSuicide.GetBool()) Souls++; - - SoulHunter_.Notify(GetString("SoulHunterNotifySuccess")); - Logger.Info("Target Died/Disconnected", "SoulHunter"); + SoulHunter_.Suicide(); + target.Notify(GetString("SoulHunterTargetNotifySurvived")); SendRPC(); - return; + if (SoulHunterId == PlayerControl.LocalPlayer.PlayerId) + Achievements.Type.OutOfTime.Complete(); } - - if (CurrentTarget.Frozen) - { - if (CurrentTarget.StartTimeStamp + waitingTime < now) - { - CurrentTarget.Frozen = false; - Main.AllPlayerSpeed[SoulHunterId] = NormalSpeed; - SoulHunter_.MarkDirtySettings(); - - CurrentTarget.StartTimeStamp = now; - SendRPC(); - } - else if (!SoulHunter_.IsModClient()) SoulHunter_.Notify(string.Format(GetString("SoulHunterNotifyFreeze"), targetName, waitingTime - (now - CurrentTarget.StartTimeStamp) + 1)); - } - else - { - if (CurrentTarget.StartTimeStamp + timeToKill < now) - { - CurrentTarget.ID = byte.MaxValue; - CurrentTarget.StartTimeStamp = 0; - SoulHunter_.Suicide(); - target.Notify(GetString("SoulHunterTargetNotifySurvived")); - SendRPC(); - - if (SoulHunterId == PlayerControl.LocalPlayer.PlayerId) - Achievements.Type.OutOfTime.Complete(); - } - else if (!SoulHunter_.IsModClient()) SoulHunter_.Notify(string.Format(GetString("SoulHunterNotify"), timeToKill - (now - CurrentTarget.StartTimeStamp) + 1, targetName), overrideAll: true); - } - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || Main.PlayerStates[seer.PlayerId].Role is not SoulHunter { IsEnable: true } sh) return string.Empty; - - if (!sh.IsTargetBlocked) return string.Empty; - - return sh.CurrentTarget.Frozen ? string.Format(GetString("SoulHunterNotifyFreeze"), GetPlayerById(sh.CurrentTarget.ID).GetRealName(), WaitingTimeAfterMeeting.GetInt() - (TimeStamp - sh.CurrentTarget.StartTimeStamp) + 1) : string.Format(GetString("SoulHunterNotify"), TimeToKillTarget.GetInt() - (TimeStamp - sh.CurrentTarget.StartTimeStamp) + 1, GetPlayerById(sh.CurrentTarget.ID).GetRealName()); - } - - public override string GetProgressText(byte id, bool comms) - { - if (Main.PlayerStates[id].Role is not SoulHunter { IsEnable: true } sh) return string.Empty; - - int souldsNeeded = NumOfSoulsToWin.GetInt(); - return $"<#777777>- <#{(sh.Souls >= souldsNeeded ? "00ff00" : "ffffff")}>{sh.Souls}/{souldsNeeded}"; + else if (!SoulHunter_.IsModClient()) SoulHunter_.Notify(string.Format(GetString("SoulHunterNotify"), timeToKill - (now - CurrentTarget.StartTimeStamp) + 1, targetName), overrideAll: true); } } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || Main.PlayerStates[seer.PlayerId].Role is not SoulHunter { IsEnable: true } sh) return string.Empty; + + if (!sh.IsTargetBlocked) return string.Empty; + + return sh.CurrentTarget.Frozen ? string.Format(GetString("SoulHunterNotifyFreeze"), GetPlayerById(sh.CurrentTarget.ID).GetRealName(), WaitingTimeAfterMeeting.GetInt() - (TimeStamp - sh.CurrentTarget.StartTimeStamp) + 1) : string.Format(GetString("SoulHunterNotify"), TimeToKillTarget.GetInt() - (TimeStamp - sh.CurrentTarget.StartTimeStamp) + 1, GetPlayerById(sh.CurrentTarget.ID).GetRealName()); + } + + public override string GetProgressText(byte id, bool comms) + { + if (Main.PlayerStates[id].Role is not SoulHunter { IsEnable: true } sh) return string.Empty; + + int souldsNeeded = NumOfSoulsToWin.GetInt(); + return $"<#777777>- <#{(sh.Souls >= souldsNeeded ? "00ff00" : "ffffff")}>{sh.Souls}/{souldsNeeded}"; + } } \ No newline at end of file diff --git a/Roles/Neutral/Spiritcaller.cs b/Roles/Neutral/Spiritcaller.cs index abba365f2..5601243b3 100644 --- a/Roles/Neutral/Spiritcaller.cs +++ b/Roles/Neutral/Spiritcaller.cs @@ -4,194 +4,193 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Spiritcaller : RoleBase { - public class Spiritcaller : RoleBase + private const int Id = 13400; + private static List PlayerIdList = []; + + private static Dictionary PlayersHaunted = []; + + private static OptionItem KillCooldown; + public static OptionItem CanVent; + public static OptionItem ImpostorVision; + private static OptionItem SpiritMax; + public static OptionItem SpiritAbilityCooldown; + private static OptionItem SpiritFreezeTime; + private static OptionItem SpiritProtectTime; + private static OptionItem SpiritCauseVision; + private static OptionItem SpiritCauseVisionTime; + + private static long ProtectTimeStamp; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 13400; - private static List PlayerIdList = []; + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Spiritcaller); - private static Dictionary PlayersHaunted = []; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 60f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Seconds); - private static OptionItem KillCooldown; - public static OptionItem CanVent; - public static OptionItem ImpostorVision; - private static OptionItem SpiritMax; - public static OptionItem SpiritAbilityCooldown; - private static OptionItem SpiritFreezeTime; - private static OptionItem SpiritProtectTime; - private static OptionItem SpiritCauseVision; - private static OptionItem SpiritCauseVisionTime; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]); - private static long ProtectTimeStamp; + ImpostorVision = new BooleanOptionItem(Id + 12, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]); - public override bool IsEnable => PlayerIdList.Count > 0; + SpiritMax = new IntegerOptionItem(Id + 13, "SpiritcallerSpiritMax", new(1, 15, 1), 2, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + SpiritAbilityCooldown = new FloatOptionItem(Id + 14, "SpiritcallerSpiritAbilityCooldown", new(5f, 90f, 1f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Seconds); + + SpiritFreezeTime = new FloatOptionItem(Id + 15, "SpiritcallerFreezeTime", new(0f, 30f, 1f), 3f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Seconds); + + SpiritProtectTime = new FloatOptionItem(Id + 16, "SpiritcallerProtectTime", new(0f, 30f, 1f), 5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Seconds); + + SpiritCauseVision = new FloatOptionItem(Id + 17, "SpiritcallerCauseVision", new(0f, 5f, 0.05f), 0.4f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Multiplier); + + SpiritCauseVisionTime = new FloatOptionItem(Id + 18, "SpiritcallerCauseVisionTime", new(0f, 45f, 1f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + PlayerIdList = []; + ProtectTimeStamp = 0; + PlayersHaunted = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(SpiritMax.GetInt()); + ProtectTimeStamp = 0; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public static bool InProtect(PlayerControl player) + { + return player.Is(CustomRoles.Spiritcaller) && ProtectTimeStamp > Utils.TimeStamp; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return true; + + if (!target.GetCustomRole().IsAbleToBeSidekicked() && !target.GetCustomRole().IsImpostor()) { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Spiritcaller); + killer.RpcRemoveAbilityUse(); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 60f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Seconds); + target.RpcSetCustomRole(CustomRoles.EvilSpirit); - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]); + var writer = CustomRpcSender.Create("SpiritCallerSendMessage"); + writer.StartMessage(target.GetClientId()); - ImpostorVision = new BooleanOptionItem(Id + 12, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]); + writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) + .Write(target.Data.NetId) + .Write(GetString("SpiritcallerNoticeTitle")) + .EndRpc(); - SpiritMax = new IntegerOptionItem(Id + 13, "SpiritcallerSpiritMax", new(1, 15, 1), 2, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Times); + writer.StartRpc(target.NetId, (byte)RpcCalls.SendChat) + .Write(GetString("SpiritcallerNoticeMessage")) + .EndRpc(); - SpiritAbilityCooldown = new FloatOptionItem(Id + 14, "SpiritcallerSpiritAbilityCooldown", new(5f, 90f, 1f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Seconds); + writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) + .Write(target.Data.NetId) + .Write(target.Data.PlayerName) + .EndRpc(); - SpiritFreezeTime = new FloatOptionItem(Id + 15, "SpiritcallerFreezeTime", new(0f, 30f, 1f), 3f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Seconds); - - SpiritProtectTime = new FloatOptionItem(Id + 16, "SpiritcallerProtectTime", new(0f, 30f, 1f), 5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Seconds); - - SpiritCauseVision = new FloatOptionItem(Id + 17, "SpiritcallerCauseVision", new(0f, 5f, 0.05f), 0.4f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Multiplier); - - SpiritCauseVisionTime = new FloatOptionItem(Id + 18, "SpiritcallerCauseVisionTime", new(0f, 45f, 1f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Spiritcaller]) - .SetValueFormat(OptionFormat.Seconds); + writer.EndMessage(); + writer.SendMessage(); } - public override void Init() + return true; + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask) return; + + if (pc.Is(CustomRoles.Spiritcaller)) { - PlayerIdList = []; - ProtectTimeStamp = 0; - PlayersHaunted = []; + if (ProtectTimeStamp < Utils.TimeStamp && ProtectTimeStamp != 0) ProtectTimeStamp = 0; } - - public override void Add(byte playerId) + else if (PlayersHaunted.ContainsKey(pc.PlayerId) && PlayersHaunted[pc.PlayerId] < Utils.TimeStamp) { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(SpiritMax.GetInt()); - ProtectTimeStamp = 0; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(ImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public static bool InProtect(PlayerControl player) - { - return player.Is(CustomRoles.Spiritcaller) && ProtectTimeStamp > Utils.TimeStamp; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return true; - - if (!target.GetCustomRole().IsAbleToBeSidekicked() && !target.GetCustomRole().IsImpostor()) - { - killer.RpcRemoveAbilityUse(); - - target.RpcSetCustomRole(CustomRoles.EvilSpirit); - - var writer = CustomRpcSender.Create("SpiritCallerSendMessage"); - writer.StartMessage(target.GetClientId()); - - writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) - .Write(target.Data.NetId) - .Write(GetString("SpiritcallerNoticeTitle")) - .EndRpc(); - - writer.StartRpc(target.NetId, (byte)RpcCalls.SendChat) - .Write(GetString("SpiritcallerNoticeMessage")) - .EndRpc(); - - writer.StartRpc(target.NetId, (byte)RpcCalls.SetName) - .Write(target.Data.NetId) - .Write(target.Data.PlayerName) - .EndRpc(); - - writer.EndMessage(); - writer.SendMessage(); - } - - return true; - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask) return; - - if (pc.Is(CustomRoles.Spiritcaller)) - { - if (ProtectTimeStamp < Utils.TimeStamp && ProtectTimeStamp != 0) ProtectTimeStamp = 0; - } - else if (PlayersHaunted.ContainsKey(pc.PlayerId) && PlayersHaunted[pc.PlayerId] < Utils.TimeStamp) - { - PlayersHaunted.Remove(pc.PlayerId); - pc.MarkDirtySettings(); - } - } - - public static void HauntPlayer(PlayerControl target) - { - if (SpiritCauseVisionTime.GetFloat() > 0 || SpiritFreezeTime.GetFloat() > 0) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Spiritcaller), GetString("HauntedByEvilSpirit"))); - - if (SpiritCauseVisionTime.GetFloat() > 0 && !PlayersHaunted.ContainsKey(target.PlayerId)) - { - long time = Utils.TimeStamp + (long)SpiritCauseVisionTime.GetFloat(); - PlayersHaunted.Add(target.PlayerId, time); - } - - if (SpiritFreezeTime.GetFloat() > 0) - { - float tmpSpeed = Main.AllPlayerSpeed[target.PlayerId]; - Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; - ReportDeadBodyPatch.CanReport[target.PlayerId] = false; - target.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[target.PlayerId] = Main.AllPlayerSpeed[target.PlayerId] - Main.MinSpeed + tmpSpeed; - ReportDeadBodyPatch.CanReport[target.PlayerId] = true; - target.MarkDirtySettings(); - RPC.PlaySoundRPC(target.PlayerId, Sounds.TaskComplete); - }, SpiritFreezeTime.GetFloat(), "SpiritcallerFreezeTime"); - - if (target.IsLocalPlayer()) - Achievements.Type.TooCold.CompleteAfterGameEnd(); - } - } - - public static void ReduceVision(IGameOptions opt, PlayerControl target) - { - if (PlayersHaunted.ContainsKey(target.PlayerId)) - { - opt.SetVision(false); - opt.SetFloat(FloatOptionNames.CrewLightMod, SpiritCauseVision.GetFloat()); - opt.SetFloat(FloatOptionNames.ImpostorLightMod, SpiritCauseVision.GetFloat()); - } - } - - public static void ProtectSpiritcaller() - { - ProtectTimeStamp = Utils.TimeStamp + (long)SpiritProtectTime.GetFloat(); + PlayersHaunted.Remove(pc.PlayerId); + pc.MarkDirtySettings(); } } + + public static void HauntPlayer(PlayerControl target) + { + if (SpiritCauseVisionTime.GetFloat() > 0 || SpiritFreezeTime.GetFloat() > 0) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Spiritcaller), GetString("HauntedByEvilSpirit"))); + + if (SpiritCauseVisionTime.GetFloat() > 0 && !PlayersHaunted.ContainsKey(target.PlayerId)) + { + long time = Utils.TimeStamp + (long)SpiritCauseVisionTime.GetFloat(); + PlayersHaunted.Add(target.PlayerId, time); + } + + if (SpiritFreezeTime.GetFloat() > 0) + { + float tmpSpeed = Main.AllPlayerSpeed[target.PlayerId]; + Main.AllPlayerSpeed[target.PlayerId] = Main.MinSpeed; + ReportDeadBodyPatch.CanReport[target.PlayerId] = false; + target.MarkDirtySettings(); + + LateTask.New(() => + { + Main.AllPlayerSpeed[target.PlayerId] = Main.AllPlayerSpeed[target.PlayerId] - Main.MinSpeed + tmpSpeed; + ReportDeadBodyPatch.CanReport[target.PlayerId] = true; + target.MarkDirtySettings(); + RPC.PlaySoundRPC(target.PlayerId, Sounds.TaskComplete); + }, SpiritFreezeTime.GetFloat(), "SpiritcallerFreezeTime"); + + if (target.IsLocalPlayer()) + Achievements.Type.TooCold.CompleteAfterGameEnd(); + } + } + + public static void ReduceVision(IGameOptions opt, PlayerControl target) + { + if (PlayersHaunted.ContainsKey(target.PlayerId)) + { + opt.SetVision(false); + opt.SetFloat(FloatOptionNames.CrewLightMod, SpiritCauseVision.GetFloat()); + opt.SetFloat(FloatOptionNames.ImpostorLightMod, SpiritCauseVision.GetFloat()); + } + } + + public static void ProtectSpiritcaller() + { + ProtectTimeStamp = Utils.TimeStamp + (long)SpiritProtectTime.GetFloat(); + } } \ No newline at end of file diff --git a/Roles/Neutral/Sprayer.cs b/Roles/Neutral/Sprayer.cs index 275b0555d..0616c6a8a 100644 --- a/Roles/Neutral/Sprayer.cs +++ b/Roles/Neutral/Sprayer.cs @@ -5,201 +5,200 @@ using static EHR.Options; using static EHR.Translator; using static EHR.Utils; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Sprayer : RoleBase { - internal class Sprayer : RoleBase + private static byte SprayerId = byte.MaxValue; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + public static OptionItem CanVent; + private static OptionItem CD; + private static OptionItem UseLimitOpt; + public static OptionItem LoweredVision; + private static OptionItem LoweredSpeed; + private static OptionItem EffectDuration; + private static OptionItem MaxTrappedTimes; + + private static readonly Dictionary Traps = []; + private static readonly Dictionary TrappedCount = []; + public static readonly List LowerVisionList = []; + private static readonly Dictionary LastUpdate = []; + private static int Id => 643240; + + private static PlayerControl SprayerPC => GetPlayerById(SprayerId); + + public override bool IsEnable => SprayerId != byte.MaxValue; + + public override void SetupCustomOption() { - private static byte SprayerId = byte.MaxValue; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Sprayer); - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - public static OptionItem CanVent; - private static OptionItem CD; - private static OptionItem UseLimitOpt; - public static OptionItem LoweredVision; - private static OptionItem LoweredSpeed; - private static OptionItem EffectDuration; - private static OptionItem MaxTrappedTimes; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Seconds); - private static readonly Dictionary Traps = []; - private static readonly Dictionary TrappedCount = []; - public static readonly List LowerVisionList = []; - private static readonly Dictionary LastUpdate = []; - private static int Id => 643240; + HasImpostorVision = new BooleanOptionItem(Id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]); - private static PlayerControl SprayerPC => GetPlayerById(SprayerId); + CanVent = new BooleanOptionItem(Id + 4, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]); - public override bool IsEnable => SprayerId != byte.MaxValue; + CD = new IntegerOptionItem(Id + 5, "AbilityCooldown", new(0, 90, 1), 15, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + UseLimitOpt = new IntegerOptionItem(Id + 6, "AbilityUseLimit", new(1, 90, 1), 5, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Times); + + LoweredVision = new FloatOptionItem(Id + 7, "FFA_LowerVision", new(0.05f, 1.5f, 0.05f), 0.25f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Multiplier); + + LoweredSpeed = new FloatOptionItem(Id + 8, "FFA_DecreasedSpeed", new(0.05f, 3f, 0.05f), 0.8f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Multiplier); + + EffectDuration = new IntegerOptionItem(Id + 10, "NegativeEffectDuration", new(1, 90, 1), 10, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Seconds); + + MaxTrappedTimes = new IntegerOptionItem(Id + 9, "SprayerMaxTrappedTimes", new(1, 90, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) + .SetValueFormat(OptionFormat.Times); + } + + public override void Init() + { + SprayerId = byte.MaxValue; + Traps.Clear(); + TrappedCount.Clear(); + LowerVisionList.Clear(); + LastUpdate.Clear(); + } + + public override void Add(byte playerId) + { + SprayerId = playerId; + playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) TrappedCount[pc.PlayerId] = 0; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = CD.GetInt(); + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = CD.GetInt(); + } + + public override bool OnSabotage(PlayerControl pc) + { + PlaceTrap(); + return false; + } + + public override void OnPet(PlayerControl pc) + { + PlaceTrap(); + } + + public override bool OnVanish(PlayerControl pc) + { + PlaceTrap(); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + PlaceTrap(); + return false; + } + + private void PlaceTrap() + { + if (!IsEnable || SprayerId.GetAbilityUseLimit() <= 0 || SprayerPC.HasAbilityCD()) return; + + Vector2 pos = SprayerPC.Pos(); + Traps[pos] = new(pos, [SprayerId]); + SprayerPC.RpcRemoveAbilityUse(); + + if (SprayerId.GetAbilityUseLimit() > 0) SprayerPC.AddAbilityCD(CD.GetInt()); + + SprayerPC.Notify(GetString("SprayerNotify")); + } + + public override void OnCheckPlayerPosition(PlayerControl pc) + { + if (!IsEnable || !GameStates.IsInTask || Traps.Count == 0) return; + + long now = TimeStamp; + + if (pc.PlayerId == SprayerId) return; + + LastUpdate.TryAdd(pc.PlayerId, now); + if (LastUpdate[pc.PlayerId] + 3 > now) return; + + LastUpdate[pc.PlayerId] = now; + + foreach (KeyValuePair trap in Traps) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Sprayer); - - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Seconds); - - HasImpostorVision = new BooleanOptionItem(Id + 3, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]); - - CanVent = new BooleanOptionItem(Id + 4, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]); - - CD = new IntegerOptionItem(Id + 5, "AbilityCooldown", new(0, 90, 1), 15, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Seconds); - - UseLimitOpt = new IntegerOptionItem(Id + 6, "AbilityUseLimit", new(1, 90, 1), 5, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Times); - - LoweredVision = new FloatOptionItem(Id + 7, "FFA_LowerVision", new(0.05f, 1.5f, 0.05f), 0.25f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Multiplier); - - LoweredSpeed = new FloatOptionItem(Id + 8, "FFA_DecreasedSpeed", new(0.05f, 3f, 0.05f), 0.8f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Multiplier); - - EffectDuration = new IntegerOptionItem(Id + 10, "NegativeEffectDuration", new(1, 90, 1), 10, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Seconds); - - MaxTrappedTimes = new IntegerOptionItem(Id + 9, "SprayerMaxTrappedTimes", new(1, 90, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Sprayer]) - .SetValueFormat(OptionFormat.Times); - } - - public override void Init() - { - SprayerId = byte.MaxValue; - Traps.Clear(); - TrappedCount.Clear(); - LowerVisionList.Clear(); - LastUpdate.Clear(); - } - - public override void Add(byte playerId) - { - SprayerId = playerId; - playerId.SetAbilityUseLimit(UseLimitOpt.GetInt()); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) TrappedCount[pc.PlayerId] = 0; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = CD.GetInt(); - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = CD.GetInt(); - } - - public override bool OnSabotage(PlayerControl pc) - { - PlaceTrap(); - return false; - } - - public override void OnPet(PlayerControl pc) - { - PlaceTrap(); - } - - public override bool OnVanish(PlayerControl pc) - { - PlaceTrap(); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - PlaceTrap(); - return false; - } - - private void PlaceTrap() - { - if (!IsEnable || SprayerId.GetAbilityUseLimit() <= 0 || SprayerPC.HasAbilityCD()) return; - - Vector2 pos = SprayerPC.Pos(); - Traps[pos] = new(pos, [SprayerId]); - SprayerPC.RpcRemoveAbilityUse(); - - if (SprayerId.GetAbilityUseLimit() > 0) SprayerPC.AddAbilityCD(CD.GetInt()); - - SprayerPC.Notify(GetString("SprayerNotify")); - } - - public override void OnCheckPlayerPosition(PlayerControl pc) - { - if (!IsEnable || !GameStates.IsInTask || Traps.Count == 0) return; - - long now = TimeStamp; - - if (pc.PlayerId == SprayerId) return; - - LastUpdate.TryAdd(pc.PlayerId, now); - if (LastUpdate[pc.PlayerId] + 3 > now) return; - - LastUpdate[pc.PlayerId] = now; - - foreach (KeyValuePair trap in Traps) + if (Vector2.Distance(pc.Pos(), trap.Key) <= 2f) { - if (Vector2.Distance(pc.Pos(), trap.Key) <= 2f) + byte playerId = pc.PlayerId; + float tempSpeed = Main.AllPlayerSpeed[playerId]; + Main.AllPlayerSpeed[playerId] = LoweredSpeed.GetFloat(); + LowerVisionList.Add(playerId); + TrappedCount[playerId]++; + + if (TrappedCount[playerId] > MaxTrappedTimes.GetInt()) { - byte playerId = pc.PlayerId; - float tempSpeed = Main.AllPlayerSpeed[playerId]; - Main.AllPlayerSpeed[playerId] = LoweredSpeed.GetFloat(); - LowerVisionList.Add(playerId); - TrappedCount[playerId]++; + pc.Suicide(realKiller: SprayerPC); + TrappedCount.Remove(playerId); + } + else + { + pc.MarkDirtySettings(); - if (TrappedCount[playerId] > MaxTrappedTimes.GetInt()) + LateTask.New(() => { - pc.Suicide(realKiller: SprayerPC); - TrappedCount.Remove(playerId); - } - else - { - pc.MarkDirtySettings(); - - LateTask.New(() => - { - Main.AllPlayerSpeed[playerId] = tempSpeed; - LowerVisionList.Remove(playerId); - if (GameStates.IsInTask) pc.MarkDirtySettings(); - }, EffectDuration.GetFloat(), "Sprayer Revert Effects"); - } + Main.AllPlayerSpeed[playerId] = tempSpeed; + LowerVisionList.Remove(playerId); + if (GameStates.IsInTask) pc.MarkDirtySettings(); + }, EffectDuration.GetFloat(), "Sprayer Revert Effects"); } } } + } - public override void OnReportDeadBody() - { - Traps.Values.Do(x => x.Despawn()); - Traps.Clear(); - } + public override void OnReportDeadBody() + { + Traps.Values.Do(x => x.Despawn()); + Traps.Clear(); + } - public override void AfterMeetingTasks() - { - if (SprayerId.GetAbilityUseLimit() > 0) SprayerPC.AddAbilityCD(Math.Max(15, CD.GetInt())); - } + public override void AfterMeetingTasks() + { + if (SprayerId.GetAbilityUseLimit() > 0) SprayerPC.AddAbilityCD(Math.Max(15, CD.GetInt())); } } \ No newline at end of file diff --git a/Roles/Neutral/Succubus.cs b/Roles/Neutral/Succubus.cs index 6485cbf59..41ab38c7b 100644 --- a/Roles/Neutral/Succubus.cs +++ b/Roles/Neutral/Succubus.cs @@ -5,156 +5,155 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Succubus : RoleBase { - public class Succubus : RoleBase + private const int Id = 11200; + private static List PlayerIdList = []; + + private static OptionItem CharmCooldown; + private static OptionItem CharmCooldownIncrese; + private static OptionItem CharmMax; + private static OptionItem KnowTargetRole; + public static OptionItem TargetKnowOtherTarget; + private static OptionItem CanCharmNeutral; + public static OptionItem CharmedCountMode; + private static OptionItem CharmedDiesOnSuccubusDeath; + + private static readonly string[] CharmedCountModeStrings = + [ + "CharmedCountMode.None", + "CharmedCountMode.Succubus", + "CharmedCountMode.Original" + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 11200; - private static List PlayerIdList = []; + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Succubus); - private static OptionItem CharmCooldown; - private static OptionItem CharmCooldownIncrese; - private static OptionItem CharmMax; - private static OptionItem KnowTargetRole; - public static OptionItem TargetKnowOtherTarget; - private static OptionItem CanCharmNeutral; - public static OptionItem CharmedCountMode; - private static OptionItem CharmedDiesOnSuccubusDeath; + CharmCooldown = new FloatOptionItem(Id + 10, "SuccubusCharmCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]) + .SetValueFormat(OptionFormat.Seconds); - private static readonly string[] CharmedCountModeStrings = - [ - "CharmedCountMode.None", - "CharmedCountMode.Succubus", - "CharmedCountMode.Original" - ]; + CharmCooldownIncrese = new FloatOptionItem(Id + 11, "SuccubusCharmCooldownIncrese", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + CharmMax = new IntegerOptionItem(Id + 12, "SuccubusCharmMax", new(1, 15, 1), 15, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]) + .SetValueFormat(OptionFormat.Times); - public override void SetupCustomOption() + KnowTargetRole = new BooleanOptionItem(Id + 13, "SuccubusKnowTargetRole", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + + TargetKnowOtherTarget = new BooleanOptionItem(Id + 14, "SuccubusTargetKnowOtherTarget", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + + CharmedCountMode = new StringOptionItem(Id + 15, "CharmedCountMode", CharmedCountModeStrings, 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + + CanCharmNeutral = new BooleanOptionItem(Id + 16, "SuccubusCanCharmNeutral", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + + CharmedDiesOnSuccubusDeath = new BooleanOptionItem(Id + 17, "CharmedDiesOnSuccubusDeath", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + } + + public override void Init() + { + PlayerIdList = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(CharmMax.GetInt()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = id.GetAbilityUseLimit() >= 1 ? CharmCooldown.GetFloat() + ((CharmMax.GetInt() - id.GetAbilityUseLimit()) * CharmCooldownIncrese.GetFloat()) : 300f; + } + + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead && player.GetAbilityUseLimit() >= 1; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return false; + + if (CanBeCharmed(target)) { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Succubus); + killer.RpcRemoveAbilityUse(); - CharmCooldown = new FloatOptionItem(Id + 10, "SuccubusCharmCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]) - .SetValueFormat(OptionFormat.Seconds); + target.RpcSetCustomRole(CustomRoles.Charmed); - CharmCooldownIncrese = new FloatOptionItem(Id + 11, "SuccubusCharmCooldownIncrese", new(0f, 180f, 0.5f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]) - .SetValueFormat(OptionFormat.Seconds); + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Succubus), GetString("SuccubusCharmedPlayer"))); + target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Succubus), GetString("CharmedBySuccubus"))); - CharmMax = new IntegerOptionItem(Id + 12, "SuccubusCharmMax", new(1, 15, 1), 15, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]) - .SetValueFormat(OptionFormat.Times); + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); + Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); - KnowTargetRole = new BooleanOptionItem(Id + 13, "SuccubusKnowTargetRole", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + killer.SetKillCooldown(); + target.RpcGuardAndKill(killer); + target.RpcGuardAndKill(target); - TargetKnowOtherTarget = new BooleanOptionItem(Id + 14, "SuccubusTargetKnowOtherTarget", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); + Logger.Info("SetRole:" + target?.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Charmed, "Assign " + CustomRoles.Charmed); - CharmedCountMode = new StringOptionItem(Id + 15, "CharmedCountMode", CharmedCountModeStrings, 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); - - CanCharmNeutral = new BooleanOptionItem(Id + 16, "SuccubusCanCharmNeutral", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); - - CharmedDiesOnSuccubusDeath = new BooleanOptionItem(Id + 17, "CharmedDiesOnSuccubusDeath", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Succubus]); - } - - public override void Init() - { - PlayerIdList = []; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(CharmMax.GetInt()); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = id.GetAbilityUseLimit() >= 1 ? CharmCooldown.GetFloat() + ((CharmMax.GetInt() - id.GetAbilityUseLimit()) * CharmCooldownIncrese.GetFloat()) : 300f; - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead && player.GetAbilityUseLimit() >= 1; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return false; - - if (CanBeCharmed(target)) - { - killer.RpcRemoveAbilityUse(); - - target.RpcSetCustomRole(CustomRoles.Charmed); - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Succubus), GetString("SuccubusCharmedPlayer"))); - target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Succubus), GetString("CharmedBySuccubus"))); - - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: target, ForceLoop: true); - Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: killer, ForceLoop: true); - - killer.SetKillCooldown(); - target.RpcGuardAndKill(killer); - target.RpcGuardAndKill(target); - - Logger.Info("SetRole:" + target?.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Charmed, "Assign " + CustomRoles.Charmed); - - if (killer.IsLocalPlayer()) - Achievements.Type.YoureMyFriendNow.Complete(); - - return false; - } - - killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Succubus), GetString("SuccubusInvalidTarget"))); + if (killer.IsLocalPlayer()) + Achievements.Type.YoureMyFriendNow.Complete(); return false; } - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; + killer.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Succubus), GetString("SuccubusInvalidTarget"))); - if (player.Is(CustomRoles.Charmed) && target.Is(CustomRoles.Succubus)) return true; + return false; + } - if (KnowTargetRole.GetBool() && player.Is(CustomRoles.Succubus) && target.Is(CustomRoles.Charmed)) return true; + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; - return TargetKnowOtherTarget.GetBool() && player.Is(CustomRoles.Charmed) && target.Is(CustomRoles.Charmed); - } + if (player.Is(CustomRoles.Charmed) && target.Is(CustomRoles.Succubus)) return true; - public static bool CanBeCharmed(PlayerControl pc) - { - return pc != null && (pc.IsCrewmate() || pc.IsImpostor() || - (CanCharmNeutral.GetBool() && (pc.GetCustomRole().IsNeutral() || pc.IsNeutralKiller()))) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Curser); - } + if (KnowTargetRole.GetBool() && player.Is(CustomRoles.Succubus) && target.Is(CustomRoles.Charmed)) return true; - public override void OnFixedUpdate(PlayerControl pc) - { - if (!CharmedDiesOnSuccubusDeath.GetBool() || !GameStates.IsInTask || !IsEnable) return; + return TargetKnowOtherTarget.GetBool() && player.Is(CustomRoles.Charmed) && target.Is(CustomRoles.Charmed); + } - if (pc == null || pc.IsAlive()) return; + public static bool CanBeCharmed(PlayerControl pc) + { + return pc != null && (pc.IsCrewmate() || pc.IsImpostor() || + (CanCharmNeutral.GetBool() && (pc.GetCustomRole().IsNeutral() || pc.IsNeutralKiller()))) && !pc.Is(CustomRoles.Charmed) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Curser); + } - foreach (PlayerControl charmed in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Charmed))) charmed.Suicide(realKiller: pc); - } + public override void OnFixedUpdate(PlayerControl pc) + { + if (!CharmedDiesOnSuccubusDeath.GetBool() || !GameStates.IsInTask || !IsEnable) return; - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(GetString("SuccubusKillButtonText")); - } + if (pc == null || pc.IsAlive()) return; + + foreach (PlayerControl charmed in Main.AllAlivePlayerControls.Where(x => x.Is(CustomRoles.Charmed))) charmed.Suicide(realKiller: pc); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(GetString("SuccubusKillButtonText")); } } \ No newline at end of file diff --git a/Roles/Neutral/Sunnyboy.cs b/Roles/Neutral/Sunnyboy.cs index 710fa9fa0..3272582bb 100644 --- a/Roles/Neutral/Sunnyboy.cs +++ b/Roles/Neutral/Sunnyboy.cs @@ -1,28 +1,27 @@ using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Sunnyboy : RoleBase { - internal class Sunnyboy : RoleBase + public static bool On; + public override bool IsEnable => On; + + public override void SetupCustomOption() { } + + public override void Add(byte playerId) { - public static bool On; - public override bool IsEnable => On; + On = true; + } - public override void SetupCustomOption() { } + public override void Init() + { + On = false; + } - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.ScientistCooldown = 0f; - AURoleOptions.ScientistBatteryCharge = 60f; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.ScientistCooldown = 0f; + AURoleOptions.ScientistBatteryCharge = 60f; } } \ No newline at end of file diff --git a/Roles/Neutral/Tank.cs b/Roles/Neutral/Tank.cs index c3bec2fdd..64c999fd0 100644 --- a/Roles/Neutral/Tank.cs +++ b/Roles/Neutral/Tank.cs @@ -2,80 +2,79 @@ using System.Linq; using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Tank : RoleBase { - public class Tank : RoleBase + public static bool On; + + private static OptionItem Speed; + public static OptionItem CanBeGuessed; + private static OptionItem CanBeKilled; + private static OptionItem VentCooldown; + + private static HashSet AllVents = []; + private HashSet EnteredVents; + private byte TankId; + + public override bool IsEnable => On; + public bool IsWon => EnteredVents.Count >= AllVents.Count; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(646950) + .AutoSetupOption(ref Speed, 0.7f, new FloatValueRule(0.1f, 3f, 0.1f), OptionFormat.Multiplier) + .AutoSetupOption(ref CanBeGuessed, false) + .AutoSetupOption(ref CanBeKilled, true) + .AutoSetupOption(ref VentCooldown, 15f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds); + } - private static OptionItem Speed; - public static OptionItem CanBeGuessed; - private static OptionItem CanBeKilled; - private static OptionItem VentCooldown; + public override void Init() + { + On = false; + if (ShipStatus.Instance == null) return; - private static HashSet AllVents = []; - private HashSet EnteredVents; - private byte TankId; + AllVents = ShipStatus.Instance.AllVents.Select(x => x.Id).ToHashSet(); + } - public override bool IsEnable => On; - public bool IsWon => EnteredVents.Count >= AllVents.Count; + public override void Add(byte playerId) + { + On = true; + TankId = playerId; + EnteredVents = []; + } - public override void SetupCustomOption() - { - StartSetup(646950) - .AutoSetupOption(ref Speed, 0.7f, new FloatValueRule(0.1f, 3f, 0.1f), OptionFormat.Multiplier) - .AutoSetupOption(ref CanBeGuessed, false) - .AutoSetupOption(ref CanBeKilled, true) - .AutoSetupOption(ref VentCooldown, 15f, new FloatValueRule(0f, 60f, 0.5f), OptionFormat.Seconds); - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 1f; + Main.AllPlayerSpeed[playerId] = Speed.GetFloat(); + } - public override void Init() - { - On = false; - if (ShipStatus.Instance == null) return; + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return CanBeKilled.GetBool(); + } - AllVents = ShipStatus.Instance.AllVents.Select(x => x.Id).ToHashSet(); - } + public override void OnCoEnterVent(PlayerPhysics physics, int ventId) + { + EnteredVents.Add(ventId); + Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); + } - public override void Add(byte playerId) - { - On = true; - TankId = playerId; - EnteredVents = []; - } + public override string GetProgressText(byte playerId, bool comms) + { + var progress = $"{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Tank), $"{EnteredVents.Count}")}/{AllVents.Count}"; + if (IsWon) progress = $"<#00ff00>{progress}"; - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 1f; - Main.AllPlayerSpeed[playerId] = Speed.GetFloat(); - } + return base.GetProgressText(playerId, comms) + progress; + } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return CanBeKilled.GetBool(); - } + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId || seer.PlayerId != TankId || meeting || (seer.IsModClient() && !hud) || IsWon) return string.Empty; - public override void OnCoEnterVent(PlayerPhysics physics, int ventId) - { - EnteredVents.Add(ventId); - Utils.NotifyRoles(SpecifySeer: physics.myPlayer, SpecifyTarget: physics.myPlayer); - } - - public override string GetProgressText(byte playerId, bool comms) - { - var progress = $"{Utils.ColorString(Utils.GetRoleColor(CustomRoles.Tank), $"{EnteredVents.Count}")}/{AllVents.Count}"; - if (IsWon) progress = $"<#00ff00>{progress}"; - - return base.GetProgressText(playerId, comms) + progress; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId || seer.PlayerId != TankId || meeting || (seer.IsModClient() && !hud) || IsWon) return string.Empty; - - string randomVentName = ShipStatus.Instance?.AllVents?.FirstOrDefault(x => x.Id == AllVents.Except(EnteredVents).FirstOrDefault())?.name ?? string.Empty; - return randomVentName == string.Empty ? string.Empty : string.Format(Translator.GetString("Tank.Suffix"), randomVentName); - } + string randomVentName = ShipStatus.Instance?.AllVents?.FirstOrDefault(x => x.Id == AllVents.Except(EnteredVents).FirstOrDefault())?.name ?? string.Empty; + return randomVentName == string.Empty ? string.Empty : string.Format(Translator.GetString("Tank.Suffix"), randomVentName); } } \ No newline at end of file diff --git a/Roles/Neutral/Technician.cs b/Roles/Neutral/Technician.cs index 1a2f93307..19378c165 100644 --- a/Roles/Neutral/Technician.cs +++ b/Roles/Neutral/Technician.cs @@ -3,207 +3,206 @@ using System.Collections.Generic; using AmongUs.GameOptions; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Technician : RoleBase { - public class Technician : RoleBase + public static bool On; + + private static OptionItem WinsAlone; + private static OptionItem RequiredPoints; + public static OptionItem CanVent; + private static OptionItem VentCooldown; + private static OptionItem MaxInVentTime; + private static readonly Dictionary PointGains = []; + + public static HashSet LocalPlayerFixedSabotageTypes = []; + private bool FixedSabotage; + + private bool Ignore; + public bool IsWon; + private PlayerControl TechnicianPC; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(646450) + .AutoSetupOption(ref WinsAlone, false) + .AutoSetupOption(ref RequiredPoints, 5, new IntegerValueRule(1, 50, 1)) + .AutoSetupOption(ref CanVent, false) + .AutoSetupOption(ref VentCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVent) + .AutoSetupOption(ref MaxInVentTime, 5f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVent); - private static OptionItem WinsAlone; - private static OptionItem RequiredPoints; - public static OptionItem CanVent; - private static OptionItem VentCooldown; - private static OptionItem MaxInVentTime; - private static readonly Dictionary PointGains = []; + var i = 0; - public static HashSet LocalPlayerFixedSabotageTypes = []; - private bool FixedSabotage; - - private bool Ignore; - public bool IsWon; - private PlayerControl TechnicianPC; - - public override bool IsEnable => On; - - public override void SetupCustomOption() + foreach (SystemTypes system in new[] { SystemTypes.Electrical, SystemTypes.Comms, SystemTypes.LifeSupp, SystemTypes.Reactor }) { - StartSetup(646450) - .AutoSetupOption(ref WinsAlone, false) - .AutoSetupOption(ref RequiredPoints, 5, new IntegerValueRule(1, 50, 1)) - .AutoSetupOption(ref CanVent, false) - .AutoSetupOption(ref VentCooldown, 30f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVent) - .AutoSetupOption(ref MaxInVentTime, 5f, new FloatValueRule(0f, 120f, 0.5f), OptionFormat.Seconds, overrideParent: CanVent); + PointGains[system] = new IntegerOptionItem(646960 + i, $"Technician.PointGain.{system}", new(0, 10, 1), 1, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Technician]); - var i = 0; - - foreach (SystemTypes system in new[] { SystemTypes.Electrical, SystemTypes.Comms, SystemTypes.LifeSupp, SystemTypes.Reactor }) - { - PointGains[system] = new IntegerOptionItem(646960 + i, $"Technician.PointGain.{system}", new(0, 10, 1), 1, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Technician]); - - i++; - } - } - - public override void Init() - { - On = false; - LocalPlayerFixedSabotageTypes = []; - } - - public override void Add(byte playerId) - { - On = true; - IsWon = false; - Ignore = false; - TechnicianPC = playerId.GetPlayer(); - FixedSabotage = false; - playerId.SetAbilityUseLimit(0); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (CanVent.GetBool()) - { - AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); - } - } - - public override void OnReportDeadBody() - { - Ignore = true; - } - - public override void AfterMeetingTasks() - { - Ignore = false; - } - - private static SystemTypes GetActualSystemType(SystemTypes systemTypes) - { - return systemTypes switch - { - SystemTypes.Laboratory => SystemTypes.Reactor, - SystemTypes.HeliSabotage => SystemTypes.Reactor, - _ => systemTypes - }; - } - - private static bool GetsAnyPoint(SystemTypes systemType) - { - return PointGains.TryGetValue(GetActualSystemType(systemType), out OptionItem pointGain) && pointGain.GetInt() > 0; - } - - private void IncreasePoints(SystemTypes systemType) - { - SystemTypes actualSystemType = GetActualSystemType(systemType); - TechnicianPC.RpcIncreaseAbilityUseLimitBy(PointGains[actualSystemType].GetInt()); - - if (TechnicianPC.GetAbilityUseLimit() >= RequiredPoints.GetInt()) - { - if (WinsAlone.GetBool()) - { - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Technician); - CustomWinnerHolder.WinnerIds.Add(TechnicianPC.PlayerId); - } - else IsWon = true; - } - - if (TechnicianPC.IsLocalPlayer()) - LocalPlayerFixedSabotageTypes.Add(actualSystemType); - } - - public static void RepairSystem(byte playerId, SystemTypes systemType, byte amount) - { - if (Main.PlayerStates[playerId].IsDead) return; - - if (Main.PlayerStates[playerId].Role is not Technician technician) return; - - if (!GetsAnyPoint(systemType) || technician.IsWon || technician.Ignore) return; - - switch (systemType) - { - case SystemTypes.Reactor: - case SystemTypes.Laboratory: - { - if (amount.HasAnyBit(ReactorSystemType.AddUserOp)) - { - ShipStatus.Instance.UpdateSystem((MapNames)Main.NormalOptions.MapId == MapNames.Polus ? SystemTypes.Laboratory : SystemTypes.Reactor, playerId.GetPlayer(), ReactorSystemType.ClearCountdown); - technician.IncreasePoints(systemType); - } - - break; - } - case SystemTypes.HeliSabotage: - { - var tags = (HeliSabotageSystem.Tags)(amount & HeliSabotageSystem.TagMask); - if (tags == HeliSabotageSystem.Tags.ActiveBit) technician.FixedSabotage = false; - - if (!technician.FixedSabotage && tags == HeliSabotageSystem.Tags.FixBit) - { - technician.FixedSabotage = true; - int consoleId = amount & HeliSabotageSystem.IdMask; - int otherConsoleId = (consoleId + 1) % 2; - ShipStatus.Instance.UpdateSystem(SystemTypes.HeliSabotage, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HeliSabotageSystem.Tags.FixBit)); - technician.IncreasePoints(systemType); - } - - break; - } - case SystemTypes.LifeSupp: - { - if (amount.HasAnyBit(LifeSuppSystemType.AddUserOp)) - { - ShipStatus.Instance.UpdateSystem(SystemTypes.LifeSupp, playerId.GetPlayer(), LifeSuppSystemType.ClearCountdown); - technician.IncreasePoints(systemType); - } - - break; - } - case SystemTypes.Comms: - { - if (Main.CurrentMap is MapNames.Mira or MapNames.Fungle) - { - var tags = (HqHudSystemType.Tags)(amount & HqHudSystemType.TagMask); - if (tags == HqHudSystemType.Tags.ActiveBit) technician.FixedSabotage = false; - - if (!technician.FixedSabotage && tags == HqHudSystemType.Tags.FixBit) - { - technician.FixedSabotage = true; - int consoleId = amount & HqHudSystemType.IdMask; - int otherConsoleId = (consoleId + 1) % 2; - ShipStatus.Instance.UpdateSystem(SystemTypes.Comms, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HqHudSystemType.Tags.FixBit)); - technician.IncreasePoints(systemType); - } - } - else if (amount == 0) technician.IncreasePoints(systemType); - - break; - } - } - } - - public static void SwitchSystemRepair(byte playerId, SwitchSystem switchSystem, byte amount) - { - if (Main.PlayerStates[playerId].IsDead) return; - - if (!GetsAnyPoint(SystemTypes.Electrical) || Main.PlayerStates[playerId].Role is not Technician technician) return; - - if (amount.HasBit(SwitchSystem.DamageSystem)) return; - - int fixbit = 1 << amount; - switchSystem.ActualSwitches = (byte)(switchSystem.ExpectedSwitches ^ fixbit); - - technician.IncreasePoints(SystemTypes.Electrical); - } - - public override string GetProgressText(byte playerId, bool comms) - { - var points = (int)Math.Round(playerId.GetAbilityUseLimit()); - int needed = RequiredPoints.GetInt(); - Color color = points >= needed ? Color.green : Color.white; - return Utils.ColorString(color, $"{points}/{needed}"); + i++; } } + + public override void Init() + { + On = false; + LocalPlayerFixedSabotageTypes = []; + } + + public override void Add(byte playerId) + { + On = true; + IsWon = false; + Ignore = false; + TechnicianPC = playerId.GetPlayer(); + FixedSabotage = false; + playerId.SetAbilityUseLimit(0); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (CanVent.GetBool()) + { + AURoleOptions.EngineerCooldown = VentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = MaxInVentTime.GetFloat(); + } + } + + public override void OnReportDeadBody() + { + Ignore = true; + } + + public override void AfterMeetingTasks() + { + Ignore = false; + } + + private static SystemTypes GetActualSystemType(SystemTypes systemTypes) + { + return systemTypes switch + { + SystemTypes.Laboratory => SystemTypes.Reactor, + SystemTypes.HeliSabotage => SystemTypes.Reactor, + _ => systemTypes + }; + } + + private static bool GetsAnyPoint(SystemTypes systemType) + { + return PointGains.TryGetValue(GetActualSystemType(systemType), out OptionItem pointGain) && pointGain.GetInt() > 0; + } + + private void IncreasePoints(SystemTypes systemType) + { + SystemTypes actualSystemType = GetActualSystemType(systemType); + TechnicianPC.RpcIncreaseAbilityUseLimitBy(PointGains[actualSystemType].GetInt()); + + if (TechnicianPC.GetAbilityUseLimit() >= RequiredPoints.GetInt()) + { + if (WinsAlone.GetBool()) + { + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Technician); + CustomWinnerHolder.WinnerIds.Add(TechnicianPC.PlayerId); + } + else IsWon = true; + } + + if (TechnicianPC.IsLocalPlayer()) + LocalPlayerFixedSabotageTypes.Add(actualSystemType); + } + + public static void RepairSystem(byte playerId, SystemTypes systemType, byte amount) + { + if (Main.PlayerStates[playerId].IsDead) return; + + if (Main.PlayerStates[playerId].Role is not Technician technician) return; + + if (!GetsAnyPoint(systemType) || technician.IsWon || technician.Ignore) return; + + switch (systemType) + { + case SystemTypes.Reactor: + case SystemTypes.Laboratory: + { + if (amount.HasAnyBit(ReactorSystemType.AddUserOp)) + { + ShipStatus.Instance.UpdateSystem((MapNames)Main.NormalOptions.MapId == MapNames.Polus ? SystemTypes.Laboratory : SystemTypes.Reactor, playerId.GetPlayer(), ReactorSystemType.ClearCountdown); + technician.IncreasePoints(systemType); + } + + break; + } + case SystemTypes.HeliSabotage: + { + var tags = (HeliSabotageSystem.Tags)(amount & HeliSabotageSystem.TagMask); + if (tags == HeliSabotageSystem.Tags.ActiveBit) technician.FixedSabotage = false; + + if (!technician.FixedSabotage && tags == HeliSabotageSystem.Tags.FixBit) + { + technician.FixedSabotage = true; + int consoleId = amount & HeliSabotageSystem.IdMask; + int otherConsoleId = (consoleId + 1) % 2; + ShipStatus.Instance.UpdateSystem(SystemTypes.HeliSabotage, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HeliSabotageSystem.Tags.FixBit)); + technician.IncreasePoints(systemType); + } + + break; + } + case SystemTypes.LifeSupp: + { + if (amount.HasAnyBit(LifeSuppSystemType.AddUserOp)) + { + ShipStatus.Instance.UpdateSystem(SystemTypes.LifeSupp, playerId.GetPlayer(), LifeSuppSystemType.ClearCountdown); + technician.IncreasePoints(systemType); + } + + break; + } + case SystemTypes.Comms: + { + if (Main.CurrentMap is MapNames.Mira or MapNames.Fungle) + { + var tags = (HqHudSystemType.Tags)(amount & HqHudSystemType.TagMask); + if (tags == HqHudSystemType.Tags.ActiveBit) technician.FixedSabotage = false; + + if (!technician.FixedSabotage && tags == HqHudSystemType.Tags.FixBit) + { + technician.FixedSabotage = true; + int consoleId = amount & HqHudSystemType.IdMask; + int otherConsoleId = (consoleId + 1) % 2; + ShipStatus.Instance.UpdateSystem(SystemTypes.Comms, playerId.GetPlayer(), (byte)(otherConsoleId | (int)HqHudSystemType.Tags.FixBit)); + technician.IncreasePoints(systemType); + } + } + else if (amount == 0) technician.IncreasePoints(systemType); + + break; + } + } + } + + public static void SwitchSystemRepair(byte playerId, SwitchSystem switchSystem, byte amount) + { + if (Main.PlayerStates[playerId].IsDead) return; + + if (!GetsAnyPoint(SystemTypes.Electrical) || Main.PlayerStates[playerId].Role is not Technician technician) return; + + if (amount.HasBit(SwitchSystem.DamageSystem)) return; + + int fixbit = 1 << amount; + switchSystem.ActualSwitches = (byte)(switchSystem.ExpectedSwitches ^ fixbit); + + technician.IncreasePoints(SystemTypes.Electrical); + } + + public override string GetProgressText(byte playerId, bool comms) + { + var points = (int)Math.Round(playerId.GetAbilityUseLimit()); + int needed = RequiredPoints.GetInt(); + Color color = points >= needed ? Color.green : Color.white; + return Utils.ColorString(color, $"{points}/{needed}"); + } } \ No newline at end of file diff --git a/Roles/Neutral/Terrorist.cs b/Roles/Neutral/Terrorist.cs index 8baf278d9..7c709e04c 100644 --- a/Roles/Neutral/Terrorist.cs +++ b/Roles/Neutral/Terrorist.cs @@ -1,26 +1,25 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Terrorist : RoleBase { - internal class Terrorist : RoleBase + public override bool IsEnable => false; + + public override void SetupCustomOption() { - public override bool IsEnable => false; + SetupRoleOptions(11500, TabGroup.NeutralRoles, CustomRoles.Terrorist); - public override void SetupCustomOption() - { - SetupRoleOptions(11500, TabGroup.NeutralRoles, CustomRoles.Terrorist); + CanTerroristSuicideWin = new BooleanOptionItem(11510, "CanTerroristSuicideWin", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Terrorist]); - CanTerroristSuicideWin = new BooleanOptionItem(11510, "CanTerroristSuicideWin", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Terrorist]); + TerroristCanGuess = new BooleanOptionItem(11511, "CanGuess", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Terrorist]); - TerroristCanGuess = new BooleanOptionItem(11511, "CanGuess", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Terrorist]); - - OverrideTasksData.Create(11512, TabGroup.NeutralRoles, CustomRoles.Terrorist); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + OverrideTasksData.Create(11512, TabGroup.NeutralRoles, CustomRoles.Terrorist); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/Neutral/Tiger.cs b/Roles/Neutral/Tiger.cs index fd0b3cb39..e57e579b2 100644 --- a/Roles/Neutral/Tiger.cs +++ b/Roles/Neutral/Tiger.cs @@ -3,175 +3,174 @@ using AmongUs.GameOptions; using EHR.Modules; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Tiger : RoleBase { - internal class Tiger : RoleBase + private const int Id = 643500; + + public static OptionItem Radius; + public static OptionItem EnrageCooldown; + public static OptionItem EnrageDuration; + public static OptionItem KillCooldown; + public static OptionItem CanVent; + + public static bool On; + private float CooldownTimer; + + private int Count; + + public float EnrageTimer; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 643500; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Tiger); - public static OptionItem Radius; - public static OptionItem EnrageCooldown; - public static OptionItem EnrageDuration; - public static OptionItem KillCooldown; - public static OptionItem CanVent; + Radius = new FloatOptionItem(Id + 2, "TigerRadius", new(0.5f, 10f, 0.5f), 3f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) + .SetValueFormat(OptionFormat.Multiplier); - public static bool On; - private float CooldownTimer; + EnrageCooldown = new FloatOptionItem(Id + 3, "EnrageCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; + EnrageDuration = new FloatOptionItem(Id + 4, "EnrageDuration", new(1f, 30f, 1f), 10f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) + .SetValueFormat(OptionFormat.Seconds); - public float EnrageTimer; - public override bool IsEnable => On; + KillCooldown = new FloatOptionItem(Id + 5, "KillCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Tiger); + CanVent = new BooleanOptionItem(Id + 6, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]); + } - Radius = new FloatOptionItem(Id + 2, "TigerRadius", new(0.5f, 10f, 0.5f), 3f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) - .SetValueFormat(OptionFormat.Multiplier); + public override void Add(byte playerId) + { + On = true; + EnrageTimer = float.NaN; + CooldownTimer = 0f; + } - EnrageCooldown = new FloatOptionItem(Id + 3, "EnrageCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + On = false; + } - EnrageDuration = new FloatOptionItem(Id + 4, "EnrageDuration", new(1f, 30f, 1f), 10f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) - .SetValueFormat(OptionFormat.Seconds); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - KillCooldown = new FloatOptionItem(Id + 5, "KillCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]) - .SetValueFormat(OptionFormat.Seconds); + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - CanVent = new BooleanOptionItem(Id + 6, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tiger]); - } + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (pc.IsAlive() && !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool())); + } - public override void Add(byte playerId) - { - On = true; - EnrageTimer = float.NaN; - CooldownTimer = 0f; - } + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); - public override void Init() - { - On = false; - } + if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (pc.IsAlive() && !(Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool())); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - if (Options.UsePhantomBasis.GetBool() && Options.UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); - - if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); - } - - public override bool OnSabotage(PlayerControl pc) - { - if (CooldownTimer <= 0f) - { - StartEnraging(); - CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); - } - - return false; - } - - public override void OnPet(PlayerControl pc) + public override bool OnSabotage(PlayerControl pc) + { + if (CooldownTimer <= 0f) { StartEnraging(); + CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); } - public override bool OnVanish(PlayerControl pc) - { - if (CooldownTimer <= 0f) - { - StartEnraging(); - CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); - } + return false; + } - return false; + public override void OnPet(PlayerControl pc) + { + StartEnraging(); + } + + public override bool OnVanish(PlayerControl pc) + { + if (CooldownTimer <= 0f) + { + StartEnraging(); + CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); } - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; + + if (CooldownTimer <= 0f) { - if (!shapeshifting && !Options.UseUnshiftTrigger.GetBool()) return true; - - if (CooldownTimer <= 0f) - { - StartEnraging(); - CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); - } - - return false; + StartEnraging(); + CooldownTimer = EnrageCooldown.GetFloat() + EnrageDuration.GetFloat(); } - private void StartEnraging() + return false; + } + + private void StartEnraging() + { + EnrageTimer = EnrageDuration.GetFloat(); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (CooldownTimer > 0f) CooldownTimer -= Time.fixedDeltaTime; + + if (float.IsNaN(EnrageTimer)) return; + + EnrageTimer -= Time.fixedDeltaTime; + + Count++; + if (Count < 10) return; + + Count = 0; + + Utils.SendRPC(CustomRPC.SyncTiger, pc.PlayerId, EnrageTimer); + + switch (EnrageTimer) { - EnrageTimer = EnrageDuration.GetFloat(); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (CooldownTimer > 0f) CooldownTimer -= Time.fixedDeltaTime; - - if (float.IsNaN(EnrageTimer)) return; - - EnrageTimer -= Time.fixedDeltaTime; - - Count++; - if (Count < 10) return; - - Count = 0; - - Utils.SendRPC(CustomRPC.SyncTiger, pc.PlayerId, EnrageTimer); - - switch (EnrageTimer) - { - case <= 0f: - EnrageTimer = float.NaN; - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - break; - case <= 5f: - Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); - break; - } - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (float.IsNaN(EnrageTimer)) return; - - PlayerControl victim = Main.AllAlivePlayerControls.Where(x => x.PlayerId != killer.PlayerId && x.PlayerId != target.PlayerId).MinBy(x => Vector2.Distance(killer.Pos(), x.Pos())); - if (victim == null || Vector2.Distance(killer.Pos(), victim.Pos()) > Radius.GetFloat()) return; - - if (killer.RpcCheckAndMurder(victim, true)) victim.Suicide(realKiller: killer); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != target.PlayerId) return string.Empty; - - if (Main.PlayerStates[seer.PlayerId].Role is not Tiger { IsEnable: true } tg) return string.Empty; - - if (float.IsNaN(tg.EnrageTimer)) return string.Empty; - - return tg.EnrageTimer > 5 ? "\u25a9" : $"\u25a9 ({(int)(tg.EnrageTimer + 1)}s)"; + case <= 0f: + EnrageTimer = float.NaN; + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + break; + case <= 5f: + Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); + break; } } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (float.IsNaN(EnrageTimer)) return; + + PlayerControl victim = Main.AllAlivePlayerControls.Where(x => x.PlayerId != killer.PlayerId && x.PlayerId != target.PlayerId).MinBy(x => Vector2.Distance(killer.Pos(), x.Pos())); + if (victim == null || Vector2.Distance(killer.Pos(), victim.Pos()) > Radius.GetFloat()) return; + + if (killer.RpcCheckAndMurder(victim, true)) victim.Suicide(realKiller: killer); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != target.PlayerId) return string.Empty; + + if (Main.PlayerStates[seer.PlayerId].Role is not Tiger { IsEnable: true } tg) return string.Empty; + + if (float.IsNaN(tg.EnrageTimer)) return string.Empty; + + return tg.EnrageTimer > 5 ? "\u25a9" : $"\u25a9 ({(int)(tg.EnrageTimer + 1)}s)"; + } } \ No newline at end of file diff --git a/Roles/Neutral/Totocalcio.cs b/Roles/Neutral/Totocalcio.cs index 1d52bc850..edc46e349 100644 --- a/Roles/Neutral/Totocalcio.cs +++ b/Roles/Neutral/Totocalcio.cs @@ -7,181 +7,180 @@ using static EHR.Options; using static EHR.Translator; using Math = System.Math; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Totocalcio : RoleBase { - public class Totocalcio : RoleBase + private const int Id = 9800; + public static List PlayerIdList = []; + + private static OptionItem MaxBetTimes; + public static OptionItem BetCooldown; + private static OptionItem BetCooldownIncrese; + private static OptionItem MaxBetCooldown; + private static OptionItem KnowTargetRole; + private static OptionItem BetTargetKnowTotocalcio; + + public byte BetPlayer; + private int BetTimes; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 9800; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Totocalcio); - private static OptionItem MaxBetTimes; - public static OptionItem BetCooldown; - private static OptionItem BetCooldownIncrese; - private static OptionItem MaxBetCooldown; - private static OptionItem KnowTargetRole; - private static OptionItem BetTargetKnowTotocalcio; + MaxBetTimes = new IntegerOptionItem(Id + 10, "TotocalcioMaxBetTimes", new(1, 5, 1), 3, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) + .SetValueFormat(OptionFormat.Times); - public byte BetPlayer; - private int BetTimes; + BetCooldown = new FloatOptionItem(Id + 12, "TotocalcioBetCooldown", new(0f, 60f, 0.5f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + BetCooldownIncrese = new FloatOptionItem(Id + 14, "TotocalcioBetCooldownIncrese", new(0f, 60f, 1f), 10f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + MaxBetCooldown = new FloatOptionItem(Id + 16, "TotocalcioMaxBetCooldown", new(0f, 180f, 0.5f), 70f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) + .SetValueFormat(OptionFormat.Seconds); + + KnowTargetRole = new BooleanOptionItem(Id + 18, "TotocalcioKnowTargetRole", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]); + + BetTargetKnowTotocalcio = new BooleanOptionItem(Id + 20, "TotocalcioBetTargetKnowTotocalcio", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]); + } + + public override void Init() + { + PlayerIdList = []; + BetTimes = MaxBetTimes.GetInt(); + BetPlayer = byte.MaxValue; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + BetTimes = MaxBetTimes.GetInt(); + BetPlayer = byte.MaxValue; + } + + private void SendRPC(byte playerId) + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncTotocalcioTargetAndTimes, SendOption.Reliable); + writer.Write(playerId); + writer.Write(BetTimes); + writer.Write(BetPlayer); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public static void ReceiveRPC(MessageReader reader) + { + byte PlayerId = reader.ReadByte(); + if (Main.PlayerStates[PlayerId].Role is not Totocalcio tc) return; + + int Times = reader.ReadInt32(); + byte Target = reader.ReadByte(); + tc.BetTimes = Times; + if (Target != byte.MaxValue) tc.BetPlayer = Target; + } + + public override bool CanUseKillButton(PlayerControl player) + { + return !player.Data.IsDead && BetTimes >= 1; + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return false; + } + + public override void SetKillCooldown(byte id) + { + if (BetTimes < 1) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Totocalcio); - - MaxBetTimes = new IntegerOptionItem(Id + 10, "TotocalcioMaxBetTimes", new(1, 5, 1), 3, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) - .SetValueFormat(OptionFormat.Times); - - BetCooldown = new FloatOptionItem(Id + 12, "TotocalcioBetCooldown", new(0f, 60f, 0.5f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) - .SetValueFormat(OptionFormat.Seconds); - - BetCooldownIncrese = new FloatOptionItem(Id + 14, "TotocalcioBetCooldownIncrese", new(0f, 60f, 1f), 10f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) - .SetValueFormat(OptionFormat.Seconds); - - MaxBetCooldown = new FloatOptionItem(Id + 16, "TotocalcioMaxBetCooldown", new(0f, 180f, 0.5f), 70f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]) - .SetValueFormat(OptionFormat.Seconds); - - KnowTargetRole = new BooleanOptionItem(Id + 18, "TotocalcioKnowTargetRole", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]); - - BetTargetKnowTotocalcio = new BooleanOptionItem(Id + 20, "TotocalcioBetTargetKnowTotocalcio", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Totocalcio]); + Main.AllPlayerKillCooldown[id] = 300f; + return; } - public override void Init() + float cd = BetCooldown.GetFloat(); + cd += Main.AllPlayerControls.Count(x => !x.IsAlive()) * BetCooldownIncrese.GetFloat(); + cd = Math.Min(cd, MaxBetCooldown.GetFloat()); + Main.AllPlayerKillCooldown[id] = cd; + } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (!KnowTargetRole.GetBool()) return false; + + return player.Is(CustomRoles.Totocalcio) && BetPlayer == target.PlayerId; + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer.PlayerId == target.PlayerId) return true; + + if (BetPlayer == target.PlayerId) return false; + + if (BetTimes < 1) return false; + + BetTimes--; + PlayerControl betPlayer = Utils.GetPlayerById(BetPlayer); + + if (betPlayer != null) { - PlayerIdList = []; - BetTimes = MaxBetTimes.GetInt(); - BetPlayer = byte.MaxValue; + Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: betPlayer, ForceLoop: true); + Utils.NotifyRoles(SpecifySeer: betPlayer, SpecifyTarget: killer, ForceLoop: true); } - public override void Add(byte playerId) + BetPlayer = target.PlayerId; + SendRPC(killer.PlayerId); + + killer.ResetKillCooldown(); + killer.SetKillCooldown(); + killer.RPCPlayCustomSound("Bet"); + + killer.Notify(GetString("TotocalcioBetPlayer")); + if (BetTargetKnowTotocalcio.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Totocalcio), GetString("TotocalcioBetOnYou"))); + + Logger.Info($"Target selected:{killer.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Totocalcio"); + return false; + } + + public static string TargetMark(PlayerControl seer, PlayerControl target) + { + if (!seer.Is(CustomRoles.Totocalcio)) { - PlayerIdList.Add(playerId); - BetTimes = MaxBetTimes.GetInt(); - BetPlayer = byte.MaxValue; + if (!BetTargetKnowTotocalcio.GetBool()) return string.Empty; + + if (Main.PlayerStates[target.PlayerId].Role is not Totocalcio tc) return string.Empty; + + return seer.PlayerId == tc.BetPlayer ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Totocalcio), "♦") : string.Empty; } - - private void SendRPC(byte playerId) + else { - if (!IsEnable || !Utils.DoRPC) return; + if (Main.PlayerStates[seer.PlayerId].Role is not Totocalcio tc) return string.Empty; - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SyncTotocalcioTargetAndTimes, SendOption.Reliable); - writer.Write(playerId); - writer.Write(BetTimes); - writer.Write(BetPlayer); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte PlayerId = reader.ReadByte(); - if (Main.PlayerStates[PlayerId].Role is not Totocalcio tc) return; - - int Times = reader.ReadInt32(); - byte Target = reader.ReadByte(); - tc.BetTimes = Times; - if (Target != byte.MaxValue) tc.BetPlayer = Target; - } - - public override bool CanUseKillButton(PlayerControl player) - { - return !player.Data.IsDead && BetTimes >= 1; - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return false; - } - - public override void SetKillCooldown(byte id) - { - if (BetTimes < 1) - { - Main.AllPlayerKillCooldown[id] = 300f; - return; - } - - float cd = BetCooldown.GetFloat(); - cd += Main.AllPlayerControls.Count(x => !x.IsAlive()) * BetCooldownIncrese.GetFloat(); - cd = Math.Min(cd, MaxBetCooldown.GetFloat()); - Main.AllPlayerKillCooldown[id] = cd; - } - - public override bool KnowRole(PlayerControl player, PlayerControl target) - { - if (base.KnowRole(player, target)) return true; - - if (!KnowTargetRole.GetBool()) return false; - - return player.Is(CustomRoles.Totocalcio) && BetPlayer == target.PlayerId; - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer.PlayerId == target.PlayerId) return true; - - if (BetPlayer == target.PlayerId) return false; - - if (BetTimes < 1) return false; - - BetTimes--; - PlayerControl betPlayer = Utils.GetPlayerById(BetPlayer); - - if (betPlayer != null) - { - Utils.NotifyRoles(SpecifySeer: killer, SpecifyTarget: betPlayer, ForceLoop: true); - Utils.NotifyRoles(SpecifySeer: betPlayer, SpecifyTarget: killer, ForceLoop: true); - } - - BetPlayer = target.PlayerId; - SendRPC(killer.PlayerId); - - killer.ResetKillCooldown(); - killer.SetKillCooldown(); - killer.RPCPlayCustomSound("Bet"); - - killer.Notify(GetString("TotocalcioBetPlayer")); - if (BetTargetKnowTotocalcio.GetBool()) target.Notify(Utils.ColorString(Utils.GetRoleColor(CustomRoles.Totocalcio), GetString("TotocalcioBetOnYou"))); - - Logger.Info($"Target selected:{killer.GetNameWithRole().RemoveHtmlTags()} => {target.GetNameWithRole().RemoveHtmlTags()}", "Totocalcio"); - return false; - } - - public static string TargetMark(PlayerControl seer, PlayerControl target) - { - if (!seer.Is(CustomRoles.Totocalcio)) - { - if (!BetTargetKnowTotocalcio.GetBool()) return string.Empty; - - if (Main.PlayerStates[target.PlayerId].Role is not Totocalcio tc) return string.Empty; - - return seer.PlayerId == tc.BetPlayer ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Totocalcio), "♦") : string.Empty; - } - else - { - if (Main.PlayerStates[seer.PlayerId].Role is not Totocalcio tc) return string.Empty; - - return tc.BetPlayer == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Totocalcio), "♦") : string.Empty; - } - } - - public override string GetProgressText(byte playerId, bool comms) - { - PlayerControl player = Utils.GetPlayerById(playerId); - if (Main.PlayerStates[playerId].Role is not Totocalcio tc) return string.Empty; - - return player == null ? string.Empty : Utils.ColorString(tc.CanUseKillButton(player) ? Utils.GetRoleColor(CustomRoles.Totocalcio) : Color.gray, $"({tc.BetTimes})"); - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.KillButton?.OverrideText(GetString("TotocalcioKillButtonText")); + return tc.BetPlayer == target.PlayerId ? Utils.ColorString(Utils.GetRoleColor(CustomRoles.Totocalcio), "♦") : string.Empty; } } + + public override string GetProgressText(byte playerId, bool comms) + { + PlayerControl player = Utils.GetPlayerById(playerId); + if (Main.PlayerStates[playerId].Role is not Totocalcio tc) return string.Empty; + + return player == null ? string.Empty : Utils.ColorString(tc.CanUseKillButton(player) ? Utils.GetRoleColor(CustomRoles.Totocalcio) : Color.gray, $"({tc.BetTimes})"); + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.KillButton?.OverrideText(GetString("TotocalcioKillButtonText")); + } } \ No newline at end of file diff --git a/Roles/Neutral/Traitor.cs b/Roles/Neutral/Traitor.cs index 4d5a62b59..6b1cfb13d 100644 --- a/Roles/Neutral/Traitor.cs +++ b/Roles/Neutral/Traitor.cs @@ -2,74 +2,73 @@ using System.Collections.Generic; using AmongUs.GameOptions; using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Traitor : RoleBase { - public class Traitor : RoleBase + private const int Id = 13100; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + public static OptionItem CanSabotage; + public static OptionItem CanGetImpostorOnlyAddons; + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 13100; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Traitor); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - public static OptionItem CanSabotage; - public static OptionItem CanGetImpostorOnlyAddons; - public override bool IsEnable => PlayerIdList.Count > 0; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Traitor); + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]) - .SetValueFormat(OptionFormat.Seconds); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); - - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); - - CanSabotage = new BooleanOptionItem(Id + 15, "CanUseSabotage", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); + CanSabotage = new BooleanOptionItem(Id + 15, "CanUseSabotage", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); - CanGetImpostorOnlyAddons = new BooleanOptionItem(Id + 16, "CanGetImpostorOnlyAddons", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); - } + CanGetImpostorOnlyAddons = new BooleanOptionItem(Id + 16, "CanGetImpostorOnlyAddons", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Traitor]); + } - public override void Init() - { - PlayerIdList = []; - } + public override void Init() + { + PlayerIdList = []; + } - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - } + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + } - public override void SetButtonTexts(HudManager __instance, byte id) - { - __instance.SabotageButton.ToggleVisible(CanSabotage.GetBool()); - } + public override void SetButtonTexts(HudManager __instance, byte id) + { + __instance.SabotageButton.ToggleVisible(CanSabotage.GetBool()); + } - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (CanSabotage.GetBool() && pc.IsAlive()); - } + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (CanSabotage.GetBool() && pc.IsAlive()); } } \ No newline at end of file diff --git a/Roles/Neutral/Tremor.cs b/Roles/Neutral/Tremor.cs index 61388b3e4..26ee6542f 100644 --- a/Roles/Neutral/Tremor.cs +++ b/Roles/Neutral/Tremor.cs @@ -5,161 +5,160 @@ using EHR.Modules; using Hazel; using UnityEngine; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Tremor : RoleBase { - public class Tremor : RoleBase + private const int Id = 644600; + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem TimerStart; + private static OptionItem TimerDecrease; + private static OptionItem DoomTime; + + private int Count; + private int DoomTimer; + private long LastUpdate = Utils.TimeStamp; + private int Timer; + private byte TremorId; + + public override bool IsEnable => On; + public bool IsDoom => Timer <= 0; + + public override void SetupCustomOption() { - private const int Id = 644600; - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Tremor); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem TimerStart; - private static OptionItem TimerDecrease; - private static OptionItem DoomTime; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) + .SetValueFormat(OptionFormat.Seconds); - private int Count; - private int DoomTimer; - private long LastUpdate = Utils.TimeStamp; - private int Timer; - private byte TremorId; + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]); - public override bool IsEnable => On; - public bool IsDoom => Timer <= 0; + HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]); - public override void SetupCustomOption() + TimerStart = new IntegerOptionItem(Id + 5, "Tremor.TimerStart", new(0, 600, 5), 180, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) + .SetValueFormat(OptionFormat.Seconds); + + TimerDecrease = new IntegerOptionItem(Id + 6, "Tremor.TimerDecrease", new(0, 180, 1), 15, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) + .SetValueFormat(OptionFormat.Seconds); + + DoomTime = new IntegerOptionItem(Id + 7, "Tremor.DoomTime", new(0, 180, 1), 30, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) + .SetValueFormat(OptionFormat.Seconds); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + Timer = TimerStart.GetInt() + 8; + DoomTimer = 0; + TremorId = playerId; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive()) return; + + bool wasDoom = IsDoom; + + if (!IsDoom && LastUpdate != Utils.TimeStamp) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Tremor); - - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]); - - HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]); - - TimerStart = new IntegerOptionItem(Id + 5, "Tremor.TimerStart", new(0, 600, 5), 180, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) - .SetValueFormat(OptionFormat.Seconds); - - TimerDecrease = new IntegerOptionItem(Id + 6, "Tremor.TimerDecrease", new(0, 180, 1), 15, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) - .SetValueFormat(OptionFormat.Seconds); - - DoomTime = new IntegerOptionItem(Id + 7, "Tremor.DoomTime", new(0, 180, 1), 30, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Tremor]) - .SetValueFormat(OptionFormat.Seconds); + Timer--; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, Timer); + LastUpdate = Utils.TimeStamp; } - public override void Init() + if (wasDoom != IsDoom) { - On = false; + Main.AllAlivePlayerControls.Do(x => x.Notify(Translator.GetString("Tremor.DoomNotify"))); + DoomTimer = DoomTime.GetInt(); } - public override void Add(byte playerId) + if (IsDoom) { - On = true; - Timer = TimerStart.GetInt() + 8; - DoomTimer = 0; - TremorId = playerId; - } + Count++; - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + if (Count % 3 == 0) pc.RpcGuardAndKill(); - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } + if (Count < 15) return; - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } + Count = 0; - public override void OnFixedUpdate(PlayerControl pc) - { - if (!GameStates.IsInTask || ExileController.Instance || !pc.IsAlive()) return; + Vector2 pos = pc.Pos(); - bool wasDoom = IsDoom; + Main.AllAlivePlayerControls + .Where(x => x.PlayerId != pc.PlayerId && Vector2.Distance(pos, x.Pos()) <= 1.5f) + .Do(pc.Kill); - if (!IsDoom && LastUpdate != Utils.TimeStamp) + if (LastUpdate == Utils.TimeStamp) return; + + LastUpdate = Utils.TimeStamp; + + DoomTimer--; + Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, DoomTimer); + + if (DoomTimer <= 0) { - Timer--; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, Timer); - LastUpdate = Utils.TimeStamp; + pc.Suicide(); + + if (PlayerControl.LocalPlayer.IsAlive()) + Achievements.Type.Armageddon.Complete(); } - - if (wasDoom != IsDoom) - { - Main.AllAlivePlayerControls.Do(x => x.Notify(Translator.GetString("Tremor.DoomNotify"))); - DoomTimer = DoomTime.GetInt(); - } - - if (IsDoom) - { - Count++; - - if (Count % 3 == 0) pc.RpcGuardAndKill(); - - if (Count < 15) return; - - Count = 0; - - Vector2 pos = pc.Pos(); - - Main.AllAlivePlayerControls - .Where(x => x.PlayerId != pc.PlayerId && Vector2.Distance(pos, x.Pos()) <= 1.5f) - .Do(pc.Kill); - - if (LastUpdate == Utils.TimeStamp) return; - - LastUpdate = Utils.TimeStamp; - - DoomTimer--; - Utils.SendRPC(CustomRPC.SyncRoleData, pc.PlayerId, DoomTimer); - - if (DoomTimer <= 0) - { - pc.Suicide(); - - if (PlayerControl.LocalPlayer.IsAlive()) - Achievements.Type.Armageddon.Complete(); - } - } - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - Timer = Math.Max(Timer - TimerDecrease.GetInt(), 0); - Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, Timer); - } - - public void ReceiveRPC(MessageReader reader) - { - int value = reader.ReadPackedInt32(); - - if (IsDoom) - DoomTimer = value; - else - Timer = value; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != TremorId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || meeting) return string.Empty; - - Color color = IsDoom ? Color.yellow : Color.cyan; - string text = IsDoom ? DoomTimer.ToString() : Timer.ToString(); - if (hud) text = $"{text}"; - - return Utils.ColorString(color, text); } } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + Timer = Math.Max(Timer - TimerDecrease.GetInt(), 0); + Utils.SendRPC(CustomRPC.SyncRoleData, killer.PlayerId, Timer); + } + + public void ReceiveRPC(MessageReader reader) + { + int value = reader.ReadPackedInt32(); + + if (IsDoom) + DoomTimer = value; + else + Timer = value; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != TremorId || seer.PlayerId != target.PlayerId || (seer.IsModClient() && !hud) || meeting) return string.Empty; + + Color color = IsDoom ? Color.yellow : Color.cyan; + string text = IsDoom ? DoomTimer.ToString() : Timer.ToString(); + if (hud) text = $"{text}"; + + return Utils.ColorString(color, text); + } } \ No newline at end of file diff --git a/Roles/Neutral/Vengeance.cs b/Roles/Neutral/Vengeance.cs index 831dea32e..a2af17993 100644 --- a/Roles/Neutral/Vengeance.cs +++ b/Roles/Neutral/Vengeance.cs @@ -4,141 +4,140 @@ using EHR.Modules; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Vengeance : RoleBase { - public class Vengeance : RoleBase + private const int Id = 12820; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + public static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem RevengeTime; + + private bool IsRevenge; + private byte Killer; + private bool Success; + private float tempKillTimer; + private int Timer; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 12820; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vengeance); - private static OptionItem KillCooldown; - public static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem RevengeTime; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]) + .SetValueFormat(OptionFormat.Seconds); - private bool IsRevenge; - private byte Killer; - private bool Success; - private float tempKillTimer; - private int Timer; + RevengeTime = new IntegerOptionItem(Id + 11, "VengeanceRevengeTime", new(0, 30, 1), 15, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]); + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]); + } - public override void SetupCustomOption() + public override void Init() + { + PlayerIdList = []; + IsRevenge = false; + Success = false; + Killer = byte.MaxValue; + tempKillTimer = 0; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + + Timer = RevengeTime.GetInt(); + + IsRevenge = false; + Success = false; + Killer = byte.MaxValue; + tempKillTimer = 0; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (killer.PlayerId == target.PlayerId) return true; + + if (IsRevenge) return true; + + LateTask.New(() => { target.TPToRandomVent(); }, 0.01f, log: false); + + Timer = RevengeTime.GetInt(); + Countdown(Timer, target); + IsRevenge = true; + killer.SetKillCooldown(); + tempKillTimer = Main.KillTimers[target.PlayerId]; + target.SetKillCooldown(1f); + Killer = killer.PlayerId; + + return false; + } + + private void Countdown(int seconds, PlayerControl player) + { + if (!player.IsAlive()) return; + + if (Success) { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vengeance); - - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]) - .SetValueFormat(OptionFormat.Seconds); - - RevengeTime = new IntegerOptionItem(Id + 11, "VengeanceRevengeTime", new(0, 30, 1), 15, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]); - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Vengeance]); - } - - public override void Init() - { - PlayerIdList = []; - IsRevenge = false; - Success = false; - Killer = byte.MaxValue; - tempKillTimer = 0; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - Timer = RevengeTime.GetInt(); - - IsRevenge = false; Success = false; - Killer = byte.MaxValue; - tempKillTimer = 0; + return; } - public override void SetKillCooldown(byte id) + if (seconds <= 0 || GameStates.IsMeeting) { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + player.Suicide(PlayerState.DeathReason.Kill); + + if (player.IsLocalPlayer()) + Achievements.Type.OutOfTime.Complete(); + + return; } - public override void ApplyGameOptions(IGameOptions opt, byte id) + player.Notify(string.Format(GetString("VengeanceRevenge"), seconds), 1.1f, overrideAll: true); + Timer = seconds; + + LateTask.New(() => { Countdown(seconds - 1, player); }, 1.01f, log: false); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + if (!IsRevenge) return true; + + if (target.PlayerId == Killer) { - opt.SetVision(HasImpostorVision.GetBool()); + Success = true; + killer.Notify(GetString("VengeanceSuccess")); + killer.SetKillCooldown(KillCooldown.GetFloat() + tempKillTimer); + IsRevenge = false; + return true; } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (killer.PlayerId == target.PlayerId) return true; - - if (IsRevenge) return true; - - LateTask.New(() => { target.TPToRandomVent(); }, 0.01f, log: false); - - Timer = RevengeTime.GetInt(); - Countdown(Timer, target); - IsRevenge = true; - killer.SetKillCooldown(); - tempKillTimer = Main.KillTimers[target.PlayerId]; - target.SetKillCooldown(1f); - Killer = killer.PlayerId; - - return false; - } - - private void Countdown(int seconds, PlayerControl player) - { - if (!player.IsAlive()) return; - - if (Success) - { - Timer = RevengeTime.GetInt(); - Success = false; - return; - } - - if (seconds <= 0 || GameStates.IsMeeting) - { - player.Suicide(PlayerState.DeathReason.Kill); - - if (player.IsLocalPlayer()) - Achievements.Type.OutOfTime.Complete(); - - return; - } - - player.Notify(string.Format(GetString("VengeanceRevenge"), seconds), 1.1f, overrideAll: true); - Timer = seconds; - - LateTask.New(() => { Countdown(seconds - 1, player); }, 1.01f, log: false); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return false; - - if (target == null) return false; - - if (!IsRevenge) return true; - - if (target.PlayerId == Killer) - { - Success = true; - killer.Notify(GetString("VengeanceSuccess")); - killer.SetKillCooldown(KillCooldown.GetFloat() + tempKillTimer); - IsRevenge = false; - return true; - } - - killer.Kill(killer); - return false; - } + killer.Kill(killer); + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Virus.cs b/Roles/Neutral/Virus.cs index fdbc64a64..7280bf698 100644 --- a/Roles/Neutral/Virus.cs +++ b/Roles/Neutral/Virus.cs @@ -7,196 +7,195 @@ using EHR.Patches; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Virus : RoleBase { - public class Virus : RoleBase + private const int Id = 13200; + public static List PlayerIdList = []; + public static List InfectedPlayer = []; + public static Dictionary VirusNotify = []; + public static List InfectedBodies = []; + private static int TotalInfections; + + private static OptionItem KillCooldown; + private static OptionItem InfectMax; + public static OptionItem CanVent; + public static OptionItem ImpostorVision; + public static OptionItem KnowTargetRole; + public static OptionItem TargetKnowOtherTarget; + public static OptionItem KillInfectedPlayerAfterMeeting; + public static OptionItem ContagiousPlayersCanKillEachOther; + public static OptionItem ContagiousCountMode; + + public static readonly string[] ContagiousCountModeStrings = + [ + "ContagiousCountMode.None", + "ContagiousCountMode.Virus", + "ContagiousCountMode.Original" + ]; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 13200; - public static List PlayerIdList = []; - public static List InfectedPlayer = []; - public static Dictionary VirusNotify = []; - public static List InfectedBodies = []; - private static int TotalInfections; + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Virus); - private static OptionItem KillCooldown; - private static OptionItem InfectMax; - public static OptionItem CanVent; - public static OptionItem ImpostorVision; - public static OptionItem KnowTargetRole; - public static OptionItem TargetKnowOtherTarget; - public static OptionItem KillInfectedPlayerAfterMeeting; - public static OptionItem ContagiousPlayersCanKillEachOther; - public static OptionItem ContagiousCountMode; + KillCooldown = new FloatOptionItem(Id + 10, "VirusKillCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]) + .SetValueFormat(OptionFormat.Seconds); - public static readonly string[] ContagiousCountModeStrings = - [ - "ContagiousCountMode.None", - "ContagiousCountMode.Virus", - "ContagiousCountMode.Original" - ]; + CanVent = new BooleanOptionItem(Id + 11, "VirusCanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - public override bool IsEnable => PlayerIdList.Count > 0; + ImpostorVision = new BooleanOptionItem(Id + 16, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - public override void SetupCustomOption() + InfectMax = new IntegerOptionItem(Id + 12, "VirusInfectMax", new(1, 15, 1), 2, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]) + .SetValueFormat(OptionFormat.Times); + + KnowTargetRole = new BooleanOptionItem(Id + 13, "VirusKnowTargetRole", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); + + TargetKnowOtherTarget = new BooleanOptionItem(Id + 14, "VirusTargetKnowOtherTarget", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); + + KillInfectedPlayerAfterMeeting = new BooleanOptionItem(Id + 15, "VirusKillInfectedPlayerAfterMeeting", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); + + ContagiousPlayersCanKillEachOther = new BooleanOptionItem(Id + 18, "ContagiousPlayersCanKillEachOther", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); + + ContagiousCountMode = new StringOptionItem(Id + 17, "ContagiousCountMode", ContagiousCountModeStrings, 0, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); + } + + public override void Init() + { + PlayerIdList = []; + InfectedPlayer = []; + TotalInfections = 0; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + playerId.SetAbilityUseLimit(InfectMax.GetInt()); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + opt.SetVision(ImpostorVision.GetBool()); + } + + public override void OnMurder(PlayerControl killer, PlayerControl target) + { + if (killer.GetAbilityUseLimit() < 1) return; + + InfectedBodies.Add(target.PlayerId); + } + + public static void OnKilledBodyReport(PlayerControl target) + { + if (!CanBeInfected(target)) return; + + byte id = PlayerIdList[0]; + Utils.GetPlayerById(id).RpcRemoveAbilityUse(); + + if (KillInfectedPlayerAfterMeeting.GetBool()) { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Virus); + InfectedPlayer.Add(target.PlayerId); + VirusNotify.Add(target.PlayerId, GetString("VirusNoticeMessage2")); - KillCooldown = new FloatOptionItem(Id + 10, "VirusKillCooldown", new(0f, 60f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 11, "VirusCanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - - ImpostorVision = new BooleanOptionItem(Id + 16, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - - InfectMax = new IntegerOptionItem(Id + 12, "VirusInfectMax", new(1, 15, 1), 2, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]) - .SetValueFormat(OptionFormat.Times); - - KnowTargetRole = new BooleanOptionItem(Id + 13, "VirusKnowTargetRole", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - - TargetKnowOtherTarget = new BooleanOptionItem(Id + 14, "VirusTargetKnowOtherTarget", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - - KillInfectedPlayerAfterMeeting = new BooleanOptionItem(Id + 15, "VirusKillInfectedPlayerAfterMeeting", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - - ContagiousPlayersCanKillEachOther = new BooleanOptionItem(Id + 18, "ContagiousPlayersCanKillEachOther", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - - ContagiousCountMode = new StringOptionItem(Id + 17, "ContagiousCountMode", ContagiousCountModeStrings, 0, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Virus]); - } - - public override void Init() - { - PlayerIdList = []; - InfectedPlayer = []; - TotalInfections = 0; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - playerId.SetAbilityUseLimit(InfectMax.GetInt()); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return pc.IsAlive(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - opt.SetVision(ImpostorVision.GetBool()); - } - - public override void OnMurder(PlayerControl killer, PlayerControl target) - { - if (killer.GetAbilityUseLimit() < 1) return; - - InfectedBodies.Add(target.PlayerId); - } - - public static void OnKilledBodyReport(PlayerControl target) - { - if (!CanBeInfected(target)) return; - - byte id = PlayerIdList[0]; - Utils.GetPlayerById(id).RpcRemoveAbilityUse(); - - if (KillInfectedPlayerAfterMeeting.GetBool()) + if (id == PlayerControl.LocalPlayer.PlayerId) { - InfectedPlayer.Add(target.PlayerId); - VirusNotify.Add(target.PlayerId, GetString("VirusNoticeMessage2")); - - if (id == PlayerControl.LocalPlayer.PlayerId) - { - TotalInfections++; - if (TotalInfections >= 2) Achievements.Type.Covid20.Complete(); - Achievements.Type.YoureMyFriendNow.Complete(); - } + TotalInfections++; + if (TotalInfections >= 2) Achievements.Type.Covid20.Complete(); + Achievements.Type.YoureMyFriendNow.Complete(); } - else - { - target.RpcSetCustomRole(CustomRoles.Contagious); - Utils.NotifyRoles(ForceLoop: true); - VirusNotify.Add(target.PlayerId, GetString("VirusNoticeMessage")); - } - - Logger.Info("Add-on assigned:" + target.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Contagious, "Assign " + CustomRoles.Contagious); + } + else + { + target.RpcSetCustomRole(CustomRoles.Contagious); + Utils.NotifyRoles(ForceLoop: true); + VirusNotify.Add(target.PlayerId, GetString("VirusNoticeMessage")); } - public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + Logger.Info("Add-on assigned:" + target.Data?.PlayerName + " = " + target.GetCustomRole() + " + " + CustomRoles.Contagious, "Assign " + CustomRoles.Contagious); + } + + public static void OnCheckForEndVoting(PlayerState.DeathReason deathReason, params byte[] exileIds) + { + try { - try + if (!KillInfectedPlayerAfterMeeting.GetBool()) return; + + PlayerControl virus = Main.AllAlivePlayerControls.FirstOrDefault(a => a.GetCustomRole() == CustomRoles.Virus); + if (virus == null || deathReason != PlayerState.DeathReason.Vote) return; + + if (exileIds.Contains(virus.PlayerId)) { - if (!KillInfectedPlayerAfterMeeting.GetBool()) return; - - PlayerControl virus = Main.AllAlivePlayerControls.FirstOrDefault(a => a.GetCustomRole() == CustomRoles.Virus); - if (virus == null || deathReason != PlayerState.DeathReason.Vote) return; - - if (exileIds.Contains(virus.PlayerId)) - { - InfectedPlayer.Clear(); - return; - } - - var infectedIdList = new List(); - - foreach (PlayerControl pc in Main.AllAlivePlayerControls) - { - bool isInfected = InfectedPlayer.Contains(pc.PlayerId); - if (!isInfected) continue; - - if (virus.IsAlive()) - { - if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) - { - pc.SetRealKiller(virus); - infectedIdList.Add(pc.PlayerId); - } - } - else - Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); - } - - CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Infected, [.. infectedIdList]); InfectedPlayer.Clear(); + return; } - catch (Exception e) + + var infectedIdList = new List(); + + foreach (PlayerControl pc in Main.AllAlivePlayerControls) { - Utils.ThrowException(e); + bool isInfected = InfectedPlayer.Contains(pc.PlayerId); + if (!isInfected) continue; + + if (virus.IsAlive()) + { + if (!Main.AfterMeetingDeathPlayers.ContainsKey(pc.PlayerId)) + { + pc.SetRealKiller(virus); + infectedIdList.Add(pc.PlayerId); + } + } + else + Main.AfterMeetingDeathPlayers.Remove(pc.PlayerId); } + + CheckForEndVotingPatch.TryAddAfterMeetingDeathPlayers(PlayerState.DeathReason.Infected, [.. infectedIdList]); + InfectedPlayer.Clear(); } - - public override bool KnowRole(PlayerControl player, PlayerControl target) + catch (Exception e) { - if (base.KnowRole(player, target)) return true; - - if (player.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Virus)) return true; - - if (KnowTargetRole.GetBool() && player.Is(CustomRoles.Virus) && target.Is(CustomRoles.Contagious)) return true; - - return TargetKnowOtherTarget.GetBool() && player.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Contagious); - } - - private static bool CanBeInfected(PlayerControl pc) - { - return !pc.Is(CustomRoles.Virus) && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Curser); + Utils.ThrowException(e); } } + + public override bool KnowRole(PlayerControl player, PlayerControl target) + { + if (base.KnowRole(player, target)) return true; + + if (player.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Virus)) return true; + + if (KnowTargetRole.GetBool() && player.Is(CustomRoles.Virus) && target.Is(CustomRoles.Contagious)) return true; + + return TargetKnowOtherTarget.GetBool() && player.Is(CustomRoles.Contagious) && target.Is(CustomRoles.Contagious); + } + + private static bool CanBeInfected(PlayerControl pc) + { + return !pc.Is(CustomRoles.Virus) && !pc.Is(CustomRoles.Contagious) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Curser); + } } \ No newline at end of file diff --git a/Roles/Neutral/Vortex.cs b/Roles/Neutral/Vortex.cs index e5f78e935..87bfae514 100644 --- a/Roles/Neutral/Vortex.cs +++ b/Roles/Neutral/Vortex.cs @@ -1,75 +1,74 @@ using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Vortex : RoleBase { - public class Vortex : RoleBase + private const int Id = 645650; + public static bool On; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem IfTargetCannotBeTeleported; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - private const int Id = 645650; - public static bool On; + Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vortex); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem IfTargetCannotBeTeleported; + KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => On; + CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); - public override void SetupCustomOption() + HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); + + IfTargetCannotBeTeleported = new StringOptionItem(Id + 5, "Vortex.IfTargetCannotBeTeleported", ["Vortex.NoTPMode.Block", "Vortex.NoTPMode.DoWithoutTP"], 0, TabGroup.NeutralRoles) + .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); + } + + public override void Init() + { + On = false; + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (!killer.RpcCheckAndMurder(target, true)) return false; + + if (!target.TPToRandomVent()) { - Options.SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vortex); - - KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]) - .SetValueFormat(OptionFormat.Seconds); - - CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); - - HasImpostorVision = new BooleanOptionItem(Id + 4, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); - - IfTargetCannotBeTeleported = new StringOptionItem(Id + 5, "Vortex.IfTargetCannotBeTeleported", ["Vortex.NoTPMode.Block", "Vortex.NoTPMode.DoWithoutTP"], 0, TabGroup.NeutralRoles) - .SetParent(Options.CustomRoleSpawnChances[CustomRoles.Vortex]); + killer.Notify(Translator.GetString("TargetCannotBeTeleported")); + return IfTargetCannotBeTeleported.GetValue() == 1; } - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (!killer.RpcCheckAndMurder(target, true)) return false; - - if (!target.TPToRandomVent()) - { - killer.Notify(Translator.GetString("TargetCannotBeTeleported")); - return IfTargetCannotBeTeleported.GetValue() == 1; - } - - LateTask.New(() => target.Suicide(PlayerState.DeathReason.Kill, killer), 0.2f, log: false); - killer.SetKillCooldown(); - return false; - } + LateTask.New(() => target.Suicide(PlayerState.DeathReason.Kill, killer), 0.2f, log: false); + killer.SetKillCooldown(); + return false; } } \ No newline at end of file diff --git a/Roles/Neutral/Vulture.cs b/Roles/Neutral/Vulture.cs index a9d30f45c..380f5951e 100644 --- a/Roles/Neutral/Vulture.cs +++ b/Roles/Neutral/Vulture.cs @@ -5,193 +5,192 @@ using UnityEngine; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Vulture : RoleBase { - public class Vulture : RoleBase + private const int Id = 11600; + private static List PlayerIdList = []; + + public static HashSet UnreportablePlayers = []; + + private static OptionItem ArrowsPointingToDeadBody; + private static OptionItem NumberOfReportsToWin; + public static OptionItem CanVent; + private static OptionItem VultureReportCD; + private static OptionItem MaxEaten; + private static OptionItem HasImpVision; + private static OptionItem ChangeRoleWhenCantWin; + private static OptionItem ChangeRole; + + private static readonly CustomRoles[] ChangeRoles = + [ + CustomRoles.Amnesiac, + CustomRoles.Pursuer, + CustomRoles.Maverick, + CustomRoles.Totocalcio, + CustomRoles.Opportunist, + CustomRoles.Crewmate, + CustomRoles.Jester + ]; + + private int BodyReportCount; + private long LastReport; + private byte VultureId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + public override void SetupCustomOption() { - private const int Id = 11600; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vulture); - public static HashSet UnreportablePlayers = []; + ArrowsPointingToDeadBody = new BooleanOptionItem(Id + 10, "VultureArrowsPointingToDeadBody", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - private static OptionItem ArrowsPointingToDeadBody; - private static OptionItem NumberOfReportsToWin; - public static OptionItem CanVent; - private static OptionItem VultureReportCD; - private static OptionItem MaxEaten; - private static OptionItem HasImpVision; - private static OptionItem ChangeRoleWhenCantWin; - private static OptionItem ChangeRole; + NumberOfReportsToWin = new IntegerOptionItem(Id + 11, "VultureNumberOfReportsToWin", new(1, 10, 1), 4, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - private static readonly CustomRoles[] ChangeRoles = - [ - CustomRoles.Amnesiac, - CustomRoles.Pursuer, - CustomRoles.Maverick, - CustomRoles.Totocalcio, - CustomRoles.Opportunist, - CustomRoles.Crewmate, - CustomRoles.Jester - ]; + CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles, true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - private int BodyReportCount; - private long LastReport; - private byte VultureId; + VultureReportCD = new FloatOptionItem(Id + 13, "VultureReportCooldown", new(0f, 180f, 2.5f), 15f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + MaxEaten = new IntegerOptionItem(Id + 14, "VultureMaxEatenInOneRound", new(1, 10, 1), 2, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - public override void SetupCustomOption() + HasImpVision = new BooleanOptionItem(Id + 15, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); + + ChangeRoleWhenCantWin = new BooleanOptionItem(Id + 16, "VultureChangeRoleWhenCantWin", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); + + ChangeRole = new StringOptionItem(Id + 17, "VultureChangeRole", ChangeRoles.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true) + .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); + } + + public override void Init() + { + PlayerIdList = []; + UnreportablePlayers = []; + } + + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + BodyReportCount = 0; + playerId.SetAbilityUseLimit(MaxEaten.GetInt()); + LastReport = Utils.TimeStamp; + + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Vulture); + PlayerControl player = playerId.GetPlayer(); + if (player != null && player.Is(CustomRoles.Vulture) && GameStates.IsInTask) player.Notify(GetString("VultureCooldownUp")); + }, VultureReportCD.GetFloat() + 8f, "Vulture CD"); - ArrowsPointingToDeadBody = new BooleanOptionItem(Id + 10, "VultureArrowsPointingToDeadBody", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); + VultureId = playerId; + } - NumberOfReportsToWin = new IntegerOptionItem(Id + 11, "VultureNumberOfReportsToWin", new(1, 10, 1), 4, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpVision.GetBool()); + AURoleOptions.EngineerCooldown = 0f; + AURoleOptions.EngineerInVentMaxTime = 0f; + } - CanVent = new BooleanOptionItem(Id + 12, "CanVent", true, TabGroup.NeutralRoles, true) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); + public static void Clear() + { + foreach (byte apc in PlayerIdList) LocateArrow.RemoveAllTarget(apc); + } - VultureReportCD = new FloatOptionItem(Id + 13, "VultureReportCooldown", new(0f, 180f, 2.5f), 15f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]) - .SetValueFormat(OptionFormat.Seconds); + public override void AfterMeetingTasks() + { + Clear(); - MaxEaten = new IntegerOptionItem(Id + 14, "VultureMaxEatenInOneRound", new(1, 10, 1), 2, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - - HasImpVision = new BooleanOptionItem(Id + 15, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - - ChangeRoleWhenCantWin = new BooleanOptionItem(Id + 16, "VultureChangeRoleWhenCantWin", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - - ChangeRole = new StringOptionItem(Id + 17, "VultureChangeRole", ChangeRoles.Select(x => x.ToColoredString()).ToArray(), 0, TabGroup.NeutralRoles, noTranslation: true) - .SetParent(CustomRoleSpawnChances[CustomRoles.Vulture]); - } - - public override void Init() + foreach (byte apc in PlayerIdList) { - PlayerIdList = []; - UnreportablePlayers = []; - } + PlayerControl player = Utils.GetPlayerById(apc); + if (player == null) continue; - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - BodyReportCount = 0; - playerId.SetAbilityUseLimit(MaxEaten.GetInt()); - LastReport = Utils.TimeStamp; - - LateTask.New(() => + if (player.IsAlive()) { - PlayerControl player = playerId.GetPlayer(); - if (player != null && player.Is(CustomRoles.Vulture) && GameStates.IsInTask) player.Notify(GetString("VultureCooldownUp")); - }, VultureReportCD.GetFloat() + 8f, "Vulture CD"); + apc.SetAbilityUseLimit(MaxEaten.GetInt()); + LastReport = Utils.TimeStamp; - VultureId = playerId; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpVision.GetBool()); - AURoleOptions.EngineerCooldown = 0f; - AURoleOptions.EngineerInVentMaxTime = 0f; - } - - public static void Clear() - { - foreach (byte apc in PlayerIdList) LocateArrow.RemoveAllTarget(apc); - } - - public override void AfterMeetingTasks() - { - Clear(); - - foreach (byte apc in PlayerIdList) - { - PlayerControl player = Utils.GetPlayerById(apc); - if (player == null) continue; - - if (player.IsAlive()) + LateTask.New(() => { - apc.SetAbilityUseLimit(MaxEaten.GetInt()); - LastReport = Utils.TimeStamp; + if (GameStates.IsInTask) Utils.GetPlayerById(apc).Notify(GetString("VultureCooldownUp")); + }, VultureReportCD.GetFloat(), "Vulture CD"); - LateTask.New(() => - { - if (GameStates.IsInTask) Utils.GetPlayerById(apc).Notify(GetString("VultureCooldownUp")); - }, VultureReportCD.GetFloat(), "Vulture CD"); - - Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); - } - } - } - - public static void OnPlayerDead(PlayerControl target) - { - if (!ArrowsPointingToDeadBody.GetBool() || target.Data.Disconnected) return; - - foreach (byte pc in PlayerIdList) - { - PlayerControl player = Utils.GetPlayerById(pc); - if (player == null || !player.IsAlive()) continue; - - LocateArrow.Add(pc, target.transform.position); Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); } } + } - public override bool CheckReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target, PlayerControl killer) + public static void OnPlayerDead(PlayerControl target) + { + if (!ArrowsPointingToDeadBody.GetBool() || target.Data.Disconnected) return; + + foreach (byte pc in PlayerIdList) { - if (pc.GetAbilityUseLimit() <= 0 || target.Object == null || target.Object.Is(CustomRoles.Unreportable)) return true; + PlayerControl player = Utils.GetPlayerById(pc); + if (player == null || !player.IsAlive()) continue; - if (Utils.TimeStamp - LastReport < VultureReportCD.GetFloat()) return true; - - BodyReportCount++; - pc.RpcRemoveAbilityUse(); - - foreach (byte apc in PlayerIdList) LocateArrow.Remove(apc, target.Object.transform.position); - - pc.Notify(GetString("VultureBodyReported")); - UnreportablePlayers.Add(target.PlayerId); - - return false; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (seer.PlayerId != VultureId) return string.Empty; - - if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; - - return GameStates.IsMeeting ? string.Empty : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - if (!pc.IsAlive() || Main.HasJustStarted) return; - - byte playerId = pc.PlayerId; - - if (BodyReportCount >= NumberOfReportsToWin.GetInt() && GameStates.IsInTask) - { - BodyReportCount = NumberOfReportsToWin.GetInt(); - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Vulture); - CustomWinnerHolder.WinnerIds.Add(playerId); - return; - } - - if (ChangeRoleWhenCantWin.GetBool() && Main.AllAlivePlayerControls.Length - 1 <= NumberOfReportsToWin.GetInt() - BodyReportCount) - { - CustomRoles role = ChangeRoles[ChangeRole.GetValue()]; - pc.RpcSetCustomRole(role); - } - } - - public override void SetButtonTexts(HudManager hud, byte id) - { - hud.ReportButton?.OverrideText(GetString("VultureEatButtonText")); + LocateArrow.Add(pc, target.transform.position); + Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player); } } + + public override bool CheckReportDeadBody(PlayerControl pc, NetworkedPlayerInfo target, PlayerControl killer) + { + if (pc.GetAbilityUseLimit() <= 0 || target.Object == null || target.Object.Is(CustomRoles.Unreportable)) return true; + + if (Utils.TimeStamp - LastReport < VultureReportCD.GetFloat()) return true; + + BodyReportCount++; + pc.RpcRemoveAbilityUse(); + + foreach (byte apc in PlayerIdList) LocateArrow.Remove(apc, target.Object.transform.position); + + pc.Notify(GetString("VultureBodyReported")); + UnreportablePlayers.Add(target.PlayerId); + + return false; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (seer.PlayerId != VultureId) return string.Empty; + + if (target != null && seer.PlayerId != target.PlayerId) return string.Empty; + + return GameStates.IsMeeting ? string.Empty : Utils.ColorString(Color.white, LocateArrow.GetArrows(seer)); + } + + public override void OnFixedUpdate(PlayerControl pc) + { + if (!pc.IsAlive() || Main.HasJustStarted) return; + + byte playerId = pc.PlayerId; + + if (BodyReportCount >= NumberOfReportsToWin.GetInt() && GameStates.IsInTask) + { + BodyReportCount = NumberOfReportsToWin.GetInt(); + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Vulture); + CustomWinnerHolder.WinnerIds.Add(playerId); + return; + } + + if (ChangeRoleWhenCantWin.GetBool() && Main.AllAlivePlayerControls.Length - 1 <= NumberOfReportsToWin.GetInt() - BodyReportCount) + { + CustomRoles role = ChangeRoles[ChangeRole.GetValue()]; + pc.RpcSetCustomRole(role); + } + } + + public override void SetButtonTexts(HudManager hud, byte id) + { + hud.ReportButton?.OverrideText(GetString("VultureEatButtonText")); + } } \ No newline at end of file diff --git a/Roles/Neutral/WeaponMaster.cs b/Roles/Neutral/WeaponMaster.cs index 7b46e12cd..2be9a6b95 100644 --- a/Roles/Neutral/WeaponMaster.cs +++ b/Roles/Neutral/WeaponMaster.cs @@ -6,257 +6,256 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class WeaponMaster : RoleBase { - public class WeaponMaster : RoleBase + private const int Id = 641200; + public static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + private static OptionItem Radius; + private static OptionItem HighKCD; + + private byte Mode; + private bool shieldUsed; + private byte WMId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + /* + * 0 = Kill (Sword) ~ Normal Kill + * 1 = TOHEN Werewolf Kill / Higher KCD (Axe) + * 2 = Reach + Swift / Can't Vent (Lance) + * 3 = 1-Time Shield / Can't Kill (Shield) + */ + + public override void SetupCustomOption() { - private const int Id = 641200; - public static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.WeaponMaster); - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; - private static OptionItem Radius; - private static OptionItem HighKCD; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]) + .SetValueFormat(OptionFormat.Seconds); - private byte Mode; - private bool shieldUsed; - private byte WMId; + CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]); - public override bool IsEnable => PlayerIdList.Count > 0; + HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]); - /* - * 0 = Kill (Sword) ~ Normal Kill - * 1 = TOHEN Werewolf Kill / Higher KCD (Axe) - * 2 = Reach + Swift / Can't Vent (Lance) - * 3 = 1-Time Shield / Can't Kill (Shield) - */ + Radius = new FloatOptionItem(Id + 12, "WMRadius", new(0f, 10f, 0.1f), 2f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]) + .SetValueFormat(OptionFormat.Multiplier); - public override void SetupCustomOption() - { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.WeaponMaster); + HighKCD = new FloatOptionItem(Id + 14, "GamblerHighKCD", new(0f, 180f, 2.5f), 35f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]) + .SetValueFormat(OptionFormat.Seconds); + } - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]) - .SetValueFormat(OptionFormat.Seconds); + public override void Init() + { + PlayerIdList = []; + Mode = 0; + shieldUsed = false; + WMId = byte.MaxValue; + } - CanVent = new BooleanOptionItem(Id + 11, "CanVent", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]); + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + WMId = playerId; - HasImpostorVision = new BooleanOptionItem(Id + 13, "ImpostorVision", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]); + Mode = 0; + shieldUsed = false; + } - Radius = new FloatOptionItem(Id + 12, "WMRadius", new(0f, 10f, 0.1f), 2f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]) - .SetValueFormat(OptionFormat.Multiplier); + private void SendRPC() + { + if (!Utils.DoRPC) return; - HighKCD = new FloatOptionItem(Id + 14, "GamblerHighKCD", new(0f, 180f, 2.5f), 35f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.WeaponMaster]) - .SetValueFormat(OptionFormat.Seconds); - } + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetWeaponMasterMode, SendOption.Reliable); + writer.Write(WMId); + writer.Write(Mode); + writer.Write(shieldUsed); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } - public override void Init() - { - PlayerIdList = []; + public static void ReceiveRPC(MessageReader reader) + { + byte id = reader.ReadByte(); + if (Main.PlayerStates[id].Role is not WeaponMaster { IsEnable: true } wm) return; + + wm.Mode = reader.ReadByte(); + wm.shieldUsed = reader.ReadBoolean(); + } + + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override bool CanUseSabotage(PlayerControl pc) + { + return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetInt(Int32OptionNames.KillDistance, Mode == 2 ? 2 : 0); + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; + + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; + } + + public override bool CanUseKillButton(PlayerControl pc) + { + return Mode != 3; + } + + public override void OnPet(PlayerControl pc) + { + SwitchMode(); + } + + public override bool OnSabotage(PlayerControl pc) + { + SwitchMode(); + return false; + } + + public override bool OnVanish(PlayerControl pc) + { + SwitchMode(); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + SwitchMode(); + return false; + } + + private void SwitchMode() + { + byte id = PlayerIdList[0]; + PlayerControl WM = Utils.GetPlayerById(id); + + if (WM == null || !WM.IsAlive()) return; + + if (Mode == 3) Mode = 0; - shieldUsed = false; - WMId = byte.MaxValue; + else + Mode++; + + switch (Mode) + { + case 1: + Main.AllPlayerKillCooldown[id] = HighKCD.GetFloat(); + break; + case 2: + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + break; + case 3 when shieldUsed: + WM.Notify(GetString("WMShieldAlreadyUsed")); + break; } - public override void Add(byte playerId) + SendRPC(); + WM.MarkDirtySettings(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + if (killer == null) return false; + + if (target == null) return false; + + switch (Mode) { - PlayerIdList.Add(playerId); - WMId = playerId; - - Mode = 0; - shieldUsed = false; - } - - private void SendRPC() - { - if (!Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetWeaponMasterMode, SendOption.Reliable); - writer.Write(WMId); - writer.Write(Mode); - writer.Write(shieldUsed); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public static void ReceiveRPC(MessageReader reader) - { - byte id = reader.ReadByte(); - if (Main.PlayerStates[id].Role is not WeaponMaster { IsEnable: true } wm) return; - - wm.Mode = reader.ReadByte(); - wm.shieldUsed = reader.ReadBoolean(); - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override bool CanUseSabotage(PlayerControl pc) - { - return base.CanUseSabotage(pc) || (pc.IsAlive() && !(UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool())); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetInt(Int32OptionNames.KillDistance, Mode == 2 ? 2 : 0); - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = 1f; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return Mode != 3; - } - - public override void OnPet(PlayerControl pc) - { - SwitchMode(); - } - - public override bool OnSabotage(PlayerControl pc) - { - SwitchMode(); - return false; - } - - public override bool OnVanish(PlayerControl pc) - { - SwitchMode(); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - SwitchMode(); - return false; - } - - private void SwitchMode() - { - byte id = PlayerIdList[0]; - PlayerControl WM = Utils.GetPlayerById(id); - - if (WM == null || !WM.IsAlive()) return; - - if (Mode == 3) - Mode = 0; - else - Mode++; - - switch (Mode) - { - case 1: - Main.AllPlayerKillCooldown[id] = HighKCD.GetFloat(); - break; - case 2: - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - break; - case 3 when shieldUsed: - WM.Notify(GetString("WMShieldAlreadyUsed")); - break; - } - - SendRPC(); - WM.MarkDirtySettings(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - if (killer == null) return false; - - if (target == null) return false; - - switch (Mode) - { - case 0: - return true; - case 1: - LateTask.New(() => + case 0: + return true; + case 1: + LateTask.New(() => + { + foreach (PlayerControl player in Main.AllAlivePlayerControls) { - foreach (PlayerControl player in Main.AllAlivePlayerControls) - { - if (Pelican.IsEaten(player.PlayerId) || player == killer || player.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) continue; + if (Pelican.IsEaten(player.PlayerId) || player == killer || player.Is(CustomRoles.Pestilence) || Veteran.VeteranInProtect.ContainsKey(target.PlayerId)) continue; - if (Vector2.Distance(killer.transform.position, player.transform.position) <= Radius.GetFloat()) player.Suicide(PlayerState.DeathReason.Kill, killer); - } - - killer.SetKillCooldown(HighKCD.GetFloat()); - }, 0.1f, "Weapon Master Axe Kill"); - - return true; - case 2: - if (killer.RpcCheckAndMurder(target, true)) - { - target.Suicide(PlayerState.DeathReason.Kill, killer); - killer.SetKillCooldown(); + if (Vector2.Distance(killer.transform.position, player.transform.position) <= Radius.GetFloat()) player.Suicide(PlayerState.DeathReason.Kill, killer); } - return false; - case 3: - return false; - default: - Logger.Error("Invalid Mode", "WeaponMaster"); - return true; - } - } + killer.SetKillCooldown(HighKCD.GetFloat()); + }, 0.1f, "Weapon Master Axe Kill"); + + return true; + case 2: + if (killer.RpcCheckAndMurder(target, true)) + { + target.Suicide(PlayerState.DeathReason.Kill, killer); + killer.SetKillCooldown(); + } - public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - if (Mode == 3 && !shieldUsed) - { - shieldUsed = true; - SendRPC(); return false; - } - - return true; - } - - public override void OnEnterVent(PlayerControl pc, Vent vent) - { - if (Mode == 2) pc?.MyPhysics?.RpcBootFromVent(vent.Id); - } - - public override string GetProgressText(byte playerId, bool comms) - { - return !playerId.IsPlayerModClient() ? GetHudAndProgressText(playerId) : string.Empty; - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - return hud ? GetHudAndProgressText(seer.PlayerId) : string.Empty; - } - - private static string GetHudAndProgressText(byte id) - { - return Main.PlayerStates[id].Role is not WeaponMaster { IsEnable: true } wm ? string.Empty : string.Format(GetString("WMMode"), ModeToText(wm.Mode)); - } - - public static string ModeToText(byte mode) - { - return mode switch - { - 0 => GetString("Sword"), - 1 => GetString("Axe"), - 2 => GetString("Lance"), - 3 => GetString("Shield"), - _ => string.Empty - }; + case 3: + return false; + default: + Logger.Error("Invalid Mode", "WeaponMaster"); + return true; } } + + public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + if (Mode == 3 && !shieldUsed) + { + shieldUsed = true; + SendRPC(); + return false; + } + + return true; + } + + public override void OnEnterVent(PlayerControl pc, Vent vent) + { + if (Mode == 2) pc?.MyPhysics?.RpcBootFromVent(vent.Id); + } + + public override string GetProgressText(byte playerId, bool comms) + { + return !playerId.IsPlayerModClient() ? GetHudAndProgressText(playerId) : string.Empty; + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return hud ? GetHudAndProgressText(seer.PlayerId) : string.Empty; + } + + private static string GetHudAndProgressText(byte id) + { + return Main.PlayerStates[id].Role is not WeaponMaster { IsEnable: true } wm ? string.Empty : string.Format(GetString("WMMode"), ModeToText(wm.Mode)); + } + + public static string ModeToText(byte mode) + { + return mode switch + { + 0 => GetString("Sword"), + 1 => GetString("Axe"), + 2 => GetString("Lance"), + 3 => GetString("Shield"), + _ => string.Empty + }; + } } \ No newline at end of file diff --git a/Roles/Neutral/Weatherman.cs b/Roles/Neutral/Weatherman.cs index db7de27d0..d962efdc3 100644 --- a/Roles/Neutral/Weatherman.cs +++ b/Roles/Neutral/Weatherman.cs @@ -1,88 +1,87 @@ using AmongUs.GameOptions; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Weatherman : RoleBase { - public class Weatherman : RoleBase + public static bool On; + + public static OptionItem AbilityCooldown; + private static OptionItem KillCooldown; + private static OptionItem CanVent; + private static OptionItem HasImpostorVision; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + StartSetup(645150) + .AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) + .AutoSetupOption(ref CanVent, true) + .AutoSetupOption(ref HasImpostorVision, true); + } - public static OptionItem AbilityCooldown; - private static OptionItem KillCooldown; - private static OptionItem CanVent; - private static OptionItem HasImpostorVision; + public override void Init() + { + On = false; + NaturalDisasters.LoadAllDisasters(); + } - public override bool IsEnable => On; + public override void Add(byte playerId) + { + On = true; + } - public override void SetupCustomOption() + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } + + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + + if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) + AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat(); + } + + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return CanVent.GetBool(); + } + + public override void OnPet(PlayerControl pc) + { + SpawnRandomDisaster(pc); + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting) SpawnRandomDisaster(shapeshifter); + return false; + } + + private static void SpawnRandomDisaster(PlayerControl pc) + { + LateTask.New(() => { - StartSetup(645150) - .AutoSetupOption(ref AbilityCooldown, 30f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds) - .AutoSetupOption(ref CanVent, true) - .AutoSetupOption(ref HasImpostorVision, true); - } + var disaster = NaturalDisasters.GetAllDisasters().RandomElement(); + var room = pc.GetPlainShipRoom(); + NaturalDisasters.FixedUpdatePatch.AddPreparingDisaster(pc.Pos(), disaster.Name, room == null ? null : room.RoomId); + }, 3f, "Weatherman.SpawnRandomDisaster"); + } - public override void Init() - { - On = false; - NaturalDisasters.LoadAllDisasters(); - } + public override void OnFixedUpdate(PlayerControl pc) + { + NaturalDisasters.FixedUpdatePatch.UpdatePreparingDisasters(); + NaturalDisasters.GetActiveDisasters().ToArray().Do(x => x.Update()); + NaturalDisasters.Sinkhole.OnFixedUpdate(); + NaturalDisasters.BuildingCollapse.OnFixedUpdate(); + } - public override void Add(byte playerId) - { - On = true; - } - - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - - if (Options.UseUnshiftTrigger.GetBool() && Options.UseUnshiftTriggerForNKs.GetBool()) - AURoleOptions.ShapeshifterCooldown = AbilityCooldown.GetFloat(); - } - - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return CanVent.GetBool(); - } - - public override void OnPet(PlayerControl pc) - { - SpawnRandomDisaster(pc); - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting) SpawnRandomDisaster(shapeshifter); - return false; - } - - private static void SpawnRandomDisaster(PlayerControl pc) - { - LateTask.New(() => - { - var disaster = NaturalDisasters.GetAllDisasters().RandomElement(); - var room = pc.GetPlainShipRoom(); - NaturalDisasters.FixedUpdatePatch.AddPreparingDisaster(pc.Pos(), disaster.Name, room == null ? null : room.RoomId); - }, 3f, "Weatherman.SpawnRandomDisaster"); - } - - public override void OnFixedUpdate(PlayerControl pc) - { - NaturalDisasters.FixedUpdatePatch.UpdatePreparingDisasters(); - NaturalDisasters.GetActiveDisasters().ToArray().Do(x => x.Update()); - NaturalDisasters.Sinkhole.OnFixedUpdate(); - NaturalDisasters.BuildingCollapse.OnFixedUpdate(); - } - - public override void OnReportDeadBody() - { - NaturalDisasters.BuildingCollapse.CollapsedRooms.Clear(); - } + public override void OnReportDeadBody() + { + NaturalDisasters.BuildingCollapse.CollapsedRooms.Clear(); } } \ No newline at end of file diff --git a/Roles/Neutral/Werewolf.cs b/Roles/Neutral/Werewolf.cs index ad00a7b92..e4d457f28 100644 --- a/Roles/Neutral/Werewolf.cs +++ b/Roles/Neutral/Werewolf.cs @@ -6,241 +6,240 @@ using Hazel; using static EHR.Options; using static EHR.Translator; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Werewolf : RoleBase { - public class Werewolf : RoleBase + private const int Id = 12850; + private static List PlayerIdList = []; + + private static OptionItem KillCooldown; + private static OptionItem HasImpostorVision; + private static OptionItem RampageCD; + private static OptionItem RampageDur; + private static int CD; + + private static long LastFixedTime; + private long lastTime; + + private long RampageTime; + private byte WWId; + + public override bool IsEnable => PlayerIdList.Count > 0; + + private bool CanRampage => GameStates.IsInTask && RampageTime == -10 && lastTime == -10; + private bool IsRampaging => RampageTime != -10; + + public override void SetupCustomOption() { - private const int Id = 12850; - private static List PlayerIdList = []; + SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Werewolf); - private static OptionItem KillCooldown; - private static OptionItem HasImpostorVision; - private static OptionItem RampageCD; - private static OptionItem RampageDur; - private static int CD; + KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]) + .SetValueFormat(OptionFormat.Seconds); - private static long LastFixedTime; - private long lastTime; + HasImpostorVision = new BooleanOptionItem(Id + 11, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]); - private long RampageTime; - private byte WWId; + RampageCD = new FloatOptionItem(Id + 12, "WWRampageCD", new(0f, 180f, 0.5f), 35f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => PlayerIdList.Count > 0; + RampageDur = new FloatOptionItem(Id + 13, "WWRampageDur", new(0f, 180f, 1f), 12f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]) + .SetValueFormat(OptionFormat.Seconds); + } - private bool CanRampage => GameStates.IsInTask && RampageTime == -10 && lastTime == -10; - private bool IsRampaging => RampageTime != -10; + public override void Init() + { + PlayerIdList = []; + RampageTime = -10; + lastTime = -10; + CD = 0; + WWId = byte.MaxValue; + } - public override void SetupCustomOption() + public override void Add(byte playerId) + { + PlayerIdList.Add(playerId); + WWId = playerId; + + RampageTime = -10; + lastTime = -10; + CD = StartingKillCooldown.GetInt(); + + LateTask.New(() => { - SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Werewolf); + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) + Utils.GetPlayerById(playerId).RpcResetAbilityCooldown(); + }, 9f, log: false); + } - KillCooldown = new FloatOptionItem(Id + 10, "KillCooldown", new(0f, 180f, 0.5f), 3f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]) - .SetValueFormat(OptionFormat.Seconds); + public override void SetKillCooldown(byte id) + { + Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); + } - HasImpostorVision = new BooleanOptionItem(Id + 11, "ImpostorVision", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]); + public override bool CanUseImpostorVentButton(PlayerControl pc) + { + return (CanRampage && (!UseUnshiftTrigger.GetBool() || !UseUnshiftTriggerForNKs.GetBool())) || IsRampaging || pc.inVent; + } - RampageCD = new FloatOptionItem(Id + 12, "WWRampageCD", new(0f, 180f, 0.5f), 35f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]) - .SetValueFormat(OptionFormat.Seconds); + public override bool CanUseKillButton(PlayerControl pc) + { + return IsRampaging; + } - RampageDur = new FloatOptionItem(Id + 13, "WWRampageDur", new(0f, 180f, 1f), 12f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Werewolf]) - .SetValueFormat(OptionFormat.Seconds); - } + public override void ApplyGameOptions(IGameOptions opt, byte id) + { + opt.SetVision(HasImpostorVision.GetBool()); + if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - public override void Init() + if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = RampageDur.GetFloat() + 0.5f; + + AURoleOptions.EngineerCooldown = 0f; + AURoleOptions.EngineerInVentMaxTime = 0f; + } + + private void SendRPC() + { + if (!IsEnable || !Utils.DoRPC) return; + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetWwTimer, SendOption.Reliable); + writer.Write(WWId); + writer.Write(RampageTime.ToString()); + writer.Write(lastTime.ToString()); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + + public void ReceiveRPC(MessageReader reader) + { + RampageTime = long.Parse(reader.ReadString()); + lastTime = long.Parse(reader.ReadString()); + } + + public override void AfterMeetingTasks() + { + RampageTime = -10; + lastTime = Utils.TimeStamp; + SendRPC(); + } + + public override void OnFixedUpdate(PlayerControl player) + { + if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || !IsEnable || Main.HasJustStarted || player == null) return; + + long now = Utils.TimeStamp; + + if (lastTime != -10) { - PlayerIdList = []; - RampageTime = -10; - lastTime = -10; - CD = 0; - WWId = byte.MaxValue; - } - - public override void Add(byte playerId) - { - PlayerIdList.Add(playerId); - WWId = playerId; - - RampageTime = -10; - lastTime = -10; - CD = StartingKillCooldown.GetInt(); - - LateTask.New(() => + if (!player.IsModClient()) { - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) - Utils.GetPlayerById(playerId).RpcResetAbilityCooldown(); - }, 9f, log: false); - } + long cooldown = lastTime + (long)RampageCD.GetFloat() - now; + if ((int)cooldown != CD) player.Notify(string.Format(GetString("CDPT"), cooldown + 1), 1.1f, overrideAll: true); - public override void SetKillCooldown(byte id) - { - Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat(); - } + CD = (int)cooldown; + } - public override bool CanUseImpostorVentButton(PlayerControl pc) - { - return (CanRampage && (!UseUnshiftTrigger.GetBool() || !UseUnshiftTriggerForNKs.GetBool())) || IsRampaging || pc.inVent; - } - - public override bool CanUseKillButton(PlayerControl pc) - { - return IsRampaging; - } - - public override void ApplyGameOptions(IGameOptions opt, byte id) - { - opt.SetVision(HasImpostorVision.GetBool()); - if (UsePhantomBasis.GetBool() && UsePhantomBasisForNKs.GetBool()) AURoleOptions.PhantomCooldown = 1f; - - if (UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool()) AURoleOptions.ShapeshifterCooldown = RampageDur.GetFloat() + 0.5f; - - AURoleOptions.EngineerCooldown = 0f; - AURoleOptions.EngineerInVentMaxTime = 0f; - } - - private void SendRPC() - { - if (!IsEnable || !Utils.DoRPC) return; - - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetWwTimer, SendOption.Reliable); - writer.Write(WWId); - writer.Write(RampageTime.ToString()); - writer.Write(lastTime.ToString()); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } - - public void ReceiveRPC(MessageReader reader) - { - RampageTime = long.Parse(reader.ReadString()); - lastTime = long.Parse(reader.ReadString()); - } - - public override void AfterMeetingTasks() - { - RampageTime = -10; - lastTime = Utils.TimeStamp; - SendRPC(); - } - - public override void OnFixedUpdate(PlayerControl player) - { - if (!GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || !IsEnable || Main.HasJustStarted || player == null) return; - - long now = Utils.TimeStamp; - - if (lastTime != -10) + if (lastTime + (long)RampageCD.GetFloat() < now) { - if (!player.IsModClient()) - { - long cooldown = lastTime + (long)RampageCD.GetFloat() - now; - if ((int)cooldown != CD) player.Notify(string.Format(GetString("CDPT"), cooldown + 1), 1.1f, overrideAll: true); + lastTime = -10; + bool unshift = UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool(); + if (!player.IsModClient()) player.Notify(GetString(unshift ? "WWCanRampageUnshift" : "WWCanRampage")); - CD = (int)cooldown; - } + if (!player.IsModClient()) player.RpcChangeRoleBasis(unshift ? CustomRoles.Werewolf : CustomRoles.EngineerEHR); + SendRPC(); + CD = 0; + } + } - if (lastTime + (long)RampageCD.GetFloat() < now) - { - lastTime = -10; - bool unshift = UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool(); - if (!player.IsModClient()) player.Notify(GetString(unshift ? "WWCanRampageUnshift" : "WWCanRampage")); + if (LastFixedTime != now && RampageTime != -10) + { + LastFixedTime = now; + var refresh = false; + long remainTime = RampageTime + (long)RampageDur.GetFloat() - now; - if (!player.IsModClient()) player.RpcChangeRoleBasis(unshift ? CustomRoles.Werewolf : CustomRoles.EngineerEHR); + switch (remainTime) + { + case < 0: + lastTime = now; + player.Notify(GetString("WWRampageOut")); + if (!player.IsModClient()) player.RpcChangeRoleBasis(CustomRoles.CrewmateEHR); + RampageTime = -10; SendRPC(); - CD = 0; - } + refresh = true; + break; + case <= 10 when !player.IsModClient(): + player.Notify(string.Format(GetString("WWRampageCountdown"), remainTime + 1), overrideAll: true); + break; } - if (LastFixedTime != now && RampageTime != -10) - { - LastFixedTime = now; - var refresh = false; - long remainTime = RampageTime + (long)RampageDur.GetFloat() - now; - - switch (remainTime) - { - case < 0: - lastTime = now; - player.Notify(GetString("WWRampageOut")); - if (!player.IsModClient()) player.RpcChangeRoleBasis(CustomRoles.CrewmateEHR); - RampageTime = -10; - SendRPC(); - refresh = true; - break; - case <= 10 when !player.IsModClient(): - player.Notify(string.Format(GetString("WWRampageCountdown"), remainTime + 1), overrideAll: true); - break; - } - - if (refresh) SendRPC(); - } - } - - public override void OnExitVent(PlayerControl pc, Vent vent) - { - if (pc == null) return; - - Rampage(pc); - } - - public override void OnPet(PlayerControl pc) - { - Rampage(pc); - } - - public override bool OnVanish(PlayerControl pc) - { - Rampage(pc); - return false; - } - - public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; - - Rampage(shapeshifter); - return false; - } - - private void Rampage(PlayerControl pc) - { - if (!AmongUsClient.Instance.AmHost || IsRampaging) return; - - LateTask.New(() => - { - if (CanRampage) - { - RampageTime = Utils.TimeStamp; - SendRPC(); - pc.Notify(GetString("WWRampaging"), RampageDur.GetFloat()); - if (!pc.IsModClient()) pc.RpcChangeRoleBasis(CustomRoles.Werewolf); - } - }, 0.5f, "Werewolf Vent"); - } - - public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - if (!hud || seer == null || !GameStates.IsInTask || !PlayerControl.LocalPlayer.IsAlive() || Main.PlayerStates[seer.PlayerId].Role is not Werewolf { IsEnable: true } ww) return string.Empty; - - var str = new StringBuilder(); - - if (ww.IsRampaging) - { - long remainTime = ww.RampageTime + (long)RampageDur.GetFloat() - Utils.TimeStamp; - str.Append(string.Format(GetString("WWRampageCountdown"), remainTime + 1)); - } - else if (ww.lastTime != -10) - { - long cooldown = ww.lastTime + (long)RampageCD.GetFloat() - Utils.TimeStamp; - str.Append(string.Format(GetString("WWCD"), cooldown + 1)); - } - else - str.Append(GetString(UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool() ? "WWCanRampageUnshift" : "WWCanRampage")); - - return str.ToString(); - } - - public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return !Medic.ProtectList.Contains(target.PlayerId) && IsRampaging; + if (refresh) SendRPC(); } } + + public override void OnExitVent(PlayerControl pc, Vent vent) + { + if (pc == null) return; + + Rampage(pc); + } + + public override void OnPet(PlayerControl pc) + { + Rampage(pc); + } + + public override bool OnVanish(PlayerControl pc) + { + Rampage(pc); + return false; + } + + public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + if (!shapeshifting && !UseUnshiftTrigger.GetBool()) return true; + + Rampage(shapeshifter); + return false; + } + + private void Rampage(PlayerControl pc) + { + if (!AmongUsClient.Instance.AmHost || IsRampaging) return; + + LateTask.New(() => + { + if (CanRampage) + { + RampageTime = Utils.TimeStamp; + SendRPC(); + pc.Notify(GetString("WWRampaging"), RampageDur.GetFloat()); + if (!pc.IsModClient()) pc.RpcChangeRoleBasis(CustomRoles.Werewolf); + } + }, 0.5f, "Werewolf Vent"); + } + + public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + if (!hud || seer == null || !GameStates.IsInTask || !PlayerControl.LocalPlayer.IsAlive() || Main.PlayerStates[seer.PlayerId].Role is not Werewolf { IsEnable: true } ww) return string.Empty; + + var str = new StringBuilder(); + + if (ww.IsRampaging) + { + long remainTime = ww.RampageTime + (long)RampageDur.GetFloat() - Utils.TimeStamp; + str.Append(string.Format(GetString("WWRampageCountdown"), remainTime + 1)); + } + else if (ww.lastTime != -10) + { + long cooldown = ww.lastTime + (long)RampageCD.GetFloat() - Utils.TimeStamp; + str.Append(string.Format(GetString("WWCD"), cooldown + 1)); + } + else + str.Append(GetString(UseUnshiftTrigger.GetBool() && UseUnshiftTriggerForNKs.GetBool() ? "WWCanRampageUnshift" : "WWCanRampage")); + + return str.ToString(); + } + + public override bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return !Medic.ProtectList.Contains(target.PlayerId) && IsRampaging; + } } \ No newline at end of file diff --git a/Roles/Neutral/Workaholic.cs b/Roles/Neutral/Workaholic.cs index 0cfb3bba8..f686556ca 100644 --- a/Roles/Neutral/Workaholic.cs +++ b/Roles/Neutral/Workaholic.cs @@ -5,77 +5,76 @@ using EHR.Modules; using static EHR.Options; // From: TOH_Y -namespace EHR.Neutral +namespace EHR.Neutral; + +internal class Workaholic : RoleBase { - internal class Workaholic : RoleBase + public static bool On; + + public static List WorkaholicAlive = []; + + public static OptionItem WorkaholicVentCooldown; + public static OptionItem WorkaholicCannotWinAtDeath; + public static OptionItem WorkaholicVisibleToEveryone; + public static OptionItem WorkaholicGiveAdviceAlive; + public static OptionItem WorkaholicCanGuess; + public static OptionItem WorkaholicSpeed; + public override bool IsEnable => On; + + public override void SetupCustomOption() { - public static bool On; + SetupRoleOptions(11700, TabGroup.NeutralRoles, CustomRoles.Workaholic); - public static List WorkaholicAlive = []; + WorkaholicCannotWinAtDeath = new BooleanOptionItem(11710, "WorkaholicCannotWinAtDeath", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]); - public static OptionItem WorkaholicVentCooldown; - public static OptionItem WorkaholicCannotWinAtDeath; - public static OptionItem WorkaholicVisibleToEveryone; - public static OptionItem WorkaholicGiveAdviceAlive; - public static OptionItem WorkaholicCanGuess; - public static OptionItem WorkaholicSpeed; - public override bool IsEnable => On; + WorkaholicVentCooldown = new FloatOptionItem(11711, "VentCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() + WorkaholicVisibleToEveryone = new BooleanOptionItem(11712, "WorkaholicVisibleToEveryone", false, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]); + + WorkaholicGiveAdviceAlive = new BooleanOptionItem(11713, "WorkaholicGiveAdviceAlive", false, TabGroup.NeutralRoles) + .SetParent(WorkaholicVisibleToEveryone); + + OverrideTasksData.Create(11714, TabGroup.NeutralRoles, CustomRoles.Workaholic); + + WorkaholicCanGuess = new BooleanOptionItem(11725, "CanGuess", true, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]); + + WorkaholicSpeed = new FloatOptionItem(11726, "WorkaholicSpeed", new(0.1f, 3f, 0.1f), 1.5f, TabGroup.NeutralRoles) + .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]) + .SetValueFormat(OptionFormat.Multiplier); + } + + public override void Add(byte playerId) + { + On = true; + } + + public override void Init() + { + On = false; + } + + public override void ApplyGameOptions(IGameOptions opt, byte playerId) + { + AURoleOptions.EngineerCooldown = WorkaholicVentCooldown.GetFloat(); + AURoleOptions.EngineerInVentMaxTime = 0f; + Main.AllPlayerSpeed[playerId] = WorkaholicSpeed.GetFloat(); + } + + public override void OnTaskComplete(PlayerControl player, int CompletedTasksCount, int AllTasksCount) + { + if (CompletedTasksCount + 1 >= AllTasksCount && (!WorkaholicCannotWinAtDeath.GetBool() || player.IsAlive())) { - SetupRoleOptions(11700, TabGroup.NeutralRoles, CustomRoles.Workaholic); + Logger.Info("Workaholic Tasks Finished", "Workaholic"); + RPC.PlaySoundRPC(player.PlayerId, Sounds.KillSound); + foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != player.PlayerId).ToArray()) pc.Suicide(pc.PlayerId == player.PlayerId ? PlayerState.DeathReason.Overtired : PlayerState.DeathReason.Ashamed, player); - WorkaholicCannotWinAtDeath = new BooleanOptionItem(11710, "WorkaholicCannotWinAtDeath", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]); - - WorkaholicVentCooldown = new FloatOptionItem(11711, "VentCooldown", new(0f, 180f, 0.5f), 30f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]) - .SetValueFormat(OptionFormat.Seconds); - - WorkaholicVisibleToEveryone = new BooleanOptionItem(11712, "WorkaholicVisibleToEveryone", false, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]); - - WorkaholicGiveAdviceAlive = new BooleanOptionItem(11713, "WorkaholicGiveAdviceAlive", false, TabGroup.NeutralRoles) - .SetParent(WorkaholicVisibleToEveryone); - - OverrideTasksData.Create(11714, TabGroup.NeutralRoles, CustomRoles.Workaholic); - - WorkaholicCanGuess = new BooleanOptionItem(11725, "CanGuess", true, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]); - - WorkaholicSpeed = new FloatOptionItem(11726, "WorkaholicSpeed", new(0.1f, 3f, 0.1f), 1.5f, TabGroup.NeutralRoles) - .SetParent(CustomRoleSpawnChances[CustomRoles.Workaholic]) - .SetValueFormat(OptionFormat.Multiplier); - } - - public override void Add(byte playerId) - { - On = true; - } - - public override void Init() - { - On = false; - } - - public override void ApplyGameOptions(IGameOptions opt, byte playerId) - { - AURoleOptions.EngineerCooldown = WorkaholicVentCooldown.GetFloat(); - AURoleOptions.EngineerInVentMaxTime = 0f; - Main.AllPlayerSpeed[playerId] = WorkaholicSpeed.GetFloat(); - } - - public override void OnTaskComplete(PlayerControl player, int CompletedTasksCount, int AllTasksCount) - { - if (CompletedTasksCount + 1 >= AllTasksCount && (!WorkaholicCannotWinAtDeath.GetBool() || player.IsAlive())) - { - Logger.Info("Workaholic Tasks Finished", "Workaholic"); - RPC.PlaySoundRPC(player.PlayerId, Sounds.KillSound); - foreach (PlayerControl pc in Main.AllAlivePlayerControls.Where(pc => pc.PlayerId != player.PlayerId).ToArray()) pc.Suicide(pc.PlayerId == player.PlayerId ? PlayerState.DeathReason.Overtired : PlayerState.DeathReason.Ashamed, player); - - CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Workaholic); - CustomWinnerHolder.WinnerIds.Add(player.PlayerId); - } + CustomWinnerHolder.ResetAndSetWinner(CustomWinner.Workaholic); + CustomWinnerHolder.WinnerIds.Add(player.PlayerId); } } } \ No newline at end of file diff --git a/Roles/Neutral/Wraith.cs b/Roles/Neutral/Wraith.cs index 43a6cf08b..88bc3115b 100644 --- a/Roles/Neutral/Wraith.cs +++ b/Roles/Neutral/Wraith.cs @@ -1,32 +1,31 @@ using static EHR.Options; -namespace EHR.Neutral +namespace EHR.Neutral; + +public class Wraith : RoleBase { - public class Wraith : RoleBase + private const int Id = 13300; + + public static OptionItem WraithCooldown; + public static OptionItem WraithDuration; + public static OptionItem WraithVentNormallyOnCooldown; + + public override bool IsEnable => false; + + public override void SetupCustomOption() { - private const int Id = 13300; + SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Wraith); - public static OptionItem WraithCooldown; - public static OptionItem WraithDuration; - public static OptionItem WraithVentNormallyOnCooldown; + WraithCooldown = new FloatOptionItem(Id + 2, "WraithCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Wraith]) + .SetValueFormat(OptionFormat.Seconds); - public override bool IsEnable => false; + WraithDuration = new FloatOptionItem(Id + 3, "WraithDuration", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Wraith]) + .SetValueFormat(OptionFormat.Seconds); - public override void SetupCustomOption() - { - SetupSingleRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Wraith); - - WraithCooldown = new FloatOptionItem(Id + 2, "WraithCooldown", new(0f, 60f, 0.5f), 20f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Wraith]) - .SetValueFormat(OptionFormat.Seconds); - - WraithDuration = new FloatOptionItem(Id + 3, "WraithDuration", new(0f, 30f, 0.5f), 10f, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Wraith]) - .SetValueFormat(OptionFormat.Seconds); - - WraithVentNormallyOnCooldown = new BooleanOptionItem(Id + 4, "WraithVentNormallyOnCooldown", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Wraith]); - } - - public override void Init() { } - - public override void Add(byte playerId) { } + WraithVentNormallyOnCooldown = new BooleanOptionItem(Id + 4, "WraithVentNormallyOnCooldown", true, TabGroup.NeutralRoles).SetParent(CustomRoleSpawnChances[CustomRoles.Wraith]); } + + public override void Init() { } + + public override void Add(byte playerId) { } } \ No newline at end of file diff --git a/Roles/RoleBase.cs b/Roles/RoleBase.cs index b88892f00..468718062 100644 --- a/Roles/RoleBase.cs +++ b/Roles/RoleBase.cs @@ -11,226 +11,225 @@ using EHR.AddOns.Impostor; using EHR.Neutral; -namespace EHR +namespace EHR; + +public abstract class RoleBase : IComparable { - public abstract class RoleBase : IComparable + public abstract bool IsEnable { get; } + + public int CompareTo(RoleBase other) { - public abstract bool IsEnable { get; } + string thisName = GetType().Name; + string translatedName = Translator.GetString(thisName); + if (translatedName != string.Empty && !translatedName.StartsWith("*") && !translatedName.StartsWith("= 14) - { - x -= 13; - TaskState ts = pc.GetTaskState(); - - suffix = pc.GetCustomRoleTypes() switch - { - CustomRoleTypes.Impostor => $"Imp{x}", - CustomRoleTypes.Neutral => $"Neutral{x}", - CustomRoleTypes.Crewmate => x == 1 ? "Crew" : ts.HasTasks && ts.IsTaskFinished ? "CrewTaskDone" : "CrewWithTasksLeft", - _ => x.ToString() - }; - } - else - suffix = x.ToString(); - - pc.Notify(Translator.GetString($"NoPetActionMsg{suffix}")); - } - - public virtual bool OnSabotage(PlayerControl pc) - { - return CanUseSabotage(pc); - } - - public virtual bool OnCheckMurder(PlayerControl killer, PlayerControl target) - { - return target != null && killer != null; - } - - public virtual bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) - { - return target != null && killer != null; - } - - public virtual void OnMurder(PlayerControl killer, PlayerControl target) { } - - public virtual bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) - { - return true; - } - - public virtual bool OnVanish(PlayerControl pc) - { - return true; - } - - public virtual bool OnVote(PlayerControl voter, PlayerControl target) - { - return false; - } - - public virtual void OnReportDeadBody() { } - - public virtual bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) - { - return true; - } - - public virtual void AfterMeetingTasks() { } - - public virtual string GetProgressText(byte playerId, bool comms) - { - StringBuilder sb = new(); - sb.Append(Utils.GetAbilityUseLimitDisplay(playerId)); - sb.Append(Utils.GetTaskCount(playerId, comms)); - return sb.ToString(); - } - - public virtual void SetButtonTexts(HudManager hud, byte id) { } - - public virtual string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) - { - return string.Empty; - } - - public virtual bool KnowRole(PlayerControl seer, PlayerControl target) - { - if (Options.NeutralsKnowEachOther.GetBool() && seer.Is(Team.Neutral) && target.Is(Team.Neutral)) return true; - - CustomRoles seerRole = seer.GetCustomRole(); - return seerRole.IsNK() && seerRole == target.GetCustomRole() && seer.GetTeam() == target.GetTeam(); - } - - protected bool IsThisRole(PlayerControl pc) - { - return pc.GetCustomRole() == Enum.Parse(GetType().Name, true); - } - - protected bool IsThisRole(byte id) - { - var role = Main.PlayerStates.TryGetValue(id, out var state) ? state.MainRole : CustomRoles.NotAssigned; - return role == Enum.Parse(GetType().Name, true); - } - - // Option setup simplifier - protected OptionSetupHandler StartSetup(int id, bool single = false) - { - var role = Enum.Parse(GetType().Name, true); - var tab = TabGroup.OtherRoles; - - if (role.IsImpostor()) tab = TabGroup.ImpostorRoles; - else if (role.IsNeutral(true)) tab = TabGroup.NeutralRoles; - else if (role.IsCrewmate()) tab = TabGroup.CrewmateRoles; - - if (single) Options.SetupSingleRoleOptions(id++, tab, role, hideMaxSetting: true); - else Options.SetupRoleOptions(id++, tab, role); - - return new(id, tab, role); - } + return string.Compare(thisName, otherName, StringComparison.Ordinal); } - public class OptionSetupHandler(int id, TabGroup tab, CustomRoles role) + // This is a base class for all roles. It contains some common methods and properties that are used by all roles. + public abstract void Init(); + public abstract void Add(byte playerId); + public abstract void SetupCustomOption(); + + // Some virtual methods that trigger actions, like venting, petting, CheckMurder, etc. These are not abstract because they have a default implementation. These should also have the same name as the methods in the derived classes. + public virtual void SetKillCooldown(byte id) { - private readonly OptionItem Parent = Options.CustomRoleSpawnChances[role]; - private int _id = id; + Main.AllPlayerKillCooldown[id] = Options.DefaultKillCooldown; + } - public OptionSetupHandler AutoSetupOption(ref OptionItem field, object defaultValue, object valueRule = null, OptionFormat format = OptionFormat.None, [CallerArgumentExpression("field")] string fieldName = "", string overrideName = "", OptionItem overrideParent = null, bool noTranslation = false) + public virtual bool CanUseKillButton(PlayerControl pc) + { + return pc.IsAlive() && (pc.Is(CustomRoleTypes.Impostor) || pc.IsNeutralKiller()); + } + + public virtual bool CanUseImpostorVentButton(PlayerControl pc) + { + return pc.IsAlive() && (pc.Is(CustomRoleTypes.Impostor) || Amnesiac.WasAmnesiac.Contains(pc.PlayerId) || (pc.Is(CustomRoles.Bloodlust) && Bloodlust.CanVent.GetBool())) && Circumvent.CanUseImpostorVentButton(pc); + } + + public virtual bool CanUseVent(PlayerControl pc, int ventId) + { + return true; + } + + public virtual bool CanUseSabotage(PlayerControl pc) + { + return pc.Is(CustomRoleTypes.Impostor) || pc.Is(CustomRoles.Trickster) || pc.Is(CustomRoles.Mischievous) || (pc.Is(CustomRoles.Bloodlust) && Bloodlust.HasImpVision.GetBool() && pc.IsAlive()); + } + + public virtual void ApplyGameOptions(IGameOptions opt, byte playerId) { } + + public virtual void OnFixedUpdate(PlayerControl pc) { } + + public virtual void OnCheckPlayerPosition(PlayerControl pc) { } + + public virtual void OnGlobalFixedUpdate(PlayerControl pc, bool lowLoad) { } + + public virtual void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount) { } + + public virtual void OnCoEnterVent(PlayerPhysics physics, int ventId) { } + + public virtual void OnEnterVent(PlayerControl pc, Vent vent) { } + + public virtual void OnExitVent(PlayerControl pc, Vent vent) { } + + public virtual void OnPet(PlayerControl pc) + { + if (!AmongUsClient.Instance.AmHost) return; + + int x = IRandom.Instance.Next(1, 16); + string suffix; + + if (x >= 14) { - try + x -= 13; + TaskState ts = pc.GetTaskState(); + + suffix = pc.GetCustomRoleTypes() switch { - bool generalOption = !Translator.GetString(fieldName).Contains("INVALID"); - string name = overrideName == "" ? generalOption ? fieldName : $"{role}.{fieldName}" : overrideName; + CustomRoleTypes.Impostor => $"Imp{x}", + CustomRoleTypes.Neutral => $"Neutral{x}", + CustomRoleTypes.Crewmate => x == 1 ? "Crew" : ts.HasTasks && ts.IsTaskFinished ? "CrewTaskDone" : "CrewWithTasksLeft", + _ => x.ToString() + }; + } + else + suffix = x.ToString(); - field = (valueRule, defaultValue) switch - { - (null, bool bdv) => new BooleanOptionItem(++_id, name, bdv, tab), - (IntegerValueRule ivr, int idv) => new IntegerOptionItem(++_id, name, ivr, idv, tab), - (FloatValueRule fvr, float fdv) => new FloatOptionItem(++_id, name, fvr, fdv, tab), - (IList selections, int index) => new StringOptionItem(++_id, name, selections, index, tab, noTranslation: noTranslation), - _ => throw new ArgumentException("The valueRule and defaultValue combination is not supported.") - }; + pc.Notify(Translator.GetString($"NoPetActionMsg{suffix}")); + } - field?.SetParent(overrideParent ?? Parent); - if (format != OptionFormat.None) field?.SetValueFormat(format); - } - catch (Exception e) + public virtual bool OnSabotage(PlayerControl pc) + { + return CanUseSabotage(pc); + } + + public virtual bool OnCheckMurder(PlayerControl killer, PlayerControl target) + { + return target != null && killer != null; + } + + public virtual bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target) + { + return target != null && killer != null; + } + + public virtual void OnMurder(PlayerControl killer, PlayerControl target) { } + + public virtual bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting) + { + return true; + } + + public virtual bool OnVanish(PlayerControl pc) + { + return true; + } + + public virtual bool OnVote(PlayerControl voter, PlayerControl target) + { + return false; + } + + public virtual void OnReportDeadBody() { } + + public virtual bool CheckReportDeadBody(PlayerControl reporter, NetworkedPlayerInfo target, PlayerControl killer) + { + return true; + } + + public virtual void AfterMeetingTasks() { } + + public virtual string GetProgressText(byte playerId, bool comms) + { + StringBuilder sb = new(); + sb.Append(Utils.GetAbilityUseLimitDisplay(playerId)); + sb.Append(Utils.GetTaskCount(playerId, comms)); + return sb.ToString(); + } + + public virtual void SetButtonTexts(HudManager hud, byte id) { } + + public virtual string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false) + { + return string.Empty; + } + + public virtual bool KnowRole(PlayerControl seer, PlayerControl target) + { + if (Options.NeutralsKnowEachOther.GetBool() && seer.Is(Team.Neutral) && target.Is(Team.Neutral)) return true; + + CustomRoles seerRole = seer.GetCustomRole(); + return seerRole.IsNK() && seerRole == target.GetCustomRole() && seer.GetTeam() == target.GetTeam(); + } + + protected bool IsThisRole(PlayerControl pc) + { + return pc.GetCustomRole() == Enum.Parse(GetType().Name, true); + } + + protected bool IsThisRole(byte id) + { + var role = Main.PlayerStates.TryGetValue(id, out var state) ? state.MainRole : CustomRoles.NotAssigned; + return role == Enum.Parse(GetType().Name, true); + } + + // Option setup simplifier + protected OptionSetupHandler StartSetup(int id, bool single = false) + { + var role = Enum.Parse(GetType().Name, true); + var tab = TabGroup.OtherRoles; + + if (role.IsImpostor()) tab = TabGroup.ImpostorRoles; + else if (role.IsNeutral(true)) tab = TabGroup.NeutralRoles; + else if (role.IsCrewmate()) tab = TabGroup.CrewmateRoles; + + if (single) Options.SetupSingleRoleOptions(id++, tab, role, hideMaxSetting: true); + else Options.SetupRoleOptions(id++, tab, role); + + return new(id, tab, role); + } +} + +public class OptionSetupHandler(int id, TabGroup tab, CustomRoles role) +{ + private readonly OptionItem Parent = Options.CustomRoleSpawnChances[role]; + private int _id = id; + + public OptionSetupHandler AutoSetupOption(ref OptionItem field, object defaultValue, object valueRule = null, OptionFormat format = OptionFormat.None, [CallerArgumentExpression("field")] string fieldName = "", string overrideName = "", OptionItem overrideParent = null, bool noTranslation = false) + { + try + { + bool generalOption = !Translator.GetString(fieldName).Contains("INVALID"); + string name = overrideName == "" ? generalOption ? fieldName : $"{role}.{fieldName}" : overrideName; + + field = (valueRule, defaultValue) switch { - Logger.Error($"Failed to setup option {fieldName} for {role}", "OptionSetupHandler"); - Utils.ThrowException(e); - } + (null, bool bdv) => new BooleanOptionItem(++_id, name, bdv, tab), + (IntegerValueRule ivr, int idv) => new IntegerOptionItem(++_id, name, ivr, idv, tab), + (FloatValueRule fvr, float fdv) => new FloatOptionItem(++_id, name, fvr, fdv, tab), + (IList selections, int index) => new StringOptionItem(++_id, name, selections, index, tab, noTranslation: noTranslation), + _ => throw new ArgumentException("The valueRule and defaultValue combination is not supported.") + }; - return this; + field?.SetParent(overrideParent ?? Parent); + if (format != OptionFormat.None) field?.SetValueFormat(format); + } + catch (Exception e) + { + Logger.Error($"Failed to setup option {fieldName} for {role}", "OptionSetupHandler"); + Utils.ThrowException(e); } - public void CreateOverrideTasksData() - { - Options.OverrideTasksData.Create(++_id, tab, role); - } + return this; + } + + public void CreateOverrideTasksData() + { + Options.OverrideTasksData.Create(++_id, tab, role); } } \ No newline at end of file diff --git a/Roles/VanillaRole.cs b/Roles/VanillaRole.cs index 1d57a3328..7d9c05bee 100644 --- a/Roles/VanillaRole.cs +++ b/Roles/VanillaRole.cs @@ -1,21 +1,20 @@ -namespace EHR +namespace EHR; + +internal class VanillaRole : RoleBase { - internal class VanillaRole : RoleBase + public static bool On; + + public override bool IsEnable => On; + + public override void SetupCustomOption() { } + + public override void Init() { - public static bool On; + On = false; + } - public override bool IsEnable => On; - - public override void SetupCustomOption() { } - - public override void Init() - { - On = false; - } - - public override void Add(byte playerId) - { - On = true; - } + public override void Add(byte playerId) + { + On = true; } } \ No newline at end of file diff --git a/main.cs b/main.cs index 41b26ba3f..8e8c7fa95 100644 --- a/main.cs +++ b/main.cs @@ -21,943 +21,936 @@ using UnityEngine; [assembly: AssemblyInformationalVersion(Main.PluginVersion)] [assembly: AssemblyVersion(Main.PluginVersion)] -namespace EHR +namespace EHR; + +[BepInPlugin(PluginGuid, "EHR", PluginVersion)] +[BepInIncompatibility("jp.ykundesu.supernewroles")] +[BepInIncompatibility("MalumMenu")] +[BepInIncompatibility("com.ten.thebetterroles")] +[BepInIncompatibility("xyz.crowdedmods.crowdedmod")] +[BepInProcess("Among Us.exe")] +public class Main : BasePlugin { - [BepInPlugin(PluginGuid, "EHR", PluginVersion)] - [BepInIncompatibility("jp.ykundesu.supernewroles")] - [BepInIncompatibility("MalumMenu")] - [BepInIncompatibility("com.ten.thebetterroles")] - [BepInIncompatibility("xyz.crowdedmods.crowdedmod")] - [BepInProcess("Among Us.exe")] - public class Main : BasePlugin + private const string DebugKeyHash = "c0fd562955ba56af3ae20d7ec9e64c664f0facecef4b3e366e109306adeae29d"; + private const string DebugKeySalt = "59687b"; + private const string PluginGuid = "com.gurge44.endlesshostroles"; + public const string PluginVersion = "5.2.0"; + public const string PluginDisplayVersion = "5.2.0"; + public const bool TestBuild = true; + public const string NeutralColor = "#ffab1b"; + public const string ImpostorColor = "#ff1919"; + public const string CrewmateColor = "#8cffff"; + + public const float MinSpeed = 0.0001f; + + // == プログラム設定 / Program Config == + public const string ModName = "EHR"; + public const string ModColor = "#00ffff"; + public const bool AllowPublicRoom = true; + public const string ForkId = "EHR"; + public const string SupportedAUVersion = "2024.8.13"; + + public static readonly Version Version = Version.Parse(PluginVersion); + + //public static ManualLogSource Logger; + public static bool HasArgumentException; + public static string CredentialsText; + + public static Dictionary PlayerVersion = []; + public static bool ChangedRole = false; + public static OptionBackupData RealOptionsData; + public static Dictionary KillTimers = []; + public static Dictionary PlayerStates = []; + public static Dictionary AllPlayerNames = []; + public static Dictionary AllClientRealNames = []; + public static Dictionary<(byte, byte), string> LastNotifyNames; + public static Dictionary PlayerColors = []; + public static Dictionary AfterMeetingDeathPlayers = []; + public static Dictionary RoleColors; + public static Dictionary SetRoles = []; + public static Dictionary> SetAddOns = []; + public static readonly Dictionary>> AlwaysSpawnTogetherCombos = []; + public static readonly Dictionary>> NeverSpawnTogetherCombos = []; + public static Dictionary LastAddOns = []; + public static List AllRoleClasses; + public static float RefixCooldownDelay; + public static bool ProcessShapeshifts = true; + public static readonly Dictionary AbilityCD = []; + public static Dictionary AbilityUseLimit = []; + public static List DontCancelVoteList = []; + public static HashSet ResetCamPlayerList = []; + public static List WinnerList = []; + public static List WinnerNameList = []; + public static List ClientIdList = []; + public static Dictionary AllPlayerKillCooldown = []; + public static Dictionary LastEnteredVent = []; + public static Dictionary LastEnteredVentLocation = []; + public static readonly List<(string Message, byte ReceiverID, string Title)> MessagesToSend = []; + public static bool IsChatCommand; + public static bool DoBlockNameChange; + public static int UpdateTime; + public static bool NewLobby; + public static readonly Dictionary SayStartTimes = []; + public static readonly Dictionary SayBanwordsTimes = []; + public static Dictionary AllPlayerSpeed = []; + public static readonly Dictionary GuesserGuessed = []; + public static bool HasJustStarted; + public static int AliveImpostorCount; + public static Dictionary CheckShapeshift = []; + public static Dictionary ShapeshiftTarget = []; + public static bool VisibleTasksCount; + public static string NickName = ""; + public static bool IntroDestroyed; + public static float DefaultCrewmateVision; + public static float DefaultImpostorVision; + public static readonly bool IsAprilFools = DateTime.Now.Month == 4 && DateTime.Now.Day == 1; + public static bool ResetOptions = true; + public static string FirstDied = string.Empty; + public static string ShieldPlayer = string.Empty; + + public static HashSet DiedThisRound = []; + public static List LoversPlayers = []; + public static bool IsLoversDead = true; + public static List CyberStarDead = []; + public static List BaitAlive = []; + public static Dictionary KilledDiseased = []; + public static Dictionary KilledAntidote = []; + public static List BrakarVoteFor = []; + public static Dictionary SleuthMsgs = []; + public static int MadmateNum; + + public static Main Instance; + + + public static string OverrideWelcomeMsg = string.Empty; + + public static readonly Dictionary> GuessNumber = []; + + public static readonly List NameSnacksCn = ["冰激凌", "奶茶", "巧克力", "蛋糕", "甜甜圈", "可乐", "柠檬水", "冰糖葫芦", "果冻", "糖果", "牛奶", "抹茶", "烧仙草", "菠萝包", "布丁", "椰子冻", "曲奇", "红豆土司", "三彩团子", "艾草团子", "泡芙", "可丽饼", "桃酥", "麻薯", "鸡蛋仔", "马卡龙", "雪梅娘", "炒酸奶", "蛋挞", "松饼", "西米露", "奶冻", "奶酥", "可颂", "奶糖"]; + + // ReSharper disable once StringLiteralTypo + public static readonly List NameSnacksEn = ["Ice cream", "Milk tea", "Chocolate", "Cake", "Donut", "Coke", "Lemonade", "Candied haws", "Jelly", "Candy", "Milk", "Matcha", "Burning Grass Jelly", "Pineapple Bun", "Pudding", "Coconut Jelly", "Cookies", "Red Bean Toast", "Three Color Dumplings", "Wormwood Dumplings", "Puffs", "Can be Crepe", "Peach Crisp", "Mochi", "Egg Waffle", "Macaron", "Snow Plum Niang", "Fried Yogurt", "Egg Tart", "Muffin", "Sago Dew", "panna cotta", "soufflé", "croissant", "toffee"]; + private Coroutines coroutines; + + private static HashAuth DebugKeyAuth { get; set; } + private static ConfigEntry DebugKeyInput { get; set; } + + private Harmony Harmony { get; } = new(PluginGuid); + + public static NormalGameOptionsV08 NormalOptions => GameOptionsManager.Instance.currentNormalGameOptions; + + // Client Options + public static ConfigEntry HideName { get; private set; } + public static ConfigEntry HideColor { get; private set; } + public static ConfigEntry MessageWait { get; private set; } + public static ConfigEntry GM { get; private set; } + public static ConfigEntry UnlockFps { get; private set; } + public static ConfigEntry AutoStart { get; private set; } + public static ConfigEntry ForceOwnLanguage { get; private set; } + public static ConfigEntry ForceOwnLanguageRoleName { get; private set; } + public static ConfigEntry EnableCustomButton { get; private set; } + public static ConfigEntry EnableCustomSoundEffect { get; private set; } + public static ConfigEntry SwitchVanilla { get; private set; } + public static ConfigEntry GodMode { get; private set; } + public static ConfigEntry DarkTheme { get; private set; } + public static ConfigEntry HorseMode { get; private set; } + public static ConfigEntry LongMode { get; private set; } + public static ConfigEntry ShowPlayerInfoInLobby { get; private set; } + public static ConfigEntry LobbyMusic { get; private set; } + public static ConfigEntry EnableCommandHelper { get; private set; } + + // Preset Name Options + public static ConfigEntry Preset1 { get; private set; } + public static ConfigEntry Preset2 { get; private set; } + public static ConfigEntry Preset3 { get; private set; } + public static ConfigEntry Preset4 { get; private set; } + public static ConfigEntry Preset5 { get; private set; } + public static ConfigEntry Preset6 { get; private set; } + public static ConfigEntry Preset7 { get; private set; } + public static ConfigEntry Preset8 { get; private set; } + public static ConfigEntry Preset9 { get; private set; } + public static ConfigEntry Preset10 { get; private set; } + + // Other Configs + public static ConfigEntry WebhookUrl { get; private set; } + public static ConfigEntry BetaBuildUrl { get; private set; } + public static ConfigEntry LastKillCooldown { get; private set; } + public static ConfigEntry LastShapeshifterCooldown { get; private set; } + public static bool IsFixedCooldown => CustomRoles.Vampire.IsEnable() || CustomRoles.Poisoner.IsEnable(); + + public static PlayerControl[] AllPlayerControls { - private const string DebugKeyHash = "c0fd562955ba56af3ae20d7ec9e64c664f0facecef4b3e366e109306adeae29d"; - private const string DebugKeySalt = "59687b"; - private const string PluginGuid = "com.gurge44.endlesshostroles"; - public const string PluginVersion = "5.2.0"; - public const string PluginDisplayVersion = "5.2.0"; - public const bool TestBuild = true; - public const string NeutralColor = "#ffab1b"; - public const string ImpostorColor = "#ff1919"; - public const string CrewmateColor = "#8cffff"; - - public const float MinSpeed = 0.0001f; - - // == プログラム設定 / Program Config == - public const string ModName = "EHR"; - public const string ModColor = "#00ffff"; - public const bool AllowPublicRoom = true; - public const string ForkId = "EHR"; - public const string SupportedAUVersion = "2024.8.13"; - - public static readonly Version Version = Version.Parse(PluginVersion); - - //public static ManualLogSource Logger; - public static bool HasArgumentException; - public static string CredentialsText; - - public static Dictionary PlayerVersion = []; - public static bool ChangedRole = false; - public static OptionBackupData RealOptionsData; - public static Dictionary KillTimers = []; - public static Dictionary PlayerStates = []; - public static Dictionary AllPlayerNames = []; - public static Dictionary AllClientRealNames = []; - public static Dictionary<(byte, byte), string> LastNotifyNames; - public static Dictionary PlayerColors = []; - public static Dictionary AfterMeetingDeathPlayers = []; - public static Dictionary RoleColors; - public static Dictionary SetRoles = []; - public static Dictionary> SetAddOns = []; - public static readonly Dictionary>> AlwaysSpawnTogetherCombos = []; - public static readonly Dictionary>> NeverSpawnTogetherCombos = []; - public static Dictionary LastAddOns = []; - public static List AllRoleClasses; - public static float RefixCooldownDelay; - public static bool ProcessShapeshifts = true; - public static readonly Dictionary AbilityCD = []; - public static Dictionary AbilityUseLimit = []; - public static List DontCancelVoteList = []; - public static HashSet ResetCamPlayerList = []; - public static List WinnerList = []; - public static List WinnerNameList = []; - public static List ClientIdList = []; - public static Dictionary AllPlayerKillCooldown = []; - public static Dictionary LastEnteredVent = []; - public static Dictionary LastEnteredVentLocation = []; - public static readonly List<(string Message, byte ReceiverID, string Title)> MessagesToSend = []; - public static bool IsChatCommand; - public static bool DoBlockNameChange; - public static int UpdateTime; - public static bool NewLobby; - public static readonly Dictionary SayStartTimes = []; - public static readonly Dictionary SayBanwordsTimes = []; - public static Dictionary AllPlayerSpeed = []; - public static readonly Dictionary GuesserGuessed = []; - public static bool HasJustStarted; - public static int AliveImpostorCount; - public static Dictionary CheckShapeshift = []; - public static Dictionary ShapeshiftTarget = []; - public static bool VisibleTasksCount; - public static string NickName = ""; - public static bool IntroDestroyed; - public static float DefaultCrewmateVision; - public static float DefaultImpostorVision; - public static readonly bool IsAprilFools = DateTime.Now.Month == 4 && DateTime.Now.Day == 1; - public static bool ResetOptions = true; - public static string FirstDied = string.Empty; - public static string ShieldPlayer = string.Empty; - - public static HashSet DiedThisRound = []; - public static List LoversPlayers = []; - public static bool IsLoversDead = true; - public static List CyberStarDead = []; - public static List BaitAlive = []; - public static Dictionary KilledDiseased = []; - public static Dictionary KilledAntidote = []; - public static List BrakarVoteFor = []; - public static Dictionary SleuthMsgs = []; - public static int MadmateNum; - - public static Main Instance; - - - public static string OverrideWelcomeMsg = string.Empty; - - public static readonly Dictionary> GuessNumber = []; - - public static readonly List NameSnacksCn = ["冰激凌", "奶茶", "巧克力", "蛋糕", "甜甜圈", "可乐", "柠檬水", "冰糖葫芦", "果冻", "糖果", "牛奶", "抹茶", "烧仙草", "菠萝包", "布丁", "椰子冻", "曲奇", "红豆土司", "三彩团子", "艾草团子", "泡芙", "可丽饼", "桃酥", "麻薯", "鸡蛋仔", "马卡龙", "雪梅娘", "炒酸奶", "蛋挞", "松饼", "西米露", "奶冻", "奶酥", "可颂", "奶糖"]; - - // ReSharper disable once StringLiteralTypo - public static readonly List NameSnacksEn = ["Ice cream", "Milk tea", "Chocolate", "Cake", "Donut", "Coke", "Lemonade", "Candied haws", "Jelly", "Candy", "Milk", "Matcha", "Burning Grass Jelly", "Pineapple Bun", "Pudding", "Coconut Jelly", "Cookies", "Red Bean Toast", "Three Color Dumplings", "Wormwood Dumplings", "Puffs", "Can be Crepe", "Peach Crisp", "Mochi", "Egg Waffle", "Macaron", "Snow Plum Niang", "Fried Yogurt", "Egg Tart", "Muffin", "Sago Dew", "panna cotta", "soufflé", "croissant", "toffee"]; - private Coroutines coroutines; - - private static HashAuth DebugKeyAuth { get; set; } - private static ConfigEntry DebugKeyInput { get; set; } - - private Harmony Harmony { get; } = new(PluginGuid); - - public static NormalGameOptionsV08 NormalOptions => GameOptionsManager.Instance.currentNormalGameOptions; - - // Client Options - public static ConfigEntry HideName { get; private set; } - public static ConfigEntry HideColor { get; private set; } - public static ConfigEntry MessageWait { get; private set; } - public static ConfigEntry GM { get; private set; } - public static ConfigEntry UnlockFps { get; private set; } - public static ConfigEntry AutoStart { get; private set; } - public static ConfigEntry ForceOwnLanguage { get; private set; } - public static ConfigEntry ForceOwnLanguageRoleName { get; private set; } - public static ConfigEntry EnableCustomButton { get; private set; } - public static ConfigEntry EnableCustomSoundEffect { get; private set; } - public static ConfigEntry SwitchVanilla { get; private set; } - public static ConfigEntry GodMode { get; private set; } - public static ConfigEntry DarkTheme { get; private set; } - public static ConfigEntry HorseMode { get; private set; } - public static ConfigEntry LongMode { get; private set; } - public static ConfigEntry ShowPlayerInfoInLobby { get; private set; } - public static ConfigEntry LobbyMusic { get; private set; } - public static ConfigEntry EnableCommandHelper { get; private set; } - - // Preset Name Options - public static ConfigEntry Preset1 { get; private set; } - public static ConfigEntry Preset2 { get; private set; } - public static ConfigEntry Preset3 { get; private set; } - public static ConfigEntry Preset4 { get; private set; } - public static ConfigEntry Preset5 { get; private set; } - public static ConfigEntry Preset6 { get; private set; } - public static ConfigEntry Preset7 { get; private set; } - public static ConfigEntry Preset8 { get; private set; } - public static ConfigEntry Preset9 { get; private set; } - public static ConfigEntry Preset10 { get; private set; } - - // Other Configs - public static ConfigEntry WebhookUrl { get; private set; } - public static ConfigEntry BetaBuildUrl { get; private set; } - public static ConfigEntry LastKillCooldown { get; private set; } - public static ConfigEntry LastShapeshifterCooldown { get; private set; } - public static bool IsFixedCooldown => CustomRoles.Vampire.IsEnable() || CustomRoles.Poisoner.IsEnable(); - - public static PlayerControl[] AllPlayerControls + get { - get + int count = PlayerControl.AllPlayerControls.Count; + var result = new PlayerControl[count]; + var i = 0; + + foreach (PlayerControl pc in PlayerControl.AllPlayerControls) { - int count = PlayerControl.AllPlayerControls.Count; - var result = new PlayerControl[count]; - var i = 0; + if (pc == null || pc.PlayerId == 255) continue; - foreach (PlayerControl pc in PlayerControl.AllPlayerControls) - { - if (pc == null || pc.PlayerId == 255) continue; - - result[i++] = pc; - } - - if (i == 0) return []; - - Array.Resize(ref result, i); - return result; - } - } - - public static PlayerControl[] AllAlivePlayerControls - { - get - { - int count = PlayerControl.AllPlayerControls.Count; - var result = new PlayerControl[count]; - var i = 0; - - foreach (PlayerControl pc in PlayerControl.AllPlayerControls) - { - if (pc == null || pc.PlayerId == 255 || !pc.IsAlive() || pc.Data.Disconnected || Pelican.IsEaten(pc.PlayerId)) continue; - - result[i++] = pc; - } - - if (i == 0) return []; - - Array.Resize(ref result, i); - return result; - } - } - - // ReSharper disable once InconsistentNaming - public static string Get_TName_Snacks => TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? NameSnacksCn.RandomElement() : NameSnacksEn.RandomElement(); - - public static NetworkedPlayerInfo LastVotedPlayerInfo { get; set; } - - public static MapNames CurrentMap => (MapNames)NormalOptions.MapId; - - public override void Load() - { - Instance = this; - - //Client Options - HideName = Config.Bind("Client Options", "Hide Game Code Name", "EHR"); - HideColor = Config.Bind("Client Options", "Hide Game Code Color", $"{ModColor}"); - DebugKeyInput = Config.Bind("Authentication", "Debug Key", string.Empty); - AutoStart = Config.Bind("Client Options", "AutoStart", false); - GM = Config.Bind("Client Options", "GM", false); - UnlockFps = Config.Bind("Client Options", "UnlockFPS", false); - AutoStart = Config.Bind("Client Options", "AutoStart", false); - ForceOwnLanguage = Config.Bind("Client Options", "ForceOwnLanguage", false); - ForceOwnLanguageRoleName = Config.Bind("Client Options", "ForceOwnLanguageRoleName", false); - EnableCustomButton = Config.Bind("Client Options", "EnableCustomButton", true); - EnableCustomSoundEffect = Config.Bind("Client Options", "EnableCustomSoundEffect", true); - SwitchVanilla = Config.Bind("Client Options", "SwitchVanilla", false); - GodMode = Config.Bind("Client Options", "GodMode", false); - DarkTheme = Config.Bind("Client Options", "DarkTheme", true); - HorseMode = Config.Bind("Client Options", "HorseMode", false); - LongMode = Config.Bind("Client Options", "LongMode", false); - ShowPlayerInfoInLobby = Config.Bind("Client Options", "ShowPlayerInfoInLobby", false); - LobbyMusic = Config.Bind("Client Options", "LobbyMusic", false); - EnableCommandHelper = Config.Bind("Client Options", "EnableCommandHelper", true); - - //Logger = BepInEx.Logging.Logger.CreateLogSource("EHR"); - coroutines = AddComponent(); - Logger.Enable(); - Logger.Disable("NotifyRoles"); - Logger.Disable("SwitchSystem"); - Logger.Disable("ModNews"); - Logger.Disable("CustomRpcSender"); - - if (!DebugModeManager.AmDebugger) - { - Logger.Disable("2018k"); - Logger.Disable("Github"); - //EHR.Logger.Disable("ReceiveRPC"); - Logger.Disable("SendRPC"); - Logger.Disable("SetRole"); - Logger.Disable("Info.Role"); - Logger.Disable("TaskState.Init"); - //EHR.Logger.Disable("Vote"); - Logger.Disable("RpcSetNamePrivate"); - //EHR.Logger.Disable("SendChat"); - Logger.Disable("SetName"); - //EHR.Logger.Disable("AssignRoles"); - //EHR.Logger.Disable("RepairSystem"); - //EHR.Logger.Disable("MurderPlayer"); - //EHR.Logger.Disable("CheckMurder"); - Logger.Disable("PlayerControl.RpcSetRole"); - Logger.Disable("SyncCustomSettings"); - } - //EHR.Logger.isDetail = true; - - // Authentication related - Initialization - DebugKeyAuth = new(DebugKeyHash, DebugKeySalt); - - DebugModeManager.Auth(DebugKeyAuth, DebugKeyInput.Value); - - Preset1 = Config.Bind("Preset Name Options", "Preset1", "Preset_1"); - Preset2 = Config.Bind("Preset Name Options", "Preset2", "Preset_2"); - Preset3 = Config.Bind("Preset Name Options", "Preset3", "Preset_3"); - Preset4 = Config.Bind("Preset Name Options", "Preset4", "Preset_4"); - Preset5 = Config.Bind("Preset Name Options", "Preset5", "Preset_5"); - Preset6 = Config.Bind("Preset Name Options", "Preset6", "Preset_6"); - Preset7 = Config.Bind("Preset Name Options", "Preset7", "Preset_7"); - Preset8 = Config.Bind("Preset Name Options", "Preset8", "Preset_8"); - Preset9 = Config.Bind("Preset Name Options", "Preset9", "Preset_9"); - Preset10 = Config.Bind("Preset Name Options", "Preset10", "Preset_10"); - WebhookUrl = Config.Bind("Other", "WebhookURL", "none"); - BetaBuildUrl = Config.Bind("Other", "BetaBuildURL", string.Empty); - MessageWait = Config.Bind("Other", "MessageWait", 0); - LastKillCooldown = Config.Bind("Other", "LastKillCooldown", (float)30); - LastShapeshifterCooldown = Config.Bind("Other", "LastShapeshifterCooldown", (float)30); - - HasArgumentException = false; - - try - { - RoleColors = new() - { - // Vanilla - { CustomRoles.Crewmate, "#8cffff" }, - { CustomRoles.Engineer, "#FF6A00" }, - { CustomRoles.Scientist, "#8ee98e" }, - { CustomRoles.GuardianAngel, "#77e6d1" }, - { CustomRoles.Tracker, "#34ad50" }, - { CustomRoles.Noisemaker, "#ff4a62" }, - // Vanilla Remakes - { CustomRoles.CrewmateEHR, "#8cffff" }, - { CustomRoles.EngineerEHR, "#FF6A00" }, - { CustomRoles.ScientistEHR, "#8ee98e" }, - { CustomRoles.GuardianAngelEHR, "#77e6d1" }, - { CustomRoles.TrackerEHR, "#34ad50" }, - { CustomRoles.NoisemakerEHR, "#ff4a62" }, - // Crewmates - { CustomRoles.Luckey, "#b8d7a3" }, - { CustomRoles.Needy, "#a4dffe" }, - { CustomRoles.SabotageMaster, "#3333ff" }, - { CustomRoles.Snitch, "#b8fb4f" }, - { CustomRoles.Marshall, "#5573aa" }, - { CustomRoles.Mayor, "#204d42" }, - { CustomRoles.Paranoia, "#c993f5" }, - { CustomRoles.Psychic, "#6F698C" }, - { CustomRoles.Sheriff, "#ffb347" }, - { CustomRoles.CopyCat, "#ffb2ab" }, - { CustomRoles.SuperStar, "#f6f657" }, - { CustomRoles.CyberStar, "#ee4a55" }, - { CustomRoles.Ventguard, "#ffa5ff" }, - { CustomRoles.Demolitionist, "#5e2801" }, - { CustomRoles.Express, "#00ffff" }, - { CustomRoles.NiceEraser, "#00a5ff" }, - { CustomRoles.TaskManager, "#00ffa5" }, - { CustomRoles.Adventurer, "#fbbb04" }, - { CustomRoles.Randomizer, "#fcba03" }, - { CustomRoles.Beacon, "#a3fdff" }, - { CustomRoles.Rabbit, "#88d2ff" }, - { CustomRoles.Shiftguard, "#eb34cc" }, - { CustomRoles.Mole, "#00ff80" }, - { CustomRoles.Markseeker, "#f2a0f1" }, - { CustomRoles.Sentinel, "#4bc8d6" }, - { CustomRoles.Electric, "#fbff00" }, - { CustomRoles.Philantropist, "#e3b384" }, - { CustomRoles.Tornado, "#303030" }, - { CustomRoles.Dad, "#037bfc" }, - { CustomRoles.Insight, "#26ff38" }, - { CustomRoles.Tunneler, "#543232" }, - { CustomRoles.Detour, "#ffd35c" }, - { CustomRoles.Gaulois, "#42d1f5" }, - { CustomRoles.Druid, "#ffb694" }, - { CustomRoles.Catcher, "#aaf2cf" }, - { CustomRoles.Autocrat, "#e2ed64" }, - { CustomRoles.LovingCrewmate, "#ff9ace" }, - { CustomRoles.LovingImpostor, "#ff9ace" }, - { CustomRoles.ToiletMaster, "#4281f5" }, - { CustomRoles.Goose, "#f9ffb8" }, - { CustomRoles.Sentry, "#db55f2" }, - { CustomRoles.Perceiver, "#ebeb34" }, - { CustomRoles.Convener, "#34eb7a" }, - { CustomRoles.Mathematician, "#eb3474" }, - { CustomRoles.Transmitter, "#c9a11e" }, - { CustomRoles.Ankylosaurus, "#7FE44C" }, - { CustomRoles.Leery, "#32a852" }, - { CustomRoles.Wizard, "#FD05CC" }, - { CustomRoles.Negotiator, "#00c3ff" }, - { CustomRoles.Grappler, "#befc03" }, - { CustomRoles.Journalist, "#fcba03" }, - { CustomRoles.Whisperer, "#82919e" }, - { CustomRoles.Car, "#6e6e6e" }, - { CustomRoles.President, "#30916f" }, - { CustomRoles.Oxyman, "#ffa58c" }, - { CustomRoles.Rhapsode, "#f5ad42" }, - { CustomRoles.Chef, "#d6d6ff" }, - { CustomRoles.Lyncher, "#14ba7d" }, - { CustomRoles.Socialite, "#32a8a8" }, - { CustomRoles.Adrenaline, "#ffff00" }, - { CustomRoles.Safeguard, "#4949e3" }, - { CustomRoles.Clairvoyant, "#d4ffdd" }, - { CustomRoles.Inquirer, "#7c55f2" }, - { CustomRoles.Soothsayer, "#4e529c" }, - { CustomRoles.Telekinetic, "#d6c618" }, - { CustomRoles.Doppelganger, "#f6f4a3" }, - { CustomRoles.Nightmare, "#1e1247" }, - { CustomRoles.Altruist, "#300000" }, - { CustomRoles.Bane, "#745da3" }, - { CustomRoles.Benefactor, "#4aeaff" }, - { CustomRoles.GuessManagerRole, "#d4ff00" }, - { CustomRoles.Drainer, "#149627" }, - { CustomRoles.NiceHacker, "#75fa4c" }, - { CustomRoles.Aid, "#D7BDE2" }, - { CustomRoles.DonutDelivery, "#a46efa" }, - { CustomRoles.Analyst, "#33ddff" }, - { CustomRoles.Escort, "#ff94e6" }, - { CustomRoles.Spy, "#34495E" }, - { CustomRoles.Doormaster, "#7FB3D5" }, - { CustomRoles.Tether, "#138D75" }, - { CustomRoles.Ricochet, "#EDBB99" }, - { CustomRoles.SpeedBooster, "#00ffff" }, - { CustomRoles.Doctor, "#80ffdd" }, - { CustomRoles.Dictator, "#df9b00" }, - { CustomRoles.Detective, "#7160e8" }, - { CustomRoles.NiceGuesser, "#f0e68c" }, - { CustomRoles.SwordsMan, "#7a7a7a" }, - { CustomRoles.Transporter, "#42D1FF" }, - { CustomRoles.TimeManager, "#6495ed" }, - { CustomRoles.Veteran, "#a77738" }, - { CustomRoles.Bodyguard, "#185abd" }, - { CustomRoles.Witness, "#e70052" }, - { CustomRoles.Lookout, "#2a52be" }, - { CustomRoles.Grenadier, "#3c4a16" }, - { CustomRoles.Lighter, "#eee5be" }, - { CustomRoles.SecurityGuard, "#c3b25f" }, - { CustomRoles.Medic, "#00ff97" }, - { CustomRoles.Divinator, "#882c83" }, - { CustomRoles.Glitch, "#39FF14" }, - { CustomRoles.Judge, "#f8d85a" }, - { CustomRoles.Mortician, "#333c49" }, - { CustomRoles.Mediumshiper, "#a200ff" }, - { CustomRoles.Observer, "#a8e0fa" }, - { CustomRoles.DovesOfNeace, "#ffffff" }, - { CustomRoles.Jailor, "#aa900d" }, - { CustomRoles.Monarch, "#FFA500" }, - { CustomRoles.Bloodhound, "#8B0000" }, - { CustomRoles.Enigma, "#676798" }, - { CustomRoles.Scout, "#3CB371" }, - { CustomRoles.CameraMan, "#000930" }, - { CustomRoles.Merchant, "#D27D2D" }, - { CustomRoles.Monitor, "#7223DA" }, - { CustomRoles.Deputy, "#df9026" }, - { CustomRoles.Cleanser, "#98FF98" }, - { CustomRoles.NiceSwapper, "#922348" }, - { CustomRoles.Ignitor, "#ffffa5" }, - { CustomRoles.Guardian, "#2E8B57" }, - { CustomRoles.Addict, "#008000" }, - { CustomRoles.Alchemist, "#e6d798" }, - { CustomRoles.Tracefinder, "#0066CC" }, - { CustomRoles.Oracle, "#6666FF" }, - { CustomRoles.Spiritualist, "#669999" }, - { CustomRoles.Chameleon, "#01C834" }, - { CustomRoles.ParityCop, "#0D57AF" }, - { CustomRoles.TimeMaster, "#44baff" }, - { CustomRoles.Crusader, "#C65C39" }, - { CustomRoles.Speedrunner, "#800080" }, - // Neutrals - { CustomRoles.Arsonist, "#ff6633" }, - { CustomRoles.Pyromaniac, "#ff6633" }, - { CustomRoles.PlagueBearer, "#e5f6b4" }, - { CustomRoles.Pestilence, "#343136" }, - { CustomRoles.Jester, "#ec62a5" }, - { CustomRoles.Terrorist, "#00e600" }, - { CustomRoles.Executioner, "#611c3a" }, - { CustomRoles.Lawyer, "#008080" }, - { CustomRoles.God, "#f96464" }, - { CustomRoles.Opportunist, "#4dff4d" }, - { CustomRoles.Mario, "#ff6201" }, - { CustomRoles.Jackal, "#00b4eb" }, - { CustomRoles.Sidekick, "#00b4eb" }, - { CustomRoles.Innocent, "#8f815e" }, - { CustomRoles.Pelican, "#34c84b" }, - { CustomRoles.Revolutionist, "#ba4d06" }, - { CustomRoles.FFF, "#414b66" }, - { CustomRoles.Konan, "#4d4dff" }, - { CustomRoles.Gamer, "#68bc71" }, - { CustomRoles.DarkHide, "#483d8b" }, - { CustomRoles.Workaholic, "#008b8b" }, - { CustomRoles.Collector, "#9d8892" }, - { CustomRoles.NecroGuesser, "#F6FE03" }, - { CustomRoles.Provocateur, "#74ba43" }, - { CustomRoles.Sunnyboy, "#ff9902" }, - { CustomRoles.Poisoner, "#e70052" }, - { CustomRoles.Totocalcio, "#ff9409" }, - { CustomRoles.Romantic, "#FF1493" }, - { CustomRoles.VengefulRomantic, "#ba2749" }, - { CustomRoles.RuthlessRomantic, "#D2691E" }, - { CustomRoles.Succubus, "#cf6acd" }, - { CustomRoles.Necromancer, "#f7adcf" }, - { CustomRoles.Deathknight, "#361d12" }, - { CustomRoles.HexMaster, "#ff00ff" }, - { CustomRoles.Wraith, "#4B0082" }, - { CustomRoles.NSerialKiller, "#233fcc" }, - { CustomRoles.Weatherman, "#347deb" }, - { CustomRoles.NoteKiller, "#4CA8E4" }, - { CustomRoles.Vortex, "#a83293" }, - { CustomRoles.Beehive, "#ffff00" }, - { CustomRoles.RouleteGrandeur, "#a88332" }, - { CustomRoles.Nonplus, "#09632f" }, - { CustomRoles.Tremor, "#e942f5" }, - { CustomRoles.Evolver, "#f2c444" }, - { CustomRoles.Rogue, "#7a629c" }, - { CustomRoles.Patroller, "#c1cc27" }, - { CustomRoles.Simon, "#c4b8ff" }, - { CustomRoles.Chemist, "#4287f5" }, - { CustomRoles.Samurai, "#73495c" }, - { CustomRoles.QuizMaster, "#CF2472" }, - { CustomRoles.Bargainer, "#4f2f36" }, - { CustomRoles.Tiger, "#fcba03" }, - { CustomRoles.SoulHunter, "#3f2c61" }, - { CustomRoles.Enderman, "#3c008a" }, - { CustomRoles.Mycologist, "#0043de" }, - { CustomRoles.Bubble, "#ff38c3" }, - { CustomRoles.Hookshot, "#32a852" }, - { CustomRoles.Sprayer, "#ffc038" }, - { CustomRoles.PlagueDoctor, "#ff6633" }, - { CustomRoles.Curser, "#510c91" }, - { CustomRoles.Postman, "#00b893" }, - { CustomRoles.SchrodingersCat, "#616161" }, - { CustomRoles.Shifter, "#777777" }, - { CustomRoles.Impartial, "#4287f5" }, - { CustomRoles.Gaslighter, "#b6aa82" }, - { CustomRoles.Tank, "#176320" }, - { CustomRoles.Technician, "#4e96f5" }, - { CustomRoles.Backstabber, "#fcba03" }, - { CustomRoles.Predator, "#c73906" }, - { CustomRoles.Reckless, "#6e000d" }, - { CustomRoles.Magician, "#BF5FFF" }, - { CustomRoles.WeaponMaster, "#6f02bd" }, - { CustomRoles.Eclipse, "#0E6655" }, - { CustomRoles.Vengeance, "#33cccc" }, - { CustomRoles.HeadHunter, "#ffcc66" }, - { CustomRoles.Imitator, "#ff00a5" }, - { CustomRoles.Werewolf, "#964B00" }, - { CustomRoles.Bandit, "#8B008B" }, - { CustomRoles.Agitater, "#F4A460" }, - { CustomRoles.BloodKnight, "#630000" }, - { CustomRoles.Juggernaut, "#A41342" }, - { CustomRoles.Cherokious, "#de4b9e" }, - { CustomRoles.Parasite, "#ff1919" }, - { CustomRoles.Crewpostor, "#ff1919" }, - { CustomRoles.Refugee, "#ff1919" }, - { CustomRoles.Virus, "#2E8B57" }, - { CustomRoles.Farseer, "#BA55D3" }, - { CustomRoles.Pursuer, "#617218" }, - { CustomRoles.Phantasm, "#662962" }, - { CustomRoles.Jinx, "#ed2f91" }, - { CustomRoles.Maverick, "#781717" }, - { CustomRoles.Ritualist, "#663399" }, - { CustomRoles.Pickpocket, "#47008B" }, - { CustomRoles.Traitor, "#BA2E05" }, - { CustomRoles.Vulture, "#556B2F" }, - { CustomRoles.Medusa, "#9900CC" }, - { CustomRoles.Spiritcaller, "#003366" }, - { CustomRoles.EvilSpirit, "#003366" }, - { CustomRoles.Convict, "#ff1919" }, - { CustomRoles.Amnesiac, "#7FBFFF" }, - { CustomRoles.Doomsayer, "#14f786" }, - // Ghost roles - { CustomRoles.Warden, "#32a852" }, - { CustomRoles.Minion, "#ff1919" }, - { CustomRoles.Specter, "#b446e3" }, - { CustomRoles.Haunter, "#d1b1de" }, - { CustomRoles.Bloodmoon, "#ff1313" }, - { CustomRoles.GA, "#8cffff" }, - // GM - { CustomRoles.GM, "#ff5b70" }, - // Add-ons - { CustomRoles.NotAssigned, "#ffffff" }, - { CustomRoles.LastImpostor, "#ff1919" }, - { CustomRoles.Lovers, "#ff9ace" }, - { CustomRoles.Bloodlust, "#630000" }, - { CustomRoles.Madmate, "#ff1919" }, - { CustomRoles.Watcher, "#800080" }, - { CustomRoles.Sleuth, "#30221c" }, - { CustomRoles.Energetic, "#ffff00" }, - { CustomRoles.Messenger, "#28b573" }, - { CustomRoles.Dynamo, "#ebe534" }, - { CustomRoles.AntiTP, "#fcba03" }, - { CustomRoles.Fragile, "#debe66" }, - { CustomRoles.Allergic, "#e3bd56" }, - { CustomRoles.Introvert, "#6293e3" }, - { CustomRoles.Deadlined, "#ffa500" }, - { CustomRoles.Rookie, "#bf671f" }, - { CustomRoles.Trainee, "#4287f5" }, - { CustomRoles.Taskcounter, "#ff1919" }, - { CustomRoles.Stained, "#e6bf91" }, - { CustomRoles.Clumsy, "#b8b8b8" }, - { CustomRoles.Mischievous, "#30221c" }, - { CustomRoles.Flashman, "#ff8400" }, - { CustomRoles.Haste, "#f0ec22" }, - { CustomRoles.Busy, "#32a852" }, - { CustomRoles.Sleep, "#000000" }, - { CustomRoles.Truant, "#eb3467" }, - { CustomRoles.Disco, "#eb34e8" }, - { CustomRoles.Sonar, "#b8fffe" }, - { CustomRoles.Asthmatic, "#8feb34" }, - { CustomRoles.Giant, "#32a852" }, - { CustomRoles.Nimble, "#feffc7" }, - { CustomRoles.Physicist, "#87e9ff" }, - { CustomRoles.Finder, "#32a879" }, - { CustomRoles.Noisy, "#e34fb2" }, - { CustomRoles.Torch, "#eee5be" }, - { CustomRoles.Seer, "#61b26c" }, - { CustomRoles.Brakar, "#1447af" }, - { CustomRoles.Oblivious, "#424242" }, - { CustomRoles.Bewilder, "#c894f5" }, - { CustomRoles.Spurt, "#c9e8f5" }, - { CustomRoles.Sunglasses, "#E7C12B" }, - { CustomRoles.Workhorse, "#00ffff" }, - { CustomRoles.Undead, "#ed9abd" }, - { CustomRoles.Cleansed, "#98FF98" }, - { CustomRoles.Fool, "#e6e7ff" }, - { CustomRoles.Avanger, "#ffab1c" }, - { CustomRoles.Youtuber, "#fb749b" }, - { CustomRoles.Egoist, "#5600ff" }, - { CustomRoles.TicketsStealer, "#ff1919" }, - { CustomRoles.DualPersonality, "#3a648f" }, - { CustomRoles.Mimic, "#ff1919" }, - { CustomRoles.Guesser, "#f8cd46" }, - { CustomRoles.Necroview, "#663399" }, - { CustomRoles.Reach, "#74ba43" }, - { CustomRoles.Magnet, "#eb3477" }, - { CustomRoles.DeadlyQuota, "#ff1919" }, - { CustomRoles.Circumvent, "#ff1919" }, - { CustomRoles.Damocles, "#ff1919" }, - { CustomRoles.Stressed, "#9403fc" }, - { CustomRoles.Charmed, "#cf6acd" }, - { CustomRoles.Bait, "#00f7ff" }, - { CustomRoles.Trapper, "#5a8fd0" }, - { CustomRoles.Onbound, "#BAAAE9" }, - { CustomRoles.Knighted, "#FFA500" }, - { CustomRoles.Contagious, "#2E8B57" }, - { CustomRoles.Unreportable, "#FF6347" }, - { CustomRoles.Lucky, "#b8d7a3" }, - { CustomRoles.Unlucky, "#d7a3a3" }, - { CustomRoles.DoubleShot, "#19fa8d" }, - { CustomRoles.Rascal, "#990000" }, - { CustomRoles.Gravestone, "#2EA8E7" }, - { CustomRoles.Lazy, "#a4dffe" }, - { CustomRoles.Autopsy, "#80ffdd" }, - { CustomRoles.Loyal, "#B71556" }, - { CustomRoles.Visionary, "#ff1919" }, - { CustomRoles.Recruit, "#00b4eb" }, - { CustomRoles.Glow, "#E2F147" }, - { CustomRoles.Diseased, "#AAAAAA" }, - { CustomRoles.Antidote, "#FF9876" }, - { CustomRoles.Swift, "#ff1919" }, - { CustomRoles.Mare, "#ff1919" }, - - // SoloKombat - { CustomRoles.KB_Normal, "#f55252" }, - // FFA - { CustomRoles.Killer, "#00ffff" }, - // Move And Stop - { CustomRoles.Tasker, "#00ffa5" }, - // Hot Potato - { CustomRoles.Potato, "#e8cd46" }, - // Speedrun - { CustomRoles.Runner, "#800080" }, - // Capture The Flag - { CustomRoles.CTFPlayer, "#1313c2" }, - // Natural Disaster - { CustomRoles.NDPlayer, "#03fc4a" }, - // Room Rush - { CustomRoles.RRPlayer, "#ffab1b" }, - // Hide And Seek - { CustomRoles.Seeker, "#ff1919" }, - { CustomRoles.Hider, "#345eeb" }, - { CustomRoles.Fox, "#00ff00" }, - { CustomRoles.Troll, "#ff00ff" }, - { CustomRoles.Jumper, "#ddf542" }, - { CustomRoles.Detector, "#42ddf5" }, - { CustomRoles.Jet, "#42f54b" }, - { CustomRoles.Dasher, "#f542b0" }, - { CustomRoles.Locator, "#f59e42" }, - { CustomRoles.Venter, "#694141" }, - { CustomRoles.Agent, "#ff8f8f" }, - { CustomRoles.Taskinator, "#561dd1" } - }; - - Enum.GetValues().Where(x => x.GetCustomRoleTypes() == CustomRoleTypes.Impostor).Do(x => RoleColors.TryAdd(x, "#ff1919")); - } - catch (ArgumentException ex) - { - Logger.Error("错误:字典出现重复项", "LoadDictionary"); - Logger.Exception(ex, "LoadDictionary"); - HasArgumentException = true; - } - catch (Exception ex) - { - Logger.Fatal(ex.ToString(), "Main"); + result[i++] = pc; } - CustomWinnerHolder.Reset(); - Translator.Init(); - BanManager.Init(); - TemplateManager.Init(); - SpamManager.Init(); - DevManager.Init(); - Cloud.Init(); + if (i == 0) return []; - IRandom.SetInstance(new NetRandomWrapper()); - - Logger.Info($"{Application.version}", "AmongUs Version"); - - LogHandler handler = Logger.Handler("GitVersion"); - handler.Info($"{nameof(ThisAssembly.Git.BaseTag)}: {ThisAssembly.Git.BaseTag}"); - handler.Info($"{nameof(ThisAssembly.Git.Commit)}: {ThisAssembly.Git.Commit}"); - handler.Info($"{nameof(ThisAssembly.Git.Commits)}: {ThisAssembly.Git.Commits}"); - handler.Info($"{nameof(ThisAssembly.Git.IsDirty)}: {ThisAssembly.Git.IsDirty}"); - handler.Info($"{nameof(ThisAssembly.Git.Sha)}: {ThisAssembly.Git.Sha}"); - handler.Info($"{nameof(ThisAssembly.Git.Tag)}: {ThisAssembly.Git.Tag}"); - - ClassInjector.RegisterTypeInIl2Cpp(); - ClassInjector.RegisterTypeInIl2Cpp(); - ClassInjector.RegisterTypeInIl2Cpp(); - ClassInjector.RegisterTypeInIl2Cpp(); - - NormalGameOptionsV08.RecommendedImpostors = NormalGameOptionsV08.MaxImpostors = Enumerable.Repeat(127, 127).ToArray(); - NormalGameOptionsV08.MinPlayers = Enumerable.Repeat(4, 127).ToArray(); - HideNSeekGameOptionsV08.MinPlayers = Enumerable.Repeat(4, 127).ToArray(); - - CustomLogger.ClearLog(); - LogMonitor.StartMonitoring(); - - Harmony.PatchAll(); - - if (!DebugModeManager.AmDebugger) - ConsoleManager.DetachConsole(); - else - ConsoleManager.CreateConsole(); - - Logger.Msg("========= EHR loaded! =========", "Plugin Load"); - } - - public static void LoadRoleClasses() - { - AllRoleClasses = []; - - try - { - AllRoleClasses.AddRange(Assembly.GetAssembly(typeof(RoleBase))! - .GetTypes() - .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(RoleBase))) - .Select(t => (RoleBase)Activator.CreateInstance(t, null))); - - AllRoleClasses.Sort(); - } - catch (Exception e) - { - Utils.ThrowException(e); - } - } - - public void StartCoroutine(IEnumerator coroutine) - { - if (coroutine == null) return; - - coroutines.StartCoroutine(coroutine.WrapToIl2Cpp()); - } - - public void StopCoroutine(IEnumerator coroutine) - { - if (coroutine == null) return; - - coroutines.StopCoroutine(coroutine.WrapToIl2Cpp()); - } - - public void StopAllCoroutines() - { - coroutines.StopAllCoroutines(); + Array.Resize(ref result, i); + return result; } } - [Flags] - public enum Team + public static PlayerControl[] AllAlivePlayerControls { - None = 0, - Impostor = 1, - Neutral = 2, - Crewmate = 4 + get + { + int count = PlayerControl.AllPlayerControls.Count; + var result = new PlayerControl[count]; + var i = 0; - /* - * Impostor | Neutral = 3 - * Impostor | Crewmate = 5 - * Neutral | Crewmate = 6 - * Impostor | Neutral | Crewmate = 7 - */ + foreach (PlayerControl pc in PlayerControl.AllPlayerControls) + { + if (pc == null || pc.PlayerId == 255 || !pc.IsAlive() || pc.Data.Disconnected || Pelican.IsEaten(pc.PlayerId)) continue; + + result[i++] = pc; + } + + if (i == 0) return []; + + Array.Resize(ref result, i); + return result; + } } + // ReSharper disable once InconsistentNaming + public static string Get_TName_Snacks => TranslationController.Instance.currentLanguage.languageID is SupportedLangs.SChinese or SupportedLangs.TChinese ? NameSnacksCn.RandomElement() : NameSnacksEn.RandomElement(); + + public static NetworkedPlayerInfo LastVotedPlayerInfo { get; set; } + + public static MapNames CurrentMap => (MapNames)NormalOptions.MapId; + + public override void Load() + { + Instance = this; + + //Client Options + HideName = Config.Bind("Client Options", "Hide Game Code Name", "EHR"); + HideColor = Config.Bind("Client Options", "Hide Game Code Color", $"{ModColor}"); + DebugKeyInput = Config.Bind("Authentication", "Debug Key", string.Empty); + AutoStart = Config.Bind("Client Options", "AutoStart", false); + GM = Config.Bind("Client Options", "GM", false); + UnlockFps = Config.Bind("Client Options", "UnlockFPS", false); + AutoStart = Config.Bind("Client Options", "AutoStart", false); + ForceOwnLanguage = Config.Bind("Client Options", "ForceOwnLanguage", false); + ForceOwnLanguageRoleName = Config.Bind("Client Options", "ForceOwnLanguageRoleName", false); + EnableCustomButton = Config.Bind("Client Options", "EnableCustomButton", true); + EnableCustomSoundEffect = Config.Bind("Client Options", "EnableCustomSoundEffect", true); + SwitchVanilla = Config.Bind("Client Options", "SwitchVanilla", false); + GodMode = Config.Bind("Client Options", "GodMode", false); + DarkTheme = Config.Bind("Client Options", "DarkTheme", true); + HorseMode = Config.Bind("Client Options", "HorseMode", false); + LongMode = Config.Bind("Client Options", "LongMode", false); + ShowPlayerInfoInLobby = Config.Bind("Client Options", "ShowPlayerInfoInLobby", false); + LobbyMusic = Config.Bind("Client Options", "LobbyMusic", false); + EnableCommandHelper = Config.Bind("Client Options", "EnableCommandHelper", true); + + //Logger = BepInEx.Logging.Logger.CreateLogSource("EHR"); + coroutines = AddComponent(); + Logger.Enable(); + Logger.Disable("NotifyRoles"); + Logger.Disable("SwitchSystem"); + Logger.Disable("ModNews"); + Logger.Disable("CustomRpcSender"); + + if (!DebugModeManager.AmDebugger) + { + Logger.Disable("2018k"); + Logger.Disable("Github"); + //EHR.Logger.Disable("ReceiveRPC"); + Logger.Disable("SendRPC"); + Logger.Disable("SetRole"); + Logger.Disable("Info.Role"); + Logger.Disable("TaskState.Init"); + //EHR.Logger.Disable("Vote"); + Logger.Disable("RpcSetNamePrivate"); + //EHR.Logger.Disable("SendChat"); + Logger.Disable("SetName"); + //EHR.Logger.Disable("AssignRoles"); + //EHR.Logger.Disable("RepairSystem"); + //EHR.Logger.Disable("MurderPlayer"); + //EHR.Logger.Disable("CheckMurder"); + Logger.Disable("PlayerControl.RpcSetRole"); + Logger.Disable("SyncCustomSettings"); + } + //EHR.Logger.isDetail = true; + + // Authentication related - Initialization + DebugKeyAuth = new(DebugKeyHash, DebugKeySalt); + + DebugModeManager.Auth(DebugKeyAuth, DebugKeyInput.Value); + + Preset1 = Config.Bind("Preset Name Options", "Preset1", "Preset_1"); + Preset2 = Config.Bind("Preset Name Options", "Preset2", "Preset_2"); + Preset3 = Config.Bind("Preset Name Options", "Preset3", "Preset_3"); + Preset4 = Config.Bind("Preset Name Options", "Preset4", "Preset_4"); + Preset5 = Config.Bind("Preset Name Options", "Preset5", "Preset_5"); + Preset6 = Config.Bind("Preset Name Options", "Preset6", "Preset_6"); + Preset7 = Config.Bind("Preset Name Options", "Preset7", "Preset_7"); + Preset8 = Config.Bind("Preset Name Options", "Preset8", "Preset_8"); + Preset9 = Config.Bind("Preset Name Options", "Preset9", "Preset_9"); + Preset10 = Config.Bind("Preset Name Options", "Preset10", "Preset_10"); + WebhookUrl = Config.Bind("Other", "WebhookURL", "none"); + BetaBuildUrl = Config.Bind("Other", "BetaBuildURL", string.Empty); + MessageWait = Config.Bind("Other", "MessageWait", 0); + LastKillCooldown = Config.Bind("Other", "LastKillCooldown", (float)30); + LastShapeshifterCooldown = Config.Bind("Other", "LastShapeshifterCooldown", (float)30); + + HasArgumentException = false; + + try + { + RoleColors = new() + { + // Vanilla + { CustomRoles.Crewmate, "#8cffff" }, + { CustomRoles.Engineer, "#FF6A00" }, + { CustomRoles.Scientist, "#8ee98e" }, + { CustomRoles.GuardianAngel, "#77e6d1" }, + { CustomRoles.Tracker, "#34ad50" }, + { CustomRoles.Noisemaker, "#ff4a62" }, + // Vanilla Remakes + { CustomRoles.CrewmateEHR, "#8cffff" }, + { CustomRoles.EngineerEHR, "#FF6A00" }, + { CustomRoles.ScientistEHR, "#8ee98e" }, + { CustomRoles.GuardianAngelEHR, "#77e6d1" }, + { CustomRoles.TrackerEHR, "#34ad50" }, + { CustomRoles.NoisemakerEHR, "#ff4a62" }, + // Crewmates + { CustomRoles.Luckey, "#b8d7a3" }, + { CustomRoles.Needy, "#a4dffe" }, + { CustomRoles.SabotageMaster, "#3333ff" }, + { CustomRoles.Snitch, "#b8fb4f" }, + { CustomRoles.Marshall, "#5573aa" }, + { CustomRoles.Mayor, "#204d42" }, + { CustomRoles.Paranoia, "#c993f5" }, + { CustomRoles.Psychic, "#6F698C" }, + { CustomRoles.Sheriff, "#ffb347" }, + { CustomRoles.CopyCat, "#ffb2ab" }, + { CustomRoles.SuperStar, "#f6f657" }, + { CustomRoles.CyberStar, "#ee4a55" }, + { CustomRoles.Ventguard, "#ffa5ff" }, + { CustomRoles.Demolitionist, "#5e2801" }, + { CustomRoles.Express, "#00ffff" }, + { CustomRoles.NiceEraser, "#00a5ff" }, + { CustomRoles.TaskManager, "#00ffa5" }, + { CustomRoles.Adventurer, "#fbbb04" }, + { CustomRoles.Randomizer, "#fcba03" }, + { CustomRoles.Beacon, "#a3fdff" }, + { CustomRoles.Rabbit, "#88d2ff" }, + { CustomRoles.Shiftguard, "#eb34cc" }, + { CustomRoles.Mole, "#00ff80" }, + { CustomRoles.Markseeker, "#f2a0f1" }, + { CustomRoles.Sentinel, "#4bc8d6" }, + { CustomRoles.Electric, "#fbff00" }, + { CustomRoles.Philantropist, "#e3b384" }, + { CustomRoles.Tornado, "#303030" }, + { CustomRoles.Dad, "#037bfc" }, + { CustomRoles.Insight, "#26ff38" }, + { CustomRoles.Tunneler, "#543232" }, + { CustomRoles.Detour, "#ffd35c" }, + { CustomRoles.Gaulois, "#42d1f5" }, + { CustomRoles.Druid, "#ffb694" }, + { CustomRoles.Catcher, "#aaf2cf" }, + { CustomRoles.Autocrat, "#e2ed64" }, + { CustomRoles.LovingCrewmate, "#ff9ace" }, + { CustomRoles.LovingImpostor, "#ff9ace" }, + { CustomRoles.ToiletMaster, "#4281f5" }, + { CustomRoles.Goose, "#f9ffb8" }, + { CustomRoles.Sentry, "#db55f2" }, + { CustomRoles.Perceiver, "#ebeb34" }, + { CustomRoles.Convener, "#34eb7a" }, + { CustomRoles.Mathematician, "#eb3474" }, + { CustomRoles.Transmitter, "#c9a11e" }, + { CustomRoles.Ankylosaurus, "#7FE44C" }, + { CustomRoles.Leery, "#32a852" }, + { CustomRoles.Wizard, "#FD05CC" }, + { CustomRoles.Negotiator, "#00c3ff" }, + { CustomRoles.Grappler, "#befc03" }, + { CustomRoles.Journalist, "#fcba03" }, + { CustomRoles.Whisperer, "#82919e" }, + { CustomRoles.Car, "#6e6e6e" }, + { CustomRoles.President, "#30916f" }, + { CustomRoles.Oxyman, "#ffa58c" }, + { CustomRoles.Rhapsode, "#f5ad42" }, + { CustomRoles.Chef, "#d6d6ff" }, + { CustomRoles.Lyncher, "#14ba7d" }, + { CustomRoles.Socialite, "#32a8a8" }, + { CustomRoles.Adrenaline, "#ffff00" }, + { CustomRoles.Safeguard, "#4949e3" }, + { CustomRoles.Clairvoyant, "#d4ffdd" }, + { CustomRoles.Inquirer, "#7c55f2" }, + { CustomRoles.Soothsayer, "#4e529c" }, + { CustomRoles.Telekinetic, "#d6c618" }, + { CustomRoles.Doppelganger, "#f6f4a3" }, + { CustomRoles.Nightmare, "#1e1247" }, + { CustomRoles.Altruist, "#300000" }, + { CustomRoles.Bane, "#745da3" }, + { CustomRoles.Benefactor, "#4aeaff" }, + { CustomRoles.GuessManagerRole, "#d4ff00" }, + { CustomRoles.Drainer, "#149627" }, + { CustomRoles.NiceHacker, "#75fa4c" }, + { CustomRoles.Aid, "#D7BDE2" }, + { CustomRoles.DonutDelivery, "#a46efa" }, + { CustomRoles.Analyst, "#33ddff" }, + { CustomRoles.Escort, "#ff94e6" }, + { CustomRoles.Spy, "#34495E" }, + { CustomRoles.Doormaster, "#7FB3D5" }, + { CustomRoles.Tether, "#138D75" }, + { CustomRoles.Ricochet, "#EDBB99" }, + { CustomRoles.SpeedBooster, "#00ffff" }, + { CustomRoles.Doctor, "#80ffdd" }, + { CustomRoles.Dictator, "#df9b00" }, + { CustomRoles.Detective, "#7160e8" }, + { CustomRoles.NiceGuesser, "#f0e68c" }, + { CustomRoles.SwordsMan, "#7a7a7a" }, + { CustomRoles.Transporter, "#42D1FF" }, + { CustomRoles.TimeManager, "#6495ed" }, + { CustomRoles.Veteran, "#a77738" }, + { CustomRoles.Bodyguard, "#185abd" }, + { CustomRoles.Witness, "#e70052" }, + { CustomRoles.Lookout, "#2a52be" }, + { CustomRoles.Grenadier, "#3c4a16" }, + { CustomRoles.Lighter, "#eee5be" }, + { CustomRoles.SecurityGuard, "#c3b25f" }, + { CustomRoles.Medic, "#00ff97" }, + { CustomRoles.Divinator, "#882c83" }, + { CustomRoles.Glitch, "#39FF14" }, + { CustomRoles.Judge, "#f8d85a" }, + { CustomRoles.Mortician, "#333c49" }, + { CustomRoles.Mediumshiper, "#a200ff" }, + { CustomRoles.Observer, "#a8e0fa" }, + { CustomRoles.DovesOfNeace, "#ffffff" }, + { CustomRoles.Jailor, "#aa900d" }, + { CustomRoles.Monarch, "#FFA500" }, + { CustomRoles.Bloodhound, "#8B0000" }, + { CustomRoles.Enigma, "#676798" }, + { CustomRoles.Scout, "#3CB371" }, + { CustomRoles.CameraMan, "#000930" }, + { CustomRoles.Merchant, "#D27D2D" }, + { CustomRoles.Monitor, "#7223DA" }, + { CustomRoles.Deputy, "#df9026" }, + { CustomRoles.Cleanser, "#98FF98" }, + { CustomRoles.NiceSwapper, "#922348" }, + { CustomRoles.Ignitor, "#ffffa5" }, + { CustomRoles.Guardian, "#2E8B57" }, + { CustomRoles.Addict, "#008000" }, + { CustomRoles.Alchemist, "#e6d798" }, + { CustomRoles.Tracefinder, "#0066CC" }, + { CustomRoles.Oracle, "#6666FF" }, + { CustomRoles.Spiritualist, "#669999" }, + { CustomRoles.Chameleon, "#01C834" }, + { CustomRoles.ParityCop, "#0D57AF" }, + { CustomRoles.TimeMaster, "#44baff" }, + { CustomRoles.Crusader, "#C65C39" }, + { CustomRoles.Speedrunner, "#800080" }, + // Neutrals + { CustomRoles.Arsonist, "#ff6633" }, + { CustomRoles.Pyromaniac, "#ff6633" }, + { CustomRoles.PlagueBearer, "#e5f6b4" }, + { CustomRoles.Pestilence, "#343136" }, + { CustomRoles.Jester, "#ec62a5" }, + { CustomRoles.Terrorist, "#00e600" }, + { CustomRoles.Executioner, "#611c3a" }, + { CustomRoles.Lawyer, "#008080" }, + { CustomRoles.God, "#f96464" }, + { CustomRoles.Opportunist, "#4dff4d" }, + { CustomRoles.Mario, "#ff6201" }, + { CustomRoles.Jackal, "#00b4eb" }, + { CustomRoles.Sidekick, "#00b4eb" }, + { CustomRoles.Innocent, "#8f815e" }, + { CustomRoles.Pelican, "#34c84b" }, + { CustomRoles.Revolutionist, "#ba4d06" }, + { CustomRoles.FFF, "#414b66" }, + { CustomRoles.Konan, "#4d4dff" }, + { CustomRoles.Gamer, "#68bc71" }, + { CustomRoles.DarkHide, "#483d8b" }, + { CustomRoles.Workaholic, "#008b8b" }, + { CustomRoles.Collector, "#9d8892" }, + { CustomRoles.NecroGuesser, "#F6FE03" }, + { CustomRoles.Provocateur, "#74ba43" }, + { CustomRoles.Sunnyboy, "#ff9902" }, + { CustomRoles.Poisoner, "#e70052" }, + { CustomRoles.Totocalcio, "#ff9409" }, + { CustomRoles.Romantic, "#FF1493" }, + { CustomRoles.VengefulRomantic, "#ba2749" }, + { CustomRoles.RuthlessRomantic, "#D2691E" }, + { CustomRoles.Succubus, "#cf6acd" }, + { CustomRoles.Necromancer, "#f7adcf" }, + { CustomRoles.Deathknight, "#361d12" }, + { CustomRoles.HexMaster, "#ff00ff" }, + { CustomRoles.Wraith, "#4B0082" }, + { CustomRoles.NSerialKiller, "#233fcc" }, + { CustomRoles.Weatherman, "#347deb" }, + { CustomRoles.NoteKiller, "#4CA8E4" }, + { CustomRoles.Vortex, "#a83293" }, + { CustomRoles.Beehive, "#ffff00" }, + { CustomRoles.RouleteGrandeur, "#a88332" }, + { CustomRoles.Nonplus, "#09632f" }, + { CustomRoles.Tremor, "#e942f5" }, + { CustomRoles.Evolver, "#f2c444" }, + { CustomRoles.Rogue, "#7a629c" }, + { CustomRoles.Patroller, "#c1cc27" }, + { CustomRoles.Simon, "#c4b8ff" }, + { CustomRoles.Chemist, "#4287f5" }, + { CustomRoles.Samurai, "#73495c" }, + { CustomRoles.QuizMaster, "#CF2472" }, + { CustomRoles.Bargainer, "#4f2f36" }, + { CustomRoles.Tiger, "#fcba03" }, + { CustomRoles.SoulHunter, "#3f2c61" }, + { CustomRoles.Enderman, "#3c008a" }, + { CustomRoles.Mycologist, "#0043de" }, + { CustomRoles.Bubble, "#ff38c3" }, + { CustomRoles.Hookshot, "#32a852" }, + { CustomRoles.Sprayer, "#ffc038" }, + { CustomRoles.PlagueDoctor, "#ff6633" }, + { CustomRoles.Curser, "#510c91" }, + { CustomRoles.Postman, "#00b893" }, + { CustomRoles.SchrodingersCat, "#616161" }, + { CustomRoles.Shifter, "#777777" }, + { CustomRoles.Impartial, "#4287f5" }, + { CustomRoles.Gaslighter, "#b6aa82" }, + { CustomRoles.Tank, "#176320" }, + { CustomRoles.Technician, "#4e96f5" }, + { CustomRoles.Backstabber, "#fcba03" }, + { CustomRoles.Predator, "#c73906" }, + { CustomRoles.Reckless, "#6e000d" }, + { CustomRoles.Magician, "#BF5FFF" }, + { CustomRoles.WeaponMaster, "#6f02bd" }, + { CustomRoles.Eclipse, "#0E6655" }, + { CustomRoles.Vengeance, "#33cccc" }, + { CustomRoles.HeadHunter, "#ffcc66" }, + { CustomRoles.Imitator, "#ff00a5" }, + { CustomRoles.Werewolf, "#964B00" }, + { CustomRoles.Bandit, "#8B008B" }, + { CustomRoles.Agitater, "#F4A460" }, + { CustomRoles.BloodKnight, "#630000" }, + { CustomRoles.Juggernaut, "#A41342" }, + { CustomRoles.Cherokious, "#de4b9e" }, + { CustomRoles.Parasite, "#ff1919" }, + { CustomRoles.Crewpostor, "#ff1919" }, + { CustomRoles.Refugee, "#ff1919" }, + { CustomRoles.Virus, "#2E8B57" }, + { CustomRoles.Farseer, "#BA55D3" }, + { CustomRoles.Pursuer, "#617218" }, + { CustomRoles.Phantasm, "#662962" }, + { CustomRoles.Jinx, "#ed2f91" }, + { CustomRoles.Maverick, "#781717" }, + { CustomRoles.Ritualist, "#663399" }, + { CustomRoles.Pickpocket, "#47008B" }, + { CustomRoles.Traitor, "#BA2E05" }, + { CustomRoles.Vulture, "#556B2F" }, + { CustomRoles.Medusa, "#9900CC" }, + { CustomRoles.Spiritcaller, "#003366" }, + { CustomRoles.EvilSpirit, "#003366" }, + { CustomRoles.Convict, "#ff1919" }, + { CustomRoles.Amnesiac, "#7FBFFF" }, + { CustomRoles.Doomsayer, "#14f786" }, + // Ghost roles + { CustomRoles.Warden, "#32a852" }, + { CustomRoles.Minion, "#ff1919" }, + { CustomRoles.Specter, "#b446e3" }, + { CustomRoles.Haunter, "#d1b1de" }, + { CustomRoles.Bloodmoon, "#ff1313" }, + { CustomRoles.GA, "#8cffff" }, + // GM + { CustomRoles.GM, "#ff5b70" }, + // Add-ons + { CustomRoles.NotAssigned, "#ffffff" }, + { CustomRoles.LastImpostor, "#ff1919" }, + { CustomRoles.Lovers, "#ff9ace" }, + { CustomRoles.Bloodlust, "#630000" }, + { CustomRoles.Madmate, "#ff1919" }, + { CustomRoles.Watcher, "#800080" }, + { CustomRoles.Sleuth, "#30221c" }, + { CustomRoles.Energetic, "#ffff00" }, + { CustomRoles.Messenger, "#28b573" }, + { CustomRoles.Dynamo, "#ebe534" }, + { CustomRoles.AntiTP, "#fcba03" }, + { CustomRoles.Fragile, "#debe66" }, + { CustomRoles.Allergic, "#e3bd56" }, + { CustomRoles.Introvert, "#6293e3" }, + { CustomRoles.Deadlined, "#ffa500" }, + { CustomRoles.Rookie, "#bf671f" }, + { CustomRoles.Trainee, "#4287f5" }, + { CustomRoles.Taskcounter, "#ff1919" }, + { CustomRoles.Stained, "#e6bf91" }, + { CustomRoles.Clumsy, "#b8b8b8" }, + { CustomRoles.Mischievous, "#30221c" }, + { CustomRoles.Flashman, "#ff8400" }, + { CustomRoles.Haste, "#f0ec22" }, + { CustomRoles.Busy, "#32a852" }, + { CustomRoles.Sleep, "#000000" }, + { CustomRoles.Truant, "#eb3467" }, + { CustomRoles.Disco, "#eb34e8" }, + { CustomRoles.Sonar, "#b8fffe" }, + { CustomRoles.Asthmatic, "#8feb34" }, + { CustomRoles.Giant, "#32a852" }, + { CustomRoles.Nimble, "#feffc7" }, + { CustomRoles.Physicist, "#87e9ff" }, + { CustomRoles.Finder, "#32a879" }, + { CustomRoles.Noisy, "#e34fb2" }, + { CustomRoles.Torch, "#eee5be" }, + { CustomRoles.Seer, "#61b26c" }, + { CustomRoles.Brakar, "#1447af" }, + { CustomRoles.Oblivious, "#424242" }, + { CustomRoles.Bewilder, "#c894f5" }, + { CustomRoles.Spurt, "#c9e8f5" }, + { CustomRoles.Sunglasses, "#E7C12B" }, + { CustomRoles.Workhorse, "#00ffff" }, + { CustomRoles.Undead, "#ed9abd" }, + { CustomRoles.Cleansed, "#98FF98" }, + { CustomRoles.Fool, "#e6e7ff" }, + { CustomRoles.Avanger, "#ffab1c" }, + { CustomRoles.Youtuber, "#fb749b" }, + { CustomRoles.Egoist, "#5600ff" }, + { CustomRoles.TicketsStealer, "#ff1919" }, + { CustomRoles.DualPersonality, "#3a648f" }, + { CustomRoles.Mimic, "#ff1919" }, + { CustomRoles.Guesser, "#f8cd46" }, + { CustomRoles.Necroview, "#663399" }, + { CustomRoles.Reach, "#74ba43" }, + { CustomRoles.Magnet, "#eb3477" }, + { CustomRoles.DeadlyQuota, "#ff1919" }, + { CustomRoles.Circumvent, "#ff1919" }, + { CustomRoles.Damocles, "#ff1919" }, + { CustomRoles.Stressed, "#9403fc" }, + { CustomRoles.Charmed, "#cf6acd" }, + { CustomRoles.Bait, "#00f7ff" }, + { CustomRoles.Trapper, "#5a8fd0" }, + { CustomRoles.Onbound, "#BAAAE9" }, + { CustomRoles.Knighted, "#FFA500" }, + { CustomRoles.Contagious, "#2E8B57" }, + { CustomRoles.Unreportable, "#FF6347" }, + { CustomRoles.Lucky, "#b8d7a3" }, + { CustomRoles.Unlucky, "#d7a3a3" }, + { CustomRoles.DoubleShot, "#19fa8d" }, + { CustomRoles.Rascal, "#990000" }, + { CustomRoles.Gravestone, "#2EA8E7" }, + { CustomRoles.Lazy, "#a4dffe" }, + { CustomRoles.Autopsy, "#80ffdd" }, + { CustomRoles.Loyal, "#B71556" }, + { CustomRoles.Visionary, "#ff1919" }, + { CustomRoles.Recruit, "#00b4eb" }, + { CustomRoles.Glow, "#E2F147" }, + { CustomRoles.Diseased, "#AAAAAA" }, + { CustomRoles.Antidote, "#FF9876" }, + { CustomRoles.Swift, "#ff1919" }, + { CustomRoles.Mare, "#ff1919" }, + + // SoloKombat + { CustomRoles.KB_Normal, "#f55252" }, + // FFA + { CustomRoles.Killer, "#00ffff" }, + // Move And Stop + { CustomRoles.Tasker, "#00ffa5" }, + // Hot Potato + { CustomRoles.Potato, "#e8cd46" }, + // Speedrun + { CustomRoles.Runner, "#800080" }, + // Capture The Flag + { CustomRoles.CTFPlayer, "#1313c2" }, + // Natural Disaster + { CustomRoles.NDPlayer, "#03fc4a" }, + // Room Rush + { CustomRoles.RRPlayer, "#ffab1b" }, + // Hide And Seek + { CustomRoles.Seeker, "#ff1919" }, + { CustomRoles.Hider, "#345eeb" }, + { CustomRoles.Fox, "#00ff00" }, + { CustomRoles.Troll, "#ff00ff" }, + { CustomRoles.Jumper, "#ddf542" }, + { CustomRoles.Detector, "#42ddf5" }, + { CustomRoles.Jet, "#42f54b" }, + { CustomRoles.Dasher, "#f542b0" }, + { CustomRoles.Locator, "#f59e42" }, + { CustomRoles.Venter, "#694141" }, + { CustomRoles.Agent, "#ff8f8f" }, + { CustomRoles.Taskinator, "#561dd1" } + }; + + Enum.GetValues().Where(x => x.GetCustomRoleTypes() == CustomRoleTypes.Impostor).Do(x => RoleColors.TryAdd(x, "#ff1919")); + } + catch (ArgumentException ex) + { + Logger.Error("错误:字典出现重复项", "LoadDictionary"); + Logger.Exception(ex, "LoadDictionary"); + HasArgumentException = true; + } + catch (Exception ex) { Logger.Fatal(ex.ToString(), "Main"); } + + CustomWinnerHolder.Reset(); + Translator.Init(); + BanManager.Init(); + TemplateManager.Init(); + SpamManager.Init(); + DevManager.Init(); + Cloud.Init(); + + IRandom.SetInstance(new NetRandomWrapper()); + + Logger.Info($"{Application.version}", "AmongUs Version"); + + LogHandler handler = Logger.Handler("GitVersion"); + handler.Info($"{nameof(ThisAssembly.Git.BaseTag)}: {ThisAssembly.Git.BaseTag}"); + handler.Info($"{nameof(ThisAssembly.Git.Commit)}: {ThisAssembly.Git.Commit}"); + handler.Info($"{nameof(ThisAssembly.Git.Commits)}: {ThisAssembly.Git.Commits}"); + handler.Info($"{nameof(ThisAssembly.Git.IsDirty)}: {ThisAssembly.Git.IsDirty}"); + handler.Info($"{nameof(ThisAssembly.Git.Sha)}: {ThisAssembly.Git.Sha}"); + handler.Info($"{nameof(ThisAssembly.Git.Tag)}: {ThisAssembly.Git.Tag}"); + + ClassInjector.RegisterTypeInIl2Cpp(); + ClassInjector.RegisterTypeInIl2Cpp(); + ClassInjector.RegisterTypeInIl2Cpp(); + ClassInjector.RegisterTypeInIl2Cpp(); + + NormalGameOptionsV08.RecommendedImpostors = NormalGameOptionsV08.MaxImpostors = Enumerable.Repeat(127, 127).ToArray(); + NormalGameOptionsV08.MinPlayers = Enumerable.Repeat(4, 127).ToArray(); + HideNSeekGameOptionsV08.MinPlayers = Enumerable.Repeat(4, 127).ToArray(); + + CustomLogger.ClearLog(); + LogMonitor.StartMonitoring(); + + Harmony.PatchAll(); + + if (!DebugModeManager.AmDebugger) + ConsoleManager.DetachConsole(); + else + ConsoleManager.CreateConsole(); + + Logger.Msg("========= EHR loaded! =========", "Plugin Load"); + } + + public static void LoadRoleClasses() + { + AllRoleClasses = []; + + try + { + AllRoleClasses.AddRange(Assembly.GetAssembly(typeof(RoleBase))! + .GetTypes() + .Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(RoleBase))) + .Select(t => (RoleBase)Activator.CreateInstance(t, null))); + + AllRoleClasses.Sort(); + } + catch (Exception e) { Utils.ThrowException(e); } + } + + public void StartCoroutine(IEnumerator coroutine) + { + if (coroutine == null) return; + + coroutines.StartCoroutine(coroutine.WrapToIl2Cpp()); + } + + public void StopCoroutine(IEnumerator coroutine) + { + if (coroutine == null) return; + + coroutines.StopCoroutine(coroutine.WrapToIl2Cpp()); + } + + public void StopAllCoroutines() + { + coroutines.StopAllCoroutines(); + } +} + +[Flags] +public enum Team +{ + None = 0, + Impostor = 1, + Neutral = 2, + Crewmate = 4 + + /* + * Impostor | Neutral = 3 + * Impostor | Crewmate = 5 + * Neutral | Crewmate = 6 + * Impostor | Neutral | Crewmate = 7 + */ +} + #pragma warning disable IDE0079 // Remove unnecessary suppression - [SuppressMessage("ReSharper", "UnusedMember.Global")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] #pragma warning restore IDE0079 // Remove unnecessary suppression - public enum CustomWinner - { - Draw = -1, - Default = -2, - None = -3, - Error = -4, - Neutrals = -5, +public enum CustomWinner +{ + Draw = -1, + Default = -2, + None = -3, + Error = -4, + Neutrals = -5, - // CTA - CustomTeam = -6, + // CTA + CustomTeam = -6, - // Hide And Seek - Hider = CustomRoles.Hider, - Seeker = CustomRoles.Seeker, - Troll = CustomRoles.Troll, - Taskinator = CustomRoles.Taskinator, + // Hide And Seek + Hider = CustomRoles.Hider, + Seeker = CustomRoles.Seeker, + Troll = CustomRoles.Troll, + Taskinator = CustomRoles.Taskinator, - // Standard - Impostor = CustomRoles.Impostor, - Crewmate = CustomRoles.Crewmate, - Jester = CustomRoles.Jester, - Terrorist = CustomRoles.Terrorist, - Lovers = CustomRoles.Lovers, - Executioner = CustomRoles.Executioner, - Arsonist = CustomRoles.Arsonist, - Revolutionist = CustomRoles.Revolutionist, - Technician = CustomRoles.Technician, - Jackal = CustomRoles.Jackal, - God = CustomRoles.God, - Mario = CustomRoles.Mario, - Innocent = CustomRoles.Innocent, - Pelican = CustomRoles.Pelican, - Youtuber = CustomRoles.Youtuber, - Egoist = CustomRoles.Egoist, - Gamer = CustomRoles.Gamer, - DarkHide = CustomRoles.DarkHide, - Workaholic = CustomRoles.Workaholic, - Collector = CustomRoles.Collector, - NecroGuesser = CustomRoles.NecroGuesser, - BloodKnight = CustomRoles.BloodKnight, - Poisoner = CustomRoles.Poisoner, - HexMaster = CustomRoles.HexMaster, - Succubus = CustomRoles.Succubus, - Necromancer = CustomRoles.Necromancer, - Wraith = CustomRoles.Wraith, - SerialKiller = CustomRoles.NSerialKiller, - Weatherman = CustomRoles.Weatherman, - NoteKiller = CustomRoles.NoteKiller, - Vortex = CustomRoles.Vortex, - Beehive = CustomRoles.Beehive, - RouleteGrandeur = CustomRoles.RouleteGrandeur, - Nonplus = CustomRoles.Nonplus, - Tremor = CustomRoles.Tremor, - Evolver = CustomRoles.Evolver, - Rogue = CustomRoles.Rogue, - Patroller = CustomRoles.Patroller, - Simon = CustomRoles.Simon, - Chemist = CustomRoles.Chemist, - Samurai = CustomRoles.Samurai, - QuizMaster = CustomRoles.QuizMaster, - Bargainer = CustomRoles.Bargainer, - Tiger = CustomRoles.Tiger, - Enderman = CustomRoles.Enderman, - Mycologist = CustomRoles.Mycologist, - Bubble = CustomRoles.Bubble, - Hookshot = CustomRoles.Hookshot, - Sprayer = CustomRoles.Sprayer, - PlagueDoctor = CustomRoles.PlagueDoctor, - Reckless = CustomRoles.Reckless, - Magician = CustomRoles.Magician, - WeaponMaster = CustomRoles.WeaponMaster, - Pyromaniac = CustomRoles.Pyromaniac, - Eclipse = CustomRoles.Eclipse, - HeadHunter = CustomRoles.HeadHunter, - Agitater = CustomRoles.Agitater, - Vengeance = CustomRoles.Vengeance, - Werewolf = CustomRoles.Werewolf, - Juggernaut = CustomRoles.Juggernaut, - Bandit = CustomRoles.Bandit, - Virus = CustomRoles.Virus, - Phantom = CustomRoles.Phantasm, - Jinx = CustomRoles.Jinx, - Ritualist = CustomRoles.Ritualist, - Pickpocket = CustomRoles.Pickpocket, - Traitor = CustomRoles.Traitor, - Vulture = CustomRoles.Vulture, - Pestilence = CustomRoles.Pestilence, - Medusa = CustomRoles.Medusa, - Spiritcaller = CustomRoles.Spiritcaller, - Glitch = CustomRoles.Glitch, - Plaguebearer = CustomRoles.PlagueBearer, - Doomsayer = CustomRoles.Doomsayer, - RuthlessRomantic = CustomRoles.RuthlessRomantic, - Doppelganger = CustomRoles.Doppelganger, - Imitator = CustomRoles.Imitator, - Cherokious = CustomRoles.Cherokious, - Specter = CustomRoles.Specter, + // Standard + Impostor = CustomRoles.Impostor, + Crewmate = CustomRoles.Crewmate, + Jester = CustomRoles.Jester, + Terrorist = CustomRoles.Terrorist, + Lovers = CustomRoles.Lovers, + Executioner = CustomRoles.Executioner, + Arsonist = CustomRoles.Arsonist, + Revolutionist = CustomRoles.Revolutionist, + Technician = CustomRoles.Technician, + Jackal = CustomRoles.Jackal, + God = CustomRoles.God, + Mario = CustomRoles.Mario, + Innocent = CustomRoles.Innocent, + Pelican = CustomRoles.Pelican, + Youtuber = CustomRoles.Youtuber, + Egoist = CustomRoles.Egoist, + Gamer = CustomRoles.Gamer, + DarkHide = CustomRoles.DarkHide, + Workaholic = CustomRoles.Workaholic, + Collector = CustomRoles.Collector, + NecroGuesser = CustomRoles.NecroGuesser, + BloodKnight = CustomRoles.BloodKnight, + Poisoner = CustomRoles.Poisoner, + HexMaster = CustomRoles.HexMaster, + Succubus = CustomRoles.Succubus, + Necromancer = CustomRoles.Necromancer, + Wraith = CustomRoles.Wraith, + SerialKiller = CustomRoles.NSerialKiller, + Weatherman = CustomRoles.Weatherman, + NoteKiller = CustomRoles.NoteKiller, + Vortex = CustomRoles.Vortex, + Beehive = CustomRoles.Beehive, + RouleteGrandeur = CustomRoles.RouleteGrandeur, + Nonplus = CustomRoles.Nonplus, + Tremor = CustomRoles.Tremor, + Evolver = CustomRoles.Evolver, + Rogue = CustomRoles.Rogue, + Patroller = CustomRoles.Patroller, + Simon = CustomRoles.Simon, + Chemist = CustomRoles.Chemist, + Samurai = CustomRoles.Samurai, + QuizMaster = CustomRoles.QuizMaster, + Bargainer = CustomRoles.Bargainer, + Tiger = CustomRoles.Tiger, + Enderman = CustomRoles.Enderman, + Mycologist = CustomRoles.Mycologist, + Bubble = CustomRoles.Bubble, + Hookshot = CustomRoles.Hookshot, + Sprayer = CustomRoles.Sprayer, + PlagueDoctor = CustomRoles.PlagueDoctor, + Reckless = CustomRoles.Reckless, + Magician = CustomRoles.Magician, + WeaponMaster = CustomRoles.WeaponMaster, + Pyromaniac = CustomRoles.Pyromaniac, + Eclipse = CustomRoles.Eclipse, + HeadHunter = CustomRoles.HeadHunter, + Agitater = CustomRoles.Agitater, + Vengeance = CustomRoles.Vengeance, + Werewolf = CustomRoles.Werewolf, + Juggernaut = CustomRoles.Juggernaut, + Bandit = CustomRoles.Bandit, + Virus = CustomRoles.Virus, + Phantom = CustomRoles.Phantasm, + Jinx = CustomRoles.Jinx, + Ritualist = CustomRoles.Ritualist, + Pickpocket = CustomRoles.Pickpocket, + Traitor = CustomRoles.Traitor, + Vulture = CustomRoles.Vulture, + Pestilence = CustomRoles.Pestilence, + Medusa = CustomRoles.Medusa, + Spiritcaller = CustomRoles.Spiritcaller, + Glitch = CustomRoles.Glitch, + Plaguebearer = CustomRoles.PlagueBearer, + Doomsayer = CustomRoles.Doomsayer, + RuthlessRomantic = CustomRoles.RuthlessRomantic, + Doppelganger = CustomRoles.Doppelganger, + Imitator = CustomRoles.Imitator, + Cherokious = CustomRoles.Cherokious, + Specter = CustomRoles.Specter, - Bloodlust = CustomRoles.Bloodlust - } + Bloodlust = CustomRoles.Bloodlust +} - [SuppressMessage("ReSharper", "UnusedMember.Global")] - public enum AdditionalWinners - { - None = -1, +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public enum AdditionalWinners +{ + None = -1, - // Hide And Seek - Fox = CustomRoles.Fox, + // Hide And Seek + Fox = CustomRoles.Fox, - // ------------- - Specter = CustomRoles.Specter, - Lovers = CustomRoles.Lovers, - Executioner = CustomRoles.Executioner, - Opportunist = CustomRoles.Opportunist, - Lawyer = CustomRoles.Lawyer, - FFF = CustomRoles.FFF, - Provocateur = CustomRoles.Provocateur, - Sunnyboy = CustomRoles.Sunnyboy, - Totocalcio = CustomRoles.Totocalcio, - Romantic = CustomRoles.Romantic, - VengefulRomantic = CustomRoles.VengefulRomantic, - Pursuer = CustomRoles.Pursuer, - Phantom = CustomRoles.Phantasm, - Sidekick = CustomRoles.Sidekick, - Maverick = CustomRoles.Maverick, - Curser = CustomRoles.Curser, - Postman = CustomRoles.Postman, - Impartial = CustomRoles.Impartial, - Gaslighter = CustomRoles.Gaslighter, - Tank = CustomRoles.Tank, - Technician = CustomRoles.Technician, - Backstabber = CustomRoles.Backstabber, - Predator = CustomRoles.Predator, - SoulHunter = CustomRoles.SoulHunter, - SchrodingersCat = CustomRoles.SchrodingersCat, - Innocent = CustomRoles.Innocent, - NoteKiller = CustomRoles.NoteKiller, - NecroGuesser = CustomRoles.NecroGuesser - } + // ------------- + Specter = CustomRoles.Specter, + Lovers = CustomRoles.Lovers, + Executioner = CustomRoles.Executioner, + Opportunist = CustomRoles.Opportunist, + Lawyer = CustomRoles.Lawyer, + FFF = CustomRoles.FFF, + Provocateur = CustomRoles.Provocateur, + Sunnyboy = CustomRoles.Sunnyboy, + Totocalcio = CustomRoles.Totocalcio, + Romantic = CustomRoles.Romantic, + VengefulRomantic = CustomRoles.VengefulRomantic, + Pursuer = CustomRoles.Pursuer, + Phantom = CustomRoles.Phantasm, + Sidekick = CustomRoles.Sidekick, + Maverick = CustomRoles.Maverick, + Curser = CustomRoles.Curser, + Postman = CustomRoles.Postman, + Impartial = CustomRoles.Impartial, + Gaslighter = CustomRoles.Gaslighter, + Tank = CustomRoles.Tank, + Technician = CustomRoles.Technician, + Backstabber = CustomRoles.Backstabber, + Predator = CustomRoles.Predator, + SoulHunter = CustomRoles.SoulHunter, + SchrodingersCat = CustomRoles.SchrodingersCat, + Innocent = CustomRoles.Innocent, + NoteKiller = CustomRoles.NoteKiller, + NecroGuesser = CustomRoles.NecroGuesser +} - public enum SuffixModes - { - None = 0, - EHR, - Streaming, - Recording, - RoomHost, - OriginalName, - DoNotKillMe, - NoAndroidPlz, - AutoHost - } +public enum SuffixModes +{ + None = 0, + EHR, + Streaming, + Recording, + RoomHost, + OriginalName, + DoNotKillMe, + NoAndroidPlz, + AutoHost +} - public enum VoteMode - { - Default, - Suicide, - SelfVote, - Skip - } +public enum VoteMode +{ + Default, + Suicide, + SelfVote, + Skip +} - public enum TieMode - { - Default, - All, - Random - } +public enum TieMode +{ + Default, + All, + Random +} - public class Coroutines : MonoBehaviour { } -} \ No newline at end of file +public class Coroutines : MonoBehaviour { } \ No newline at end of file