]> git.deb.at Git - rhonda/theotherroles.git/commitdiff
Update 4.3.0
authorMallöris <85371585+Mallaris@users.noreply.github.com>
Fri, 3 Mar 2023 18:30:04 +0000 (19:30 +0100)
committerMallöris <85371585+Mallaris@users.noreply.github.com>
Fri, 3 Mar 2023 18:30:04 +0000 (19:30 +0100)
144 files changed:
.github/FUNDING.yml
README.md
TheOtherRoles/Buttons.cs
TheOtherRoles/CustomGameModes/HideNSeekGM.cs
TheOtherRoles/CustomOptionHolder.cs
TheOtherRoles/GameHistory.cs
TheOtherRoles/Helpers.cs
TheOtherRoles/Main.cs
TheOtherRoles/MapOptions.cs
TheOtherRoles/Modules/CustomHats.cs
TheOtherRoles/Modules/CustomOptions.cs
TheOtherRoles/Modules/ModUpdater.cs
TheOtherRoles/Objects/Bomb.cs [new file with mode: 0644]
TheOtherRoles/Objects/Portal.cs
TheOtherRoles/Patches/ClientOptionsPatch.cs
TheOtherRoles/Patches/CreateOptionsPickerPatch.cs
TheOtherRoles/Patches/CredentialsPatch.cs
TheOtherRoles/Patches/EndGamePatch.cs
TheOtherRoles/Patches/ExileControllerPatch.cs
TheOtherRoles/Patches/GameStartManagerPatch.cs
TheOtherRoles/Patches/HauntMenuMinigamePatch.cs
TheOtherRoles/Patches/IntroPatch.cs
TheOtherRoles/Patches/MainMenuPatch.cs
TheOtherRoles/Patches/MapBehaviourPatch.cs
TheOtherRoles/Patches/MeetingPatch.cs
TheOtherRoles/Patches/PlayerControlPatch.cs
TheOtherRoles/Patches/RegionMenuPatch.cs
TheOtherRoles/Patches/RoleAssignmentPatch.cs
TheOtherRoles/Patches/ShipStatusPatch.cs
TheOtherRoles/Patches/UpdatePatch.cs
TheOtherRoles/Patches/UsablesPatch.cs
TheOtherRoles/RPC.cs
TheOtherRoles/Resources/Banner2.png [new file with mode: 0644]
TheOtherRoles/Resources/Bomb.png [new file with mode: 0644]
TheOtherRoles/Resources/BombBackground.png [new file with mode: 0644]
TheOtherRoles/Resources/Bomb_Button_Defuse.png [new file with mode: 0644]
TheOtherRoles/Resources/Bomb_Button_Plant.png [new file with mode: 0644]
TheOtherRoles/Resources/CurrentSettingsButton.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat10.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat10_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat12.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat12_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat13.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat13_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat14.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat14_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat15.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat15_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat19.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat19_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat1_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat2.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat21.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat22.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat23_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat24_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat25_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat26.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat26_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat27.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat27_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat28.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat28_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat29_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat30_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat31_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat34.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat34_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat36_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat38_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat39.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat39_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat4.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat41_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat43_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat44_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive_flip.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat46.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat46_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat47_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat49_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat50_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat51_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat53_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat54_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat55_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat56.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_climb.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_flip.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_bounce_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_climb_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat5_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat7.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat7_back.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_back_adaptive.png [new file with mode: 0644]
TheOtherRoles/Resources/HorseHats/HorseSpecialHat9.png [new file with mode: 0644]
TheOtherRoles/Resources/NightVisionOverlay.png [new file with mode: 0644]
TheOtherRoles/Resources/PortalAnimation/plattform.png
TheOtherRoles/Resources/SoundEffects/bombDefused.raw [new file with mode: 0644]
TheOtherRoles/Resources/SoundEffects/bombExplosion.raw [new file with mode: 0644]
TheOtherRoles/Resources/SoundEffects/bombFuseBurning.raw [new file with mode: 0644]
TheOtherRoles/Resources/SoundEffects/bombTick.raw [new file with mode: 0644]
TheOtherRoles/Resources/SoundEffects/knockKnock.raw [new file with mode: 0644]
TheOtherRoles/Resources/conversion/byteFileAction.py [new file with mode: 0644]
TheOtherRoles/RoleInfo.cs
TheOtherRoles/SoundEffectsManager.cs
TheOtherRoles/TheOtherRoles.cs
TheOtherRoles/TheOtherRoles.csproj
TheOtherRoles/Utilities/EventUtility.cs [new file with mode: 0644]
TheOtherRoles/Utilities/HandleGuesser.cs

index fc8128f77918583acdf42641f963c65e8f16983c..f43861fa056e025ef1a2afcd4459b4973a67ce9c 100644 (file)
@@ -1 +1 @@
-ko_fi: TheOtherRoles
+ko_fi: theotherroles
index a9c6bccbf6a66ac1ef93f2dfc2ef8decf3c73440..2233f0e686e45fe9d699b53a0a1b8e20b48819e6 100644 (file)
--- 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)
+<details>
+  <summary>Click to show older versions</summary>
+  
+| 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)
-
+</details>
+  
+  
 # Changelog
 <details>
   <summary>Click to show the Changelog</summary>
