From fe30e14810529909cce95863943cb87a9d1f1cd8 Mon Sep 17 00:00:00 2001
From: =?utf8?q?Mall=C3=B6ris?= <85371585+Mallaris@users.noreply.github.com>
Date: Fri, 3 Mar 2023 19:30:04 +0100
Subject: [PATCH] Update 4.3.0
---
.github/FUNDING.yml | 2 +-
README.md | 143 +++++++--
TheOtherRoles/Buttons.cs | 282 ++++++++++++++----
TheOtherRoles/CustomGameModes/HideNSeekGM.cs | 8 +-
TheOtherRoles/CustomOptionHolder.cs | 60 +++-
TheOtherRoles/GameHistory.cs | 2 +
TheOtherRoles/Helpers.cs | 52 +++-
TheOtherRoles/Main.cs | 18 +-
TheOtherRoles/MapOptions.cs | 6 +-
TheOtherRoles/Modules/CustomHats.cs | 88 ++++--
TheOtherRoles/Modules/CustomOptions.cs | 172 +++++++++--
TheOtherRoles/Modules/ModUpdater.cs | 42 ++-
TheOtherRoles/Objects/Bomb.cs | 111 +++++++
TheOtherRoles/Objects/Portal.cs | 16 +-
TheOtherRoles/Patches/ClientOptionsPatch.cs | 22 +-
.../Patches/CreateOptionsPickerPatch.cs | 12 +-
TheOtherRoles/Patches/CredentialsPatch.cs | 11 +-
TheOtherRoles/Patches/EndGamePatch.cs | 5 +-
TheOtherRoles/Patches/ExileControllerPatch.cs | 18 +-
.../Patches/GameStartManagerPatch.cs | 6 +-
.../Patches/HauntMenuMinigamePatch.cs | 31 +-
TheOtherRoles/Patches/IntroPatch.cs | 33 +-
TheOtherRoles/Patches/MainMenuPatch.cs | 47 ++-
TheOtherRoles/Patches/MapBehaviourPatch.cs | 1 +
TheOtherRoles/Patches/MeetingPatch.cs | 188 +++++++++---
TheOtherRoles/Patches/PlayerControlPatch.cs | 130 +++++---
TheOtherRoles/Patches/RegionMenuPatch.cs | 21 +-
TheOtherRoles/Patches/RoleAssignmentPatch.cs | 15 +-
TheOtherRoles/Patches/ShipStatusPatch.cs | 4 +-
TheOtherRoles/Patches/UpdatePatch.cs | 9 +-
TheOtherRoles/Patches/UsablesPatch.cs | 203 +++++++++++--
TheOtherRoles/RPC.cs | 169 +++++++++--
TheOtherRoles/Resources/Banner2.png | Bin 0 -> 170975 bytes
TheOtherRoles/Resources/Bomb.png | Bin 0 -> 12917 bytes
TheOtherRoles/Resources/BombBackground.png | Bin 0 -> 9995 bytes
.../Resources/Bomb_Button_Defuse.png | Bin 0 -> 22111 bytes
TheOtherRoles/Resources/Bomb_Button_Plant.png | Bin 0 -> 18854 bytes
.../Resources/CurrentSettingsButton.png | Bin 0 -> 16257 bytes
.../Resources/HorseHats/HorseSpecialHat10.png | Bin 0 -> 57295 bytes
.../HorseHats/HorseSpecialHat10_back.png | Bin 0 -> 9866 bytes
.../HorseHats/HorseSpecialHat11_adaptive.png | Bin 0 -> 36890 bytes
.../HorseSpecialHat11_adaptive_back.png | Bin 0 -> 40877 bytes
.../Resources/HorseHats/HorseSpecialHat12.png | Bin 0 -> 35590 bytes
.../HorseHats/HorseSpecialHat12_back.png | Bin 0 -> 49159 bytes
.../Resources/HorseHats/HorseSpecialHat13.png | Bin 0 -> 31378 bytes
.../HorseHats/HorseSpecialHat13_back.png | Bin 0 -> 9042 bytes
.../Resources/HorseHats/HorseSpecialHat14.png | Bin 0 -> 26991 bytes
.../HorseHats/HorseSpecialHat14_back.png | Bin 0 -> 17772 bytes
.../Resources/HorseHats/HorseSpecialHat15.png | Bin 0 -> 56320 bytes
.../HorseHats/HorseSpecialHat15_back.png | Bin 0 -> 7350 bytes
.../HorseHats/HorseSpecialHat16_adaptive.png | Bin 0 -> 26403 bytes
.../HorseSpecialHat16_adaptive_back.png | Bin 0 -> 22351 bytes
.../HorseHats/HorseSpecialHat17_adaptive.png | Bin 0 -> 63849 bytes
.../HorseSpecialHat17_adaptive_back.png | Bin 0 -> 5058 bytes
.../HorseHats/HorseSpecialHat18_adaptive.png | Bin 0 -> 34442 bytes
.../HorseSpecialHat18_adaptive_back.png | Bin 0 -> 10571 bytes
.../Resources/HorseHats/HorseSpecialHat19.png | Bin 0 -> 41693 bytes
.../HorseHats/HorseSpecialHat19_back.png | Bin 0 -> 32902 bytes
.../HorseHats/HorseSpecialHat1_adaptive.png | Bin 0 -> 38493 bytes
.../Resources/HorseHats/HorseSpecialHat2.png | Bin 0 -> 48653 bytes
.../HorseHats/HorseSpecialHat20_adaptive.png | Bin 0 -> 32871 bytes
.../HorseSpecialHat20_adaptive_back.png | Bin 0 -> 7976 bytes
.../Resources/HorseHats/HorseSpecialHat21.png | Bin 0 -> 40632 bytes
.../Resources/HorseHats/HorseSpecialHat22.png | Bin 0 -> 36797 bytes
.../HorseHats/HorseSpecialHat23_adaptive.png | Bin 0 -> 42554 bytes
.../HorseHats/HorseSpecialHat24_adaptive.png | Bin 0 -> 61769 bytes
.../HorseHats/HorseSpecialHat25_adaptive.png | Bin 0 -> 45669 bytes
.../Resources/HorseHats/HorseSpecialHat26.png | Bin 0 -> 50730 bytes
.../HorseHats/HorseSpecialHat26_back.png | Bin 0 -> 9637 bytes
.../Resources/HorseHats/HorseSpecialHat27.png | Bin 0 -> 66384 bytes
.../HorseHats/HorseSpecialHat27_back.png | Bin 0 -> 35539 bytes
.../Resources/HorseHats/HorseSpecialHat28.png | Bin 0 -> 46671 bytes
.../HorseHats/HorseSpecialHat28_back.png | Bin 0 -> 5878 bytes
.../HorseHats/HorseSpecialHat29_adaptive.png | Bin 0 -> 37878 bytes
.../HorseHats/HorseSpecialHat30_adaptive.png | Bin 0 -> 50632 bytes
.../HorseHats/HorseSpecialHat31_adaptive.png | Bin 0 -> 38039 bytes
.../HorseHats/HorseSpecialHat32_adaptive.png | Bin 0 -> 28536 bytes
.../HorseSpecialHat32_adaptive_back.png | Bin 0 -> 7376 bytes
.../HorseHats/HorseSpecialHat33_adaptive.png | Bin 0 -> 44311 bytes
.../HorseSpecialHat33_adaptive_back.png | Bin 0 -> 7181 bytes
.../Resources/HorseHats/HorseSpecialHat34.png | Bin 0 -> 75328 bytes
.../HorseHats/HorseSpecialHat34_back.png | Bin 0 -> 37969 bytes
.../HorseHats/HorseSpecialHat35_adaptive.png | Bin 0 -> 46164 bytes
.../HorseSpecialHat35_adaptive_back.png | Bin 0 -> 46319 bytes
.../HorseHats/HorseSpecialHat36_adaptive.png | Bin 0 -> 83812 bytes
.../HorseHats/HorseSpecialHat37_adaptive.png | Bin 0 -> 74035 bytes
.../HorseSpecialHat37_adaptive_back.png | Bin 0 -> 9687 bytes
.../HorseHats/HorseSpecialHat38_adaptive.png | Bin 0 -> 47915 bytes
.../Resources/HorseHats/HorseSpecialHat39.png | Bin 0 -> 39951 bytes
.../HorseHats/HorseSpecialHat39_back.png | Bin 0 -> 55423 bytes
.../HorseHats/HorseSpecialHat3_adaptive.png | Bin 0 -> 41340 bytes
.../HorseSpecialHat3_adaptive_back.png | Bin 0 -> 20538 bytes
.../Resources/HorseHats/HorseSpecialHat4.png | Bin 0 -> 56334 bytes
.../HorseHats/HorseSpecialHat40_adaptive.png | Bin 0 -> 51315 bytes
.../HorseSpecialHat40_adaptive_back.png | Bin 0 -> 7281 bytes
.../HorseHats/HorseSpecialHat41_adaptive.png | Bin 0 -> 54737 bytes
.../HorseHats/HorseSpecialHat42_adaptive.png | Bin 0 -> 51597 bytes
.../HorseSpecialHat42_adaptive_back.png | Bin 0 -> 13449 bytes
.../HorseHats/HorseSpecialHat43_adaptive.png | Bin 0 -> 28401 bytes
.../HorseHats/HorseSpecialHat44_adaptive.png | Bin 0 -> 25830 bytes
.../HorseHats/HorseSpecialHat45_adaptive.png | Bin 0 -> 10796 bytes
.../HorseSpecialHat45_adaptive_flip.png | Bin 0 -> 10911 bytes
.../Resources/HorseHats/HorseSpecialHat46.png | Bin 0 -> 47035 bytes
.../HorseHats/HorseSpecialHat46_back.png | Bin 0 -> 7350 bytes
.../HorseHats/HorseSpecialHat47_adaptive.png | Bin 0 -> 20119 bytes
.../HorseHats/HorseSpecialHat48_adaptive.png | Bin 0 -> 35736 bytes
.../HorseSpecialHat48_adaptive_back.png | Bin 0 -> 10823 bytes
.../HorseHats/HorseSpecialHat49_adaptive.png | Bin 0 -> 36533 bytes
.../HorseHats/HorseSpecialHat50_adaptive.png | Bin 0 -> 19072 bytes
.../HorseHats/HorseSpecialHat51_adaptive.png | Bin 0 -> 17852 bytes
.../HorseHats/HorseSpecialHat52_adaptive.png | Bin 0 -> 38064 bytes
.../HorseSpecialHat52_adaptive_back.png | Bin 0 -> 3833 bytes
.../HorseHats/HorseSpecialHat53_adaptive.png | Bin 0 -> 29094 bytes
.../HorseHats/HorseSpecialHat54_adaptive.png | Bin 0 -> 31237 bytes
.../HorseHats/HorseSpecialHat55_adaptive.png | Bin 0 -> 29833 bytes
.../Resources/HorseHats/HorseSpecialHat56.png | Bin 0 -> 59930 bytes
.../HorseHats/HorseSpecialHat56_back.png | Bin 0 -> 44467 bytes
.../HorseHats/HorseSpecialHat56_climb.png | Bin 0 -> 43705 bytes
.../HorseHats/HorseSpecialHat56_flip.png | Bin 0 -> 61441 bytes
.../HorseHats/HorseSpecialHat57_adaptive.png | Bin 0 -> 23865 bytes
.../HorseSpecialHat57_bounce_back.png | Bin 0 -> 11297 bytes
.../HorseSpecialHat57_climb_adaptive.png | Bin 0 -> 28583 bytes
.../HorseHats/HorseSpecialHat5_adaptive.png | Bin 0 -> 49871 bytes
.../HorseHats/HorseSpecialHat6_adaptive.png | Bin 0 -> 39206 bytes
.../HorseSpecialHat6_adaptive_back.png | Bin 0 -> 22962 bytes
.../Resources/HorseHats/HorseSpecialHat7.png | Bin 0 -> 41459 bytes
.../HorseHats/HorseSpecialHat7_back.png | Bin 0 -> 13429 bytes
.../HorseHats/HorseSpecialHat8_adaptive.png | Bin 0 -> 50140 bytes
.../HorseSpecialHat8_back_adaptive.png | Bin 0 -> 9352 bytes
.../Resources/HorseHats/HorseSpecialHat9.png | Bin 0 -> 63320 bytes
.../Resources/NightVisionOverlay.png | Bin 0 -> 636296 bytes
.../Resources/PortalAnimation/plattform.png | Bin 22748 -> 16507 bytes
.../Resources/SoundEffects/bombDefused.raw | Bin 0 -> 265048 bytes
.../Resources/SoundEffects/bombExplosion.raw | Bin 0 -> 618928 bytes
.../SoundEffects/bombFuseBurning.raw | Bin 0 -> 8504560 bytes
.../Resources/SoundEffects/bombTick.raw | Bin 0 -> 6225152 bytes
.../Resources/SoundEffects/knockKnock.raw | Bin 0 -> 455520 bytes
.../Resources/conversion/byteFileAction.py | 11 +
TheOtherRoles/RoleInfo.cs | 42 ++-
TheOtherRoles/SoundEffectsManager.cs | 23 +-
TheOtherRoles/TheOtherRoles.cs | 254 +++++++++++++---
TheOtherRoles/TheOtherRoles.csproj | 19 +-
TheOtherRoles/Utilities/EventUtility.cs | 152 ++++++++++
TheOtherRoles/Utilities/HandleGuesser.cs | 2 +-
144 files changed, 1958 insertions(+), 472 deletions(-)
create mode 100644 TheOtherRoles/Objects/Bomb.cs
create mode 100644 TheOtherRoles/Resources/Banner2.png
create mode 100644 TheOtherRoles/Resources/Bomb.png
create mode 100644 TheOtherRoles/Resources/BombBackground.png
create mode 100644 TheOtherRoles/Resources/Bomb_Button_Defuse.png
create mode 100644 TheOtherRoles/Resources/Bomb_Button_Plant.png
create mode 100644 TheOtherRoles/Resources/CurrentSettingsButton.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat10.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat10_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat12.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat12_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat13.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat13_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat14.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat14_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat15.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat15_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat19.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat19_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat1_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat2.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat21.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat22.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat23_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat24_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat25_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat26.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat26_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat27.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat27_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat28.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat28_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat29_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat30_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat31_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat34.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat34_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat36_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat38_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat39.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat39_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat4.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat41_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat43_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat44_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive_flip.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat46.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat46_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat47_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat49_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat50_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat51_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat53_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat54_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat55_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat56.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_climb.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_flip.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_bounce_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_climb_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat5_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat7.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat7_back.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_back_adaptive.png
create mode 100644 TheOtherRoles/Resources/HorseHats/HorseSpecialHat9.png
create mode 100644 TheOtherRoles/Resources/NightVisionOverlay.png
create mode 100644 TheOtherRoles/Resources/SoundEffects/bombDefused.raw
create mode 100644 TheOtherRoles/Resources/SoundEffects/bombExplosion.raw
create mode 100644 TheOtherRoles/Resources/SoundEffects/bombFuseBurning.raw
create mode 100644 TheOtherRoles/Resources/SoundEffects/bombTick.raw
create mode 100644 TheOtherRoles/Resources/SoundEffects/knockKnock.raw
create mode 100644 TheOtherRoles/Resources/conversion/byteFileAction.py
create mode 100644 TheOtherRoles/Utilities/EventUtility.cs
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index fc8128f..f43861f 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1 @@
-ko_fi: TheOtherRoles
+ko_fi: theotherroles
diff --git a/README.md b/README.md
index a9c6bcc..2233f0e 100644
--- a/README.md
+++ b/README.md
@@ -30,20 +30,25 @@ Even more roles are coming soon. :)
| [Bounty Hunter](#bounty-hunter) | [Hacker](#hacker) | | [Shifter](#shifter)
| [Witch](#witch) | [Tracker](#tracker) | | |
| [Ninja](#ninja) | [Snitch](#snitch) | | |
-| [Evil Guesser](#guesser) | [Spy](#spy) | | |
-| | [Portalmaker](#portalmaker) | | |
+| [Bomber](#bomber) | [Spy](#spy) | | |
+| [Evil Guesser](#guesser) | [Portalmaker](#portalmaker) | | |
| | [Security Guard](#security-guard) | | |
| | [Medium](#medium) | | |
| | [Trapper](#trapper) | | |
| | [Nice Guesser](#guesser) | | |
-| | | |
The [Role Assignment](#role-assignment) sections explains how the roles are being distributed among the players.
# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2022.12.14s| v4.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.3.0/TheOtherRoles.zip)
| 2022.12.14s| v4.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.2.1/TheOtherRoles.zip)
+
+ Click to show older versions
+
+| Among Us - Version| Mod Version | Link |
+|----------|-------------|-----------------|
| 2022.10.25s| v4.2.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.2.0/TheOtherRoles.zip)
| 2022.9.20s| v4.1.7| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.7/TheOtherRoles.zip)
| 2022.6.21s| v4.1.6| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.6/TheOtherRoles.zip)
@@ -109,10 +114,30 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
| 2021.3.5s | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip)
| 2020.12.19s | v1.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.1/TheOtherRoles.zip)
| 2020.12.19s | v1.0 | [Download](https://github.com/Eisbison/TheOtherRoles/files/6097191/TheOtherRoles.zip)
-
+
+
+
# Changelog
Click to show the Changelog
+
+**Version 4.3.0**
+- Updated to Among Us version 2023.2.28
+- Added a new role: [Bomber](#bomber)
+- Added more information for ghosts (e.g. who is cuffed, witched, ninja marked etc.), can be turned off in the mod options menu
+- Added the option to change between cameras (Polus & Airship) with A & D and left & right arrow keys on your keyboard
+- Added a new feature in settings: Fill Crewmate Roles
+- Added a new option: Cams Switch To Night Vision If Lights Are Off (+ Option: Impostor Vision Ignores Night Vision Cams)
+- Added a new option: Finish Tasks Before Haunting Or Zooming Out
+- Added a new option to Mayor: Mayor Can Choose Single Vote
+- Added a button to see the game settings while ingame (F1-Key)
+- Added a new ability to Portalmaker: Port to any of the portals from anywhere
+- Added a new option to Medium: Chance That The Answer Contains Additional Information (see [Medium](#medium))
+- Changed the question pattern for the Medium
+- Changed the role: Lighter (Flashlight vision)
+- Changed the role: Snitch (no more arrows, location information instead)
+- Fixed a bug where entering a custom server manually did not work anymore (+ nicer UI for entering a custom server)
+- Fixed a bug where a witch-spelled prosecutor would not win if their target was voted
**Version 4.2.1**
- Updated to Among Us version 2022.12.14
@@ -729,6 +754,7 @@ docker run -d -p 22023:22023/udp --env IMPOSTOR_AntiCheatEnabled=false --env IMP
# Settings
The mod adds a few new settings to Among Us (in addition to the role settings):
- **Number of Crewmates:** The number of Crewmate roles can be set inside a lobby.
+- **Fill Crewmate Roles (Ignores Min/Max):** Everyone will get a role, even if the settings say there would be plain Crewmates (needs enough roles over 0%).
- **Number of Neutrals:** The number of Neutral roles can be set inside a lobby.
- **Number of Impostors:** The number of Impostor roles can be set inside a lobby.
- **Number of Modifiers:** The number of Modifiers can be set inside a lobby.
@@ -738,16 +764,21 @@ The mod adds a few new settings to Among Us (in addition to the role settings):
- **Hide Player Names:** Hides the names of all players that have role which is unknown to you. Team Lovers/Impostors/Jackal still see the names of their teammates. Impostors can also see the name of the Spy and everyone can still see the age of the mini.
- **Allow Parallel MedBay Scans:** Allows players to perform their MedBay scans at the same time.
- **Shield Last Game First Kill** The first killed player of the previous round will be shielded for all players visible until the first meeting.
+- **Finish Tasks Before Haunting Or Zooming Out ** The zoom out function as well as haunting will be hidden for the player until all their tasks are finished
+- **Admin Table Shows Dead Bodies**
+- **Cams Switch To Night Vision If Lights Are Off** No colors and cosmetics can be seen on the cameras when the lights are off. Mini can be spottet!
+- **Impostor Vision Ignores Night Vision Cams**
- **Play On A Random Map** If enabled it allows you to set percentages for each current map, except ehT dlekS.
- **Ghosts Can See Roles.**
- **Ghosts Can See Votes.**
- **Ghosts Can Additionally See Modifier.**
-- **Ghosts Can See The Number Of Remaining Tasks.**
+- **Ghosts See Tasks And Other Info** Other Info: Things like who is cursed, cuffed etc. but also the medic shield, flashes, engineer fix etc.
- **The map is accessable during a meeting and will show your last location when a body gets reported/meeting gets called.**
- **When you're a ghost and done with tasks, you'll get a zoom out/overview function.**
- **Task Counts:** You are now able to select more tasks.
- **Role Summary:** When a game ends there will be a list of all players, their roles, their task progression and how many player a killer killed.
- **Darker/Lighter:** Displays color type of each player in meetings.
+- **Show The Settings In-Game** You can use the button located under the map button or the F1-key to open or close the display of the lobby settings
### Task Count Limits per map
You can configure:
@@ -769,6 +800,8 @@ Example: If you configure 4 common tasks on Airship crewmates will only receive
### Random Maps
**Play On A Random Map** If enabled it allows you to set percentages for each current map.
+**Finish Tasks Before Haunting Or Zooming Out** You have to finish your tasks before you get the possibility to zoom out.
+
**Use Random Map Setting Presets:** When this option is turned on, the game will switch to the `Random Preset ` when a game is started on a random map. **This includes vanilla options!**. This makes it possible to e.g. play with less tasks on large maps like Airship.
The game will **remain** in the preset for that random map after the match.
@@ -850,7 +883,7 @@ The Morphling is an Impostor which can additionally scan the appearance of a pla
- The color of the footprints changes accordingly (also the ones that were already on the ground).
- The other Impostor still sees that they are an Impostor (the name remains red).
- The shield indicator changes accordingly (the Morphling gains or loses the shield indicator).
-- Tracker and Snitch arrows keep working.
+- Tracker arrows keep working.
### Game Options
| Name | Description |
@@ -871,7 +904,7 @@ are hidden and all players have the same color.\
- The color of the footprints turns gray (also the ones that were already on the ground).
- The Hacker sees gray icons on the admin table
- The shield is not visible anymore
-- Tracker and Snitch arrows keep working
+- Tracker arrows keep working
### Game Options
| Name | Description |
@@ -891,6 +924,8 @@ If a victim is near a garlic, the "Bite Button" turns into the default "Kill But
- If a bitten player is still alive when a meeting is being called, they die at the start of the meeting.
- The cooldown is the same as the default kill cooldown (+ the kill delay if the Vampire bites the target).
- If there is a Vampire in the game, there can't be a Warlock.
+- If the Vampire bites a player and the Thief kills the Vampire, the bite will still be performed, but the new Vampire will be displayed in the kill animation.
+- If the Vampire bites a player and gets killed before the bite is performed, the biten player will survive.
### Game Options
| Name | Description |
@@ -1052,6 +1087,33 @@ When performing a ninja ability kill, the ninja can be invisible for some second
| Time The Ninja Is Invisible | -
-----------------------
+## Bomber
+### **Team: Impostors**
+The Bomber is an Impostor who has the ability to be very explosive. They have the ability to plant bombs to spread grouping Crewmates and also kill them.\
+The Bomber also has the ability to perform a normal kill like all Impostors.\
+The plant time of the bomb can be different to the kill cooldown depending on the settings.\
+Crewmates can defuse a bomb depending on the settings.
+
+**NOTE:**
+- The bomb won't kill a shielded player (Medic, First Kill Shield)
+- The bomb won't kill a Mini until it's grown up
+- The bomb can kill the bomber as well as their teammate(s)
+- The hearing range can be higher/lower than the actual destruction range, depending on the settings, and has a visual indicator
+- The visual indicator slowly fades into red until the bomb explodes and it does not show the explosion range (only hearing range)!
+- The bomb can be defused by standing on it and snipping the fuse (button)
+
+### Game Options
+| Name | Description |
+|----------|:-------------:|
+| Bomber Spawn Chance | -
+| Bomb Destruction Time | -
+| Bomb Destruction Range | -
+| Bomb Hear Range | -
+| Bomb Defuse Duration | -
+| Bomb Cooldown | -
+| Bomb Is Active After |
+-----------------------
+
## Guesser
### **Team: Crewmates or Impostors**
The Guesser can be a Crewmate or an Impostor (depending on the settings).\
@@ -1281,7 +1343,8 @@ the Sheriff. While the Thief hasn't fired, their tasks do not count towards the
The Mayor leads the Crewmates by having a vote that counts twice.\
The Mayor can always use their meeting, even if the maximum number of meetings was reached.\
The Mayor has a portable Meeting Button, depending on the options.\
-The Mayor can see the vote colors after completing a configurable amount of tasks, depending on the options.
+The Mayor can see the vote colors after completing a configurable amount of tasks, depending on the options.\
+The Mayor has the option to vote with only one vote instead of two (via a button in the meeting screen), depending on the settings.
### Game Options
| Name | Description |
@@ -1290,6 +1353,7 @@ The Mayor can see the vote colors after completing a configurable amount of task
| Mayor Can See Vote Colors | -
| Completed Tasks Needed To See Vote Colors | -
| Mobile Emergency Button | -
+| Mayor Can Choose Single Vote | Off, On (Before Voting), On (Until Meeting Ends)
-----------------------
## Engineer
@@ -1356,16 +1420,16 @@ Handcuffs disable:
## Lighter
### **Team: Crewmates**
-The Lighter can turn on their Lighter every now and then, which increases their vision by a customizable amount.
+The Lighter has a different vision than everyone else depending on the settings.\
+Their vision looks like a flashlight cone which can be moved around (known from the Hide'n'Seek mode).
### Game Options
| Name | Description |
|----------|:-------------:|
| Lighter Spawn Chance | -
-| Lighter Mode Vision On Lights On | The vision the Lighter has when the lights are on and the Lighter mode is on
-| Lighter Mode Vision On Lights Off | The vision the Lighter has when the lights are down and the Lighter mode is on
-| Lighter Cooldown | -
-| Lighter Duration | -
+| Vision On Lights On | The vision the Lighter has when the lights are on
+| Vision On Lights Off | The vision the Lighter has when the lights are down
+| Flashlight Width | -
-----------------------
## Detective
@@ -1523,20 +1587,18 @@ Depending on the options, the Tracker has another ability: They can track all co
## Snitch
### **Team: Crewmates**
-When the Snitch finishes all the tasks, arrows will appear (only visible to the Snitch) that point to the Impostors (depending on the options also to members of team Jackal).\
-When the Snitch has one task left (configurable) the Snitch will be revealed to the Impostors (depending on the options also to members of team Jackal) with an arrow pointing to the Snitch.
+When the Snitch finishes all their tasks, they will get information in chat of the last location of all killers when the meeting starts.\
+When the Snitch only has a configurable amount of tasks left, it will be revealed that there is a Snitch in the game with a text on evil player's screens.
-**NOTE**
-- If the Impostor(s)/Jackal(s) is/are on a different floor on Submerged when the Snitch finished their tasks, the arrow will always point to the elevator
+**NOTE:**
+- If the Snitch dies, all killers will be informed that the Snitch is dead
+- Last location can be a room or open field
### Game Options
| Name | Description
|----------|:-------------:|
| Snitch Spawn Chance | -
| Task Count Where The Snitch Will Be Revealed | -
-| Include Team Jackal | -
-| Use Different Arrow Color For Team Jackal | -
-| Snitch can't be guessed after finishing all their tasks | -
-----------------------
## Spy
@@ -1566,10 +1628,11 @@ You can set whether the Sheriff can kill the Spy or not (in order to keep the li
The Portalmaker is a Crewmate that can place two portals on the map.\
These two portals are connected to each other.\
Those portals will be visible after the next meeting and can be used by everyone.\
-Additionally to that, the Portalmaker gets information about who used the portals and when in the chat during each meeting, depending on the options.
+Additionally to that, the Portalmaker gets information about who used the portals and when in the chat during each meeting, depending on the options.\
+The Portalmaker can teleport themself to their placed portals from anywhere if the setting is enabled.
**NOTE:**
-- The extra button to use a portal will appear after the Portalmaker set his portals and a meeting/body report was called.
+- The extra button to use a portal will appear after the Portalmaker set their portals and a meeting/body report was called.
- While one player uses a portal, it is blocked for any other player until the player got teleported.
- All ghosts can still use the portals, but won't block any living player from using it and the Portalmaker won't get any information about it in chat.
- If a morphed person uses a portal it will show the morphed name/color depending on the options.
@@ -1583,6 +1646,7 @@ Additionally to that, the Portalmaker gets information about who used the portal
| Use Portal Cooldown | -
| Portalmaker Log Only Shows Color Type | -
| Log Shows Time | -
+| Can Port To Portal From Everywhere | -
-----------------------
## Security Guard
@@ -1617,13 +1681,33 @@ The vents will be sealed after the next meeting, players can't enter or exit sea
## Medium
### **Team: Crewmates**
-The medium is a crewmate who can ask the souls of dead players for information. Like the Seer, it sees the places where the players have died (after the next meeting) and can question them. It then gets random information about the soul or the killer in the chat. The souls only stay for one round, i.e. until the next meeting. Depending on the options, the souls can only be questioned once and then disappear.\
-
-Questions:
-What is your Role?
-What is your killer's color type?
-When did you die?
-What is your killers role?
+The medium is a crewmate who can ask the souls of dead players for information. Like the Seer, the medium will see the souls of the players who have died (after the next meeting) and can question them. They then gets random information about the soul or the killer in the chat. The souls only stay for one round, i.e. until the next meeting. Depending on the options, the souls can only be questioned once and then disappear.
+\
+**Questions:**
+The souls will always prefer to answer with the role specific information first!
+
+**Role specific:**
+- Sheriff suicide: "Yikes, that Sheriff shot backfired."
+- Thief suicide: "I tried to steal the gun from their pocket, but they were just happy to see me."
+- Active Lover dies: "I wanted to get out of this toxic relationship anyways."
+- Passiv Lover suicide: "The love of my life died, thus with a kiss I die."
+- Lawyer client kills Lawyer: "My client killed me. Do I still get paid?"
+- Teamkill Jackal/Sidekick: "First they sidekicked me, then they killed me... BUT WHY?"
+- Teamkill Impostor: "I guess they confused me for the Spy, is there even one?"
+- Submerged o2-Kill: "Do I really need that mask for breathing?"
+- Warlock self kill: "MAYBE I cursed the person next to me and killed myself. Oops."
+- Vulture/Cleaner eats/cleans body: "Is my dead body some kind of art now or... aaand it's gone."
+
+**Else random:**
+- "I'm not sure, but I guess a darker/lighter color killed me."
+- "If I counted correctly, then I died x seconds before the meeting started."
+- "If my role hasn't been saved, there's no (role) in the game anymore."
+- "It seems like my killer was the (role)."
+
+**Chance That The Answer Contains Additional Information:**
+- When you asked, x killer(s) was/were still alive.
+- When you asked, x player(s) who can use vents was/were still alive.
+- When you asked, x player(s) who is/are neutral but cannot kill was/were still alive.
### Game Options
| Name | Description
@@ -1632,6 +1716,7 @@ What is your killers role?
| Medium Cooldown | -
| Medium Duration | The time it takes to question a soul
| Medium Each Soul Can Only Be Questioned Once | If set to true, souls can only be questioned once and then disappear
+| Chance That The Answer Contains The Remaining Amount Of Killing Roles | Chance includes Sheriff and Thief
-----------------------
## Trapper
diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs
index e243f30..41e73b4 100644
--- a/TheOtherRoles/Buttons.cs
+++ b/TheOtherRoles/Buttons.cs
@@ -28,6 +28,7 @@ namespace TheOtherRoles
private static CustomButton camouflagerButton;
private static CustomButton portalmakerPlacePortalButton;
private static CustomButton usePortalButton;
+ private static CustomButton portalmakerMoveToPortalButton;
private static CustomButton hackerButton;
public static CustomButton hackerVitalsButton;
public static CustomButton hackerAdminTableButton;
@@ -38,7 +39,6 @@ namespace TheOtherRoles
public static CustomButton jackalKillButton;
public static CustomButton sidekickKillButton;
private static CustomButton jackalSidekickButton;
- private static CustomButton lighterButton;
private static CustomButton eraserButton;
private static CustomButton placeJackInTheBoxButton;
private static CustomButton lightsOutButton;
@@ -55,6 +55,8 @@ namespace TheOtherRoles
public static CustomButton mayorMeetingButton;
public static CustomButton thiefKillButton;
public static CustomButton trapperButton;
+ public static CustomButton bomberButton;
+ public static CustomButton defuseButton;
public static CustomButton zoomOutButton;
private static CustomButton hunterLighterButton;
private static CustomButton hunterAdminTableButton;
@@ -71,6 +73,8 @@ namespace TheOtherRoles
public static TMPro.TMP_Text hackerAdminTableChargesText;
public static TMPro.TMP_Text hackerVitalsChargesText;
public static TMPro.TMP_Text trapperChargesText;
+ public static TMPro.TMP_Text portalmakerButtonText1;
+ public static TMPro.TMP_Text portalmakerButtonText2;
public static TMPro.TMP_Text huntedShieldCountText;
public static void setCustomButtonCooldowns() {
@@ -94,6 +98,7 @@ namespace TheOtherRoles
camouflagerButton.MaxTimer = Camouflager.cooldown;
portalmakerPlacePortalButton.MaxTimer = Portalmaker.cooldown;
usePortalButton.MaxTimer = Portalmaker.usePortalCooldown;
+ portalmakerMoveToPortalButton.MaxTimer = Portalmaker.usePortalCooldown;
hackerButton.MaxTimer = Hacker.cooldown;
hackerVitalsButton.MaxTimer = Hacker.cooldown;
hackerAdminTableButton.MaxTimer = Hacker.cooldown;
@@ -103,7 +108,6 @@ namespace TheOtherRoles
jackalKillButton.MaxTimer = Jackal.cooldown;
sidekickKillButton.MaxTimer = Sidekick.cooldown;
jackalSidekickButton.MaxTimer = Jackal.createSidekickCooldown;
- lighterButton.MaxTimer = Lighter.cooldown;
eraserButton.MaxTimer = Eraser.cooldown;
placeJackInTheBoxButton.MaxTimer = Trickster.placeBoxCooldown;
lightsOutButton.MaxTimer = Trickster.lightsOutCooldown;
@@ -121,17 +125,19 @@ namespace TheOtherRoles
thiefKillButton.MaxTimer = Thief.cooldown;
mayorMeetingButton.MaxTimer = GameManager.Instance.LogicOptions.GetEmergencyCooldown();
trapperButton.MaxTimer = Trapper.cooldown;
+ bomberButton.MaxTimer = Bomber.bombCooldown;
hunterLighterButton.MaxTimer = Hunter.lightCooldown;
hunterAdminTableButton.MaxTimer = Hunter.AdminCooldown;
hunterArrowButton.MaxTimer = Hunter.ArrowCooldown;
huntedShieldButton.MaxTimer = Hunted.shieldCooldown;
+ defuseButton.MaxTimer = 0f;
+ defuseButton.Timer = 0f;
timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
hackerButton.EffectDuration = Hacker.duration;
hackerVitalsButton.EffectDuration = Hacker.duration;
hackerAdminTableButton.EffectDuration = Hacker.duration;
vampireKillButton.EffectDuration = Vampire.delay;
- lighterButton.EffectDuration = Lighter.duration;
camouflagerButton.EffectDuration = Camouflager.duration;
morphlingButton.EffectDuration = Morphling.duration;
lightsOutButton.EffectDuration = Trickster.lightsOutDuration;
@@ -143,6 +149,8 @@ namespace TheOtherRoles
hunterLighterButton.EffectDuration = Hunter.lightDuration;
hunterArrowButton.EffectDuration = Hunter.ArrowDuration;
huntedShieldButton.EffectDuration = Hunted.shieldDuration;
+ defuseButton.EffectDuration = Bomber.defuseDuration;
+ bomberButton.EffectDuration = Bomber.destructionTime + Bomber.bombActiveAfter;
// Already set the timer to the max, as the button is enabled during the game and not available at the start
lightsOutButton.Timer = lightsOutButton.MaxTimer;
zoomOutButton.MaxTimer = 0f;
@@ -307,8 +315,9 @@ namespace TheOtherRoles
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
writer.Write(playerInfo.PlayerId);
+ writer.Write(Janitor.janitor.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.cleanBody(playerInfo.PlayerId);
+ RPCProcedure.cleanBody(playerInfo.PlayerId, Janitor.janitor.PlayerId);
janitorCleanButton.Timer = janitorCleanButton.MaxTimer;
SoundEffectsManager.play("cleanerClean");
@@ -756,7 +765,19 @@ namespace TheOtherRoles
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0);
+ byte lastTimer = (byte)Vampire.delay;
FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action((p) => { // Delayed action
+ if (p <= 1f) {
+ byte timer = (byte)vampireKillButton.Timer;
+ if (timer != lastTimer) {
+ lastTimer = timer;
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.VampireTimer);
+ writer.Write(timer);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ }
+ }
if (p == 1f) {
// Perform kill if possible and reset bitten (regardless whether the kill was successful or not)
Helpers.checkMurderAttemptAndKill(Vampire.vampire, Vampire.bitten, showAnimation: false);
@@ -808,6 +829,7 @@ namespace TheOtherRoles
garlicButton = new CustomButton(
() => {
+ Utilities.EventUtility.StartEvent(EventUtility.EventTypes.Animation);
Vampire.localPlacedGarlic = true;
var pos = CachedPlayer.LocalPlayer.transform.position;
byte[] buff = new byte[sizeof(float) * 2];
@@ -859,15 +881,24 @@ namespace TheOtherRoles
bool didTeleport = false;
Vector3 exit = Portal.findExit(CachedPlayer.LocalPlayer.transform.position);
Vector3 entry = Portal.findEntry(CachedPlayer.LocalPlayer.transform.position);
+
+ bool portalMakerSoloTeleport = !Portal.locationNearEntry(CachedPlayer.LocalPlayer.transform.position);
+ if (portalMakerSoloTeleport) {
+ exit = Portal.firstPortal.portalGameObject.transform.position;
+ entry = CachedPlayer.LocalPlayer.transform.position;
+ }
+
CachedPlayer.LocalPlayer.NetTransform.RpcSnapTo(entry);
if (!CachedPlayer.LocalPlayer.Data.IsDead) { // Ghosts can portal too, but non-blocking and only with a local animation
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UsePortal, Hazel.SendOption.Reliable, -1);
writer.Write((byte)CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write(portalMakerSoloTeleport ? (byte)1 : (byte)0);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
- RPCProcedure.usePortal(CachedPlayer.LocalPlayer.PlayerId);
+ RPCProcedure.usePortal(CachedPlayer.LocalPlayer.PlayerId, portalMakerSoloTeleport ? (byte)1 : (byte)0);
usePortalButton.Timer = usePortalButton.MaxTimer;
+ portalmakerMoveToPortalButton.Timer = usePortalButton.MaxTimer;
SoundEffectsManager.play("portalUse");
FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action((p) => { // Delayed action
CachedPlayer.LocalPlayer.PlayerControl.moveable = false;
@@ -884,8 +915,11 @@ namespace TheOtherRoles
}
})));
},
- () => { return Portal.bothPlacedAndEnabled; },
- () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && Portal.locationNearEntry(CachedPlayer.LocalPlayer.transform.position) && !Portal.isTeleporting; },
+ () => {
+ if (CachedPlayer.LocalPlayer.PlayerControl == Portalmaker.portalmaker && Portal.bothPlacedAndEnabled)
+ portalmakerButtonText1.text = Portal.locationNearEntry(CachedPlayer.LocalPlayer.transform.position) || !Portalmaker.canPortalFromAnywhere ? "" : "1. " + Portal.firstPortal.room;
+ return Portal.bothPlacedAndEnabled; },
+ () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && (Portal.locationNearEntry(CachedPlayer.LocalPlayer.transform.position) || Portalmaker.canPortalFromAnywhere && CachedPlayer.LocalPlayer.PlayerControl == Portalmaker.portalmaker) && !Portal.isTeleporting; },
() => { usePortalButton.Timer = usePortalButton.MaxTimer; },
Portalmaker.getUsePortalButtonSprite(),
new Vector3(0.9f, -0.06f, 0),
@@ -894,6 +928,61 @@ namespace TheOtherRoles
mirror: true
);
+ portalmakerMoveToPortalButton = new CustomButton(
+ () => {
+ bool didTeleport = false;
+ Vector3 exit = Portal.secondPortal.portalGameObject.transform.position;
+
+ if (!CachedPlayer.LocalPlayer.Data.IsDead) { // Ghosts can portal too, but non-blocking and only with a local animation
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UsePortal, Hazel.SendOption.Reliable, -1);
+ writer.Write((byte)CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)2);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ }
+ RPCProcedure.usePortal(CachedPlayer.LocalPlayer.PlayerId, 2);
+ usePortalButton.Timer = usePortalButton.MaxTimer;
+ portalmakerMoveToPortalButton.Timer = usePortalButton.MaxTimer;
+ SoundEffectsManager.play("portalUse");
+ FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action((p) => { // Delayed action
+ CachedPlayer.LocalPlayer.PlayerControl.moveable = false;
+ CachedPlayer.LocalPlayer.NetTransform.Halt();
+ if (p >= 0.5f && p <= 0.53f && !didTeleport && !MeetingHud.Instance) {
+ if (SubmergedCompatibility.IsSubmerged) {
+ SubmergedCompatibility.ChangeFloor(exit.y > -7);
+ }
+ CachedPlayer.LocalPlayer.NetTransform.RpcSnapTo(exit);
+ didTeleport = true;
+ }
+ if (p == 1f) {
+ CachedPlayer.LocalPlayer.PlayerControl.moveable = true;
+ }
+ })));
+ },
+ () => { return Portalmaker.canPortalFromAnywhere && Portal.bothPlacedAndEnabled && CachedPlayer.LocalPlayer.PlayerControl == Portalmaker.portalmaker; },
+ () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && !Portal.locationNearEntry(CachedPlayer.LocalPlayer.transform.position) && !Portal.isTeleporting; },
+ () => { portalmakerMoveToPortalButton.Timer = usePortalButton.MaxTimer; },
+ Portalmaker.getUsePortalButtonSprite(),
+ new Vector3(0.9f, 1f, 0),
+ __instance,
+ KeyCode.J,
+ mirror: true
+ );
+
+
+ portalmakerButtonText1 = GameObject.Instantiate(usePortalButton.actionButton.cooldownTimerText, usePortalButton.actionButton.cooldownTimerText.transform.parent);
+ portalmakerButtonText1.text = "";
+ portalmakerButtonText1.enableWordWrapping = false;
+ portalmakerButtonText1.transform.localScale = Vector3.one * 0.5f;
+ portalmakerButtonText1.transform.localPosition += new Vector3(-0.05f, 0.55f, -1f);
+
+ portalmakerButtonText2 = GameObject.Instantiate(portalmakerMoveToPortalButton.actionButton.cooldownTimerText, portalmakerMoveToPortalButton.actionButton.cooldownTimerText.transform.parent);
+ portalmakerButtonText2.text = "";
+ portalmakerButtonText2.enableWordWrapping = false;
+ portalmakerButtonText2.transform.localScale = Vector3.one * 0.5f;
+ portalmakerButtonText2.transform.localPosition += new Vector3(-0.05f, 0.55f, -1f);
+
+
+
// Jackal Sidekick Button
jackalSidekickButton = new CustomButton(
() => {
@@ -945,31 +1034,6 @@ namespace TheOtherRoles
KeyCode.Q
);
- // Lighter light
- lighterButton = new CustomButton(
- () => {
- Lighter.lighterTimer = Lighter.duration;
- SoundEffectsManager.play("lighterLight");
- },
- () => { return Lighter.lighter != null && Lighter.lighter == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; },
- () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove; },
- () => {
- lighterButton.Timer = lighterButton.MaxTimer;
- lighterButton.isEffectActive = false;
- lighterButton.actionButton.graphic.color = Palette.EnabledColor;
- },
- Lighter.getButtonSprite(),
- CustomButton.ButtonPositions.lowerRowRight,
- __instance,
- KeyCode.F,
- true,
- Lighter.duration,
- () => {
- lighterButton.Timer = lighterButton.MaxTimer;
- SoundEffectsManager.play("lighterLight");
- }
- );
-
// Eraser erase button
eraserButton = new CustomButton(
() => {
@@ -1058,8 +1122,9 @@ namespace TheOtherRoles
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
writer.Write(playerInfo.PlayerId);
+ writer.Write(Cleaner.cleaner.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.cleanBody(playerInfo.PlayerId);
+ RPCProcedure.cleanBody(playerInfo.PlayerId, Cleaner.cleaner.PlayerId);
Cleaner.cleaner.killTimer = cleanerCleanButton.Timer = cleanerCleanButton.MaxTimer;
SoundEffectsManager.play("cleanerClean");
@@ -1087,6 +1152,14 @@ namespace TheOtherRoles
warlockCurseButton.Sprite = Warlock.getCurseKillButtonSprite();
warlockCurseButton.Timer = 1f;
SoundEffectsManager.play("warlockCurse");
+
+ // Ghost Info
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.WarlockTarget);
+ writer.Write(Warlock.curseVictim.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+
} else if (Warlock.curseVictim != null && Warlock.curseVictimTarget != null) {
MurderAttemptResult murder = Helpers.checkMurderAttemptAndKill(Warlock.warlock, Warlock.curseVictimTarget, showAnimation: false);
if (murder == MurderAttemptResult.SuppressKill) return;
@@ -1107,7 +1180,13 @@ namespace TheOtherRoles
Warlock.curseVictimTarget = null;
warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
Warlock.warlock.killTimer = warlockCurseButton.Timer = warlockCurseButton.MaxTimer;
-
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.WarlockTarget);
+ writer.Write(Byte.MaxValue); // This will set it to null!
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+
}
},
() => { return Warlock.warlock != null && Warlock.warlock == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; },
@@ -1278,14 +1357,23 @@ namespace TheOtherRoles
Arsonist.duration,
() => {
if (Arsonist.douseTarget != null) Arsonist.dousedPlayers.Add(Arsonist.douseTarget);
- Arsonist.douseTarget = null;
+
arsonistButton.Timer = Arsonist.dousedEveryoneAlive() ? 0 : arsonistButton.MaxTimer;
foreach (PlayerControl p in Arsonist.dousedPlayers) {
- if (MapOptions.playerIcons.ContainsKey(p.PlayerId)) {
- MapOptions.playerIcons[p.PlayerId].setSemiTransparent(false);
+ if (TORMapOptions.playerIcons.ContainsKey(p.PlayerId)) {
+ TORMapOptions.playerIcons[p.PlayerId].setSemiTransparent(false);
}
}
+
+ // Ghost Info
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.ArsonistDouse);
+ writer.Write(Arsonist.douseTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+
+ Arsonist.douseTarget = null;
}
);
@@ -1303,23 +1391,17 @@ namespace TheOtherRoles
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
writer.Write(playerInfo.PlayerId);
+ writer.Write(Vulture.vulture.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.cleanBody(playerInfo.PlayerId);
+ RPCProcedure.cleanBody(playerInfo.PlayerId, Vulture.vulture.PlayerId);
Vulture.cooldown = vultureEatButton.Timer = vultureEatButton.MaxTimer;
- Vulture.eatenBodies++;
SoundEffectsManager.play("vultureEat");
break;
}
}
}
}
- if (Vulture.eatenBodies == Vulture.vultureNumberToWin) {
- MessageWriter winWriter = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.VultureWin, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(winWriter);
- RPCProcedure.vultureWin();
- return;
- }
},
() => { return Vulture.vulture != null && Vulture.vulture == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; },
() => { return __instance.ReportButton.graphic.color == Palette.EnabledColor && CachedPlayer.LocalPlayer.PlayerControl.CanMove; },
@@ -1362,20 +1444,15 @@ namespace TheOtherRoles
() => {
mediumButton.Timer = mediumButton.MaxTimer;
if (Medium.target == null || Medium.target.player == null) return;
- string msg = "";
-
- int randomNumber = TheOtherRoles.rnd.Next(4);
- string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting.Data.DefaultOutfit.ColorId) ? "lighter" : "darker";
- float timeSinceDeath = ((float)(Medium.meetingStartTime - Medium.target.timeOfDeath).TotalMilliseconds);
- string name = " (" + Medium.target.player.Data.PlayerName + ")";
-
-
- if (randomNumber == 0) msg = "What is your role? My role is " + RoleInfo.GetRolesString(Medium.target.player, false) + name;
- else if (randomNumber == 1) msg = "What is your killer`s color type? My killer is a " + typeOfColor + " color" + name;
- else if (randomNumber == 2) msg = "When did you die? I have died " + Math.Round(timeSinceDeath / 1000) + "s before meeting started" + name;
- else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false, false) + name;
-
- FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{msg}");
+ string msg = Medium.getInfo(Medium.target.player, Medium.target.killerIfExisting);
+ FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, msg);
+
+ // Ghost Info
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(Medium.target.player.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.MediumInfo);
+ writer.Write(msg);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
// Remove soul
if (Medium.oneTimeUse) {
@@ -1508,6 +1585,7 @@ namespace TheOtherRoles
// Ninja mark and assassinate button
ninjaButton = new CustomButton(
() => {
+ MessageWriter writer;
if (Ninja.ninjaMarked != null) {
// Murder attempt with teleport
MurderAttemptResult attempt = Helpers.checkMuderAttempt(Ninja.ninja, Ninja.ninjaMarked);
@@ -1518,7 +1596,7 @@ namespace TheOtherRoles
Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0 * sizeof(float), sizeof(float));
Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1 * sizeof(float), sizeof(float));
- MessageWriter writer = AmongUsClient.Instance.StartRpc(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PlaceNinjaTrace, Hazel.SendOption.Reliable);
+ writer = AmongUsClient.Instance.StartRpc(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PlaceNinjaTrace, Hazel.SendOption.Reliable);
writer.WriteBytesAndSize(buff);
writer.EndMessage();
RPCProcedure.placeNinjaTrace(buff);
@@ -1565,6 +1643,13 @@ namespace TheOtherRoles
Ninja.ninjaMarked = Ninja.currentTarget;
ninjaButton.Timer = 5f;
SoundEffectsManager.play("warlockCurse");
+
+ // Ghost Info
+ writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.NinjaMarked);
+ writer.Write(Ninja.ninjaMarked.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
}
},
() => { return Ninja.ninja != null && Ninja.ninja == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; },
@@ -1647,6 +1732,75 @@ namespace TheOtherRoles
KeyCode.F
);
+ // Bomber button
+ bomberButton = new CustomButton(
+ () => {
+ if (Helpers.checkMuderAttempt(Bomber.bomber, Bomber.bomber) != MurderAttemptResult.BlankKill) {
+ var pos = CachedPlayer.LocalPlayer.transform.position;
+ byte[] buff = new byte[sizeof(float) * 2];
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0 * sizeof(float), sizeof(float));
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1 * sizeof(float), sizeof(float));
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.PlaceBomb, Hazel.SendOption.Reliable);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.placeBomb(buff);
+
+ SoundEffectsManager.play("trapperTrap");
+ }
+
+ bomberButton.Timer = bomberButton.MaxTimer;
+ Bomber.isPlanted = true;
+ },
+ () => { return Bomber.bomber != null && Bomber.bomber == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; },
+ () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && !Bomber.isPlanted; },
+ () => { bomberButton.Timer = bomberButton.MaxTimer; },
+ Bomber.getButtonSprite(),
+ CustomButton.ButtonPositions.upperRowLeft,
+ __instance,
+ KeyCode.F,
+ true,
+ Bomber.destructionTime,
+ () => {
+ bomberButton.Timer = bomberButton.MaxTimer;
+ bomberButton.isEffectActive = false;
+ bomberButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ }
+ );
+
+ defuseButton = new CustomButton(
+ () => {
+ defuseButton.HasEffect = true;
+ },
+ () => { return Bomber.bomb != null && Bomb.canDefuse && !CachedPlayer.LocalPlayer.Data.IsDead; },
+ () => {
+ if (defuseButton.isEffectActive && !Bomb.canDefuse) {
+ defuseButton.Timer = 0f;
+ defuseButton.isEffectActive = false;
+ }
+ return CachedPlayer.LocalPlayer.PlayerControl.CanMove;
+ },
+ () => {
+ defuseButton.Timer = 0f;
+ defuseButton.isEffectActive = false;
+ },
+ Bomb.getDefuseSprite(),
+ new Vector3(1f, 1f, 0),
+ __instance,
+ null,
+ true,
+ Bomber.defuseDuration,
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.DefuseBomb, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.defuseBomb();
+
+ defuseButton.Timer = 0f;
+ Bomb.canDefuse = false;
+ },
+ true
+ );
+
thiefKillButton = new CustomButton(
() => {
PlayerControl thief = Thief.thief;
@@ -1710,7 +1864,7 @@ namespace TheOtherRoles
() => { if (CachedPlayer.LocalPlayer.PlayerControl == null || !CachedPlayer.LocalPlayer.Data.IsDead || CachedPlayer.LocalPlayer.Data.Role.IsImpostor) return false;
var (playerCompleted, playerTotal) = TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data);
int numberOfLeftTasks = playerTotal - playerCompleted;
- return numberOfLeftTasks <= 0;
+ return numberOfLeftTasks <= 0 || !CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool();
},
() => { return true; },
() => { return; },
@@ -1735,11 +1889,11 @@ namespace TheOtherRoles
() => { return HideNSeek.isHunter() && !CachedPlayer.LocalPlayer.Data.IsDead; },
() => { return true; },
() => {
- hunterLighterButton.Timer = lighterButton.MaxTimer;
+ hunterLighterButton.Timer = 30f;
hunterLighterButton.isEffectActive = false;
hunterLighterButton.actionButton.graphic.color = Palette.EnabledColor;
},
- Lighter.getButtonSprite(),
+ Hunter.getLightSprite(),
CustomButton.ButtonPositions.upperRowCenter,
__instance,
KeyCode.F,
@@ -1801,7 +1955,7 @@ namespace TheOtherRoles
() => { return HideNSeek.isHunter() && !CachedPlayer.LocalPlayer.Data.IsDead; },
() => { return true; },
() => {
- hunterArrowButton.Timer = lighterButton.MaxTimer;
+ hunterArrowButton.Timer = 30f;
hunterArrowButton.isEffectActive = false;
hunterArrowButton.actionButton.graphic.color = Palette.EnabledColor;
},
diff --git a/TheOtherRoles/CustomGameModes/HideNSeekGM.cs b/TheOtherRoles/CustomGameModes/HideNSeekGM.cs
index 0526c74..1a597ef 100644
--- a/TheOtherRoles/CustomGameModes/HideNSeekGM.cs
+++ b/TheOtherRoles/CustomGameModes/HideNSeekGM.cs
@@ -36,7 +36,7 @@ namespace TheOtherRoles.CustomGameModes {
}
public static void clearAndReload() {
- isHideNSeekGM = MapOptions.gameMode == CustomGamemodes.HideNSeek;
+ isHideNSeekGM = TORMapOptions.gameMode == CustomGamemodes.HideNSeek;
if (timerText != null) UnityEngine.Object.Destroy(timerText);
timerText = null;
if (polusVent != null) UnityEngine.Object.Destroy(polusVent);
@@ -87,6 +87,12 @@ namespace TheOtherRoles.CustomGameModes {
return buttonSprite;
}
+ public static Sprite getLightSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LighterButton.png", 115f);
+ return buttonSprite;
+ }
+
public static void clearAndReload() {
if (localArrows != null) {
foreach (Arrow arrow in localArrows)
diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs
index c7ece06..bca9a45 100644
--- a/TheOtherRoles/CustomOptionHolder.cs
+++ b/TheOtherRoles/CustomOptionHolder.cs
@@ -13,6 +13,7 @@ namespace TheOtherRoles {
public static CustomOption activateRoles;
public static CustomOption crewmateRolesCountMin;
public static CustomOption crewmateRolesCountMax;
+ public static CustomOption crewmateRolesFill;
public static CustomOption neutralRolesCountMin;
public static CustomOption neutralRolesCountMax;
public static CustomOption impostorRolesCountMin;
@@ -20,6 +21,8 @@ namespace TheOtherRoles {
public static CustomOption modifiersCountMin;
public static CustomOption modifiersCountMax;
+ public static CustomOption enableCodenameHorsemode;
+
public static CustomOption mafiaSpawnRate;
public static CustomOption janitorCooldown;
@@ -95,12 +98,14 @@ namespace TheOtherRoles {
public static CustomOption mayorTasksNeededToSeeVoteColors;
public static CustomOption mayorMeetingButton;
public static CustomOption mayorMaxRemoteMeetings;
+ public static CustomOption mayorChooseSingleVote;
public static CustomOption portalmakerSpawnRate;
public static CustomOption portalmakerCooldown;
public static CustomOption portalmakerUsePortalCooldown;
public static CustomOption portalmakerLogOnlyColorType;
public static CustomOption portalmakerLogHasTime;
+ public static CustomOption portalmakerCanPortalFromAnywhere;
public static CustomOption engineerSpawnRate;
public static CustomOption engineerNumberOfFixes;
@@ -122,8 +127,7 @@ namespace TheOtherRoles {
public static CustomOption lighterSpawnRate;
public static CustomOption lighterModeLightsOnVision;
public static CustomOption lighterModeLightsOffVision;
- public static CustomOption lighterCooldown;
- public static CustomOption lighterDuration;
+ public static CustomOption lighterFlashlightWidth;
public static CustomOption detectiveSpawnRate;
public static CustomOption detectiveAnonymousFootprints;
@@ -172,8 +176,6 @@ namespace TheOtherRoles {
public static CustomOption snitchSpawnRate;
public static CustomOption snitchLeftTasksForReveal;
- public static CustomOption snitchIncludeTeamJackal;
- public static CustomOption snitchTeamJackalUseDifferentArrowColor;
public static CustomOption spySpawnRate;
public static CustomOption spyCanDieToSheriff;
@@ -213,6 +215,7 @@ namespace TheOtherRoles {
public static CustomOption mediumCooldown;
public static CustomOption mediumDuration;
public static CustomOption mediumOneTimeUse;
+ public static CustomOption mediumChanceAdditionalInfo;
public static CustomOption lawyerSpawnRate;
public static CustomOption lawyerIsProsecutorChance;
@@ -239,6 +242,14 @@ namespace TheOtherRoles {
public static CustomOption trapperInfoType;
public static CustomOption trapperTrapDuration;
+ public static CustomOption bomberSpawnRate;
+ public static CustomOption bomberBombDestructionTime;
+ public static CustomOption bomberBombDestructionRange;
+ public static CustomOption bomberBombHearRange;
+ public static CustomOption bomberDefuseDuration;
+ public static CustomOption bomberBombCooldown;
+ public static CustomOption bomberBombActiveAfter;
+
public static CustomOption modifiersAreHidden;
public static CustomOption modifierBait;
@@ -291,6 +302,9 @@ namespace TheOtherRoles {
public static CustomOption hidePlayerNames;
public static CustomOption allowParallelMedBayScans;
public static CustomOption shieldFirstKill;
+ public static CustomOption finishTasksBeforeHauntingOrZoomingOut;
+ public static CustomOption camsNightVision;
+ public static CustomOption camsNoNightVisionIfImpVision;
public static CustomOption dynamicMap;
public static CustomOption dynamicMapEnableSkeld;
@@ -362,9 +376,12 @@ namespace TheOtherRoles {
presetSelection = CustomOption.Create(0, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true);
activateRoles = CustomOption.Create(1, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Enable Mod Roles And Block Vanilla Roles"), true, null, true);
+ if (Utilities.EventUtility.canBeEnabled) enableCodenameHorsemode = CustomOption.Create(10423, Types.General, cs(Color.green, "Enable Codename Horse"), false, null, true);
+
// Using new id's for the options to not break compatibilty with older versions
crewmateRolesCountMin = CustomOption.Create(300, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 15f, 0f, 15f, 1f, null, true);
crewmateRolesCountMax = CustomOption.Create(301, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 15f, 0f, 15f, 1f);
+ crewmateRolesFill = CustomOption.Create(308, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Fill Crewmate Roles\n(Ignores Min/Max)"), false);
neutralRolesCountMin = CustomOption.Create(302, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Neutral Roles"), 15f, 0f, 15f, 1f);
neutralRolesCountMax = CustomOption.Create(303, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Neutral Roles"), 15f, 0f, 15f, 1f);
impostorRolesCountMin = CustomOption.Create(304, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Impostor Roles"), 15f, 0f, 15f, 1f);
@@ -372,8 +389,8 @@ namespace TheOtherRoles {
modifiersCountMin = CustomOption.Create(306, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Modifiers"), 15f, 0f, 15f, 1f);
modifiersCountMax = CustomOption.Create(307, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Modifiers"), 15f, 0f, 15f, 1f);
- mafiaSpawnRate = CustomOption.Create(10, Types.Impostor, cs(Janitor.color, "Mafia"), rates, null, true);
- janitorCooldown = CustomOption.Create(11, Types.Impostor, "Janitor Cooldown", 30f, 10f, 60f, 2.5f, mafiaSpawnRate);
+ mafiaSpawnRate = CustomOption.Create(18, Types.Impostor, cs(Janitor.color, "Mafia"), rates, null, true);
+ janitorCooldown = CustomOption.Create(19, Types.Impostor, "Janitor Cooldown", 30f, 10f, 60f, 2.5f, mafiaSpawnRate);
morphlingSpawnRate = CustomOption.Create(20, Types.Impostor, cs(Morphling.color, "Morphling"), rates, null, true);
morphlingCooldown = CustomOption.Create(21, Types.Impostor, "Morphling Cooldown", 30f, 10f, 60f, 2.5f, morphlingSpawnRate);
@@ -426,6 +443,14 @@ namespace TheOtherRoles {
ninjaTraceColorTime = CustomOption.Create(384, Types.Impostor, "Time Till Trace Color Has Faded", 2f, 0f, 20f, 0.5f, ninjaSpawnRate);
ninjaInvisibleDuration = CustomOption.Create(385, Types.Impostor, "Time The Ninja Is Invisible", 3f, 0f, 20f, 1f, ninjaSpawnRate);
+ bomberSpawnRate = CustomOption.Create(460, Types.Impostor, cs(Bomber.color, "Bomber"), rates, null, true);
+ bomberBombDestructionTime = CustomOption.Create(461, Types.Impostor, "Bomb Destruction Time", 20f, 2.5f, 120f, 2.5f, bomberSpawnRate);
+ bomberBombDestructionRange = CustomOption.Create(462, Types.Impostor, "Bomb Destruction Range", 50f, 5f, 150f, 5f, bomberSpawnRate);
+ bomberBombHearRange = CustomOption.Create(463, Types.Impostor, "Bomb Hear Range", 60f, 5f, 150f, 5f, bomberSpawnRate);
+ bomberDefuseDuration = CustomOption.Create(464, Types.Impostor, "Bomb Defuse Duration", 3f, 0.5f, 30f, 0.5f, bomberSpawnRate);
+ bomberBombCooldown = CustomOption.Create(465, Types.Impostor, "Bomb Cooldown", 15f, 2.5f, 30f, 2.5f, bomberSpawnRate);
+ bomberBombActiveAfter = CustomOption.Create(466, Types.Impostor, "Bomb Is Active After", 3f, 0.5f, 15f, 0.5f, bomberSpawnRate);
+
guesserSpawnRate = CustomOption.Create(310, Types.Neutral, cs(Guesser.color, "Guesser"), rates, null, true);
guesserIsImpGuesserRate = CustomOption.Create(311, Types.Neutral, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate);
guesserNumberOfShots = CustomOption.Create(312, Types.Neutral, "Guesser Number Of Shots", 2f, 1f, 15f, 1f, guesserSpawnRate);
@@ -475,6 +500,7 @@ namespace TheOtherRoles {
mayorTasksNeededToSeeVoteColors = CustomOption.Create(82, Types.Crewmate, "Completed Tasks Needed To See Vote Colors", 5f, 0f, 20f, 1f, mayorCanSeeVoteColors);
mayorMeetingButton = CustomOption.Create(83, Types.Crewmate, "Mobile Emergency Button", true, mayorSpawnRate);
mayorMaxRemoteMeetings = CustomOption.Create(84, Types.Crewmate, "Number Of Remote Meetings", 1f, 1f, 5f, 1f, mayorMeetingButton);
+ mayorChooseSingleVote = CustomOption.Create(85, Types.Crewmate, "Mayor Can Choose Single Vote", new string[] { "Off", "On (Before Voting)", "On (Until Meeting Ends)" }, mayorSpawnRate);
engineerSpawnRate = CustomOption.Create(90, Types.Crewmate, cs(Engineer.color, "Engineer"), rates, null, true);
engineerNumberOfFixes = CustomOption.Create(91, Types.Crewmate, "Number Of Sabotage Fixes", 1f, 1f, 3f, 1f, engineerSpawnRate);
@@ -493,10 +519,9 @@ namespace TheOtherRoles {
deputyKeepsHandcuffs = CustomOption.Create(109, Types.Crewmate, "Deputy Keeps Handcuffs When Promoted", true, deputyGetsPromoted);
lighterSpawnRate = CustomOption.Create(110, Types.Crewmate, cs(Lighter.color, "Lighter"), rates, null, true);
- lighterModeLightsOnVision = CustomOption.Create(111, Types.Crewmate, "Lighter Mode Vision On Lights On", 2f, 0.25f, 5f, 0.25f, lighterSpawnRate);
- lighterModeLightsOffVision = CustomOption.Create(112, Types.Crewmate, "Lighter Mode Vision On Lights Off", 0.75f, 0.25f, 5f, 0.25f, lighterSpawnRate);
- lighterCooldown = CustomOption.Create(113, Types.Crewmate, "Lighter Cooldown", 30f, 5f, 120f, 5f, lighterSpawnRate);
- lighterDuration = CustomOption.Create(114, Types.Crewmate, "Lighter Duration", 5f, 2.5f, 60f, 2.5f, lighterSpawnRate);
+ lighterModeLightsOnVision = CustomOption.Create(111, Types.Crewmate, "Vision On Lights On", 1.5f, 0.25f, 5f, 0.25f, lighterSpawnRate);
+ lighterModeLightsOffVision = CustomOption.Create(112, Types.Crewmate, "Vision On Lights Off", 0.5f, 0.25f, 5f, 0.25f, lighterSpawnRate);
+ lighterFlashlightWidth = CustomOption.Create(113, Types.Crewmate, "Flashlight Width", 0.3f, 0.1f, 1f, 0.1f, lighterSpawnRate);
detectiveSpawnRate = CustomOption.Create(120, Types.Crewmate, cs(Detective.color, "Detective"), rates, null, true);
detectiveAnonymousFootprints = CustomOption.Create(121, Types.Crewmate, "Anonymous Footprints", false, detectiveSpawnRate);
@@ -546,9 +571,7 @@ namespace TheOtherRoles {
trackerCorpsesTrackingDuration = CustomOption.Create(205, Types.Crewmate, "Corpses Tracking Duration", 5f, 2.5f, 30f, 2.5f, trackerCanTrackCorpses);
snitchSpawnRate = CustomOption.Create(210, Types.Crewmate, cs(Snitch.color, "Snitch"), rates, null, true);
- snitchLeftTasksForReveal = CustomOption.Create(211, Types.Crewmate, "Task Count Where The Snitch Will Be Revealed", 1f, 0f, 5f, 1f, snitchSpawnRate);
- snitchIncludeTeamJackal = CustomOption.Create(212, Types.Crewmate, "Include Team Jackal", false, snitchSpawnRate);
- snitchTeamJackalUseDifferentArrowColor = CustomOption.Create(213, Types.Crewmate, "Use Different Arrow Color For Team Jackal", true, snitchIncludeTeamJackal);
+ snitchLeftTasksForReveal = CustomOption.Create(219, Types.Crewmate, "Task Count Where The Snitch Will Be Revealed", 5f, 0f, 25f, 1f, snitchSpawnRate);
spySpawnRate = CustomOption.Create(240, Types.Crewmate, cs(Spy.color, "Spy"), rates, null, true);
spyCanDieToSheriff = CustomOption.Create(241, Types.Crewmate, "Spy Can Die To Sheriff", false, spySpawnRate);
@@ -561,6 +584,8 @@ namespace TheOtherRoles {
portalmakerUsePortalCooldown = CustomOption.Create(392, Types.Crewmate, "Use Portal Cooldown", 30f, 10f, 60f, 2.5f, portalmakerSpawnRate);
portalmakerLogOnlyColorType = CustomOption.Create(393, Types.Crewmate, "Portalmaker Log Only Shows Color Type", true, portalmakerSpawnRate);
portalmakerLogHasTime = CustomOption.Create(394, Types.Crewmate, "Log Shows Time", true, portalmakerSpawnRate);
+ portalmakerCanPortalFromAnywhere = CustomOption.Create(395, Types.Crewmate, "Can Port To Portal From Everywhere", true, portalmakerSpawnRate);
+
securityGuardSpawnRate = CustomOption.Create(280, Types.Crewmate, cs(SecurityGuard.color, "Security Guard"), rates, null, true);
securityGuardCooldown = CustomOption.Create(281, Types.Crewmate, "Security Guard Cooldown", 30f, 10f, 60f, 2.5f, securityGuardSpawnRate);
securityGuardTotalScrews = CustomOption.Create(282, Types.Crewmate, "Security Guard Number Of Screws", 7f, 1f, 15f, 1f, securityGuardSpawnRate);
@@ -575,6 +600,7 @@ namespace TheOtherRoles {
mediumCooldown = CustomOption.Create(361, Types.Crewmate, "Medium Questioning Cooldown", 30f, 5f, 120f, 5f, mediumSpawnRate);
mediumDuration = CustomOption.Create(362, Types.Crewmate, "Medium Questioning Duration", 3f, 0f, 15f, 1f, mediumSpawnRate);
mediumOneTimeUse = CustomOption.Create(363, Types.Crewmate, "Each Soul Can Only Be Questioned Once", false, mediumSpawnRate);
+ mediumChanceAdditionalInfo = CustomOption.Create(364, Types.Crewmate, "Chance That The Answer Contains \n Additional Information", rates, mediumSpawnRate);
thiefSpawnRate = CustomOption.Create(400, Types.Neutral, cs(Thief.color, "Thief"), rates, null, true);
thiefCooldown = CustomOption.Create(401, Types.Neutral, "Thief Cooldown", 30f, 5f, 120f, 5f, thiefSpawnRate);
@@ -688,14 +714,18 @@ namespace TheOtherRoles {
hidePlayerNames = CustomOption.Create(6, Types.General, "Hide Player Names", false);
allowParallelMedBayScans = CustomOption.Create(7, Types.General, "Allow Parallel MedBay Scans", false);
shieldFirstKill = CustomOption.Create(8, Types.General, "Shield Last Game First Kill", false);
+ finishTasksBeforeHauntingOrZoomingOut = CustomOption.Create(9, Types.General, "Finish Tasks Before Haunting Or Zooming Out", true);
+ camsNightVision = CustomOption.Create(11, Types.General, "Cams Switch To Night Vision If Lights Are Off", false, null, true);
+ camsNoNightVisionIfImpVision = CustomOption.Create(12, Types.General, "Impostor Vision Ignores Night Vision Cams", false, camsNightVision, false);
+
- dynamicMap = CustomOption.Create(500, Types.General, "Play On A Random Map", false, null, false);
+ dynamicMap = CustomOption.Create(500, Types.General, "Play On A Random Map", false, null, true);
dynamicMapEnableSkeld = CustomOption.Create(501, Types.General, "Skeld", rates, dynamicMap, false);
dynamicMapEnableMira = CustomOption.Create(502, Types.General, "Mira", rates, dynamicMap, false);
dynamicMapEnablePolus = CustomOption.Create(503, Types.General, "Polus", rates, dynamicMap, false);
dynamicMapEnableAirShip = CustomOption.Create(504, Types.General, "Airship", rates, dynamicMap, false);
dynamicMapEnableSubmerged = CustomOption.Create(505, Types.General, "Submerged", rates, dynamicMap, false);
- dynamicMapSeparateSettings = CustomOption.Create(509, Types.General, "Use Random Map Setting Presets", true, dynamicMap, false);
+ dynamicMapSeparateSettings = CustomOption.Create(509, Types.General, "Use Random Map Setting Presets", false, dynamicMap, false);
blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock});
blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire});
diff --git a/TheOtherRoles/GameHistory.cs b/TheOtherRoles/GameHistory.cs
index 90611b3..2d9e75e 100644
--- a/TheOtherRoles/GameHistory.cs
+++ b/TheOtherRoles/GameHistory.cs
@@ -9,12 +9,14 @@ namespace TheOtherRoles {
public DateTime timeOfDeath;
public DeathReason deathReason;
public PlayerControl killerIfExisting;
+ public bool wasCleaned;
public DeadPlayer(PlayerControl player, DateTime timeOfDeath, DeathReason deathReason, PlayerControl killerIfExisting) {
this.player = player;
this.timeOfDeath = timeOfDeath;
this.deathReason = deathReason;
this.killerIfExisting = killerIfExisting;
+ this.wasCleaned = false;
}
}
diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs
index a0c661a..d07e230 100644
--- a/TheOtherRoles/Helpers.cs
+++ b/TheOtherRoles/Helpers.cs
@@ -13,6 +13,7 @@ using TheOtherRoles.Utilities;
using System.Threading.Tasks;
using System.Net;
using TheOtherRoles.CustomGameModes;
+using Reactor.Utilities.Extensions;
namespace TheOtherRoles {
@@ -54,6 +55,9 @@ namespace TheOtherRoles {
var length = stream.Length;
var byteTexture = new Il2CppStructArray(length);
stream.Read(new Span(IntPtr.Add(byteTexture.Pointer, IntPtr.Size * 4).ToPointer(), (int) length));
+ if (path.Contains("HorseHats")) {
+ byteTexture = new Il2CppStructArray(byteTexture.Reverse().ToArray());
+ }
ImageConversion.LoadImage(texture, byteTexture, false);
return texture;
} catch {
@@ -71,7 +75,7 @@ namespace TheOtherRoles {
return texture;
}
} catch {
- System.Console.WriteLine("Error loading texture from disk: " + path);
+ TheOtherRolesPlugin.Logger.LogError("Error loading texture from disk: " + path);
}
return null;
}
@@ -91,7 +95,7 @@ namespace TheOtherRoles {
}
int channels = 2;
int sampleRate = 48000;
- AudioClip audioClip = AudioClip.Create(clipName, samples.Length, channels, sampleRate, false);
+ AudioClip audioClip = AudioClip.Create(clipName, samples.Length / 2, channels, sampleRate, false);
audioClip.SetData(samples, 0);
return audioClip;
} catch {
@@ -200,6 +204,10 @@ namespace TheOtherRoles {
return (player != Jackal.jackal && player != Sidekick.sidekick && !Jackal.formerJackals.Any(x => x == player));
}
+ public static bool shouldShowGhostInfo() {
+ return CachedPlayer.LocalPlayer.PlayerControl != null && CachedPlayer.LocalPlayer.PlayerControl.Data.IsDead && TORMapOptions.ghostsSeeInformation;
+ }
+
public static void clearAllTasks(this PlayerControl player) {
if (player == null) return;
foreach (var playerTask in player.myTasks.GetFastEnumerator())
@@ -227,7 +235,11 @@ namespace TheOtherRoles {
public static string cs(Color c, string s) {
return string.Format("{4}", ToByte(c.r), ToByte(c.g), ToByte(c.b), ToByte(c.a), s);
}
-
+
+ public static int lineCount(string text) {
+ return text.Count(c => c == '\n');
+ }
+
private static byte ToByte(float f) {
f = Mathf.Clamp01(f);
return (byte)(f * 255);
@@ -253,8 +265,9 @@ namespace TheOtherRoles {
public static bool hidePlayerName(PlayerControl source, PlayerControl target) {
if (Camouflager.camouflageTimer > 0f) return true; // No names are visible
- else if (Ninja.isInvisble && Ninja.ninja == target) return true;
- else if (!MapOptions.hidePlayerNames) return false; // All names are visible
+ if (Patches.SurveillanceMinigamePatch.nightVisionIsActive) return true;
+ else if (Ninja.isInvisble && Ninja.ninja == target) return true;
+ else if (!TORMapOptions.hidePlayerNames) return false; // All names are visible
else if (source == null || target == null) return true;
else if (source == target) return false; // Player sees his own name
else if (source.Data.Role.IsImpostor && (target.Data.Role.IsImpostor || target == Spy.spy || target == Sidekick.sidekick && Sidekick.wasTeamRed || target == Jackal.jackal && Jackal.wasTeamRed)) return false; // Members of team Impostors see the names of Impostors/Spies
@@ -264,11 +277,11 @@ namespace TheOtherRoles {
return true;
}
- public static void setDefaultLook(this PlayerControl target) {
- target.setLook(target.Data.PlayerName, target.Data.DefaultOutfit.ColorId, target.Data.DefaultOutfit.HatId, target.Data.DefaultOutfit.VisorId, target.Data.DefaultOutfit.SkinId, target.Data.DefaultOutfit.PetId);
+ public static void setDefaultLook(this PlayerControl target, bool enforceNightVisionUpdate = true) {
+ target.setLook(target.Data.PlayerName, target.Data.DefaultOutfit.ColorId, target.Data.DefaultOutfit.HatId, target.Data.DefaultOutfit.VisorId, target.Data.DefaultOutfit.SkinId, target.Data.DefaultOutfit.PetId, enforceNightVisionUpdate);
}
- public static void setLook(this PlayerControl target, String playerName, int colorId, string hatId, string visorId, string skinId, string petId) {
+ public static void setLook(this PlayerControl target, String playerName, int colorId, string hatId, string visorId, string skinId, string petId, bool enforceNightVisionUpdate = true) {
target.RawSetColor(colorId);
target.RawSetVisor(visorId, colorId);
target.RawSetHat(hatId, colorId);
@@ -302,13 +315,21 @@ namespace TheOtherRoles {
target.cosmetics.currentPet.Visible = target.Visible;
target.SetPlayerMaterialColors(target.cosmetics.currentPet.rend);
+ if (enforceNightVisionUpdate) Patches.SurveillanceMinigamePatch.enforceNightVision(target);
Chameleon.update(); // so that morphling and camo wont make the chameleons visible
}
- public static void showFlash(Color color, float duration=1f) {
+ public static void showFlash(Color color, float duration=1f, string message="") {
if (FastDestroyableSingleton.Instance == null || FastDestroyableSingleton.Instance.FullScreen == null) return;
FastDestroyableSingleton.Instance.FullScreen.gameObject.SetActive(true);
FastDestroyableSingleton.Instance.FullScreen.enabled = true;
+ // Message Text
+ TMPro.TextMeshPro messageText = GameObject.Instantiate(FastDestroyableSingleton.Instance.KillButton.cooldownTimerText, FastDestroyableSingleton.Instance.transform);
+ messageText.text = message;
+ messageText.enableWordWrapping = false;
+ messageText.transform.localScale = Vector3.one * 0.5f;
+ messageText.transform.localPosition += new Vector3(0f, 2f, -69f);
+ messageText.gameObject.SetActive(true);
FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(duration, new Action((p) => {
var renderer = FastDestroyableSingleton.Instance.FullScreen;
@@ -320,6 +341,7 @@ namespace TheOtherRoles {
renderer.color = new Color(color.r, color.g, color.b, Mathf.Clamp01((1 - p) * 2 * 0.75f));
}
if (p == 1f && renderer != null) renderer.enabled = false;
+ if (p == 1f) messageText.gameObject.Destroy();
})));
}
@@ -348,19 +370,19 @@ namespace TheOtherRoles {
return roleCouldUse;
}
- public static MurderAttemptResult checkMuderAttempt(PlayerControl killer, PlayerControl target, bool blockRewind = false) {
+ public static MurderAttemptResult checkMuderAttempt(PlayerControl killer, PlayerControl target, bool blockRewind = false, bool ignoreBlank = false, bool ignoreIfKillerIsDead = false) {
var targetRole = RoleInfo.getRoleInfoForPlayer(target, false).FirstOrDefault();
// Modified vanilla checks
if (AmongUsClient.Instance.IsGameOver) return MurderAttemptResult.SuppressKill;
- if (killer == null || killer.Data == null || killer.Data.IsDead || killer.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow non Impostor kills compared to vanilla code
+ if (killer == null || killer.Data == null || (killer.Data.IsDead && !ignoreIfKillerIsDead) || killer.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow non Impostor kills compared to vanilla code
if (target == null || target.Data == null || target.Data.IsDead || target.Data.Disconnected) return MurderAttemptResult.SuppressKill; // Allow killing players in vents compared to vanilla code
// Handle first kill attempt
- if (MapOptions.shieldFirstKill && MapOptions.firstKillPlayer == target) return MurderAttemptResult.SuppressKill;
+ if (TORMapOptions.shieldFirstKill && TORMapOptions.firstKillPlayer == target) return MurderAttemptResult.SuppressKill;
// Handle blank shot
- if (Pursuer.blankedList.Any(x => x.PlayerId == killer.PlayerId)) {
+ if (!ignoreBlank && Pursuer.blankedList.Any(x => x.PlayerId == killer.PlayerId)) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetBlanked, Hazel.SendOption.Reliable, -1);
writer.Write(killer.PlayerId);
writer.Write((byte)0);
@@ -413,11 +435,11 @@ namespace TheOtherRoles {
return MurderAttemptResult.PerformKill;
}
- public static MurderAttemptResult checkMurderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false, bool showAnimation = true) {
+ public static MurderAttemptResult checkMurderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false, bool showAnimation = true, bool ignoreBlank = false, bool ignoreIfKillerIsDead = false) {
// The local player checks for the validity of the kill and performs it afterwards (different to vanilla, where the host performs all the checks)
// The kill attempt will be shared using a custom RPC, hence combining modded and unmodded versions is impossible
- MurderAttemptResult murder = checkMuderAttempt(killer, target, isMeetingStart);
+ MurderAttemptResult murder = checkMuderAttempt(killer, target, isMeetingStart, ignoreBlank, ignoreIfKillerIsDead);
if (murder == MurderAttemptResult.PerformKill) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
writer.Write(killer.PlayerId);
diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs
index 42f2044..99a02f9 100644
--- a/TheOtherRoles/Main.cs
+++ b/TheOtherRoles/Main.cs
@@ -31,19 +31,19 @@ namespace TheOtherRoles
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "4.2.1";
+ public const string VersionString = "4.3.0";
public static uint betaDays = 0; // amount of days for the build to be usable (0 for infinite!)
public static Version Version = Version.Parse(VersionString);
internal static BepInEx.Logging.ManualLogSource Logger;
-
+
public Harmony Harmony { get; } = new Harmony(Id);
public static TheOtherRolesPlugin Instance;
public static int optionsPage = 2;
public static ConfigEntry DebugMode { get; private set; }
- public static ConfigEntry GhostsSeeTasks { get; set; }
+ public static ConfigEntry GhostsSeeInformation { get; set; }
public static ConfigEntry GhostsSeeRoles { get; set; }
public static ConfigEntry GhostsSeeModifier { get; set; }
public static ConfigEntry GhostsSeeVotes{ get; set; }
@@ -59,12 +59,13 @@ namespace TheOtherRoles
public static IRegionInfo[] defaultRegions;
+
// This is part of the Mini.RegionInstaller, Licensed under GPLv3
// file="RegionInstallPlugin.cs" company="miniduikboot">
public static void UpdateRegions() {
ServerManager serverManager = FastDestroyableSingleton.Instance;
var regions = new IRegionInfo[] {
- new DnsRegionInfo(Ip.Value, "Custom", StringNames.NoTranslation, Ip.Value, Port.Value, false).CastFast()
+ new StaticHttpRegionInfo("Custom", StringNames.NoTranslation, Ip.Value, new Il2CppReferenceArray(new ServerInfo[1] { new ServerInfo("Custom", Ip.Value, Port.Value, false) })).CastFast()
};
IRegionInfo currentRegion = serverManager.CurrentRegion;
@@ -89,11 +90,11 @@ namespace TheOtherRoles
public override void Load() {
Logger = Log;
Instance = this;
-
+
_ = Helpers.checkBeta(); // Exit if running an expired beta
DebugMode = Config.Bind("Custom", "Enable Debug Mode", "false");
- GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true);
+ GhostsSeeInformation = Config.Bind("Custom", "Ghosts See Remaining Tasks", true);
GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true);
GhostsSeeModifier = Config.Bind("Custom", "Ghosts See Modifier", true);
GhostsSeeVotes = Config.Bind("Custom", "Ghosts See Votes", true);
@@ -103,7 +104,6 @@ namespace TheOtherRoles
EnableHorseMode = Config.Bind("Custom", "Enable Horse Mode", false);
ShowPopUpVersion = Config.Bind("Custom", "Show PopUp", "0");
-
Ip = Config.Bind("Custom", "Custom Server IP", "127.0.0.1");
Port = Config.Bind("Custom", "Custom Server Port", (ushort)22023);
defaultRegions = ServerManager.DefaultRegions;
@@ -121,7 +121,9 @@ namespace TheOtherRoles
AddComponent();
return;
}
-
+
+ EventUtility.Load();
+
SubmergedCompatibility.Initialize();
AddComponent();
Modules.MainMenuPatch.addSceneChangeCallbacks();
diff --git a/TheOtherRoles/MapOptions.cs b/TheOtherRoles/MapOptions.cs
index 9523edc..7999494 100644
--- a/TheOtherRoles/MapOptions.cs
+++ b/TheOtherRoles/MapOptions.cs
@@ -2,7 +2,7 @@ using System.Collections.Generic;
using UnityEngine;
namespace TheOtherRoles{
- static class MapOptions {
+ static class TORMapOptions {
// Set values
public static int maxNumberOfMeetings = 10;
public static bool blockSkippingInEmergencyMeetings = false;
@@ -10,7 +10,7 @@ namespace TheOtherRoles{
public static bool hidePlayerNames = false;
public static bool ghostsSeeRoles = true;
public static bool ghostsSeeModifier = true;
- public static bool ghostsSeeTasks = true;
+ public static bool ghostsSeeInformation = true;
public static bool ghostsSeeVotes = true;
public static bool showRoleSummary = true;
public static bool allowParallelMedBayScans = false;
@@ -46,7 +46,7 @@ namespace TheOtherRoles{
public static void reloadPluginOptions() {
ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value;
ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value;
- ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value;
+ ghostsSeeInformation = TheOtherRolesPlugin.GhostsSeeInformation.Value;
ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value;
showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value;
showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value;
diff --git a/TheOtherRoles/Modules/CustomHats.cs b/TheOtherRoles/Modules/CustomHats.cs
index 67c14a4..aeb250e 100644
--- a/TheOtherRoles/Modules/CustomHats.cs
+++ b/TheOtherRoles/Modules/CustomHats.cs
@@ -13,6 +13,9 @@ using TheOtherRoles.Players;
using TheOtherRoles.Utilities;
using AmongUs.Data;
using AmongUs.Data.Legacy;
+using System.Reflection;
+using static Rewired.Controller;
+using static TheOtherRoles.Modules.CustomHatLoader;
namespace TheOtherRoles.Modules {
[HarmonyPatch]
@@ -116,7 +119,9 @@ namespace TheOtherRoles.Modules {
return null;
texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
+
return sprite;
+
}
private static HatData CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) {
@@ -165,18 +170,21 @@ namespace TheOtherRoles.Modules {
return hat;
}
- private static HatData CreateHatBehaviour(CustomHatLoader.CustomHatOnline chd) {
- string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\";
- chd.resource = filePath + chd.resource;
- if (chd.backresource != null)
- chd.backresource = filePath + chd.backresource;
- if (chd.climbresource != null)
- chd.climbresource = filePath + chd.climbresource;
- if (chd.flipresource != null)
- chd.flipresource = filePath + chd.flipresource;
- if (chd.backflipresource != null)
- chd.backflipresource = filePath + chd.backflipresource;
- return CreateHatBehaviour(chd, true);
+ private static HatData CreateHatBehaviour(CustomHatLoader.CustomHatOnline chd, bool fromDisk = true) {
+ if (fromDisk) {
+ string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\";
+ chd.resource = filePath + chd.resource;
+ if (chd.backresource != null)
+ chd.backresource = filePath + chd.backresource;
+ if (chd.climbresource != null)
+ chd.climbresource = filePath + chd.climbresource;
+ if (chd.flipresource != null)
+ chd.flipresource = filePath + chd.flipresource;
+ if (chd.backflipresource != null)
+ chd.backflipresource = filePath + chd.backflipresource;
+ }
+
+ return CreateHatBehaviour(chd, fromDisk, false);
}
[HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))]
@@ -189,7 +197,8 @@ namespace TheOtherRoles.Modules {
try {
while (CustomHatLoader.hatdetails.Count > 0) {
- allHatsList.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0]));
+ bool fromDisk = !(CustomHatLoader.hatdetails[0].name.Contains("HorseHat_") && (EventUtility.canBeEnabled || EventUtility.isEnabled));
+ allHatsList.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0], fromDisk));
CustomHatLoader.hatdetails.RemoveAt(0);
}
__instance.allHats = allHatsList.ToArray();
@@ -230,14 +239,6 @@ namespace TheOtherRoles.Modules {
}
}
- // public void SetHat(HatData hat, HatViewData hatViewData, int color)
- // {
- // this.Hat = hat;
- // this.hatView = hatViewData;
- // this.PopulateFromHatViewData();
- // this.SetColor(color);
- // }
-
[HarmonyPatch]
private static class FreeplayHatTestingPatches
{
@@ -386,6 +387,8 @@ namespace TheOtherRoles.Modules {
}
}
+ packages.Remove("Horse Hats"); // Cannot be selected!
+
float YOffset = __instance.YStart;
textTemplate = GameObject.Find("HatsGroup").transform.FindChild("Text").GetComponent();
@@ -426,7 +429,7 @@ namespace TheOtherRoles.Modules {
} catch (System.Exception e) {
System.Console.WriteLine("Unable to fetch hats\n" + e.Message);
}
- running = false;
+ running = false;
}
private static string sanitizeResourcePath(string res) {
@@ -513,6 +516,9 @@ namespace TheOtherRoles.Modules {
}
}
+ if (EventUtility.canBeEnabled || EventUtility.isEnabled) addHorseHats(ref hatdatas);
+
+
hatdetails = hatdatas;
} catch (System.Exception ex) {
TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
@@ -531,6 +537,44 @@ namespace TheOtherRoles.Modules {
}
}
+
+ public static List horseHatProductIds = null;
+ private static void addHorseHats(ref List hatdatas) {
+
+ horseHatProductIds = new();
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ string[] resourceNames = assembly.GetManifestResourceNames();
+ List hatFiles = new();
+ Dictionary> hatFilesSorted = new Dictionary>();
+ foreach (string resourceName in resourceNames) {
+ if (resourceName.Contains("TheOtherRoles.Resources.HorseHats.") && resourceName.Contains(".png")) {
+ hatFiles.Add(resourceName);
+ }
+ }
+
+ foreach (string s in hatFiles) {
+ string value = s.Substring(0, s.LastIndexOf("HorseSpecialHat") + 17);
+ if (value.Contains(".")) value.Remove(value.LastIndexOf("."));
+ if (!hatFilesSorted.ContainsKey(value)) hatFilesSorted.Add(value, new List());
+ hatFilesSorted[value].Add(s);
+ }
+
+ foreach (var item in hatFilesSorted) {
+ CustomHatOnline info = new CustomHatOnline();
+ info.name = "HorseHat_" + item.Key;
+ info.resource = item.Value.FirstOrDefault(x => !x.Contains("back"));
+ info.backresource = item.Value.FirstOrDefault(x => x.Contains("back"));
+ info.adaptive = info.resource != null && info.resource.Contains("adaptive");
+ info.flipresource = item.Value.FirstOrDefault(x => x.Contains("flip"));
+ info.climbresource = item.Value.FirstOrDefault(x => x.Contains("climb"));
+ info.package = "Horse Hats";
+ if (info.resource == null || info.name == null) // required
+ continue;
+ horseHatProductIds.Add("hat_" + info.name.Replace(" ", "_"));
+ hatdatas.Add(info);
+ }
+ }
+
public class CustomHatOnline : CustomHats.CustomHat {
public string reshasha { get; set;}
public string reshashb { get; set;}
diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs
index d496a86..a16f389 100644
--- a/TheOtherRoles/Modules/CustomOptions.cs
+++ b/TheOtherRoles/Modules/CustomOptions.cs
@@ -11,6 +11,7 @@ using TheOtherRoles.Players;
using TheOtherRoles.Utilities;
using static TheOtherRoles.TheOtherRoles;
using static TheOtherRoles.CustomOption;
+using Reactor.Utilities.Extensions;
namespace TheOtherRoles {
public class CustomOption {
@@ -182,7 +183,7 @@ namespace TheOtherRoles {
class GameOptionsMenuStartPatch {
public static void Postfix(GameOptionsMenu __instance) {
- switch (MapOptions.gameMode) {
+ switch (TORMapOptions.gameMode) {
case CustomGamemodes.Classic:
createClassicTabs(__instance);
break;
@@ -546,6 +547,43 @@ namespace TheOtherRoles {
}
+ private static void setListener(Dictionary settingsHighlightMap, int index) {
+ foreach (KeyValuePair entry in settingsHighlightMap) {
+ entry.Key.SetActive(false);
+ entry.Value.enabled = false;
+ }
+ settingsHighlightMap.ElementAt(index).Key.SetActive(true);
+ settingsHighlightMap.ElementAt(index).Value.enabled = true;
+ }
+
+ private static void destroyOptions (List> optionBehavioursList) {
+ foreach (List optionBehaviours in optionBehavioursList) {
+ foreach (OptionBehaviour option in optionBehaviours)
+ UnityEngine.Object.Destroy(option.gameObject);
+ }
+ }
+
+ private static bool setNames (Dictionary gameObjectNameDisplayNameMap) {
+ foreach (KeyValuePair entry in gameObjectNameDisplayNameMap) {
+ if (GameObject.Find(entry.Key) != null) { // Settings setup has already been performed, fixing the title of the tab and returning
+ GameObject.Find(entry.Key).transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText(entry.Value);
+ return true;
+ }
+ }
+
+ setOptions(
+ new List{torMenu, hideNSeekMenu},
+ new List> {torOptions, hideNSeekOptions},
+ new List {torSettings, hideNSeekSettings}
+ );
+
+ torSettings.gameObject.SetActive(true);
+ torTabHighlight.enabled = true;
+ gameSettingMenu.RegularGameSettings.SetActive(false);
+ gameSettingMenu.GameSettingsHightlight.enabled = false;
+ }
+
+
private static void setListener(Dictionary settingsHighlightMap, int index) {
foreach (KeyValuePair entry in settingsHighlightMap) {
entry.Key.SetActive(false);
@@ -748,14 +786,14 @@ namespace TheOtherRoles {
private static string buildOptionsOfType(CustomOption.CustomOptionType type, bool headerOnly) {
StringBuilder sb = new StringBuilder("\n");
var options = CustomOption.options.Where(o => o.type == type);
- if (MapOptions.gameMode == CustomGamemodes.Guesser) {
+ if (TORMapOptions.gameMode == CustomGamemodes.Guesser) {
if (type == CustomOption.CustomOptionType.General)
options = CustomOption.options.Where(o => o.type == type || o.type == CustomOption.CustomOptionType.Guesser);
- List remove = new List{ 310, 311, 312, 313, 314, 315, 316, 317, 318 };
+ List remove = new List{ 308, 310, 311, 312, 313, 314, 315, 316, 317, 318 };
options = options.Where(x => !remove.Contains(x.id));
- } else if (MapOptions.gameMode == CustomGamemodes.Classic)
- options = options.Where(x => !(x.type == CustomOption.CustomOptionType.Guesser));
- else if (MapOptions.gameMode == CustomGamemodes.HideNSeek)
+ } else if (TORMapOptions.gameMode == CustomGamemodes.Classic)
+ options = options.Where(x => !(x.type == CustomOption.CustomOptionType.Guesser || x == CustomOptionHolder.crewmateRolesFill));
+ else if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek)
options = options.Where(x => (x.type == CustomOption.CustomOptionType.HideNSeekMain || x.type == CustomOption.CustomOptionType.HideNSeekRoles));
foreach (var option in options) {
@@ -777,7 +815,7 @@ namespace TheOtherRoles {
else sb = new StringBuilder();
foreach (CustomOption option in options) {
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek && option.type != CustomOptionType.HideNSeekMain && option.type != CustomOptionType.HideNSeekRoles) continue;
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek && option.type != CustomOptionType.HideNSeekMain && option.type != CustomOptionType.HideNSeekRoles) continue;
if (option.parent != null) {
bool isIrrelevant = option.parent.getSelection() == 0 || (option.parent.parent != null && option.parent.parent.getSelection() == 0);
@@ -789,8 +827,17 @@ namespace TheOtherRoles {
var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Crewmate Roles");
var min = CustomOptionHolder.crewmateRolesCountMin.getSelection();
var max = CustomOptionHolder.crewmateRolesCountMax.getSelection();
+ string optionValue = "";
+ if (CustomOptionHolder.crewmateRolesFill.getBool()) {
+ var crewCount = PlayerControl.AllPlayerControls.Count - GameOptionsManager.Instance.currentGameOptions.NumImpostors;
+ min = crewCount - CustomOptionHolder.neutralRolesCountMax.getSelection();
+ max = crewCount - CustomOptionHolder.neutralRolesCountMin.getSelection();
+ if (min < 0) min = 0;
+ if (max < 0) max = 0;
+ optionValue = "Fill: ";
+ }
if (min > max) min = max;
- var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
+ optionValue += (min == max) ? $"{max}" : $"{min} - {max}";
sb.AppendLine($"{optionName}: {optionValue}");
} else if (option == CustomOptionHolder.neutralRolesCountMin) {
var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Neutral Roles");
@@ -823,14 +870,13 @@ namespace TheOtherRoles {
return sb.ToString();
}
- [HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.ToHudString))]
- private static void Postfix(ref string __result)
- {
- if (GameOptionsManager.Instance.currentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek) return; // Allow Vanilla Hide N Seek
+ public static string buildAllOptions(string vanillaSettings = "", bool hideExtras = false) {
+ if (vanillaSettings == "")
+ vanillaSettings = GameOptionsManager.Instance.CurrentGameOptions.ToHudString(PlayerControl.AllPlayerControls.Count);
int counter = TheOtherRolesPlugin.optionsPage;
- string hudString = counter != 0 ? Helpers.cs(DateTime.Now.Second % 2 == 0 ? Color.white : Color.red, "(Use scroll wheel if necessary)\n\n") : "";
+ string hudString = counter != 0 && !hideExtras ? Helpers.cs(DateTime.Now.Second % 2 == 0 ? Color.white : Color.red, "(Use scroll wheel if necessary)\n\n") : "";
int maxPage = 7;
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek) {
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
if (TheOtherRolesPlugin.optionsPage > 1) TheOtherRolesPlugin.optionsPage = 0;
maxPage = 2;
switch (counter) {
@@ -844,7 +890,7 @@ namespace TheOtherRoles {
} else {
switch (counter) {
case 0:
- hudString += "Page 1: Vanilla Settings \n\n" + __result;
+ hudString += (!hideExtras ? "" :"Page 1: Vanilla Settings \n\n") + vanillaSettings;
break;
case 1:
hudString += "Page 2: The Other Roles Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.General, false);
@@ -867,8 +913,16 @@ namespace TheOtherRoles {
}
}
- hudString += $"\n Press TAB or Page Number for more... ({counter+1}/{maxPage})";
- __result = hudString;
+ if (!hideExtras || counter != 0) hudString += $"\n Press TAB or Page Number for more... ({counter + 1}/{maxPage})";
+ return hudString;
+ }
+
+
+ [HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.ToHudString))]
+ private static void Postfix(ref string __result)
+ {
+ if (GameOptionsManager.Instance.currentGameOptions.GameMode == AmongUs.GameOptions.GameModes.HideNSeek) return; // Allow Vanilla Hide N Seek
+ __result = buildAllOptions(vanillaSettings:__result);
}
}
@@ -902,6 +956,9 @@ namespace TheOtherRoles {
if (Input.GetKeyDown(KeyCode.Alpha7) || Input.GetKeyDown(KeyCode.Keypad7)) {
TheOtherRolesPlugin.optionsPage = 6;
}
+ if (Input.GetKeyDown(KeyCode.F1))
+ HudManagerUpdate.ToggleSettings(HudManager.Instance);
+
if (page != TheOtherRolesPlugin.optionsPage) {
Vector3 position = (Vector3)FastDestroyableSingleton.Instance?.GameSettings?.transform.localPosition;
FastDestroyableSingleton.Instance.GameSettings.transform.localPosition = new Vector3(position.x, 2.9f, position.z);
@@ -918,7 +975,7 @@ namespace TheOtherRoles {
}
- // This class is taken from Town of Us Reactivated, https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/CustomOption/Patches.cs, Licensed under GPLv3
+ // This class is taken and adapted from Town of Us Reactivated, https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/CustomOption/Patches.cs, Licensed under GPLv3
[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
public class HudManagerUpdate {
public static float
@@ -976,6 +1033,8 @@ namespace TheOtherRoles {
private static void CreateScroller(HudManager __instance) {
if (Scroller != null) return;
+ Transform target = __instance.GameSettings.transform;
+
Scroller = new GameObject("SettingsScroller").AddComponent();
Scroller.transform.SetParent(__instance.GameSettings.transform.parent);
Scroller.gameObject.layer = 5;
@@ -989,8 +1048,81 @@ namespace TheOtherRoles {
Scroller.ContentXBounds = new FloatRange(MinX, MinX);
Scroller.enabled = true;
- Scroller.Inner = __instance.GameSettings.transform;
- __instance.GameSettings.transform.SetParent(Scroller.transform);
+ Scroller.Inner = target;
+ target.SetParent(Scroller.transform);
+ }
+
+ [HarmonyPrefix]
+ public static void Prefix2(HudManager __instance) {
+ if (!settingsTMPs[0]) return;
+ foreach (var tmp in settingsTMPs) tmp.text = "";
+ var settingsString = GameOptionsDataPatch.buildAllOptions(hideExtras: true);
+ var blocks = settingsString.Split("\n\n", StringSplitOptions.RemoveEmptyEntries); ;
+ string curString = "";
+ string curBlock;
+ int j = 0;
+ for (int i = 0; i < blocks.Length; i++) {
+ curBlock = blocks[i];
+ if (Helpers.lineCount(curBlock) + Helpers.lineCount(curString) < 40) {
+ curString += curBlock + "\n\n";
+ } else {
+ settingsTMPs[j].text = curString;
+ j++;
+
+ curString = "\n" + curBlock + "\n\n";
+ if (curString.Substring(0, 2) != "\n\n") curString = "\n" + curString;
+ }
+ }
+ if (j < 3) settingsTMPs[j].text = curString;
+ }
+
+ private static TMPro.TextMeshPro[] settingsTMPs = new TMPro.TextMeshPro[3];
+ private static GameObject settingsBackground;
+ public static void OpenSettings(HudManager __instance) {
+ if (__instance.FullScreen == null || MapBehaviour.Instance && MapBehaviour.Instance.IsOpen /*|| AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started*/) return;
+ settingsBackground = GameObject.Instantiate(__instance.FullScreen.gameObject, __instance.transform);
+ settingsBackground.SetActive(true);
+ var renderer = settingsBackground.GetComponent();
+ renderer.color = new Color(0.2f, 0.2f, 0.2f, 0.9f);
+ renderer.enabled = true;
+ for (int i = 0; i < 3; i++) {
+ settingsTMPs[i] = GameObject.Instantiate(__instance.KillButton.cooldownTimerText, __instance.transform);
+ settingsTMPs[i].alignment = TMPro.TextAlignmentOptions.TopLeft;
+ settingsTMPs[i].enableWordWrapping = false;
+ settingsTMPs[i].transform.localScale = Vector3.one * 0.25f;
+ settingsTMPs[i].transform.localPosition += new Vector3(-4f + 3f * i, 1.8f, -500f);
+ settingsTMPs[i].gameObject.SetActive(true);
+ }
+ }
+
+ public static void CloseSettings() {
+ foreach (var tmp in settingsTMPs)
+ if (tmp) tmp.gameObject.Destroy();
+
+ if (settingsBackground) settingsBackground.Destroy();
+ }
+
+ public static void ToggleSettings(HudManager __instance) {
+ if (settingsTMPs[0]) CloseSettings();
+ else OpenSettings(__instance);
+ }
+
+ static PassiveButton toggleSettingsButton;
+ static GameObject toggleSettingsButtonObject;
+ [HarmonyPostfix]
+ public static void Postfix(HudManager __instance) {
+ if (!toggleSettingsButton || !toggleSettingsButtonObject) {
+ // add a special button for settings viewing:
+ toggleSettingsButtonObject = GameObject.Instantiate(__instance.MapButton.gameObject, __instance.MapButton.transform.parent);
+ toggleSettingsButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -0.66f, -500f);
+ SpriteRenderer renderer = toggleSettingsButtonObject.GetComponent();
+ renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CurrentSettingsButton.png", 180f);
+ toggleSettingsButton = toggleSettingsButtonObject.GetComponent();
+ toggleSettingsButton.OnClick.RemoveAllListeners();
+ toggleSettingsButton.OnClick.AddListener((Action)(() => ToggleSettings(__instance)));
+ }
+ toggleSettingsButtonObject.SetActive(__instance.MapButton.gameObject.active && !(MapBehaviour.Instance && MapBehaviour.Instance.IsOpen));
+ toggleSettingsButtonObject.transform.localPosition = __instance.MapButton.transform.localPosition + new Vector3(0, -0.66f, -500f);
}
}
}
diff --git a/TheOtherRoles/Modules/ModUpdater.cs b/TheOtherRoles/Modules/ModUpdater.cs
index 3d6f8de..57f5e2b 100644
--- a/TheOtherRoles/Modules/ModUpdater.cs
+++ b/TheOtherRoles/Modules/ModUpdater.cs
@@ -35,6 +35,7 @@ namespace TheOtherRoles.Modules
{
public string Content;
public string Tag;
+ public string TimeString;
public JObject Request;
public Version Version => Version.Parse(Tag);
@@ -42,6 +43,7 @@ namespace TheOtherRoles.Modules
{
Tag = data["tag_name"]?.ToString().TrimStart('v');
Content = data["body"]?.ToString();
+ TimeString = DateTime.FromBinary(((Il2CppSystem.DateTime)data["published_at"]).ToBinaryRaw()).ToString();
Request = data;
}
@@ -79,7 +81,7 @@ namespace TheOtherRoles.Modules
showPopUp = false;
return;
}
-
+
var template = GameObject.Find("ExitGameButton");
if (!template) return;
@@ -108,7 +110,7 @@ namespace TheOtherRoles.Modules
passiveButton.OnMouseOut.AddListener((Action)(() => buttonSprite.color = text.color = Color.red));
var isSubmerged = TORUpdate == null;
- var announcement = $"A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}";
+ var announcement = $"A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}";
var mgr = FindObjectOfType(true);
if (!isSubmerged) {
@@ -118,7 +120,7 @@ namespace TheOtherRoles.Modules
passiveButton.OnClick.RemoveAllListeners();
passiveButton.OnClick = new Button.ButtonClickedEvent();
passiveButton.OnClick.AddListener((Action)(() => {
- mgr.StartCoroutine(CoShowAnnouncement($"A MANUAL UPDATE IS REQUIRED"));
+ mgr.StartCoroutine(CoShowAnnouncement($"A MANUAL UPDATE IS REQUIRED"));
}));
}
} catch {
@@ -126,9 +128,9 @@ namespace TheOtherRoles.Modules
}
}
-
+
if (isSubmerged && !SubmergedCompatibility.Loaded) showPopUp = false;
- if (showPopUp) mgr.StartCoroutine(CoShowAnnouncement(announcement));
+ if (showPopUp) mgr.StartCoroutine(CoShowAnnouncement(announcement, shortTitle: isSubmerged ? "Submerged Update" : "TOR Update", date: isSubmerged ? SubmergedUpdate.TimeString : TORUpdate.TimeString));
showPopUp = false;
}
@@ -156,17 +158,28 @@ namespace TheOtherRoles.Modules
popup.TextAreaTMP.text = download.Result ? $"{updateName}\nupdated successfully\nPlease restart the game." : "Update wasn't successful\nTry again later,\nor update manually.";
}
+
+ private static int announcementNumber = 501;
[HideFromIl2Cpp]
- public IEnumerator CoShowAnnouncement(string announcement)
+ public IEnumerator CoShowAnnouncement(string announcement, bool show=true, string shortTitle="TOR Update", string date="")
{
var popUp = Instantiate(FindObjectOfType(true));
- popUp.gameObject.SetActive(true);
- yield return popUp.Init();
- var last = DataManager.Announcements.LastViewedAnnouncement;
- last.Id = 1;
- last.Text = announcement;
- SelectableHyperLinkHelper.DestroyGOs(popUp.selectableHyperLinks, name);
- popUp.AnnounceTextMeshPro.text = announcement;
+ popUp.gameObject.SetActive(show);
+ yield return popUp.Init(show);
+
+ var announcementS = new Assets.InnerNet.Announcement();
+ announcementS.Title = "TOR Announcement";
+ announcementS.Text = announcement; // Can add clickable urls like this: "[https://www.google.de/]Text[]"
+ announcementS.ShortTitle = shortTitle;
+ announcementS.Id = "torUpdateAnnouncement_" + announcementNumber.ToString();
+ announcementS.Language = 0;
+ announcementS.Number = announcementNumber++;
+ announcementS.SubTitle = "";
+ announcementS.PinState = true;
+ announcementS.Date = date == "" ? DateTime.Today.ToString() : date;
+
+ DataManager.Player.Announcements.allAnnouncements.Insert(0, announcementS);
+ popUp.CreateAnnouncementList();
}
[HideFromIl2Cpp]
@@ -174,12 +187,11 @@ namespace TheOtherRoles.Modules
{
var torUpdateCheck = Task.Run(() => Instance.GetGithubUpdate("Eisbison", "TheOtherRoles"));
while (!torUpdateCheck.IsCompleted) yield return null;
- Announcement.updateData = torUpdateCheck.Result;
if (torUpdateCheck.Result != null && torUpdateCheck.Result.IsNewer(Version.Parse(TheOtherRolesPlugin.VersionString)))
{
Instance.TORUpdate = torUpdateCheck.Result;
}
-
+
if (CheckForSubmergedUpdates)
{
var submergedUpdateCheck = Task.Run(() => Instance.GetGithubUpdate("SubmergedAmongUs", "Submerged"));
diff --git a/TheOtherRoles/Objects/Bomb.cs b/TheOtherRoles/Objects/Bomb.cs
new file mode 100644
index 0000000..b5b37d0
--- /dev/null
+++ b/TheOtherRoles/Objects/Bomb.cs
@@ -0,0 +1,111 @@
+using Hazel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using TheOtherRoles.Players;
+using TheOtherRoles.Utilities;
+using UnityEngine;
+
+namespace TheOtherRoles.Objects {
+ public class Bomb {
+ public GameObject bomb;
+ public GameObject background;
+
+ private static Sprite bombSprite;
+ private static Sprite backgroundSprite;
+ private static Sprite defuseSprite;
+ public static bool canDefuse = false;
+
+ public static Sprite getBombSprite() {
+ if (bombSprite) return bombSprite;
+ bombSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Bomb.png", 300f);
+ return bombSprite;
+ }
+ public static Sprite getBackgroundSprite() {
+ if (backgroundSprite) return backgroundSprite;
+ backgroundSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.BombBackground.png", 110f / Bomber.hearRange);
+ return backgroundSprite;
+ }
+
+ public static Sprite getDefuseSprite() {
+ if (defuseSprite) return defuseSprite;
+ defuseSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Bomb_Button_Defuse.png", 115f);
+ return defuseSprite;
+ }
+
+ public Bomb(Vector2 p) {
+ bomb = new GameObject("Bomb") { layer = 11 };
+ bomb.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
+ Vector3 position = new Vector3(p.x, p.y, p.y / 1000 + 0.001f); // just behind player
+ bomb.transform.position = position;
+
+ background = new GameObject("Background") { layer = 11 };
+ background.transform.SetParent(bomb.transform);
+ background.transform.localPosition = new Vector3(0, 0, -1f); // before player
+ background.transform.position = position;
+
+ var bombRenderer = bomb.AddComponent();
+ bombRenderer.sprite = getBombSprite();
+ var backgroundRenderer = background.AddComponent();
+ backgroundRenderer.sprite = getBackgroundSprite();
+
+ bomb.SetActive(false);
+ background.SetActive(false);
+ if (CachedPlayer.LocalPlayer.PlayerControl == Bomber.bomber) {
+ bomb.SetActive(true);
+ }
+ Bomber.bomb = this;
+ Color c = Color.white;
+ Color g = Color.red;
+ backgroundRenderer.color = Color.white;
+ Bomber.isActive = false;
+
+ FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Bomber.bombActiveAfter, new Action((x) => {
+ if (x == 1f && this != null) {
+ bomb.SetActive(true);
+ background.SetActive(true);
+ SoundEffectsManager.playAtPosition("bombFuseBurning", p, Bomber.destructionTime, Bomber.hearRange, true);
+ Bomber.isActive = true;
+
+ FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Bomber.destructionTime, new Action((x) => { // can you feel the pain?
+ Color combinedColor = Mathf.Clamp01(x) * g + Mathf.Clamp01(1 - x) * c;
+ if (backgroundRenderer) backgroundRenderer.color = combinedColor;
+ if (x == 1f && this != null) {
+ explode(this);
+ }
+ })));
+ }
+ })));
+
+ }
+ public static void explode(Bomb b) {
+ if (b == null) return;
+ if (Bomber.bomber != null) {
+ var position = b.bomb.transform.position;
+ var distance = Vector2.Distance(position, CachedPlayer.LocalPlayer.transform.position); // every player only checks that for their own client (desynct with positions sucks)
+ if (distance < Bomber.destructionRange && !CachedPlayer.LocalPlayer.Data.IsDead) {
+ Helpers.checkMurderAttemptAndKill(Bomber.bomber, CachedPlayer.LocalPlayer.PlayerControl, false, false, true, true);
+ }
+ SoundEffectsManager.playAtPosition("bombExplosion", position, range: Bomber.hearRange) ;
+ }
+ Bomber.clearBomb();
+ canDefuse = false;
+ Bomber.isActive = false;
+ }
+
+ public static void update() {
+ if (Bomber.bomb == null || !Bomber.isActive) {
+ canDefuse = false;
+ return;
+ }
+ Bomber.bomb.background.transform.Rotate(Vector3.forward * 50 * Time.fixedDeltaTime);
+
+ if (Vector2.Distance(CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition(), Bomber.bomb.bomb.transform.position) > 1f) canDefuse = false;
+ else canDefuse = true;
+ }
+
+ public static void clearBackgroundSprite() {
+ backgroundSprite = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TheOtherRoles/Objects/Portal.cs b/TheOtherRoles/Objects/Portal.cs
index 7ee7467..abc3ac0 100644
--- a/TheOtherRoles/Objects/Portal.cs
+++ b/TheOtherRoles/Objects/Portal.cs
@@ -14,7 +14,8 @@ namespace TheOtherRoles.Objects {
public static Sprite[] portalFgAnimationSprites = new Sprite[205];
public static Sprite portalSprite;
public static bool isTeleporting = false;
- public static float teleportDuration = 3.4166666667f;
+ public static float teleportDuration = 3.4166666667f;
+ public string room;
public struct tpLogEntry {
public byte playerId;
@@ -37,7 +38,7 @@ namespace TheOtherRoles.Objects {
return portalFgAnimationSprites[index];
}
- public static void startTeleport(byte playerId) {
+ public static void startTeleport(byte playerId, byte exit) {
if (firstPortal == null || secondPortal == null) return;
isTeleporting = true;
@@ -55,12 +56,14 @@ namespace TheOtherRoles.Objects {
playerNameDisplay = "A camouflaged player";
colorId = 6;
}
- teleportedPlayers.Add(new tpLogEntry(playerId, playerNameDisplay, DateTime.UtcNow));
+
+ if (!playerControl.Data.IsDead)
+ teleportedPlayers.Add(new tpLogEntry(playerId, playerNameDisplay, DateTime.UtcNow));
FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(teleportDuration, new Action((p) => {
if (firstPortal != null && firstPortal.animationFgRenderer != null && secondPortal != null && secondPortal.animationFgRenderer != null) {
- firstPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length));
- secondPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length));
+ if (exit == 0 || exit == 1) firstPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length));
+ if (exit == 0 || exit == 2) secondPortal.animationFgRenderer.sprite = getFgAnimationSprite((int)(p * portalFgAnimationSprites.Length));
playerControl.SetPlayerMaterialColors(firstPortal.animationFgRenderer);
playerControl.SetPlayerMaterialColors(secondPortal.animationFgRenderer);
if (p == 1f) {
@@ -105,6 +108,8 @@ namespace TheOtherRoles.Objects {
else if (secondPortal == null) {
secondPortal = this;
}
+ var lastRoom = FastDestroyableSingleton.Instance?.roomTracker.LastRoom.RoomId;
+ this.room = lastRoom != null ? DestroyableSingleton.Instance.GetString((SystemTypes)lastRoom) : "Open Field";
}
public static bool locationNearEntry(Vector2 p) {
@@ -135,6 +140,7 @@ namespace TheOtherRoles.Objects {
firstPortal.portalGameObject.SetActive(true);
secondPortal.portalGameObject.SetActive(true);
bothPlacedAndEnabled = true;
+ HudManagerStartPatch.portalmakerButtonText2.text = "2. " + secondPortal.room;
}
// reset teleported players
diff --git a/TheOtherRoles/Patches/ClientOptionsPatch.cs b/TheOtherRoles/Patches/ClientOptionsPatch.cs
index 8aa21ed..132929b 100644
--- a/TheOtherRoles/Patches/ClientOptionsPatch.cs
+++ b/TheOtherRoles/Patches/ClientOptionsPatch.cs
@@ -14,13 +14,13 @@ namespace TheOtherRoles.Patches
public static class ClientOptionsPatch
{
private static SelectionBehaviour[] AllOptions = {
- new SelectionBehaviour("Ghosts See Remaining Tasks", () => MapOptions.ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value = !TheOtherRolesPlugin.GhostsSeeTasks.Value, TheOtherRolesPlugin.GhostsSeeTasks.Value),
- new SelectionBehaviour("Ghosts Can See Votes", () => MapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value, TheOtherRolesPlugin.GhostsSeeVotes.Value),
- new SelectionBehaviour("Ghosts Can See Roles", () => MapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value, TheOtherRolesPlugin.GhostsSeeRoles.Value),
- new SelectionBehaviour("Ghosts Can Additionally See Modifier", () => MapOptions.ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value = !TheOtherRolesPlugin.GhostsSeeModifier.Value, TheOtherRolesPlugin.GhostsSeeModifier.Value),
- new SelectionBehaviour("Show Role Summary", () => MapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value, TheOtherRolesPlugin.ShowRoleSummary.Value),
- new SelectionBehaviour("Show Lighter / Darker", () => MapOptions.showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value = !TheOtherRolesPlugin.ShowLighterDarker.Value, TheOtherRolesPlugin.ShowLighterDarker.Value),
- new SelectionBehaviour("Enable Sound Effects", () => MapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value, TheOtherRolesPlugin.EnableSoundEffects.Value),
+ new SelectionBehaviour("Ghosts See Tasks & Other Info", () => TORMapOptions.ghostsSeeInformation = TheOtherRolesPlugin.GhostsSeeInformation.Value = !TheOtherRolesPlugin.GhostsSeeInformation.Value, TheOtherRolesPlugin.GhostsSeeInformation.Value),
+ new SelectionBehaviour("Ghosts Can See Votes", () => TORMapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value, TheOtherRolesPlugin.GhostsSeeVotes.Value),
+ new SelectionBehaviour("Ghosts Can See Roles", () => TORMapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value, TheOtherRolesPlugin.GhostsSeeRoles.Value),
+ new SelectionBehaviour("Ghosts Can Additionally See Modifier", () => TORMapOptions.ghostsSeeModifier = TheOtherRolesPlugin.GhostsSeeModifier.Value = !TheOtherRolesPlugin.GhostsSeeModifier.Value, TheOtherRolesPlugin.GhostsSeeModifier.Value),
+ new SelectionBehaviour("Show Role Summary", () => TORMapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value, TheOtherRolesPlugin.ShowRoleSummary.Value),
+ new SelectionBehaviour("Show Lighter / Darker", () => TORMapOptions.showLighterDarker = TheOtherRolesPlugin.ShowLighterDarker.Value = !TheOtherRolesPlugin.ShowLighterDarker.Value, TheOtherRolesPlugin.ShowLighterDarker.Value),
+ new SelectionBehaviour("Enable Sound Effects", () => TORMapOptions.enableSoundEffects = TheOtherRolesPlugin.EnableSoundEffects.Value = !TheOtherRolesPlugin.EnableSoundEffects.Value, TheOtherRolesPlugin.EnableSoundEffects.Value),
};
private static GameObject popUp;
@@ -34,11 +34,12 @@ namespace TheOtherRoles.Patches
public static void MainMenuManager_StartPostfix(MainMenuManager __instance)
{
// Prefab for the title
- var tmp = __instance.Announcement.transform.Find("Title_Text").gameObject.GetComponent();
+ var go = new GameObject("TitleTextTOR");
+ var tmp = go.AddComponent();
+ tmp.fontSize = 4;
tmp.alignment = TextAlignmentOptions.Center;
tmp.transform.localPosition += Vector3.left * 0.2f;
titleText = Object.Instantiate(tmp);
- Object.Destroy(titleText.GetComponent());
titleText.gameObject.SetActive(false);
Object.DontDestroyOnLoad(titleText);
}
@@ -61,7 +62,6 @@ namespace TheOtherRoles.Patches
buttonPrefab.name = "CensorChatPrefab";
buttonPrefab.gameObject.SetActive(false);
}
-
SetUpOptions();
InitializeMoreButton(__instance);
}
@@ -162,7 +162,7 @@ namespace TheOtherRoles.Patches
button.Background.color = button.onState ? Color.green : Palette.ImpostorRed;
button.Text.text = info.Title;
- button.Text.fontSizeMin = button.Text.fontSizeMax = 2.5f;
+ button.Text.fontSizeMin = button.Text.fontSizeMax = 1.8f;
button.Text.font = Object.Instantiate(titleText.font);
button.Text.GetComponent().sizeDelta = new Vector2(2, 2);
diff --git a/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs b/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs
index 4087ff8..e436b96 100644
--- a/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs
+++ b/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs
@@ -14,7 +14,7 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.SetGameMode))]
public static bool Prefix(CreateOptionsPicker __instance, ref GameModes mode) {
if (mode <= GameModes.HideNSeek) {
- MapOptions.gameMode = CustomGamemodes.Classic;
+ TORMapOptions.gameMode = CustomGamemodes.Classic;
return true;
}
@@ -22,10 +22,10 @@ namespace TheOtherRoles.Patches {
if ((int)mode == 3) {
__instance.GameModeText.text = "TOR Guesser";
- MapOptions.gameMode = CustomGamemodes.Guesser;
+ TORMapOptions.gameMode = CustomGamemodes.Guesser;
} else {
__instance.GameModeText.text = "TOR Hide N Seek";
- MapOptions.gameMode = CustomGamemodes.HideNSeek;
+ TORMapOptions.gameMode = CustomGamemodes.HideNSeek;
}
return false;
}
@@ -33,10 +33,10 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Refresh))]
public static void Postfix(CreateOptionsPicker __instance) {
- if (MapOptions.gameMode == CustomGamemodes.Guesser) {
+ if (TORMapOptions.gameMode == CustomGamemodes.Guesser) {
__instance.GameModeText.text = "TOR Guesser";
}
- else if (MapOptions.gameMode == CustomGamemodes.HideNSeek) {
+ else if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
__instance.GameModeText.text = "TOR Hide N Seek";
}
}
@@ -66,7 +66,7 @@ namespace TheOtherRoles.Patches {
__instance.ChooseOption(entry);
});
- bool isCurrentMode = i <= 2 && MapOptions.gameMode == CustomGamemodes.Classic ? (long)entry == (long)((ulong)gameMode) : (i == 3 && MapOptions.gameMode == CustomGamemodes.Guesser || i == 4 && MapOptions.gameMode == CustomGamemodes.HideNSeek);
+ bool isCurrentMode = i <= 2 && TORMapOptions.gameMode == CustomGamemodes.Classic ? (long)entry == (long)((ulong)gameMode) : (i == 3 && TORMapOptions.gameMode == CustomGamemodes.Guesser || i == 4 && TORMapOptions.gameMode == CustomGamemodes.HideNSeek);
chatLanguageButton.SetSelected(isCurrentMode);
__instance.controllerSelectable.Add(chatLanguageButton.Button);
if (isCurrentMode) {
diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs
index 8210e3c..b11af6b 100644
--- a/TheOtherRoles/Patches/CredentialsPatch.cs
+++ b/TheOtherRoles/Patches/CredentialsPatch.cs
@@ -72,8 +72,8 @@ $@" Special thanks to K3ndo & Smeggy";
}
} else {
string gameModeText = $"";
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek";
- else if (MapOptions.gameMode == CustomGamemodes.Guesser) gameModeText = $"Guesser";
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek";
+ else if (TORMapOptions.gameMode == CustomGamemodes.Guesser) gameModeText = $"Guesser";
if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n";
__instance.text.text = $"{fullCredentialsVersion}\n {gameModeText + fullCredentials}\n {__instance.text.text}";
@@ -88,6 +88,7 @@ $@" Special thanks to K3ndo & Smeggy";
public static SpriteRenderer renderer;
public static Sprite bannerSprite;
public static Sprite horseBannerSprite;
+ public static Sprite banner2Sprite;
private static PingTracker instance;
static void Postfix(PingTracker __instance) {
var amongUsLogo = GameObject.Find("bannerLogo_AmongUs");
@@ -104,11 +105,13 @@ $@" Special thanks to K3ndo & Smeggy";
instance = __instance;
loadSprites();
- renderer.sprite = MapOptions.enableHorseMode ? horseBannerSprite : bannerSprite;
+ // renderer.sprite = TORMapOptions.enableHorseMode ? horseBannerSprite : bannerSprite;
+ renderer.sprite = EventUtility.isEnabled ? banner2Sprite : bannerSprite;
}
public static void loadSprites() {
if (bannerSprite == null) bannerSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);
+ if (banner2Sprite == null) banner2Sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner2.png", 300f);
if (horseBannerSprite == null) horseBannerSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.bannerTheHorseRoles.png", 300f);
}
@@ -119,7 +122,7 @@ $@" Special thanks to K3ndo & Smeggy";
instance.StartCoroutine(Effects.Lerp(fadeDuration, new Action((p) => {
renderer.color = new Color(1, 1, 1, 1 - p);
if (p == 1) {
- renderer.sprite = MapOptions.enableHorseMode ? horseBannerSprite : bannerSprite;
+ renderer.sprite = TORMapOptions.enableHorseMode ? horseBannerSprite : bannerSprite;
instance.StartCoroutine(Effects.Lerp(fadeDuration, new Action((p) => {
renderer.color = new Color(1, 1, 1, p);
})));
diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs
index 7aa2ffc..b611b9c 100644
--- a/TheOtherRoles/Patches/EndGamePatch.cs
+++ b/TheOtherRoles/Patches/EndGamePatch.cs
@@ -225,8 +225,9 @@ namespace TheOtherRoles.Patches {
AdditionalTempData.timer = ((float)(DateTime.UtcNow - HideNSeek.startTime).TotalMilliseconds) / 1000;
// Reset Settings
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek) ShipStatusPatch.resetVanillaSettings();
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) ShipStatusPatch.resetVanillaSettings();
RPCProcedure.resetVariables();
+ EventUtility.gameEndsUpdate();
}
}
@@ -328,7 +329,7 @@ namespace TheOtherRoles.Patches {
}
}
- if (MapOptions.showRoleSummary || HideNSeek.isHideNSeekGM) {
+ if (TORMapOptions.showRoleSummary || HideNSeek.isHideNSeekGM) {
var position = Camera.main.ViewportToWorldPoint(new Vector3(0f, 1f, Camera.main.nearClipPlane));
GameObject roleSummary = UnityEngine.Object.Instantiate(__instance.WinText.gameObject);
roleSummary.transform.position = new Vector3(__instance.Navigation.ExitButton.transform.position.x + 0.1f, position.y - 0.1f, -14f);
diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs
index 270eb67..bbf07c5 100644
--- a/TheOtherRoles/Patches/ExileControllerPatch.cs
+++ b/TheOtherRoles/Patches/ExileControllerPatch.cs
@@ -65,6 +65,8 @@ namespace TheOtherRoles.Patches {
foreach (PlayerControl target in Witch.futureSpelled) {
if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill)
{
+ if (exiled != null && Lawyer.lawyer != null && (target == Lawyer.lawyer || target == Lovers.otherLover(Lawyer.lawyer)) && Lawyer.target != null && Lawyer.isProsecutor && Lawyer.target.PlayerId == exiled.PlayerId)
+ continue;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedExilePlayer, Hazel.SendOption.Reliable, -1);
writer.Write(target.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
@@ -81,15 +83,15 @@ namespace TheOtherRoles.Patches {
// SecurityGuard vents and cameras
var allCameras = MapUtilities.CachedShipStatus.AllCameras.ToList();
- MapOptions.camerasToAdd.ForEach(camera => {
+ TORMapOptions.camerasToAdd.ForEach(camera => {
camera.gameObject.SetActive(true);
camera.gameObject.GetComponent().color = Color.white;
allCameras.Add(camera);
});
MapUtilities.CachedShipStatus.AllCameras = allCameras.ToArray();
- MapOptions.camerasToAdd = new List();
+ TORMapOptions.camerasToAdd = new List();
- foreach (Vent vent in MapOptions.ventsToSeal) {
+ foreach (Vent vent in TORMapOptions.ventsToSeal) {
PowerTools.SpriteAnim animator = vent.GetComponent();
animator?.Stop();
vent.EnterVentAnim = vent.ExitVentAnim = null;
@@ -99,7 +101,9 @@ namespace TheOtherRoles.Patches {
vent.myRend.color = Color.white;
vent.name = "SealedVent_" + vent.name;
}
- MapOptions.ventsToSeal = new List();
+ TORMapOptions.ventsToSeal = new List();
+
+ EventUtility.meetingEndsUpdate();
}
}
@@ -188,11 +192,11 @@ namespace TheOtherRoles.Patches {
Vector3 newBottomLeft = IntroCutsceneOnDestroyPatch.bottomLeft;
var BottomLeft = newBottomLeft + new Vector3(-0.25f, -0.25f, 0);
foreach (PlayerControl p in CachedPlayer.AllPlayers) {
- if (!MapOptions.playerIcons.ContainsKey(p.PlayerId)) continue;
+ if (!TORMapOptions.playerIcons.ContainsKey(p.PlayerId)) continue;
if (p.Data.IsDead || p.Data.Disconnected) {
- MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
+ TORMapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
} else {
- MapOptions.playerIcons[p.PlayerId].transform.localPosition = newBottomLeft + Vector3.right * visibleCounter * 0.35f;
+ TORMapOptions.playerIcons[p.PlayerId].transform.localPosition = newBottomLeft + Vector3.right * visibleCounter * 0.35f;
visibleCounter++;
}
}
diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs
index e9409f2..f1c7a1d 100644
--- a/TheOtherRoles/Patches/GameStartManagerPatch.cs
+++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs
@@ -155,9 +155,9 @@ namespace TheOtherRoles.Patches {
if (AmongUsClient.Instance.AmHost) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGamemode, Hazel.SendOption.Reliable, -1);
- writer.Write((byte) MapOptions.gameMode);
+ writer.Write((byte) TORMapOptions.gameMode);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.shareGamemode((byte) MapOptions.gameMode);
+ RPCProcedure.shareGamemode((byte) TORMapOptions.gameMode);
}
}
}
@@ -187,7 +187,7 @@ namespace TheOtherRoles.Patches {
break;
}
}
- if (continueStart && MapOptions.gameMode == CustomGamemodes.HideNSeek) {
+ if (continueStart && TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
byte mapId = (byte) CustomOptionHolder.hideNSeekMap.getSelection();
if (mapId >= 3) mapId++;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.DynamicMapOption, Hazel.SendOption.Reliable, -1);
diff --git a/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs b/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs
index dd6bcce..ac44ba1 100644
--- a/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs
+++ b/TheOtherRoles/Patches/HauntMenuMinigamePatch.cs
@@ -2,6 +2,8 @@
using AmongUs.GameOptions;
using System.Collections.Generic;
using System.Linq;
+using TheOtherRoles.Players;
+using System;
namespace TheOtherRoles.Patches {
[HarmonyPatch]
@@ -14,13 +16,12 @@ namespace TheOtherRoles.Patches {
if (GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.Normal) return;
var target = __instance.HauntTarget;
var roleInfo = RoleInfo.getRoleInfoForPlayer(target, false);
- string roleString = (roleInfo.Count > 0 && MapOptions.ghostsSeeRoles) ? roleInfo[0].name : "";
+ string roleString = (roleInfo.Count > 0 && TORMapOptions.ghostsSeeRoles) ? roleInfo[0].name : "";
if (__instance.HauntTarget.Data.IsDead) {
__instance.FilterText.text = roleString + " Ghost";
return;
}
__instance.FilterText.text = roleString;
- TheOtherRolesPlugin.Logger.LogMessage(roleString);
return;
}
@@ -40,7 +41,7 @@ namespace TheOtherRoles.Patches {
[HarmonyPrefix]
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.Start))]
public static bool StartPrefix(HauntMenuMinigame __instance) {
- if (GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.Normal || !MapOptions.ghostsSeeRoles) return true;
+ if (GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.Normal || !TORMapOptions.ghostsSeeRoles) return true;
__instance.FilterButtons[0].gameObject.SetActive(true);
int numActive = 0;
int numButtons = __instance.FilterButtons.Count((PassiveButton s) => s.isActiveAndEnabled);
@@ -56,5 +57,29 @@ namespace TheOtherRoles.Patches {
}
return false;
}
+
+ // Moves the haunt menu a bit further down
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.FixedUpdate))]
+ public static void UpdatePostfix(HauntMenuMinigame __instance) {
+ if (GameOptionsManager.Instance.currentGameOptions.GameMode != GameModes.Normal) return;
+ if (CachedPlayer.LocalPlayer.Data.Role.IsImpostor && Vampire.vampire != CachedPlayer.LocalPlayer.PlayerControl)
+ __instance.gameObject.transform.localPosition = new UnityEngine.Vector3(-6f, -1.1f, __instance.gameObject.transform.localPosition.z);
+ return;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.Update))]
+ public static void showOrHideAbilityButtonPostfix(AbilityButton __instance) {
+ if (CachedPlayer.LocalPlayer.Data.IsDead && CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool()) {
+ // player has haunt button.
+ var (playerCompleted, playerTotal) = TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data);
+ int numberOfLeftTasks = playerTotal - playerCompleted;
+ if (numberOfLeftTasks <= 0)
+ __instance.Show();
+ else
+ __instance.Hide();
+ }
+ }
}
}
diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs
index bc2ac0f..fe0f5e0 100644
--- a/TheOtherRoles/Patches/IntroPatch.cs
+++ b/TheOtherRoles/Patches/IntroPatch.cs
@@ -36,7 +36,7 @@ namespace TheOtherRoles.Patches {
// PlayerControl.SetPetImage(data.DefaultOutfit.PetId, data.DefaultOutfit.ColorId, player.PetSlot);
player.cosmetics.nameText.text = data.PlayerName;
player.SetFlipX(true);
- MapOptions.playerIcons[p.PlayerId] = player;
+ TORMapOptions.playerIcons[p.PlayerId] = player;
player.gameObject.SetActive(false);
if (CachedPlayer.LocalPlayer.PlayerControl == Arsonist.arsonist && p != Arsonist.arsonist) {
@@ -81,8 +81,8 @@ namespace TheOtherRoles.Patches {
SoundEffectsManager.Load();
// First kill
- if (AmongUsClient.Instance.AmHost && MapOptions.shieldFirstKill && MapOptions.firstKillName != "" && !HideNSeek.isHideNSeekGM) {
- PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(MapOptions.firstKillName));
+ if (AmongUsClient.Instance.AmHost && TORMapOptions.shieldFirstKill && TORMapOptions.firstKillName != "" && !HideNSeek.isHideNSeekGM) {
+ PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(TORMapOptions.firstKillName));
if (target != null) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetFirstKill, Hazel.SendOption.Reliable, -1);
writer.Write(target.PlayerId);
@@ -90,7 +90,9 @@ namespace TheOtherRoles.Patches {
RPCProcedure.setFirstKill(target.PlayerId);
}
}
- MapOptions.firstKillName = "";
+ TORMapOptions.firstKillName = "";
+
+ EventUtility.gameStartsUpdate();
if (HideNSeek.isHideNSeekGM) {
foreach (PlayerControl player in HideNSeek.getHunters()) {
@@ -193,11 +195,22 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.ShowRole))]
class SetUpRoleTextPatch {
+ static int seed = 0;
static public void SetRoleTexts(IntroCutscene __instance) {
// Don't override the intro of the vanilla roles
List infos = RoleInfo.getRoleInfoForPlayer(CachedPlayer.LocalPlayer.PlayerControl);
RoleInfo roleInfo = infos.Where(info => !info.isModifier).FirstOrDefault();
RoleInfo modifierInfo = infos.Where(info => info.isModifier).FirstOrDefault();
+
+ if (EventUtility.isEnabled) {
+ var roleInfos = RoleInfo.allRoleInfos.Where(x => !x.isModifier).ToList();
+ if (roleInfo.isNeutral) roleInfos.RemoveAll(x => !x.isNeutral);
+ if (roleInfo.color == Palette.ImpostorRed) roleInfos.RemoveAll(x => x.color != Palette.ImpostorRed);
+ if (!roleInfo.isNeutral && roleInfo.color != Palette.ImpostorRed) roleInfos.RemoveAll(x => x.color == Palette.ImpostorRed || x.isNeutral);
+ var rnd = new System.Random(seed);
+ roleInfo = roleInfos[rnd.Next(roleInfos.Count)];
+ }
+
__instance.RoleBlurbText.text = "";
if (roleInfo != null) {
__instance.RoleText.text = roleInfo.name;
@@ -222,6 +235,7 @@ namespace TheOtherRoles.Patches {
}
public static bool Prefix(IntroCutscene __instance) {
if (!CustomOptionHolder.activateRoles.getBool()) return true;
+ seed = rnd.Next(5000);
FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(1f, new Action((p) => {
SetRoleTexts(__instance);
})));
@@ -252,17 +266,12 @@ namespace TheOtherRoles.Patches {
}
}
- /*[HarmonyPatch(typeof(Constants), nameof(Constants.ShouldHorseAround))]
+ [HarmonyPatch(typeof(Constants), nameof(Constants.ShouldHorseAround))]
public static class ShouldAlwaysHorseAround {
- public static bool isHorseMode;
public static bool Prefix(ref bool __result) {
- if (isHorseMode != MapOptions.enableHorseMode && LobbyBehaviour.Instance != null) __result = isHorseMode;
- else {
- __result = MapOptions.enableHorseMode;
- isHorseMode = MapOptions.enableHorseMode;
- }
+ __result = false;
return false;
}
- }*/
+ }
}
diff --git a/TheOtherRoles/Patches/MainMenuPatch.cs b/TheOtherRoles/Patches/MainMenuPatch.cs
index 8d09a49..19d6782 100644
--- a/TheOtherRoles/Patches/MainMenuPatch.cs
+++ b/TheOtherRoles/Patches/MainMenuPatch.cs
@@ -7,11 +7,15 @@ using static UnityEngine.UI.Button;
using Object = UnityEngine.Object;
using TheOtherRoles.Patches;
using UnityEngine.SceneManagement;
+using TheOtherRoles.Utilities;
+using AmongUs.Data;
+using Assets.InnerNet;
+using System.Linq;
namespace TheOtherRoles.Modules {
[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))]
public class MainMenuPatch {
- private static bool horseButtonState = MapOptions.enableHorseMode;
+ private static bool horseButtonState = TORMapOptions.enableHorseMode;
//private static Sprite horseModeOffSprite = null;
//private static Sprite horseModeOnSprite = null;
private static GameObject bottomTemplate;
@@ -46,7 +50,7 @@ namespace TheOtherRoles.Modules {
bottomTemplate = GameObject.Find("InventoryButton");
/*
// Horse mode stuff
- var horseModeSelectionBehavior = new ClientOptionsPatch.SelectionBehaviour("Enable Horse Mode", () => MapOptions.enableHorseMode = TheOtherRolesPlugin.EnableHorseMode.Value = !TheOtherRolesPlugin.EnableHorseMode.Value, TheOtherRolesPlugin.EnableHorseMode.Value);
+ var horseModeSelectionBehavior = new ClientOptionsPatch.SelectionBehaviour("Enable Horse Mode", () => TORMapOptions.enableHorseMode = TheOtherRolesPlugin.EnableHorseMode.Value = !TheOtherRolesPlugin.EnableHorseMode.Value, TheOtherRolesPlugin.EnableHorseMode.Value);
if (bottomTemplate == null) return;
@@ -94,8 +98,16 @@ namespace TheOtherRoles.Modules {
if (popUp != null) Object.Destroy(popUp);
popUp = Object.Instantiate(Object.FindObjectOfType(true));
popUp.gameObject.SetActive(true);
- popUp.Init();
- //SelectableHyperLinkHelper.DestroyGOs(popUp.selectableHyperLinks, "test");
+
+ Assets.InnerNet.Announcement creditsAnnouncement = new Assets.InnerNet.Announcement();
+ creditsAnnouncement.Id = "torCredits";
+ creditsAnnouncement.Language = 0;
+ creditsAnnouncement.Number = 500;
+ creditsAnnouncement.Title = "The Other Roles Credits & Resources";
+ creditsAnnouncement.ShortTitle = "TOR Credits";
+ creditsAnnouncement.SubTitle = "";
+ creditsAnnouncement.PinState = false;
+ creditsAnnouncement.Date = "03.07.2020";
string creditsString = @$"Github Contributors:
Alex2911 amsyarasyiq MaximeGillot
Psynomit probablyadnf JustASysAdmin
@@ -126,11 +138,22 @@ Ottomated - Idea for the Morphling, Snitch and Camouflager role came from Ottoma
Crowded-Mod - Our implementation for 10+ player lobbies was inspired by the one from the Crowded Mod Team
Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose";
creditsString += "";
- popUp.AnnounceTextMeshPro.text = creditsString;
+ creditsAnnouncement.Text = creditsString;
__instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => {
if (p == 1) {
+ var backup = DataManager.Player.Announcements.allAnnouncements;
+ popUp.Init(false);
+ DataManager.Player.Announcements.allAnnouncements = new();
+ DataManager.Player.Announcements.allAnnouncements.Insert(0, creditsAnnouncement);
+ foreach (var item in popUp.visibleAnnouncements) Object.Destroy(item);
+ foreach (var item in Object.FindObjectsOfType()) {
+ if (item != popUp.ErrorPanel) Object.Destroy(item.gameObject);
+ }
+ popUp.CreateAnnouncementList();
+ popUp.visibleAnnouncements[0].PassiveButton.OnClick.RemoveAllListeners();
+ DataManager.Player.Announcements.allAnnouncements = backup;
var titleText = GameObject.Find("Title_Text").GetComponent();
- if (titleText != null) titleText.text = "Credits and Contributors";
+ if (titleText != null) titleText.text = "";
}
})));
});
@@ -152,7 +175,7 @@ Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose";
public static void addSceneChangeCallbacks() {
SceneManager.add_sceneLoaded((Action)((scene, _) => {
if (!scene.name.Equals("MatchMaking", StringComparison.Ordinal)) return;
- MapOptions.gameMode = CustomGamemodes.Classic;
+ TORMapOptions.gameMode = CustomGamemodes.Classic;
// Add buttons For Guesser Mode, Hide N Seek in this scene.
// find "HostLocalGameButton"
var template = GameObject.FindObjectOfType();
@@ -166,7 +189,7 @@ Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose";
guesserButtonPassiveButton.OnClick = new Button.ButtonClickedEvent();
guesserButtonPassiveButton.OnClick.AddListener((System.Action)(() => {
- MapOptions.gameMode = CustomGamemodes.Guesser;
+ TORMapOptions.gameMode = CustomGamemodes.Guesser;
template.OnClick();
}));
@@ -177,7 +200,7 @@ Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose";
HideNSeekButtonPassiveButton.OnClick = new Button.ButtonClickedEvent();
HideNSeekButtonPassiveButton.OnClick.AddListener((System.Action)(() => {
- MapOptions.gameMode = CustomGamemodes.HideNSeek;
+ TORMapOptions.gameMode = CustomGamemodes.HideNSeek;
template.OnClick();
}));
@@ -189,7 +212,7 @@ Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose";
}
}
- [HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.UpdateAnnounceText))]
+ /*[HarmonyPatch(typeof(AnnouncementPopUp), nameof(AnnouncementPopUp.UpdateAnnounceText))]
public static class Announcement
{
public static ModUpdateBehaviour.UpdateData updateData = null;
@@ -202,5 +225,5 @@ Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose";
return false;
}
- }
-}
\ No newline at end of file
+ }*/
+}
diff --git a/TheOtherRoles/Patches/MapBehaviourPatch.cs b/TheOtherRoles/Patches/MapBehaviourPatch.cs
index e9629ac..9faed3f 100644
--- a/TheOtherRoles/Patches/MapBehaviourPatch.cs
+++ b/TheOtherRoles/Patches/MapBehaviourPatch.cs
@@ -36,6 +36,7 @@ namespace TheOtherRoles.Patches {
herePoints.Remove(s.Key);
}
}
+ HudManagerUpdate.CloseSettings();
}
}
}
diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs
index 885ece3..f2e5e94 100644
--- a/TheOtherRoles/Patches/MeetingPatch.cs
+++ b/TheOtherRoles/Patches/MeetingPatch.cs
@@ -3,7 +3,7 @@ using Hazel;
using System.Collections.Generic;
using System.Linq;
using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.MapOptions;
+using static TheOtherRoles.TORMapOptions;
using TheOtherRoles.Objects;
using System;
using TheOtherRoles.Players;
@@ -17,9 +17,9 @@ namespace TheOtherRoles.Patches {
static SpriteRenderer[] renderers;
private static GameData.PlayerInfo target = null;
private const float scale = 0.65f;
- private static TMPro.TextMeshPro swapperChargesText;
+ private static TMPro.TextMeshPro meetingExtraButtonText;
private static PassiveButton[] swapperButtonList;
- private static TMPro.TextMeshPro swapperConfirmButtonLabel;
+ private static TMPro.TextMeshPro meetingExtraButtonLabel;
private static PlayerVoteArea swapped1 = null;
private static PlayerVoteArea swapped2 = null;
@@ -34,7 +34,7 @@ namespace TheOtherRoles.Patches {
if (player == null || player.Data == null || player.Data.IsDead || player.Data.Disconnected) continue;
int currentVotes;
- int additionalVotes = (Mayor.mayor != null && Mayor.mayor.PlayerId == playerVoteArea.TargetPlayerId) ? 2 : 1; // Mayor vote
+ int additionalVotes = (Mayor.mayor != null && Mayor.mayor.PlayerId == playerVoteArea.TargetPlayerId && Mayor.voteTwice) ? 2 : 1; // Mayor vote
if (dictionary.TryGetValue(playerVoteArea.VotedFor, out currentVotes))
dictionary[playerVoteArea.VotedFor] = currentVotes + additionalVotes;
else
@@ -139,7 +139,7 @@ namespace TheOtherRoles.Patches {
public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]GameData.PlayerInfo voterPlayer, [HarmonyArgument(1)]int index, [HarmonyArgument(2)]Transform parent) {
SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab);
int cId = voterPlayer.DefaultOutfit.ColorId;
- if (!(!GameOptionsManager.Instance.currentNormalGameOptions.AnonymousVotes || (CachedPlayer.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes) || Mayor.mayor != null && CachedPlayer.LocalPlayer.PlayerControl == Mayor.mayor && Mayor.canSeeVoteColors && TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data).Item1 >= Mayor.tasksNeededToSeeVoteColors))
+ if (!(!GameOptionsManager.Instance.currentNormalGameOptions.AnonymousVotes || (CachedPlayer.LocalPlayer.Data.IsDead && TORMapOptions.ghostsSeeVotes) || Mayor.mayor != null && CachedPlayer.LocalPlayer.PlayerControl == Mayor.mayor && Mayor.canSeeVoteColors && TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data).Item1 >= Mayor.tasksNeededToSeeVoteColors))
voterPlayer.Object.SetColor(6);
voterPlayer.Object.SetPlayerMaterialColors(spriteRenderer);
spriteRenderer.transform.SetParent(parent);
@@ -197,7 +197,7 @@ namespace TheOtherRoles.Patches {
}
// Major vote, redo this iteration to place a second vote
- if (Mayor.mayor != null && voterState.VoterId == (sbyte)Mayor.mayor.PlayerId && !mayorFirstVoteDisplayed) {
+ if (Mayor.mayor != null && voterState.VoterId == (sbyte)Mayor.mayor.PlayerId && !mayorFirstVoteDisplayed && Mayor.voteTwice) {
mayorFirstVoteDisplayed = true;
j--;
}
@@ -246,13 +246,13 @@ namespace TheOtherRoles.Patches {
} else {
selections[i] = true;
renderer.color = Color.yellow;
- swapperConfirmButtonLabel.text = Helpers.cs(Color.yellow, "Confirm Swap");
+ meetingExtraButtonLabel.text = Helpers.cs(Color.yellow, "Confirm Swap");
}
} else if (selectedCount == 2) {
if (selections[i]) {
renderer.color = Color.red;
selections[i] = false;
- swapperConfirmButtonLabel.text = Helpers.cs(Color.red, "Confirm Swap");
+ meetingExtraButtonLabel.text = Helpers.cs(Color.red, "Confirm Swap");
}
}
}
@@ -285,12 +285,32 @@ namespace TheOtherRoles.Patches {
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.swapperSwap((byte)firstPlayer.TargetPlayerId, (byte)secondPlayer.TargetPlayerId);
- swapperConfirmButtonLabel.text = Helpers.cs(Color.green, "Swapping!");
+ meetingExtraButtonLabel.text = Helpers.cs(Color.green, "Swapping!");
Swapper.charges--;
- swapperChargesText.text = $"Swaps: {Swapper.charges}";
+ meetingExtraButtonText.text = $"Swaps: {Swapper.charges}";
}
}
+ static void mayorToggleVoteTwice(MeetingHud __instance) {
+ __instance.playerStates[0].Cancel(); // This will stop the underlying buttons of the template from showing up
+ if (__instance.state == MeetingHud.VoteStates.Results || Mayor.mayor.Data.IsDead) return;
+ if (Mayor.mayorChooseSingleVote == 1) { // Only accept changes until the mayor voted
+ var mayorPVA = __instance.playerStates.FirstOrDefault(x => x.TargetPlayerId == Mayor.mayor.PlayerId);
+ if (mayorPVA != null && mayorPVA.DidVote) {
+ SoundEffectsManager.play("fail");
+ return;
+ }
+ }
+
+ Mayor.voteTwice = !Mayor.voteTwice;
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.MayorSetVoteTwice, Hazel.SendOption.Reliable, -1);
+ writer.Write(Mayor.voteTwice);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+
+ meetingExtraButtonLabel.text = Helpers.cs(Mayor.color, "Double Vote: " + (Mayor.voteTwice ? Helpers.cs(Color.green, "On ") : Helpers.cs(Color.red, "Off")));
+ }
+
public static GameObject guesserUI;
public static PassiveButton guesserUIExitButton;
public static byte guesserCurrentTarget;
@@ -298,7 +318,8 @@ namespace TheOtherRoles.Patches {
if (guesserUI != null || !(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted)) return;
__instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(false));
- Transform container = UnityEngine.Object.Instantiate(__instance.transform.FindChild("PhoneUI"), __instance.transform);
+ Transform PhoneUI = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.name == "PhoneUI");
+ Transform container = UnityEngine.Object.Instantiate(PhoneUI, __instance.transform);
container.transform.localPosition = new Vector3(0, 0, -5f);
guesserUI = container.gameObject;
@@ -427,7 +448,9 @@ namespace TheOtherRoles.Patches {
static void populateButtonsPostfix(MeetingHud __instance) {
// Add Swapper Buttons
- if (Swapper.swapper != null && CachedPlayer.LocalPlayer.PlayerControl == Swapper.swapper && !Swapper.swapper.Data.IsDead) {
+ bool addSwapperButtons = Swapper.swapper != null && CachedPlayer.LocalPlayer.PlayerControl == Swapper.swapper && !Swapper.swapper.Data.IsDead;
+ bool addMayorButton = Mayor.mayor != null && CachedPlayer.LocalPlayer.PlayerControl == Mayor.mayor && !Mayor.mayor.Data.IsDead && Mayor.mayorChooseSingleVote > 0;
+ if (addSwapperButtons) {
selections = new bool[__instance.playerStates.Length];
renderers = new SpriteRenderer[__instance.playerStates.Length];
swapperButtonList = new PassiveButton[__instance.playerStates.Length];
@@ -457,45 +480,57 @@ namespace TheOtherRoles.Patches {
selections[i] = false;
renderers[i] = renderer;
}
+ }
+
+ // Add meeting extra button, i.e. Swapper Confirm Button or Mayor Toggle Double Vote Button. Swapper Button uses ExtraButtonText on the Left of the Button. (Future meeting buttons can easily be added here)
+ if (addSwapperButtons || addMayorButton) {
+ Transform meetingUI = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(x => x.name == "PhoneUI");
- // Add the "Confirm Swap" button and "Swaps: X" text next to it
- Transform meetingUI = __instance.transform.FindChild("PhoneUI");
var buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase");
var maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea");
var textTemplate = __instance.playerStates[0].NameText;
- Transform confirmSwapButtonParent = (new GameObject()).transform;
- confirmSwapButtonParent.SetParent(meetingUI);
- Transform confirmSwapButton = UnityEngine.Object.Instantiate(buttonTemplate, confirmSwapButtonParent);
+ Transform meetingExtraButtonParent = (new GameObject()).transform;
+ meetingExtraButtonParent.SetParent(meetingUI);
+ Transform meetingExtraButton = UnityEngine.Object.Instantiate(buttonTemplate, meetingExtraButtonParent);
Transform infoTransform = __instance.playerStates[0].NameText.transform.parent.FindChild("Info");
TMPro.TextMeshPro meetingInfo = infoTransform != null ? infoTransform.GetComponent() : null;
- swapperChargesText = UnityEngine.Object.Instantiate(__instance.playerStates[0].NameText, confirmSwapButtonParent);
- swapperChargesText.text = $"Swaps: {Swapper.charges}";
- swapperChargesText.enableWordWrapping = false;
- swapperChargesText.transform.localScale = Vector3.one * 1.7f;
- swapperChargesText.transform.localPosition = new Vector3(-2.5f, 0f, 0f);
-
- Transform confirmSwapButtonMask = UnityEngine.Object.Instantiate(maskTemplate, confirmSwapButtonParent);
- swapperConfirmButtonLabel = UnityEngine.Object.Instantiate(textTemplate, confirmSwapButton);
- confirmSwapButton.GetComponent().sprite = FastDestroyableSingleton.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image;
- confirmSwapButtonParent.localPosition = new Vector3(0, -2.225f, -5);
- confirmSwapButtonParent.localScale = new Vector3(0.55f, 0.55f, 1f);
- swapperConfirmButtonLabel.text = Helpers.cs(Color.red, "Confirm Swap");
- swapperConfirmButtonLabel.alignment = TMPro.TextAlignmentOptions.Center;
- swapperConfirmButtonLabel.transform.localPosition = new Vector3(0, 0, swapperConfirmButtonLabel.transform.localPosition.z);
- swapperConfirmButtonLabel.transform.localScale *= 1.7f;
-
- PassiveButton passiveButton = confirmSwapButton.GetComponent();
- passiveButton.OnClick.RemoveAllListeners();
- if (!CachedPlayer.LocalPlayer.Data.IsDead) passiveButton.OnClick.AddListener((Action)(() => swapperConfirm(__instance)));
- confirmSwapButton.parent.gameObject.SetActive(false);
+ meetingExtraButtonText = UnityEngine.Object.Instantiate(__instance.playerStates[0].NameText, meetingExtraButtonParent);
+ meetingExtraButtonText.text = addSwapperButtons ? $"Swaps: {Swapper.charges}" : "";
+ meetingExtraButtonText.enableWordWrapping = false;
+ meetingExtraButtonText.transform.localScale = Vector3.one * 1.7f;
+ meetingExtraButtonText.transform.localPosition = new Vector3(-2.5f, 0f, 0f);
+
+ Transform meetingExtraButtonMask = UnityEngine.Object.Instantiate(maskTemplate, meetingExtraButtonParent);
+ meetingExtraButtonLabel = UnityEngine.Object.Instantiate(textTemplate, meetingExtraButton);
+ meetingExtraButton.GetComponent().sprite = FastDestroyableSingleton.Instance.GetNamePlateById("nameplate_NoPlate")?.viewData?.viewData?.Image;
+ meetingExtraButtonParent.localPosition = new Vector3(0, -2.225f, -5);
+ meetingExtraButtonParent.localScale = new Vector3(0.55f, 0.55f, 1f);
+ meetingExtraButtonLabel.alignment = TMPro.TextAlignmentOptions.Center;
+ meetingExtraButtonLabel.transform.localPosition = new Vector3(0, 0, meetingExtraButtonLabel.transform.localPosition.z);
+ if (addSwapperButtons) {
+ meetingExtraButtonLabel.transform.localScale *= 1.7f;
+ meetingExtraButtonLabel.text = Helpers.cs(Color.red, "Confirm Swap");
+ } else if (addMayorButton) {
+ meetingExtraButtonLabel.transform.localScale = new Vector3(meetingExtraButtonLabel.transform.localScale.x * 1.5f, meetingExtraButtonLabel.transform.localScale.x * 1.7f, meetingExtraButtonLabel.transform.localScale.x * 1.7f);
+ meetingExtraButtonLabel.text = Helpers.cs(Mayor.color, "Double Vote: " + (Mayor.voteTwice ? Helpers.cs(Color.green, "On ") : Helpers.cs(Color.red, "Off")));
+ }
+ PassiveButton passiveButton = meetingExtraButton.GetComponent();
+ passiveButton.OnClick.RemoveAllListeners();
+ if (!CachedPlayer.LocalPlayer.Data.IsDead) {
+ if (addSwapperButtons)
+ passiveButton.OnClick.AddListener((Action)(() => swapperConfirm(__instance)));
+ else if (addMayorButton)
+ passiveButton.OnClick.AddListener((Action)(() => mayorToggleVoteTwice(__instance)));
+ }
+ meetingExtraButton.parent.gameObject.SetActive(false);
__instance.StartCoroutine(Effects.Lerp(7.27f, new Action((p) => { // Button appears delayed, so that its visible in the voting screen only!
if (p == 1f) {
- confirmSwapButton.parent.gameObject.SetActive(true);
+ meetingExtraButton.parent.gameObject.SetActive(true);
}
})));
}
-
+
//Fix visor in Meetings
/**
foreach (PlayerVoteArea pva in __instance.playerStates) {
@@ -563,6 +598,14 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.StartMeeting))]
class StartMeetingPatch {
public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]GameData.PlayerInfo meetingTarget) {
+ RoomTracker roomTracker = FastDestroyableSingleton.Instance?.roomTracker;
+ if (Snitch.snitch != null && roomTracker != null) {
+ MessageWriter roomWriter = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareRoom, Hazel.SendOption.Reliable, -1);
+ roomWriter.Write(CachedPlayer.LocalPlayer.PlayerId);
+ roomWriter.Write(roomTracker.LastRoom ? (byte)roomTracker.LastRoom.RoomId : Byte.MinValue);
+ AmongUsClient.Instance.FinishRpcImmediately(roomWriter);
+ }
+
// Resett Bait list
Bait.active = new Dictionary();
// Save AntiTeleport position, if the player is able to move (i.e. not on a ladder or a gap thingy)
@@ -586,43 +629,83 @@ namespace TheOtherRoles.Patches {
// Add Portal info into Portalmaker Chat:
- if (Portalmaker.portalmaker != null && CachedPlayer.LocalPlayer.PlayerControl == Portalmaker.portalmaker && !CachedPlayer.LocalPlayer.Data.IsDead) {
- foreach (var entry in Portal.teleportedPlayers) {
- float timeBeforeMeeting = ((float)(DateTime.UtcNow - entry.time).TotalMilliseconds) / 1000;
- string msg = Portalmaker.logShowsTime ? $"{(int)timeBeforeMeeting}s ago: " : "";
- msg = msg + $"{entry.name} used the teleporter";
- FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{msg}");
+ if (Portalmaker.portalmaker != null && (CachedPlayer.LocalPlayer.PlayerControl == Portalmaker.portalmaker || Helpers.shouldShowGhostInfo()) && !Portalmaker.portalmaker.Data.IsDead) {
+ if (Portal.teleportedPlayers.Count > 0) {
+ string msg = "Portal Log:\n";
+ foreach (var entry in Portal.teleportedPlayers) {
+ float timeBeforeMeeting = ((float)(DateTime.UtcNow - entry.time).TotalMilliseconds) / 1000;
+ msg += Portalmaker.logShowsTime ? $"{(int)timeBeforeMeeting}s ago: " : "";
+ msg = msg + $"{entry.name} used the teleporter\n";
+ }
+ FastDestroyableSingleton.Instance.Chat.AddChat(Portalmaker.portalmaker, $"{msg}");
}
}
// Add trapped Info into Trapper chat
- if (Trapper.trapper != null && CachedPlayer.LocalPlayer.PlayerControl == Trapper.trapper) {
+ if (Trapper.trapper != null && (CachedPlayer.LocalPlayer.PlayerControl == Trapper.trapper || Helpers.shouldShowGhostInfo()) && !Trapper.trapper.Data.IsDead) {
+ if (Trap.traps.Any(x => x.revealed))
+ FastDestroyableSingleton.Instance.Chat.AddChat(Trapper.trapper, "Trap Logs:");
foreach (Trap trap in Trap.traps) {
if (!trap.revealed) continue;
string message = $"Trap {trap.instanceId}: \n";
trap.trappedPlayer = trap.trappedPlayer.OrderBy(x => rnd.Next()).ToList();
foreach (PlayerControl p in trap.trappedPlayer) {
- if (Trapper.infoType == 0) message += RoleInfo.GetRolesString(p, false, false) + "\n";
+ if (Trapper.infoType == 0) message += RoleInfo.GetRolesString(p, false, false, true) + "\n";
else if (Trapper.infoType == 1) {
if (Helpers.isNeutral(p) || p.Data.Role.IsImpostor) message += "Evil Role \n";
else message += "Good Role \n";
}
else message += p.Data.PlayerName + "\n";
}
- FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{message}");
+ FastDestroyableSingleton.Instance.Chat.AddChat(Trapper.trapper, $"{message}");
+ }
+ }
+
+ // Add Snitch info
+ string output = "";
+
+ if (Snitch.snitch != null && (CachedPlayer.LocalPlayer.PlayerControl == Snitch.snitch || Helpers.shouldShowGhostInfo()) && !Snitch.snitch.Data.IsDead) {
+ var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data);
+ int numberOfTasks = playerTotal - playerCompleted;
+ if (numberOfTasks == 0) {
+ output = $"Bad alive roles in game: \n \n";
+ FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(0.3f, new Action((x) => {
+ if (x == 1f) {
+ foreach (PlayerControl p in CachedPlayer.AllPlayers) {
+ if (!Helpers.isNeutral(p) && !p.Data.Role.IsImpostor) continue;
+ if (!Snitch.playerRoomMap.ContainsKey(p.PlayerId)) continue;
+ if (p.Data.IsDead) continue;
+ var room = Snitch.playerRoomMap[p.PlayerId];
+ var roomName = "open fields";
+ if (room != byte.MinValue) {
+ roomName = DestroyableSingleton.Instance.GetString((SystemTypes)room);
+ }
+ output += "- " + RoleInfo.GetRolesString(p, false, false, true) + ", was last seen in " + roomName + "\n";
+ }
+ FastDestroyableSingleton.Instance.Chat.AddChat(Snitch.snitch, $"{output}");
+ }
+ })));
}
}
+ if (CachedPlayer.LocalPlayer.Data.IsDead && output != "") FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer, $"{output}");
+
Trapper.playersOnMap = new List();
+ Snitch.playerRoomMap = new Dictionary();
// Remove revealed traps
Trap.clearRevealedTraps();
+ Bomber.clearBomb();
+
// Reset zoomed out ghosts
Helpers.toggleZoom(reset: true);
// Stop all playing sounds
SoundEffectsManager.stopAll();
+
+ // Close In-Game Settings Display if open
+ HudManagerUpdate.CloseSettings();
}
}
@@ -636,9 +719,16 @@ namespace TheOtherRoles.Patches {
if (__instance.state >= MeetingHud.VoteStates.Discussion)
{
// Remove first kill shield
- MapOptions.firstKillPlayer = null;
+ TORMapOptions.firstKillPlayer = null;
}
}
}
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.StartMeeting))]
+ public static void MeetingHudIntroPrefix() {
+ EventUtility.meetingStartsUpdate();
+ }
+
}
}
diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs
index 829aab1..aa1d404 100644
--- a/TheOtherRoles/Patches/PlayerControlPatch.cs
+++ b/TheOtherRoles/Patches/PlayerControlPatch.cs
@@ -67,14 +67,14 @@ namespace TheOtherRoles.Patches {
bool hasVisibleShield = false;
Color color = Medic.shieldedColor;
if (Camouflager.camouflageTimer <= 0f && Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) {
- hasVisibleShield = Medic.showShielded == 0 // Everyone
+ hasVisibleShield = Medic.showShielded == 0 || Helpers.shouldShowGhostInfo() // Everyone or Ghost info
|| (Medic.showShielded == 1 && (CachedPlayer.LocalPlayer.PlayerControl == Medic.shielded || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic)) // Shielded + Medic
|| (Medic.showShielded == 2 && CachedPlayer.LocalPlayer.PlayerControl == Medic.medic); // Medic only
// Make shield invisible till after the next meeting if the option is set (the medic can already see the shield)
- hasVisibleShield = hasVisibleShield && (Medic.meetingAfterShielding || !Medic.showShieldAfterMeeting || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic);
+ hasVisibleShield = hasVisibleShield && (Medic.meetingAfterShielding || !Medic.showShieldAfterMeeting || CachedPlayer.LocalPlayer.PlayerControl == Medic.medic || Helpers.shouldShowGhostInfo());
}
- if (Camouflager.camouflageTimer <= 0f && MapOptions.firstKillPlayer != null && MapOptions.shieldFirstKill && ((target == MapOptions.firstKillPlayer && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == MapOptions.firstKillPlayer))) {
+ if (Camouflager.camouflageTimer <= 0f && TORMapOptions.firstKillPlayer != null && TORMapOptions.shieldFirstKill && ((target == TORMapOptions.firstKillPlayer && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == TORMapOptions.firstKillPlayer))) {
hasVisibleShield = true;
color = Color.blue;
}
@@ -279,6 +279,12 @@ namespace TheOtherRoles.Patches {
Deputy.handcuffedKnows.Remove(CachedPlayer.LocalPlayer.PlayerId);
// Resets the buttons
Deputy.setHandcuffedKnows(false);
+
+ // Ghost info
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.HandcuffOver);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
}
}
@@ -504,7 +510,7 @@ namespace TheOtherRoles.Patches {
var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data);
string roleNames = RoleInfo.GetRolesString(p, true, false);
- string roleText = RoleInfo.GetRolesString(p, true, MapOptions.ghostsSeeModifier);
+ string roleText = RoleInfo.GetRolesString(p, true, TORMapOptions.ghostsSeeModifier);
string taskInfo = tasksTotal > 0 ? $"({tasksCompleted}/{tasksTotal})" : "";
string playerInfoText = "";
@@ -519,15 +525,15 @@ namespace TheOtherRoles.Patches {
}
meetingInfoText = $"{roleNames} {taskInfo}".Trim();
}
- else if (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks) {
+ else if (TORMapOptions.ghostsSeeRoles && TORMapOptions.ghostsSeeInformation) {
playerInfoText = $"{roleText} {taskInfo}".Trim();
meetingInfoText = playerInfoText;
}
- else if (MapOptions.ghostsSeeTasks) {
+ else if (TORMapOptions.ghostsSeeInformation) {
playerInfoText = $"{taskInfo}".Trim();
meetingInfoText = playerInfoText;
}
- else if (MapOptions.ghostsSeeRoles || (Lawyer.lawyerKnowsRole && CachedPlayer.LocalPlayer.PlayerControl == Lawyer.lawyer && p == Lawyer.target)) {
+ else if (TORMapOptions.ghostsSeeRoles || (Lawyer.lawyerKnowsRole && CachedPlayer.LocalPlayer.PlayerControl == Lawyer.lawyer && p == Lawyer.target)) {
playerInfoText = $"{roleText}";
meetingInfoText = playerInfoText;
}
@@ -587,41 +593,27 @@ namespace TheOtherRoles.Patches {
}
static void snitchUpdate() {
- if (Snitch.localArrows == null) return;
-
- foreach (Arrow arrow in Snitch.localArrows) arrow.arrow.SetActive(false);
-
- if (Snitch.snitch == null || Snitch.snitch.Data.IsDead) return;
+ if (Snitch.snitch == null) return;
+ bool snitchIsDead = Snitch.snitch.Data.IsDead;
var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data);
int numberOfTasks = playerTotal - playerCompleted;
- if (numberOfTasks <= Snitch.taskCountForReveal && (CachedPlayer.LocalPlayer.Data.Role.IsImpostor || (Snitch.includeTeamJackal && (CachedPlayer.LocalPlayer.PlayerControl == Jackal.jackal || CachedPlayer.LocalPlayer.PlayerControl == Sidekick.sidekick)))) {
- if (Snitch.localArrows.Count == 0) Snitch.localArrows.Add(new Arrow(Color.blue));
- if (Snitch.localArrows.Count != 0 && Snitch.localArrows[0] != null) {
- Snitch.localArrows[0].arrow.SetActive(true);
- Snitch.localArrows[0].Update(Snitch.snitch.transform.position);
+ if (Snitch.isRevealed && (CachedPlayer.LocalPlayer.PlayerControl.Data.Role.IsImpostor || Helpers.isNeutral(CachedPlayer.LocalPlayer.PlayerControl))) {
+ if (Snitch.text == null) {
+ Snitch.text = GameObject.Instantiate(FastDestroyableSingleton.Instance.KillButton.cooldownTimerText, FastDestroyableSingleton.Instance.transform);
+ Snitch.text.enableWordWrapping = false;
+ Snitch.text.transform.localScale = Vector3.one * 0.75f;
+ Snitch.text.transform.localPosition += new Vector3(0f, 1.8f, -69f);
+ Snitch.text.gameObject.SetActive(true);
+ } else {
+ Snitch.text.text = $"Snitch is alive: " + playerCompleted + "/" + playerTotal;
+ if (snitchIsDead) Snitch.text.text = $"Snitch is dead!";
}
- }
- else if (CachedPlayer.LocalPlayer.PlayerControl == Snitch.snitch && numberOfTasks == 0) {
- int arrowIndex = 0;
- foreach (PlayerControl p in CachedPlayer.AllPlayers)
- {
- bool arrowForImp = p.Data.Role.IsImpostor;
- bool arrowForTeamJackal = Snitch.includeTeamJackal && (p == Jackal.jackal || p == Sidekick.sidekick);
- if (!p.Data.IsDead && (arrowForImp || arrowForTeamJackal)) {
- if (arrowIndex >= Snitch.localArrows.Count) {
- Snitch.localArrows.Add(new Arrow(Palette.ImpostorRed));
- }
- if (arrowIndex < Snitch.localArrows.Count && Snitch.localArrows[arrowIndex] != null) {
- Snitch.localArrows[arrowIndex].arrow.SetActive(true);
- Snitch.localArrows[arrowIndex].Update(p.transform.position, (arrowForTeamJackal && Snitch.teamJackalUseDifferentArrowColor ? Jackal.color : Palette.ImpostorRed));
- }
- arrowIndex++;
- }
- }
}
+ if (snitchIsDead) return;
+ if (numberOfTasks <= Snitch.taskCountForReveal) Snitch.isRevealed = true;
}
static void bountyHunterUpdate() {
@@ -633,7 +625,7 @@ namespace TheOtherRoles.Patches {
if (BountyHunter.cooldownText != null && BountyHunter.cooldownText.gameObject != null) UnityEngine.Object.Destroy(BountyHunter.cooldownText.gameObject);
BountyHunter.cooldownText = null;
BountyHunter.bounty = null;
- foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
+ foreach (PoolablePlayer p in TORMapOptions.playerIcons.Values) {
if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
}
return;
@@ -654,17 +646,24 @@ namespace TheOtherRoles.Patches {
BountyHunter.bounty = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)];
if (BountyHunter.bounty == null) return;
+ // Ghost Info
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.BountyTarget);
+ writer.Write(BountyHunter.bounty.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+
// Show poolable player
if (FastDestroyableSingleton.Instance != null && FastDestroyableSingleton.Instance.UseButton != null) {
- foreach (PoolablePlayer pp in MapOptions.playerIcons.Values) pp.gameObject.SetActive(false);
- if (MapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null)
- MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(true);
+ foreach (PoolablePlayer pp in TORMapOptions.playerIcons.Values) pp.gameObject.SetActive(false);
+ if (TORMapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && TORMapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null)
+ TORMapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(true);
}
}
// Hide in meeting
- if (MeetingHud.Instance && MapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null)
- MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(false);
+ if (MeetingHud.Instance && TORMapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && TORMapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null)
+ TORMapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(false);
// Update Cooldown Text
if (BountyHunter.cooldownText != null) {
@@ -1051,6 +1050,7 @@ namespace TheOtherRoles.Patches {
miniCooldownUpdate();
// Chameleon (invis stuff, timers)
Chameleon.update();
+ Bomb.update();
// -- GAME MODE --
hunterUpdate();
@@ -1114,6 +1114,13 @@ namespace TheOtherRoles.Patches {
if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance)
{
FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, msg);
+
+ // Ghost Info
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareGhostInfo, Hazel.SendOption.Reliable, -1);
+ writer.Write(CachedPlayer.LocalPlayer.PlayerId);
+ writer.Write((byte)RPCProcedure.GhostInfoTypes.DetectiveOrMedicInfo);
+ writer.Write(msg);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
}
if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
{
@@ -1155,7 +1162,7 @@ namespace TheOtherRoles.Patches {
target.clearAllTasks();
// First kill (set before lover suicide)
- if (MapOptions.firstKillName == "") MapOptions.firstKillName = target.Data.PlayerName;
+ if (TORMapOptions.firstKillName == "") TORMapOptions.firstKillName = target.Data.PlayerName;
// Lover suicide trigger on murder
if ((Lovers.lover1 != null && target == Lovers.lover1) || (Lovers.lover2 != null && target == Lovers.lover2)) {
@@ -1180,8 +1187,8 @@ namespace TheOtherRoles.Patches {
}
// Seer show flash and add dead player position
- if (Seer.seer != null && CachedPlayer.LocalPlayer.PlayerControl == Seer.seer && !Seer.seer.Data.IsDead && Seer.seer != target && Seer.mode <= 1) {
- Helpers.showFlash(new Color(42f / 255f, 187f / 255f, 245f / 255f));
+ if (Seer.seer != null && (CachedPlayer.LocalPlayer.PlayerControl == Seer.seer || Helpers.shouldShowGhostInfo()) && !Seer.seer.Data.IsDead && Seer.seer != target && Seer.mode <= 1) {
+ Helpers.showFlash(new Color(42f / 255f, 187f / 255f, 245f / 255f), message : "Seer Info: Someone Died");
}
if (Seer.deadBodyPositions != null) Seer.deadBodyPositions.Add(target.transform.position);
@@ -1261,12 +1268,12 @@ namespace TheOtherRoles.Patches {
int visibleCounter = 0;
Vector3 bottomLeft = IntroCutsceneOnDestroyPatch.bottomLeft + new Vector3(-0.25f, -0.25f, 0);
foreach (PlayerControl p in CachedPlayer.AllPlayers) {
- if (!MapOptions.playerIcons.ContainsKey(p.PlayerId) || p.Data.Role.IsImpostor) continue;
+ if (!TORMapOptions.playerIcons.ContainsKey(p.PlayerId) || p.Data.Role.IsImpostor) continue;
if (p.Data.IsDead || p.Data.Disconnected) {
- MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
+ TORMapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
}
else {
- MapOptions.playerIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f;
+ TORMapOptions.playerIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f;
visibleCounter++;
}
}
@@ -1364,15 +1371,40 @@ namespace TheOtherRoles.Patches {
public static class PlayerPhysicsFixedUpdate {
public static void Postfix(PlayerPhysics __instance)
{
+ bool shouldInvert = (Invert.invert.FindAll(x => x.PlayerId == CachedPlayer.LocalPlayer.PlayerId).Count > 0 && Invert.meetings > 0) ^ EventUtility.eventInvert; // xor. if already invert, eventInvert will turn it off for 10s
if (__instance.AmOwner &&
AmongUsClient.Instance &&
AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started &&
!CachedPlayer.LocalPlayer.Data.IsDead &&
- Invert.invert.FindAll(x => x.PlayerId == CachedPlayer.LocalPlayer.PlayerId).Count > 0 &&
- Invert.meetings > 0 &&
+ shouldInvert &&
GameData.Instance &&
__instance.myPlayer.CanMove)
__instance.body.velocity *= -1;
}
}
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.IsFlashlightEnabled))]
+ public static class IsFlashlightEnabledPatch {
+ public static bool Prefix(ref bool __result) {
+ __result = false;
+ if (!CachedPlayer.LocalPlayer.Data.IsDead && Lighter.lighter != null && Lighter.lighter.PlayerId == CachedPlayer.LocalPlayer.PlayerId) {
+ __result = true;
+ }
+
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.AdjustLighting))]
+ public static class AdjustLight {
+ public static bool Prefix(PlayerControl __instance) {
+ if (__instance == null || CachedPlayer.LocalPlayer == null || Lighter.lighter == null) return true;
+
+ bool hasFlashlight = !CachedPlayer.LocalPlayer.Data.IsDead && Lighter.lighter.PlayerId == CachedPlayer.LocalPlayer.PlayerId;
+ __instance.SetFlashlightInputMethod();
+ __instance.lightSource.SetupLightingForGameplay(hasFlashlight, Lighter.flashlightWidth, __instance.TargetFlashlight.transform);
+
+ return false;
+ }
+ }
}
diff --git a/TheOtherRoles/Patches/RegionMenuPatch.cs b/TheOtherRoles/Patches/RegionMenuPatch.cs
index 1dfd714..2f69c75 100644
--- a/TheOtherRoles/Patches/RegionMenuPatch.cs
+++ b/TheOtherRoles/Patches/RegionMenuPatch.cs
@@ -78,7 +78,7 @@ namespace TheOtherRoles.Patches {
if (arrow == null || arrow.gameObject == null) return;
UnityEngine.Object.DestroyImmediate(arrow.gameObject);
- ipField.transform.localPosition = new Vector3(0.225f, -1f, -100f);
+ ipField.transform.localPosition = new Vector3(3.225f, -0.8f, -100f);
ipField.characterLimit = 30;
ipField.AllowSymbols = true;
ipField.ForceUppercase = false;
@@ -101,7 +101,6 @@ namespace TheOtherRoles.Patches {
void onFocusLost() {
TheOtherRolesPlugin.UpdateRegions();
- __instance.ChooseOption(ServerManager.DefaultRegions[ServerManager.DefaultRegions.Length - 1]);
}
}
@@ -112,7 +111,7 @@ namespace TheOtherRoles.Patches {
if (arrow == null || arrow.gameObject == null) return;
UnityEngine.Object.DestroyImmediate(arrow.gameObject);
- portField.transform.localPosition = new Vector3(0.225f, -1.75f, -100f);
+ portField.transform.localPosition = new Vector3(3.225f, -1.55f, -100f);
portField.characterLimit = 5;
portField.SetText(TheOtherRolesPlugin.Port.Value.ToString());
__instance.StartCoroutine(Effects.Lerp(0.1f, new Action((p) => {
@@ -140,9 +139,23 @@ namespace TheOtherRoles.Patches {
void onFocusLost() {
TheOtherRolesPlugin.UpdateRegions();
- __instance.ChooseOption(ServerManager.DefaultRegions[ServerManager.DefaultRegions.Length - 1]);
}
}
}
}
+
+ [HarmonyPatch(typeof(RegionMenu), nameof(RegionMenu.ChooseOption))]
+ public static class RegionMenuChooseOptionPatch {
+ public static bool Prefix(RegionMenu __instance, IRegionInfo region) {
+ if (region.Name != "Custom" || FastDestroyableSingleton.Instance.CurrentRegion.Name == "Custom") return true;
+ DestroyableSingleton.Instance.SetRegion(region);
+ __instance.RegionText.text = "Custom";
+ foreach (var Button in __instance.ButtonPool.activeChildren) {
+ ServerListButton serverListButton = Button.TryCast();
+ if (serverListButton != null) serverListButton.SetSelected(serverListButton.Text.text == "Custom");
+ }
+ __instance.Open();
+ return false;
+ }
+ }
}
\ No newline at end of file
diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs
index 41d6d8d..d65bdcb 100644
--- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs
+++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs
@@ -21,7 +21,7 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(IGameOptionsExtensions), nameof(IGameOptionsExtensions.GetAdjustedNumImpostors))]
class GameOptionsDataGetAdjustedNumImpostorsPatch {
public static void Postfix(ref int __result) {
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek) {
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) {
int impCount = Mathf.RoundToInt(CustomOptionHolder.hideNSeekHunterCount.getFloat());
__result = impCount; ; // Set Imp Num
} else if (GameOptionsManager.Instance.CurrentGameOptions.GameMode == GameModes.Normal) { // Ignore Vanilla impostor limits in TOR Games.
@@ -33,7 +33,7 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.Validate))]
class GameOptionsDataValidatePatch {
public static void Postfix(GameOptionsData __instance) {
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek || GameOptionsManager.Instance.CurrentGameOptions.GameMode != GameModes.Normal) return;
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || GameOptionsManager.Instance.CurrentGameOptions.GameMode != GameModes.Normal) return;
__instance.NumImpostors = GameOptionsManager.Instance.CurrentGameOptions.NumImpostors;
}
}
@@ -44,12 +44,12 @@ namespace TheOtherRoles.Patches {
private static int impValues;
private static bool isEvilGuesser;
private static List> playerRoleMap = new List>();
- public static bool isGuesserGamemode { get { return MapOptions.gameMode == CustomGamemodes.Guesser; } }
+ public static bool isGuesserGamemode { get { return TORMapOptions.gameMode == CustomGamemodes.Guesser; } }
public static void Postfix() {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.resetVariables();
- if (MapOptions.gameMode == CustomGamemodes.HideNSeek || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; // Don't assign Roles in Hide N Seek
+ if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek || GameOptionsManager.Instance.currentGameOptions.GameMode == GameModes.HideNSeek) return; // Don't assign Roles in Hide N Seek
if (CustomOptionHolder.activateRoles.getBool()) // Don't assign Roles in Tutorial or if deactivated
assignRoles();
}
@@ -80,6 +80,12 @@ namespace TheOtherRoles.Patches {
var neutralMax = CustomOptionHolder.neutralRolesCountMax.getSelection();
var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection();
var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection();
+
+ // Automatically force everyone to get a role by setting crew Min / Max according to Neutral Settings
+ if (CustomOptionHolder.crewmateRolesFill.getBool()) {
+ crewmateMax = crewmates.Count - neutralMin;
+ crewmateMin = crewmates.Count - neutralMax;
+ }
// Make sure min is less or equal to max
if (crewmateMin > crewmateMax) crewmateMin = crewmateMax;
@@ -111,6 +117,7 @@ namespace TheOtherRoles.Patches {
impSettings.Add((byte)RoleId.BountyHunter, CustomOptionHolder.bountyHunterSpawnRate.getSelection());
impSettings.Add((byte)RoleId.Witch, CustomOptionHolder.witchSpawnRate.getSelection());
impSettings.Add((byte)RoleId.Ninja, CustomOptionHolder.ninjaSpawnRate.getSelection());
+ impSettings.Add((byte)RoleId.Bomber, CustomOptionHolder.bomberSpawnRate.getSelection());
neutralSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection());
neutralSettings.Add((byte)RoleId.Arsonist, CustomOptionHolder.arsonistSpawnRate.getSelection());
diff --git a/TheOtherRoles/Patches/ShipStatusPatch.cs b/TheOtherRoles/Patches/ShipStatusPatch.cs
index 7da988b..40d6cd9 100644
--- a/TheOtherRoles/Patches/ShipStatusPatch.cs
+++ b/TheOtherRoles/Patches/ShipStatusPatch.cs
@@ -28,7 +28,7 @@ namespace TheOtherRoles.Patches {
}
// If player is Lighter with ability active
- if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId && Lighter.lighterTimer > 0f) {
+ if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId) {
float unlerped = Mathf.InverseLerp(__instance.MinLightRadius, __instance.MaxLightRadius, GetNeutralLightRadius(__instance, false));
__result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, unlerped);
}
@@ -103,7 +103,7 @@ namespace TheOtherRoles.Patches {
originalNumShortTasksOption = GameOptionsManager.Instance.currentNormalGameOptions.NumShortTasks;
originalNumLongTasksOption = GameOptionsManager.Instance.currentNormalGameOptions.NumLongTasks;
- if (MapOptions.gameMode != CustomGamemodes.HideNSeek) {
+ if (TORMapOptions.gameMode != CustomGamemodes.HideNSeek) {
var commonTaskCount = __instance.CommonTasks.Count;
var normalTaskCount = __instance.NormalTasks.Count;
var longTaskCount = __instance.LongTasks.Count;
diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs
index d606cc0..6dcf225 100644
--- a/TheOtherRoles/Patches/UpdatePatch.cs
+++ b/TheOtherRoles/Patches/UpdatePatch.cs
@@ -224,7 +224,7 @@ namespace TheOtherRoles.Patches {
}
// Display lighter / darker color for all alive players
- if (CachedPlayer.LocalPlayer != null && MeetingHud.Instance != null && MapOptions.showLighterDarker) {
+ if (CachedPlayer.LocalPlayer != null && MeetingHud.Instance != null && TORMapOptions.showLighterDarker) {
foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
var target = Helpers.playerById(player.TargetPlayerId);
if (target != null) player.NameText.text += $" ({(Helpers.isLighterColor(target.Data.DefaultOutfit.ColorId) ? "L" : "D")})";
@@ -243,7 +243,6 @@ namespace TheOtherRoles.Patches {
static void timerUpdate() {
var dt = Time.deltaTime;
Hacker.hackerTimer -= dt;
- Lighter.lighterTimer -= dt;
Trickster.lightsOutTimer -= dt;
Tracker.corpsesTrackingTimer -= dt;
Ninja.invisibleTimer -= dt;
@@ -311,7 +310,7 @@ namespace TheOtherRoles.Patches {
}
static void updateSabotageButton(HudManager __instance) {
- if (MeetingHud.Instance || MapOptions.gameMode == CustomGamemodes.HideNSeek) __instance.SabotageButton.Hide();
+ if (MeetingHud.Instance || TORMapOptions.gameMode == CustomGamemodes.HideNSeek) __instance.SabotageButton.Hide();
}
static void updateMapButton(HudManager __instance) {
@@ -322,6 +321,8 @@ namespace TheOtherRoles.Patches {
static void Postfix(HudManager __instance)
{
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
+
+ EventUtility.Update();
CustomButton.HudUpdate();
resetNameTagsAndColors();
@@ -343,7 +344,7 @@ namespace TheOtherRoles.Patches {
updateSabotageButton(__instance);
updateUseButton(__instance);
updateMapButton(__instance);
-
+ if (!MeetingHud.Instance) __instance.AbilityButton?.Update();
}
}
}
diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs
index 9bd4c2d..d8a299b 100644
--- a/TheOtherRoles/Patches/UsablesPatch.cs
+++ b/TheOtherRoles/Patches/UsablesPatch.cs
@@ -5,12 +5,13 @@ using UnityEngine;
using System.Linq;
using static TheOtherRoles.TheOtherRoles;
using static TheOtherRoles.GameHistory;
-using static TheOtherRoles.MapOptions;
+using static TheOtherRoles.TORMapOptions;
using System.Collections.Generic;
using TheOtherRoles.Players;
using TheOtherRoles.Utilities;
using TheOtherRoles.Objects;
using TheOtherRoles.CustomGameModes;
+using Reactor.Utilities.Extensions;
namespace TheOtherRoles.Patches {
@@ -356,7 +357,6 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch]
class AdminPanelPatch {
static Dictionary> players = new Dictionary>();
-
[HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))]
class MapCountOverlayUpdatePatch {
static bool Prefix(MapCountOverlay __instance) {
@@ -397,31 +397,19 @@ namespace TheOtherRoles.Patches {
{
PlainShipRoom plainShipRoom = MapUtilities.CachedShipStatus.FastRooms[counterArea.RoomType];
- if (plainShipRoom != null && plainShipRoom.roomArea)
- {
+ if (plainShipRoom != null && plainShipRoom.roomArea) {
+
+
+ HashSet hashSet = new HashSet();
int num = plainShipRoom.roomArea.OverlapCollider(__instance.filter, __instance.buffer);
- int num2 = num;
- for (int j = 0; j < num; j++)
- {
+ int num2 = 0;
+ for (int j = 0; j < num; j++) {
Collider2D collider2D = __instance.buffer[j];
- if (!(collider2D.tag == "DeadBody"))
- {
- PlayerControl component = collider2D.GetComponent();
- if (!component || component.Data == null || component.Data.Disconnected || component.Data.IsDead)
- {
- num2--;
- } else if (component?.cosmetics?.currentBodySprite?.BodySprite?.material != null) {
- Color color = component.cosmetics.currentBodySprite.BodySprite.material.GetColor("_BodyColor");
- if (Hacker.onlyColorType) {
- var id = Mathf.Max(0, Palette.PlayerColors.IndexOf(color));
- color = Helpers.isLighterColor((byte)id) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
- }
- roomColors.Add(color);
- }
- } else {
- DeadBody component = collider2D.GetComponent();
- if (component) {
- GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
+ if (collider2D.CompareTag("DeadBody") && __instance.includeDeadBodies) {
+ num2++;
+ DeadBody bodyComponent = collider2D.GetComponent();
+ if (bodyComponent) {
+ GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(bodyComponent.ParentId);
if (playerInfo != null) {
var color = Palette.PlayerColors[playerInfo.DefaultOutfit.ColorId];
if (Hacker.onlyColorType)
@@ -429,8 +417,22 @@ namespace TheOtherRoles.Patches {
roomColors.Add(color);
}
}
+ } else {
+ PlayerControl component = collider2D.GetComponent();
+ if (component && component.Data != null && !component.Data.Disconnected && !component.Data.IsDead && (__instance.showLivePlayerPosition || !component.AmOwner) && hashSet.Add((int)component.PlayerId)) {
+ num2++;
+ if (component?.cosmetics?.currentBodySprite?.BodySprite?.material != null) {
+ Color color = component.cosmetics.currentBodySprite.BodySprite.material.GetColor("_BodyColor");
+ if (Hacker.onlyColorType) {
+ var id = Mathf.Max(0, Palette.PlayerColors.IndexOf(color));
+ color = Helpers.isLighterColor((byte)id) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
+ }
+ roomColors.Add(color);
+ }
+ }
}
}
+
counterArea.UpdateCount(num2);
}
else
@@ -490,6 +492,12 @@ namespace TheOtherRoles.Patches {
class SurveillanceMinigamePatch {
private static int page = 0;
private static float timer = 0f;
+
+ public static List nightVisionOverlays = null;
+ private static Sprite overlaySprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.NightVisionOverlay.png", 350f);
+ public static bool nightVisionIsActive = false;
+ private static bool isLightsOut;
+
[HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Begin))]
class SurveillanceMinigameBeginPatch {
public static void Postfix(SurveillanceMinigame __instance) {
@@ -514,7 +522,6 @@ namespace TheOtherRoles.Patches {
[HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Update))]
class SurveillanceMinigameUpdatePatch {
-
public static bool Prefix(SurveillanceMinigame __instance) {
// Update normal and securityGuard cameras
timer += Time.deltaTime;
@@ -522,11 +529,11 @@ namespace TheOtherRoles.Patches {
bool update = false;
- if (timer > 3f || Input.GetKeyDown(KeyCode.RightArrow)) {
+ if (timer > 3f || Input.GetKeyDown(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.D)) {
update = true;
timer = 0f;
page = (page + 1) % numberOfPages;
- } else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
+ } else if (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.A)) {
page = (page + numberOfPages - 1) % numberOfPages;
update = true;
timer = 0f;
@@ -549,27 +556,159 @@ namespace TheOtherRoles.Patches {
__instance.SabText[j].gameObject.SetActive(true);
}
}
+
+ nightVisionUpdate(SkeldCamsMinigame: __instance);
return false;
}
}
+
+ [HarmonyPatch(typeof(PlanetSurveillanceMinigame), nameof(PlanetSurveillanceMinigame.Update))]
+ class PlanetSurveillanceMinigameUpdatePatch {
+ public static void Postfix(PlanetSurveillanceMinigame __instance) {
+ if (Input.GetKeyDown(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.D))
+ __instance.NextCamera(1);
+ if (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.A))
+ __instance.NextCamera(-1);
+
+ nightVisionUpdate(SwitchCamsMinigame: __instance);
+ }
+ }
+
+ [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.OnDestroy))]
+ class SurveillanceMinigameDestroyPatch {
+ public static void Prefix() {
+ resetNightVision();
+ }
+ }
+
+ [HarmonyPatch(typeof(PlanetSurveillanceMinigame), nameof(PlanetSurveillanceMinigame.OnDestroy))]
+ class PlanetSurveillanceMinigameDestroyPatch {
+ public static void Prefix() {
+ resetNightVision();
+ }
+ }
+
+
+ private static void nightVisionUpdate(SurveillanceMinigame SkeldCamsMinigame = null, PlanetSurveillanceMinigame SwitchCamsMinigame = null) {
+ if (nightVisionOverlays == null) {
+ GameObject closeButton = null;
+ List viewPorts = new();
+ Transform viewablesTransform = null;
+ if (SkeldCamsMinigame != null) {
+ closeButton = SkeldCamsMinigame.Viewables.transform.Find("CloseButton").gameObject;
+ foreach (var rend in SkeldCamsMinigame.ViewPorts) viewPorts.Add(rend);
+ viewablesTransform = SkeldCamsMinigame.Viewables.transform;
+ } else if (SwitchCamsMinigame != null) {
+ closeButton = SwitchCamsMinigame.Viewables.transform.Find("CloseButton").gameObject;
+ viewPorts.Add(SwitchCamsMinigame.ViewPort);
+ viewablesTransform = SwitchCamsMinigame.Viewables.transform;
+ } else return;
+
+ nightVisionOverlays = new List();
+
+ foreach (var renderer in viewPorts) {
+ GameObject overlayObject = GameObject.Instantiate(closeButton, viewablesTransform);
+ overlayObject.transform.position = new Vector3(renderer.transform.position.x, renderer.transform.position.y, overlayObject.transform.position.z);
+ overlayObject.transform.localScale = (SkeldCamsMinigame != null) ? new Vector3(0.91f, 0.612f, 1f) : new Vector3(2.124f, 1.356f, 1f);
+ overlayObject.layer = closeButton.layer;
+ var overlayRenderer = overlayObject.GetComponent();
+ overlayRenderer.sprite = overlaySprite;
+ overlayObject.SetActive(false);
+ GameObject.Destroy(overlayObject.GetComponent());
+ nightVisionOverlays.Add(overlayObject);
+ }
+ }
+
+
+ isLightsOut = CachedPlayer.LocalPlayer.PlayerControl.myTasks.ToArray().Any(x => x.name.Contains("FixLightsTask"));
+ bool ignoreNightVision = CustomOptionHolder.camsNoNightVisionIfImpVision.getBool() && Helpers.hasImpVision(GameData.Instance.GetPlayerById(CachedPlayer.LocalPlayer.PlayerId)) || CachedPlayer.LocalPlayer.Data.IsDead;
+ bool nightVisionEnabled = CustomOptionHolder.camsNightVision.getBool();
+
+ if (isLightsOut && !nightVisionIsActive && nightVisionEnabled && !ignoreNightVision) { // only update when something changed!
+ foreach (PlayerControl pc in CachedPlayer.AllPlayers) {
+ if (pc == Ninja.ninja && Ninja.invisibleTimer > 0f) {
+ continue;
+ }
+ pc.setLook("", 11, "", "", "", "", false);
+ }
+ foreach (var overlayObject in nightVisionOverlays) {
+ overlayObject.SetActive(true);
+ }
+ // Dead Bodies
+ foreach (DeadBody deadBody in GameObject.FindObjectsOfType()) {
+ SpriteRenderer component = deadBody.bodyRenderer;
+ component.material.SetColor("_BackColor", Palette.ShadowColors[11]);
+ component.material.SetColor("_BodyColor", Palette.PlayerColors[11]);
+ }
+ nightVisionIsActive = true;
+ } else if (!isLightsOut && nightVisionIsActive) {
+ resetNightVision();
+ }
+ }
+
+ private static void resetNightVision() {
+ foreach (var go in nightVisionOverlays) {
+ go.Destroy();
+ }
+ nightVisionOverlays = null;
+
+ if (nightVisionIsActive) {
+ nightVisionIsActive = false;
+ foreach (PlayerControl pc in CachedPlayer.AllPlayers) {
+ if (Camouflager.camouflageTimer > 0) {
+ pc.setLook("", 6, "", "", "", "", false);
+ } else if (pc == Morphling.morphling && Morphling.morphTimer > 0) {
+ PlayerControl target = Morphling.morphTarget;
+ Morphling.morphling.setLook(target.Data.PlayerName, target.Data.DefaultOutfit.ColorId, target.Data.DefaultOutfit.HatId, target.Data.DefaultOutfit.VisorId, target.Data.DefaultOutfit.SkinId, target.Data.DefaultOutfit.PetId, false);
+ } else if (pc == Ninja.ninja && Ninja.invisibleTimer > 0f) {
+ continue;
+ } else {
+ Helpers.setDefaultLook(pc, false);
+ }
+ // Dead Bodies
+ foreach (DeadBody deadBody in GameObject.FindObjectsOfType()) {
+ var colorId = GameData.Instance.GetPlayerById(deadBody.ParentId).Object.Data.DefaultOutfit.ColorId;
+ SpriteRenderer component = deadBody.bodyRenderer;
+ component.material.SetColor("_BackColor", Palette.ShadowColors[colorId]);
+ component.material.SetColor("_BodyColor", Palette.PlayerColors[colorId]);
+ }
+ }
+ }
+
+ }
+
+ public static void enforceNightVision(PlayerControl player) {
+ if (isLightsOut && nightVisionOverlays != null && nightVisionIsActive) {
+ player.setLook("", 11, "", "", "", "", false);
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetPlayerMaterialColors))]
+ public static void Postfix(PlayerControl __instance, SpriteRenderer rend) {
+ if (!nightVisionIsActive) return;
+ foreach (DeadBody deadBody in GameObject.FindObjectsOfType()) {
+ foreach (SpriteRenderer component in new SpriteRenderer[2] { deadBody.bodyRenderer, deadBody.bloodSplatter }) {
+ component.material.SetColor("_BackColor", Palette.ShadowColors[11]);
+ component.material.SetColor("_BodyColor", Palette.PlayerColors[11]);
+ }
+ }
+ }
}
[HarmonyPatch(typeof(MedScanMinigame), nameof(MedScanMinigame.FixedUpdate))]
class MedScanMinigameFixedUpdatePatch {
static void Prefix(MedScanMinigame __instance) {
- if (MapOptions.allowParallelMedBayScans) {
+ if (TORMapOptions.allowParallelMedBayScans) {
__instance.medscan.CurrentUser = CachedPlayer.LocalPlayer.PlayerId;
__instance.medscan.UsersList.Clear();
}
}
}
-
[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.ShowSabotageMap))]
class ShowSabotageMapPatch {
static bool Prefix(MapBehaviour __instance) {
- if (HideNSeek.isHideNSeekGM)
+ if (HideNSeek.isHideNSeekGM)
return HideNSeek.canSabotage;
-
return true;
}
}
diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs
index 5f8f523..57f31a2 100644
--- a/TheOtherRoles/RPC.cs
+++ b/TheOtherRoles/RPC.cs
@@ -3,7 +3,7 @@ using Hazel;
using static TheOtherRoles.TheOtherRoles;
using static TheOtherRoles.HudManagerStartPatch;
using static TheOtherRoles.GameHistory;
-using static TheOtherRoles.MapOptions;
+using static TheOtherRoles.TORMapOptions;
using TheOtherRoles.Objects;
using TheOtherRoles.Patches;
using System.Collections.Generic;
@@ -61,6 +61,7 @@ namespace TheOtherRoles
Witch,
Ninja,
Thief,
+ Bomber,
Crewmate,
Impostor,
// Modifier ---
@@ -131,7 +132,6 @@ namespace TheOtherRoles
SealVent,
ArsonistWin,
GuesserShoot,
- VultureWin,
LawyerSetTarget,
LawyerPromotesToPursuer,
SetBlanked,
@@ -143,12 +143,19 @@ namespace TheOtherRoles
ThiefStealsRole,
SetTrap,
TriggerTrap,
+ MayorSetVoteTwice,
+ PlaceBomb,
+ DefuseBomb,
+ ShareRoom,
// Gamemode
SetGuesserGm,
HuntedShield,
HuntedRewindTime,
- ShareTimer
+
+ // Other functionality
+ ShareTimer,
+ ShareGhostInfo,
}
public static class RPCProcedure {
@@ -166,11 +173,14 @@ namespace TheOtherRoles
clearAndReloadRoles();
clearGameHistory();
setCustomButtonCooldowns();
+ reloadPluginOptions();
Helpers.toggleZoom(reset : true);
GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 0;
+ SurveillanceMinigamePatch.nightVisionOverlays = null;
+ EventUtility.clearAndReload();
}
- public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) {
+ public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) {
try {
for (int i = 0; i < numberOfOptions; i++) {
uint optionId = reader.ReadPackedUInt32();
@@ -200,7 +210,7 @@ namespace TheOtherRoles
}
public static void shareGamemode(byte gm) {
- MapOptions.gameMode = (CustomGamemodes) gm;
+ TORMapOptions.gameMode = (CustomGamemodes) gm;
}
public static void workaroundSetRoles(byte numberOfRoles, MessageReader reader)
@@ -351,8 +361,11 @@ namespace TheOtherRoles
break;
case RoleId.Thief:
Thief.thief = player;
- break;
- }
+ break;
+ case RoleId.Bomber:
+ Bomber.bomber = player;
+ break;
+ }
}
}
@@ -462,15 +475,29 @@ namespace TheOtherRoles
public static void engineerUsedRepair() {
Engineer.remainingFixes--;
+ if (Helpers.shouldShowGhostInfo()) {
+ Helpers.showFlash(Engineer.color, 0.5f, "Engineer Fix"); ;
+ }
}
- public static void cleanBody(byte playerId) {
+ public static void cleanBody(byte playerId, byte cleaningPlayerId) {
+ if (Medium.featureDeadBodies != null) {
+ var deadBody = Medium.featureDeadBodies.Find(x => x.Item1.player.PlayerId == playerId).Item1;
+ if (deadBody != null) deadBody.wasCleaned = true;
+ }
+
DeadBody[] array = UnityEngine.Object.FindObjectsOfType();
for (int i = 0; i < array.Length; i++) {
if (GameData.Instance.GetPlayerById(array[i].ParentId).PlayerId == playerId) {
UnityEngine.Object.Destroy(array[i].gameObject);
}
}
+ if (Vulture.vulture != null && cleaningPlayerId == Vulture.vulture.PlayerId) {
+ Vulture.eatenBodies++;
+ if (Vulture.eatenBodies == Vulture.vultureNumberToWin) {
+ Vulture.triggerVultureWin = true;
+ }
+ }
}
public static void timeMasterRewindTime() {
@@ -517,7 +544,7 @@ namespace TheOtherRoles
isShieldedAndShow = isShieldedAndShow && (Medic.meetingAfterShielding || !Medic.showShieldAfterMeeting); // Dont show attempt, if shield is not shown yet
bool isMedicAndShow = Medic.medic == CachedPlayer.LocalPlayer.PlayerControl && Medic.showAttemptToMedic;
- if (isShieldedAndShow || isMedicAndShow) Helpers.showFlash(Palette.ImpostorRed, duration: 0.5f);
+ if (isShieldedAndShow || isMedicAndShow || Helpers.shouldShowGhostInfo()) Helpers.showFlash(Palette.ImpostorRed, duration: 0.5f, "Failed Murder Attempt on Shielded Player");
}
public static void shifterShift(byte targetId) {
@@ -759,6 +786,8 @@ namespace TheOtherRoles
position.x = BitConverter.ToSingle(buff, 0 * sizeof(float));
position.y = BitConverter.ToSingle(buff, 1 * sizeof(float));
new NinjaTrace(position, Ninja.traceTime);
+ if (CachedPlayer.LocalPlayer.PlayerControl != Ninja.ninja)
+ Ninja.ninjaMarked = null;
}
public static void setInvisible(byte playerId, byte flag)
@@ -790,8 +819,8 @@ namespace TheOtherRoles
new Portal(position);
}
- public static void usePortal(byte playerId) {
- Portal.startTeleport(playerId);
+ public static void usePortal(byte playerId, byte exit) {
+ Portal.startTeleport(playerId, exit);
}
public static void placeJackInTheBox(byte[] buff) {
@@ -842,7 +871,7 @@ namespace TheOtherRoles
} else {
camera.gameObject.SetActive(false);
}
- MapOptions.camerasToAdd.Add(camera);
+ TORMapOptions.camerasToAdd.Add(camera);
}
public static void sealVent(int ventId) {
@@ -861,7 +890,7 @@ namespace TheOtherRoles
vent.name = "FutureSealedVent_" + vent.name;
}
- MapOptions.ventsToSeal.Add(vent);
+ TORMapOptions.ventsToSeal.Add(vent);
}
public static void arsonistWin() {
@@ -871,10 +900,6 @@ namespace TheOtherRoles
}
}
- public static void vultureWin() {
- Vulture.triggerVultureWin = true;
- }
-
public static void lawyerSetTarget(byte playerId) {
Lawyer.target = Helpers.playerById(playerId);
}
@@ -974,7 +999,7 @@ namespace TheOtherRoles
public static void setFirstKill(byte playerId) {
PlayerControl target = Helpers.playerById(playerId);
if (target == null) return;
- MapOptions.firstKillPlayer = target;
+ TORMapOptions.firstKillPlayer = target;
}
public static void setTiebreak() {
@@ -1008,6 +1033,7 @@ namespace TheOtherRoles
if (target == BountyHunter.bountyHunter) BountyHunter.bountyHunter = thief;
if (target == Witch.witch) Witch.witch = thief;
if (target == Ninja.ninja) Ninja.ninja = thief;
+ if (target == Bomber.bomber) Bomber.bomber = thief;
if (target.Data.Role.IsImpostor) {
RoleManager.Instance.SetRole(Thief.thief, RoleTypes.Impostor);
FastDestroyableSingleton.Instance.KillButton.SetCoolDown(Thief.thief.killTimer, GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown);
@@ -1072,7 +1098,83 @@ namespace TheOtherRoles
Minigame.Instance.ForceClose();
CachedPlayer.LocalPlayer.PlayerControl.moveable = false;
}
- }
+
+ public enum GhostInfoTypes {
+ HandcuffNoticed,
+ HandcuffOver,
+ ArsonistDouse,
+ BountyTarget,
+ NinjaMarked,
+ WarlockTarget,
+ MediumInfo,
+ BlankUsed,
+ DetectiveOrMedicInfo,
+ VampireTimer,
+ }
+
+ public static void receiveGhostInfo (byte senderId, MessageReader reader) {
+ PlayerControl sender = Helpers.playerById(senderId);
+
+ GhostInfoTypes infoType = (GhostInfoTypes)reader.ReadByte();
+ switch (infoType) {
+ case GhostInfoTypes.HandcuffNoticed:
+ Deputy.setHandcuffedKnows(true, senderId);
+ break;
+ case GhostInfoTypes.HandcuffOver:
+ _ = Deputy.handcuffedKnows.Remove(senderId);
+ break;
+ case GhostInfoTypes.ArsonistDouse:
+ Arsonist.dousedPlayers.Add(Helpers.playerById(reader.ReadByte()));
+ break;
+ case GhostInfoTypes.BountyTarget:
+ BountyHunter.bounty = Helpers.playerById(reader.ReadByte());
+ break;
+ case GhostInfoTypes.NinjaMarked:
+ Ninja.ninjaMarked = Helpers.playerById(reader.ReadByte());
+ break;
+ case GhostInfoTypes.WarlockTarget:
+ Warlock.curseVictim = Helpers.playerById(reader.ReadByte());
+ break;
+ case GhostInfoTypes.MediumInfo:
+ string mediumInfo = reader.ReadString();
+ if (Helpers.shouldShowGhostInfo())
+ FastDestroyableSingleton.Instance.Chat.AddChat(sender, mediumInfo);
+ break;
+ case GhostInfoTypes.DetectiveOrMedicInfo:
+ string detectiveInfo = reader.ReadString();
+ if (Helpers.shouldShowGhostInfo())
+ FastDestroyableSingleton.Instance.Chat.AddChat(sender, detectiveInfo);
+ break;
+ case GhostInfoTypes.BlankUsed:
+ Pursuer.blankedList.Remove(sender);
+ break;
+ case GhostInfoTypes.VampireTimer:
+ HudManagerStartPatch.vampireKillButton.Timer = (float)reader.ReadByte();
+ break;
+ }
+ }
+
+ public static void placeBomb(byte[] buff) {
+ if (Bomber.bomber == null) return;
+ Vector3 position = Vector3.zero;
+ position.x = BitConverter.ToSingle(buff, 0 * sizeof(float));
+ position.y = BitConverter.ToSingle(buff, 1 * sizeof(float));
+ new Bomb(position);
+ }
+
+ public static void defuseBomb() {
+ SoundEffectsManager.playAtPosition("bombDefused", Bomber.bomb.bomb.transform.position, range: Bomber.hearRange);
+ Bomber.clearBomb();
+ bomberButton.Timer = bomberButton.MaxTimer;
+ bomberButton.isEffectActive = false;
+ bomberButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ }
+
+ public static void shareRoom(byte playerId, byte roomId) {
+ if (Snitch.playerRoomMap.ContainsKey(playerId)) Snitch.playerRoomMap[playerId] = roomId;
+ else Snitch.playerRoomMap.Add(playerId, roomId);
+ }
+ }
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))]
class RPCHandlerPatch
@@ -1167,7 +1269,7 @@ namespace TheOtherRoles
RPCProcedure.engineerUsedRepair();
break;
case (byte)CustomRPC.CleanBody:
- RPCProcedure.cleanBody(reader.ReadByte());
+ RPCProcedure.cleanBody(reader.ReadByte(), reader.ReadByte());
break;
case (byte)CustomRPC.TimeMasterRewindTime:
RPCProcedure.timeMasterRewindTime();
@@ -1189,6 +1291,9 @@ namespace TheOtherRoles
byte playerId2 = reader.ReadByte();
RPCProcedure.swapperSwap(playerId1, playerId2);
break;
+ case (byte)CustomRPC.MayorSetVoteTwice:
+ Mayor.voteTwice = reader.ReadBoolean();
+ break;
case (byte)CustomRPC.MorphlingMorph:
RPCProcedure.morphlingMorph(reader.ReadByte());
break;
@@ -1239,7 +1344,7 @@ namespace TheOtherRoles
RPCProcedure.placePortal(reader.ReadBytesAndSize());
break;
case (byte)CustomRPC.UsePortal:
- RPCProcedure.usePortal(reader.ReadByte());
+ RPCProcedure.usePortal(reader.ReadByte(), reader.ReadByte());
break;
case (byte)CustomRPC.PlaceJackInTheBox:
RPCProcedure.placeJackInTheBox(reader.ReadBytesAndSize());
@@ -1263,9 +1368,6 @@ namespace TheOtherRoles
byte guessedRoleId = reader.ReadByte();
RPCProcedure.guesserShoot(killerId, dyingTarget, guessedTarget, guessedRoleId);
break;
- case (byte)CustomRPC.VultureWin:
- RPCProcedure.vultureWin();
- break;
case (byte)CustomRPC.LawyerSetTarget:
RPCProcedure.lawyerSetTarget(reader.ReadByte());
break;
@@ -1309,13 +1411,18 @@ namespace TheOtherRoles
byte trapId = reader.ReadByte();
RPCProcedure.triggerTrap(trappedPlayer, trapId);
break;
+ case (byte)CustomRPC.PlaceBomb:
+ RPCProcedure.placeBomb(reader.ReadBytesAndSize());
+ break;
+ case (byte)CustomRPC.DefuseBomb:
+ RPCProcedure.defuseBomb();
+ break;
case (byte)CustomRPC.ShareGamemode:
byte gm = reader.ReadByte();
RPCProcedure.shareGamemode(gm);
break;
// Game mode
-
case (byte)CustomRPC.SetGuesserGm:
byte guesserGm = reader.ReadByte();
RPCProcedure.setGuesserGm(guesserGm);
@@ -1332,7 +1439,17 @@ namespace TheOtherRoles
byte rewindPlayer = reader.ReadByte();
RPCProcedure.huntedRewindTime(rewindPlayer);
break;
+ case (byte)CustomRPC.ShareGhostInfo:
+ RPCProcedure.receiveGhostInfo(reader.ReadByte(), reader);
+ break;
+
+
+ case (byte)CustomRPC.ShareRoom:
+ byte roomPlayer = reader.ReadByte();
+ byte roomId = reader.ReadByte();
+ RPCProcedure.shareRoom(roomPlayer, roomId);
+ break;
}
}
}
-}
+}
diff --git a/TheOtherRoles/Resources/Banner2.png b/TheOtherRoles/Resources/Banner2.png
new file mode 100644
index 0000000000000000000000000000000000000000..29ce90b962f79334f1771910b7a9de5ee234d67f
GIT binary patch
literal 170975
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