+
+**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 <MapName>` 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
index e243f308ae934082c333f6784ad6bdaf197cddff..41e73b45b2c6e70697d5634fd13f0ccf5474bfa6 100644 (file)
@@ -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<HudManager>.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action<float>((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<HudManager>.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action<float>((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<HudManager>.Instance.StartCoroutine(Effects.Lerp(Portal.teleportDuration, new Action<float>((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<HudManager>.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{msg}");
+                    string msg = Medium.getInfo(Medium.target.player, Medium.target.killerIfExisting);
+                    FastDestroyableSingleton<HudManager>.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;
                 },
index 0526c746a6c3a883852ac01e11aeb1132492a7d8..1a597ef83dacb84924396125a8d82c0a0e6abd00 100644 (file)
@@ -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)
index c7ece06b0e3f94547c87e94bcf75bf0e9d268cd6..bca9a45ce8e3a3514141552b89479ea2f2a9bfef 100644 (file)
@@ -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});
index 90611b3c612bebb6fa4f2f43c544f0ff276ccd42..2d9e75e42a479c3470bddd887c6f55fd7d53d425 100644 (file)
@@ -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;
         }
     }
 
index a0c661afa74d9a60d998416b255eae9d24828bb6..d07e2300b78498a154f7f3636e3a7129919696a9 100644 (file)
@@ -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<byte>(length);
                 stream.Read(new Span<byte>(IntPtr.Add(byteTexture.Pointer, IntPtr.Size * 4).ToPointer(), (int) length));
+                if (path.Contains("HorseHats")) {
+                    byteTexture = new Il2CppStructArray<byte>(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("<color=#{0:X2}{1:X2}{2:X2}{3:X2}>{4}</color>", 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<HudManager>.Instance == null || FastDestroyableSingleton<HudManager>.Instance.FullScreen == null) return;
             FastDestroyableSingleton<HudManager>.Instance.FullScreen.gameObject.SetActive(true);
             FastDestroyableSingleton<HudManager>.Instance.FullScreen.enabled = true;
+            // Message Text
+            TMPro.TextMeshPro messageText = GameObject.Instantiate(FastDestroyableSingleton<HudManager>.Instance.KillButton.cooldownTimerText, FastDestroyableSingleton<HudManager>.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<HudManager>.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
                 var renderer = FastDestroyableSingleton<HudManager>.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);
index 42f2044e420da8f0569b4ab6bb690812e8ec02bb..99a02f967ef950e1d71239a25e853a0d1b9ecc01 100644 (file)
@@ -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<string> DebugMode { get; private set; }
-        public static ConfigEntry<bool> GhostsSeeTasks { get; set; }
+        public static ConfigEntry<bool> GhostsSeeInformation { get; set; }
         public static ConfigEntry<bool> GhostsSeeRoles { get; set; }
         public static ConfigEntry<bool> GhostsSeeModifier { get; set; }
         public static ConfigEntry<bool> 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<ServerManager>.Instance;
             var regions = new IRegionInfo[] {
-                new DnsRegionInfo(Ip.Value, "Custom", StringNames.NoTranslation, Ip.Value, Port.Value, false).CastFast<IRegionInfo>()
+                new StaticHttpRegionInfo("Custom", StringNames.NoTranslation, Ip.Value, new Il2CppReferenceArray<ServerInfo>(new ServerInfo[1] { new ServerInfo("Custom", Ip.Value, Port.Value, false) })).CastFast<IRegionInfo>()
             };
             
             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<BepInExUpdater>();
                 return;
             }
-            
+
+            EventUtility.Load();
+
             SubmergedCompatibility.Initialize();
             AddComponent<ModUpdateBehaviour>();
             Modules.MainMenuPatch.addSceneChangeCallbacks();
index 9523edc112129ea52c32e9edbc76dc81345d8c00..79994947c429132b34c48824832b669b67cbf445 100644 (file)
@@ -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;
index 67c14a44e1c6e9e6e467e51d69cd8457280ee987..aeb250e61c32111f423ba0e17aa894a819cc8ee1 100644 (file)
@@ -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<TMPro.TMP_Text>();
 
@@ -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<string> horseHatProductIds = null;
+        private static void addHorseHats(ref List<CustomHatOnline> hatdatas) {
+
+            horseHatProductIds = new();
+            Assembly assembly = Assembly.GetExecutingAssembly();
+            string[] resourceNames = assembly.GetManifestResourceNames();
+            List<string> hatFiles = new();
+            Dictionary<string, List<string>> hatFilesSorted = new Dictionary<string, List<string>>();
+            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<string>());
+                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;}
index d496a864059c48d4245e238c5ca7f18daa1dd472..a16f38976d209fc1a9dbb321facda6f57b5905d4 100644 (file)
@@ -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<GameObject, SpriteRenderer> settingsHighlightMap, int index) {
+            foreach (KeyValuePair<GameObject, SpriteRenderer> 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<List<OptionBehaviour>> optionBehavioursList) {
+           foreach (List<OptionBehaviour> optionBehaviours in optionBehavioursList) {
+                foreach (OptionBehaviour option in optionBehaviours)
+                    UnityEngine.Object.Destroy(option.gameObject);
+            }
+        }
+
+        private static bool setNames (Dictionary<string, string> gameObjectNameDisplayNameMap) {
+            foreach (KeyValuePair <string, string> 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<TMPro.TextMeshPro>().SetText(entry.Value);
+                    return true;
+                }
+            }
+
+            setOptions(
+                new List<GameOptionsMenu>{torMenu, hideNSeekMenu},
+                new List<List<OptionBehaviour>> {torOptions, hideNSeekOptions}, 
+                new List<GameObject> {torSettings, hideNSeekSettings}
+            );
+
+            torSettings.gameObject.SetActive(true);
+            torTabHighlight.enabled = true;
+            gameSettingMenu.RegularGameSettings.SetActive(false);
+            gameSettingMenu.GameSettingsHightlight.enabled = false;
+        }
+
+        
         private static void setListener(Dictionary<GameObject, SpriteRenderer> settingsHighlightMap, int index) {
             foreach (KeyValuePair<GameObject, SpriteRenderer> 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<int> remove = new List<int>{ 310, 311, 312, 313, 314, 315, 316, 317, 318 };
+                List<int> remove = new List<int>{ 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<HudManager>.Instance?.GameSettings?.transform.localPosition;
                 FastDestroyableSingleton<HudManager>.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>();
             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<SpriteRenderer>();
+            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<SpriteRenderer>();
+                renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CurrentSettingsButton.png", 180f);
+                toggleSettingsButton = toggleSettingsButtonObject.GetComponent<PassiveButton>();
+                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);
         }
     }
 }
index 3d6f8de9e3149ee9e803b6693f9dd83bbc928cb7..57f5e2b63b6863c24a46e87c105bbf29dfe0a089 100644 (file)
@@ -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 = $"<size=150%>A new <color=#FC0303>{(isSubmerged ? "Submerged" : "THE OTHER ROLES")}</color> update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available</size>\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}";
+            var announcement = $"<size=150%>A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available</size>\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}";
             var mgr = FindObjectOfType<MainMenuManager>(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($"<size=150%><color=#FC0303>A MANUAL UPDATE IS REQUIRED</color></size>"));
+                            mgr.StartCoroutine(CoShowAnnouncement($"<size=150%>A MANUAL UPDATE IS REQUIRED</size>"));
                         }));
                     }
                 } 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<AnnouncementPopUp>(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 (file)
index 0000000..b5b37d0
--- /dev/null
@@ -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<SpriteRenderer>();
+            bombRenderer.sprite = getBombSprite();
+            var backgroundRenderer = background.AddComponent<SpriteRenderer>();
+            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<HudManager>.Instance.StartCoroutine(Effects.Lerp(Bomber.bombActiveAfter, new Action<float>((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<HudManager>.Instance.StartCoroutine(Effects.Lerp(Bomber.destructionTime, new Action<float>((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
index 7ee7467f3e3a6998ea3b6ab5b1458ddf54bbdf21..abc3ac05044a7785a4740409a677ea7c99fe6b2d 100644 (file)
@@ -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<HudManager>.Instance.StartCoroutine(Effects.Lerp(teleportDuration, new Action<float>((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<HudManager>.Instance?.roomTracker.LastRoom.RoomId;
+            this.room = lastRoom != null ? DestroyableSingleton<TranslationController>.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
index 8aa21edfc1a1ed05fc9193db629807c0a53a1779..132929b2dcb8ae0c65a1d1782ebc6da9bb1390b4 100644 (file)
@@ -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<TextMeshPro>();
+            var go = new GameObject("TitleTextTOR");
+            var tmp = go.AddComponent<TextMeshPro>();
+            tmp.fontSize = 4;
             tmp.alignment = TextAlignmentOptions.Center;
             tmp.transform.localPosition += Vector3.left * 0.2f;
             titleText = Object.Instantiate(tmp);
-            Object.Destroy(titleText.GetComponent<TextTranslatorTMP>());
             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<RectTransform>().sizeDelta = new Vector2(2, 2);
 
index 4087ff86f316d691ac64cf49c246012b5ebe226d..e436b96104c56b74a0d86d5df1eb29cb0d3ee56c 100644 (file)
@@ -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) {
index 8210e3c91a7e4fbcedfeb028dda5fe5aa513fcc8..b11af6bf74b60a10fa715b8e8a471409b42fd2ff 100644 (file)
@@ -72,8 +72,8 @@ $@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color></size>";
                     }
                 } 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 @@ $@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color></size>";
             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 @@ $@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color></size>";
 
                 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 @@ $@"<size=60%> <color=#FCCE03FF>Special thanks to K3ndo & Smeggy</color></size>";
                     instance.StartCoroutine(Effects.Lerp(fadeDuration, new Action<float>((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<float>((p) => {
                                 renderer.color = new Color(1, 1, 1, p);
                             })));
index 7aa2ffc459973792c9024bcce10c7d4b798a48bf..b611b9cc5cd4fcc5ea07cac0e620c5155ebcce0f 100644 (file)
@@ -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); 
index 270eb679d55900aa63f33611cfa3ee3e0fc36842..bbf07c516c4b12a74602c24294f717cf41ac090f 100644 (file)
@@ -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<SpriteRenderer>().color = Color.white;
                 allCameras.Add(camera);
             });
             MapUtilities.CachedShipStatus.AllCameras = allCameras.ToArray();
-            MapOptions.camerasToAdd = new List<SurvCamera>();
+            TORMapOptions.camerasToAdd = new List<SurvCamera>();
 
-            foreach (Vent vent in MapOptions.ventsToSeal) {
+            foreach (Vent vent in TORMapOptions.ventsToSeal) {
                 PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>(); 
                 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<Vent>();
+            TORMapOptions.ventsToSeal = new List<Vent>();
+
+            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++;
                     }
                 }
index e9409f248dcf3acc8ec98ce949cf9d8f98c5b2d1..f1c7a1d302b4becb16dee6ba818be46baf5b7e50 100644 (file)
@@ -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);
index dd6bcceadb65210d0713100d2d1cf69f53f14a04..ac44ba1004583daa4ad99a03c19aecec967d60bc 100644 (file)
@@ -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();
+            }
+        }
     }
 }
index bc2ac0f553bda670c386dfc6d43eba5773f68731..fe0f5e0a08e3e00cecb813307359cbe3cb33fc7b 100644 (file)
@@ -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<RoleInfo> 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<HudManager>.Instance.StartCoroutine(Effects.Lerp(1f, new Action<float>((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;
         }
-    }*/
+    }
 }
 
index 8d09a49276d1f4299e1297f0fb459b3cef3472df..19d6782ccb0f6e2273b57e3aa412cf75f9deec71 100644 (file)
@@ -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<AnnouncementPopUp>(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 = @$"<align=""center"">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</size>";
                 creditsString += "</align>";
-                popUp.AnnounceTextMeshPro.text = creditsString;
+                creditsAnnouncement.Text = creditsString;
                 __instance.StartCoroutine(Effects.Lerp(0.01f, new Action<float>((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<AnnouncementPanel>()) {
+                            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<TMPro.TextMeshPro>();
-                        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</size>";
         public static void addSceneChangeCallbacks() {
             SceneManager.add_sceneLoaded((Action<Scene, LoadSceneMode>)((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<HostLocalGameButton>();
@@ -166,7 +189,7 @@ Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose</size>";
                 
                 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</size>";
                 
                 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</size>";
         }
     }
 
-    [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</size>";
 
             return false;
         }
-    }
-}
\ No newline at end of file
+    }*/
+}
index e9629ac7ff3bdd274f8f49fafe7dd17a6247b1ff..9faed3f60a1c37e2c302cacb6e2a058e6d9527aa 100644 (file)
@@ -36,6 +36,7 @@ namespace TheOtherRoles.Patches {
                                        herePoints.Remove(s.Key);
                                }
                        }
+                       HudManagerUpdate.CloseSettings();
                }
        }
 }
index 885ece3cc8893b240cad9e7cf24be3128626f5b3..f2e5e94c885612cf60c8943b4b6303caa67d5eb3 100644 (file)
@@ -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<SpriteRenderer>(__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<Transform>().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<Transform>().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<TMPro.TextMeshPro>() : 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<SpriteRenderer>().sprite = FastDestroyableSingleton<HatManager>.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>();
-                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<SpriteRenderer>().sprite = FastDestroyableSingleton<HatManager>.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>();
+                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<float>((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<HudManager>.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<DeadPlayer, float>();
                 // 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<HudManager>.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<HudManager>.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<HudManager>.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<HudManager>.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{message}");
+                        FastDestroyableSingleton<HudManager>.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<HudManager>.Instance.StartCoroutine(Effects.Lerp(0.3f, new Action<float>((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<TranslationController>.Instance.GetString((SystemTypes)room);
+                                    }
+                                    output += "- " + RoleInfo.GetRolesString(p, false, false, true) + ", was last seen in " + roomName + "\n";
+                                }
+                                FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(Snitch.snitch, $"{output}");
+                            }
+                        })));
                     }
                 }
 
+                if (CachedPlayer.LocalPlayer.Data.IsDead && output != "") FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(CachedPlayer.LocalPlayer, $"{output}");
+
                 Trapper.playersOnMap = new List<PlayerControl>();
+                Snitch.playerRoomMap = new Dictionary<byte, byte>();
 
                 // 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();
+        }
+
     }
 }
index 829aab1f07c08b7ef74c0be13bacfb24ac41408c..aa1d4049e49f1c15678fdd815d950687c7969dcd 100644 (file)
@@ -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 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
 
                     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<HudManager>.Instance.KillButton.cooldownTimerText, FastDestroyableSingleton<HudManager>.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<HudManager>.Instance != null && FastDestroyableSingleton<HudManager>.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<HudManager>.Instance)
                         {
                             FastDestroyableSingleton<HudManager>.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;
+        }
+    }
 }
index 1dfd7149dbd2779c160cfc1542188bf1958b1ac3..2f69c758ba60e3b5ca01f65afc538f9adcd14e4e 100644 (file)
@@ -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<float>((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<ServerManager>.Instance.CurrentRegion.Name == "Custom") return true;
+            DestroyableSingleton<ServerManager>.Instance.SetRegion(region);
+            __instance.RegionText.text = "Custom";
+            foreach (var Button in __instance.ButtonPool.activeChildren) {
+                ServerListButton serverListButton = Button.TryCast<ServerListButton>();
+                if (serverListButton != null) serverListButton.SetSelected(serverListButton.Text.text == "Custom");
+            }
+            __instance.Open();
+            return false;
+        }
+    }
 }
\ No newline at end of file
index 41d6d8dcfba3645abe2cf7ea994c6381fd8caca6..d65bdcb5c76afc8c1a1da1d82d30649cdc4f7d3c 100644 (file)
@@ -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<Tuple<byte, byte>> playerRoleMap = new List<Tuple<byte, byte>>();
-        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());
index 7da988bec0e69507ac9f0c5b8e9875b122c5d6d3..40d6cd97144543074af8c0a8e064d43268732bd5 100644 (file)
@@ -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;
index d606cc088c00bfebd15997dc65c23d8b60d0b2e2..6dcf2255c66633b0b989b3b24b654966fe791b83 100644 (file)
@@ -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();
         }
     }
 }
index 9bd4c2d7b28715b8f53b0ca01946b947c909ea4d..d8a299ba65c7976c0e0fa8240e0b348670f51b64 100644 (file)
@@ -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<SystemTypes, List<Color>> players = new Dictionary<SystemTypes, System.Collections.Generic.List<Color>>();
-
         [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<int> hashSet = new HashSet<int>();
                             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<PlayerControl>();
-                                    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<DeadBody>();
-                                    if (component) {
-                                        GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
+                                if (collider2D.CompareTag("DeadBody") && __instance.includeDeadBodies) {
+                                    num2++;
+                                    DeadBody bodyComponent = collider2D.GetComponent<DeadBody>();
+                                    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<PlayerControl>();
+                                    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<GameObject> 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<MeshRenderer> 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<GameObject>();
+
+                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<SpriteRenderer>();
+                    overlayRenderer.sprite = overlaySprite;
+                    overlayObject.SetActive(false);
+                    GameObject.Destroy(overlayObject.GetComponent<CircleCollider2D>());
+                    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<DeadBody>()) {
+                    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<DeadBody>()) {
+                        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<DeadBody>()) {
+                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;
         }
     }
index 5f8f523529d9c68dfa756d8b2ebb07c42676c485..57f31a2555bef8b1e93a71db0d207f7205cc7e3c 100644 (file)
@@ -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<DeadBody>();
             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<HudManager>.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<HudManager>.Instance.Chat.AddChat(sender, mediumInfo);
+                    break;
+                case GhostInfoTypes.DetectiveOrMedicInfo:
+                    string detectiveInfo = reader.ReadString();
+                    if (Helpers.shouldShowGhostInfo())
+                               FastDestroyableSingleton<HudManager>.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 (file)
index 0000000..29ce90b
Binary files /dev/null and b/TheOtherRoles/Resources/Banner2.png differ
diff --git a/TheOtherRoles/Resources/Bomb.png b/TheOtherRoles/Resources/Bomb.png
new file mode 100644 (file)
index 0000000..5d9fcb6
Binary files /dev/null and b/TheOtherRoles/Resources/Bomb.png differ
diff --git a/TheOtherRoles/Resources/BombBackground.png b/TheOtherRoles/Resources/BombBackground.png
new file mode 100644 (file)
index 0000000..1d6dbee
Binary files /dev/null and b/TheOtherRoles/Resources/BombBackground.png differ
diff --git a/TheOtherRoles/Resources/Bomb_Button_Defuse.png b/TheOtherRoles/Resources/Bomb_Button_Defuse.png
new file mode 100644 (file)
index 0000000..f7df1de
Binary files /dev/null and b/TheOtherRoles/Resources/Bomb_Button_Defuse.png differ
diff --git a/TheOtherRoles/Resources/Bomb_Button_Plant.png b/TheOtherRoles/Resources/Bomb_Button_Plant.png
new file mode 100644 (file)
index 0000000..53847e3
Binary files /dev/null and b/TheOtherRoles/Resources/Bomb_Button_Plant.png differ
diff --git a/TheOtherRoles/Resources/CurrentSettingsButton.png b/TheOtherRoles/Resources/CurrentSettingsButton.png
new file mode 100644 (file)
index 0000000..a593620
Binary files /dev/null and b/TheOtherRoles/Resources/CurrentSettingsButton.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat10.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat10.png
new file mode 100644 (file)
index 0000000..dc6340f
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat10.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat10_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat10_back.png
new file mode 100644 (file)
index 0000000..132eb1a
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat10_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive.png
new file mode 100644 (file)
index 0000000..3992150
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive_back.png
new file mode 100644 (file)
index 0000000..f790f25
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat11_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat12.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat12.png
new file mode 100644 (file)
index 0000000..d3b3802
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat12.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat12_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat12_back.png
new file mode 100644 (file)
index 0000000..9adfa8d
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat12_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat13.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat13.png
new file mode 100644 (file)
index 0000000..e89ae9d
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat13.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat13_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat13_back.png
new file mode 100644 (file)
index 0000000..3486011
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat13_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat14.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat14.png
new file mode 100644 (file)
index 0000000..7c679b7
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat14.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat14_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat14_back.png
new file mode 100644 (file)
index 0000000..7726bd0
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat14_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat15.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat15.png
new file mode 100644 (file)
index 0000000..1986ba3
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat15.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat15_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat15_back.png
new file mode 100644 (file)
index 0000000..87057cf
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat15_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive.png
new file mode 100644 (file)
index 0000000..ef69fa9
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive_back.png
new file mode 100644 (file)
index 0000000..0963ce6
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat16_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive.png
new file mode 100644 (file)
index 0000000..afd162d
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive_back.png
new file mode 100644 (file)
index 0000000..0f24b32
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat17_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive.png
new file mode 100644 (file)
index 0000000..dee0ddb
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive_back.png
new file mode 100644 (file)
index 0000000..cbb2790
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat18_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat19.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat19.png
new file mode 100644 (file)
index 0000000..84367e3
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat19.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat19_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat19_back.png
new file mode 100644 (file)
index 0000000..41208f0
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat19_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat1_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat1_adaptive.png
new file mode 100644 (file)
index 0000000..e564106
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat1_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat2.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat2.png
new file mode 100644 (file)
index 0000000..ef2904b
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat2.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive.png
new file mode 100644 (file)
index 0000000..620a69e
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive_back.png
new file mode 100644 (file)
index 0000000..1dc8055
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat20_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat21.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat21.png
new file mode 100644 (file)
index 0000000..609a795
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat21.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat22.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat22.png
new file mode 100644 (file)
index 0000000..f94ae76
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat22.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat23_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat23_adaptive.png
new file mode 100644 (file)
index 0000000..d0082d9
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat23_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat24_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat24_adaptive.png
new file mode 100644 (file)
index 0000000..b6d76a5
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat24_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat25_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat25_adaptive.png
new file mode 100644 (file)
index 0000000..ab36cb1
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat25_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat26.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat26.png
new file mode 100644 (file)
index 0000000..d17e869
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat26.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat26_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat26_back.png
new file mode 100644 (file)
index 0000000..e4ebc38
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat26_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat27.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat27.png
new file mode 100644 (file)
index 0000000..ed39d6d
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat27.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat27_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat27_back.png
new file mode 100644 (file)
index 0000000..d41c533
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat27_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat28.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat28.png
new file mode 100644 (file)
index 0000000..a50fa9d
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat28.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat28_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat28_back.png
new file mode 100644 (file)
index 0000000..d5de4ce
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat28_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat29_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat29_adaptive.png
new file mode 100644 (file)
index 0000000..7f59019
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat29_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat30_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat30_adaptive.png
new file mode 100644 (file)
index 0000000..329a858
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat30_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat31_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat31_adaptive.png
new file mode 100644 (file)
index 0000000..5904cea
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat31_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive.png
new file mode 100644 (file)
index 0000000..958596c
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive_back.png
new file mode 100644 (file)
index 0000000..1859a17
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat32_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive.png
new file mode 100644 (file)
index 0000000..4b0b09b
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive_back.png
new file mode 100644 (file)
index 0000000..b49e0f1
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat33_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat34.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat34.png
new file mode 100644 (file)
index 0000000..1671a11
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat34.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat34_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat34_back.png
new file mode 100644 (file)
index 0000000..6727917
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat34_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive.png
new file mode 100644 (file)
index 0000000..fa29393
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive_back.png
new file mode 100644 (file)
index 0000000..701e8b6
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat35_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat36_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat36_adaptive.png
new file mode 100644 (file)
index 0000000..d55a633
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat36_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive.png
new file mode 100644 (file)
index 0000000..be2eef0
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive_back.png
new file mode 100644 (file)
index 0000000..0072417
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat37_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat38_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat38_adaptive.png
new file mode 100644 (file)
index 0000000..af966e3
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat38_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat39.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat39.png
new file mode 100644 (file)
index 0000000..a42e49f
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat39.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat39_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat39_back.png
new file mode 100644 (file)
index 0000000..e7bc515
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat39_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive.png
new file mode 100644 (file)
index 0000000..88e6e7c
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive_back.png
new file mode 100644 (file)
index 0000000..8a9d65a
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat3_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat4.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat4.png
new file mode 100644 (file)
index 0000000..9eafb10
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat4.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive.png
new file mode 100644 (file)
index 0000000..a0c5653
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive_back.png
new file mode 100644 (file)
index 0000000..ff12b08
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat40_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat41_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat41_adaptive.png
new file mode 100644 (file)
index 0000000..0d3366b
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat41_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive.png
new file mode 100644 (file)
index 0000000..801a316
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive_back.png
new file mode 100644 (file)
index 0000000..87d66db
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat42_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat43_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat43_adaptive.png
new file mode 100644 (file)
index 0000000..bf32e42
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat43_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat44_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat44_adaptive.png
new file mode 100644 (file)
index 0000000..bc82ae5
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat44_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive.png
new file mode 100644 (file)
index 0000000..34a9c04
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive_flip.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive_flip.png
new file mode 100644 (file)
index 0000000..d2f65c8
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat45_adaptive_flip.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat46.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat46.png
new file mode 100644 (file)
index 0000000..0af5438
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat46.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat46_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat46_back.png
new file mode 100644 (file)
index 0000000..87057cf
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat46_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat47_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat47_adaptive.png
new file mode 100644 (file)
index 0000000..a0e09e9
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat47_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive.png
new file mode 100644 (file)
index 0000000..68937cd
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive_back.png
new file mode 100644 (file)
index 0000000..3eee17c
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat48_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat49_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat49_adaptive.png
new file mode 100644 (file)
index 0000000..1600833
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat49_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat50_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat50_adaptive.png
new file mode 100644 (file)
index 0000000..5508732
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat50_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat51_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat51_adaptive.png
new file mode 100644 (file)
index 0000000..3101883
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat51_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive.png
new file mode 100644 (file)
index 0000000..029db37
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive_back.png
new file mode 100644 (file)
index 0000000..129f398
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat52_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat53_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat53_adaptive.png
new file mode 100644 (file)
index 0000000..3df429e
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat53_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat54_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat54_adaptive.png
new file mode 100644 (file)
index 0000000..1e4d260
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat54_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat55_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat55_adaptive.png
new file mode 100644 (file)
index 0000000..9923213
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat55_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56.png
new file mode 100644 (file)
index 0000000..25b0d06
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_back.png
new file mode 100644 (file)
index 0000000..d6655e5
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_climb.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_climb.png
new file mode 100644 (file)
index 0000000..999d645
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_climb.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_flip.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_flip.png
new file mode 100644 (file)
index 0000000..a9dc0ed
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat56_flip.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_adaptive.png
new file mode 100644 (file)
index 0000000..d68ff55
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_bounce_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_bounce_back.png
new file mode 100644 (file)
index 0000000..35461d5
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_bounce_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_climb_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_climb_adaptive.png
new file mode 100644 (file)
index 0000000..bc0a437
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat57_climb_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat5_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat5_adaptive.png
new file mode 100644 (file)
index 0000000..e83fe1b
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat5_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive.png
new file mode 100644 (file)
index 0000000..e683187
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive_back.png
new file mode 100644 (file)
index 0000000..14d9232
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat6_adaptive_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat7.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat7.png
new file mode 100644 (file)
index 0000000..a18a0e4
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat7.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat7_back.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat7_back.png
new file mode 100644 (file)
index 0000000..8282da8
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat7_back.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_adaptive.png
new file mode 100644 (file)
index 0000000..f4cbe6c
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_back_adaptive.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_back_adaptive.png
new file mode 100644 (file)
index 0000000..7b6de65
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat8_back_adaptive.png differ
diff --git a/TheOtherRoles/Resources/HorseHats/HorseSpecialHat9.png b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat9.png
new file mode 100644 (file)
index 0000000..e9d3215
Binary files /dev/null and b/TheOtherRoles/Resources/HorseHats/HorseSpecialHat9.png differ
diff --git a/TheOtherRoles/Resources/NightVisionOverlay.png b/TheOtherRoles/Resources/NightVisionOverlay.png
new file mode 100644 (file)
index 0000000..20b631a
Binary files /dev/null and b/TheOtherRoles/Resources/NightVisionOverlay.png differ
index 78e9462b193c4b4cd0d69d7d8d10c03cffeeadd5..0d9f694073d27bd2ec2d6a85052f7675df2027b1 100644 (file)
Binary files a/TheOtherRoles/Resources/PortalAnimation/plattform.png and b/TheOtherRoles/Resources/PortalAnimation/plattform.png differ
diff --git a/TheOtherRoles/Resources/SoundEffects/bombDefused.raw b/TheOtherRoles/Resources/SoundEffects/bombDefused.raw
new file mode 100644 (file)
index 0000000..f12bfc2
Binary files /dev/null and b/TheOtherRoles/Resources/SoundEffects/bombDefused.raw differ
diff --git a/TheOtherRoles/Resources/SoundEffects/bombExplosion.raw b/TheOtherRoles/Resources/SoundEffects/bombExplosion.raw
new file mode 100644 (file)
index 0000000..2b6ab1c
Binary files /dev/null and b/TheOtherRoles/Resources/SoundEffects/bombExplosion.raw differ
diff --git a/TheOtherRoles/Resources/SoundEffects/bombFuseBurning.raw b/TheOtherRoles/Resources/SoundEffects/bombFuseBurning.raw
new file mode 100644 (file)
index 0000000..eb65d7a
Binary files /dev/null and b/TheOtherRoles/Resources/SoundEffects/bombFuseBurning.raw differ
diff --git a/TheOtherRoles/Resources/SoundEffects/bombTick.raw b/TheOtherRoles/Resources/SoundEffects/bombTick.raw
new file mode 100644 (file)
index 0000000..0fcfdaa
Binary files /dev/null and b/TheOtherRoles/Resources/SoundEffects/bombTick.raw differ
diff --git a/TheOtherRoles/Resources/SoundEffects/knockKnock.raw b/TheOtherRoles/Resources/SoundEffects/knockKnock.raw
new file mode 100644 (file)
index 0000000..a764777
Binary files /dev/null and b/TheOtherRoles/Resources/SoundEffects/knockKnock.raw differ
diff --git a/TheOtherRoles/Resources/conversion/byteFileAction.py b/TheOtherRoles/Resources/conversion/byteFileAction.py
new file mode 100644 (file)
index 0000000..305a99c
--- /dev/null
@@ -0,0 +1,11 @@
+from glob import glob
+
+for filename in glob("*.png"):
+    l = []
+    with open(filename, "rb") as f:
+        while byte := f.read(1):
+            l.append(byte)
+    l = l[::-1]
+    with open(filename, "wb") as f:
+        for b in l:
+            f.write(b)
index 7dc53d8ce2b07690b520aa29c28cded02f82077f..a9542f617c026b4623c588e673dadb354d238486 100644 (file)
@@ -72,6 +72,7 @@ namespace TheOtherRoles
         public static RoleInfo witch = new RoleInfo("Witch", Witch.color, "Cast a spell upon your foes", "Cast a spell upon your foes", RoleId.Witch);
         public static RoleInfo ninja = new RoleInfo("Ninja", Ninja.color, "Surprise and assassinate your foes", "Surprise and assassinate your foes", RoleId.Ninja);
         public static RoleInfo thief = new RoleInfo("Thief", Thief.color, "Steal a killers role by killing them", "Steal a killers role", RoleId.Thief, true);
+        public static RoleInfo bomber = new RoleInfo("Bomber", Bomber.color, "Bomb all Crewmates", "Bomb all Crewmates", RoleId.Bomber);
 
         public static RoleInfo hunter = new RoleInfo("Hunter", Palette.ImpostorRed, Helpers.cs(Palette.ImpostorRed, "Seek and kill everyone"), "Seek and kill everyone", RoleId.Impostor);
         public static RoleInfo hunted = new RoleInfo("Hunted", Color.white, "Hide", "Hide", RoleId.Crewmate);
@@ -107,6 +108,7 @@ namespace TheOtherRoles
             bountyHunter,
             witch,
             ninja,
+            bomber,
             goodGuesser,
             badGuesser,
             lover,
@@ -195,6 +197,7 @@ namespace TheOtherRoles
             if (p == Warlock.warlock) infos.Add(warlock);
             if (p == Witch.witch) infos.Add(witch);
             if (p == Ninja.ninja) infos.Add(ninja);
+            if (p == Bomber.bomber) infos.Add(bomber);
             if (p == Detective.detective) infos.Add(detective);
             if (p == TimeMaster.timeMaster) infos.Add(timeMaster);
             if (p == Medic.medic) infos.Add(medic);
@@ -222,20 +225,53 @@ namespace TheOtherRoles
             // Default roles (just impostor, just crewmate, or hunter / hunted for hide n seek
             if (infos.Count == count) {
                 if (p.Data.Role.IsImpostor)
-                    infos.Add(MapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunter : RoleInfo.impostor);
+                    infos.Add(TORMapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunter : RoleInfo.impostor);
                 else
-                    infos.Add(MapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunted : RoleInfo.crewmate);
+                    infos.Add(TORMapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunted : RoleInfo.crewmate);
             }
 
             return infos;
         }
 
-        public static String GetRolesString(PlayerControl p, bool useColors, bool showModifier = true) {
+        public static String GetRolesString(PlayerControl p, bool useColors, bool showModifier = true, bool suppressGhostInfo = false) {
             string roleName;
             roleName = String.Join(" ", getRoleInfoForPlayer(p, showModifier).Select(x => useColors ? Helpers.cs(x.color, x.name) : x.name).ToArray());
             if (Lawyer.target != null && p.PlayerId == Lawyer.target.PlayerId && CachedPlayer.LocalPlayer.PlayerControl != Lawyer.target) 
                 roleName += (useColors ? Helpers.cs(Pursuer.color, " §") : " §");
             if (HandleGuesser.isGuesserGm && HandleGuesser.isGuesser(p.PlayerId)) roleName += " (Guesser)";
+
+            if (!suppressGhostInfo && p != null) {
+                if (p == Shifter.shifter && (CachedPlayer.LocalPlayer.PlayerControl == Shifter.shifter || Helpers.shouldShowGhostInfo()) && Shifter.futureShift != null)
+                    roleName += Helpers.cs(Color.yellow, " ← " + Shifter.futureShift.Data.PlayerName);
+                if (p == Vulture.vulture && (CachedPlayer.LocalPlayer.PlayerControl == Vulture.vulture || Helpers.shouldShowGhostInfo()))
+                    roleName = roleName + Helpers.cs(Vulture.color, $" ({Vulture.vultureNumberToWin - Vulture.eatenBodies} left)");
+                if (Helpers.shouldShowGhostInfo()) {
+                    if (Eraser.futureErased.Contains(p))
+                        roleName = Helpers.cs(Color.gray, "(erased) ") + roleName;
+                    if (Vampire.vampire != null && !Vampire.vampire.Data.IsDead && Vampire.bitten == p && !p.Data.IsDead)
+                        roleName = Helpers.cs(Vampire.color, $"(bitten {(int)HudManagerStartPatch.vampireKillButton.Timer + 1}) ") + roleName;
+                    if (Deputy.handcuffedPlayers.Contains(p.PlayerId))
+                        roleName = Helpers.cs(Color.gray, "(cuffed) ") + roleName;
+                    if (Deputy.handcuffedKnows.ContainsKey(p.PlayerId))  // Active cuff
+                        roleName = Helpers.cs(Deputy.color, "(cuffed) ") + roleName;
+                    if (p == Warlock.curseVictim)
+                        roleName = Helpers.cs(Warlock.color, "(cursed) ") + roleName;
+                    if (p == Ninja.ninjaMarked)
+                        roleName = Helpers.cs(Ninja.color, "(marked) ") + roleName;
+                    if (Pursuer.blankedList.Contains(p) && !p.Data.IsDead)
+                        roleName = Helpers.cs(Pursuer.color, "(blanked) ") + roleName;
+                    if (Witch.futureSpelled.Contains(p) && !MeetingHud.Instance) // This is already displayed in meetings!
+                        roleName = Helpers.cs(Witch.color, "☆ ") + roleName;
+                    if (BountyHunter.bounty == p)
+                        roleName = Helpers.cs(BountyHunter.color, "(bounty) ") + roleName;
+                    if (Arsonist.dousedPlayers.Contains(p))
+                        roleName = Helpers.cs(Arsonist.color, "♨ ") + roleName;
+                    if (p == Arsonist.arsonist)
+                        roleName = roleName + Helpers.cs(Arsonist.color, $" ({CachedPlayer.AllPlayers.Count(x => { return x.PlayerControl != Arsonist.arsonist && !x.Data.IsDead && !x.Data.Disconnected && !Arsonist.dousedPlayers.Any(y => y.PlayerId == x.PlayerId); })} left)");
+                    if (p == Jackal.fakeSidekick)
+                        roleName = Helpers.cs(Sidekick.color, $" (fake SK)") + roleName;
+                }
+            }
             return roleName;
         }
     }
index 76b772f7c34ed0c97a83f039099a5555247fe0ab..d464863d418a0ee727dc689e7367dd7bfefc29e6 100644 (file)
@@ -40,12 +40,33 @@ namespace TheOtherRoles
 
         public static void play(string path, float volume=0.8f)
         {
-            if (!MapOptions.enableSoundEffects) return;
+            if (!TORMapOptions.enableSoundEffects) return;
             AudioClip clipToPlay = get(path);
             // if (false) clipToPlay = get("exampleClip"); for april fools?
             stop(path);
             if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(clipToPlay, false, volume);
         }
+        public static void playAtPosition(string path, Vector2 position, float maxDuration = 15f, float range = 5f, bool loop = false) {
+            if (!TORMapOptions.enableSoundEffects || !Constants.ShouldPlaySfx()) return;
+            AudioClip clipToPlay = get(path);
+
+            AudioSource source = SoundManager.Instance.PlaySound(clipToPlay, false, 1f);
+            source.loop = loop;
+            HudManager.Instance.StartCoroutine(Effects.Lerp(maxDuration, new Action<float>((p) => {
+                if (source != null) {
+                    if (p == 1) {
+                        source.Stop();
+                    }
+                    float distance, volume;
+                    distance = Vector2.Distance(position, Players.CachedPlayer.LocalPlayer.PlayerControl.GetTruePosition());
+                    if (distance < range)
+                        volume = (1f - distance / range);
+                    else
+                        volume = 0f;
+                    source.volume = volume;
+                }
+            })));
+        }
 
         public static void stop(string path) {
             if (Constants.ShouldPlaySfx()) SoundManager.Instance.StopSound(get(path));
index f939fdecddfd04d43356efd0ffb4989a5d75eccb..512f662feeb3af410b788f28da7bb2eed6ba71ce 100644 (file)
@@ -9,6 +9,7 @@ using TheOtherRoles.Utilities;
 using TheOtherRoles.CustomGameModes;
 using static TheOtherRoles.TheOtherRoles;
 using AmongUs.Data;
+using Hazel;
 
 namespace TheOtherRoles
 {
@@ -59,6 +60,7 @@ namespace TheOtherRoles
             Ninja.clearAndReload();
             Thief.clearAndReload();
             Trapper.clearAndReload();
+            Bomber.clearAndReload();
 
             // Modifier
             Bait.clearAndReload();
@@ -74,6 +76,7 @@ namespace TheOtherRoles
             // Gamemodes
             HandleGuesser.clearAndReload();
             HideNSeek.clearAndReload();
+
         }
 
         public static class Jester {
@@ -100,9 +103,12 @@ namespace TheOtherRoles
             public static float usePortalCooldown;
             public static bool logOnlyHasColors;
             public static bool logShowsTime;
+            public static bool canPortalFromAnywhere;
 
             private static Sprite placePortalButtonSprite;
             private static Sprite usePortalButtonSprite;
+            private static Sprite usePortalSpecialButtonSprite1;
+            private static Sprite usePortalSpecialButtonSprite2;
             private static Sprite logSprite;
 
             public static Sprite getPlacePortalButtonSprite() {
@@ -117,6 +123,18 @@ namespace TheOtherRoles
                 return usePortalButtonSprite;
             }
 
+            public static Sprite getUsePortalSpecialButtonSprite(bool first) {
+                if (first) {
+                    if (usePortalSpecialButtonSprite1) return usePortalSpecialButtonSprite1;
+                    usePortalSpecialButtonSprite1 = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.UsePortalSpecialButton1.png", 115f);
+                    return usePortalSpecialButtonSprite1;
+                } else {
+                    if (usePortalSpecialButtonSprite2) return usePortalSpecialButtonSprite2;
+                    usePortalSpecialButtonSprite2 = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.UsePortalSpecialButton2.png", 115f);
+                    return usePortalSpecialButtonSprite2;
+                }
+            }
+
             public static Sprite getLogSprite() {
                 if (logSprite) return logSprite;
                 logSprite = FastDestroyableSingleton<HudManager>.Instance.UseButton.fastUseSettings[ImageNames.DoorLogsButton].Image;
@@ -129,6 +147,7 @@ namespace TheOtherRoles
                 usePortalCooldown = CustomOptionHolder.portalmakerUsePortalCooldown.getFloat();
                 logOnlyHasColors = CustomOptionHolder.portalmakerLogOnlyColorType.getBool();
                 logShowsTime = CustomOptionHolder.portalmakerLogHasTime.getBool();
+                canPortalFromAnywhere = CustomOptionHolder.portalmakerCanPortalFromAnywhere.getBool();
             }
 
 
@@ -144,6 +163,9 @@ namespace TheOtherRoles
             public static bool canSeeVoteColors = false;
             public static int tasksNeededToSeeVoteColors;
             public static bool meetingButton = true;
+            public static int mayorChooseSingleVote;
+
+            public static bool voteTwice = true;
 
             public static Sprite getMeetingSprite()
             {
@@ -160,6 +182,8 @@ namespace TheOtherRoles
                 canSeeVoteColors = CustomOptionHolder.mayorCanSeeVoteColors.getBool();
                 tasksNeededToSeeVoteColors = (int)CustomOptionHolder.mayorTasksNeededToSeeVoteColors.getFloat();
                 meetingButton = CustomOptionHolder.mayorMeetingButton.getBool();
+                mayorChooseSingleVote = CustomOptionHolder.mayorChooseSingleVote.getSelection();
+                voteTwice = true;
             }
         }
 
@@ -289,17 +313,29 @@ namespace TheOtherRoles
             }
 
             // Can be used to enable / disable the handcuff effect on the target's buttons
-            public static void setHandcuffedKnows(bool active = true)
+            public static void setHandcuffedKnows(bool active = true, byte playerId = Byte.MaxValue)
             {
-                if (active) {
-                    byte localPlayerId = CachedPlayer.LocalPlayer.PlayerId;
-                    handcuffedKnows.Add(localPlayerId, handcuffDuration);
-                    handcuffedPlayers.RemoveAll(x => x == localPlayerId);
-                    SoundEffectsManager.play("deputyHandcuff");
+                if (playerId == Byte.MaxValue)
+                    playerId = CachedPlayer.LocalPlayer.PlayerId;
+
+                if (active && playerId == CachedPlayer.LocalPlayer.PlayerId) {
+                    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.HandcuffNoticed);
+                    AmongUsClient.Instance.FinishRpcImmediately(writer);
                 }
 
-                HudManagerStartPatch.setAllButtonsHandcuffedStatus(active);
-            }
+                if (active) {
+                    handcuffedKnows.Add(playerId, handcuffDuration);
+                    handcuffedPlayers.RemoveAll(x => x == playerId);
+               }
+
+                if (playerId == CachedPlayer.LocalPlayer.PlayerId) {
+                    HudManagerStartPatch.setAllButtonsHandcuffedStatus(active);
+                    SoundEffectsManager.play("deputyHandcuff");
+               }
+           }
 
             public static void clearAndReload()
             {
@@ -323,24 +359,11 @@ namespace TheOtherRoles
             
             public static float lighterModeLightsOnVision = 2f;
             public static float lighterModeLightsOffVision = 0.75f;
-
-            public static float cooldown = 30f;
-            public static float duration = 5f;
-
-            public static float lighterTimer = 0f;
-
-            private static Sprite buttonSprite;
-            public static Sprite getButtonSprite() {
-                if (buttonSprite) return buttonSprite;
-                buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LighterButton.png", 115f);
-                return buttonSprite;
-            }
+            public static float flashlightWidth = 0.75f;
 
             public static void clearAndReload() {
                 lighter = null;
-                lighterTimer = 0f;
-                cooldown = CustomOptionHolder.lighterCooldown.getFloat();
-                duration = CustomOptionHolder.lighterDuration.getFloat();
+                flashlightWidth = CustomOptionHolder.lighterFlashlightWidth.getFloat();
                 lighterModeLightsOnVision = CustomOptionHolder.lighterModeLightsOnVision.getFloat();
                 lighterModeLightsOffVision = CustomOptionHolder.lighterModeLightsOffVision.getFloat();
             }
@@ -487,6 +510,13 @@ namespace TheOtherRoles
             return existing() && !lover1.Data.IsDead && !lover2.Data.IsDead && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER
         }
 
+        public static PlayerControl otherLover(PlayerControl oneLover) {
+            if (!existingAndAlive()) return null;
+            if (oneLover == lover1) return lover2;
+            if (oneLover == lover2) return lover1;
+            return null;
+        }
+
         public static bool existingWithKiller() {
             return existing() && (lover1 == Jackal.jackal     || lover2 == Jackal.jackal
                                || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick
@@ -792,23 +822,18 @@ namespace TheOtherRoles
         public static PlayerControl snitch;
         public static Color color = new Color32(184, 251, 79, byte.MaxValue);
 
-        public static List<Arrow> localArrows = new List<Arrow>();
         public static int taskCountForReveal = 1;
-        public static bool includeTeamJackal = false;
-        public static bool teamJackalUseDifferentArrowColor = true;
-
+        public static bool isRevealed = false;
+        public static Dictionary<byte, byte> playerRoomMap = new Dictionary<byte, byte>();
+        public static TMPro.TextMeshPro text = null;
 
         public static void clearAndReload() {
-            if (localArrows != null) {
-                foreach (Arrow arrow in localArrows)
-                    if (arrow?.arrow != null)
-                    UnityEngine.Object.Destroy(arrow.arrow);
-            }
-            localArrows = new List<Arrow>();
             taskCountForReveal = Mathf.RoundToInt(CustomOptionHolder.snitchLeftTasksForReveal.getFloat());
-            includeTeamJackal = CustomOptionHolder.snitchIncludeTeamJackal.getBool();
-            teamJackalUseDifferentArrowColor = CustomOptionHolder.snitchTeamJackalUseDifferentArrowColor.getBool();
             snitch = null;
+            isRevealed = false;
+            playerRoomMap = new Dictionary<byte, byte>();
+            if (text != null) UnityEngine.Object.Destroy(text);
+            text = null;
         }
     }
 
@@ -1177,7 +1202,7 @@ namespace TheOtherRoles
             douseTarget = null; 
             triggerArsonistWin = false;
             dousedPlayers = new List<PlayerControl>();
-            foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
+            foreach (PoolablePlayer p in TORMapOptions.playerIcons.Values) {
                 if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
             }
             cooldown = CustomOptionHolder.arsonistCooldown.getFloat();
@@ -1248,7 +1273,7 @@ namespace TheOtherRoles
             arrow = null;
             if (cooldownText != null && cooldownText.gameObject != null) UnityEngine.Object.Destroy(cooldownText.gameObject);
             cooldownText = 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);
             }
 
@@ -1309,8 +1334,23 @@ namespace TheOtherRoles
         public static float cooldown = 30f;
         public static float duration = 3f;
         public static bool oneTimeUse = false;
+        public static float chanceAdditionalInfo = 0f;
 
         private static Sprite soulSprite;
+
+        enum SpecialMediumInfo {
+            SheriffSuicide,
+            ThiefSuicide,
+            ActiveLoverDies,
+            PassiveLoverSuicide,
+            LawyerKilledByClient,
+            JackalKillsSidekick,
+            ImpostorTeamkill,
+            SubmergedO2,
+            WarlockSuicide,
+            BodyCleaned,
+        }
+
         public static Sprite getSoulSprite() {
             if (soulSprite) return soulSprite;
             soulSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Soul.png", 500f);
@@ -1335,6 +1375,95 @@ namespace TheOtherRoles
             cooldown = CustomOptionHolder.mediumCooldown.getFloat();
             duration = CustomOptionHolder.mediumDuration.getFloat();
             oneTimeUse = CustomOptionHolder.mediumOneTimeUse.getBool();
+            chanceAdditionalInfo = CustomOptionHolder.mediumChanceAdditionalInfo.getSelection() / 10f;
+        }
+
+        public static string getInfo(PlayerControl target, PlayerControl killer) {
+            string msg = "";
+
+            List<SpecialMediumInfo> infos = new List<SpecialMediumInfo>();
+            // collect fitting death info types.
+            // suicides:
+            if (killer == target) {
+                if (target == Sheriff.sheriff || target == Sheriff.formerSheriff) infos.Add(SpecialMediumInfo.SheriffSuicide);
+                if (target == Lovers.lover1 || target == Lovers.lover2) infos.Add(SpecialMediumInfo.PassiveLoverSuicide);
+                if (target == Thief.thief) infos.Add(SpecialMediumInfo.ThiefSuicide);
+                if (target == Warlock.warlock) infos.Add(SpecialMediumInfo.WarlockSuicide);
+            } else {
+                if (target == Lovers.lover1 || target == Lovers.lover2) infos.Add(SpecialMediumInfo.ActiveLoverDies);
+                if (target.Data.Role.IsImpostor && killer.Data.Role.IsImpostor && Thief.formerThief != killer) infos.Add(SpecialMediumInfo.ImpostorTeamkill);
+            }
+            if (target == Sidekick.sidekick && (killer == Jackal.jackal || Jackal.formerJackals.Any(x => x.PlayerId == killer.PlayerId))) infos.Add(SpecialMediumInfo.JackalKillsSidekick);
+            if (target == Lawyer.lawyer && killer == Lawyer.target) infos.Add(SpecialMediumInfo.LawyerKilledByClient);
+            if (Medium.target.wasCleaned) infos.Add(SpecialMediumInfo.BodyCleaned);
+            
+            if (infos.Count > 0) {
+                var selectedInfo = infos[rnd.Next(infos.Count)];
+                switch (selectedInfo) {
+                    case SpecialMediumInfo.SheriffSuicide:
+                        msg = "Yikes, that Sheriff shot backfired.";
+                        break;
+                    case SpecialMediumInfo.WarlockSuicide:
+                        msg = "MAYBE I cursed the person next to me and killed myself. Oops.";
+                        break;
+                    case SpecialMediumInfo.ThiefSuicide:
+                        msg = "I tried to steal the gun from their pocket, but they were just happy to see me.";
+                        break;
+                    case SpecialMediumInfo.ActiveLoverDies:
+                        msg = "I wanted to get out of this toxic relationship anyways.";
+                        break;
+                    case SpecialMediumInfo.PassiveLoverSuicide:
+                        msg = "The love of my life died, thus with a kiss I die.";
+                        break;
+                    case SpecialMediumInfo.LawyerKilledByClient:
+                        msg = "My client killed me. Do I still get paid?";
+                        break;
+                    case SpecialMediumInfo.JackalKillsSidekick:
+                        msg = "First they sidekicked me, then they killed me. At least I don't need to do tasks anymore.";
+                        break;
+                    case SpecialMediumInfo.ImpostorTeamkill:
+                        msg = "I guess they confused me for the Spy, is there even one?";
+                        break;
+                    case SpecialMediumInfo.BodyCleaned:
+                        msg = "Is my dead body some kind of art now or... aaand it's gone.";
+                        break;
+                }
+            } else {
+                int randomNumber = rnd.Next(4);
+                string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting.Data.DefaultOutfit.ColorId) ? "lighter" : "darker";
+                float timeSinceDeath = ((float)(Medium.meetingStartTime - Medium.target.timeOfDeath).TotalMilliseconds);
+                
+                if (randomNumber == 0) msg = "If my role hasn't been saved, there's no " + RoleInfo.GetRolesString(Medium.target.player, false) + " in the game anymore.";
+                else if (randomNumber == 1) msg = "I'm not sure, but I guess a " + typeOfColor + " color killed me.";
+                else if (randomNumber == 2) msg = "If I counted correctly, I died " + Math.Round(timeSinceDeath / 1000) + "s before the next meeting started.";
+                else msg = "It seems like my killer was the " + RoleInfo.GetRolesString(Medium.target.killerIfExisting, false, false, true) + ".";
+            }
+
+            if (rnd.NextDouble() < chanceAdditionalInfo) {
+                int count = 0;
+                string condition = "";
+                var alivePlayersList = PlayerControl.AllPlayerControls.ToArray().Where(pc => !pc.Data.IsDead);
+                switch (rnd.Next(3)) {
+                    case 0:
+                        count = alivePlayersList.Where(pc => pc.Data.Role.IsImpostor || new List<RoleInfo>() { RoleInfo.jackal, RoleInfo.sidekick, RoleInfo.sheriff, RoleInfo.thief }.Contains(RoleInfo.getRoleInfoForPlayer(pc, false).FirstOrDefault())).Count();
+                        condition = "killer" + (count == 1 ? "" : "s");
+                        break;
+                    case 1:
+                        count = alivePlayersList.Where(Helpers.roleCanUseVents).Count();
+                        condition = "player" + (count == 1 ? "" : "s") + " who can use vents";
+                        break;
+                    case 2:
+                        count = alivePlayersList.Where(pc => Helpers.isNeutral(pc) && pc != Jackal.jackal && pc != Sidekick.sidekick && pc != Thief.thief).Count();
+                        condition = "player" + (count == 1 ? "" : "s") + " who " + (count == 1 ? "is" : "are") + " neutral but cannot kill";
+                        break;
+                    case 3:
+                        //count = alivePlayersList.Where(pc =>
+                        break;               
+                }
+                msg += $"\nWhen you asked, {count} " + condition + (count == 1 ? " was" : " were") + " still alive";
+            }
+
+            return Medium.target.player.Data.PlayerName + "'s Soul:\n" + msg;
         }
     }
 
@@ -1509,7 +1638,7 @@ namespace TheOtherRoles
             suicideFlag = false;
             currentTarget = null;
             formerThief = null;
-            hasImpostorVision = CustomOptionHolder.thiefHasImpVision.getBool();  // todo option and implementation
+            hasImpostorVision = CustomOptionHolder.thiefHasImpVision.getBool();
             cooldown = CustomOptionHolder.thiefCooldown.getFloat();
             canUseVents = CustomOptionHolder.thiefCanUseVents.getBool();
             canKillSheriff = CustomOptionHolder.thiefCanKillSheriff.getBool();
@@ -1554,6 +1683,55 @@ namespace TheOtherRoles
         }
     }
 
+    public static class Bomber {
+        public static PlayerControl bomber = null;
+        public static Color color = Palette.ImpostorRed;
+
+        public static Bomb bomb = null;
+        public static bool isPlanted = false;
+        public static bool isActive = false;
+        public static float destructionTime = 20f;
+        public static float destructionRange = 2f;
+        public static float hearRange = 30f;
+        public static float defuseDuration = 3f;
+        public static float bombCooldown = 15f;
+        public static float bombActiveAfter = 3f;
+
+        private static Sprite buttonSprite;
+
+        public static Sprite getButtonSprite() {
+            if (buttonSprite) return buttonSprite;
+            buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Bomb_Button_Plant.png", 115f);
+            return buttonSprite;
+        }
+
+        public static void clearBomb(bool flag = true) {
+            if (bomb != null) {
+                UnityEngine.Object.Destroy(bomb.bomb);
+                UnityEngine.Object.Destroy(bomb.background);
+                bomb = null;
+            }
+            isPlanted = false;
+            isActive = false;
+            if (flag) SoundEffectsManager.stop("bombFuseBurning");
+        }
+
+        public static void clearAndReload() {
+            clearBomb(false);
+            bomber = null;
+            bomb = null;
+            isPlanted = false;
+            isActive = false;
+            destructionTime = CustomOptionHolder.bomberBombDestructionTime.getFloat();
+            destructionRange = CustomOptionHolder.bomberBombDestructionRange.getFloat() / 10;
+            hearRange = CustomOptionHolder.bomberBombHearRange.getFloat() / 10;
+            defuseDuration = CustomOptionHolder.bomberDefuseDuration.getFloat();
+            bombCooldown = CustomOptionHolder.bomberBombCooldown.getFloat();
+            bombActiveAfter = CustomOptionHolder.bomberBombActiveAfter.getFloat();
+            Bomb.clearBackgroundSprite();
+        }
+    }
+
     // Modifier
     public static class Bait {
         public static List<PlayerControl> bait = new List<PlayerControl>();
index 5fdd45b77e6087b969ed253dc213de759a16fa9e..eafa1f6cc68adebb6bb496165b33ed7dc0f8d5f2 100644 (file)
@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
     <PropertyGroup>
         <TargetFramework>net6.0</TargetFramework>
-        <Version>4.2.1</Version>
+        <Version>4.3.0</Version>
         <Description>TheOtherRoles</Description>
         <Authors>Eisbison</Authors>
                <LangVersion>latest</LangVersion>
         <PackageReference Include="Reactor" Version="2.1.0" />
     </ItemGroup>       
 
+    <Target Name="Date" BeforeTargets="BeforeBuild">  <!--Will make the compile time available s.t. we can let betas expire-->
+    <WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
+    <ItemGroup>
+    </ItemGroup>
+
+       <ItemGroup>
+               <!--PackageReference Include="AmongUs.GameLibs.Steam" Version="2023.02.28" /-->
+        <PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.664" />
+        <PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" />
+        <PackageReference Include="Reactor" Version="2.1.0" />
+    </ItemGroup>
+
+       <ItemGroup>
+               <Reference Include="$(AmongUs)/BepInEx/core/*.dll" />
+               <Reference Include="$(AmongUs)/BepInEx/interop/*.dll" />
+       </ItemGroup>
+
     <Target Name="Date" BeforeTargets="BeforeBuild">  <!--Will make the compile time available s.t. we can let betas expire-->
     <WriteLinesToFile File="$(IntermediateOutputPath)gen.cs" Lines="static partial class Builtin { public static long CompileTime = $([System.DateTime]::UtcNow.Ticks) %3B }" Overwrite="true" />
     <ItemGroup>
diff --git a/TheOtherRoles/Utilities/EventUtility.cs b/TheOtherRoles/Utilities/EventUtility.cs
new file mode 100644 (file)
index 0000000..b933486
--- /dev/null
@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using TheOtherRoles;
+using TheOtherRoles.Patches;
+using TheOtherRoles.Players;
+using static TheOtherRoles.TheOtherRoles;
+using System.Linq;
+using InnerNet;
+using TheOtherRoles.Modules;
+
+namespace TheOtherRoles.Utilities {
+    public static class EventUtility {
+
+        public enum EventTypes {
+            Communication,
+            Animation,
+            Invert,
+            KnockKnock
+        }
+
+        public static readonly float[] eventFrequencies = {15f, 60f, 60f, 300f};
+        public static readonly float[] eventDurations = {0f, 1f, 5f, 0f};
+        public static double[] eventProbabilities;
+        private static bool knocked = false;
+
+        public static void Load() {
+            if (!isEnabled) return;
+            eventProbabilities = new double[6];
+            foreach (EventTypes curEvent in Enum.GetValues(typeof(EventTypes))) {
+                float desired_trials = 60 * eventFrequencies[(int)curEvent];
+                eventProbabilities[(int)curEvent] = desired_trials != 0 ? 1f / desired_trials : 1f;
+            }
+                
+        } 
+
+        private static List<EventTypes> eventQueue = null;
+        public static bool eventInvert = false;
+
+        public static void clearAndReload() {
+            eventQueue = new List<EventTypes>();
+            eventInvert = false;
+
+        }
+
+        public static void Update() {
+            if (!isEnabled || eventQueue == null || AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started || TheOtherRoles.rnd == null || IntroCutscene.Instance) return;
+            foreach (EventTypes curEvent in eventQueue.ToArray()) {
+                if (TheOtherRoles.rnd.NextSingle() < eventProbabilities[(int)curEvent]) {
+                    eventQueue.Remove(curEvent);
+                    StartEvent(curEvent);
+                }
+            }
+
+            AddToQueue(EventTypes.Animation);
+            AddToQueue(EventTypes.Invert);
+            if (!knocked) {
+                AddToQueue(EventTypes.KnockKnock);
+            }
+        }
+
+        private static DateTime enabled = DateTime.FromBinary(-8585213068854775808);
+        public static bool isEventDate => DateTime.Today.Date == enabled;
+
+        public static bool canBeEnabled => DateTime.Today.Date > enabled && DateTime.Today.Date <= enabled.AddDays(7); // One Week after the EVENT
+        public static bool isEnabled => isEventDate || canBeEnabled && CustomOptionHolder.enableCodenameHorsemode != null && CustomOptionHolder.enableCodenameHorsemode.getBool();
+
+        public static void AddToQueue(EventTypes newEvent) {
+            if (!isEnabled || eventQueue == null || eventQueue.Contains(newEvent)) return;
+            eventQueue.Add(newEvent);
+        }
+
+
+        private static string defaultHat = "default";
+        public static void meetingEndsUpdate() {
+            if (!isEnabled) return;
+            PlayerControl.LocalPlayer.RpcSetHat(CustomHatLoader.horseHatProductIds[rnd.Next(CustomHatLoader.horseHatProductIds.Count)]);
+        }
+
+
+        public static void meetingStartsUpdate() {
+            if (!isEnabled) return;
+            if (rnd.NextDouble() <= 0.3f) { AddToQueue(EventTypes.Communication); }
+            PlayerControl.LocalPlayer.RpcSetHat(defaultHat);
+            HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action<float>((p) => {
+                if (MeetingHud.Instance && MeetingHud.Instance.playerStates != null) {
+                    foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) {
+                        GameData.PlayerInfo pInfo = GameData.Instance.AllPlayers.ToArray().First(x => x.PlayerId == pva.TargetPlayerId);
+                        pva.SetCosmetics(pInfo);  // Needed cause cosmetics are set async'd.
+                    }
+                }
+            })));
+        }
+
+        public static void gameStartsUpdate() {
+            if (!isEnabled) return;
+            defaultHat = PlayerControl.LocalPlayer.Data.DefaultOutfit.HatId;
+            meetingEndsUpdate();
+        }
+
+        public static void gameEndsUpdate() {
+            if (!isEnabled) return;
+            if (defaultHat != "default")
+                PlayerControl.LocalPlayer.RpcSetHat(defaultHat);
+        }
+
+        public static void StartEvent(EventTypes eventToStart) {
+            List<CachedPlayer> relevantPlayers = CachedPlayer.AllPlayers.Where(x => !x.Data.IsDead && x != CachedPlayer.LocalPlayer).ToList();
+            switch (eventToStart) {
+                case EventTypes.Animation:
+                    CachedPlayer animationPlayer = relevantPlayers[rnd.Next(relevantPlayers.Count)];
+                    animationPlayer.PlayerPhysics.SetBodyType(rnd.Next(2) > 0 ? PlayerBodyTypes.Horse : PlayerBodyTypes.Seeker);
+                    FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Animation], new Action<float>((p) => {
+                        if (p==1)
+                            animationPlayer.PlayerControl.MyPhysics.SetBodyType(PlayerBodyTypes.Normal);
+                    })));
+                    break;
+                case EventTypes.Communication:
+                    int index = TheOtherRoles.rnd.Next(relevantPlayers.Count);
+                    CachedPlayer firstPlayer = relevantPlayers[index];
+                    relevantPlayers.RemoveAt(index);
+                    string msg = firstPlayer.Data.PlayerName + " ";
+                    foreach (CachedPlayer pc in relevantPlayers.ToArray()) {
+                        if (TheOtherRoles.rnd.NextSingle() < 1f / relevantPlayers.Count) {
+                            relevantPlayers.Remove(pc);
+                            msg += pc.Data.PlayerName + " ";
+                        }
+                    }
+                    RoomTracker tracker = FastDestroyableSingleton<HudManager>.Instance.roomTracker;
+                    string lastRoom = "";
+                    try { lastRoom = FastDestroyableSingleton<TranslationController>.Instance.GetString(tracker.LastRoom.RoomId); } catch { }
+                    msg += lastRoom != ""? lastRoom : (TheOtherRoles.rnd.Next(2) > 0 ? "sus" : "safe");
+
+                    FastDestroyableSingleton<HudManager>.Instance.Chat.AddChat(relevantPlayers[rnd.Next(relevantPlayers.Count)], $"{msg}");
+                    break;
+                case EventTypes.Invert:
+                    eventInvert = true;
+                    FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(eventDurations[(int)EventTypes.Invert], new Action<float>((p) => {
+                        if (p == 1)
+                            eventInvert = false;
+                    })));
+                    break;
+                case EventTypes.KnockKnock:
+                    SoundEffectsManager.play("knockKnock");
+                    knocked = true;
+                    break;
+
+            }
+        }
+    }
+}
index 2e6b8c11ad0f8799ccfa323d4b07f1e923e1065a..2b81e0852e45aa4de5bb606306bbbd0789ac2199 100644 (file)
@@ -37,7 +37,7 @@ namespace TheOtherRoles.Utilities {
         public static void clearAndReload() {
             Guesser.clearAndReload();
             GuesserGM.clearAndReload();
-            isGuesserGm = MapOptions.gameMode == CustomGamemodes.Guesser;
+            isGuesserGm = TORMapOptions.gameMode == CustomGamemodes.Guesser;
             if (isGuesserGm) {
                 guesserCantGuessSnitch = CustomOptionHolder.guesserGamemodeCantGuessSnitchIfTaksDone.getBool();
                 hasMultipleShotsPerMeeting = CustomOptionHolder.guesserGamemodeHasMultipleShotsPerMeeting.getBool();