From: Mallöris <|||||@||.||> Date: Wed, 26 Oct 2022 18:34:36 +0000 (+0200) Subject: Update to v4.2.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=8243ded4bd6c9d02f91d5c6d7a0657990c8516a9;p=rhonda%2Ftheotherroles.git Update to v4.2.0 --- diff --git a/README.md b/README.md index 7816a43..28d0abf 100644 --- a/README.md +++ b/README.md @@ -10,44 +10,46 @@ This mod is not affiliated with Among Us or Innersloth LLC, and the content cont # The Other Roles -The **The Other Roles**, is a mod for [Among Us](https://store.steampowered.com/app/945360/Among_Us) which adds many new roles, new [Settings](#settings) and new [Custom Hats](#custom-hats) to the game. +The **The Other Roles** is a mod for [Among Us](https://store.steampowered.com/app/945360/Among_Us) which adds many new roles, new [Settings](#settings) and new [Custom Hats](#custom-hats) to the game. Even more roles are coming soon. :) -| Impostors | Crewmates | Neutral | Modifier | -|----------|-------------|-----------------|----------------| -| [Godfather (Mafia)](#mafia) | [Shifter](#shifter) | [Jester](#jester) | [Bloody](#bloody) | -| [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | [Arsonist](#arsonist) | [Anti Teleport](#anti-teleport) | -| [Janitor (Mafia)](#mafia) | [Engineer](#engineer) | [Jackal](#jackal) | [Tie Breaker](#tie-breaker) | -| [Morphling](#morphling) | [Sheriff](#sheriff) | [Sidekick](#sidekick) | [Bait](#bait) | -| [Camouflager](#camouflager) | [Deputy](#deputy) | [Vulture](#vulture) | [Lovers](#lovers) | -| [Vampire](#vampire) | [Lighter](#lighter) | [Lawyer](#lawyer) | [Sunglasses](#sunglasses) | -| [Eraser](#eraser) | [Detective](#detective) | | [Mini](#mini) | -| [Trickster](#trickster) | [Time Master](#time-master) | | [VIP](#vip) | -| [Cleaner](#cleaner) | [Medic](#medic) | | [Invert](#invert) | -| [Warlock](#warlock) | [Swapper](#swapper) | -| [Bounty Hunter](#bounty-hunter) | [Seer](#seer) | | -| [Witch](#witch) | [Hacker](#hacker) | | | -| [Ninja](#ninja) | [Tracker](#tracker) | | | -| [Evil Guesser](#guesser) | [Snitch](#snitch) | | | -| | [Spy](#spy) | | | +| Impostor | Crewmate | Neutral | Modifier | Gamemode | +|----------|-------------|-----------------|----------------|----------------| +| [Godfather (Mafia)](#mafia) | [Mayor](#mayor) | [Jester](#jester) | [Bloody](#bloody) | [Classic](#roles) | +| [Mafioso (Mafia)](#mafia) | [Engineer](#engineer) | [Arsonist](#arsonist) | [Anti Teleport](#anti-teleport) | [Guesser Modifier](#guesser-modifier) | +| [Janitor (Mafia)](#mafia) | [Sheriff](#sheriff) | [Jackal](#jackal) | [Tie Breaker](#tie-breaker) | [Hide 'n Seek](#hide-n-seek) | +| [Morphling](#morphling) | [Deputy](#deputy) | [Sidekick](#sidekick) | [Bait](#bait) | +| [Camouflager](#camouflager) | [Lighter](#lighter) | [Vulture](#vulture) | [Lovers](#lovers) | +| [Vampire](#vampire) | [Detective](#detective) | [Lawyer](#lawyer) | [Sunglasses](#sunglasses) | +| [Eraser](#eraser) | [Time Master](#time-master) | [Prosecutor](#prosecutor) | [Mini](#mini) | +| [Trickster](#trickster) | [Medic](#medic) | [Pursuer](#pursuer) | [VIP](#vip) | +| [Cleaner](#cleaner) | [Swapper](#swapper) | [Thief](#thief) | [Invert](#invert) | +| [Warlock](#warlock) | [Seer](#seer) | | [Chameleon](#chameleon) | +| [Bounty Hunter](#bounty-hunter) | [Hacker](#hacker) | | [Shifter](#shifter) +| [Witch](#witch) | [Tracker](#tracker) | | | +| [Ninja](#ninja) | [Snitch](#snitch) | | | +| [Evil Guesser](#guesser) | [Spy](#spy) | | | | | [Portalmaker](#portalmaker) | | | | | [Security Guard](#security-guard) | | | | | [Medium](#medium) | | | -| | [Nice Guesser](#guesser) | | +| | [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.8.24| v4.1.7| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.7/TheOtherRoles.zip) -| 2022.6.21| v4.1.6| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.6/TheOtherRoles.zip) -| 2022.6.21| v4.1.5| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.5/TheOtherRoles.zip) -| 2022.3.29| v4.1.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.4/TheOtherRoles.zip) -| 2022.3.29| v4.1.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.3/TheOtherRoles.zip) -| 2022.3.29| v4.1.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.2/TheOtherRoles.zip) -| 2022.3.29| v4.1.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.1/TheOtherRoles.zip) -| 2022.3.29| v4.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.0/TheOtherRoles.zip) +| 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) +| 2022.6.21s| v4.1.5| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.5/TheOtherRoles.zip) +| 2022.3.29s| v4.1.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.4/TheOtherRoles.zip) +| 2022.3.29s| v4.1.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.3/TheOtherRoles.zip) +| 2022.3.29s| v4.1.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.2/TheOtherRoles.zip) +| 2022.3.29s| v4.1.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.1/TheOtherRoles.zip) +| 2022.3.29s| v4.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.1.0/TheOtherRoles.zip) | 2022.3.29s| v4.0.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v4.0.0/TheOtherRoles.zip) | 2022.3.29s| v3.4.5| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.4.5/TheOtherRoles.zip) | 2022.2.23s| v3.4.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.4.4/TheOtherRoles.zip) @@ -109,6 +111,32 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 4.2.0** +- Added a new Guesser-Gamemode +- Added a new Hide 'n Seek-Gamemode | Thanks to [Oberbayer](https://twitch.tv/oberbayer) +- Added a new role for Hide 'n Seek-Gamemode: Hunter +- Added a new role for Hide 'n Seek-Gamemode: Hunted +- Added a new role: Thief +- Added a new role: Trapper +- Added a new modifier: Chameleon +- Added a new feature to hide the "Colorblind"-Text when hiding behind obstacles +- Added a new role: Prosecutor (in the Lawyer settings) | Thanks to [Scoom](https://github.com/JustASysAdmin) +- Added a new option to the Mini Modifier: "Mini Grows Up In Meeting" +- Added a new server: Modded Asia (MAS) | Thanks to [miniduikboot](https://github.com/miniduikboot) +- Added support for joining public TOR lobbies using the "Find Game"-Button **on Modded Servers (only!)** | Thanks to [miniduikboot](https://github.com/miniduikboot) +- Changed Shifter to be a Modifier instead of being a role +- Changed Eraser to only erase the role and not the Modifier +- Changed that neutral roles doesn't have to do tasks anymore to get the overview when they died before their client +- Changed "Play On Random Map". You can now set percentages for each map (like you can do for roles) +- Fixed a bug where the trail color of the player with the "Bloody"-Modifier sometimes had the wrong color when they got killed +- Fixed a bug where the Ninja could target the Spy, but not the Impostor +- Fixed a bug where the countdown wasn't displayed for all player when starting a game +- Fixed bugs in Freeplay (hats were always adaptive when testing & Meetingscreen fixed) +- Fixed a bug where Crewmate/Impostor wasn't displayed correctly in Endscreen +- Fixed a bug where Crewmate/Impostor wasn't displayed correctly in Meetingscreen +- Fixed a bug where Lawyer didn't get promoted to Pursuer when client died (in a Meeting) +- Fixed a bug in Meetings when a Tiebreaker and Swapper where involved + **Version 4.1.7** - Updated to Among Us version 2022.8.24 @@ -627,7 +655,7 @@ Not working? You might want to install the dependency [vc_redist](https://aka.ms ## Linux Installation 1. Install Among Us via Steam 2. Download newest [release](https://github.com/Eisbison/TheOtherRoles/releases/latest) and extract it to ~/.steam/steam/steamapps/common/Among Us -3. Enable `winhttp.dll` via the proton winecfg (https://docs.bepinex.dev/articles/advanced/steam_interop.html#open-winecfg-for-the-target-game) +3. Enable winhttp.dll via the proton winecfg (https://docs.bepinex.dev/articles/advanced/proton_wine.html) 4. Launch the game via Steam ## The Other Roles Custom Servers @@ -664,28 +692,28 @@ docker run -d -p 22023:22023/udp --env IMPOSTOR_AntiCheatEnabled=false --env IMP # Credits & Resources -[OxygenFilter](https://github.com/NuclearPowered/Reactor.OxygenFilter) - For all the version v2.3.0 to v2.6.1, we were using the OxygenFilter for automatic deobfuscation\ -[Reactor](https://github.com/NuclearPowered/Reactor) - The framework used for all version before v2.0.0\ -[BepInEx](https://github.com/BepInEx) - Used to hook game functions\ +[OxygenFilter](https://github.com/NuclearPowered/Reactor.OxygenFilter) - For all the versions between v2.3.0 and v2.6.1, we were using the OxygenFilter for automatic deobfuscation\ +[Reactor](https://github.com/NuclearPowered/Reactor) - The framework used for all versions before v2.0.0\ +[BepInEx](https://github.com/BepInEx) - Used to hook to game functions\ [Essentials](https://github.com/DorCoMaNdO/Reactor-Essentials) - Custom game options by **DorCoMaNdO**: - Before v1.6: We used the default Essentials release -- v1.6-v1.8: We slightly changed the default Essentials. The changes can be found on this [branch](https://github.com/Eisbison/Reactor-Essentials/tree/feature/TheOtherRoles-Adaption) of our fork. -- v2.0.0 and later: As we're not using Reactor anymore, we are using our own implementation, inspired by the one from **DorCoMaNdO** - -[Jackal and Sidekick](https://www.twitch.tv/dhalucard) - Original idea for the Jackal and Sidekick comes from **Dhalucard**\ -[Among-Us-Love-Couple-Mod](https://github.com/Woodi-dev/Among-Us-Love-Couple-Mod) - Idea for the Lovers role comes from **Woodi-dev**\ -[Jester](https://github.com/Maartii/Jester) - Idea for the Jester role comes from **Maartii**\ -[ExtraRolesAmongUs](https://github.com/NotHunter101/ExtraRolesAmongUs) - Idea for the Engineer and Medic role comes from **NotHunter101**. Also some code snippets come of the implementation were used.\ -[Among-Us-Sheriff-Mod](https://github.com/Woodi-dev/Among-Us-Sheriff-Mod) - Idea for the Sheriff role comes from **Woodi-dev**\ -[TooManyRolesMods](https://github.com/Hardel-DW/TooManyRolesMods) - Idea for the Detective and Time Master roles comes from **Hardel-DW**. Also some code snippets of the implementation were used.\ -[TownOfUs](https://github.com/slushiegoose/Town-Of-Us) - Idea for the Swapper, Shifter, Arsonist and a similar Mayor role come from **Slushiegoose**\ -[Ottomated](https://twitter.com/ottomated_) - Idea for the Morphling, Snitch and Camouflager role come from **Ottomated**\ -[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies is inspired by the one from the **Crowded Mod Team**\ -[Goose-Goose-Duck](https://store.steampowered.com/app/1568590/Goose_Goose_Duck) - Idea for the Vulture role come from **Slushygoose** +- v1.6-v1.8: We slightly changed the default Essentials release. The changes can be found on this [branch](https://github.com/Eisbison/Reactor-Essentials/tree/feature/TheOtherRoles-Adaption) of our fork. +- v2.0.0 and later: We're no longer using Reactor anymore we are using our own implementation inspired by the one from **DorCoMaNdO** + +[Jackal and Sidekick](https://www.twitch.tv/dhalucard) - Original idea for the Jackal and Sidekick came from **Dhalucard**\ +[Among-Us-Love-Couple-Mod](https://github.com/Woodi-dev/Among-Us-Love-Couple-Mod) - Idea for the Lovers modifier came from **Woodi-dev**\ +[Jester](https://github.com/Maartii/Jester) - Idea for the Jester role came from **Maartii**\ +[ExtraRolesAmongUs](https://github.com/NotHunter101/ExtraRolesAmongUs) - Idea for the Engineer and Medic role came from **NotHunter101**. Also some code snippets from their implementation were used.\ +[Among-Us-Sheriff-Mod](https://github.com/Woodi-dev/Among-Us-Sheriff-Mod) - Idea for the Sheriff role came from **Woodi-dev**\ +[TooManyRolesMods](https://github.com/Hardel-DW/TooManyRolesMods) - Idea for the Detective and Time Master roles came from **Hardel-DW**. Also some code snippets from their implementation were used.\ +[TownOfUs](https://github.com/slushiegoose/Town-Of-Us) - Idea for the Swapper, Shifter, Arsonist and a similar Mayor role came from **Slushiegoose**\ +[Ottomated](https://twitter.com/ottomated_) - Idea for the Morphling, Snitch and Camouflager role came from **Ottomated**\ +[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies were inspired by the one from the **Crowded Mod Team**\ +[Goose-Goose-Duck](https://store.steampowered.com/app/1568590/Goose_Goose_Duck) - Idea for the Vulture role came from **Slushiegoose**\ +[Ninja](#ninja), [Thief](#thief), [Lawyer](#lawyer) / [Pursuer](#pursuer), [Deputy](#deputy), [Portalmaker](#portalmaker), [Guesser Modifier](#guesser-modifier) - Idea: [K3ndo](https://github.com/K3ndoo) ; Developed by [Gendelo](https://github.com/gendelo3) & [Mallöris](https://github.com/Mallaris) # Settings The mod adds a few new settings to Among Us (in addition to the role settings): -- **Streamer Mode:** You can activate the streamer mode in the Among Us settings. It hides the lobby code, the custom server ip and the custom server port. You can set a custom lobby code replacement text, by changing the *Streamer Mode Replacement Text* in the `BepInEx\config\me.eisbison.theotherroles.cfg` file. - **Number of Crewmates:** The number of Crewmate roles can be set inside a lobby. - **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. @@ -696,16 +724,16 @@ 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. -- **Play On A Random Map** If enabled it allows you to set a rotation of all current maps, except ehT dlekS -- **Ghosts Can See Roles** -- **Ghosts Can See Votes** -- **Ghosts Can Additionally See Modifier** -- **Ghosts Can See The Number Of Remaining Tasks** -- **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** +- **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.** +- **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 and their roles and their task progress -- **Darker/Lighter:** Displays color type of each player in meetings +- **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. ### Task Count Limits per map You can configure: @@ -860,11 +888,9 @@ By default the Eraser can erase everyone but the Spy and other Impostors.\ Depending on the options they can also erase them (Impostors will lose their special Impostor ability).\ \ **NOTE:** -- The Shifter shift will always be triggered before the Erase (hence either the new role of the Shifter will be erased or the Shifter saves the role of their target, depending on whom the Eraser erased) -- Erasing a Lover automatically erases the other Lover as well (if the second Lover is an ImpLover, they will turn into an Impostor) -- Erasing a Jackal that has a Sidekick, triggers the Sidekick promotion if it's activated in the settings -- As the erase is being triggered before the ejection of a player, erasing and voting out a Lover in the same round, would result in the -ex-Lover surviving, as the partnership was erased before. Also a Jester win would not happen, as the erase will be triggered before. +- The Shifter shift will always be triggered before the Erase (hence either the new role of the Shifter will be erased or the Shifter saves the role of their target, depending on whom the Eraser erased). +- As the erase is being triggered before the ejection of a player, a Jester win would not happen, as the erase will be triggered before. +- Modifier will not be erased. ### Game Options | Name | Description | @@ -1157,52 +1183,74 @@ How the Lawyer wins: - If their client is Jester and the Jester gets voted out, the Lawyer wins together with the Jester. **NOTE:** -- If the client disconnects, the Lawyer will also turn into the Pursuer -- The Lawyer needs to figure out the role of their client depending on the options +- If the client disconnects, the Lawyer will also turn into the Pursuer. +- The Lawyer needs to figure out the role of their client depending on the options. +- The tasks only count, if the Lawyer gets promoted to Pursuer. +- If the Lawyer dies before their client, they will lose all their tasks and will get the overview immediately. ### Game Options | Name | Description | |----------|:-------------:| | Lawyer Spawn Chance | - +| Chance That The Lawyer Is Prosecutor | - +| Lawyer/Prosecutor Vision | Pursuer has normal vision +| Lawyer/Prosecutor Knows Target Role | - +| Lawyer/Prosecutor Can Call Emergency Meeting | - | Lawyer Target Can Be The Jester | - -| Lawyer Wins After Meetings | If set to true, the Lawyer wins after a configurable amount of meetings (can't start meetings himself) -| Lawyer Needed Meetings To Win | - -| Lawyer Vision | Pursuer has normal vision -| Lawyer Knows Target Role | - -| Pursuer Blank Cooldown | - -| Pursuer Number Of Blanks | - ----------------------- +## Prosecutor +### **Team: Neutral** +The Prosecutor is a neutral role that resembles the Lawyer. The Prosecutor has a client who is a Crewmate.\ +The Prosecutor needs their client to be voted out in order to win the game.\ +The Prosecutor's client doesn't know that they are their client.\ +If the client gets sidekicked, the Prosecutor changes their role and becomes the client's [Lawyer](#lawyer) and has to protect the +client from now on.\ +If the Prosecutor's client dies, the Prosecutor changes their role and becomes the [Pursuer](#pursuer), which has a different goal to win the game. + +**NOTE:** +- The Prosecutor's role settings are shared with the Lawyer settings. +- If the client disconnects, the Prosecutor will also turn into the Pursuer. +- The Prosecutor needs to figure out the role of their client depending on the options. +- The tasks only count, if the Prosecutor gets promoted to Pursuer. +- If the Prosecutor dies before their client, they will lose all their tasks and will get the overview immediately. + ## Pursuer ### **Team: Neutral** The Pursuer is still a neutral role, but has a different goal to win the game; they have to be alive when the game ends and the Crew wins.\ -In order to achieve this goal, the Pursuer has an ability called "Blank", where they can fill a killers (this also includes the Sheriff) weapon with a blank. So, if the killer attempts to kill someone, the killer will miss their target, and their cooldowns will be triggered as usual.\ +In order to achieve this goal, the Pursuer has an ability called "Blank", where they can fill a killer's (this also includes the Sheriff) weapon with a blank. So, if the killer attempts to kill someone, the killer will miss their target, and their cooldowns will be triggered as usual.\ If the killer fires the "Blank", shields (e.g. Medic shield or Time Master shield) will not be triggered.\ -The Pursuer has tasks (which can already be done while being a Lawyer), that count towards the task win for the Crewmates. If the Pursuer dies, their tasks won't be counted anymore. +The Pursuer has tasks (which can already be done while being a Lawyer/Prosecutor), that count towards the task win for the Crewmates. If the Pursuer dies, their tasks won't be counted anymore. +### Game Options +| Name | Description | +|----------|:-------------:| +| Pursuer Blank Cooldown | - +| Pursuer Number Of Blanks | - ----------------------- -## Shifter -### **Team: Crewmates** -The Shifter can take over the role of another Crewmate, the other player will transform into a Crewmate.\ -The Shift will always be performed at the end of the next meeting right before a player is exiled. The target needs to be chosen during the round.\ -Even if the Shifter or the target dies before the meeting, the Shift will still be performed.\ -Swapping roles with an Impostor or Neutral fails and the Shifter commits suicide after the next meeting (there won't be any body).\ -The Shifter aims to save roles from leaving the game, by e.g. taking over a Sheriff or Medic that is known to the Impostors.\ -This works especially well against the Eraser, but also gives the Eraser the possibility to act like a Shifter.\ -The **special interactions** with the Shifter are noted in the chapters of the respective roles.\ +## Thief +### **Team: Neutral** +The Thief has to kill another killer (Impostor, Jackal/Sidekick and if enabled Sheriff) +in order to have a win condition.\ +If the Thief doesn't kill another killer they will lose the game.\ +If the Thief kills one of the other killers, the Thief overtakes their role (e.g. Ninja) and joins their team (in this case +team Impostor). They then have the new Role's win condition (e.g. Impostor-Win).\ +If the Thief tries to kill any non-killing role (Crewmate or Neutral), they die similar to a misfiring Sheriff.\ \ -**NOTE:** -- The Shifter shift will always be triggered before the Erase (hence either the new role of the Shifter will be erased or the Shifter saves the role of their target, depending on whom the Eraser erased) -- If the Shifter takes over a role, their new cooldowns will start at the maximum cooldown of the ability -- One time use abilities (e.g. shielding a player or Engineer sabotage fix) can only used by one player in the game (i.e. the Shifter -can only use them, if the previous player did not use them before) +**NOTE** +- If the option "Thief Can Kill Sheriff" is On, the Thief has tasks which will ONLY begin to count, if they kill +the Sheriff. While the Thief hasn't fired, their tasks do not count towards the taskwin. +- If the option "Thief Can Kill Sheriff" is Off, the Thief will not have tasks. ### Game Options -| Name | Description +| Name | Description | |----------|:-------------:| -| Shifter Spawn Chance | - -| Shifter Shifts Modifiers | Sets if Lovers and/or Medic Shield will be shifted +| Thief Spawn Chance | - +| Thief Countdown | - +| Thief Can Kill Sheriff | - +| Thief Has Impostor Vision | - +| Thief Can Use Vents | - ----------------------- ## Mayor @@ -1522,7 +1570,6 @@ The new camera will be visible after the next meeting and accessible by everyone The vents will be sealed after the next meeting, players can't enter or exit sealed vents, but they can still "move to them" underground. **NOTE:** - - Trickster boxes can't be sealed - The Security Guard can't place cameras on MiraHQ - The remaining number of screws can be seen above their special button. @@ -1564,15 +1611,43 @@ What is your killers role? | Medium Each Soul Can Only Be Questioned Once | If set to true, souls can only be questioned once and then disappear ----------------------- +## Trapper +### **Team: Crewmates** +The Trapper is a crewmate which can set up traps to trap player and gain information from them.\ +The traps will stuck player for x-seconds (depends on the setting) and reveal information in chat +about their "Role", if they are a "Good/Evil Role" or their "Name".\ +The trap is not visible until a configurable amount of player were trapped.\ +When the trap gets visible, the Trapper will gain the information in chat (in a random order).\ +If a trap is triggered (and the option is enabled), the map of the Trapper will open up and show which trap +was triggered.\ +The traps have a maximum of charges (uses) and a configurable amount of tasks are needed to recharge.\ +\ +**NOTE:** +- The Trapper can't be trapped in their own trap(s). + +### Game Options +| Name | Description +|----------|:-------------:| +| Trapper Spawn Chance | - +| Trapper Cooldown | - +| Max Traps Charges | - +| Number Of Tasks Needed For Recharging | - +| Trap Needed Trigger To Reveal | - +| Show Anonymous Map | - +| Trap Information Type | "Name", "Role", "Good/Evil Role" +| Trap Duration | - +----------------------- + # Modifier -A modifier is an addition to your Impostor/Neutral/Crewmate role. -Some modifiers can be ingame more than once (Quantity option). +A Modifier is an addition to your Impostor/Neutral/Crewmate role. +Some Modifiers can be ingame more than once (Quantity option). ## Bloody -### Bloody -If killed, the Bloody Modifier will leave a trail for x-seconds on their killer. The trail will have the color of the killed person. Impostor, -Neutral or Crewmate roles can have this Modifier. +If killed, the Bloody Modifier will leave a trail for x-seconds on their killer. The trail will have the color of the killed person.\ +\ +**NOTE** +- Impostor, Neutral or Crewmate roles can have this Modifier ### Game Options | Name | Description | @@ -1585,7 +1660,10 @@ Neutral or Crewmate roles can have this Modifier. ## Anti Teleport The Anti Teleport Modifier prevents the player from getting teleported to the Meeting Table if a body gets reported or an Emergency Meeting is called.\ -The player will start the round where the previous one ended (Emergency Meeting Call/Body Report). +The player will start the round where the previous one ended (Emergency Meeting Call/Body Report).\ +\ +**NOTE** +- Impostor, Neutral or Crewmate roles can have this Modifier ### Game Options | Name | Description | @@ -1597,7 +1675,11 @@ The player will start the round where the previous one ended (Emergency Meeting ## Tie Breaker If the Voting ends in a tie, the Tie Breaker takes place and the player with the Tie Breaker Modifier gets an extra vote thats not visible to anyone.\ -Everyone will know if the Tie Breaker was involved in the Meeting or not. +Everyone will know if the Tie Breaker was involved in the Meeting or not.\ +\ +**NOTE** +- Impostor, Neutral or Crewmate roles can have this Modifier +- There can only be on player with this Modifier ### Game Options | Name | Description | @@ -1611,7 +1693,8 @@ The Bait forces the killer to self report the body (you can configure a delay in There can be more than one Bait. **NOTE:** -- If the Sheriff has the Bait modifier and dies while trying to kill a Crewmate, the Sheriff will *NOT* report themself. +- If the Sheriff has the Bait Modifier and dies while trying to kill a Crewmate, the Sheriff will *NOT* report themself. +- Impostor, Neutral or Crewmate roles can have this Modifier ### Game Options | Name | Description @@ -1650,16 +1733,16 @@ In a 2 Crewmates vs 2 Impostors (or 2 members of team Jackal) and the Lovers are ----------------------- **NOTE:** -- The role **Lover** can't be guessed, you'll have to guess the primary role of one of the Lovers, to kill both of them. +- The Modifier **Lover** can't be guessed, you'll have to guess the primary role of one of the Lovers, to kill both of them. ## Sunglasses -The Sunglasses will lower the Crewmate's vision by small percentage. The percentage is configurable in the options.\ +The Sunglasses will lower the Crewmate's vision by a small percentage. The percentage is configurable in the options.\ The vision will also be affected when lights out. **NOTE:** - Sunglasses only affects Crewmates. -- If you have the Sunglasses modifier and get sidekicked, you lose the modifier. +- If you have the Sunglasses Modifier and get sidekicked, you will lose the Modifier. ### Game Options | Name | Description @@ -1697,6 +1780,7 @@ The Mini cannot be killed until it turns 18 years old, however it can be voted o |----------|:-------------:| | Mini Spawn Chance | - | Mini | Mini Growing Up Duration +| Mini Grows Up In Meeting | - ----------------------- ## VIP @@ -1721,7 +1805,9 @@ Teams: ## Invert The Invert Modifier inverts your controls (no matter if keyboard or mouse).\ -The Invert can affect all teams (Impostor, Neutral, Crewmate). +\ +**NOTE** +- Impostor, Neutral or Crewmate roles can have this Modifier ### Game Options | Name | Description @@ -1730,6 +1816,111 @@ The Invert can affect all teams (Impostor, Neutral, Crewmate). | Invert Quantity | - ----------------------- +## Chameleon + +The Chameleon becomes (partially or fully) invisible when standing still for x-seconds (depends on the settings).\ +\ +**NOTE** +- You can use abilities while being invisible, only moving will make you visible again +- Impostor, Neutral or Crewmate roles can have this Modifier + +### Game Options +| Name | Description +|----------|:-------------:| +| Chameleon Spawn Chance | - +| Chameleon Quantity | - +| Time Until Fading Starts | - +| Fade Duration | - +| Minimum Visibility | - +----------------------- + +## Shifter + +The Shifter is a Modifier that can shift with another player. If the other player is Crewmate as well, they will swap their roles.\ +Swapping roles with an Impostor or Neutral fails and the Shifter commits suicide after the next meeting (there won't be a body).\ +The Shift will always be performed at the end of the next meeting right before a player is exiled. The target needs to be chosen during the round.\ +Even if the Shifter or the target dies before the meeting, the Shift will still be performed.\ +\ +**NOTE:** +- The Shifter shift will always be triggered before the Erase (hence either the new role of the Shifter will be erased or the Shifter gets the role of their target, depending on whom the Eraser erased) +- One time use abilities (e.g. shielding a player or Engineer sabotage fix) can only used by one player in the game (i.e. the Shifter +can only use them, if the previous player did not use them before) +- The Shifter button is located in the bottom left corner right next to the garlic button (if the Vampire is enabled) +- Only a Crewmate role can have this Modifier + +### Game Options +| Name | Description +|----------|:-------------:| +| Shifter Spawn Chance | - +----------------------- + +# Gamemodes + +## Guesser Modifier +The **Guesser-Gamemode** is an extension to the Classic-Gamemode and gives you a multitude of new options for Guessers.\ +The Guesser is now like an addition and can be applied to all players IF you want to. Settings are available to set the number of guessers for each team separately (Impostor, Neutral, Crewmate).\ +When playing this game mode, the Guessers can have another role (e.g. Medic Guesser). The same +applies of course for Impostor and/or Neutral roles.\ +Players can additionally have a modifier, if enabled (e.g. Medic Guesser Mini). + +### Game Options +| Name | Description +|----------|:-------------:| +| Number of Crew Guessers | - +| Number of Neutral Guessers | - +| Number of Impostor Guessers | - +| Force Jackal Guesser | If set to "On", the first neutral role who will be Guesser is the Jackal. +| Guessers Can Have A Modifier | - +| Guesser Number Of Shots | - +| Guesser Can Shoot Multiple Times Per Meeting | - +| Guesses Ignore The Medic Shield | - +| Evil Guesser Can Guess The Spy | - +| Guesser Can't Guess Snitch When Tasks Completed | - +----------------------- + +**NOTE** +- If a Crewmate Guesser gets sidekicked, they will remain a Guesser even if the host (maybe) has set up only 1 "Neutral role Guesser". + +## Hide 'n Seek +The **Hide 'n Seek-Gamemode** is a standalone Gamemode where Hunter have to catch their prey ("Hunted" players).\ +The Hunter and Hunted player who are still alive are displayed to everyone in the bottom left corner (similar to the Arsonist display).\ +When the game starts, the Hunter's movement is disabled for x-seconds (depends on the settings).\ +There is a time-limit for each round, if the timer runs out and at least one Hunted is still alive, the Crew wins. The Hunted players can also win, if Taskwin is enabled and the Crew completes all tasks.\ +If the Hunters kill all players before one of these conditions is triggered, the Hunters win.\ + +#### Hunter Abilities: +- Enable arrows (arrows point to all Hunted players for x-seconds (depending on settings)) +- Mobile Admin table (like Hacker) +- Lighter ability (gives the Hunter a biggier vision radius for x-seconds (depending on settings)) + +#### Hunted Ability: +- Timeshield (like Timemaster, depending on settings) + +Each Hunter action or finished Crew task will lower the timer by a configurable amount of time.\ +\ +**NOTE:** +- We added a vent on Polus (Specimen), but only for this Gamemode. The vent is connected with Admin & Lab. +- The Report button lights up, but cannot be pressed. +- The tasks can be configured separately for this mode, without affecting your normal game settings. +- Only the Hunter will be rewinded if they try to kill a player with an active timeshield. + +### Game Options +| General | Description | Hunter | Description | Hunted | Description | +|----------|:-------------:|----------|:-------------:|----------|:-------------:| +| Map | - | Hunter Light Cooldown | - | Hunted Shield Cooldown | - +| Number Of Hunters | - | Hunter Light Duration | - | Hunted Shield Duration | - +| Kill Cooldown | - | Hunter Light Vision | - | Hunted Rewind Time | - +| Hunter Vision | - | Hunter Light Punish in Sec | - | Hunted Shield Number | - +| Hunted Vision | - | Hunter Admin Cooldown | - +| Common Tasks | - | Hunter Admin Duration | - +| Short Tasks | - | Hunter Admin Punish In Sec | - +| Long Tasks | - | Hunter Arrow Cooldown | - +| Timer In Min | - | Hunter Arrow Duration | - +| Task Win Is Possible | - | Hunter Arrow Punish In Sec | - +| Finish Tasks Punish In Sec | - +| Enable Sabotages | - +| Time The Hunter Needs To Wait | - + # Source code You can use parts of the code but don't copy paste the whole thing. Make sure you give credits to the other developers, because some parts of the code are based on theirs. diff --git a/TheOtherRoles/Buttons.cs b/TheOtherRoles/Buttons.cs index 26ee68c..f584569 100644 --- a/TheOtherRoles/Buttons.cs +++ b/TheOtherRoles/Buttons.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Collections.Generic; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles { @@ -50,7 +51,13 @@ namespace TheOtherRoles public static CustomButton witchSpellButton; public static CustomButton ninjaButton; public static CustomButton mayorMeetingButton; + public static CustomButton thiefKillButton; + public static CustomButton trapperButton; public static CustomButton zoomOutButton; + private static CustomButton hunterLighterButton; + private static CustomButton hunterAdminTableButton; + private static CustomButton hunterArrowButton; + private static CustomButton huntedShieldButton; public static Dictionary> deputyHandcuffedButtons = null; public static PoolablePlayer morphTargetDisplay; @@ -61,6 +68,8 @@ namespace TheOtherRoles public static TMPro.TMP_Text pursuerButtonBlanksText; public static TMPro.TMP_Text hackerAdminTableChargesText; public static TMPro.TMP_Text hackerVitalsChargesText; + public static TMPro.TMP_Text trapperChargesText; + public static TMPro.TMP_Text huntedShieldCountText; public static void setCustomButtonCooldowns() { engineerRepairButton.MaxTimer = 0f; @@ -98,7 +107,13 @@ namespace TheOtherRoles trackerTrackCorpsesButton.MaxTimer = Tracker.corpsesTrackingCooldown; witchSpellButton.MaxTimer = Witch.cooldown; ninjaButton.MaxTimer = Ninja.cooldown; + thiefKillButton.MaxTimer = Thief.cooldown; mayorMeetingButton.MaxTimer = PlayerControl.GameOptions.EmergencyCooldown; + trapperButton.MaxTimer = Trapper.cooldown; + hunterLighterButton.MaxTimer = Hunter.lightCooldown; + hunterAdminTableButton.MaxTimer = Hunter.AdminCooldown; + hunterArrowButton.MaxTimer = Hunter.ArrowCooldown; + huntedShieldButton.MaxTimer = Hunted.shieldCooldown; timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration; hackerButton.EffectDuration = Hacker.duration; @@ -114,6 +129,9 @@ namespace TheOtherRoles trackerTrackCorpsesButton.EffectDuration = Tracker.corpsesTrackingDuration; witchSpellButton.EffectDuration = Witch.spellCastingDuration; securityGuardCamButton.EffectDuration = SecurityGuard.duration; + hunterLighterButton.EffectDuration = Hunter.lightDuration; + hunterArrowButton.EffectDuration = Hunter.ArrowDuration; + huntedShieldButton.EffectDuration = Hunted.shieldDuration; // 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; @@ -126,6 +144,13 @@ namespace TheOtherRoles SoundEffectsManager.stop("timemasterShield"); } + public static void resetHuntedRewindButton() { + huntedShieldButton.Timer = huntedShieldButton.MaxTimer; + huntedShieldButton.isEffectActive = false; + huntedShieldButton.actionButton.cooldownTimerText.color = Palette.EnabledColor; + SoundEffectsManager.stop("timemasterShield"); + } + private static void addReplacementHandcuffedButton(CustomButton button, Vector3? positionOffset = null, Func couldUse = null) { Vector3 positionOffsetValue = positionOffset ?? button.PositionOffset; // For non custom buttons, we can set these manually. @@ -292,7 +317,7 @@ namespace TheOtherRoles byte targetId = 0; if ((Sheriff.currentTarget.Data.Role.IsImpostor && (Sheriff.currentTarget != Mini.mini || Mini.isGrownUp())) || (Sheriff.spyCanDieToSheriff && Spy.spy == Sheriff.currentTarget) || - (Sheriff.canKillNeutrals && (Arsonist.arsonist == Sheriff.currentTarget || Jester.jester == Sheriff.currentTarget || Vulture.vulture == Sheriff.currentTarget || Lawyer.lawyer == Sheriff.currentTarget || Pursuer.pursuer == Sheriff.currentTarget)) || + (Sheriff.canKillNeutrals && Helpers.isNeutral(Sheriff.currentTarget)) || (Jackal.jackal == Sheriff.currentTarget || Sidekick.sidekick == Sheriff.currentTarget)) { targetId = Sheriff.currentTarget.PlayerId; } @@ -420,9 +445,10 @@ namespace TheOtherRoles () => { return Shifter.currentTarget && Shifter.futureShift == null && CachedPlayer.LocalPlayer.PlayerControl.CanMove; }, () => { }, Shifter.getButtonSprite(), - new Vector3(-1.8f, -0.06f, 0), + new Vector3(0, 0.73f, 0), __instance, - KeyCode.F + null, + true ); // Morphling morph @@ -711,7 +737,7 @@ namespace TheOtherRoles FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action((p) => { // Delayed action if (p == 1f) { // Perform kill if possible and reset bitten (regardless whether the kill was successful or not) - Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten, showAnimation: false); + Helpers.checkMurderAttemptAndKill(Vampire.vampire, Vampire.bitten, showAnimation: false); MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); writer.Write(byte.MaxValue); writer.Write(byte.MaxValue); @@ -772,7 +798,7 @@ namespace TheOtherRoles RPCProcedure.placeGarlic(buff); SoundEffectsManager.play("garlic"); }, - () => { return !Vampire.localPlacedGarlic && !CachedPlayer.LocalPlayer.Data.IsDead && Vampire.garlicsActive; }, + () => { return !Vampire.localPlacedGarlic && !CachedPlayer.LocalPlayer.Data.IsDead && Vampire.garlicsActive && !HideNSeek.isHideNSeekGM; }, () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove && !Vampire.localPlacedGarlic; }, () => { }, Vampire.getGarlicButtonSprite(), @@ -867,7 +893,7 @@ namespace TheOtherRoles // Jackal Kill jackalKillButton = new CustomButton( () => { - if (Helpers.checkMuderAttemptAndKill(Jackal.jackal, Jackal.currentTarget) == MurderAttemptResult.SuppressKill) return; + if (Helpers.checkMurderAttemptAndKill(Jackal.jackal, Jackal.currentTarget) == MurderAttemptResult.SuppressKill) return; jackalKillButton.Timer = jackalKillButton.MaxTimer; Jackal.currentTarget = null; @@ -884,7 +910,7 @@ namespace TheOtherRoles // Sidekick Kill sidekickKillButton = new CustomButton( () => { - if (Helpers.checkMuderAttemptAndKill(Sidekick.sidekick, Sidekick.currentTarget) == MurderAttemptResult.SuppressKill) return; + if (Helpers.checkMurderAttemptAndKill(Sidekick.sidekick, Sidekick.currentTarget) == MurderAttemptResult.SuppressKill) return; sidekickKillButton.Timer = sidekickKillButton.MaxTimer; Sidekick.currentTarget = null; }, @@ -1040,7 +1066,7 @@ namespace TheOtherRoles warlockCurseButton.Timer = 1f; SoundEffectsManager.play("warlockCurse"); } else if (Warlock.curseVictim != null && Warlock.curseVictimTarget != null) { - MurderAttemptResult murder = Helpers.checkMuderAttemptAndKill(Warlock.warlock, Warlock.curseVictimTarget, showAnimation: false); + MurderAttemptResult murder = Helpers.checkMurderAttemptAndKill(Warlock.warlock, Warlock.curseVictimTarget, showAnimation: false); if (murder == MurderAttemptResult.SuppressKill) return; // If blanked or killed @@ -1547,7 +1573,7 @@ namespace TheOtherRoles AmongUsClient.Instance.FinishRpcImmediately(writer); mayorMeetingButton.Timer = 1f; }, - () => { return Mayor.mayor != null && Mayor.mayor == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead ;}, + () => { return Mayor.mayor != null && Mayor.mayor == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; }, () => { mayorMeetingButton.actionButton.OverrideText("Emergency ("+ Mayor.remoteMeetingsLeft + ")"); bool sabotageActive = false; @@ -1569,6 +1595,93 @@ namespace TheOtherRoles "Meeting" ); + // Trapper button + trapperButton = new CustomButton( + () => { + + + 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.SetTrap, Hazel.SendOption.Reliable); + writer.WriteBytesAndSize(buff); + writer.EndMessage(); + RPCProcedure.setTrap(buff); + + SoundEffectsManager.play("trapperTrap"); + trapperButton.Timer = trapperButton.MaxTimer; + }, + () => { return Trapper.trapper != null && Trapper.trapper == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; }, + () => { + if (trapperChargesText != null) trapperChargesText.text = $"{Trapper.charges} / {Trapper.maxCharges}"; + return CachedPlayer.LocalPlayer.PlayerControl.CanMove && Trapper.charges > 0; + }, + () => { trapperButton.Timer = trapperButton.MaxTimer; }, + Trapper.getButtonSprite(), + new Vector3(-1.8f, -0.06f, 0), + __instance, + KeyCode.F + ); + + thiefKillButton = new CustomButton( + () => { + PlayerControl thief = Thief.thief; + PlayerControl target = Thief.currentTarget; + var result = Helpers.checkMuderAttempt(thief, target); + if (result == MurderAttemptResult.BlankKill) { + thiefKillButton.Timer = thiefKillButton.MaxTimer; + return; + } + + if (Thief.suicideFlag) { + // Suicide + MessageWriter writer2 = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); + writer2.Write(thief.PlayerId); + writer2.Write(thief.PlayerId); + writer2.Write(0); + RPCProcedure.uncheckedMurderPlayer(thief.PlayerId, thief.PlayerId, 0); + AmongUsClient.Instance.FinishRpcImmediately(writer2); + Thief.thief.clearAllTasks(); + } + + // Steal role if survived. + if (!Thief.thief.Data.IsDead && result == MurderAttemptResult.PerformKill) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ThiefStealsRole, Hazel.SendOption.Reliable, -1); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.thiefStealsRole(target.PlayerId); + } + // Kill the victim (after becoming their role - so that no win is triggered for other teams) + if (result == MurderAttemptResult.PerformKill) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1); + writer.Write(thief.PlayerId); + writer.Write(target.PlayerId); + writer.Write(byte.MaxValue); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.uncheckedMurderPlayer(thief.PlayerId, target.PlayerId, byte.MaxValue); + } + + + + }, + () => { return Thief.thief != null && CachedPlayer.LocalPlayer.PlayerControl == Thief.thief && !CachedPlayer.LocalPlayer.Data.IsDead; }, + () => { return Thief.currentTarget != null && CachedPlayer.LocalPlayer.PlayerControl.CanMove; }, + () => { thiefKillButton.Timer = thiefKillButton.MaxTimer; }, + __instance.KillButton.graphic.sprite, + new Vector3(0, 1f, 0), + __instance, + KeyCode.Q + ); + + // Trapper Charges + trapperChargesText = GameObject.Instantiate(trapperButton.actionButton.cooldownTimerText, trapperButton.actionButton.cooldownTimerText.transform.parent); + trapperChargesText.text = ""; + trapperChargesText.enableWordWrapping = false; + trapperChargesText.transform.localScale = Vector3.one * 0.5f; + trapperChargesText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0); + zoomOutButton = new CustomButton( () => { Helpers.toggleZoom(); }, @@ -1586,6 +1699,138 @@ namespace TheOtherRoles ); zoomOutButton.Timer = 0f; + + hunterLighterButton = new CustomButton( + () => { + Hunter.lightActive.Add(CachedPlayer.LocalPlayer.PlayerId); + SoundEffectsManager.play("lighterLight"); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareTimer, Hazel.SendOption.Reliable, -1); + writer.Write(Hunter.lightPunish); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shareTimer(Hunter.lightPunish); + }, + () => { return HideNSeek.isHunter() && !CachedPlayer.LocalPlayer.Data.IsDead; }, + () => { return true; }, + () => { + hunterLighterButton.Timer = lighterButton.MaxTimer; + hunterLighterButton.isEffectActive = false; + hunterLighterButton.actionButton.graphic.color = Palette.EnabledColor; + }, + Lighter.getButtonSprite(), + new Vector3(-1.8f, -0.06f, 0), + __instance, + KeyCode.F, + true, + Hunter.lightDuration, + () => { + Hunter.lightActive.Remove(CachedPlayer.LocalPlayer.PlayerId); + hunterLighterButton.Timer = hunterLighterButton.MaxTimer; + SoundEffectsManager.play("lighterLight"); + } + ); + + hunterAdminTableButton = new CustomButton( + () => { + FastDestroyableSingleton.Instance.ShowMap((System.Action)(m => m.ShowCountOverlay())); + + CachedPlayer.LocalPlayer.NetTransform.Halt(); // Stop current movement + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareTimer, Hazel.SendOption.Reliable, -1); + writer.Write(Hunter.AdminPunish); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shareTimer(Hunter.AdminPunish); + }, + () => { return HideNSeek.isHunter() && !CachedPlayer.LocalPlayer.Data.IsDead; }, + () => { return true; }, + () => { + hunterAdminTableButton.Timer = hunterAdminTableButton.MaxTimer; + hunterAdminTableButton.isEffectActive = false; + hunterAdminTableButton.actionButton.cooldownTimerText.color = Palette.EnabledColor; + }, + Hacker.getAdminSprite(), + new Vector3(-2.7f, -0.06f, 0), + __instance, + KeyCode.G, + true, + Hunter.AdminDuration, + () => { + hunterAdminTableButton.Timer = hunterAdminTableButton.MaxTimer; + if (MapBehaviour.Instance && MapBehaviour.Instance.isActiveAndEnabled) MapBehaviour.Instance.Close(); + }, + false, + "ADMIN" + ); + + hunterArrowButton = new CustomButton( + () => { + Hunter.arrowActive = true; + SoundEffectsManager.play("trackerTrackPlayer"); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareTimer, Hazel.SendOption.Reliable, -1); + writer.Write(Hunter.ArrowPunish); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shareTimer(Hunter.ArrowPunish); + }, + () => { return HideNSeek.isHunter() && !CachedPlayer.LocalPlayer.Data.IsDead; }, + () => { return true; }, + () => { + hunterArrowButton.Timer = lighterButton.MaxTimer; + hunterArrowButton.isEffectActive = false; + hunterArrowButton.actionButton.graphic.color = Palette.EnabledColor; + }, + Hunter.getArrowSprite(), + new Vector3(-2.7f, 1f, 0), + __instance, + KeyCode.R, + true, + Hunter.ArrowDuration, + () => { + Hunter.arrowActive = false; + hunterArrowButton.Timer = hunterArrowButton.MaxTimer; + SoundEffectsManager.play("trackerTrackPlayer"); + } + ); + + huntedShieldButton = new CustomButton( + () => { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.HuntedShield, Hazel.SendOption.Reliable, -1); + writer.Write(CachedPlayer.LocalPlayer.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.huntedShield(CachedPlayer.LocalPlayer.PlayerId); + SoundEffectsManager.play("timemasterShield"); + + Hunted.shieldCount--; + }, + () => { return HideNSeek.isHunted() && !CachedPlayer.LocalPlayer.Data.IsDead; }, + () => { + if (huntedShieldCountText != null) huntedShieldCountText.text = $"{Hunted.shieldCount}"; + return CachedPlayer.LocalPlayer.PlayerControl.CanMove && Hunted.shieldCount > 0; + }, + () => { + huntedShieldButton.Timer = huntedShieldButton.MaxTimer; + huntedShieldButton.isEffectActive = false; + huntedShieldButton.actionButton.cooldownTimerText.color = Palette.EnabledColor; + }, + TimeMaster.getButtonSprite(), + new Vector3(-1.8f, -0.06f, 0), + __instance, + KeyCode.F, + true, + Hunted.shieldDuration, + () => { + huntedShieldButton.Timer = huntedShieldButton.MaxTimer; + SoundEffectsManager.stop("timemasterShield"); + + } + ); + + huntedShieldCountText = GameObject.Instantiate(huntedShieldButton.actionButton.cooldownTimerText, huntedShieldButton.actionButton.cooldownTimerText.transform.parent); + huntedShieldCountText.text = ""; + huntedShieldCountText.enableWordWrapping = false; + huntedShieldCountText.transform.localScale = Vector3.one * 0.5f; + huntedShieldCountText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0); + // Set the default (or settings from the previous game) timers / durations when spawning the buttons setCustomButtonCooldowns(); deputyHandcuffedButtons = null; diff --git a/TheOtherRoles/CustomGameModes/GuesserGM.cs b/TheOtherRoles/CustomGameModes/GuesserGM.cs new file mode 100644 index 0000000..cc0c8cb --- /dev/null +++ b/TheOtherRoles/CustomGameModes/GuesserGM.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace TheOtherRoles.CustomGameModes { + class GuesserGM { // Guesser Gamemode + public static List guessers = new List(); + public static Color color = new Color32(255, 255, 0, byte.MaxValue); + + public PlayerControl guesser = null; + public int shots = Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeNumberOfShots.getFloat()); + public GuesserGM(PlayerControl player) { + guesser = player; + guessers.Add(this); + } + + public static int remainingShots(byte playerId, bool shoot = false) { + + var g = guessers.FindLast(x => x.guesser.PlayerId == playerId); + if (g == null) return 0; + if (shoot) g.shots--; + return g.shots; + } + + public static void clear(byte playerId) { + var g = guessers.FindLast(x => x.guesser.PlayerId == playerId); + if (g == null) return; + g.guesser = null; + g.shots = Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeNumberOfShots.getFloat()); + + guessers.Remove(g); + } + + public static void clearAndReload() { + guessers = new List(); + } + + public static bool isGuesser(byte playerId) { + return guessers.FindAll(x => x.guesser.PlayerId == playerId).Count > 0; + } + } +} \ No newline at end of file diff --git a/TheOtherRoles/CustomGameModes/HideNSeekGM.cs b/TheOtherRoles/CustomGameModes/HideNSeekGM.cs new file mode 100644 index 0000000..0526c74 --- /dev/null +++ b/TheOtherRoles/CustomGameModes/HideNSeekGM.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using TheOtherRoles.Objects; +using TheOtherRoles.Players; +using UnityEngine; + +namespace TheOtherRoles.CustomGameModes { + public static class HideNSeek { // HideNSeek Gamemode + public static bool isHideNSeekGM = false; + public static TMPro.TMP_Text timerText = null; + public static Vent polusVent = null; + public static bool isWaitingTimer = true; + public static DateTime startTime = DateTime.UtcNow; + + public static float timer = 300f; + public static float hunterVision = 0.5f; + public static float huntedVision = 2f; + public static bool taskWinPossible = false; + public static float taskPunish = 10f; + public static int impNumber = 2; + public static bool canSabotage = false; + public static float killCooldown = 10f; + public static float hunterWaitingTime = 15f; + public static bool isHunter() { + return isHideNSeekGM && CachedPlayer.LocalPlayer != null && CachedPlayer.LocalPlayer.Data.Role.IsImpostor; + } + + public static List getHunters() { + List hunters = new List(CachedPlayer.AllPlayers); + hunters.RemoveAll(x => !x.Data.Role.IsImpostor); + return hunters; + } + + public static bool isHunted() { + return isHideNSeekGM && CachedPlayer.LocalPlayer != null && !CachedPlayer.LocalPlayer.Data.Role.IsImpostor; + } + + public static void clearAndReload() { + isHideNSeekGM = MapOptions.gameMode == CustomGamemodes.HideNSeek; + if (timerText != null) UnityEngine.Object.Destroy(timerText); + timerText = null; + if (polusVent != null) UnityEngine.Object.Destroy(polusVent); + polusVent = null; + isWaitingTimer = true; + startTime = DateTime.UtcNow; + + timer = CustomOptionHolder.hideNSeekTimer.getFloat() * 60; + hunterVision = CustomOptionHolder.hideNSeekHunterVision.getFloat(); + huntedVision = CustomOptionHolder.hideNSeekHuntedVision.getFloat(); + taskWinPossible = CustomOptionHolder.hideNSeekTaskWin.getBool(); + taskPunish = CustomOptionHolder.hideNSeekTaskPunish.getFloat(); + impNumber = Mathf.RoundToInt(CustomOptionHolder.hideNSeekHunterCount.getFloat()); + canSabotage = CustomOptionHolder.hideNSeekCanSabotage.getBool(); + killCooldown = CustomOptionHolder.hideNSeekKillCooldown.getFloat(); + hunterWaitingTime = CustomOptionHolder.hideNSeekHunterWaiting.getFloat(); + + Hunter.clearAndReload(); + Hunted.clearAndReload(); + } + } + + public static class Hunter { + public static List localArrows = new List(); + public static List lightActive = new List(); + public static bool arrowActive = false; + public static Dictionary playerKillCountMap = new Dictionary(); + + public static float lightCooldown = 30f; + public static float lightDuration = 5f; + public static float lightVision = 2f; + public static float lightPunish = 5f; + public static float AdminCooldown = 30f; + public static float AdminDuration = 5f; + public static float AdminPunish = 5f; + public static float ArrowCooldown = 30f; + public static float ArrowDuration = 5f; + public static float ArrowPunish = 5f; + private static Sprite buttonSprite; + + public static bool isLightActive (byte playerId) { + return lightActive.Contains(playerId); + } + + public static Sprite getArrowSprite() { + if (buttonSprite) return buttonSprite; + buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.HideNSeekArrowButton.png", 115f); + return buttonSprite; + } + + public static void clearAndReload() { + if (localArrows != null) { + foreach (Arrow arrow in localArrows) + if (arrow?.arrow != null) + UnityEngine.Object.Destroy(arrow.arrow); + } + localArrows = new List(); + lightActive = new List(); + arrowActive = false; + + lightCooldown = CustomOptionHolder.hunterLightCooldown.getFloat(); + lightDuration = CustomOptionHolder.hunterLightDuration.getFloat(); + lightVision = CustomOptionHolder.hunterLightVision.getFloat(); + lightPunish = CustomOptionHolder.hunterLightPunish.getFloat(); + AdminCooldown = CustomOptionHolder.hunterAdminCooldown.getFloat(); + AdminDuration = CustomOptionHolder.hunterAdminDuration.getFloat(); + AdminPunish = CustomOptionHolder.hunterAdminPunish.getFloat(); + ArrowCooldown = CustomOptionHolder.hunterArrowCooldown.getFloat(); + ArrowDuration = CustomOptionHolder.hunterArrowDuration.getFloat(); + ArrowPunish = CustomOptionHolder.hunterArrowPunish.getFloat(); + } + } + + public static class Hunted { + public static List timeshieldActive = new List(); + public static int shieldCount = 3; + + public static float shieldCooldown = 30f; + public static float shieldDuration = 5f; + public static float shieldRewindTime = 3f; + public static bool taskPunish = false; + public static void clearAndReload() { + timeshieldActive = new List(); + taskPunish = false; + + shieldCount = Mathf.RoundToInt(CustomOptionHolder.huntedShieldNumber.getFloat()); + shieldCooldown = CustomOptionHolder.huntedShieldCooldown.getFloat(); + shieldDuration = CustomOptionHolder.huntedShieldDuration.getFloat(); + shieldRewindTime = CustomOptionHolder.huntedShieldRewindTime.getFloat(); + } + } +} \ No newline at end of file diff --git a/TheOtherRoles/CustomOptionHolder.cs b/TheOtherRoles/CustomOptionHolder.cs index 5cb322f..bff9f74 100644 --- a/TheOtherRoles/CustomOptionHolder.cs +++ b/TheOtherRoles/CustomOptionHolder.cs @@ -43,7 +43,6 @@ namespace TheOtherRoles { public static CustomOption guesserIsImpGuesserRate; public static CustomOption guesserNumberOfShots; public static CustomOption guesserHasMultipleShotsPerMeeting; - public static CustomOption guesserShowInfoInGhostChat; public static CustomOption guesserKillsThroughShield; public static CustomOption guesserEvilCanKillSpy; public static CustomOption guesserSpawnBothRate; @@ -91,9 +90,6 @@ namespace TheOtherRoles { public static CustomOption ninjaTraceColorTime; public static CustomOption ninjaInvisibleDuration; - public static CustomOption shifterSpawnRate; - public static CustomOption shifterShiftsModifiers; - public static CustomOption mayorSpawnRate; public static CustomOption mayorCanSeeVoteColors; public static CustomOption mayorTasksNeededToSeeVoteColors; @@ -219,12 +215,30 @@ namespace TheOtherRoles { public static CustomOption mediumOneTimeUse; public static CustomOption lawyerSpawnRate; + public static CustomOption lawyerIsProsecutorChance; public static CustomOption lawyerTargetCanBeJester; public static CustomOption lawyerVision; public static CustomOption lawyerKnowsRole; + public static CustomOption lawyerCanCallEmergency; public static CustomOption pursuerCooldown; public static CustomOption pursuerBlanksNumber; + public static CustomOption thiefSpawnRate; + public static CustomOption thiefCooldown; + public static CustomOption thiefHasImpVision; + public static CustomOption thiefCanUseVents; + public static CustomOption thiefCanKillSheriff; + + + public static CustomOption trapperSpawnRate; + public static CustomOption trapperCooldown; + public static CustomOption trapperMaxCharges; + public static CustomOption trapperRechargeTasksNumber; + public static CustomOption trapperTrapNeededTriggerToReveal; + public static CustomOption trapperAnonymousMap; + public static CustomOption trapperInfoType; + public static CustomOption trapperTrapDuration; + public static CustomOption modifiersAreHidden; public static CustomOption modifierBait; @@ -253,6 +267,7 @@ namespace TheOtherRoles { public static CustomOption modifierMini; public static CustomOption modifierMiniGrowingUpDuration; + public static CustomOption modifierMiniGrowingUpInMeeting; public static CustomOption modifierVip; public static CustomOption modifierVipQuantity; @@ -261,7 +276,15 @@ namespace TheOtherRoles { public static CustomOption modifierInvert; public static CustomOption modifierInvertQuantity; public static CustomOption modifierInvertDuration; - + + public static CustomOption modifierChameleon; + public static CustomOption modifierChameleonQuantity; + public static CustomOption modifierChameleonHoldDuration; + public static CustomOption modifierChameleonFadeDuration; + public static CustomOption modifierChameleonMinVisibility; + + public static CustomOption modifierShifter; + public static CustomOption maxNumberOfMeetings; public static CustomOption blockSkippingInEmergencyMeetings; public static CustomOption noVoteIsSelfVote; @@ -276,6 +299,48 @@ namespace TheOtherRoles { public static CustomOption dynamicMapEnableAirShip; public static CustomOption dynamicMapEnableSubmerged; + //Guesser Gamemode + public static CustomOption guesserGamemodeCrewNumber; + public static CustomOption guesserGamemodeNeutralNumber; + public static CustomOption guesserGamemodeImpNumber; + public static CustomOption guesserForceJackalGuesser; + public static CustomOption guesserGamemodeHaveModifier; + public static CustomOption guesserGamemodeNumberOfShots; + public static CustomOption guesserGamemodeHasMultipleShotsPerMeeting; + public static CustomOption guesserGamemodeKillsThroughShield; + public static CustomOption guesserGamemodeEvilCanKillSpy; + public static CustomOption guesserGamemodeCantGuessSnitchIfTaksDone; + + // Hide N Seek Gamemode + public static CustomOption hideNSeekHunterCount; + public static CustomOption hideNSeekKillCooldown; + public static CustomOption hideNSeekHunterVision; + public static CustomOption hideNSeekHuntedVision; + public static CustomOption hideNSeekTimer; + public static CustomOption hideNSeekCommonTasks; + public static CustomOption hideNSeekShortTasks; + public static CustomOption hideNSeekLongTasks; + public static CustomOption hideNSeekTaskWin; + public static CustomOption hideNSeekTaskPunish; + public static CustomOption hideNSeekCanSabotage; + public static CustomOption hideNSeekMap; + public static CustomOption hideNSeekHunterWaiting; + + public static CustomOption hunterLightCooldown; + public static CustomOption hunterLightDuration; + public static CustomOption hunterLightVision; + public static CustomOption hunterLightPunish; + public static CustomOption hunterAdminCooldown; + public static CustomOption hunterAdminDuration; + public static CustomOption hunterAdminPunish; + public static CustomOption hunterArrowCooldown; + public static CustomOption hunterArrowDuration; + public static CustomOption hunterArrowPunish; + + public static CustomOption huntedShieldCooldown; + public static CustomOption huntedShieldDuration; + public static CustomOption huntedShieldRewindTime; + public static CustomOption huntedShieldNumber; internal static Dictionary blockedRolePairings = new Dictionary(); @@ -300,8 +365,8 @@ namespace TheOtherRoles { crewmateRolesCountMax = CustomOption.Create(301, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 15f, 0f, 15f, 1f); 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, 3f, 1f); - impostorRolesCountMax = CustomOption.Create(305, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Impostor Roles"), 15f, 0f, 3f, 1f); + impostorRolesCountMin = CustomOption.Create(304, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Impostor Roles"), 15f, 0f, 15f, 1f); + impostorRolesCountMax = CustomOption.Create(305, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Impostor Roles"), 15f, 0f, 15f, 1f); 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); @@ -363,7 +428,6 @@ namespace TheOtherRoles { 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); guesserHasMultipleShotsPerMeeting = CustomOption.Create(313, Types.Neutral, "Guesser Can Shoot Multiple Times Per Meeting", false, guesserSpawnRate); - guesserShowInfoInGhostChat = CustomOption.Create(314, Types.Neutral, "Guesses Visible In Ghost Chat", true, guesserSpawnRate); guesserKillsThroughShield = CustomOption.Create(315, Types.Neutral, "Guesses Ignore The Medic Shield", true, guesserSpawnRate); guesserEvilCanKillSpy = CustomOption.Create(316, Types.Neutral, "Evil Guesser Can Guess The Spy", true, guesserSpawnRate); guesserSpawnBothRate = CustomOption.Create(317, Types.Neutral, "Both Guesser Spawn Rate", rates, guesserSpawnRate); @@ -396,15 +460,14 @@ namespace TheOtherRoles { vultureShowArrows = CustomOption.Create(344, Types.Neutral, "Show Arrows Pointing Towards The Corpses", true, vultureSpawnRate); lawyerSpawnRate = CustomOption.Create(350, Types.Neutral, cs(Lawyer.color, "Lawyer"), rates, null, true); + lawyerIsProsecutorChance = CustomOption.Create(358, Types.Neutral, "Chance That The Lawyer Is Prosecutor", rates, lawyerSpawnRate); + lawyerVision = CustomOption.Create(354, Types.Neutral, "Vision", 1f, 0.25f, 3f, 0.25f, lawyerSpawnRate); + lawyerKnowsRole = CustomOption.Create(355, Types.Neutral, "Lawyer/Prosecutor Knows Target Role", false, lawyerSpawnRate); + lawyerCanCallEmergency = CustomOption.Create(352, Types.Neutral, "Lawyer/Prosecutor Can Call Emergency Meeting", true, lawyerSpawnRate); lawyerTargetCanBeJester = CustomOption.Create(351, Types.Neutral, "Lawyer Target Can Be The Jester", false, lawyerSpawnRate); - lawyerVision = CustomOption.Create(354, Types.Neutral, "Lawyer Vision", 1f, 0.25f, 3f, 0.25f, lawyerSpawnRate); - lawyerKnowsRole = CustomOption.Create(355, Types.Neutral, "Lawyer Knows Target Role", false, lawyerSpawnRate); pursuerCooldown = CustomOption.Create(356, Types.Neutral, "Pursuer Blank Cooldown", 30f, 5f, 60f, 2.5f, lawyerSpawnRate); pursuerBlanksNumber = CustomOption.Create(357, Types.Neutral, "Pursuer Number Of Blanks", 5f, 1f, 20f, 1f, lawyerSpawnRate); - shifterSpawnRate = CustomOption.Create(70, Types.Crewmate, cs(Shifter.color, "Shifter"), rates, null, true); - shifterShiftsModifiers = CustomOption.Create(71, Types.Crewmate, "Shifter Shifts Modifiers", false, shifterSpawnRate); - mayorSpawnRate = CustomOption.Create(80, Types.Crewmate, cs(Mayor.color, "Mayor"), rates, null, true); mayorCanSeeVoteColors = CustomOption.Create(81, Types.Crewmate, "Mayor Can See Vote Colors", false, mayorSpawnRate); mayorTasksNeededToSeeVoteColors = CustomOption.Create(82, Types.Crewmate, "Completed Tasks Needed To See Vote Colors", 5f, 0f, 20f, 1f, mayorCanSeeVoteColors); @@ -511,7 +574,22 @@ namespace TheOtherRoles { 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); - // Modifier + 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); + thiefCanKillSheriff = CustomOption.Create(402, Types.Neutral, "Thief Can Kill Sheriff", true, thiefSpawnRate); + thiefHasImpVision = CustomOption.Create(403, Types.Neutral, "Thief Has Impostor Vision", true, thiefSpawnRate); + thiefCanUseVents = CustomOption.Create(404, Types.Neutral, "Thief Can Use Vents", true, thiefSpawnRate); + + trapperSpawnRate = CustomOption.Create(410, Types.Crewmate, cs(Trapper.color, "Trapper"), rates, null, true); + trapperCooldown = CustomOption.Create(420, Types.Crewmate, "Trapper Cooldown", 30f, 5f, 120f, 5f, trapperSpawnRate); + trapperMaxCharges = CustomOption.Create(440, Types.Crewmate, "Max Traps Charges", 5f, 1f, 15f, 1f, trapperSpawnRate); + trapperRechargeTasksNumber = CustomOption.Create(450, Types.Crewmate, "Number Of Tasks Needed For Recharging", 2f, 1f, 15f, 1f, trapperSpawnRate); + trapperTrapNeededTriggerToReveal = CustomOption.Create(451, Types.Crewmate, "Trap Needed Trigger To Reveal", 3f, 2f, 10f, 1f, trapperSpawnRate); + trapperAnonymousMap = CustomOption.Create(452, Types.Crewmate, "Show Anonymous Map", false, trapperSpawnRate); + trapperInfoType = CustomOption.Create(453, Types.Crewmate, "Trap Information Type", new string[] { "Role", "Good/Evil Role", "Name" }, trapperSpawnRate); + trapperTrapDuration = CustomOption.Create(454, Types.Crewmate, "Trap Duration", 5f, 1f, 15f, 1f, trapperSpawnRate); + + // Modifier (1000 - 1999) modifiersAreHidden = CustomOption.Create(1009, Types.Modifier, cs(Color.yellow, "Hide After Death Modifiers"), true, null, true); modifierBloody = CustomOption.Create(1000, Types.Modifier, cs(Color.yellow, "Bloody"), rates, null, true); @@ -540,6 +618,7 @@ namespace TheOtherRoles { modifierMini = CustomOption.Create(1061, Types.Modifier, cs(Color.yellow, "Mini"), rates, null, true); modifierMiniGrowingUpDuration = CustomOption.Create(1062, Types.Modifier, "Mini Growing Up Duration", 400f, 100f, 1500f, 100f, modifierMini); + modifierMiniGrowingUpInMeeting = CustomOption.Create(1063, Types.Modifier, "Mini Grows Up In Meeting", true, modifierMini); modifierVip = CustomOption.Create(1070, Types.Modifier, cs(Color.yellow, "VIP"), rates, null, true); modifierVipQuantity = CustomOption.Create(1071, Types.Modifier, cs(Color.yellow, "VIP Quantity"), ratesModifier, modifierVip); @@ -549,6 +628,57 @@ namespace TheOtherRoles { modifierInvertQuantity = CustomOption.Create(1081, Types.Modifier, cs(Color.yellow, "Modifier Quantity"), ratesModifier, modifierInvert); modifierInvertDuration = CustomOption.Create(1082, Types.Modifier, "Number Of Meetings Inverted", 3f, 1f, 15f, 1f, modifierInvert); + modifierChameleon = CustomOption.Create(1090, Types.Modifier, cs(Color.yellow, "Chameleon"), rates, null, true); + modifierChameleonQuantity = CustomOption.Create(1091, Types.Modifier, cs(Color.yellow, "Chameleon Quantity"), ratesModifier, modifierChameleon); + modifierChameleonHoldDuration = CustomOption.Create(1092, Types.Modifier, "Time Until Fading Starts", 3f, 1f, 10f, 0.5f, modifierChameleon); + modifierChameleonFadeDuration = CustomOption.Create(1093, Types.Modifier, "Fade Duration", 1f, 0.25f, 10f, 0.25f, modifierChameleon); + modifierChameleonMinVisibility = CustomOption.Create(1094, Types.Modifier, "Minimum Visibility", new string[] { "0%", "10%", "20%", "30%", "40%", "50%" }, modifierChameleon); + + modifierShifter = CustomOption.Create(1100, Types.Modifier, cs(Color.yellow, "Shifter"), rates, null, true); + + // Guesser Gamemode (2000 - 2999) + guesserGamemodeCrewNumber = CustomOption.Create(2001, Types.Guesser, cs(Guesser.color, "Number of Crew Guessers"), 15f, 1f, 15f, 1f, null, true); + guesserGamemodeNeutralNumber = CustomOption.Create(2002, Types.Guesser, cs(Guesser.color, "Number of Neutral Guessers"), 15f, 1f, 15f, 1f, null, true); + guesserGamemodeImpNumber = CustomOption.Create(2003, Types.Guesser, cs(Guesser.color, "Number of Impostor Guessers"), 15f, 1f, 15f, 1f, null, true); + guesserForceJackalGuesser = CustomOption.Create(2007, Types.Guesser, "Force Jackal Guesser", false, null, true); + guesserGamemodeHaveModifier = CustomOption.Create(2004, Types.Guesser, "Guessers Can Have A Modifier", true, null); + guesserGamemodeNumberOfShots = CustomOption.Create(2005, Types.Guesser, "Guesser Number Of Shots", 3f, 1f, 15f, 1f, null); + guesserGamemodeHasMultipleShotsPerMeeting = CustomOption.Create(2006, Types.Guesser, "Guesser Can Shoot Multiple Times Per Meeting", false, null); + guesserGamemodeKillsThroughShield = CustomOption.Create(2008, Types.Guesser, "Guesses Ignore The Medic Shield", true, null); + guesserGamemodeEvilCanKillSpy = CustomOption.Create(2009, Types.Guesser, "Evil Guesser Can Guess The Spy", true, null); + guesserGamemodeCantGuessSnitchIfTaksDone = CustomOption.Create(2010, Types.Guesser, "Guesser Can't Guess Snitch When Tasks Completed", true, null); + + // Hide N Seek Gamemode (3000 - 3999) + hideNSeekMap = CustomOption.Create(3020, Types.HideNSeekMain, cs(Color.yellow, "Map"), new string[] { "The Skeld", "Mira", "Polus", "Airship", "Submerged" }, null, true); + hideNSeekHunterCount = CustomOption.Create(3000, Types.HideNSeekMain, cs(Color.yellow, "Number Of Hunters"), 1f, 1f, 3f, 1f); + hideNSeekKillCooldown = CustomOption.Create(3021, Types.HideNSeekMain, cs(Color.yellow, "Kill Cooldown"), 10f, 2.5f, 60f, 2.5f); + hideNSeekHunterVision = CustomOption.Create(3001, Types.HideNSeekMain, cs(Color.yellow, "Hunter Vision"), 0.5f, 0.25f, 2f, 0.25f); + hideNSeekHuntedVision = CustomOption.Create(3002, Types.HideNSeekMain, cs(Color.yellow, "Hunted Vision"), 2f, 0.25f, 5f, 0.25f); + hideNSeekCommonTasks = CustomOption.Create(3023, Types.HideNSeekMain, cs(Color.yellow, "Common Tasks"), 1f, 0f, 4f, 1f); + hideNSeekShortTasks = CustomOption.Create(3024, Types.HideNSeekMain, cs(Color.yellow, "Short Tasks"), 3f, 1f, 23f, 1f); + hideNSeekLongTasks = CustomOption.Create(3025, Types.HideNSeekMain, cs(Color.yellow, "Long Tasks"), 3f, 0f, 15f, 1f); + hideNSeekTimer = CustomOption.Create(3003, Types.HideNSeekMain, cs(Color.yellow, "Timer In Min"), 5f, 1f, 30f, 1f); + hideNSeekTaskWin = CustomOption.Create(3004, Types.HideNSeekMain, cs(Color.yellow, "Task Win Is Possible"), false); + hideNSeekTaskPunish = CustomOption.Create(3017, Types.HideNSeekMain, cs(Color.yellow, "Finish Tasks Punish In Sec"), 10f, 0f, 30f, 1f); + hideNSeekCanSabotage = CustomOption.Create(3019, Types.HideNSeekMain, cs(Color.yellow, "Enable Sabotages"), false); + hideNSeekHunterWaiting = CustomOption.Create(3022, Types.HideNSeekMain, cs(Color.yellow, "Time The Hunter Needs To Wait"), 15f, 2.5f, 60f, 2.5f); + + hunterLightCooldown = CustomOption.Create(3005, Types.HideNSeekRoles, cs(Color.red, "Hunter Light Cooldown"), 30f, 5f, 60f, 1f, null, true); + hunterLightDuration = CustomOption.Create(3006, Types.HideNSeekRoles, cs(Color.red, "Hunter Light Duration"), 5f, 1f, 60f, 1f); + hunterLightVision = CustomOption.Create(3007, Types.HideNSeekRoles, cs(Color.red, "Hunter Light Vision"), 3f, 1f, 5f, 0.25f); + hunterLightPunish = CustomOption.Create(3008, Types.HideNSeekRoles, cs(Color.red, "Hunter Light Punish In Sec"), 5f, 0f, 30f, 1f); + hunterAdminCooldown = CustomOption.Create(3009, Types.HideNSeekRoles, cs(Color.red, "Hunter Admin Cooldown"), 30f, 5f, 60f, 1f); + hunterAdminDuration = CustomOption.Create(3010, Types.HideNSeekRoles, cs(Color.red, "Hunter Admin Duration"), 5f, 1f, 60f, 1f); + hunterAdminPunish = CustomOption.Create(3011, Types.HideNSeekRoles, cs(Color.red, "Hunter Admin Punish In Sec"), 5f, 0f, 30f, 1f); + hunterArrowCooldown = CustomOption.Create(3012, Types.HideNSeekRoles, cs(Color.red, "Hunter Arrow Cooldown"), 30f, 5f, 60f, 1f); + hunterArrowDuration = CustomOption.Create(3013, Types.HideNSeekRoles, cs(Color.red, "Hunter Arrow Duration"), 5f, 0f, 60f, 1f); + hunterArrowPunish = CustomOption.Create(3014, Types.HideNSeekRoles, cs(Color.red, "Hunter Arrow Punish In Sec"), 5f, 0f, 30f, 1f); + + huntedShieldCooldown = CustomOption.Create(3015, Types.HideNSeekRoles, cs(Color.gray, "Hunted Shield Cooldown"), 30f, 5f, 60f, 1f, null, true); + huntedShieldDuration = CustomOption.Create(3016, Types.HideNSeekRoles, cs(Color.gray, "Hunted Shield Duration"), 5f, 1f, 60f, 1f); + huntedShieldRewindTime = CustomOption.Create(3018, Types.HideNSeekRoles, cs(Color.gray, "Hunted Rewind Time"), 3f, 1f, 10f, 1f); + huntedShieldNumber = CustomOption.Create(3026, Types.HideNSeekRoles, cs(Color.gray, "Hunted Shield Number"), 3f, 1f, 15f, 1f); + // Other options maxNumberOfMeetings = CustomOption.Create(3, Types.General, "Number Of Meetings (excluding Mayor meeting)", 10, 0, 15, 1, null, true); blockSkippingInEmergencyMeetings = CustomOption.Create(4, Types.General, "Block Skipping In Emergency Meetings", false); @@ -558,12 +688,11 @@ namespace TheOtherRoles { shieldFirstKill = CustomOption.Create(8, Types.General, "Shield Last Game First Kill", false); dynamicMap = CustomOption.Create(500, Types.General, "Play On A Random Map", false, null, false); - dynamicMapEnableSkeld = CustomOption.Create(501, Types.General, "Enable Skeld Rotation", true, dynamicMap, false); - dynamicMapEnableMira = CustomOption.Create(502, Types.General, "Enable Mira Rotation", true, dynamicMap, false); - dynamicMapEnablePolus = CustomOption.Create(503, Types.General, "Enable Polus Rotation", true, dynamicMap, false); - dynamicMapEnableAirShip = CustomOption.Create(504, Types.General, "Enable Airship Rotation", true, dynamicMap, false); - - dynamicMapEnableSubmerged = CustomOption.Create(505, Types.General, "Enable Submerged Rotation", true, dynamicMap, false); + 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); blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock}); blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire}); diff --git a/TheOtherRoles/Helpers.cs b/TheOtherRoles/Helpers.cs index b7d7fc0..b01d8f3 100644 --- a/TheOtherRoles/Helpers.cs +++ b/TheOtherRoles/Helpers.cs @@ -10,13 +10,23 @@ using HarmonyLib; using Hazel; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using System.Threading.Tasks; +using System.Net; +using System.Globalization; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles { public enum MurderAttemptResult { PerformKill, SuppressKill, - BlankKill + BlankKill, + } + + public enum CustomGamemodes { + Classic, + Guesser, + HideNSeek } public static class Helpers { @@ -113,7 +123,7 @@ namespace TheOtherRoles { public static void handleVampireBiteOnBodyReport() { // Murder the bitten player and reset bitten (regardless whether the kill was successful or not) - Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten, true, false); + Helpers.checkMurderAttemptAndKill(Vampire.vampire, Vampire.bitten, true, false); MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1); writer.Write(byte.MaxValue); writer.Write(byte.MaxValue); @@ -184,11 +194,11 @@ namespace TheOtherRoles { } public static bool hasFakeTasks(this PlayerControl player) { - return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist || player == Vulture.vulture || Jackal.formerJackals.Contains(player)); + return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist || player == Vulture.vulture || Jackal.formerJackals.Any(x => x == player)); } public static bool canBeErased(this PlayerControl player) { - return (player != Jackal.jackal && player != Sidekick.sidekick && !Jackal.formerJackals.Contains(player)); + return (player != Jackal.jackal && player != Sidekick.sidekick && !Jackal.formerJackals.Any(x => x == player)); } public static void clearAllTasks(this PlayerControl player) { @@ -290,6 +300,8 @@ namespace TheOtherRoles { target.cosmetics.currentPet.Source = target; target.cosmetics.currentPet.Visible = target.Visible; target.SetPlayerMaterialColors(target.cosmetics.currentPet.rend); + + Chameleon.update(); // so that morphling and camo wont make the chameleons visible } public static void showFlash(Color color, float duration=1f) { @@ -322,6 +334,8 @@ namespace TheOtherRoles { roleCouldUse = true; else if (Vulture.canUseVents && Vulture.vulture != null && Vulture.vulture == player) roleCouldUse = true; + else if (Thief.canUseVents && Thief.thief != null && Thief.thief == player) + roleCouldUse = true; else if (player.Data?.Role != null && player.Data.Role.CanVent) { if (Janitor.janitor != null && Janitor.janitor == CachedPlayer.LocalPlayer.PlayerControl) roleCouldUse = false; @@ -334,6 +348,8 @@ namespace TheOtherRoles { } public static MurderAttemptResult checkMuderAttempt(PlayerControl killer, PlayerControl target, bool blockRewind = 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 @@ -376,10 +392,27 @@ namespace TheOtherRoles { } return MurderAttemptResult.SuppressKill; } + + // Thief if hit crew only kill if setting says so, but also kill the thief. + else if (killer == Thief.thief && !target.Data.Role.IsImpostor && !new List {RoleInfo.jackal, Thief.canKillSheriff ? RoleInfo.sheriff : null, RoleInfo.sidekick }.Contains(targetRole)) { + Thief.suicideFlag = true; + return MurderAttemptResult.SuppressKill; + } + + // Block hunted with time shield kill + else if (Hunted.timeshieldActive.Contains(target.PlayerId)) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(killer.NetId, (byte)CustomRPC.HuntedRewindTime, Hazel.SendOption.Reliable, -1); + writer.Write(target.PlayerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.huntedRewindTime(target.PlayerId); + + return MurderAttemptResult.SuppressKill; + } + return MurderAttemptResult.PerformKill; } - public static MurderAttemptResult checkMuderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false, bool showAnimation = true) { + public static MurderAttemptResult checkMurderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false, bool showAnimation = true) { // 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 @@ -419,10 +452,16 @@ namespace TheOtherRoles { return team; } + public static bool isNeutral(PlayerControl player) { + RoleInfo roleInfo = RoleInfo.getRoleInfoForPlayer(player, false).FirstOrDefault(); + if (roleInfo != null) + return roleInfo.isNeutral; + return false; + } + public static bool zoomOutStatus = false; public static void toggleZoom(bool reset=false) { - TheOtherRolesPlugin.Logger.LogMessage(Camera.main.orthographicSize); float orthographicSize = reset || zoomOutStatus ? 3f : 12f; zoomOutStatus = !zoomOutStatus && !reset; @@ -435,6 +474,37 @@ namespace TheOtherRoles { HudManagerStartPatch.zoomOutButton.PositionOffset = zoomOutStatus ? new Vector3(0f, 3f, 0) : new Vector3(0.4f, 2.8f, 0); ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height); // This will move button positions to the correct position. } + + public static void checkBeta() { + if (TheOtherRolesPlugin.betaDays > 0) { + var compileTime = new DateTime(Builtin.CompileTime, DateTimeKind.Utc); // This may show as an error, but it is not, compilation will work! + DateTime now; + // Get time from the internet, so no-one can cheat it. + try { + using (var response = + WebRequest.Create("http://www.google.com").GetResponse()) + now = DateTime.ParseExact(response.Headers["date"], "ddd, dd MMM yyyy HH:mm:ss 'GMT'", CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AssumeUniversal); + } catch (WebException e) { + TheOtherRolesPlugin.Logger.LogMessage($"{e}"); + now = DateTime.Now; //In case something goes wrong. + } + if ((now - compileTime).TotalDays > TheOtherRolesPlugin.betaDays) { + TheOtherRolesPlugin.Logger.LogMessage($"Beta expired!"); + BepInExUpdater.MessageBox(IntPtr.Zero, "BETA is expired. You cannot play this version anymore.", "The Other Roles Beta", 1); + Application.Quit(); + + } else TheOtherRolesPlugin.Logger.LogMessage($"Beta will remain runnable for {(DateTime.Now - compileTime).TotalDays - TheOtherRolesPlugin.betaDays} days!"); + } + } + + public static bool hasImpVision(GameData.PlayerInfo player) { + return player.Role.IsImpostor + || ((Jackal.jackal != null && Jackal.jackal.PlayerId == player.PlayerId || Jackal.formerJackals.Any(x => x.PlayerId == player.PlayerId)) && Jackal.hasImpostorVision) + || (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == player.PlayerId && Sidekick.hasImpostorVision) + || (Spy.spy != null && Spy.spy.PlayerId == player.PlayerId && Spy.hasImpostorVision) + || (Jester.jester != null && Jester.jester.PlayerId == player.PlayerId && Jester.hasImpostorVision) + || (Thief.thief != null && Thief.thief.PlayerId == player.PlayerId && Thief.hasImpostorVision); + } public static object TryCast(this Il2CppObjectBase self, Type type) { diff --git a/TheOtherRoles/Main.cs b/TheOtherRoles/Main.cs index df96276..fb51c4c 100644 --- a/TheOtherRoles/Main.cs +++ b/TheOtherRoles/Main.cs @@ -1,10 +1,12 @@ -global using UnhollowerBaseLib; -global using UnhollowerBaseLib.Attributes; -global using UnhollowerRuntimeLib; +global using Il2CppInterop.Runtime; +global using Il2CppInterop.Runtime.Attributes; +global using Il2CppInterop.Runtime.InteropTypes; +global using Il2CppInterop.Runtime.InteropTypes.Arrays; +global using Il2CppInterop.Runtime.Injection; using BepInEx; using BepInEx.Configuration; -using BepInEx.IL2CPP; +using BepInEx.Unity.IL2CPP; using HarmonyLib; using Hazel; using System.Collections.Generic; @@ -14,16 +16,23 @@ using UnityEngine; using TheOtherRoles.Modules; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using Reactor; +using Il2CppSystem.Security.Cryptography; +using Il2CppSystem.Text; +using Reactor.Networking.Attributes; +using AmongUs.Data; namespace TheOtherRoles { [BepInPlugin(Id, "The Other Roles", VersionString)] [BepInDependency(SubmergedCompatibility.SUBMERGED_GUID, BepInDependency.DependencyFlags.SoftDependency)] [BepInProcess("Among Us.exe")] + [ReactorModFlags(Reactor.Networking.ModFlags.RequireOnAllClients)] public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "4.1.7"; + public const string VersionString = "4.2.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; @@ -33,7 +42,7 @@ namespace TheOtherRoles public static int optionsPage = 2; - public static ConfigEntry DebugMode { get; private set; } + public static ConfigEntry DebugMode { get; private set; } public static ConfigEntry GhostsSeeTasks { get; set; } public static ConfigEntry GhostsSeeRoles { get; set; } public static ConfigEntry GhostsSeeModifier { get; set; } @@ -55,8 +64,6 @@ namespace TheOtherRoles public static void UpdateRegions() { ServerManager serverManager = FastDestroyableSingleton.Instance; var regions = new IRegionInfo[] { - new DnsRegionInfo("mods.hopto.org", "Modded NA (MNA)", StringNames.NoTranslation, "mods.hopto.org", 443, false).CastFast(), - new DnsRegionInfo("au-eu.duikbo.at", "Modded EU (MEU)", StringNames.NoTranslation, "au-eu.duikbo.at", 22023, false).CastFast(), new DnsRegionInfo(Ip.Value, "Custom", StringNames.NoTranslation, Ip.Value, Port.Value, false).CastFast() }; @@ -83,7 +90,9 @@ namespace TheOtherRoles Logger = Log; Instance = this; - DebugMode = Config.Bind("Custom", "Enable Debug Mode", false); + 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); GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true); GhostsSeeModifier = Config.Bind("Custom", "Ghosts See Modifier", true); @@ -101,15 +110,11 @@ namespace TheOtherRoles UpdateRegions(); - GameOptionsData.RecommendedImpostors = GameOptionsData.MaxImpostors = Enumerable.Repeat(3, 16).ToArray(); // Max Imp = Recommended Imp = 3 - GameOptionsData.MinPlayers = Enumerable.Repeat(4, 15).ToArray(); // Min Players = 4 - - DebugMode = Config.Bind("Custom", "Enable Debug Mode", false); + DebugMode = Config.Bind("Custom", "Enable Debug Mode", "false"); Harmony.PatchAll(); CustomOptionHolder.Load(); CustomColors.Load(); - Patches.FreeNamePatch.Initialize(); if (BepInExUpdater.UpdateRequired) { @@ -139,8 +144,7 @@ namespace TheOtherRoles public static class ChatControllerAwakePatch { private static void Prefix() { if (!EOSManager.Instance.isKWSMinor) { - SaveManager.chatModeType = 1; - SaveManager.isGuest = false; + DataManager.Settings.Multiplayer.ChatMode = InnerNet.QuickChatModes.FreeChatOrQuickChat; } } } @@ -149,12 +153,22 @@ namespace TheOtherRoles [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] public static class DebugManager { + private static readonly string passwordHash = "d1f51dfdfd8d38027fd2ca9dfeb299399b5bdee58e6c0b3b5e9a45cd4e502848"; private static readonly System.Random random = new System.Random((int)DateTime.Now.Ticks); private static List bots = new List(); public static void Postfix(KeyboardJoystick __instance) { - if (!TheOtherRolesPlugin.DebugMode.Value) return; + // Check if debug mode is active. + StringBuilder builder = new StringBuilder(); + SHA256 sha = SHA256Managed.Create(); + Byte[] hashed = sha.ComputeHash(Encoding.UTF8.GetBytes(TheOtherRolesPlugin.DebugMode.Value)); + foreach (var b in hashed) { + builder.Append(b.ToString("x2")); + } + string enteredHash = builder.ToString(); + if (enteredHash != passwordHash) return; + // Spawn dummys if (Input.GetKeyDown(KeyCode.F)) { diff --git a/TheOtherRoles/MapOptions.cs b/TheOtherRoles/MapOptions.cs index 2ecb05d..aff6781 100644 --- a/TheOtherRoles/MapOptions.cs +++ b/TheOtherRoles/MapOptions.cs @@ -18,6 +18,7 @@ namespace TheOtherRoles{ public static bool enableSoundEffects = true; public static bool enableHorseMode = false; public static bool shieldFirstKill = false; + public static CustomGamemodes gameMode = CustomGamemodes.Classic; // Updating values public static int meetingsCount = 0; diff --git a/TheOtherRoles/Modules/BepInExUpdater.cs b/TheOtherRoles/Modules/BepInExUpdater.cs index a68ba5c..be69e5f 100644 --- a/TheOtherRoles/Modules/BepInExUpdater.cs +++ b/TheOtherRoles/Modules/BepInExUpdater.cs @@ -9,8 +9,8 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Threading.Tasks; using BepInEx; -using BepInEx.IL2CPP; -using BepInEx.IL2CPP.Utils; +using BepInEx.Unity.IL2CPP; +using BepInEx.Unity.IL2CPP.Utils; using HarmonyLib; using UnityEngine; using UnityEngine.Networking; @@ -19,14 +19,16 @@ namespace TheOtherRoles.Modules; public class BepInExUpdater : MonoBehaviour { - public const string MinimumBepInExVersion = "6.0.0.559"; - public const string BepInExDownloadURL = "https://builds.bepinex.dev/projects/bepinex_be/559/BepInEx_UnityIL2CPP_x86_fba4461_6.0.0-be.559.zip"; - public static bool UpdateRequired => typeof(IL2CPPChainloader).Assembly.GetName().Version < Version.Parse(MinimumBepInExVersion); + public const string RequiredBepInExVersion = "6.0.0-be.662+3ad398a160d0d138a2d012978e6709e20589cf82"; + public const string BepInExDownloadURL = "https://builds.bepinex.dev/projects/bepinex_be/662/BepInEx-Unity.IL2CPP-win-x86-6.0.0-be.662%2B3ad398a.zip"; + public static bool UpdateRequired => Paths.BepInExVersion.ToString() != RequiredBepInExVersion; public void Awake() { TheOtherRolesPlugin.Logger.LogMessage("BepInEx Update Required..."); + TheOtherRolesPlugin.Logger.LogMessage($"{Paths.BepInExVersion}, {RequiredBepInExVersion} "); this.StartCoroutine(CoUpdate()); + } [HideFromIl2Cpp] diff --git a/TheOtherRoles/Modules/CustomColors.cs b/TheOtherRoles/Modules/CustomColors.cs index 1c21486..5dd1da2 100644 --- a/TheOtherRoles/Modules/CustomColors.cs +++ b/TheOtherRoles/Modules/CustomColors.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; using HarmonyLib; +using AmongUs.Data.Legacy; using TheOtherRoles.Utilities; namespace TheOtherRoles.Modules { @@ -167,16 +168,16 @@ namespace TheOtherRoles.Modules { } } } - [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.LoadPlayerPrefs))] + [HarmonyPatch(typeof(LegacySaveManager), nameof(LegacySaveManager.LoadPlayerPrefs))] private static class LoadPlayerPrefsPatch { // Fix Potential issues with broken colors private static bool needsPatch = false; public static void Prefix([HarmonyArgument(0)] bool overrideLoad) { - if (!SaveManager.loaded || overrideLoad) + if (!LegacySaveManager.loaded || overrideLoad) needsPatch = true; } public static void Postfix() { if (!needsPatch) return; - SaveManager.colorConfig %= CustomColors.pickableColors; + LegacySaveManager.colorConfig %= CustomColors.pickableColors; needsPatch = false; } } diff --git a/TheOtherRoles/Modules/CustomHats.cs b/TheOtherRoles/Modules/CustomHats.cs index 2a950b0..dfa5c04 100644 --- a/TheOtherRoles/Modules/CustomHats.cs +++ b/TheOtherRoles/Modules/CustomHats.cs @@ -11,6 +11,8 @@ using System.Security.Cryptography; using Newtonsoft.Json.Linq; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using AmongUs.Data; +using AmongUs.Data.Legacy; namespace TheOtherRoles.Modules { [HarmonyPatch] @@ -179,14 +181,18 @@ namespace TheOtherRoles.Modules { [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))] private static class HatManagerPatch { + private static List allHatsList; static void Prefix(HatManager __instance) { if (RUNNING) return; RUNNING = true; // prevent simultanious execution + allHatsList = __instance.allHats.ToList(); + try { while (CustomHatLoader.hatdetails.Count > 0) { - __instance.allHats.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0])); + allHatsList.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0])); CustomHatLoader.hatdetails.RemoveAt(0); } + __instance.allHats = allHatsList.ToArray(); } catch (System.Exception e) { if (!LOADED) System.Console.WriteLine("Unable to add Custom Hats\n" + e); @@ -286,7 +292,7 @@ namespace TheOtherRoles.Modules { __instance.PopulateFromHatViewData(); - __instance.SpriteColor = Palette.PlayerColors[color]; + __instance.SetMaterialColor(color); return false; } } @@ -321,7 +327,7 @@ namespace TheOtherRoles.Modules { ColorChip colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard) { colorChip.Button.OnMouseOver.AddListener((System.Action)(() => __instance.SelectHat(hat))); - colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(FastDestroyableSingleton.Instance.GetHatById(SaveManager.LastHat)))); + colorChip.Button.OnMouseOut.AddListener((System.Action)(() => __instance.SelectHat(FastDestroyableSingleton.Instance.GetHatById(DataManager.Player.Customization.Hat)))); colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.ClickEquip())); } else { colorChip.Button.OnClick.AddListener((System.Action)(() => __instance.SelectHat(hat))); @@ -349,7 +355,7 @@ namespace TheOtherRoles.Modules { } colorChip.transform.localPosition = new Vector3(xpos, ypos, -1f); - colorChip.Inner.SetHat(hat, __instance.HasLocalPlayer() ? CachedPlayer.LocalPlayer.Data.DefaultOutfit.ColorId : ((int)SaveManager.BodyColor)); + colorChip.Inner.SetHat(hat, __instance.HasLocalPlayer() ? CachedPlayer.LocalPlayer.Data.DefaultOutfit.ColorId : ((int)DataManager.Player.Customization.Color)); colorChip.Inner.transform.localPosition = hat.ChipOffset; colorChip.Tag = hat; colorChip.SelectionHighlight.gameObject.SetActive(false); diff --git a/TheOtherRoles/Modules/CustomOptions.cs b/TheOtherRoles/Modules/CustomOptions.cs index 7665ece..5263c6b 100644 --- a/TheOtherRoles/Modules/CustomOptions.cs +++ b/TheOtherRoles/Modules/CustomOptions.cs @@ -9,6 +9,8 @@ using System.Reflection; using System.Text; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using static TheOtherRoles.TheOtherRoles; +using static TheOtherRoles.CustomOption; namespace TheOtherRoles { public class CustomOption { @@ -17,7 +19,10 @@ namespace TheOtherRoles { Impostor, Neutral, Crewmate, - Modifier + Modifier, + Guesser, + HideNSeekMain, + HideNSeekRoles } public static List options = new List(); @@ -144,79 +149,70 @@ namespace TheOtherRoles { [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))] class GameOptionsMenuStartPatch { public static void Postfix(GameOptionsMenu __instance) { - if (GameObject.Find("TORSettings") != null) { // Settings setup has already been performed, fixing the title of the tab and returning - GameObject.Find("TORSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("The Other Roles Settings"); - return; - } - if (GameObject.Find("ImpostorSettings") != null) { - GameObject.Find("ImpostorSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("Impostor Roles Settings"); - return; - } - if (GameObject.Find("NeutralSettings") != null) { - GameObject.Find("NeutralSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("Neutral Roles Settings"); - return; - } - if (GameObject.Find("CrewmateSettings") != null) { - GameObject.Find("CrewmateSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("Crewmate Roles Settings"); - return; - } - if (GameObject.Find("ModifierSettings") != null) { - GameObject.Find("ModifierSettings").transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText("Modifier Settings"); - return; - } + + switch (MapOptions.gameMode) { + case CustomGamemodes.Classic: + createClassicTabs(__instance); + break; + case CustomGamemodes.Guesser: + createGuesserTabs(__instance); + break; + case CustomGamemodes.HideNSeek: + createHideNSeekTabs(__instance); + break; + } + } + + private static void createClassicTabs(GameOptionsMenu __instance) { + bool isReturn = setNames( + new Dictionary() { + ["TORSettings"] = "The Other Roles Settings", + ["ImpostorSettings"] = "Impostor Roles Settings", + ["NeutralSettings"] = "Neutral Roles Settings", + ["CrewmateSettings"] = "Crewmate Roles Settings", + ["ModifierSettings"] = "Modifier Settings" + }); + + if (isReturn) return; // Setup TOR tab var template = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); if (template == null) return; var gameSettings = GameObject.Find("Game Settings"); var gameSettingMenu = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); - + var torSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); - var torMenu = torSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); - torSettings.name = "TORSettings"; + var torMenu = getMenu(torSettings, "TORSettings"); var impostorSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); - var impostorMenu = impostorSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); - impostorSettings.name = "ImpostorSettings"; + var impostorMenu = getMenu(impostorSettings, "ImpostorSettings"); var neutralSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); - var neutralMenu = neutralSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); - neutralSettings.name = "NeutralSettings"; + var neutralMenu = getMenu(neutralSettings, "NeutralSettings"); var crewmateSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); - var crewmateMenu = crewmateSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); - crewmateSettings.name = "CrewmateSettings"; + var crewmateMenu = getMenu(crewmateSettings, "CrewmateSettings"); var modifierSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); - var modifierMenu = modifierSettings.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); - modifierSettings.name = "ModifierSettings"; + var modifierMenu = getMenu(modifierSettings, "ModifierSettings"); var roleTab = GameObject.Find("RoleTab"); var gameTab = GameObject.Find("GameTab"); var torTab = UnityEngine.Object.Instantiate(roleTab, roleTab.transform.parent); - var torTabHighlight = torTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - torTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIcon.png", 100f); + var torTabHighlight = getTabHighlight(torTab, "TheOtherRolesTab", "TheOtherRoles.Resources.TabIcon.png"); var impostorTab = UnityEngine.Object.Instantiate(roleTab, torTab.transform); - var impostorTabHighlight = impostorTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - impostorTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIconImpostor.png", 100f); - impostorTab.name = "ImpostorTab"; + var impostorTabHighlight = getTabHighlight(impostorTab, "ImpostorTab", "TheOtherRoles.Resources.TabIconImpostor.png"); var neutralTab = UnityEngine.Object.Instantiate(roleTab, impostorTab.transform); - var neutralTabHighlight = neutralTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - neutralTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIconNeutral.png", 100f); - neutralTab.name = "NeutralTab"; + var neutralTabHighlight = getTabHighlight(neutralTab, "NeutralTab", "TheOtherRoles.Resources.TabIconNeutral.png"); var crewmateTab = UnityEngine.Object.Instantiate(roleTab, neutralTab.transform); - var crewmateTabHighlight = crewmateTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - crewmateTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIconCrewmate.png", 100f); - crewmateTab.name = "CrewmateTab"; + var crewmateTabHighlight = getTabHighlight(crewmateTab, "CrewmateTab", "TheOtherRoles.Resources.TabIconCrewmate.png"); var modifierTab = UnityEngine.Object.Instantiate(roleTab, crewmateTab.transform); - var modifierTabHighlight = modifierTab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); - modifierTab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TabIconModifier.png", 100f); - modifierTab.name = "ModifierTab"; + var modifierTabHighlight = getTabHighlight(modifierTab, "ModifierTab", "TheOtherRoles.Resources.TabIconModifier.png"); // Position of Tab Icons gameTab.transform.position += Vector3.left * 3f; @@ -227,78 +223,47 @@ namespace TheOtherRoles { crewmateTab.transform.localPosition = Vector3.right * 1f; modifierTab.transform.localPosition = Vector3.right * 1f; - var tabs = new GameObject[] { gameTab, roleTab, torTab, impostorTab, neutralTab, crewmateTab, modifierTab}; + var tabs = new GameObject[] { gameTab, roleTab, torTab, impostorTab, neutralTab, crewmateTab, modifierTab }; + var settingsHighlightMap = new Dictionary { + [gameSettingMenu.RegularGameSettings] = gameSettingMenu.GameSettingsHightlight, + [gameSettingMenu.RolesSettings.gameObject] = gameSettingMenu.RolesSettingsHightlight, + [torSettings.gameObject] = torTabHighlight, + [impostorSettings.gameObject] = impostorTabHighlight, + [neutralSettings.gameObject] = neutralTabHighlight, + [crewmateSettings.gameObject] = crewmateTabHighlight, + [modifierSettings.gameObject] = modifierTabHighlight + }; for (int i = 0; i < tabs.Length; i++) { var button = tabs[i].GetComponentInChildren(); if (button == null) continue; int copiedIndex = i; button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); button.OnClick.AddListener((System.Action)(() => { - gameSettingMenu.RegularGameSettings.SetActive(false); - gameSettingMenu.RolesSettings.gameObject.SetActive(false); - torSettings.gameObject.SetActive(false); - impostorSettings.gameObject.SetActive(false); - neutralSettings.gameObject.SetActive(false); - crewmateSettings.gameObject.SetActive(false); - modifierSettings.gameObject.SetActive(false); - gameSettingMenu.GameSettingsHightlight.enabled = false; - gameSettingMenu.RolesSettingsHightlight.enabled = false; - torTabHighlight.enabled = false; - impostorTabHighlight.enabled = false; - neutralTabHighlight.enabled = false; - crewmateTabHighlight.enabled = false; - modifierTabHighlight.enabled = false; - if (copiedIndex == 0) { - gameSettingMenu.RegularGameSettings.SetActive(true); - gameSettingMenu.GameSettingsHightlight.enabled = true; - } else if (copiedIndex == 1) { - gameSettingMenu.RolesSettings.gameObject.SetActive(true); - gameSettingMenu.RolesSettingsHightlight.enabled = true; - } else if (copiedIndex == 2) { - torSettings.gameObject.SetActive(true); - torTabHighlight.enabled = true; - } else if (copiedIndex == 3) { - impostorSettings.gameObject.SetActive(true); - impostorTabHighlight.enabled = true; - } else if (copiedIndex == 4) { - neutralSettings.gameObject.SetActive(true); - neutralTabHighlight.enabled = true; - } else if (copiedIndex == 5) { - crewmateSettings.gameObject.SetActive(true); - crewmateTabHighlight.enabled = true; - } else if (copiedIndex == 6) { - modifierSettings.gameObject.SetActive(true); - modifierTabHighlight.enabled = true; - } + setListener(settingsHighlightMap, copiedIndex); })); } - foreach (OptionBehaviour option in torMenu.GetComponentsInChildren()) - UnityEngine.Object.Destroy(option.gameObject); - List torOptions = new List(); + destroyOptions(new List>{ + torMenu.GetComponentsInChildren().ToList(), + impostorMenu.GetComponentsInChildren().ToList(), + neutralMenu.GetComponentsInChildren().ToList(), + crewmateMenu.GetComponentsInChildren().ToList(), + modifierMenu.GetComponentsInChildren().ToList() + }); - foreach (OptionBehaviour option in impostorMenu.GetComponentsInChildren()) - UnityEngine.Object.Destroy(option.gameObject); + List torOptions = new List(); List impostorOptions = new List(); - - foreach (OptionBehaviour option in neutralMenu.GetComponentsInChildren()) - UnityEngine.Object.Destroy(option.gameObject); List neutralOptions = new List(); - - foreach (OptionBehaviour option in crewmateMenu.GetComponentsInChildren()) - UnityEngine.Object.Destroy(option.gameObject); List crewmateOptions = new List(); - - foreach (OptionBehaviour option in modifierMenu.GetComponentsInChildren()) - UnityEngine.Object.Destroy(option.gameObject); List modifierOptions = new List(); - List menus = new List() {torMenu.transform, impostorMenu.transform, neutralMenu.transform, crewmateMenu.transform, modifierMenu.transform}; + List menus = new List() { torMenu.transform, impostorMenu.transform, neutralMenu.transform, crewmateMenu.transform, modifierMenu.transform }; List> optionBehaviours = new List>() { torOptions, impostorOptions, neutralOptions, crewmateOptions, modifierOptions }; for (int i = 0; i < CustomOption.options.Count; i++) { CustomOption option = CustomOption.options[i]; + if ((int)option.type > 4) continue; if (option.optionBehaviour == null) { StringOption stringOption = UnityEngine.Object.Instantiate(template, menus[(int)option.type]); optionBehaviours[(int)option.type].Add(stringOption); @@ -312,31 +277,306 @@ namespace TheOtherRoles { option.optionBehaviour.gameObject.SetActive(true); } - torMenu.Children = torOptions.ToArray(); - torSettings.gameObject.SetActive(false); + setOptions( + new List { torMenu, impostorMenu, neutralMenu, crewmateMenu, modifierMenu }, + new List> { torOptions, impostorOptions, neutralOptions, crewmateOptions, modifierOptions }, + new List { torSettings, impostorSettings, neutralSettings, crewmateSettings, modifierSettings } + ); - impostorMenu.Children = impostorOptions.ToArray(); - impostorSettings.gameObject.SetActive(false); + adaptTaskCount(__instance); + } - neutralMenu.Children = neutralOptions.ToArray(); - neutralSettings.gameObject.SetActive(false); + private static void createGuesserTabs(GameOptionsMenu __instance) { + bool isReturn = setNames( + new Dictionary() { + ["TORSettings"] = "The Other Roles Settings", + ["GuesserSettings"] = "Guesser Mode Settings", + ["ImpostorSettings"] = "Impostor Roles Settings", + ["NeutralSettings"] = "Neutral Roles Settings", + ["CrewmateSettings"] = "Crewmate Roles Settings", + ["ModifierSettings"] = "Modifier Settings" + }); - crewmateMenu.Children = crewmateOptions.ToArray(); - crewmateSettings.gameObject.SetActive(false); + if (isReturn) return; - modifierMenu.Children = modifierOptions.ToArray(); - modifierSettings.gameObject.SetActive(false); + // Setup TOR tab + var template = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); + if (template == null) return; + var gameSettings = GameObject.Find("Game Settings"); + var gameSettingMenu = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); - // Adapt task count for main options + var torSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var torMenu = getMenu(torSettings, "TORSettings"); + + var guesserSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var guesserMenu = getMenu(guesserSettings, "GuesserSettings"); + + var impostorSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var impostorMenu = getMenu(impostorSettings, "ImpostorSettings"); + + var neutralSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var neutralMenu = getMenu(neutralSettings, "NeutralSettings"); + + var crewmateSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var crewmateMenu = getMenu(crewmateSettings, "CrewmateSettings"); + + var modifierSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var modifierMenu = getMenu(modifierSettings, "ModifierSettings"); + + var roleTab = GameObject.Find("RoleTab"); + var gameTab = GameObject.Find("GameTab"); + + var torTab = UnityEngine.Object.Instantiate(roleTab, gameTab.transform.parent); + var torTabHighlight = getTabHighlight(torTab, "TheOtherRolesTab", "TheOtherRoles.Resources.TabIcon.png"); + + var guesserTab = UnityEngine.Object.Instantiate(roleTab, torTab.transform); + var guesserTabHighlight = getTabHighlight(guesserTab, "GuesserTab", "TheOtherRoles.Resources.TabIconGuesserSettings.png"); + + var impostorTab = UnityEngine.Object.Instantiate(roleTab, guesserTab.transform); + var impostorTabHighlight = getTabHighlight(impostorTab, "ImpostorTab", "TheOtherRoles.Resources.TabIconImpostor.png"); + + var neutralTab = UnityEngine.Object.Instantiate(roleTab, impostorTab.transform); + var neutralTabHighlight = getTabHighlight(neutralTab, "NeutralTab", "TheOtherRoles.Resources.TabIconNeutral.png"); + + var crewmateTab = UnityEngine.Object.Instantiate(roleTab, neutralTab.transform); + var crewmateTabHighlight = getTabHighlight(crewmateTab, "CrewmateTab", "TheOtherRoles.Resources.TabIconCrewmate.png"); + + var modifierTab = UnityEngine.Object.Instantiate(roleTab, crewmateTab.transform); + var modifierTabHighlight = getTabHighlight(modifierTab, "ModifierTab", "TheOtherRoles.Resources.TabIconModifier.png"); + + roleTab.active = false; + // Position of Tab Icons + gameTab.transform.position += Vector3.left * 3f; + torTab.transform.position += Vector3.left * 3f; + guesserTab.transform.localPosition = Vector3.right * 1f; + impostorTab.transform.localPosition = Vector3.right * 1f; + neutralTab.transform.localPosition = Vector3.right * 1f; + crewmateTab.transform.localPosition = Vector3.right * 1f; + modifierTab.transform.localPosition = Vector3.right * 1f; + + var tabs = new GameObject[] { gameTab, torTab, impostorTab, neutralTab, crewmateTab, modifierTab, guesserTab }; + var settingsHighlightMap = new Dictionary { + [gameSettingMenu.RegularGameSettings] = gameSettingMenu.GameSettingsHightlight, + [torSettings.gameObject] = torTabHighlight, + [impostorSettings.gameObject] = impostorTabHighlight, + [neutralSettings.gameObject] = neutralTabHighlight, + [crewmateSettings.gameObject] = crewmateTabHighlight, + [modifierSettings.gameObject] = modifierTabHighlight, + [guesserSettings.gameObject] = guesserTabHighlight + }; + for (int i = 0; i < tabs.Length; i++) { + var button = tabs[i].GetComponentInChildren(); + if (button == null) continue; + int copiedIndex = i; + button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + button.OnClick.AddListener((System.Action)(() => { + setListener(settingsHighlightMap, copiedIndex); + })); + } + + destroyOptions(new List>{ + torMenu.GetComponentsInChildren().ToList(), + guesserMenu.GetComponentsInChildren().ToList(), + impostorMenu.GetComponentsInChildren().ToList(), + neutralMenu.GetComponentsInChildren().ToList(), + crewmateMenu.GetComponentsInChildren().ToList(), + modifierMenu.GetComponentsInChildren().ToList() + }); + + List torOptions = new List(); + List guesserOptions = new List(); + List impostorOptions = new List(); + List neutralOptions = new List(); + List crewmateOptions = new List(); + List modifierOptions = new List(); + + + List menus = new List() { torMenu.transform, impostorMenu.transform, neutralMenu.transform, crewmateMenu.transform, modifierMenu.transform, guesserMenu.transform }; + List> optionBehaviours = new List>() { torOptions, impostorOptions, neutralOptions, crewmateOptions, modifierOptions, guesserOptions }; + List exludedIds = new List { 310, 311, 312, 313, 314, 315, 316, 317, 318 }; + + for (int i = 0; i < CustomOption.options.Count; i++) { + CustomOption option = CustomOption.options[i]; + if (exludedIds.Contains(option.id)) continue; + if ((int)option.type > 5) continue; + if (option.optionBehaviour == null) { + StringOption stringOption = UnityEngine.Object.Instantiate(template, menus[(int)option.type]); + optionBehaviours[(int)option.type].Add(stringOption); + stringOption.OnValueChanged = new Action((o) => { }); + stringOption.TitleText.text = option.name; + stringOption.Value = stringOption.oldValue = option.selection; + stringOption.ValueText.text = option.selections[option.selection].ToString(); + + option.optionBehaviour = stringOption; + } + option.optionBehaviour.gameObject.SetActive(true); + } + + setOptions( + new List { torMenu, impostorMenu, neutralMenu, crewmateMenu, modifierMenu, guesserMenu }, + new List> { torOptions, impostorOptions, neutralOptions, crewmateOptions, modifierOptions, guesserOptions }, + new List { torSettings, impostorSettings, neutralSettings, crewmateSettings, modifierSettings, guesserSettings } + ); + adaptTaskCount(__instance); + } + + private static void createHideNSeekTabs(GameOptionsMenu __instance) { + bool isReturn = setNames( + new Dictionary(){ + ["TORSettings"] = "The Other Roles Settings", + ["HideNSeekSettings"] = "Hide 'N Seek Settings" + }); + + if (isReturn) return; + + // Setup TOR tab + var template = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); + if (template == null) return; + var gameSettings = GameObject.Find("Game Settings"); + var gameSettingMenu = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(); + + var torSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var torMenu = getMenu(torSettings, "TORSettings"); + + var hideNSeekSettings = UnityEngine.Object.Instantiate(gameSettings, gameSettings.transform.parent); + var hideNSeekMenu = getMenu(hideNSeekSettings, "HideNSeekSettings"); + + var roleTab = GameObject.Find("RoleTab"); + var gameTab = GameObject.Find("GameTab"); + + var torTab = UnityEngine.Object.Instantiate(roleTab, gameTab.transform.parent); + var torTabHighlight = getTabHighlight(torTab, "TheOtherRolesTab", "TheOtherRoles.Resources.TabIconHideNSeekSettings.png"); + + var hideNSeekTab = UnityEngine.Object.Instantiate(roleTab, torTab.transform); + var hideNSeekTabHighlight = getTabHighlight(hideNSeekTab, "HideNSeekTab", "TheOtherRoles.Resources.TabIconHideNSeekRoles.png"); + + roleTab.active = false; + gameTab.active = false; + + // Position of Tab Icons + torTab.transform.position += Vector3.left * 3f; + hideNSeekTab.transform.position += Vector3.right * 1f; + + var tabs = new GameObject[] { torTab, hideNSeekTab}; + var settingsHighlightMap = new Dictionary { + [torSettings.gameObject] = torTabHighlight, + [hideNSeekSettings.gameObject] = hideNSeekTabHighlight + }; + for (int i = 0; i < tabs.Length; i++) { + var button = tabs[i].GetComponentInChildren(); + if (button == null) continue; + int copiedIndex = i; + button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent(); + button.OnClick.AddListener((System.Action)(() => { + setListener(settingsHighlightMap, copiedIndex); + })); + } + + destroyOptions(new List>{ + torMenu.GetComponentsInChildren().ToList(), + hideNSeekMenu.GetComponentsInChildren().ToList() + }); + + List torOptions = new List(); + List hideNSeekOptions = new List(); + + List menus = new List() { torMenu.transform, hideNSeekMenu.transform}; + List> optionBehaviours = new List>() { torOptions, hideNSeekOptions }; + + for (int i = 0; i < CustomOption.options.Count; i++) { + CustomOption option = CustomOption.options[i]; + if (option.type != CustomOptionType.HideNSeekMain && option.type != CustomOptionType.HideNSeekRoles) continue; + if (option.optionBehaviour == null) { + int index = (int)option.type - 6; + StringOption stringOption = UnityEngine.Object.Instantiate(template, menus[index]); + optionBehaviours[index].Add(stringOption); + stringOption.OnValueChanged = new Action((o) => { }); + stringOption.TitleText.text = option.name; + stringOption.Value = stringOption.oldValue = option.selection; + stringOption.ValueText.text = option.selections[option.selection].ToString(); + + option.optionBehaviour = stringOption; + } + option.optionBehaviour.gameObject.SetActive(true); + } + + setOptions( + new List{torMenu, hideNSeekMenu}, + new List> {torOptions, hideNSeekOptions}, + new List {torSettings, hideNSeekSettings} + ); + + torSettings.gameObject.SetActive(true); + torTabHighlight.enabled = true; + gameSettingMenu.RegularGameSettings.SetActive(false); + gameSettingMenu.GameSettingsHightlight.enabled = false; + } + + + private static void setListener(Dictionary settingsHighlightMap, int index) { + foreach (KeyValuePair entry in settingsHighlightMap) { + entry.Key.SetActive(false); + entry.Value.enabled = false; + } + settingsHighlightMap.ElementAt(index).Key.SetActive(true); + settingsHighlightMap.ElementAt(index).Value.enabled = true; + } + + private static void destroyOptions (List> optionBehavioursList) { + foreach (List optionBehaviours in optionBehavioursList) { + foreach (OptionBehaviour option in optionBehaviours) + UnityEngine.Object.Destroy(option.gameObject); + } + } + + private static bool setNames (Dictionary gameObjectNameDisplayNameMap) { + foreach (KeyValuePair entry in gameObjectNameDisplayNameMap) { + if (GameObject.Find(entry.Key) != null) { // Settings setup has already been performed, fixing the title of the tab and returning + GameObject.Find(entry.Key).transform.FindChild("GameGroup").FindChild("Text").GetComponent().SetText(entry.Value); + return true; + } + } + + return false; + } + + private static GameOptionsMenu getMenu(GameObject setting, string settingName) { + var menu = setting.transform.FindChild("GameGroup").FindChild("SliderInner").GetComponent(); + setting.name = settingName; + + return menu; + } + + private static SpriteRenderer getTabHighlight (GameObject tab, string tabName, string tabSpritePath) { + var tabHighlight = tab.transform.FindChild("Hat Button").FindChild("Tab Background").GetComponent(); + tab.transform.FindChild("Hat Button").FindChild("Icon").GetComponent().sprite = Helpers.loadSpriteFromResources(tabSpritePath, 100f); + tab.name = "tabName"; + + return tabHighlight; + } + + private static void setOptions (List menus, List> options, List settings) { + if (!(menus.Count == options.Count && options.Count == settings.Count)) { + TheOtherRolesPlugin.Logger.LogError("List counts are not equal"); + return; + } + for (int i = 0; i < menus.Count; i++) { + menus[i].Children = options[i].ToArray(); + settings[i].gameObject.SetActive(false); + } + } + + private static void adaptTaskCount(GameOptionsMenu __instance) { + // Adapt task count for main options var commonTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumCommonTasks").TryCast(); - if(commonTasksOption != null) commonTasksOption.ValidRange = new FloatRange(0f, 4f); + if (commonTasksOption != null) commonTasksOption.ValidRange = new FloatRange(0f, 4f); var shortTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumShortTasks").TryCast(); - if(shortTasksOption != null) shortTasksOption.ValidRange = new FloatRange(0f, 23f); + if (shortTasksOption != null) shortTasksOption.ValidRange = new FloatRange(0f, 23f); var longTasksOption = __instance.Children.FirstOrDefault(x => x.name == "NumLongTasks").TryCast(); - if(longTasksOption != null) longTasksOption.ValidRange = new FloatRange(0f, 15f); + if (longTasksOption != null) longTasksOption.ValidRange = new FloatRange(0f, 15f); } } @@ -405,7 +645,7 @@ namespace TheOtherRoles { float offset = 2.75f; foreach (CustomOption option in CustomOption.options) { - if (GameObject.Find("TORSettings") && option.type != CustomOption.CustomOptionType.General) + if (GameObject.Find("TORSettings") && option.type != CustomOption.CustomOptionType.General && option.type != CustomOptionType.HideNSeekMain) continue; if (GameObject.Find("ImpostorSettings") && option.type != CustomOption.CustomOptionType.Impostor) continue; @@ -415,6 +655,10 @@ namespace TheOtherRoles { continue; if (GameObject.Find("ModifierSettings") && option.type != CustomOption.CustomOptionType.Modifier) continue; + if (GameObject.Find("GuesserSettings") && option.type != CustomOption.CustomOptionType.Guesser) + continue; + if (GameObject.Find("HideNSeekSettings") && option.type != CustomOption.CustomOptionType.HideNSeekRoles) + continue; if (option?.optionBehaviour != null && option.optionBehaviour.gameObject != null) { bool enabled = true; var parent = option.parent; @@ -447,22 +691,54 @@ namespace TheOtherRoles { var modifiers = buildOptionsOfType(CustomOption.CustomOptionType.Modifier, true); return impRoles + neutralRoles + crewRoles + modifiers; } + private static string buildModifierExtras(CustomOption customOption) { + // find options children with quantity + var children = CustomOption.options.Where(o => o.parent == customOption); + var quantity = children.Where(o => o.name.Contains("Quantity")).ToList(); + if (customOption.getSelection() == 0) return ""; + if (quantity.Count == 1) return $" ({quantity[0].getQuantity()})"; + if (customOption == CustomOptionHolder.modifierLover) { + return $" (1 Evil: {CustomOptionHolder.modifierLoverImpLoverRate.getSelection() * 10}%)"; + } + return ""; + } 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 (type == CustomOption.CustomOptionType.General) + options = CustomOption.options.Where(o => o.type == type || o.type == CustomOption.CustomOptionType.Guesser); + List remove = new List{ 310, 311, 312, 313, 314, 315, 316, 317, 318 }; + 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) + options = options.Where(x => (x.type == CustomOption.CustomOptionType.HideNSeekMain || x.type == CustomOption.CustomOptionType.HideNSeekRoles)); + foreach (var option in options) { if (option.parent == null) { - sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}"); + string line = $"{option.name}: {option.selections[option.selection].ToString()}"; + if (type == CustomOption.CustomOptionType.Modifier) line += buildModifierExtras(option); + sb.AppendLine(line); + } + else if (option.parent.getSelection() > 0) { + if (option.id == 103) //Deputy + sb.AppendLine($"- {Helpers.cs(Deputy.color, "Deputy")}: {option.selections[option.selection].ToString()}"); + else if (option.id == 224) //Sidekick + sb.AppendLine($"- {Helpers.cs(Sidekick.color, "Sidekick")}: {option.selections[option.selection].ToString()}"); + else if (option.id == 358) //Prosecutor + sb.AppendLine($"- {Helpers.cs(Lawyer.color, "Prosecutor")}: {option.selections[option.selection].ToString()}"); } } if (headerOnly) return sb.ToString(); else sb = new StringBuilder(); foreach (CustomOption option in options) { + if (MapOptions.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); - + Color c = isIrrelevant ? Color.grey : Color.white; // No use for now if (isIrrelevant) continue; sb.AppendLine(Helpers.cs(c, $"{option.name}: {option.selections[option.selection].ToString()}")); @@ -499,7 +775,7 @@ namespace TheOtherRoles { continue; } else { sb.AppendLine($"\n{option.name}: {option.selections[option.selection].ToString()}"); - } + } } } return sb.ToString(); @@ -509,32 +785,45 @@ namespace TheOtherRoles { { 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") : ""; - - switch (counter) { - case 0: - hudString += "Page 1: Vanilla Settings \n\n" + __result; - break; - case 1: - hudString += "Page 2: The Other Roles Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.General, false); - break; - case 2: - hudString += "Page 3: Role and Modifier Rates \n" + buildRoleOptions(); - break; - case 3: - hudString += "Page 4: Impostor Role Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Impostor, false); - break; - case 4: - hudString += "Page 5: Neutral Role Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Neutral, false); - break; - case 5: - hudString += "Page 6: Crewmate Role Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Crewmate, false); - break; - case 6: - hudString += "Page 7: Modifier Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Modifier, false); - break; + int maxPage = 7; + if (MapOptions.gameMode == CustomGamemodes.HideNSeek) { + if (TheOtherRolesPlugin.optionsPage > 1) TheOtherRolesPlugin.optionsPage = 0; + maxPage = 2; + switch (counter) { + case 0: + hudString += "Page 1: Hide N Seek Settings \n\n" + buildOptionsOfType(CustomOption.CustomOptionType.HideNSeekMain, false); + break; + case 1: + hudString += "Page 2: Hide N Seek Role Settings \n\n" + buildOptionsOfType(CustomOption.CustomOptionType.HideNSeekRoles, false); + break; + } + } else { + switch (counter) { + case 0: + hudString += "Page 1: Vanilla Settings \n\n" + __result; + break; + case 1: + hudString += "Page 2: The Other Roles Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.General, false); + break; + case 2: + hudString += "Page 3: Role and Modifier Rates \n" + buildRoleOptions(); + break; + case 3: + hudString += "Page 4: Impostor Role Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Impostor, false); + break; + case 4: + hudString += "Page 5: Neutral Role Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Neutral, false); + break; + case 5: + hudString += "Page 6: Crewmate Role Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Crewmate, false); + break; + case 6: + hudString += "Page 7: Modifier Settings \n" + buildOptionsOfType(CustomOption.CustomOptionType.Modifier, false); + break; + } } - hudString += $"\n Press TAB or Page Number for more... ({counter+1}/7)"; + hudString += $"\n Press TAB or Page Number for more... ({counter+1}/{maxPage})"; __result = hudString; } } diff --git a/TheOtherRoles/Modules/DynamicLobbies.cs b/TheOtherRoles/Modules/DynamicLobbies.cs index 87b3e23..aaa488a 100644 --- a/TheOtherRoles/Modules/DynamicLobbies.cs +++ b/TheOtherRoles/Modules/DynamicLobbies.cs @@ -1,4 +1,5 @@ using System; +using AmongUs.Data; using HarmonyLib; using Hazel; using InnerNet; @@ -46,7 +47,7 @@ namespace TheOtherRoles.Modules { public static void Prefix(InnerNet.InnerNetClient __instance, [HarmonyArgument(0)] GameOptionsData settings) { DynamicLobbies.LobbyLimit = settings.MaxPlayers; settings.MaxPlayers = 15; // Force 15 Player Lobby on Server - SaveManager.ChatModeType = InnerNet.QuickChatModes.FreeChatOrQuickChat; + DataManager.Settings.Multiplayer.ChatMode = InnerNet.QuickChatModes.FreeChatOrQuickChat; } public static void Postfix(InnerNet.InnerNetClient __instance, [HarmonyArgument(0)] GameOptionsData settings) { settings.MaxPlayers = DynamicLobbies.LobbyLimit; @@ -55,7 +56,7 @@ namespace TheOtherRoles.Modules { [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.JoinGame))] public static class InnerNetClientJoinPatch { public static void Prefix(InnerNet.InnerNetClient __instance) { - SaveManager.ChatModeType = InnerNet.QuickChatModes.FreeChatOrQuickChat; + DataManager.Settings.Multiplayer.ChatMode = InnerNet.QuickChatModes.FreeChatOrQuickChat; } } [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))] diff --git a/TheOtherRoles/Modules/ModUpdater.cs b/TheOtherRoles/Modules/ModUpdater.cs index 86f5712..300386e 100644 --- a/TheOtherRoles/Modules/ModUpdater.cs +++ b/TheOtherRoles/Modules/ModUpdater.cs @@ -5,10 +5,11 @@ using System.Linq; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; +using AmongUs.Data; using BepInEx; using BepInEx.Bootstrap; -using BepInEx.IL2CPP; -using BepInEx.IL2CPP.Utils; +using BepInEx.Unity.IL2CPP; +using BepInEx.Unity.IL2CPP.Utils; using Mono.Cecil; using Newtonsoft.Json.Linq; using TMPro; @@ -109,6 +110,23 @@ namespace TheOtherRoles.Modules var isSubmerged = TORUpdate == null; var announcement = $"A new {(isSubmerged ? "Submerged" : "THE OTHER ROLES")} update to {(isSubmerged ? SubmergedUpdate.Tag : TORUpdate.Tag)} is available\n{(isSubmerged ? SubmergedUpdate.Content : TORUpdate.Content)}"; var mgr = FindObjectOfType(true); + + if (!isSubmerged) { + try { + string updateVersion = TORUpdate.Content[^5..]; + if (Version.Parse(TheOtherRolesPlugin.VersionString).BaseVersion() < Version.Parse(updateVersion).BaseVersion()) { + passiveButton.OnClick.RemoveAllListeners(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.OnClick.AddListener((Action)(() => { + mgr.StartCoroutine(CoShowAnnouncement($"A MANUAL UPDATE IS REQUIRED")); + })); + } + } catch { + TheOtherRolesPlugin.Logger.LogError("parsing version for auto updater failed :("); + } + + } + if (isSubmerged && !SubmergedCompatibility.Loaded) showPopUp = false; if (showPopUp) mgr.StartCoroutine(CoShowAnnouncement(announcement)); showPopUp = false; @@ -144,7 +162,7 @@ namespace TheOtherRoles.Modules var popUp = Instantiate(FindObjectOfType(true)); popUp.gameObject.SetActive(true); yield return popUp.Init(); - var last = SaveManager.LastAnnouncement; + var last = DataManager.Announcements.LastViewedAnnouncement; last.Id = 1; last.Text = announcement; SelectableHyperLinkHelper.DestroyGOs(popUp.selectableHyperLinks, name); diff --git a/TheOtherRoles/Objects/Bloodytrail.cs b/TheOtherRoles/Objects/Bloodytrail.cs index 5090e4a..6b9ec64 100644 --- a/TheOtherRoles/Objects/Bloodytrail.cs +++ b/TheOtherRoles/Objects/Bloodytrail.cs @@ -38,7 +38,7 @@ namespace TheOtherRoles.Objects { spriteRenderer = blood.AddComponent(); spriteRenderer.sprite = sp[index]; spriteRenderer.material = FastDestroyableSingleton.Instance.PlayerMaterial; - player.SetPlayerMaterialColors(spriteRenderer); + bloodyPlayer.SetPlayerMaterialColors(spriteRenderer); // spriteRenderer.color = color; blood.SetActive(true); diff --git a/TheOtherRoles/Objects/Trap.cs b/TheOtherRoles/Objects/Trap.cs new file mode 100644 index 0000000..f2f01f6 --- /dev/null +++ b/TheOtherRoles/Objects/Trap.cs @@ -0,0 +1,140 @@ +using Hazel; +using System; +using System.Collections.Generic; +using System.Linq; +using TheOtherRoles.Players; +using TheOtherRoles.Utilities; +using UnityEngine; + +namespace TheOtherRoles.Objects { + class Trap { + public static List traps = new List(); + public static Dictionary trapPlayerIdMap = new Dictionary(); + + private static int instanceCounter = 0; + public int instanceId = 0; + public GameObject trap; + public bool revealed = false; + public bool triggerable = false; + private int usedCount = 0; + private int neededCount = Trapper.trapCountToReveal; + public List trappedPlayer = new List(); + private Arrow arrow = new Arrow(Color.blue); + + private static Sprite trapSprite; + public static Sprite getTrapSprite() { + if (trapSprite) return trapSprite; + trapSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Trapper_Trap_Ingame.png", 300f); + return trapSprite; + } + + public Trap(Vector2 p) { + trap = new GameObject("Trap") { layer = 11 }; + trap.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + Vector3 position = new Vector3(p.x, p.y, p.y / 1000 + 0.001f); // just behind player + trap.transform.position = position; + neededCount = Trapper.trapCountToReveal; + + var trapRenderer = trap.AddComponent(); + trapRenderer.sprite = getTrapSprite(); + trap.SetActive(false); + if (CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) trap.SetActive(true); + this.instanceId = ++instanceCounter; + traps.Add(this); + arrow.Update(position); + arrow.arrow.SetActive(false); + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(5, new Action((x) => { + if (x == 1f) { + this.triggerable = true; + } + }))); + } + + public static void clearTraps() { + foreach (Trap t in traps) { + UnityEngine.Object.Destroy(t.arrow.arrow); + UnityEngine.Object.Destroy(t.trap); + } + traps = new List(); + trapPlayerIdMap = new Dictionary(); + } + + public static void clearRevealedTraps() { + var trapsToClear = traps.FindAll(x => x.revealed); + + foreach (Trap t in trapsToClear) { + traps.Remove(t); + UnityEngine.Object.Destroy(t.trap); + } + } + + public static void triggerTrap(byte playerId, byte trapId) { + Trap t = traps.FirstOrDefault(x => x.instanceId == (int)trapId); + PlayerControl player = Helpers.playerById(playerId); + if (Trapper.trapper == null || t == null || player == null) return; + bool localIsTrapper = CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId; + if (!trapPlayerIdMap.ContainsKey(playerId)) trapPlayerIdMap.Add(playerId, t); + t.usedCount ++; + t.triggerable = false; + if (playerId == CachedPlayer.LocalPlayer.PlayerId || playerId == Trapper.trapper.PlayerId) { + t.trap.SetActive(true); + SoundEffectsManager.play("trapperTrap"); + } + player.moveable = false; + player.NetTransform.Halt(); + Trapper.playersOnMap.Add(player); + if (localIsTrapper) t.arrow.arrow.SetActive(true); + + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Trapper.trapDuration, new Action((p) => { + if (p == 1f) { + player.moveable = true; + Trapper.playersOnMap.RemoveAll(x => x == player); + if (trapPlayerIdMap.ContainsKey(playerId)) trapPlayerIdMap.Remove(playerId); + t.arrow.arrow.SetActive(false); + } + }))); + + if (t.usedCount == t.neededCount) { + t.revealed = true; + } + + t.trappedPlayer.Add(player); + t.triggerable = true; + + } + + public static void Update() { + if (Trapper.trapper == null) return; + CachedPlayer player = CachedPlayer.LocalPlayer; + Vent vent = MapUtilities.CachedShipStatus.AllVents[0]; + float closestDistance = float.MaxValue; + + if (vent == null || player == null) return; + float ud = vent.UsableDistance / 2; + Trap target = null; + foreach (Trap trap in traps) { + if (trap.arrow.arrow.active) trap.arrow.Update(); + if (trap.revealed || !trap.triggerable || trap.trappedPlayer.Contains(player.PlayerControl)) continue; + if (player.PlayerControl.inVent || !player.PlayerControl.CanMove) continue; + float distance = Vector2.Distance(trap.trap.transform.position, player.PlayerControl.GetTruePosition()); + if (distance <= ud && distance < closestDistance) { + closestDistance = distance; + target = trap; + } + } + if (target != null && player.PlayerId != Trapper.trapper.PlayerId && !player.Data.IsDead) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.TriggerTrap, Hazel.SendOption.Reliable, -1); + writer.Write(player.PlayerId); + writer.Write(target.instanceId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.triggerTrap(player.PlayerId,(byte)target.instanceId); + } + + + if (!player.Data.IsDead || player.PlayerId == Trapper.trapper.PlayerId) return; + foreach (Trap trap in traps) { + if (!trap.trap.active) trap.trap.SetActive(true); + } + } + } +} \ No newline at end of file diff --git a/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs b/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs new file mode 100644 index 0000000..0c0e703 --- /dev/null +++ b/TheOtherRoles/Patches/CreateOptionsPickerPatch.cs @@ -0,0 +1,62 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using static UnityEngine.UI.Button; + +namespace TheOtherRoles.Patches { + [HarmonyPatch(typeof(CreateOptionsPicker))] + class CreateOptionsPickerPatch { + private static List renderers = new List(); + + [HarmonyPatch(typeof(CreateOptionsPicker), "Awake")] + [HarmonyPrefix] + public static void Prefix(CreateOptionsPicker __instance) { + if (__instance.mode == SettingsMode.Host) { + renderers = new List(); + + // space for max 5 buttons + addGamemodeButton(__instance, "Classic", "TheOtherRoles.Resources.TabIconClassicMode.png", CustomGamemodes.Classic); + addGamemodeButton(__instance, "Guesser", "TheOtherRoles.Resources.TabIconGuesserMode.png", CustomGamemodes.Guesser); + addGamemodeButton(__instance, "Hide 'N Seek", "TheOtherRoles.Resources.TabIconHideNSeekMode.png", CustomGamemodes.HideNSeek); + + switch (MapOptions.gameMode) { + case CustomGamemodes.Classic: renderers.FindLast(x => x.name == "Classic").color = Color.white; break; + case CustomGamemodes.Guesser: renderers.FindLast(x => x.name == "Guesser").color = Color.white; break; + case CustomGamemodes.HideNSeek: renderers.FindLast(x => x.name == "Hide 'N Seek").color = Color.white; break; + } + } + else { + // + } + } + + private static void addGamemodeButton(CreateOptionsPicker __instance, string name, string spritePath, CustomGamemodes gamemode) { + Vector3 position1 = __instance.MapButtons[3].transform.position; + Vector3 position2 = __instance.transform.Find("Max Players").position; + float p = -5.8f + (renderers.Count * 1.4f); + + GameObject gameObject = UnityEngine.Object.Instantiate(__instance.MapButtons[2].gameObject, __instance.MapButtons[2].transform.parent); + gameObject.name = name; + gameObject.transform.position = new Vector3(position1.x + p, position2.y - 0.6f, position1.z); + SpriteRenderer component = gameObject.transform.Find("MapIcon2").GetComponent(); + component.gameObject.name = "gm" + name; + component.sprite = Helpers.loadSpriteFromResources(spritePath, 150f); + PassiveButton passiveButton = gameObject.GetComponent(); + SpriteRenderer buttonSprite = gameObject.GetComponent(); + buttonSprite.color *= 0; + + renderers.Add(buttonSprite); + + passiveButton.OnClick = new ButtonClickedEvent(); + passiveButton.OnClick.AddListener((System.Action)(() => setListener(buttonSprite, gamemode))); + } + + private static void setListener(SpriteRenderer renderer, CustomGamemodes gameMode) { + MapOptions.gameMode = gameMode; + foreach (SpriteRenderer r in renderers) r.color *= 0; + renderer.color = Color.white; + } + } +} diff --git a/TheOtherRoles/Patches/CredentialsPatch.cs b/TheOtherRoles/Patches/CredentialsPatch.cs index 4f59346..8210e3c 100644 --- a/TheOtherRoles/Patches/CredentialsPatch.cs +++ b/TheOtherRoles/Patches/CredentialsPatch.cs @@ -1,5 +1,7 @@ using HarmonyLib; using System; +using TheOtherRoles; +using TheOtherRoles.CustomGameModes; using TheOtherRoles.Players; using TheOtherRoles.Utilities; using UnityEngine; @@ -7,14 +9,15 @@ using UnityEngine; namespace TheOtherRoles.Patches { [HarmonyPatch] public static class CredentialsPatch { - public static string fullCredentials = -$@"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()} -Modded by Eisbison, EndOfFile -Thunderstorm584 & Mallöris + public static string fullCredentialsVersion = +$@"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays>0 ? "-BETA": "")}"; +public static string fullCredentials = +$@"Modded by Eisbison, EndOfFile +Thunderstorm584, Mallöris & Gendelo Design by Bavari"; public static string mainMenuCredentials = -$@"Modded by Eisbison, Thunderstorm584, EndOfFile & Mallöris +$@"Modded by Eisbison, Thunderstorm584, EndOfFile, Mallöris & Gendelo Design by Bavari"; public static string contributorsCredentials = @@ -29,7 +32,7 @@ $@" Special thanks to K3ndo & Smeggy"; var credentials = UnityEngine.Object.Instantiate(__instance.text); credentials.transform.position = new Vector3(0, 0, 0); - credentials.SetText($"v{TheOtherRolesPlugin.Version.ToString()}\n\n{mainMenuCredentials}\n\n{contributorsCredentials}"); + credentials.SetText($"v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays > 0 ? "-BETA" : "")}\n\n{mainMenuCredentials}\n\n{contributorsCredentials}"); credentials.alignment = TMPro.TextAlignmentOptions.Center; credentials.fontSize *= 0.75f; @@ -41,7 +44,7 @@ $@" Special thanks to K3ndo & Smeggy"; internal static class PingTrackerPatch { public static GameObject modStamp; - static void Prefix(PingTracker __instance) { + /*static void Prefix(PingTracker __instance) { if (modStamp == null) { modStamp = new GameObject("ModStamp"); var rend = modStamp.AddComponent(); @@ -52,19 +55,28 @@ $@" Special thanks to K3ndo & Smeggy"; } float offset = (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) ? 0.75f : 0f; modStamp.transform.position = FastDestroyableSingleton.Instance.MapButton.transform.position + Vector3.down * offset; - } + }*/ static void Postfix(PingTracker __instance){ __instance.text.alignment = TMPro.TextAlignmentOptions.TopRight; if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) { - __instance.text.text = $"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()}\n" + __instance.text.text; + string gameModeText = $""; + if (HideNSeek.isHideNSeekGM) gameModeText = $"Hide 'N Seek"; + else if (HandleGuesser.isGuesserGm) gameModeText = $"Guesser"; + if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n"; + __instance.text.text = $"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays > 0 ? "-BETA" : "")}\n{gameModeText}" + __instance.text.text; if (CachedPlayer.LocalPlayer.Data.IsDead || (!(CachedPlayer.LocalPlayer.PlayerControl == null) && (CachedPlayer.LocalPlayer.PlayerControl == Lovers.lover1 || CachedPlayer.LocalPlayer.PlayerControl == Lovers.lover2))) { __instance.transform.localPosition = new Vector3(3.45f, __instance.transform.localPosition.y, __instance.transform.localPosition.z); } else { __instance.transform.localPosition = new Vector3(4.2f, __instance.transform.localPosition.y, __instance.transform.localPosition.z); } } else { - __instance.text.text = $"{fullCredentials}\n{__instance.text.text}"; + string gameModeText = $""; + if (MapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek"; + else if (MapOptions.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}"; __instance.transform.localPosition = new Vector3(3.5f, __instance.transform.localPosition.y, __instance.transform.localPosition.z); } } diff --git a/TheOtherRoles/Patches/EndGamePatch.cs b/TheOtherRoles/Patches/EndGamePatch.cs index d462434..16a1f49 100644 --- a/TheOtherRoles/Patches/EndGamePatch.cs +++ b/TheOtherRoles/Patches/EndGamePatch.cs @@ -8,6 +8,7 @@ using System; using System.Text; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles.Patches { enum CustomGameOverReason { @@ -17,7 +18,7 @@ namespace TheOtherRoles.Patches { JesterWin = 13, ArsonistWin = 14, VultureWin = 15, - LawyerSoloWin = 16 + ProsecutorWin = 16 } enum WinCondition { @@ -29,9 +30,9 @@ namespace TheOtherRoles.Patches { MiniLose, ArsonistWin, VultureWin, - LawyerSoloWin, AdditionalLawyerBonusWin, - AdditionalAlivePursuerWin + AdditionalAlivePursuerWin, + ProsecutorWin } static class AdditionalTempData { @@ -39,11 +40,13 @@ namespace TheOtherRoles.Patches { public static WinCondition winCondition = WinCondition.Default; public static List additionalWinConditions = new List(); public static List playerRoles = new List(); + public static float timer = 0; public static void clear() { playerRoles.Clear(); additionalWinConditions.Clear(); winCondition = WinCondition.Default; + timer = 0; } internal class PlayerRoleInfo { @@ -51,6 +54,8 @@ namespace TheOtherRoles.Patches { public List Roles {get;set;} public int TasksCompleted {get;set;} public int TasksTotal {get;set;} + public bool IsGuesser {get; set;} + public int? Kills {get; set;} } } @@ -72,7 +77,12 @@ namespace TheOtherRoles.Patches { foreach(var playerControl in CachedPlayer.AllPlayers) { var roles = RoleInfo.getRoleInfoForPlayer(playerControl); var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(playerControl.Data); - AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted }); + bool isGuesser = HandleGuesser.isGuesserGm && HandleGuesser.isGuesser(playerControl.PlayerId); + int? killCount = GameHistory.deadPlayers.FindAll(x => x.killerIfExisting != null && x.killerIfExisting.PlayerId == playerControl.PlayerId).Count; + if (killCount == 0 && !(new List() { RoleInfo.sheriff, RoleInfo.jackal, RoleInfo.sidekick, RoleInfo.thief }.Contains(RoleInfo.getRoleInfoForPlayer(playerControl, false).FirstOrDefault()) || playerControl.Data.Role.IsImpostor)) { + killCount = null; + } + AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted, IsGuesser = isGuesser, Kills = killCount }); } // Remove Jester, Arsonist, Vulture, Jackal, former Jackals and Sidekick from winners (if they win, they'll be readded) @@ -84,6 +94,7 @@ namespace TheOtherRoles.Patches { if (Vulture.vulture != null) notWinners.Add(Vulture.vulture); if (Lawyer.lawyer != null) notWinners.Add(Lawyer.lawyer); if (Pursuer.pursuer != null) notWinners.Add(Pursuer.pursuer); + if (Thief.thief != null) notWinners.Add(Thief.thief); notWinners.AddRange(Jackal.formerJackals); @@ -99,7 +110,7 @@ namespace TheOtherRoles.Patches { bool loversWin = Lovers.existingAndAlive() && (gameOverReason == (GameOverReason)CustomGameOverReason.LoversWin || (TempData.DidHumansWin(gameOverReason) && !Lovers.existingWithKiller())); // Either they win if they are among the last 3 players, or they win if they are both Crewmates and both alive and the Crew wins (Team Imp/Jackal Lovers can only win solo wins) bool teamJackalWin = gameOverReason == (GameOverReason)CustomGameOverReason.TeamJackalWin && ((Jackal.jackal != null && !Jackal.jackal.Data.IsDead) || (Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead)); bool vultureWin = Vulture.vulture != null && gameOverReason == (GameOverReason)CustomGameOverReason.VultureWin; - bool lawyerSoloWin = Lawyer.lawyer != null && gameOverReason == (GameOverReason)CustomGameOverReason.LawyerSoloWin; + bool prosecutorWin = Lawyer.lawyer != null && gameOverReason == (GameOverReason)CustomGameOverReason.ProsecutorWin; bool isPursurerLose = jesterWin || arsonistWin || miniLose || vultureWin || teamJackalWin; @@ -136,6 +147,14 @@ namespace TheOtherRoles.Patches { AdditionalTempData.winCondition = WinCondition.VultureWin; } + // Jester win + else if (prosecutorWin) { + TempData.winners = new Il2CppSystem.Collections.Generic.List(); + WinningPlayerData wpd = new WinningPlayerData(Lawyer.lawyer.Data); + TempData.winners.Add(wpd); + AdditionalTempData.winCondition = WinCondition.ProsecutorWin; + } + // Lovers win conditions else if (loversWin) { // Double win for lovers, crewmates also win @@ -182,16 +201,8 @@ namespace TheOtherRoles.Patches { } } - // Lawyer solo win - else if (lawyerSoloWin && !Pursuer.notAckedExiled) { - TempData.winners = new Il2CppSystem.Collections.Generic.List(); - WinningPlayerData wpd = new WinningPlayerData(Lawyer.lawyer.Data); - TempData.winners.Add(wpd); - AdditionalTempData.winCondition = WinCondition.LawyerSoloWin; - } - // Possible Additional winner: Lawyer - if (!lawyerSoloWin && Lawyer.lawyer != null && Lawyer.target != null && (!Lawyer.target.Data.IsDead || Lawyer.target == Jester.jester) && !Pursuer.notAckedExiled) { + if (Lawyer.lawyer != null && Lawyer.target != null && (!Lawyer.target.Data.IsDead || Lawyer.target == Jester.jester) && !Pursuer.notAckedExiled && !Lawyer.isProsecutor) { WinningPlayerData winningClient = null; foreach (WinningPlayerData winner in TempData.winners.GetFastEnumerator()) { if (winner.PlayerName == Lawyer.target.Data.PlayerName) @@ -211,7 +222,10 @@ namespace TheOtherRoles.Patches { AdditionalTempData.additionalWinConditions.Add(WinCondition.AdditionalAlivePursuerWin); } + AdditionalTempData.timer = ((float)(DateTime.UtcNow - HideNSeek.startTime).TotalMilliseconds) / 1000; + // Reset Settings + if (MapOptions.gameMode == CustomGamemodes.HideNSeek) ShipStatusPatch.resetVanillaSettings(); RPCProcedure.resetVariables(); } } @@ -283,8 +297,8 @@ namespace TheOtherRoles.Patches { textRenderer.text = "Vulture Wins"; textRenderer.color = Vulture.color; } - else if (AdditionalTempData.winCondition == WinCondition.LawyerSoloWin) { - textRenderer.text = "Lawyer Wins"; + else if (AdditionalTempData.winCondition == WinCondition.ProsecutorWin) { + textRenderer.text = "Prosecutor Wins"; textRenderer.color = Lawyer.color; } else if (AdditionalTempData.winCondition == WinCondition.LoversTeamWin) { @@ -314,18 +328,25 @@ namespace TheOtherRoles.Patches { } } - if (MapOptions.showRoleSummary) { + if (MapOptions.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); roleSummary.transform.localScale = new Vector3(1f, 1f, 1f); var roleSummaryText = new StringBuilder(); + if (HideNSeek.isHideNSeekGM) { + int minutes = (int)AdditionalTempData.timer / 60; + int seconds = (int)AdditionalTempData.timer % 60; + roleSummaryText.AppendLine($"Time: {minutes:00}:{seconds:00} \n"); + } roleSummaryText.AppendLine("Players and roles at the end of the game:"); foreach(var data in AdditionalTempData.playerRoles) { var roles = string.Join(" ", data.Roles.Select(x => Helpers.cs(x.color, x.name))); + if (data.IsGuesser) roles += " (Guesser)"; var taskInfo = data.TasksTotal > 0 ? $" - ({data.TasksCompleted}/{data.TasksTotal})" : ""; - roleSummaryText.AppendLine($"{data.PlayerName} - {roles}{taskInfo}"); + if (data.Kills != null) taskInfo += $" - (Kills: {data.Kills})"; + roleSummaryText.AppendLine($"{data.PlayerName} - {roles}{taskInfo}"); } TMPro.TMP_Text roleSummaryTextMesh = roleSummary.GetComponent(); roleSummaryTextMesh.alignment = TMPro.TextAlignmentOptions.TopLeft; @@ -351,11 +372,11 @@ namespace TheOtherRoles.Patches { var statistics = new PlayerStatistics(__instance); if (CheckAndEndGameForMiniLose(__instance)) return false; if (CheckAndEndGameForJesterWin(__instance)) return false; - if (CheckAndEndGameForLawyerMeetingWin(__instance)) return false; if (CheckAndEndGameForArsonistWin(__instance)) return false; if (CheckAndEndGameForVultureWin(__instance)) return false; if (CheckAndEndGameForSabotageWin(__instance)) return false; if (CheckAndEndGameForTaskWin(__instance)) return false; + if (CheckAndEndGameForProsecutorWin(__instance)) return false; if (CheckAndEndGameForLoverWin(__instance, statistics)) return false; if (CheckAndEndGameForJackalWin(__instance, statistics)) return false; if (CheckAndEndGameForImpostorWin(__instance, statistics)) return false; @@ -398,15 +419,6 @@ namespace TheOtherRoles.Patches { } return false; } - - private static bool CheckAndEndGameForLawyerMeetingWin(ShipStatus __instance) { - if (Lawyer.triggerLawyerWin) { - __instance.enabled = false; - ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.LawyerSoloWin, false); - return true; - } - return false; - } private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) { if (MapUtilities.Systems == null) return false; @@ -435,6 +447,7 @@ namespace TheOtherRoles.Patches { } private static bool CheckAndEndGameForTaskWin(ShipStatus __instance) { + if (HideNSeek.isHideNSeekGM && !HideNSeek.taskWinPossible) return false; if (GameData.Instance.TotalTasks > 0 && GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) { __instance.enabled = false; ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false); @@ -443,6 +456,15 @@ namespace TheOtherRoles.Patches { return false; } + private static bool CheckAndEndGameForProsecutorWin(ShipStatus __instance) { + if (Lawyer.triggerProsecutorWin) { + __instance.enabled = false; + ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.ProsecutorWin, false); + return true; + } + return false; + } + private static bool CheckAndEndGameForLoverWin(ShipStatus __instance, PlayerStatistics statistics) { if (statistics.TeamLoversAlive == 2 && statistics.TotalAlive <= 3) { __instance.enabled = false; @@ -462,6 +484,9 @@ namespace TheOtherRoles.Patches { } private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerStatistics statistics) { + if (HideNSeek.isHideNSeekGM) + if ((0 != statistics.TotalAlive - statistics.TeamImpostorsAlive)) return false; + if (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && statistics.TeamJackalAlive == 0 && !(statistics.TeamImpostorHasAliveLover && statistics.TeamLoversAlive == 2)) { __instance.enabled = false; GameOverReason endReason; @@ -483,6 +508,11 @@ namespace TheOtherRoles.Patches { } private static bool CheckAndEndGameForCrewmateWin(ShipStatus __instance, PlayerStatistics statistics) { + if (HideNSeek.isHideNSeekGM && HideNSeek.timer <= 0 && !HideNSeek.isWaitingTimer) { + __instance.enabled = false; + ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false); + return true; + } if (statistics.TeamImpostorsAlive == 0 && statistics.TeamJackalAlive == 0) { __instance.enabled = false; ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false); diff --git a/TheOtherRoles/Patches/ExileControllerPatch.cs b/TheOtherRoles/Patches/ExileControllerPatch.cs index ef57546..ccf2661 100644 --- a/TheOtherRoles/Patches/ExileControllerPatch.cs +++ b/TheOtherRoles/Patches/ExileControllerPatch.cs @@ -121,8 +121,10 @@ namespace TheOtherRoles.Patches { if (!SubmergedCompatibility.IsSubmerged) return; if (obj.name.Contains("ExileCutscene")) { WrapUpPostfix(ExileControllerBeginPatch.lastExiled); - } else if (obj.name.Contains("SpawnInMinigame")) + } else if (obj.name.Contains("SpawnInMinigame")) { AntiTeleport.setPosition(); + Chameleon.lastMoved.Clear(); + } } static void WrapUpPostfix(GameData.PlayerInfo exiled) { @@ -133,7 +135,10 @@ namespace TheOtherRoles.Patches { // Jester win condition else if (exiled != null && Jester.jester != null && Jester.jester.PlayerId == exiled.PlayerId) { Jester.triggerJesterWin = true; - } + } + // Prosecutor win condition + else if (exiled != null && Lawyer.lawyer != null && Lawyer.target != null && Lawyer.isProsecutor && Lawyer.target.PlayerId == exiled.PlayerId && !Lawyer.lawyer.Data.IsDead) + Lawyer.triggerProsecutorWin = true; // Reset custom button timers where necessary CustomButton.MeetingEndedUpdate(); @@ -226,6 +231,13 @@ namespace TheOtherRoles.Patches { // Invert add meeting if (Invert.meetings > 0) Invert.meetings--; + + Chameleon.lastMoved.Clear(); + + foreach (Trap trap in Trap.traps) trap.triggerable = false; + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(PlayerControl.GameOptions.KillCooldown / 2 + 2, new Action((p) => { + if (p == 1f) foreach (Trap trap in Trap.traps) trap.triggerable = true; + }))); } } @@ -233,6 +245,7 @@ namespace TheOtherRoles.Patches { class AirshipSpawnInPatch { static void Postfix() { AntiTeleport.setPosition(); + Chameleon.lastMoved.Clear(); } } @@ -259,4 +272,4 @@ namespace TheOtherRoles.Patches { } } } -} \ No newline at end of file +} diff --git a/TheOtherRoles/Patches/FreeNamePatch.cs b/TheOtherRoles/Patches/FreeNamePatch.cs deleted file mode 100644 index 1e4b0d4..0000000 --- a/TheOtherRoles/Patches/FreeNamePatch.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Taken from https://github.com/NuclearPowered/Reactor/ , licensed under the LGPLv3 - -using System; -using System.Collections.Generic; -using System.Linq; -using TheOtherRoles.Utilities; -using TMPro; -using UnityEngine; -using UnityEngine.SceneManagement; -using Object = UnityEngine.Object; - -namespace TheOtherRoles.Patches { - internal class FreeNamePatch { - - public static void Initialize() { - SceneManager.add_sceneLoaded((Action)((scene, _) => { - if (!scene.name.Equals("MMOnline")) return; - if (!TryMoveObjects()) return; - - var editName = FastDestroyableSingleton.Instance.accountTab.editNameScreen; - var nameText = Object.Instantiate(editName.nameText.gameObject); - - nameText.transform.localPosition += Vector3.up * 2.2f; - - var textBox = nameText.GetComponent(); - textBox.outputText.alignment = TextAlignmentOptions.CenterGeoAligned; - textBox.outputText.transform.position = nameText.transform.position; - textBox.outputText.fontSize = 4f; - - textBox.OnChange.AddListener((Action)(() => { - SaveManager.PlayerName = textBox.text; - })); - textBox.OnEnter = textBox.OnFocusLost = textBox.OnChange; - - textBox.Pipe.GetComponent().fontSize = 4f; - })); - } - - private static bool TryMoveObjects() { - var toMove = new List - { - "HostGameButton", - "FindGameButton", - "JoinGameButton" - }; - - var yStart = Vector3.up; - var yOffset = Vector3.down * 1.5f; - - var gameObjects = toMove.Select(x => GameObject.Find("NormalMenu/" + x)).ToList(); - if (gameObjects.Any(x => x == null)) return false; - - for (var i = 0; i < gameObjects.Count; i++) { - gameObjects[i].transform.position = yStart + (yOffset * i); - } - - return true; - } - } -} diff --git a/TheOtherRoles/Patches/GameStartManagerPatch.cs b/TheOtherRoles/Patches/GameStartManagerPatch.cs index 91e2818..97400aa 100644 --- a/TheOtherRoles/Patches/GameStartManagerPatch.cs +++ b/TheOtherRoles/Patches/GameStartManagerPatch.cs @@ -7,6 +7,7 @@ using Hazel; using System; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using System.Linq; namespace TheOtherRoles.Patches { public class GameStartManagerPatch { @@ -43,6 +44,7 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))] public class GameStartManagerUpdatePatch { + public static float startingTimer = 0; private static bool update = false; private static string currentText = ""; @@ -59,7 +61,7 @@ namespace TheOtherRoles.Patches { } // Check version handshake infos - + bool versionMismatch = false; string message = ""; foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients.ToArray()) { @@ -96,6 +98,13 @@ namespace TheOtherRoles.Patches { __instance.StartButton.color = __instance.startLabelText.color = ((__instance.LastPlayerCount >= __instance.MinPlayers) ? Palette.EnabledColor : Palette.DisabledClear); __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; } + + // Make starting info available to clients: + if (startingTimer <= 0 && __instance.startState == GameStartManager.StartingStates.Countdown) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetGameStarting, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.setGameStarting(); + } } // Client update with handshake infos @@ -115,12 +124,22 @@ namespace TheOtherRoles.Patches { __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2; } else { __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition; - if (__instance.startState != GameStartManager.StartingStates.Countdown) { + if (__instance.startState != GameStartManager.StartingStates.Countdown && startingTimer <= 0) { __instance.GameStartText.text = String.Empty; } + else { + __instance.GameStartText.text = $"Starting in {(int)startingTimer + 1}"; + if (startingTimer <= 0) { + __instance.GameStartText.text = String.Empty; + } + } } } + // Start Timer + if (startingTimer > 0) { + startingTimer -= Time.deltaTime; + } // Lobby timer if (!GameData.Instance) return; // No instance @@ -134,6 +153,12 @@ namespace TheOtherRoles.Patches { __instance.PlayerCounter.text = currentText + suffix; __instance.PlayerCounter.autoSizeTextContainer = true; + 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); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shareGamemode((byte) MapOptions.gameMode); + } } } @@ -162,25 +187,52 @@ namespace TheOtherRoles.Patches { break; } } - - if (CustomOptionHolder.dynamicMap.getBool() && continueStart) { + if (continueStart && MapOptions.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); + writer.Write(mapId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.dynamicMapOption(mapId); + } + else if (CustomOptionHolder.dynamicMap.getBool() && continueStart) { // 0 = Skeld // 1 = Mira HQ // 2 = Polus // 3 = Dleks - deactivated // 4 = Airship - List possibleMaps = new List(); - if (CustomOptionHolder.dynamicMapEnableSkeld.getBool()) - possibleMaps.Add(0); - if (CustomOptionHolder.dynamicMapEnableMira.getBool()) - possibleMaps.Add(1); - if (CustomOptionHolder.dynamicMapEnablePolus.getBool()) - possibleMaps.Add(2); - if (CustomOptionHolder.dynamicMapEnableAirShip.getBool()) - possibleMaps.Add(4); - if (CustomOptionHolder.dynamicMapEnableSubmerged.getBool()) - possibleMaps.Add(5); - byte chosenMapId = possibleMaps[TheOtherRoles.rnd.Next(possibleMaps.Count)]; + // 5 = Submerged + byte chosenMapId = 0; + List probabilities = new List(); + probabilities.Add(CustomOptionHolder.dynamicMapEnableSkeld.getSelection() / 10f); + probabilities.Add(CustomOptionHolder.dynamicMapEnableMira.getSelection() / 10f); + probabilities.Add(CustomOptionHolder.dynamicMapEnablePolus.getSelection() / 10f); + probabilities.Add(CustomOptionHolder.dynamicMapEnableAirShip.getSelection() / 10f); + probabilities.Add(CustomOptionHolder.dynamicMapEnableSubmerged.getSelection() / 10f); + + // if any map is at 100%, remove all maps that are not! + if (probabilities.Contains(1.0f)) { + for (int i=0; i < probabilities.Count; i++) { + if (probabilities[i] != 1.0) probabilities[i] = 0; + } + } + + float sum = probabilities.Sum(); + if (sum == 0) return continueStart; // All maps set to 0, why are you doing this??? + for (int i = 0; i < probabilities.Count; i++) { // Normalize to [0,1] + probabilities[i] /= sum; + } + float selection = (float)TheOtherRoles.rnd.NextDouble(); + float cumsum = 0; + for (byte i = 0; i < probabilities.Count; i++) { + cumsum += probabilities[i]; + if (cumsum > selection) { + chosenMapId = i; + break; + } + } + + if (chosenMapId >= 3) chosenMapId++; // Skip dlekS MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.DynamicMapOption, Hazel.SendOption.Reliable, -1); writer.Write(chosenMapId); diff --git a/TheOtherRoles/Patches/IntroPatch.cs b/TheOtherRoles/Patches/IntroPatch.cs index bc47d42..47f0605 100644 --- a/TheOtherRoles/Patches/IntroPatch.cs +++ b/TheOtherRoles/Patches/IntroPatch.cs @@ -7,6 +7,7 @@ using System.Linq; using Hazel; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] @@ -16,6 +17,7 @@ namespace TheOtherRoles.Patches { public static void Prefix(IntroCutscene __instance) { // Generate and initialize player icons int playerCounter = 0; + int hideNSeekCounter = 0; if (CachedPlayer.LocalPlayer != null && FastDestroyableSingleton.Instance != null) { Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.z); foreach (PlayerControl p in CachedPlayer.AllPlayers) { @@ -29,18 +31,30 @@ namespace TheOtherRoles.Patches { player.cosmetics.nameText.text = data.PlayerName; player.SetFlipX(true); MapOptions.playerIcons[p.PlayerId] = player; + player.gameObject.SetActive(false); if (CachedPlayer.LocalPlayer.PlayerControl == Arsonist.arsonist && p != Arsonist.arsonist) { player.transform.localPosition = bottomLeft + new Vector3(-0.25f, -0.25f, 0) + Vector3.right * playerCounter++ * 0.35f; player.transform.localScale = Vector3.one * 0.2f; player.setSemiTransparent(true); player.gameObject.SetActive(true); - } else if (CachedPlayer.LocalPlayer.PlayerControl == BountyHunter.bountyHunter) { + } else if (HideNSeek.isHideNSeekGM) { + if (HideNSeek.isHunted() && p.Data.Role.IsImpostor) { + player.transform.localPosition = bottomLeft + new Vector3(-0.25f, 0.4f, 0) + Vector3.right * playerCounter++ * 0.6f; + player.transform.localScale = Vector3.one * 0.3f; + player.cosmetics.nameText.text += $"{Helpers.cs(Color.red, " (Hunter)")}"; + player.gameObject.SetActive(true); + } else if (!p.Data.Role.IsImpostor) { + player.transform.localPosition = bottomLeft + new Vector3(-0.35f, -0.25f, 0) + Vector3.right * hideNSeekCounter++ * 0.35f; + player.transform.localScale = Vector3.one * 0.2f; + player.setSemiTransparent(true); + player.gameObject.SetActive(true); + } + + } else { // This can be done for all players not just for the bounty hunter as it was before. Allows the thief to have the correct position and scaling player.transform.localPosition = bottomLeft + new Vector3(-0.25f, 0f, 0); player.transform.localScale = Vector3.one * 0.4f; player.gameObject.SetActive(false); - } else { - player.gameObject.SetActive(false); } } } @@ -61,7 +75,7 @@ namespace TheOtherRoles.Patches { SoundEffectsManager.Load(); // First kill - if (AmongUsClient.Instance.AmHost && MapOptions.shieldFirstKill && MapOptions.firstKillName != "") { + 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 (target != null) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetFirstKill, Hazel.SendOption.Reliable, -1); @@ -71,6 +85,50 @@ namespace TheOtherRoles.Patches { } } MapOptions.firstKillName = ""; + + if (HideNSeek.isHideNSeekGM) { + foreach (PlayerControl player in HideNSeek.getHunters()) { + player.moveable = false; + player.NetTransform.Halt(); + HideNSeek.timer = HideNSeek.hunterWaitingTime; + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(HideNSeek.hunterWaitingTime, new Action((p) => { + if (p == 1f) { + player.moveable = true; + HideNSeek.timer = CustomOptionHolder.hideNSeekTimer.getFloat() * 60; + HideNSeek.isWaitingTimer = false; + } + }))); + } + + if (HideNSeek.polusVent == null && PlayerControl.GameOptions.MapId == 2) { + var list = GameObject.FindObjectsOfType().ToList(); + var adminVent = list.FirstOrDefault(x => x.gameObject.name == "AdminVent"); + var bathroomVent = list.FirstOrDefault(x => x.gameObject.name == "BathroomVent"); + HideNSeek.polusVent = UnityEngine.Object.Instantiate(adminVent); + HideNSeek.polusVent.gameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover); + HideNSeek.polusVent.transform.position = new Vector3(36.55068f, -21.5168f, -0.0215168f); + HideNSeek.polusVent.Left = adminVent; + HideNSeek.polusVent.Right = bathroomVent; + HideNSeek.polusVent.Center = null; + HideNSeek.polusVent.Id = MapUtilities.CachedShipStatus.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id + var allVentsList = MapUtilities.CachedShipStatus.AllVents.ToList(); + allVentsList.Add(HideNSeek.polusVent); + MapUtilities.CachedShipStatus.AllVents = allVentsList.ToArray(); + HideNSeek.polusVent.gameObject.SetActive(true); + HideNSeek.polusVent.name = "newVent_" + HideNSeek.polusVent.Id; + + adminVent.Center = HideNSeek.polusVent; + bathroomVent.Center = HideNSeek.polusVent; + } + + ShipStatusPatch.originalNumCrewVisionOption = PlayerControl.GameOptions.CrewLightMod; + ShipStatusPatch.originalNumImpVisionOption = PlayerControl.GameOptions.ImpostorLightMod; + ShipStatusPatch.originalNumKillCooldownOption = PlayerControl.GameOptions.killCooldown; + + PlayerControl.GameOptions.ImpostorLightMod = CustomOptionHolder.hideNSeekHunterVision.getFloat(); + PlayerControl.GameOptions.CrewLightMod = CustomOptionHolder.hideNSeekHuntedVision.getFloat(); + PlayerControl.GameOptions.KillCooldown = CustomOptionHolder.hideNSeekKillCooldown.getFloat(); + } } } @@ -78,7 +136,7 @@ namespace TheOtherRoles.Patches { class IntroPatch { public static void setupIntroTeamIcons(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { // Intro solo teams - if (CachedPlayer.LocalPlayer.PlayerControl == Jester.jester || CachedPlayer.LocalPlayer.PlayerControl == Jackal.jackal || CachedPlayer.LocalPlayer.PlayerControl == Arsonist.arsonist || CachedPlayer.LocalPlayer.PlayerControl == Vulture.vulture || CachedPlayer.LocalPlayer.PlayerControl == Lawyer.lawyer) { + if (Helpers.isNeutral(CachedPlayer.LocalPlayer.PlayerControl)) { var soloTeam = new Il2CppSystem.Collections.Generic.List(); soloTeam.Add(CachedPlayer.LocalPlayer.PlayerControl); yourTeam = soloTeam; diff --git a/TheOtherRoles/Patches/MainMenuPatch.cs b/TheOtherRoles/Patches/MainMenuPatch.cs index 4fbe205..42e0dfc 100644 --- a/TheOtherRoles/Patches/MainMenuPatch.cs +++ b/TheOtherRoles/Patches/MainMenuPatch.cs @@ -94,30 +94,33 @@ namespace TheOtherRoles.Modules { popUp.Init(); //SelectableHyperLinkHelper.DestroyGOs(popUp.selectableHyperLinks, "test"); string creditsString = @$"Github Contributors: -Gendelo Alex2911 amsyarasyiq MaximeGillot Psynomit probablyadnf JustASysAdmin +Discord Moderators: +Streamblox Draco Cordraconis + +Thanks to all our discord helpers! + "; creditsString += $@" Other Credits & Resources: -OxygenFilter - For all the version v2.3.0 to v2.6.1, we were using the OxygenFilter for automatic deobfuscation -Reactor - The framework used for all version before v2.0.0 +OxygenFilter - For the versions v2.3.0 to v2.6.1, we were using the OxygenFilter for automatic deobfuscation +Reactor - The framework used for all versions before v2.0.0, and again since 4.2.0 BepInEx - Used to hook game functions Essentials - Custom game options by DorCoMaNdO: - Before v1.6: We used the default Essentials release -v1.6-v1.8: We slightly changed the default Essentials. The changes can be found on this branch of our fork. -v2.0.0 and later: As we're not using Reactor anymore, we are using our own implementation, inspired by the one from DorCoMaNdO -Jackal and Sidekick - Original idea for the Jackal and Sidekick comes from Dhalucard -Among-Us-Love-Couple-Mod - Idea for the Lovers role comes from Woodi-dev -Jester - Idea for the Jester role comes from Maartii -ExtraRolesAmongUs - Idea for the Engineer and Medic role comes from NotHunter101. Also some code snippets come of the implementation were used. -Among-Us-Sheriff-Mod - Idea for the Sheriff role comes from Woodi-dev -TooManyRolesMods - Idea for the Detective and Time Master roles comes from Hardel-DW. Also some code snippets of the implementation were used. -TownOfUs - Idea for the Swapper, Shifter, Arsonist and a similar Mayor role come from Slushiegoose -Ottomated - Idea for the Morphling, Snitch and Camouflager role come from Ottomated -Crowded-Mod - Our implementation for 10+ player lobbies is inspired by the one from the Crowded Mod Team -Goose-Goose-Duck - Idea for the Vulture role come from Slushygoose"; +v1.6-v1.8: We slightly changed the default Essentials. +v2.0.0 and later: As we were not using Reactor anymore, we are using our own implementation, inspired by the one from DorCoMaNdO +Jackal and Sidekick - Original idea for the Jackal and Sidekick came from Dhalucard +Among-Us-Love-Couple-Mod - Idea for the Lovers modifier comes from Woodi-dev +Jester - Idea for the Jester role came from Maartii +ExtraRolesAmongUs - Idea for the Engineer and Medic role came from NotHunter101. Also some code snippets from their implementation were used. +Among-Us-Sheriff-Mod - Idea for the Sheriff role came from Woodi-dev +TooManyRolesMods - Idea for the Detective and Time Master roles comes from Hardel-DW. Also some code snippets from their implementation were used. +TownOfUs - Idea for the Swapper, Shifter, Arsonist and a similar Mayor role came from Slushiegoose +Ottomated - Idea for the Morphling, Snitch and Camouflager role came from Ottomated +Crowded-Mod - Our implementation for 10+ player lobbies was inspired by the one from the Crowded Mod Team +Goose-Goose-Duck - Idea for the Vulture role came from Slushiegoose"; creditsString += ""; popUp.AnnounceTextMeshPro.text = creditsString; __instance.StartCoroutine(Effects.Lerp(0.01f, new Action((p) => { diff --git a/TheOtherRoles/Patches/MapBehaviourPatch.cs b/TheOtherRoles/Patches/MapBehaviourPatch.cs index 47ac6c6..e9629ac 100644 --- a/TheOtherRoles/Patches/MapBehaviourPatch.cs +++ b/TheOtherRoles/Patches/MapBehaviourPatch.cs @@ -1,4 +1,7 @@ using HarmonyLib; +using System.Collections.Generic; +using System.Linq; +using TheOtherRoles.Objects; using TheOtherRoles.Players; using TheOtherRoles.Utilities; using UnityEngine; @@ -8,21 +11,31 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(MapBehaviour))] class MapBehaviourPatch { + private static Dictionary herePoints = new Dictionary(); [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.FixedUpdate))] - static bool Prefix(MapBehaviour __instance) { - if (!MeetingHud.Instance) return true; // Only run in meetings, and then set the Position of the HerePoint to the Position before the Meeting! - if (!MapUtilities.CachedShipStatus) { - return false; + static void Postfix(MapBehaviour __instance) { + if (Trapper.trapper != null && CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) { + foreach (PlayerControl player in Trapper.playersOnMap) { + if (herePoints.ContainsKey(player)) continue; + Vector3 v = Trap.trapPlayerIdMap[player.PlayerId].trap.transform.position; + v /= MapUtilities.CachedShipStatus.MapScale; + v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x); + v.z = -1f; + var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true); + herePoint.transform.localPosition = v; + herePoint.enabled = true; + int colorId = player.CurrentOutfit.ColorId; + if (Trapper.anonymousMap) player.CurrentOutfit.ColorId = 6; + player.SetPlayerMaterialColors(herePoint); + player.CurrentOutfit.ColorId = colorId; + herePoints.Add(player, herePoint); + } + foreach (var s in herePoints.Where(x => !Trapper.playersOnMap.Contains(x.Key)).ToList()) { + UnityEngine.Object.Destroy(s.Value); + herePoints.Remove(s.Key); + } } - Vector3 vector = AntiTeleport.position != null ? AntiTeleport.position : CachedPlayer.LocalPlayer.transform.position; - vector /= MapUtilities.CachedShipStatus.MapScale; - vector.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x); - vector.z = -1f; - __instance.HerePoint.transform.localPosition = vector; - CachedPlayer.LocalPlayer.PlayerControl.SetPlayerMaterialColors(__instance.HerePoint); - - return false; } } } diff --git a/TheOtherRoles/Patches/MeetingPatch.cs b/TheOtherRoles/Patches/MeetingPatch.cs index 3c95bb8..20f8d11 100644 --- a/TheOtherRoles/Patches/MeetingPatch.cs +++ b/TheOtherRoles/Patches/MeetingPatch.cs @@ -20,6 +20,8 @@ namespace TheOtherRoles.Patches { private static TMPro.TextMeshPro swapperChargesText; private static PassiveButton[] swapperButtonList; private static TMPro.TextMeshPro swapperConfirmButtonLabel; + private static PlayerVoteArea swapped1 = null; + private static PlayerVoteArea swapped2 = null; [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))] class MeetingCalculateVotesPatch { @@ -41,8 +43,8 @@ namespace TheOtherRoles.Patches { } // Swapper swap votes if (Swapper.swapper != null && !Swapper.swapper.Data.IsDead) { - PlayerVoteArea swapped1 = null; - PlayerVoteArea swapped2 = null; + swapped1 = null; + swapped2 = null; foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) { if (playerVoteArea.TargetPlayerId == Swapper.playerId1) swapped1 = playerVoteArea; if (playerVoteArea.TargetPlayerId == Swapper.playerId2) swapped2 = playerVoteArea; @@ -79,6 +81,7 @@ namespace TheOtherRoles.Patches { // TieBreaker List potentialExiled = new List(); + bool skipIsTie = false; if (self.Count > 0) { Tiebreaker.isTiebreak = false; int maxVoteValue = self.Values.Max(); @@ -88,9 +91,13 @@ namespace TheOtherRoles.Patches { bool isTiebreakerSkip = tb == null || tb.VotedFor == 253; if (tb != null && tb.AmDead) isTiebreakerSkip = true; - foreach (KeyValuePair pair in self) - if (pair.Value == maxVoteValue && !isTiebreakerSkip && pair.Key != 253) + foreach (KeyValuePair pair in self) { + if (pair.Value != maxVoteValue || isTiebreakerSkip) continue; + if (pair.Key != 253) potentialExiled.Add(GameData.Instance.AllPlayers.ToArray().FirstOrDefault(x => x.PlayerId == pair.Key)); + else + skipIsTie = true; + } } MeetingHud.VoterState[] array = new MeetingHud.VoterState[__instance.playerStates.Length]; @@ -102,8 +109,16 @@ namespace TheOtherRoles.Patches { VotedForId = playerVoteArea.VotedFor }; - if (Tiebreaker.tiebreaker != null && tie && playerVoteArea.TargetPlayerId == Tiebreaker.tiebreaker.PlayerId && potentialExiled.FindAll(x => x != null && x.PlayerId == playerVoteArea.VotedFor).Count > 0) { - exiled = potentialExiled.ToArray().FirstOrDefault(v => v.PlayerId == playerVoteArea.VotedFor); + if (Tiebreaker.tiebreaker == null || playerVoteArea.TargetPlayerId != Tiebreaker.tiebreaker.PlayerId) continue; + + byte tiebreakerVote = playerVoteArea.VotedFor; + if (swapped1 != null && swapped2 != null) { + if (tiebreakerVote == swapped1.TargetPlayerId) tiebreakerVote = swapped2.TargetPlayerId; + else if (tiebreakerVote == swapped2.TargetPlayerId) tiebreakerVote = swapped1.TargetPlayerId; + } + + if (potentialExiled.FindAll(x => x != null && x.PlayerId == tiebreakerVote).Count > 0 && (potentialExiled.Count > 1 || skipIsTie)) { + exiled = potentialExiled.ToArray().FirstOrDefault(v => v.PlayerId == tiebreakerVote); tie = false; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetTiebreak, Hazel.SendOption.Reliable, -1); @@ -205,9 +220,11 @@ namespace TheOtherRoles.Patches { Pursuer.notAckedExiled = false; if (exiled != null) { Lovers.notAckedExiledIsLover = ((Lovers.lover1 != null && Lovers.lover1.PlayerId == exiled.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == exiled.PlayerId)); - Pursuer.notAckedExiled = (Pursuer.pursuer != null && Pursuer.pursuer.PlayerId == exiled.PlayerId) || (Lawyer.lawyer != null && Lawyer.target != null && Lawyer.target.PlayerId == exiled.PlayerId && Lawyer.target != Jester.jester); + Pursuer.notAckedExiled = (Pursuer.pursuer != null && Pursuer.pursuer.PlayerId == exiled.PlayerId) || (Lawyer.lawyer != null && Lawyer.target != null && Lawyer.target.PlayerId == exiled.PlayerId && Lawyer.target != Jester.jester && !Lawyer.isProsecutor); } - + + // Mini + if (!Mini.isGrowingUpInMeeting) Mini.timeOfGrowthStart = Mini.timeOfGrowthStart.Add(DateTime.UtcNow.Subtract(Mini.timeOfMeetingStart)); } } @@ -276,6 +293,7 @@ namespace TheOtherRoles.Patches { public static GameObject guesserUI; public static PassiveButton guesserUIExitButton; + public static byte guesserCurrentTarget; static void guesserOnClick(int buttonTarget, MeetingHud __instance) { if (guesserUI != null || !(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted)) return; __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(false)); @@ -290,6 +308,8 @@ namespace TheOtherRoles.Patches { var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton"); var textTemplate = __instance.playerStates[0].NameText; + guesserCurrentTarget = __instance.playerStates[buttonTarget].TargetPlayerId; + Transform exitButtonParent = (new GameObject()).transform; exitButtonParent.SetParent(container); Transform exitButton = UnityEngine.Object.Instantiate(buttonTemplate.transform, exitButtonParent); @@ -312,7 +332,9 @@ namespace TheOtherRoles.Patches { foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) { RoleId guesserRole = (Guesser.niceGuesser != null && CachedPlayer.LocalPlayer.PlayerId == Guesser.niceGuesser.PlayerId) ? RoleId.NiceGuesser : RoleId.EvilGuesser; - if (roleInfo.isModifier || roleInfo.roleId == guesserRole || (!Guesser.evilGuesserCanGuessSpy && guesserRole == RoleId.EvilGuesser && roleInfo.roleId == RoleId.Spy)) continue; // Not guessable roles & modifier + if (roleInfo.isModifier || roleInfo.roleId == guesserRole || (!HandleGuesser.evilGuesserCanGuessSpy && guesserRole == RoleId.EvilGuesser && roleInfo.roleId == RoleId.Spy && !HandleGuesser.isGuesserGm)) continue; // Not guessable roles & modifier + if (HandleGuesser.isGuesserGm && (roleInfo.roleId == RoleId.NiceGuesser || roleInfo.roleId == RoleId.EvilGuesser)) continue; // remove Guesser for guesser game mode + if (HandleGuesser.isGuesserGm && CachedPlayer.LocalPlayer.PlayerControl.Data.Role.IsImpostor && !HandleGuesser.evilGuesserCanGuessSpy && roleInfo.roleId == RoleId.Spy) continue; // remove all roles that cannot spawn due to the settings from the ui. RoleManagerSelectRolesPatch.RoleAssignmentData roleData = RoleManagerSelectRolesPatch.getRoleAssignmentData(); if (roleData.neutralSettings.ContainsKey((byte)roleInfo.roleId) && roleData.neutralSettings[(byte)roleInfo.roleId] == 0) continue; @@ -323,8 +345,9 @@ namespace TheOtherRoles.Patches { if (roleInfo.roleId == RoleId.Deputy && (CustomOptionHolder.deputySpawnRate.getSelection() == 0 || CustomOptionHolder.sheriffSpawnRate.getSelection() == 0)) continue; if (roleInfo.roleId == RoleId.Pursuer && CustomOptionHolder.lawyerSpawnRate.getSelection() == 0) continue; if (roleInfo.roleId == RoleId.Spy && roleData.impostors.Count <= 1) continue; - - if (Guesser.guesserCantGuessSnitch && Snitch.snitch != null) { + if (roleInfo.roleId == RoleId.Prosecutor && (CustomOptionHolder.lawyerIsProsecutorChance.getSelection() == 0 || CustomOptionHolder.lawyerSpawnRate.getSelection() == 0)) continue; + if (roleInfo.roleId == RoleId.Lawyer && (CustomOptionHolder.lawyerIsProsecutorChance.getSelection() == 10 || CustomOptionHolder.lawyerSpawnRate.getSelection() == 0)) continue; + if (Snitch.snitch != null && HandleGuesser.guesserCantGuessSnitch) { var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data); int numberOfLeftTasks = playerTotal - playerCompleted; if (numberOfLeftTasks <= 0 && roleInfo.roleId == RoleId.Snitch) continue; @@ -347,15 +370,15 @@ namespace TheOtherRoles.Patches { int copiedIndex = i; button.GetComponent().OnClick.RemoveAllListeners(); - if (!CachedPlayer.LocalPlayer.Data.IsDead) button.GetComponent().OnClick.AddListener((System.Action)(() => { + if (!CachedPlayer.LocalPlayer.Data.IsDead && !Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId).Data.IsDead) button.GetComponent().OnClick.AddListener((System.Action)(() => { if (selectedButton != button) { selectedButton = button; buttons.ForEach(x => x.GetComponent().color = x == selectedButton ? Color.red : Color.white); } else { PlayerControl focusedTarget = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId); - if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || focusedTarget == null || Guesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId) <= 0 ) return; + if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || focusedTarget == null || HandleGuesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId) <= 0 ) return; - if (!Guesser.killsThroughShield && focusedTarget == Medic.shielded) { // Depending on the options, shooting the shielded player will not allow the guess, notifiy everyone about the kill attempt and close the window + if (!HandleGuesser.killsThroughShield && focusedTarget == Medic.shielded) { // Depending on the options, shooting the shielded player will not allow the guess, notifiy everyone about the kill attempt and close the window __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); UnityEngine.Object.Destroy(container.gameObject); @@ -374,7 +397,7 @@ namespace TheOtherRoles.Patches { // Reset the GUI __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true)); UnityEngine.Object.Destroy(container.gameObject); - if (Guesser.hasMultipleShotsPerMeeting && Guesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId) > 1 && dyingTarget != CachedPlayer.LocalPlayer.PlayerControl) + if (HandleGuesser.hasMultipleShotsPerMeeting && HandleGuesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId) > 1 && dyingTarget != CachedPlayer.LocalPlayer.PlayerControl) __instance.playerStates.ToList().ForEach(x => { if (x.TargetPlayerId == dyingTarget.PlayerId && x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); else __instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); @@ -398,7 +421,7 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Select))] class PlayerVoteAreaSelectPatch { static bool Prefix(MeetingHud __instance) { - return !(CachedPlayer.LocalPlayer != null && Guesser.isGuesser(CachedPlayer.LocalPlayer.PlayerId) && guesserUI != null); + return !(CachedPlayer.LocalPlayer != null && HandleGuesser.isGuesser(CachedPlayer.LocalPlayer.PlayerId) && guesserUI != null); } } @@ -418,6 +441,7 @@ namespace TheOtherRoles.Patches { checkbox.transform.SetParent(playerVoteArea.transform); checkbox.transform.position = template.transform.position; checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.3f); + if (HandleGuesser.isGuesserGm && HandleGuesser.isGuesser(CachedPlayer.LocalPlayer.PlayerId)) checkbox.transform.localPosition = new Vector3(-0.5f, 0.03f, -1.3f); SpriteRenderer renderer = checkbox.GetComponent(); renderer.sprite = Swapper.getCheckSprite(); renderer.color = Color.red; @@ -494,17 +518,21 @@ namespace TheOtherRoles.Patches { } // Add Guesser Buttons - if (Guesser.isGuesser(CachedPlayer.LocalPlayer.PlayerId) && !CachedPlayer.LocalPlayer.Data.IsDead && Guesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId) > 0) { + bool isGuesser = HandleGuesser.isGuesser(CachedPlayer.LocalPlayer.PlayerId); + int remainingShots = HandleGuesser.remainingShots(CachedPlayer.LocalPlayer.PlayerId); + + if (isGuesser && !CachedPlayer.LocalPlayer.Data.IsDead && remainingShots > 0) { for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; if (playerVoteArea.AmDead || playerVoteArea.TargetPlayerId == CachedPlayer.LocalPlayer.PlayerId) continue; + if (CachedPlayer.LocalPlayer != null && CachedPlayer.LocalPlayer.PlayerControl == Eraser.eraser && Eraser.alreadyErased.Contains(playerVoteArea.TargetPlayerId)) continue; GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject; GameObject targetBox = UnityEngine.Object.Instantiate(template, playerVoteArea.transform); targetBox.name = "ShootButton"; targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1.3f); SpriteRenderer renderer = targetBox.GetComponent(); - renderer.sprite = Guesser.getTargetSprite(); + renderer.sprite = HandleGuesser.getTargetSprite(); PassiveButton button = targetBox.GetComponent(); button.OnClick.RemoveAllListeners(); int copiedIndex = i; @@ -546,6 +574,9 @@ namespace TheOtherRoles.Patches { // Medium meeting start time Medium.meetingStartTime = DateTime.UtcNow; + // Mini + Mini.timeOfMeetingStart = DateTime.UtcNow; + Mini.ageOnMeetingStart = Mathf.FloorToInt(Mini.growingProgress() * 18); // Reset vampire bitten Vampire.bitten = null; // Count meetings @@ -563,7 +594,30 @@ namespace TheOtherRoles.Patches { FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{msg}"); } } - + + // Add trapped Info into Trapper chat + if (Trapper.trapper != null && CachedPlayer.LocalPlayer.PlayerControl == Trapper.trapper) { + 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"; + else if (Trapper.infoType == 1) { + if (Helpers.isNeutral(p) || p.Data.Role.IsImpostor) message += "Evil Role \n"; + else message += "Good Role \n"; + } + else message += p.Data.PlayerName + "\n"; + } + FastDestroyableSingleton.Instance.Chat.AddChat(CachedPlayer.LocalPlayer.PlayerControl, $"{message}"); + } + } + + Trapper.playersOnMap = new List(); + + // Remove revealed traps + Trap.clearRevealedTraps(); + // Reset zoomed out ghosts Helpers.toggleZoom(reset: true); diff --git a/TheOtherRoles/Patches/NameFix.cs b/TheOtherRoles/Patches/NameFix.cs index 9a21ea7..70e8dc5 100644 --- a/TheOtherRoles/Patches/NameFix.cs +++ b/TheOtherRoles/Patches/NameFix.cs @@ -1,4 +1,6 @@ using HarmonyLib; +using AmongUs.Data; +using AmongUs.Data.Legacy; namespace TheOtherRoles.Patches { [Harmony] @@ -6,9 +8,9 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(AccountManager), nameof(AccountManager.RandomizeName))] public static class RandomizeNamePatch { static bool Prefix(AccountManager __instance) { - if (SaveManager.lastPlayerName == null) + if (LegacySaveManager.lastPlayerName == null) return true; - SaveManager.PlayerName = SaveManager.lastPlayerName; + DataManager.Player.Customization.Name = LegacySaveManager.lastPlayerName; __instance.accountTab.UpdateNameDisplay(); return false; // Don't execute original } diff --git a/TheOtherRoles/Patches/PlayerControlPatch.cs b/TheOtherRoles/Patches/PlayerControlPatch.cs index f71a800..5967970 100644 --- a/TheOtherRoles/Patches/PlayerControlPatch.cs +++ b/TheOtherRoles/Patches/PlayerControlPatch.cs @@ -9,6 +9,8 @@ using TheOtherRoles.Objects; using TheOtherRoles.Players; using TheOtherRoles.Utilities; using UnityEngine; +using TheOtherRoles.CustomGameModes; +using static UnityEngine.GraphicsBuffer; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] @@ -48,6 +50,8 @@ namespace TheOtherRoles.Patches { static void setPlayerOutline(PlayerControl target, Color color) { if (target == null || target.cosmetics?.currentBodySprite?.BodySprite == null) return; + color = color.SetAlpha(Chameleon.visibility(target.PlayerId)); + target.cosmetics.currentBodySprite.BodySprite.material.SetFloat("_Outline", 1f); target.cosmetics.currentBodySprite.BodySprite.material.SetColor("_OutlineColor", color); } @@ -136,7 +140,7 @@ namespace TheOtherRoles.Patches { static void shifterSetTarget() { if (Shifter.shifter == null || Shifter.shifter != CachedPlayer.LocalPlayer.PlayerControl) return; Shifter.currentTarget = setTarget(); - if (Shifter.futureShift == null) setPlayerOutline(Shifter.currentTarget, Shifter.color); + if (Shifter.futureShift == null) setPlayerOutline(Shifter.currentTarget, Color.yellow); } @@ -464,6 +468,7 @@ namespace TheOtherRoles.Patches { playerInfo.transform.localPosition += Vector3.up * 0.5f; playerInfo.fontSize *= 0.75f; playerInfo.gameObject.name = "Info"; + playerInfo.color = playerInfo.color.SetAlpha(1f); } PlayerVoteArea playerVoteArea = MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == p.PlayerId); @@ -471,7 +476,7 @@ namespace TheOtherRoles.Patches { TMPro.TextMeshPro meetingInfo = meetingInfoTransform != null ? meetingInfoTransform.GetComponent() : null; if (meetingInfo == null && playerVoteArea != null) { meetingInfo = UnityEngine.Object.Instantiate(playerVoteArea.NameText, playerVoteArea.NameText.transform.parent); - meetingInfo.transform.localPosition += Vector3.down * 0.10f; + meetingInfo.transform.localPosition += Vector3.down * 0.16f; meetingInfo.fontSize *= 0.60f; meetingInfo.gameObject.name = "Info"; } @@ -727,7 +732,7 @@ namespace TheOtherRoles.Patches { if (Lawyer.lawyer == null || Lawyer.lawyer != CachedPlayer.LocalPlayer.PlayerControl) return; // Promote to Pursuer - if (Lawyer.target != null && Lawyer.target.Data.Disconnected) { + if (Lawyer.target != null && Lawyer.target.Data.Disconnected && !Lawyer.lawyer.Data.IsDead) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.lawyerPromotesToPursuer(); @@ -784,10 +789,21 @@ namespace TheOtherRoles.Patches { if (Mini.mini != null && !Mini.isGrownUp()) untargetables.Add(Mini.mini); if (Sidekick.wasTeamRed && !Spy.impostorsCanKillAnyone) untargetables.Add(Sidekick.sidekick); if (Jackal.wasTeamRed && !Spy.impostorsCanKillAnyone) untargetables.Add(Jackal.jackal); - Ninja.currentTarget = setTarget(onlyCrewmates: true, untargetablePlayers: untargetables); + Ninja.currentTarget = setTarget(onlyCrewmates: Spy.spy == null || !Spy.impostorsCanKillAnyone, untargetablePlayers: untargetables); setPlayerOutline(Ninja.currentTarget, Ninja.color); } + static void thiefSetTarget() { + if (Thief.thief == null || Thief.thief != CachedPlayer.LocalPlayer.PlayerControl) return; + List untargetables = new List(); + if (Mini.mini != null && !Mini.isGrownUp()) untargetables.Add(Mini.mini); + Thief.currentTarget = setTarget(onlyCrewmates: false, untargetablePlayers: untargetables); + setPlayerOutline(Thief.currentTarget, Thief.color); + } + + + + static void baitUpdate() { if (!Bait.active.Any()) return; @@ -836,14 +852,92 @@ namespace TheOtherRoles.Patches { HudManagerStartPatch.cleanerCleanButton.MaxTimer = Cleaner.cooldown * multiplier; HudManagerStartPatch.witchSpellButton.MaxTimer = (Witch.cooldown + Witch.currentCooldownAddition) * multiplier; HudManagerStartPatch.ninjaButton.MaxTimer = Ninja.cooldown * multiplier; + HudManagerStartPatch.thiefKillButton.MaxTimer = Thief.cooldown * multiplier; + } + } + + public static void trapperUpdate() { + if (Trapper.trapper == null || CachedPlayer.LocalPlayer.PlayerControl != Trapper.trapper || Trapper.trapper.Data.IsDead) return; + var (playerCompleted, _) = TasksHandler.taskInfo(Trapper.trapper.Data); + if (playerCompleted == Trapper.rechargedTasks) { + Trapper.rechargedTasks += Trapper.rechargeTasksNumber; + if (Trapper.maxCharges > Trapper.charges) Trapper.charges++; + } + } + + static void hunterUpdate() { + if (!HideNSeek.isHideNSeekGM) return; + int minutes = (int)HideNSeek.timer / 60; + int seconds = (int)HideNSeek.timer % 60; + string suffix = $" {minutes:00}:{seconds:00}"; + + if (HideNSeek.timerText == null) { + RoomTracker roomTracker = FastDestroyableSingleton.Instance?.roomTracker; + if (roomTracker != null) { + GameObject gameObject = UnityEngine.Object.Instantiate(roomTracker.gameObject); + + gameObject.transform.SetParent(FastDestroyableSingleton.Instance.transform); + UnityEngine.Object.DestroyImmediate(gameObject.GetComponent()); + HideNSeek.timerText = gameObject.GetComponent(); + + // Use local position to place it in the player's view instead of the world location + gameObject.transform.localPosition = new Vector3(0, -1.8f, gameObject.transform.localPosition.z); + if (AmongUs.Data.DataManager.Settings.Gameplay.StreamerMode) gameObject.transform.localPosition = new Vector3(0, 2f, gameObject.transform.localPosition.z); + } + } else { + if (HideNSeek.isWaitingTimer) { + HideNSeek.timerText.text = "" + suffix + ""; + HideNSeek.timerText.color = Color.blue; + } else { + HideNSeek.timerText.text = "" + suffix + ""; + HideNSeek.timerText.color = Color.red; + } + } + if (HideNSeek.isHunted() && !Hunted.taskPunish && !HideNSeek.isWaitingTimer) { + var (playerCompleted, playerTotal) = TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data); + int numberOfTasks = playerTotal - playerCompleted; + if (numberOfTasks == 0) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.ShareTimer, Hazel.SendOption.Reliable, -1); + writer.Write(HideNSeek.taskPunish); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.shareTimer(HideNSeek.taskPunish); + + Hunted.taskPunish = true; + } + } + + if (!HideNSeek.isHunter()) return; + + byte playerId = CachedPlayer.LocalPlayer.PlayerId; + foreach (Arrow arrow in Hunter.localArrows) arrow.arrow.SetActive(false); + if (Hunter.arrowActive) { + int arrowIndex = 0; + foreach (PlayerControl p in CachedPlayer.AllPlayers) { + if (!p.Data.IsDead && !p.Data.Role.IsImpostor) { + if (arrowIndex >= Hunter.localArrows.Count) { + Hunter.localArrows.Add(new Arrow(Color.blue)); + } + if (arrowIndex < Hunter.localArrows.Count && Hunter.localArrows[arrowIndex] != null) { + Hunter.localArrows[arrowIndex].arrow.SetActive(true); + Hunter.localArrows[arrowIndex].Update(p.transform.position, Color.blue); + } + arrowIndex++; + } + } } } - public static void Postfix(PlayerControl __instance) { + + public static void Postfix(PlayerControl __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; // Mini and Morphling shrink playerSizeUpdate(__instance); + // set position of colorblind text + foreach (var pc in PlayerControl.AllPlayerControls) { + pc.cosmetics.colorBlindText.gameObject.transform.localPosition = new Vector3(0, 0, -0.0001f); + } + if (CachedPlayer.LocalPlayer.PlayerControl == __instance) { // Update player outlines setBasePlayerOutlines(); @@ -874,6 +968,7 @@ namespace TheOtherRoles.Patches { // Vampire vampireSetTarget(); Garlic.UpdateAll(); + Trap.Update(); // Eraser eraserSetTarget(); // Engineer @@ -917,19 +1012,28 @@ namespace TheOtherRoles.Patches { ninjaSetTarget(); NinjaTrace.UpdateAll(); ninjaUpdate(); + // Thief + thiefSetTarget(); + hackerUpdate(); swapperUpdate(); - // Hacker hackerUpdate(); + // Trapper + trapperUpdate(); - // --MODIFIER-- + // -- MODIFIER-- // Bait baitUpdate(); // Bloody bloodyUpdate(); // mini (for the cooldowns) miniCooldownUpdate(); + // Chameleon (invis stuff, timers) + Chameleon.update(); + + // -- GAME MODE -- + hunterUpdate(); } } } @@ -949,8 +1053,10 @@ namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))] class PlayerControlCmdReportDeadBodyPatch { - public static void Prefix(PlayerControl __instance) { + public static bool Prefix(PlayerControl __instance) { + if (HideNSeek.isHideNSeekGM) return false; Helpers.handleVampireBiteOnBodyReport(); + return true; } } @@ -1025,7 +1131,7 @@ namespace TheOtherRoles.Patches { if (resetToDead) __instance.Data.IsDead = true; // Remove fake tasks when player dies - if (target.hasFakeTasks()) + if (target.hasFakeTasks() || target == Lawyer.lawyer || target == Pursuer.pursuer || target == Thief.thief) target.clearAllTasks(); // First kill (set before lover suicide) @@ -1047,7 +1153,7 @@ namespace TheOtherRoles.Patches { } // Pursuer promotion trigger on murder (the host sends the call such that everyone recieves the update before a possible game End) - if (target == Lawyer.target && AmongUsClient.Instance.AmHost) { + if (target == Lawyer.target && AmongUsClient.Instance.AmHost && Lawyer.lawyer != null) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.lawyerPromotesToPursuer(); @@ -1102,7 +1208,6 @@ namespace TheOtherRoles.Patches { if (Ninja.ninja != null && CachedPlayer.LocalPlayer.PlayerControl == Ninja.ninja && __instance == Ninja.ninja && HudManagerStartPatch.ninjaButton != null) HudManagerStartPatch.ninjaButton.Timer = HudManagerStartPatch.ninjaButton.MaxTimer; - // Bait if (Bait.bait.FindAll(x => x.PlayerId == target.PlayerId).Count > 0) { float reportDelay = (float) rnd.Next((int)Bait.reportDelayMin, (int)Bait.reportDelayMax + 1); @@ -1130,6 +1235,23 @@ namespace TheOtherRoles.Patches { } Helpers.showFlash(color, 1.5f); } + + // HideNSeek + if (HideNSeek.isHideNSeekGM) { + int visibleCounter = 0; + Vector3 bottomLeft = new Vector3(-FastDestroyableSingleton.Instance.UseButton.transform.localPosition.x, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.y, FastDestroyableSingleton.Instance.UseButton.transform.localPosition.z); + bottomLeft += new Vector3(-0.35f, -0.25f, 0); + foreach (PlayerControl p in CachedPlayer.AllPlayers) { + if (!MapOptions.playerIcons.ContainsKey(p.PlayerId) || p.Data.Role.IsImpostor) continue; + if (p.Data.IsDead || p.Data.Disconnected) { + MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false); + } + else { + MapOptions.playerIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f; + visibleCounter++; + } + } + } } } @@ -1185,7 +1307,7 @@ namespace TheOtherRoles.Patches { GameHistory.deadPlayers.Add(deadPlayer); // Remove fake tasks when player dies - if (__instance.hasFakeTasks()) + if (__instance.hasFakeTasks() || __instance == Lawyer.lawyer || __instance == Pursuer.pursuer || __instance == Thief.thief) __instance.clearAllTasks(); // Lover suicide trigger on exile @@ -1203,17 +1325,18 @@ namespace TheOtherRoles.Patches { } // Pursuer promotion trigger on exile & suicide (the host sends the call such that everyone recieves the update before a possible game End) - if (__instance == Lawyer.target && Lawyer.target != Jester.jester && AmongUsClient.Instance.AmHost) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.lawyerPromotesToPursuer(); - } - if (__instance == Lawyer.target && !Lawyer.targetWasGuessed) - { - if (Lawyer.lawyer != null) Lawyer.lawyer.Exiled(); - if (Pursuer.pursuer != null) Pursuer.pursuer.Exiled(); - } + if (Lawyer.lawyer != null && __instance == Lawyer.target) { + if (AmongUsClient.Instance.AmHost && ((Lawyer.target != Jester.jester && !Lawyer.isProsecutor) || Lawyer.targetWasGuessed)) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.lawyerPromotesToPursuer(); + } + if (!Lawyer.targetWasGuessed && !Lawyer.isProsecutor) { + if (Lawyer.lawyer != null) Lawyer.lawyer.Exiled(); + if (Pursuer.pursuer != null) Pursuer.pursuer.Exiled(); + } + } } } diff --git a/TheOtherRoles/Patches/RoleAssignmentPatch.cs b/TheOtherRoles/Patches/RoleAssignmentPatch.cs index f870b43..2e6dcfa 100644 --- a/TheOtherRoles/Patches/RoleAssignmentPatch.cs +++ b/TheOtherRoles/Patches/RoleAssignmentPatch.cs @@ -7,6 +7,7 @@ using System; using TheOtherRoles.Players; using TheOtherRoles.Utilities; using static TheOtherRoles.TheOtherRoles; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(RoleOptionsData), nameof(RoleOptionsData.GetNumPerGame))] @@ -16,17 +17,28 @@ namespace TheOtherRoles.Patches { } } + [HarmonyPatch(typeof(GameOptionsData), nameof(GameOptionsData.GetAdjustedNumImpostors))] + class GameOptionsDataGetAdjustedNumImpostorsPatch { + public static void Postfix(ref int __result) { + if (MapOptions.gameMode == CustomGamemodes.HideNSeek) { + int impCount = Mathf.RoundToInt(CustomOptionHolder.hideNSeekHunterCount.getFloat()); + __result = impCount; ; // Set Imp Num + } + } + } + [HarmonyPatch(typeof(RoleManager), nameof(RoleManager.SelectRoles))] class RoleManagerSelectRolesPatch { private static int crewValues; private static int impValues; private static bool isEvilGuesser; private static List> playerRoleMap = new List>(); + public static bool isGuesserGamemode { get { return MapOptions.gameMode == CustomGamemodes.Guesser; } } public static 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) return; // Don't assign Roles in Hide N Seek if (CustomOptionHolder.activateRoles.getBool()) // Don't assign Roles in Tutorial or if deactivated assignRoles(); } @@ -38,8 +50,9 @@ namespace TheOtherRoles.Patches { assignEnsuredRoles(data); // Assign roles that should always be in the game next assignDependentRoles(data); // Assign roles that may have a dependent role assignChanceRoles(data); // Assign roles that may or may not be in the game last - assignRoleTargets(data); - assignModifiers(); + assignRoleTargets(data); // Assign targets for Lawyer & Prosecutor + if (isGuesserGamemode) assignGuesserGamemode(); + assignModifiers(); // Assign modifier setRolesAgain(); } @@ -92,7 +105,12 @@ namespace TheOtherRoles.Patches { neutralSettings.Add((byte)RoleId.Arsonist, CustomOptionHolder.arsonistSpawnRate.getSelection()); neutralSettings.Add((byte)RoleId.Jackal, CustomOptionHolder.jackalSpawnRate.getSelection()); neutralSettings.Add((byte)RoleId.Vulture, CustomOptionHolder.vultureSpawnRate.getSelection()); - neutralSettings.Add((byte)RoleId.Lawyer, CustomOptionHolder.lawyerSpawnRate.getSelection()); + neutralSettings.Add((byte)RoleId.Thief, CustomOptionHolder.thiefSpawnRate.getSelection()); + + if ((rnd.Next(1, 101) <= CustomOptionHolder.lawyerIsProsecutorChance.getSelection() * 10)) // Lawyer or Prosecutor + neutralSettings.Add((byte)RoleId.Prosecutor, CustomOptionHolder.lawyerSpawnRate.getSelection()); + else + neutralSettings.Add((byte)RoleId.Lawyer, CustomOptionHolder.lawyerSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Mayor, CustomOptionHolder.mayorSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Portalmaker, CustomOptionHolder.portalmakerSpawnRate.getSelection()); @@ -101,13 +119,13 @@ namespace TheOtherRoles.Patches { crewSettings.Add((byte)RoleId.Detective, CustomOptionHolder.detectiveSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.TimeMaster, CustomOptionHolder.timeMasterSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Medic, CustomOptionHolder.medicSpawnRate.getSelection()); - crewSettings.Add((byte)RoleId.Shifter, CustomOptionHolder.shifterSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Swapper,CustomOptionHolder.swapperSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Seer, CustomOptionHolder.seerSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Hacker, CustomOptionHolder.hackerSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Tracker, CustomOptionHolder.trackerSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Snitch, CustomOptionHolder.snitchSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Medium, CustomOptionHolder.mediumSpawnRate.getSelection()); + crewSettings.Add((byte)RoleId.Trapper, CustomOptionHolder.trapperSpawnRate.getSelection()); if (impostors.Count > 1) { // Only add Spy if more than 1 impostor as the spy role is otherwise useless crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection()); @@ -137,14 +155,16 @@ namespace TheOtherRoles.Patches { } private static void selectFactionForFactionIndependentRoles(RoleAssignmentData data) { - // Assign Guesser (chance to be impostor based on setting) - isEvilGuesser = rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10; - if ((CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0 && - CustomOptionHolder.guesserSpawnRate.getSelection() == 10) || - CustomOptionHolder.guesserSpawnBothRate.getSelection() == 0) { + if (!isGuesserGamemode) { + // Assign Guesser (chance to be impostor based on setting) + isEvilGuesser = rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10; + if ((CustomOptionHolder.guesserSpawnBothRate.getSelection() > 0 && + CustomOptionHolder.guesserSpawnRate.getSelection() == 10) || + CustomOptionHolder.guesserSpawnBothRate.getSelection() == 0) { if (isEvilGuesser) data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnRate.getSelection()); else data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnRate.getSelection()); + } } // Assign Sheriff @@ -216,6 +236,7 @@ namespace TheOtherRoles.Patches { bool sheriffFlag = CustomOptionHolder.deputySpawnRate.getSelection() > 0 && CustomOptionHolder.sheriffSpawnRate.getSelection() > 0; + if (isGuesserGamemode) guesserFlag = false; if (!guesserFlag && !sheriffFlag) return; // assignDependentRoles is not needed int crew = data.crewmates.Count < data.maxCrewmateRoles ? data.crewmates.Count : data.maxCrewmateRoles; // Max number of crew loops @@ -246,14 +267,17 @@ namespace TheOtherRoles.Patches { data.crewmates.ToList().RemoveAll(x => x.PlayerId == sheriff); data.maxCrewmateRoles--; } - if (!isEvilGuesser && isGuesser && Guesser.niceGuesser == null && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && guesserFlag) { // Set Nice Guesser cause he won the lottery - byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates); - data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser); - data.maxCrewmateRoles--; - } else if (isEvilGuesser && isGuesser && Guesser.evilGuesser == null && data.impostors.Count > 0 && data.maxImpostorRoles > 0 && guesserFlag) { // Set Evil Guesser cause he won the lottery - byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors); - data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser); - data.maxImpostorRoles--; + if (!isGuesserGamemode) { + if (!isEvilGuesser && isGuesser && Guesser.niceGuesser == null && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && guesserFlag) { // Set Nice Guesser cause he won the lottery + byte niceGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates); + data.crewmates.ToList().RemoveAll(x => x.PlayerId == niceGuesser); + data.maxCrewmateRoles--; + } + else if (isEvilGuesser && isGuesser && Guesser.evilGuesser == null && data.impostors.Count > 0 && data.maxImpostorRoles > 0 && guesserFlag) { // Set Evil Guesser cause he won the lottery + byte evilGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors); + data.impostors.ToList().RemoveAll(x => x.PlayerId == evilGuesser); + data.maxImpostorRoles--; + } } // --- Assign Dependent Roles if main role exists --- @@ -265,21 +289,28 @@ namespace TheOtherRoles.Patches { } else if (CustomOptionHolder.deputySpawnRate.getSelection() < 10) // Dont force, add Deputy to the ticket system data.crewSettings.Add((byte)RoleId.Deputy, CustomOptionHolder.deputySpawnRate.getSelection()); } - if (!isEvilGuesser && Guesser.niceGuesser != null) { // Other Guesser (evil) - if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Force other guesser (evil) - byte bothGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors); - data.impostors.ToList().RemoveAll(x => x.PlayerId == bothGuesser); - data.maxImpostorRoles--; - } else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) // Dont force, add Guesser (evil) to the ticket system - data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection()); - } else if (isEvilGuesser && Guesser.evilGuesser != null) { // ELSE other Guesser (nice) - if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Force other guesser (nice) - byte bothGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates); - data.crewmates.ToList().RemoveAll(x => x.PlayerId == bothGuesser); - data.maxCrewmateRoles--; + + if (!data.crewSettings.ContainsKey((byte)RoleId.Sheriff)) data.crewSettings.Add((byte)RoleId.Sheriff, 0); + + if (!isGuesserGamemode) { + if (!isEvilGuesser && Guesser.niceGuesser != null) { // Other Guesser (evil) + if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.impostors.Count > 0 && data.maxImpostorRoles > 0) { // Force other guesser (evil) + byte bothGuesser = setRoleToRandomPlayer((byte)RoleId.EvilGuesser, data.impostors); + data.impostors.ToList().RemoveAll(x => x.PlayerId == bothGuesser); + data.maxImpostorRoles--; + } + else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) // Dont force, add Guesser (evil) to the ticket system + data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection()); + } + else if (isEvilGuesser && Guesser.evilGuesser != null) { // ELSE other Guesser (nice) + if (CustomOptionHolder.guesserSpawnBothRate.getSelection() == 10 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) { // Force other guesser (nice) + byte bothGuesser = setRoleToRandomPlayer((byte)RoleId.NiceGuesser, data.crewmates); + data.crewmates.ToList().RemoveAll(x => x.PlayerId == bothGuesser); + data.maxCrewmateRoles--; + } + else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) // Dont force, add Guesser (nice) to the ticket system + data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection()); } - else if (CustomOptionHolder.guesserSpawnBothRate.getSelection() < 10) // Dont force, add Guesser (nice) to the ticket system - data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection()); } } private static void assignChanceRoles(RoleAssignmentData data) { @@ -330,13 +361,21 @@ namespace TheOtherRoles.Patches { } private static void assignRoleTargets(RoleAssignmentData data) { - // Set Lawyer Target + // Set Lawyer or Prosecutor Target if (Lawyer.lawyer != null) { var possibleTargets = new List(); - foreach (PlayerControl p in CachedPlayer.AllPlayers) { - if (!p.Data.IsDead && !p.Data.Disconnected && p != Lovers.lover1 && p != Lovers.lover2 && (p.Data.Role.IsImpostor || p == Jackal.jackal || (Lawyer.targetCanBeJester && p == Jester.jester))) - possibleTargets.Add(p); + if (!Lawyer.isProsecutor) { // Lawyer + foreach (PlayerControl p in CachedPlayer.AllPlayers) { + if (!p.Data.IsDead && !p.Data.Disconnected && p != Lovers.lover1 && p != Lovers.lover2 && (p.Data.Role.IsImpostor || p == Jackal.jackal || (Lawyer.targetCanBeJester && p == Jester.jester))) + possibleTargets.Add(p); + } + } else { // Prosecutor + foreach (PlayerControl p in CachedPlayer.AllPlayers) { + if (!p.Data.IsDead && !p.Data.Disconnected && p != Lovers.lover1 && p != Lovers.lover2 && p != Mini.mini && !p.Data.Role.IsImpostor && !Helpers.isNeutral(p) && p != Swapper.swapper) + possibleTargets.Add(p); + } } + if (possibleTargets.Count == 0) { MessageWriter w = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.LawyerPromotesToPursuer, Hazel.SendOption.Reliable, -1); AmongUsClient.Instance.FinishRpcImmediately(w); @@ -357,6 +396,8 @@ namespace TheOtherRoles.Patches { if (modifierMin > modifierMax) modifierMin = modifierMax; int modifierCountSettings = rnd.Next(modifierMin, modifierMax + 1); List players = PlayerControl.AllPlayerControls.ToArray().ToList(); + if (isGuesserGamemode && !CustomOptionHolder.guesserGamemodeHaveModifier.getBool()) + players.RemoveAll(x => GuesserGM.isGuesser(x.PlayerId)); int modifierCount = Mathf.Min(players.Count, modifierCountSettings); if (modifierCount == 0) return; @@ -372,7 +413,9 @@ namespace TheOtherRoles.Patches { RoleId.AntiTeleport, RoleId.Sunglasses, RoleId.Vip, - RoleId.Invert + RoleId.Invert, + RoleId.Chameleon, + RoleId.Shifter }); if (rnd.Next(1, 101) <= CustomOptionHolder.modifierLover.getSelection() * 10) { // Assign lover @@ -381,7 +424,7 @@ namespace TheOtherRoles.Patches { List impPlayer = new List(players); List crewPlayer = new List(players); impPlayer.RemoveAll(x => !x.Data.Role.IsImpostor); - crewPlayer.RemoveAll(x => x.Data.Role.IsImpostor); + crewPlayer.RemoveAll(x => x.Data.Role.IsImpostor || x == Lawyer.lawyer); if (isEvilLover) firstLoverId = setModifierToRandomPlayer((byte)RoleId.Lover, impPlayer); else firstLoverId = setModifierToRandomPlayer((byte)RoleId.Lover, crewPlayer); @@ -418,6 +461,36 @@ namespace TheOtherRoles.Patches { assignModifiersToPlayers(chanceModifierToAssign, players, modifierCount); // Assign chance modifier } + private static void assignGuesserGamemode() { + List impPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); + List neutralPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); + List crewPlayer = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList(); + impPlayer.RemoveAll(x => !x.Data.Role.IsImpostor); + neutralPlayer.RemoveAll(x => !Helpers.isNeutral(x)); + crewPlayer.RemoveAll(x => x.Data.Role.IsImpostor || Helpers.isNeutral(x)); + assignGuesserGamemodeToPlayers(crewPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeCrewNumber.getFloat())); + assignGuesserGamemodeToPlayers(neutralPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeNeutralNumber.getFloat()), CustomOptionHolder.guesserForceJackalGuesser.getBool()); + assignGuesserGamemodeToPlayers(impPlayer, Mathf.RoundToInt(CustomOptionHolder.guesserGamemodeImpNumber.getFloat())); + } + + private static void assignGuesserGamemodeToPlayers(List playerList, int count, bool forceJackal = false) { + for (int i = 0; i < count && playerList.Count > 0; i++) { + var index = rnd.Next(0, playerList.Count); + if (forceJackal) { + if (Jackal.jackal != null) + index = playerList.FindIndex(x => x == Jackal.jackal); + forceJackal = false; + } + byte playerId = playerList[index].PlayerId; + playerList.RemoveAt(index); + + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.SetGuesserGm, Hazel.SendOption.Reliable, -1); + writer.Write(playerId); + AmongUsClient.Instance.FinishRpcImmediately(writer); + RPCProcedure.setGuesserGm(playerId); + } + } + private static byte setRoleToRandomPlayer(byte roleId, List playerList, bool removePlayer = true) { var index = rnd.Next(0, playerList.Count); byte playerId = playerList[index].PlayerId; @@ -456,9 +529,18 @@ namespace TheOtherRoles.Patches { } byte playerId; + + List crewPlayer = new List(playerList); + crewPlayer.RemoveAll(x => x.Data.Role.IsImpostor || RoleInfo.getRoleInfoForPlayer(x).Any(r => r.isNeutral)); + if (modifiers.Contains(RoleId.Shifter)) { + var crewPlayerShifter = new List(crewPlayer); + crewPlayerShifter.RemoveAll(x => x == Spy.spy); + playerId = setModifierToRandomPlayer((byte)RoleId.Shifter, crewPlayerShifter); + crewPlayer.RemoveAll(x => x.PlayerId == playerId); + playerList.RemoveAll(x => x.PlayerId == playerId); + modifiers.RemoveAll(x => x == RoleId.Shifter); + } if (modifiers.Contains(RoleId.Sunglasses)) { - List crewPlayer = new List(playerList); - crewPlayer.RemoveAll(x => x.Data.Role.IsImpostor || RoleInfo.getRoleInfoForPlayer(x).Any(r => r.isNeutral)); int sunglassesCount = 0; while (sunglassesCount < modifiers.FindAll(x => x == RoleId.Sunglasses).Count) { playerId = setModifierToRandomPlayer((byte)RoleId.Sunglasses, crewPlayer); @@ -509,6 +591,12 @@ namespace TheOtherRoles.Patches { selection = CustomOptionHolder.modifierInvert.getSelection(); if (multiplyQuantity) selection *= CustomOptionHolder.modifierInvertQuantity.getQuantity(); break; + case RoleId.Chameleon: + selection = CustomOptionHolder.modifierChameleon.getSelection(); + if (multiplyQuantity) selection *= CustomOptionHolder.modifierChameleonQuantity.getQuantity(); + break; + case RoleId.Shifter: + selection = CustomOptionHolder.modifierShifter.getSelection(); break; } return selection; @@ -533,7 +621,6 @@ namespace TheOtherRoles.Patches { } } - public class RoleAssignmentData { public List crewmates {get;set;} public List impostors {get;set;} diff --git a/TheOtherRoles/Patches/ShipStatusPatch.cs b/TheOtherRoles/Patches/ShipStatusPatch.cs index cd20d65..c95456e 100644 --- a/TheOtherRoles/Patches/ShipStatusPatch.cs +++ b/TheOtherRoles/Patches/ShipStatusPatch.cs @@ -4,6 +4,7 @@ using HarmonyLib; using TheOtherRoles.Utilities; using static TheOtherRoles.TheOtherRoles; using UnityEngine; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles.Patches { @@ -15,15 +16,14 @@ namespace TheOtherRoles.Patches { public static bool Prefix(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo player) { if (!__instance.Systems.ContainsKey(SystemTypes.Electrical)) return true; - // If player is a role which has Impostor vision - if (player.Role.IsImpostor - || (Jackal.jackal != null && Jackal.jackal.PlayerId == player.PlayerId && Jackal.hasImpostorVision) - || (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == player.PlayerId && Sidekick.hasImpostorVision) - || (Spy.spy != null && Spy.spy.PlayerId == player.PlayerId && Spy.hasImpostorVision) - || (Jester.jester != null && Jester.jester.PlayerId == player.PlayerId && Jester.hasImpostorVision)) { - //__result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod; - __result = GetNeutralLightRadius(__instance, true); - return false; + + if (!HideNSeek.isHideNSeekGM || (HideNSeek.isHideNSeekGM && !Hunter.lightActive.Contains(player.PlayerId))) { + // If player is a role which has Impostor vision + if (Helpers.hasImpVision(player)) { + //__result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod; + __result = GetNeutralLightRadius(__instance, true); + return false; + } } // If player is Lighter with ability active @@ -32,6 +32,12 @@ namespace TheOtherRoles.Patches { __result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, unlerped); } + // If Game mode is Hide N Seek and hunter with ability active + else if (HideNSeek.isHideNSeekGM && Hunter.isLightActive(player.PlayerId)) { + float unlerped = Mathf.InverseLerp(__instance.MinLightRadius, __instance.MaxLightRadius, GetNeutralLightRadius(__instance, false)); + __result = Mathf.Lerp(__instance.MaxLightRadius * Hunter.lightVision, __instance.MaxLightRadius * Hunter.lightVision, unlerped); + } + // If there is a Trickster with their ability active else if (Trickster.trickster != null && Trickster.lightsOutTimer > 0f) { float lerpValue = 1f; @@ -84,20 +90,32 @@ namespace TheOtherRoles.Patches { private static int originalNumCommonTasksOption = 0; private static int originalNumShortTasksOption = 0; private static int originalNumLongTasksOption = 0; + public static float originalNumCrewVisionOption = 0; + public static float originalNumImpVisionOption = 0; + public static float originalNumKillCooldownOption = 0; [HarmonyPrefix] [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))] public static bool Prefix(ShipStatus __instance) { - var commonTaskCount = __instance.CommonTasks.Count; - var normalTaskCount = __instance.NormalTasks.Count; - var longTaskCount = __instance.LongTasks.Count; originalNumCommonTasksOption = PlayerControl.GameOptions.NumCommonTasks; originalNumShortTasksOption = PlayerControl.GameOptions.NumShortTasks; originalNumLongTasksOption = PlayerControl.GameOptions.NumLongTasks; - if(PlayerControl.GameOptions.NumCommonTasks > commonTaskCount) PlayerControl.GameOptions.NumCommonTasks = commonTaskCount; - if(PlayerControl.GameOptions.NumShortTasks > normalTaskCount) PlayerControl.GameOptions.NumShortTasks = normalTaskCount; - if(PlayerControl.GameOptions.NumLongTasks > longTaskCount) PlayerControl.GameOptions.NumLongTasks = longTaskCount; + + if (MapOptions.gameMode != CustomGamemodes.HideNSeek) { + var commonTaskCount = __instance.CommonTasks.Count; + var normalTaskCount = __instance.NormalTasks.Count; + var longTaskCount = __instance.LongTasks.Count; + + if (PlayerControl.GameOptions.NumCommonTasks > commonTaskCount) PlayerControl.GameOptions.NumCommonTasks = commonTaskCount; + if (PlayerControl.GameOptions.NumShortTasks > normalTaskCount) PlayerControl.GameOptions.NumShortTasks = normalTaskCount; + if (PlayerControl.GameOptions.NumLongTasks > longTaskCount) PlayerControl.GameOptions.NumLongTasks = longTaskCount; + } else { + PlayerControl.GameOptions.NumCommonTasks = Mathf.RoundToInt(CustomOptionHolder.hideNSeekCommonTasks.getFloat()); + PlayerControl.GameOptions.NumShortTasks = Mathf.RoundToInt(CustomOptionHolder.hideNSeekShortTasks.getFloat()); + PlayerControl.GameOptions.NumLongTasks = Mathf.RoundToInt(CustomOptionHolder.hideNSeekLongTasks.getFloat()); + } + return true; } @@ -110,5 +128,11 @@ namespace TheOtherRoles.Patches { PlayerControl.GameOptions.NumShortTasks = originalNumShortTasksOption; PlayerControl.GameOptions.NumLongTasks = originalNumLongTasksOption; } + + public static void resetVanillaSettings() { + PlayerControl.GameOptions.ImpostorLightMod = originalNumImpVisionOption; + PlayerControl.GameOptions.CrewLightMod = originalNumCrewVisionOption; + PlayerControl.GameOptions.KillCooldown = originalNumKillCooldownOption; + } } } diff --git a/TheOtherRoles/Patches/UpdatePatch.cs b/TheOtherRoles/Patches/UpdatePatch.cs index 7e0fac9..90a2cfa 100644 --- a/TheOtherRoles/Patches/UpdatePatch.cs +++ b/TheOtherRoles/Patches/UpdatePatch.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles.Patches { [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))] @@ -36,6 +37,7 @@ namespace TheOtherRoles.Patches { nameText.text = Helpers.hidePlayerName(localPlayer, player) ? "" : playerName; nameText.color = color = amImpostor && data.Role.IsImpostor ? Palette.ImpostorRed : Color.white; + nameText.color = nameText.color.SetAlpha(Chameleon.visibility(player.PlayerId)); } else { @@ -59,17 +61,19 @@ namespace TheOtherRoles.Patches { } static void setPlayerNameColor(PlayerControl p, Color color) { - p.cosmetics.nameText.color = color; + p.cosmetics.nameText.color = color.SetAlpha(Chameleon.visibility(p.PlayerId)); if (MeetingHud.Instance != null) foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) if (player.NameText != null && p.PlayerId == player.TargetPlayerId) player.NameText.color = color; } - static void setNameColors() - { + static void setNameColors() { var localPlayer = CachedPlayer.LocalPlayer.PlayerControl; - if (Jester.jester != null && Jester.jester == localPlayer) + var localRole = RoleInfo.getRoleInfoForPlayer(localPlayer, false).FirstOrDefault(); + setPlayerNameColor(localPlayer, localRole.color); + + /*if (Jester.jester != null && Jester.jester == localPlayer) setPlayerNameColor(Jester.jester, Jester.color); else if (Mayor.mayor != null && Mayor.mayor == localPlayer) setPlayerNameColor(Mayor.mayor, Mayor.color); @@ -80,12 +84,13 @@ namespace TheOtherRoles.Patches { if (Deputy.deputy != null && Deputy.knowsSheriff) { setPlayerNameColor(Deputy.deputy, Deputy.color); } - } else if (Deputy.deputy != null && Deputy.deputy == localPlayer) { + } else*/ + if (Deputy.deputy != null && Deputy.deputy == localPlayer) { setPlayerNameColor(Deputy.deputy, Deputy.color); if (Sheriff.sheriff != null && Deputy.knowsSheriff) { setPlayerNameColor(Sheriff.sheriff, Sheriff.color); } - } else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == localPlayer) + } /*else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == localPlayer) setPlayerNameColor(Portalmaker.portalmaker, Portalmaker.color); else if (Lighter.lighter != null && Lighter.lighter == localPlayer) setPlayerNameColor(Lighter.lighter, Lighter.color); @@ -106,7 +111,7 @@ namespace TheOtherRoles.Patches { else if (Tracker.tracker != null && Tracker.tracker == localPlayer) setPlayerNameColor(Tracker.tracker, Tracker.color); else if (Snitch.snitch != null && Snitch.snitch == localPlayer) - setPlayerNameColor(Snitch.snitch, Snitch.color); + setPlayerNameColor(Snitch.snitch, Snitch.color);*/ else if (Jackal.jackal != null && Jackal.jackal == localPlayer) { // Jackal can see his sidekick setPlayerNameColor(Jackal.jackal, Jackal.color); @@ -117,7 +122,7 @@ namespace TheOtherRoles.Patches { setPlayerNameColor(Jackal.fakeSidekick, Jackal.color); } } - else if (Spy.spy != null && Spy.spy == localPlayer) { + /*else if (Spy.spy != null && Spy.spy == localPlayer) { setPlayerNameColor(Spy.spy, Spy.color); } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == localPlayer) { setPlayerNameColor(SecurityGuard.securityGuard, SecurityGuard.color); @@ -131,11 +136,13 @@ namespace TheOtherRoles.Patches { setPlayerNameColor(Vulture.vulture, Vulture.color); } else if (Medium.medium != null && Medium.medium == localPlayer) { setPlayerNameColor(Medium.medium, Medium.color); + } else if (Trapper.trapper != null && Trapper.trapper == localPlayer) { + setPlayerNameColor(Trapper.trapper, Trapper.color); } else if (Lawyer.lawyer != null && Lawyer.lawyer == localPlayer) { setPlayerNameColor(Lawyer.lawyer, Lawyer.color); } else if (Pursuer.pursuer != null && Pursuer.pursuer == localPlayer) { setPlayerNameColor(Pursuer.pursuer, Pursuer.color); - } + }*/ // No else if here, as a Lover of team Jackal needs the colors if (Sidekick.sidekick != null && Sidekick.sidekick == localPlayer) { @@ -193,14 +200,26 @@ namespace TheOtherRoles.Patches { player.NameText.text += suffix; } - // Lawyer - if (Lawyer.lawyer != null && Lawyer.target != null && Lawyer.lawyer == CachedPlayer.LocalPlayer.PlayerControl) { - string suffix = Helpers.cs(Lawyer.color, " §"); - Lawyer.target.cosmetics.nameText.text += suffix; + // Lawyer or Prosecutor + if ((Lawyer.lawyer != null && Lawyer.target != null && Lawyer.lawyer == CachedPlayer.LocalPlayer.PlayerControl)) { + Color color = Lawyer.color; + PlayerControl target = Lawyer.target; + string suffix = Helpers.cs(color, " §"); + target.cosmetics.nameText.text += suffix; + + if (MeetingHud.Instance != null) + foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) + if (player.TargetPlayerId == target.PlayerId) + player.NameText.text += suffix; + } + // Former Thief + if (Thief.formerThief != null && (Thief.formerThief == CachedPlayer.LocalPlayer.PlayerControl || CachedPlayer.LocalPlayer.PlayerControl.Data.IsDead)) { + string suffix = Helpers.cs(Thief.color, " $"); + Thief.formerThief.cosmetics.nameText.text += suffix; if (MeetingHud.Instance != null) foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) - if (player.TargetPlayerId == Lawyer.target.PlayerId) + if (player.TargetPlayerId == Thief.formerThief.PlayerId) player.NameText.text += suffix; } @@ -228,6 +247,7 @@ namespace TheOtherRoles.Patches { Trickster.lightsOutTimer -= dt; Tracker.corpsesTrackingTimer -= dt; Ninja.invisibleTimer -= dt; + HideNSeek.timer -= dt; foreach (byte key in Deputy.handcuffedKnows.Keys) Deputy.handcuffedKnows[key] -= dt; } @@ -240,6 +260,8 @@ namespace TheOtherRoles.Patches { string suffix = ""; if (growingProgress != 1f) suffix = " (" + Mathf.FloorToInt(growingProgress * 18) + ")"; + if (!Mini.isGrowingUpInMeeting && MeetingHud.Instance != null && Mini.ageOnMeetingStart != 0 && !(Mini.ageOnMeetingStart >= 18)) + suffix = " (" + Mini.ageOnMeetingStart + ")"; Mini.mini.cosmetics.nameText.text += suffix; if (MeetingHud.Instance != null) { @@ -292,6 +314,11 @@ namespace TheOtherRoles.Patches { if (MeetingHud.Instance) __instance.SabotageButton.Hide(); } + static void updateMapButton(HudManager __instance) { + if (Trapper.trapper == null || !(CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) || __instance == null || __instance.MapButton == null) return; + __instance.MapButton.color = Trapper.playersOnMap.Any() ? Trapper.color : Color.white; + } + static void Postfix(HudManager __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; @@ -315,6 +342,7 @@ namespace TheOtherRoles.Patches { // Meeting hide buttons if needed (used for the map usage, because closing the map would show buttons) updateSabotageButton(__instance); updateUseButton(__instance); + updateMapButton(__instance); } } diff --git a/TheOtherRoles/Patches/UsablesPatch.cs b/TheOtherRoles/Patches/UsablesPatch.cs index 9314eae..11c277c 100644 --- a/TheOtherRoles/Patches/UsablesPatch.cs +++ b/TheOtherRoles/Patches/UsablesPatch.cs @@ -9,7 +9,8 @@ using static TheOtherRoles.MapOptions; using System.Collections.Generic; using TheOtherRoles.Players; using TheOtherRoles.Utilities; - +using TheOtherRoles.Objects; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles.Patches { @@ -99,11 +100,12 @@ namespace TheOtherRoles.Patches { Deputy.setHandcuffedKnows(); return false; } + if (Trapper.playersOnMap.Contains(CachedPlayer.LocalPlayer.PlayerControl)) return false; bool canUse; bool couldUse; __instance.CanUse(CachedPlayer.LocalPlayer.Data, out canUse, out couldUse); - bool canMoveInVents = CachedPlayer.LocalPlayer.PlayerControl != Spy.spy; + bool canMoveInVents = CachedPlayer.LocalPlayer.PlayerControl != Spy.spy && !Trapper.playersOnMap.Contains(CachedPlayer.LocalPlayer.PlayerControl); if (!canUse) return false; // No need to execute the native method as using is disallowed anyways bool isEnter = !CachedPlayer.LocalPlayer.PlayerControl.inVent; @@ -130,6 +132,13 @@ namespace TheOtherRoles.Patches { } } + [HarmonyPatch(typeof(Vent), nameof(Vent.MoveToVent))] + public static class MoveToVentPatch { + public static bool Prefix(Vent otherVent) { + return !Trapper.playersOnMap.Contains(CachedPlayer.LocalPlayer.PlayerControl); + } + } + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))] class VentButtonVisibilityPatch { static void Postfix(PlayerControl __instance) { @@ -164,7 +173,7 @@ namespace TheOtherRoles.Patches { } // Use an unchecked kill command, to allow shorter kill cooldowns etc. without getting kicked - MurderAttemptResult res = Helpers.checkMuderAttemptAndKill(CachedPlayer.LocalPlayer.PlayerControl, __instance.currentTarget); + MurderAttemptResult res = Helpers.checkMurderAttemptAndKill(CachedPlayer.LocalPlayer.PlayerControl, __instance.currentTarget); // Handle blank kill if (res == MurderAttemptResult.BlankKill) { CachedPlayer.LocalPlayer.PlayerControl.killTimer = PlayerControl.GameOptions.KillCooldown; @@ -221,6 +230,12 @@ namespace TheOtherRoles.Patches { roleCanCallEmergency = false; statusText = "The Jester can't start an emergency meeting"; } + // Potentially deactivate emergency button for Lawyer/Prosecutor + if (Lawyer.lawyer != null && Lawyer.lawyer == CachedPlayer.LocalPlayer.PlayerControl && !Lawyer.canCallEmergency) { + roleCanCallEmergency = false; + statusText = "The Lawyer can't start an emergency meeting"; + if (Lawyer.isProsecutor) statusText = "The Prosecutor can't start an emergency meeting"; + } if (!roleCanCallEmergency) { __instance.StatusText.text = statusText; @@ -549,4 +564,14 @@ namespace TheOtherRoles.Patches { } } + [HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.ShowSabotageMap))] + class ShowSabotageMapPatch { + static bool Prefix(MapBehaviour __instance) { + if (HideNSeek.isHideNSeekGM) + return HideNSeek.canSabotage; + + return true; + } + } + } diff --git a/TheOtherRoles/RPC.cs b/TheOtherRoles/RPC.cs index 9f0246d..293a659 100644 --- a/TheOtherRoles/RPC.cs +++ b/TheOtherRoles/RPC.cs @@ -12,6 +12,8 @@ using UnityEngine; using System; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; +using AmongUs.Data; namespace TheOtherRoles { @@ -29,7 +31,6 @@ namespace TheOtherRoles Detective, TimeMaster, Medic, - Shifter, Swapper, Seer, Morphling, @@ -52,10 +53,13 @@ namespace TheOtherRoles BountyHunter, Vulture, Medium, + Trapper, Lawyer, + Prosecutor, Pursuer, Witch, Ninja, + Thief, Crewmate, Impostor, // Modifier --- @@ -67,7 +71,9 @@ namespace TheOtherRoles Sunglasses, Mini, Vip, - Invert + Invert, + Chameleon, + Shifter } enum CustomRPC @@ -86,10 +92,12 @@ namespace TheOtherRoles UncheckedCmdReportDeadBody, UncheckedExilePlayer, DynamicMapOption, + SetGameStarting, + ShareGamemode, // Role functionality - EngineerFixLights = 91, + EngineerFixLights = 101, EngineerFixSubmergedOxygen, EngineerUsedRepair, CleanBody, @@ -123,7 +131,6 @@ namespace TheOtherRoles ArsonistWin, GuesserShoot, VultureWin, - LawyerWin, LawyerSetTarget, LawyerPromotesToPursuer, SetBlanked, @@ -131,7 +138,16 @@ namespace TheOtherRoles SetFirstKill, Invert, SetTiebreak, - SetInvisible + SetInvisible, + ThiefStealsRole, + SetTrap, + TriggerTrap, + + // Gamemode + SetGuesserGm, + HuntedShield, + HuntedRewindTime, + ShareTimer } public static class RPCProcedure { @@ -144,11 +160,13 @@ namespace TheOtherRoles NinjaTrace.clearTraces(); Portal.clearPortals(); Bloodytrail.resetSprites(); + Trap.clearTraps(); clearAndReloadMapOptions(); clearAndReloadRoles(); clearGameHistory(); setCustomButtonCooldowns(); Helpers.toggleZoom(reset : true); + GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 0; } public static void HandleShareOptions(byte numberOfOptions, MessageReader reader) { @@ -165,6 +183,7 @@ namespace TheOtherRoles } public static void forceEnd() { + if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; foreach (PlayerControl player in CachedPlayer.AllPlayers) { if (!player.Data.Role.IsImpostor) @@ -176,6 +195,10 @@ namespace TheOtherRoles } } + public static void shareGamemode(byte gm) { + MapOptions.gameMode = (CustomGamemodes) gm; + } + public static void workaroundSetRoles(byte numberOfRoles, MessageReader reader) { for (int i = 0; i < numberOfRoles; i++) @@ -303,9 +326,16 @@ namespace TheOtherRoles case RoleId.Medium: Medium.medium = player; break; + case RoleId.Trapper: + Trapper.trapper = player; + break; case RoleId.Lawyer: Lawyer.lawyer = player; break; + case RoleId.Prosecutor: + Lawyer.lawyer = player; + Lawyer.isProsecutor = true; + break; case RoleId.Pursuer: Pursuer.pursuer = player; break; @@ -315,9 +345,12 @@ namespace TheOtherRoles case RoleId.Ninja: Ninja.ninja = player; break; - } + case RoleId.Thief: + Thief.thief = player; + break; } } + } public static void setModifier(byte modifierId, byte playerId, byte flag) { PlayerControl player = Helpers.playerById(playerId); @@ -350,6 +383,12 @@ namespace TheOtherRoles case RoleId.Invert: Invert.invert.Add(player); break; + case RoleId.Chameleon: + Chameleon.chameleon.Add(player); + break; + case RoleId.Shifter: + Shifter.shifter = player; + break; } } @@ -378,6 +417,7 @@ namespace TheOtherRoles } public static void uncheckedMurderPlayer(byte sourceId, byte targetId, byte showAnimation) { + if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; PlayerControl source = Helpers.playerById(sourceId); PlayerControl target = Helpers.playerById(targetId); if (source != null && target != null) { @@ -401,6 +441,10 @@ namespace TheOtherRoles PlayerControl.GameOptions.MapId = mapId; } + public static void setGameStarting() { + GameStartManagerPatch.GameStartManagerUpdatePatch.startingTimer = 5f; + } + // Role functionality public static void engineerFixLights() { @@ -481,68 +525,12 @@ namespace TheOtherRoles Shifter.clearAndReload(); // Suicide (exile) when impostor or impostor variants - if (player.Data.Role.IsImpostor || player == Jackal.jackal || player == Sidekick.sidekick || Jackal.formerJackals.Contains(player) || player == Jester.jester || player == Arsonist.arsonist || player == Vulture.vulture || player == Lawyer.lawyer) { + if (player.Data.Role.IsImpostor || Helpers.isNeutral(player)) { oldShifter.Exiled(); return; } - - if (Shifter.shiftModifiers) { - // Switch shield - if (Medic.shielded != null && Medic.shielded == player) { - Medic.shielded = oldShifter; - } else if (Medic.shielded != null && Medic.shielded == oldShifter) { - Medic.shielded = player; - } - // Shift Lovers Role - if (Lovers.lover1 != null && oldShifter == Lovers.lover1) Lovers.lover1 = player; - else if (Lovers.lover1 != null && player == Lovers.lover1) Lovers.lover1 = oldShifter; - - if (Lovers.lover2 != null && oldShifter == Lovers.lover2) Lovers.lover2 = player; - else if (Lovers.lover2 != null && player == Lovers.lover2) Lovers.lover2 = oldShifter; - - // TODO other Modifiers? - } - - // Shift role - if (Mayor.mayor != null && Mayor.mayor == player) - Mayor.mayor = oldShifter; - if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == player) - Portalmaker.portalmaker = oldShifter; - if (Engineer.engineer != null && Engineer.engineer == player) - Engineer.engineer = oldShifter; - if (Sheriff.sheriff != null && Sheriff.sheriff == player) { - if (Sheriff.formerDeputy != null && Sheriff.formerDeputy == Sheriff.sheriff) Sheriff.formerDeputy = oldShifter; // Shifter also shifts info on promoted deputy (to get handcuffs) - Sheriff.sheriff = oldShifter; - } - if (Deputy.deputy != null && Deputy.deputy == player) - Deputy.deputy = oldShifter; - if (Lighter.lighter != null && Lighter.lighter == player) - Lighter.lighter = oldShifter; - if (Detective.detective != null && Detective.detective == player) - Detective.detective = oldShifter; - if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == player) - TimeMaster.timeMaster = oldShifter; - if (Medic.medic != null && Medic.medic == player) - Medic.medic = oldShifter; - if (Swapper.swapper != null && Swapper.swapper == player) - Swapper.swapper = oldShifter; - if (Seer.seer != null && Seer.seer == player) - Seer.seer = oldShifter; - if (Hacker.hacker != null && Hacker.hacker == player) - Hacker.hacker = oldShifter; - if (Tracker.tracker != null && Tracker.tracker == player) - Tracker.tracker = oldShifter; - if (Snitch.snitch != null && Snitch.snitch == player) - Snitch.snitch = oldShifter; - if (Spy.spy != null && Spy.spy == player) - Spy.spy = oldShifter; - if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player) - SecurityGuard.securityGuard = oldShifter; - if (Guesser.niceGuesser != null && Guesser.niceGuesser == player) - Guesser.niceGuesser = oldShifter; - - if (Medium.medium != null && Medium.medium == player) - Medium.medium = oldShifter; + + Shifter.shiftRole(oldShifter, player); // Set cooldowns to max for both players if (CachedPlayer.LocalPlayer.PlayerControl == oldShifter || CachedPlayer.LocalPlayer.PlayerControl == player) @@ -621,6 +609,7 @@ namespace TheOtherRoles public static void jackalCreatesSidekick(byte targetId) { PlayerControl player = Helpers.playerById(targetId); if (player == null) return; + if (Lawyer.target == player && Lawyer.isProsecutor && Lawyer.lawyer != null && !Lawyer.lawyer.Data.IsDead) Lawyer.isProsecutor = false; if (!Jackal.canCreateSidekickFromImpostor && player.Data.Role.IsImpostor) { Jackal.fakeSidekick = player; @@ -656,7 +645,7 @@ namespace TheOtherRoles return; } - public static void erasePlayerRoles(byte playerId, bool ignoreModifier = false) { + public static void erasePlayerRoles(byte playerId, bool ignoreModifier = true) { PlayerControl player = Helpers.playerById(playerId); if (player == null) return; @@ -679,6 +668,7 @@ namespace TheOtherRoles if (player == Spy.spy) Spy.clearAndReload(); if (player == SecurityGuard.securityGuard) SecurityGuard.clearAndReload(); if (player == Medium.medium) Medium.clearAndReload(); + if (player == Trapper.trapper) Trapper.clearAndReload(); // Impostor roles if (player == Morphling.morphling) Morphling.clearAndReload(); @@ -710,6 +700,7 @@ namespace TheOtherRoles if (player == Vulture.vulture) Vulture.clearAndReload(); if (player == Lawyer.lawyer) Lawyer.clearAndReload(); if (player == Pursuer.pursuer) Pursuer.clearAndReload(); + if (player == Thief.thief) Thief.clearAndReload(); // Modifier if (!ignoreModifier) @@ -723,6 +714,7 @@ namespace TheOtherRoles if (player == Mini.mini) Mini.clearAndReload(); if (Vip.vip.Any(x => x.PlayerId == player.PlayerId)) Vip.vip.RemoveAll(x => x.PlayerId == player.PlayerId); if (Invert.invert.Any(x => x.PlayerId == player.PlayerId)) Invert.invert.RemoveAll(x => x.PlayerId == player.PlayerId); + if (Chameleon.chameleon.Any(x => x.PlayerId == player.PlayerId)) Chameleon.chameleon.RemoveAll(x => x.PlayerId == player.PlayerId); } } @@ -767,7 +759,7 @@ namespace TheOtherRoles if (flag == byte.MaxValue) { target.cosmetics.currentBodySprite.BodySprite.color = Color.white; - target.cosmetics.colorBlindText.gameObject.SetActive(SaveManager.colorblindMode); + target.cosmetics.colorBlindText.gameObject.SetActive(DataManager.Settings.Accessibility.ColorBlindMode); if (Camouflager.camouflageTimer <= 0) target.setDefaultLook(); Ninja.isInvisble = false; return; @@ -803,7 +795,7 @@ namespace TheOtherRoles public static void lightsOut() { Trickster.lightsOutTimer = Trickster.lightsOutDuration; // If the local player is impostor indicate lights out - if(CachedPlayer.LocalPlayer.Data.Role.IsImpostor) { + if(Helpers.hasImpVision(GameData.Instance.GetPlayerById(CachedPlayer.LocalPlayer.PlayerId))) { new CustomMessage("Lights are out", Trickster.lightsOutDuration); } } @@ -874,10 +866,6 @@ namespace TheOtherRoles Vulture.triggerVultureWin = true; } - public static void lawyerWin() { - Lawyer.triggerLawyerWin = true; - } - public static void lawyerSetTarget(byte playerId) { Lawyer.target = Helpers.playerById(playerId); } @@ -886,6 +874,7 @@ namespace TheOtherRoles PlayerControl player = Lawyer.lawyer; PlayerControl client = Lawyer.target; Lawyer.clearAndReload(false); + Pursuer.pursuer = player; if (player.PlayerId == CachedPlayer.LocalPlayer.PlayerId && client != null) { @@ -904,7 +893,7 @@ namespace TheOtherRoles dyingTarget.Exiled(); byte partnerId = dyingLoverPartner != null ? dyingLoverPartner.PlayerId : dyingTargetId; - Guesser.remainingShots(killerId, true); + HandleGuesser.remainingShots(killerId, true); if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(dyingTarget.KillSfx, false, 0.8f); if (MeetingHud.Instance) { foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) { @@ -933,11 +922,26 @@ namespace TheOtherRoles FastDestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(dyingLoverPartner.Data, dyingLoverPartner.Data); if (MeetingHudPatch.guesserUI != null) MeetingHudPatch.guesserUIExitButton.OnClick.Invoke(); } + + // remove shoot button from targets for all guessers and close their guesserUI + if (GuesserGM.isGuesser(PlayerControl.LocalPlayer.PlayerId) && PlayerControl.LocalPlayer != guesser && !PlayerControl.LocalPlayer.Data.IsDead && GuesserGM.remainingShots(PlayerControl.LocalPlayer.PlayerId) > 0 && MeetingHud.Instance) { + MeetingHud.Instance.playerStates.ToList().ForEach(x => { if (x.TargetPlayerId == dyingTarget.PlayerId && x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); + if (dyingLoverPartner != null) + MeetingHud.Instance.playerStates.ToList().ForEach(x => { if (x.TargetPlayerId == dyingLoverPartner.PlayerId && x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); }); + + if (MeetingHudPatch.guesserUI != null && MeetingHudPatch.guesserUIExitButton != null) { + if (MeetingHudPatch.guesserCurrentTarget == dyingTarget.PlayerId) + MeetingHudPatch.guesserUIExitButton.OnClick.Invoke(); + else if (dyingLoverPartner != null && MeetingHudPatch.guesserCurrentTarget == dyingLoverPartner.PlayerId) + MeetingHudPatch.guesserUIExitButton.OnClick.Invoke(); + } + } + PlayerControl guessedTarget = Helpers.playerById(guessedTargetId); - if (Guesser.showInfoInGhostChat && CachedPlayer.LocalPlayer.Data.IsDead && guessedTarget != null) { + if (CachedPlayer.LocalPlayer.Data.IsDead && guessedTarget != null && guesser != null) { RoleInfo roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId); - string msg = $"Guesser guessed the role {roleInfo?.name ?? ""} for {guessedTarget.Data.PlayerName}!"; + string msg = $"{guesser.Data.PlayerName} guessed the role {roleInfo?.name ?? ""} for {guessedTarget.Data.PlayerName}!"; if (AmongUsClient.Instance.AmClient && FastDestroyableSingleton.Instance) FastDestroyableSingleton.Instance.Chat.AddChat(guesser, msg); if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0) @@ -964,10 +968,101 @@ namespace TheOtherRoles MapOptions.firstKillPlayer = target; } - public static void setTiebreak() - { + public static void setTiebreak() { Tiebreaker.isTiebreak = true; } + + public static void thiefStealsRole(byte playerId) { + PlayerControl target = Helpers.playerById(playerId); + PlayerControl thief = Thief.thief; + if (target == null) return; + if (target == Sheriff.sheriff) Sheriff.sheriff = thief; + if (target == Jackal.jackal) { + Jackal.jackal = thief; + Jackal.formerJackals.Add(target); + } + if (target == Sidekick.sidekick) { + Sidekick.sidekick = thief; + Jackal.formerJackals.Add(target); + } + if (target == Guesser.evilGuesser) Guesser.evilGuesser = thief; + if (target == Godfather.godfather) Godfather.godfather = thief; + if (target == Mafioso.mafioso) Mafioso.mafioso = thief; + if (target == Janitor.janitor) Janitor.janitor = thief; + if (target == Morphling.morphling) Morphling.morphling = thief; + if (target == Camouflager.camouflager) Camouflager.camouflager = thief; + if (target == Vampire.vampire) Vampire.vampire = thief; + if (target == Eraser.eraser) Eraser.eraser = thief; + if (target == Trickster.trickster) Trickster.trickster = thief; + if (target == Cleaner.cleaner) Cleaner.cleaner = thief; + if (target == Warlock.warlock) Warlock.warlock = thief; + if (target == BountyHunter.bountyHunter) BountyHunter.bountyHunter = thief; + if (target == Witch.witch) Witch.witch = thief; + if (target == Ninja.ninja) Ninja.ninja = thief; + if (target.Data.Role.IsImpostor) { + RoleManager.Instance.SetRole(Thief.thief, RoleTypes.Impostor); + FastDestroyableSingleton.Instance.KillButton.SetCoolDown(Thief.thief.killTimer, PlayerControl.GameOptions.KillCooldown); + } + if (Lawyer.lawyer != null && target == Lawyer.target) + Lawyer.target = thief; + if (Thief.thief == PlayerControl.LocalPlayer) CustomButton.ResetAllCooldowns(); + Thief.clearAndReload(); + Thief.formerThief = thief; // After clearAndReload, else it would get reset... + } + + public static void setTrap(byte[] buff) { + if (Trapper.trapper == null) return; + Trapper.charges -= 1; + Vector3 position = Vector3.zero; + position.x = BitConverter.ToSingle(buff, 0 * sizeof(float)); + position.y = BitConverter.ToSingle(buff, 1 * sizeof(float)); + new Trap(position); + } + + public static void triggerTrap(byte playerId, byte trapId) { + Trap.triggerTrap(playerId, trapId); + } + + public static void setGuesserGm (byte playerId) { + PlayerControl target = Helpers.playerById(playerId); + if (target == null) return; + new GuesserGM(target); + } + + public static void shareTimer(float punish) { + HideNSeek.timer -= punish; + } + + public static void huntedShield(byte playerId) { + if (!Hunted.timeshieldActive.Contains(playerId)) Hunted.timeshieldActive.Add(playerId); + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Hunted.shieldDuration, new Action((p) => { + if (p == 1f) Hunted.timeshieldActive.Remove(playerId); + }))); + } + + public static void huntedRewindTime(byte playerId) { + Hunted.timeshieldActive.Remove(playerId); // Shield is no longer active when rewinding + SoundEffectsManager.stop("timemasterShield"); // Shield sound stopped when rewinding + if (playerId == CachedPlayer.LocalPlayer.PlayerControl.PlayerId) { + resetHuntedRewindButton(); + } + FastDestroyableSingleton.Instance.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f); + FastDestroyableSingleton.Instance.FullScreen.enabled = true; + FastDestroyableSingleton.Instance.FullScreen.gameObject.SetActive(true); + FastDestroyableSingleton.Instance.StartCoroutine(Effects.Lerp(Hunted.shieldRewindTime, new Action((p) => { + if (p == 1f) FastDestroyableSingleton.Instance.FullScreen.enabled = false; + }))); + + if (!CachedPlayer.LocalPlayer.Data.Role.IsImpostor) return; // only rewind hunter + + TimeMaster.isRewinding = true; + + if (MapBehaviour.Instance) + MapBehaviour.Instance.Close(); + if (Minigame.Instance) + Minigame.Instance.ForceClose(); + CachedPlayer.LocalPlayer.PlayerControl.moveable = false; + } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] @@ -1047,6 +1142,9 @@ namespace TheOtherRoles byte mapId = reader.ReadByte(); RPCProcedure.dynamicMapOption(mapId); break; + case (byte)CustomRPC.SetGameStarting: + RPCProcedure.setGameStarting(); + break; // Role functionality @@ -1112,7 +1210,9 @@ namespace TheOtherRoles RPCProcedure.sidekickPromotes(); break; case (byte)CustomRPC.ErasePlayerRoles: - RPCProcedure.erasePlayerRoles(reader.ReadByte()); + byte eraseTarget = reader.ReadByte(); + RPCProcedure.erasePlayerRoles(eraseTarget); + Eraser.alreadyErased.Add(eraseTarget); break; case (byte)CustomRPC.SetFutureErased: RPCProcedure.setFutureErased(reader.ReadByte()); @@ -1157,9 +1257,6 @@ namespace TheOtherRoles case (byte)CustomRPC.VultureWin: RPCProcedure.vultureWin(); break; - case (byte)CustomRPC.LawyerWin: - RPCProcedure.lawyerWin(); - break; case (byte)CustomRPC.LawyerSetTarget: RPCProcedure.lawyerSetTarget(reader.ReadByte()); break; @@ -1190,7 +1287,42 @@ namespace TheOtherRoles byte invisiblePlayer = reader.ReadByte(); byte invisibleFlag = reader.ReadByte(); RPCProcedure.setInvisible(invisiblePlayer, invisibleFlag); - break; + break; + case (byte)CustomRPC.ThiefStealsRole: + byte thiefTargetId = reader.ReadByte(); + RPCProcedure.thiefStealsRole(thiefTargetId); + break; + case (byte)CustomRPC.SetTrap: + RPCProcedure.setTrap(reader.ReadBytesAndSize()); + break; + case (byte)CustomRPC.TriggerTrap: + byte trappedPlayer = reader.ReadByte(); + byte trapId = reader.ReadByte(); + RPCProcedure.triggerTrap(trappedPlayer, trapId); + 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); + break; + case (byte)CustomRPC.ShareTimer: + float punish = reader.ReadSingle(); + RPCProcedure.shareTimer(punish); + break; + case (byte)CustomRPC.HuntedShield: + byte huntedPlayer = reader.ReadByte(); + RPCProcedure.huntedShield(huntedPlayer); + break; + case (byte)CustomRPC.HuntedRewindTime: + byte rewindPlayer = reader.ReadByte(); + RPCProcedure.huntedRewindTime(rewindPlayer); + break; } } } diff --git a/TheOtherRoles/Resources/HideNSeekArrowButton.png b/TheOtherRoles/Resources/HideNSeekArrowButton.png new file mode 100644 index 0000000..9e8708a Binary files /dev/null and b/TheOtherRoles/Resources/HideNSeekArrowButton.png differ diff --git a/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md b/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md index 5cc3a56..33f11e8 100644 --- a/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md +++ b/TheOtherRoles/Resources/SoundEffects/SoundEffectSourcesAndLicenses.md @@ -38,4 +38,5 @@ |witchSpell | | [witch's laugh](https://freesound.org/people/Yogoorth/sounds/589144/ ) | CC0 1.0 | |portalmakerPlacePortal | same as tricksterPlaceBox| - | - | |portalUse | | [Static electronic noise - Xbox 360](https://freesound.org/people/scenes/sounds/431654/ ) + [Teleport Slurp](https://freesound.org/people/GameAudio/sounds/220163/ ) | CC0 1.0 both | -|fail | | [twitch: SmeggyTV: Mein Auge](https://www.twitch.tv/smeggytv/clip/KindQuaintEndiveDancingBanana-Zoc-bXUnqNCoqQD5 ) | License granted for use in TOR | \ No newline at end of file +|fail | | [twitch: SmeggyTV: Mein Auge](https://www.twitch.tv/smeggytv/clip/KindQuaintEndiveDancingBanana-Zoc-bXUnqNCoqQD5 ) | License granted for use in TOR | +|trapperTrap | | [Karabiner_Click_04.wav](https://freesound.org/people/Rudmer_Rotteveel/sounds/457454/ ) & [Steel Spring Bear Trap](https://freesound.org/people/fractionalist/sounds/644245/ )| CC0 1.0 both | diff --git a/TheOtherRoles/Resources/SoundEffects/trapperTrap.raw b/TheOtherRoles/Resources/SoundEffects/trapperTrap.raw new file mode 100644 index 0000000..29c0508 Binary files /dev/null and b/TheOtherRoles/Resources/SoundEffects/trapperTrap.raw differ diff --git a/TheOtherRoles/Resources/SoundEffects/vultureEat.raw b/TheOtherRoles/Resources/SoundEffects/vultureEat.raw index 774911a..dc15559 100644 Binary files a/TheOtherRoles/Resources/SoundEffects/vultureEat.raw and b/TheOtherRoles/Resources/SoundEffects/vultureEat.raw differ diff --git a/TheOtherRoles/Resources/TabIconClassicMode.png b/TheOtherRoles/Resources/TabIconClassicMode.png new file mode 100644 index 0000000..10eb786 Binary files /dev/null and b/TheOtherRoles/Resources/TabIconClassicMode.png differ diff --git a/TheOtherRoles/Resources/TabIconGuesserMode.png b/TheOtherRoles/Resources/TabIconGuesserMode.png new file mode 100644 index 0000000..85027e2 Binary files /dev/null and b/TheOtherRoles/Resources/TabIconGuesserMode.png differ diff --git a/TheOtherRoles/Resources/TabIconGuesserSettings.png b/TheOtherRoles/Resources/TabIconGuesserSettings.png new file mode 100644 index 0000000..ac04d21 Binary files /dev/null and b/TheOtherRoles/Resources/TabIconGuesserSettings.png differ diff --git a/TheOtherRoles/Resources/TabIconHideNSeekMode.png b/TheOtherRoles/Resources/TabIconHideNSeekMode.png new file mode 100644 index 0000000..69658e4 Binary files /dev/null and b/TheOtherRoles/Resources/TabIconHideNSeekMode.png differ diff --git a/TheOtherRoles/Resources/TabIconHideNSeekRoles.png b/TheOtherRoles/Resources/TabIconHideNSeekRoles.png new file mode 100644 index 0000000..da0cf99 Binary files /dev/null and b/TheOtherRoles/Resources/TabIconHideNSeekRoles.png differ diff --git a/TheOtherRoles/Resources/TabIconHideNSeekSettings.png b/TheOtherRoles/Resources/TabIconHideNSeekSettings.png new file mode 100644 index 0000000..e6edb12 Binary files /dev/null and b/TheOtherRoles/Resources/TabIconHideNSeekSettings.png differ diff --git a/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll b/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll deleted file mode 100644 index bc6f4e7..0000000 Binary files a/TheOtherRoles/Resources/ThirdParty/Submerged/Submerged.dll and /dev/null differ diff --git a/TheOtherRoles/Resources/Trapper_Place_Button.png b/TheOtherRoles/Resources/Trapper_Place_Button.png new file mode 100644 index 0000000..e2130c8 Binary files /dev/null and b/TheOtherRoles/Resources/Trapper_Place_Button.png differ diff --git a/TheOtherRoles/Resources/Trapper_Trap_Ingame.png b/TheOtherRoles/Resources/Trapper_Trap_Ingame.png new file mode 100644 index 0000000..2487d09 Binary files /dev/null and b/TheOtherRoles/Resources/Trapper_Trap_Ingame.png differ diff --git a/TheOtherRoles/RoleInfo.cs b/TheOtherRoles/RoleInfo.cs index 827986f..db14e01 100644 --- a/TheOtherRoles/RoleInfo.cs +++ b/TheOtherRoles/RoleInfo.cs @@ -4,6 +4,8 @@ using System.Collections.Generic; using TheOtherRoles.Players; using static TheOtherRoles.TheOtherRoles; using UnityEngine; +using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; namespace TheOtherRoles { @@ -47,7 +49,6 @@ namespace TheOtherRoles public static RoleInfo detective = new RoleInfo("Detective", Detective.color, "Find the Impostors by examining footprints", "Examine footprints", RoleId.Detective); public static RoleInfo timeMaster = new RoleInfo("Time Master", TimeMaster.color, "Save yourself with your time shield", "Use your time shield", RoleId.TimeMaster); public static RoleInfo medic = new RoleInfo("Medic", Medic.color, "Protect someone with your shield", "Protect other players", RoleId.Medic); - public static RoleInfo shifter = new RoleInfo("Shifter", Shifter.color, "Shift your role", "Shift your role", RoleId.Shifter); public static RoleInfo swapper = new RoleInfo("Swapper", Swapper.color, "Swap votes to exile the Impostors", "Swap votes", RoleId.Swapper); public static RoleInfo seer = new RoleInfo("Seer", Seer.color, "You will see players die", "You will see players die", RoleId.Seer); public static RoleInfo hacker = new RoleInfo("Hacker", Hacker.color, "Hack systems to find the Impostors", "Hack to find the Impostors", RoleId.Hacker); @@ -62,12 +63,18 @@ namespace TheOtherRoles public static RoleInfo badGuesser = new RoleInfo("Evil Guesser", Palette.ImpostorRed, "Guess and shoot", "Guess and shoot", RoleId.EvilGuesser); public static RoleInfo vulture = new RoleInfo("Vulture", Vulture.color, "Eat corpses to win", "Eat dead bodies", RoleId.Vulture, true); public static RoleInfo medium = new RoleInfo("Medium", Medium.color, "Question the souls of the dead to gain informations", "Question the souls", RoleId.Medium); + public static RoleInfo trapper = new RoleInfo("Trapper", Trapper.color, "Place traps to find the Impostors", "Place traps", RoleId.Trapper); public static RoleInfo lawyer = new RoleInfo("Lawyer", Lawyer.color, "Defend your client", "Defend your client", RoleId.Lawyer, true); + public static RoleInfo prosecutor = new RoleInfo("Prosecutor", Lawyer.color, "Vote out your target", "Vote our your target", RoleId.Prosecutor, true); public static RoleInfo pursuer = new RoleInfo("Pursuer", Pursuer.color, "Blank the Impostors", "Blank the Impostors", RoleId.Pursuer); public static RoleInfo impostor = new RoleInfo("Impostor", Palette.ImpostorRed, Helpers.cs(Palette.ImpostorRed, "Sabotage and kill everyone"), "Sabotage and kill everyone", RoleId.Impostor); public static RoleInfo crewmate = new RoleInfo("Crewmate", Color.white, "Find the Impostors", "Find the Impostors", RoleId.Crewmate); 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 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); @@ -81,6 +88,8 @@ namespace TheOtherRoles public static RoleInfo mini = new RoleInfo("Mini", Color.yellow, "No one will harm you until you grow up", "No one will harm you", RoleId.Mini, false, true); public static RoleInfo vip = new RoleInfo("VIP", Color.yellow, "You are the VIP", "Everyone is notified when you die", RoleId.Vip, false, true); public static RoleInfo invert = new RoleInfo("Invert", Color.yellow, "Your movement is inverted", "Your movement is inverted", RoleId.Invert, false, true); + public static RoleInfo chameleon = new RoleInfo("Chameleon", Color.yellow, "You're hard to see when not moving", "You're hard to see when not moving", RoleId.Chameleon, false, true); + public static RoleInfo shifter = new RoleInfo("Shifter", Color.yellow, "Shift your role", "Shift your role", RoleId.Shifter, false, true); public static List allRoleInfos = new List() { @@ -108,8 +117,9 @@ namespace TheOtherRoles vulture, pursuer, lawyer, + thief, + prosecutor, crewmate, - shifter, mayor, portalmaker, engineer, @@ -128,13 +138,16 @@ namespace TheOtherRoles securityGuard, bait, medium, + trapper, bloody, antiTeleport, tiebreaker, sunglasses, mini, vip, - invert + invert, + chameleon, + shifter }; public static List getRoleInfoForPlayer(PlayerControl p, bool showModifier = true) { @@ -144,7 +157,7 @@ namespace TheOtherRoles // Modifier if (showModifier) { // after dead modifier - if (!CustomOptionHolder.modifiersAreHidden.getBool() || PlayerControl.LocalPlayer.Data.IsDead) + if (!CustomOptionHolder.modifiersAreHidden.getBool() || PlayerControl.LocalPlayer.Data.IsDead || AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Ended) { if (Bait.bait.Any(x => x.PlayerId == p.PlayerId)) infos.Add(bait); if (Bloody.bloody.Any(x => x.PlayerId == p.PlayerId)) infos.Add(bloody); @@ -156,8 +169,12 @@ namespace TheOtherRoles if (Sunglasses.sunglasses.Any(x => x.PlayerId == p.PlayerId)) infos.Add(sunglasses); if (p == Mini.mini) infos.Add(mini); if (Invert.invert.Any(x => x.PlayerId == p.PlayerId)) infos.Add(invert); + if (Chameleon.chameleon.Any(x => x.PlayerId == p.PlayerId)) infos.Add(chameleon); + if (p == Shifter.shifter) infos.Add(shifter); } + int count = infos.Count; // Save count after modifiers are added so that the role count can be checked + // Special roles if (p == Jester.jester) infos.Add(jester); if (p == Mayor.mayor) infos.Add(mayor); @@ -181,7 +198,6 @@ namespace TheOtherRoles if (p == Detective.detective) infos.Add(detective); if (p == TimeMaster.timeMaster) infos.Add(timeMaster); if (p == Medic.medic) infos.Add(medic); - if (p == Shifter.shifter) infos.Add(shifter); if (p == Swapper.swapper) infos.Add(swapper); if (p == Seer.seer) infos.Add(seer); if (p == Hacker.hacker) infos.Add(hacker); @@ -197,12 +213,19 @@ namespace TheOtherRoles if (p == BountyHunter.bountyHunter) infos.Add(bountyHunter); if (p == Vulture.vulture) infos.Add(vulture); if (p == Medium.medium) infos.Add(medium); - if (p == Lawyer.lawyer) infos.Add(lawyer); + if (p == Lawyer.lawyer && !Lawyer.isProsecutor) infos.Add(lawyer); + if (p == Lawyer.lawyer && Lawyer.isProsecutor) infos.Add(prosecutor); + if (p == Trapper.trapper) infos.Add(trapper); if (p == Pursuer.pursuer) infos.Add(pursuer); + if (p == Thief.thief) infos.Add(thief); - // Default roles - if (infos.Count == 0 && p.Data.Role.IsImpostor) infos.Add(impostor); // Just Impostor - if (infos.Count == 0 && !p.Data.Role.IsImpostor) infos.Add(crewmate); // Just Crewmate + // 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); + else + infos.Add(MapOptions.gameMode == CustomGamemodes.HideNSeek ? RoleInfo.hunted : RoleInfo.crewmate); + } return infos; } @@ -210,7 +233,9 @@ namespace TheOtherRoles public static String GetRolesString(PlayerControl p, bool useColors, bool showModifier = true) { 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 (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)"; return roleName; } } diff --git a/TheOtherRoles/SoundEffectsManager.cs b/TheOtherRoles/SoundEffectsManager.cs index eb047d9..76b772f 100644 --- a/TheOtherRoles/SoundEffectsManager.cs +++ b/TheOtherRoles/SoundEffectsManager.cs @@ -52,6 +52,7 @@ namespace TheOtherRoles } public static void stopAll() { + if (soundEffects == null) return; foreach (var path in soundEffects.Keys) stop(path); } } diff --git a/TheOtherRoles/SubmergedCompatibility.cs b/TheOtherRoles/SubmergedCompatibility.cs index a03cbc3..d9fe18e 100644 --- a/TheOtherRoles/SubmergedCompatibility.cs +++ b/TheOtherRoles/SubmergedCompatibility.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using BepInEx; -using BepInEx.IL2CPP; +using BepInEx.Unity.IL2CPP; using HarmonyLib; using TheOtherRoles.Patches; using TheOtherRoles.Players; @@ -130,7 +130,7 @@ namespace TheOtherRoles Types = AccessTools.GetTypesFromAssembly(Assembly); - InjectedTypes = (Dictionary) AccessTools.PropertyGetter(Types.FirstOrDefault(t => t.Name == "RegisterInIl2CppAttribute"), "RegisteredTypes") + InjectedTypes = (Dictionary) AccessTools.PropertyGetter(Types.FirstOrDefault(t => t.Name == "ComponentExtensions"), "RegisteredTypes") .Invoke(null, Array.Empty()); SubmarineStatusType = Types.First(t => t.Name == "SubmarineStatus"); diff --git a/TheOtherRoles/TasksHandler.cs b/TheOtherRoles/TasksHandler.cs index ff4b9f1..763defe 100644 --- a/TheOtherRoles/TasksHandler.cs +++ b/TheOtherRoles/TasksHandler.cs @@ -37,6 +37,7 @@ namespace TheOtherRoles { && playerInfo.Object.hasAliveKillingLover() // Tasks do not count if a Crewmate has an alive killing Lover || playerInfo.PlayerId == Lawyer.lawyer?.PlayerId // Tasks of the Lawyer do not count || (playerInfo.PlayerId == Pursuer.pursuer?.PlayerId && Pursuer.pursuer.Data.IsDead) // Tasks of the Pursuer only count, if he's alive + || playerInfo.PlayerId == Thief.thief?.PlayerId // Thief's tasks only count after joining crew team as sheriff (and then the thief is not the thief anymore) ) continue; var (playerCompleted, playerTotal) = taskInfo(playerInfo); diff --git a/TheOtherRoles/TheOtherRoles.cs b/TheOtherRoles/TheOtherRoles.cs index 7ec73db..15b891e 100644 --- a/TheOtherRoles/TheOtherRoles.cs +++ b/TheOtherRoles/TheOtherRoles.cs @@ -6,6 +6,9 @@ using UnityEngine; using TheOtherRoles.Objects; using TheOtherRoles.Players; using TheOtherRoles.Utilities; +using TheOtherRoles.CustomGameModes; +using static TheOtherRoles.TheOtherRoles; +using AmongUs.Data; namespace TheOtherRoles { @@ -47,7 +50,6 @@ namespace TheOtherRoles Warlock.clearAndReload(); SecurityGuard.clearAndReload(); Arsonist.clearAndReload(); - Guesser.clearAndReload(); BountyHunter.clearAndReload(); Vulture.clearAndReload(); Medium.clearAndReload(); @@ -55,6 +57,8 @@ namespace TheOtherRoles Pursuer.clearAndReload(); Witch.clearAndReload(); Ninja.clearAndReload(); + Thief.clearAndReload(); + Trapper.clearAndReload(); // Modifier Bait.clearAndReload(); @@ -65,6 +69,11 @@ namespace TheOtherRoles Mini.clearAndReload(); Vip.clearAndReload(); Invert.clearAndReload(); + Chameleon.clearAndReload(); + + // Gamemodes + HandleGuesser.clearAndReload(); + HideNSeek.clearAndReload(); } public static class Jester { @@ -429,29 +438,6 @@ namespace TheOtherRoles } } - public static class Shifter { - public static PlayerControl shifter; - public static Color color = new Color32(102, 102, 102, byte.MaxValue); - - public static PlayerControl futureShift; - public static PlayerControl currentTarget; - public static bool shiftModifiers = false; - - private static Sprite buttonSprite; - public static Sprite getButtonSprite() { - if (buttonSprite) return buttonSprite; - buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ShiftButton.png", 115f); - return buttonSprite; - } - - public static void clearAndReload() { - shifter = null; - currentTarget = null; - futureShift = null; - shiftModifiers = CustomOptionHolder.shifterShiftsModifiers.getBool(); - } - } - public static class Swapper { public static PlayerControl swapper; public static Color color = new Color32(134, 55, 86, byte.MaxValue); @@ -909,6 +895,8 @@ namespace TheOtherRoles public static PlayerControl eraser; public static Color color = Palette.ImpostorRed; + public static List alreadyErased = new List(); + public static List futureErased = new List(); public static PlayerControl currentTarget; public static float cooldown = 30f; @@ -927,6 +915,7 @@ namespace TheOtherRoles currentTarget = null; cooldown = CustomOptionHolder.eraserCooldown.getFloat(); canEraseAnyone = CustomOptionHolder.eraserCanEraseAnyone.getBool(); + alreadyErased = new List(); } } @@ -1200,20 +1189,9 @@ namespace TheOtherRoles public static PlayerControl niceGuesser; public static PlayerControl evilGuesser; public static Color color = new Color32(255, 255, 0, byte.MaxValue); - private static Sprite targetSprite; public static int remainingShotsEvilGuesser = 2; public static int remainingShotsNiceGuesser = 2; - public static bool hasMultipleShotsPerMeeting = false; - public static bool showInfoInGhostChat = true; - public static bool killsThroughShield = true; - public static bool evilGuesserCanGuessSpy = true; - public static bool guesserCantGuessSnitch = false; - public static Sprite getTargetSprite() { - if (targetSprite) return targetSprite; - targetSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TargetIcon.png", 150f); - return targetSprite; - } public static bool isGuesser (byte playerId) { if ((niceGuesser != null && niceGuesser.PlayerId == playerId) || (evilGuesser != null && evilGuesser.PlayerId == playerId)) return true; @@ -1239,13 +1217,8 @@ namespace TheOtherRoles public static void clearAndReload() { niceGuesser = null; evilGuesser = null; - guesserCantGuessSnitch = CustomOptionHolder.guesserCantGuessSnitchIfTaksDone.getBool(); remainingShotsEvilGuesser = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat()); remainingShotsNiceGuesser = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat()); - hasMultipleShotsPerMeeting = CustomOptionHolder.guesserHasMultipleShotsPerMeeting.getBool(); - showInfoInGhostChat = CustomOptionHolder.guesserShowInfoInGhostChat.getBool(); - killsThroughShield = CustomOptionHolder.guesserKillsThroughShield.getBool(); - evilGuesserCanGuessSpy = CustomOptionHolder.guesserEvilCanKillSpy.getBool(); } } @@ -1370,7 +1343,9 @@ namespace TheOtherRoles public static PlayerControl target; public static Color color = new Color32(134, 153, 25, byte.MaxValue); public static Sprite targetSprite; - public static bool triggerLawyerWin = false; + public static bool triggerProsecutorWin = false; + public static bool isProsecutor = false; + public static bool canCallEmergency = true; public static float vision = 1f; public static bool lawyerKnowsRole = false; @@ -1389,11 +1364,12 @@ namespace TheOtherRoles target = null; targetWasGuessed = false; } - triggerLawyerWin = false; - + isProsecutor = false; + triggerProsecutorWin = false; vision = CustomOptionHolder.lawyerVision.getFloat(); lawyerKnowsRole = CustomOptionHolder.lawyerKnowsRole.getBool(); targetCanBeJester = CustomOptionHolder.lawyerTargetCanBeJester.getBool(); + canCallEmergency = CustomOptionHolder.jesterCanCallEmergency.getBool(); } } @@ -1514,6 +1490,70 @@ namespace TheOtherRoles } } + public static class Thief { + public static PlayerControl thief; + public static Color color = new Color32(71, 99, 45, Byte.MaxValue); + public static PlayerControl currentTarget; + public static PlayerControl formerThief; + + public static float cooldown = 30f; + + public static bool suicideFlag = false; // Used as a flag for suicide + + public static bool hasImpostorVision; + public static bool canUseVents; + public static bool canKillSheriff; + + public static void clearAndReload() { + thief = null; + suicideFlag = false; + currentTarget = null; + formerThief = null; + hasImpostorVision = CustomOptionHolder.thiefHasImpVision.getBool(); // todo option and implementation + cooldown = CustomOptionHolder.thiefCooldown.getFloat(); + canUseVents = CustomOptionHolder.thiefCanUseVents.getBool(); + canKillSheriff = CustomOptionHolder.thiefCanKillSheriff.getBool(); + } + } + + public static class Trapper { + public static PlayerControl trapper; + public static Color color = new Color32(110, 57, 105, byte.MaxValue); + + public static float cooldown = 30f; + public static int maxCharges = 5; + public static int rechargeTasksNumber = 3; + public static int rechargedTasks = 3; + public static int charges = 1; + public static int trapCountToReveal = 2; + public static List playersOnMap = new List(); + public static bool anonymousMap = false; + public static int infoType = 0; // 0 = Role, 1 = Good/Evil, 2 = Name + public static float trapDuration = 5f; + + private static Sprite trapButtonSprite; + + public static Sprite getButtonSprite() { + if (trapButtonSprite) return trapButtonSprite; + trapButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Trapper_Place_Button.png", 115f); + return trapButtonSprite; + } + + public static void clearAndReload() { + trapper = null; + cooldown = CustomOptionHolder.trapperCooldown.getFloat(); + maxCharges = Mathf.RoundToInt(CustomOptionHolder.trapperMaxCharges.getFloat()); + rechargeTasksNumber = Mathf.RoundToInt(CustomOptionHolder.trapperRechargeTasksNumber.getFloat()); + rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.trapperRechargeTasksNumber.getFloat()); + charges = Mathf.RoundToInt(CustomOptionHolder.trapperMaxCharges.getFloat()) / 2; + trapCountToReveal = Mathf.RoundToInt(CustomOptionHolder.trapperTrapNeededTriggerToReveal.getFloat()); + playersOnMap = new List(); + anonymousMap = CustomOptionHolder.trapperAnonymousMap.getBool(); + infoType = CustomOptionHolder.trapperInfoType.getSelection(); + trapDuration = CustomOptionHolder.trapperTrapDuration.getFloat(); + } + } + // Modifier public static class Bait { public static List bait = new List(); @@ -1596,13 +1636,17 @@ namespace TheOtherRoles public const float defaultColliderOffset = 0.3636057f; public static float growingUpDuration = 400f; + public static bool isGrowingUpInMeeting = true; public static DateTime timeOfGrowthStart = DateTime.UtcNow; + public static DateTime timeOfMeetingStart = DateTime.UtcNow; + public static float ageOnMeetingStart = 0f; public static bool triggerMiniLose = false; public static void clearAndReload() { mini = null; triggerMiniLose = false; growingUpDuration = CustomOptionHolder.modifierMiniGrowingUpDuration.getFloat(); + isGrowingUpInMeeting = CustomOptionHolder.modifierMiniGrowingUpInMeeting.getBool(); timeOfGrowthStart = DateTime.UtcNow; } @@ -1637,4 +1681,150 @@ namespace TheOtherRoles meetings = (int) CustomOptionHolder.modifierInvertDuration.getFloat(); } } + + public static class Chameleon { + public static List chameleon = new List(); + public static float minVisibility = 0.2f; + public static float holdDuration = 1f; + public static float fadeDuration = 0.5f; + public static Dictionary lastMoved; + + public static void clearAndReload() { + chameleon = new List(); + lastMoved = new Dictionary(); + holdDuration = CustomOptionHolder.modifierChameleonHoldDuration.getFloat(); + fadeDuration = CustomOptionHolder.modifierChameleonFadeDuration.getFloat(); + minVisibility = CustomOptionHolder.modifierChameleonMinVisibility.getSelection() / 10f; + } + + public static float visibility(byte playerId) { + float visibility = 1f; + if (lastMoved != null && lastMoved.ContainsKey(playerId)) { + var tStill = Time.time - lastMoved[playerId]; + if (tStill > holdDuration) { + if (tStill - holdDuration > fadeDuration) visibility = minVisibility; + else visibility = (1 - (tStill - holdDuration) / fadeDuration) * (1 - minVisibility) + minVisibility; + } + } + if (PlayerControl.LocalPlayer.Data.IsDead && visibility < 0.1f) { // Ghosts can always see! + visibility = 0.1f; + } + return visibility; + } + + public static void update() { + foreach (var chameleonPlayer in chameleon) { + if (chameleonPlayer == Ninja.ninja && Ninja.isInvisble) continue; // Dont make Ninja visible... + // check movement by animation + PlayerPhysics playerPhysics = chameleonPlayer.MyPhysics; + var currentPhysicsAnim = playerPhysics.Animator.GetCurrentAnimation(); + if (currentPhysicsAnim != playerPhysics.CurrentAnimationGroup.IdleAnim) { + lastMoved[chameleonPlayer.PlayerId] = Time.time; + } + // calculate and set visibility + float visibility = Chameleon.visibility(chameleonPlayer.PlayerId); + float petVisibility = visibility; + if (chameleonPlayer.Data.IsDead) { + visibility = 0.5f; + petVisibility = 1f; + } + + try { // Sometimes renderers are missing for weird reasons. Try catch to avoid exceptions + chameleonPlayer.cosmetics.currentBodySprite.BodySprite.color = chameleonPlayer.cosmetics.currentBodySprite.BodySprite.color.SetAlpha(visibility); + if (DataManager.Settings.Accessibility.ColorBlindMode) chameleonPlayer.cosmetics.colorBlindText.color = chameleonPlayer.cosmetics.colorBlindText.color.SetAlpha(visibility); + chameleonPlayer.SetHatAndVisorAlpha(visibility); + chameleonPlayer.cosmetics.skin.layer.color = chameleonPlayer.cosmetics.skin.layer.color.SetAlpha(visibility); + chameleonPlayer.cosmetics.nameText.color = chameleonPlayer.cosmetics.nameText.color.SetAlpha(visibility); + chameleonPlayer.cosmetics.currentPet.rend.color = chameleonPlayer.cosmetics.currentPet.rend.color.SetAlpha(petVisibility); + chameleonPlayer.cosmetics.currentPet.shadowRend.color = chameleonPlayer.cosmetics.currentPet.shadowRend.color.SetAlpha(petVisibility); + } catch { } + } + + } + } + + public static class Shifter { + public static PlayerControl shifter; + + public static PlayerControl futureShift; + public static PlayerControl currentTarget; + + private static Sprite buttonSprite; + public static Sprite getButtonSprite() { + if (buttonSprite) return buttonSprite; + buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ShiftButton.png", 115f); + return buttonSprite; + } + + public static void shiftRole (PlayerControl player1, PlayerControl player2, bool repeat = true) { + if (Mayor.mayor != null && Mayor.mayor == player2) { + if (repeat) shiftRole(player2, player1, false); + Mayor.mayor = player1; + } else if (Portalmaker.portalmaker != null && Portalmaker.portalmaker == player2) { + if (repeat) shiftRole(player2, player1, false); + Portalmaker.portalmaker = player1; + } else if (Engineer.engineer != null && Engineer.engineer == player2) { + if (repeat) shiftRole(player2, player1, false); + Engineer.engineer = player1; + } else if (Sheriff.sheriff != null && Sheriff.sheriff == player2) { + if (repeat) shiftRole(player2, player1, false); + if (Sheriff.formerDeputy != null && Sheriff.formerDeputy == Sheriff.sheriff) Sheriff.formerDeputy = player1; // Shifter also shifts info on promoted deputy (to get handcuffs) + Sheriff.sheriff = player1; + } else if (Deputy.deputy != null && Deputy.deputy == player2) { + if (repeat) shiftRole(player2, player1, false); + Deputy.deputy = player1; + } else if (Lighter.lighter != null && Lighter.lighter == player2) { + if (repeat) shiftRole(player2, player1, false); + Lighter.lighter = player1; + } else if (Detective.detective != null && Detective.detective == player2) { + if (repeat) shiftRole(player2, player1, false); + Detective.detective = player1; + } else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == player2) { + if (repeat) shiftRole(player2, player1, false); + TimeMaster.timeMaster = player1; + } else if (Medic.medic != null && Medic.medic == player2) { + if (repeat) shiftRole(player2, player1, false); + Medic.medic = player1; + } else if (Swapper.swapper != null && Swapper.swapper == player2) { + if (repeat) shiftRole(player2, player1, false); + Swapper.swapper = player1; + } else if (Seer.seer != null && Seer.seer == player2) { + if (repeat) shiftRole(player2, player1, false); + Seer.seer = player1; + } else if (Hacker.hacker != null && Hacker.hacker == player2) { + if (repeat) shiftRole(player2, player1, false); + Hacker.hacker = player1; + } else if (Tracker.tracker != null && Tracker.tracker == player2) { + if (repeat) shiftRole(player2, player1, false); + Tracker.tracker = player1; + } else if (Snitch.snitch != null && Snitch.snitch == player2) { + if (repeat) shiftRole(player2, player1, false); + Snitch.snitch = player1; + } else if (Spy.spy != null && Spy.spy == player2) { + if (repeat) shiftRole(player2, player1, false); + Spy.spy = player1; + } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player2) { + if (repeat) shiftRole(player2, player1, false); + SecurityGuard.securityGuard = player1; + } else if (Guesser.niceGuesser != null && Guesser.niceGuesser == player2) { + if (repeat) shiftRole(player2, player1, false); + Guesser.niceGuesser = player1; + } else if (Medium.medium != null && Medium.medium == player2) { + if (repeat) shiftRole(player2, player1, false); + Medium.medium = player1; + } else if (Pursuer.pursuer != null && Pursuer.pursuer == player2) { + if (repeat) shiftRole(player2, player1, false); + Pursuer.pursuer = player1; + } else if (Trapper.trapper != null && Trapper.trapper == player2) { + if (repeat) shiftRole(player2, player1, false); + Trapper.trapper = player1; + } + } + + public static void clearAndReload() { + shifter = null; + currentTarget = null; + futureShift = null; + } + } } diff --git a/TheOtherRoles/TheOtherRoles.csproj b/TheOtherRoles/TheOtherRoles.csproj index abb2581..f5eb618 100644 --- a/TheOtherRoles/TheOtherRoles.csproj +++ b/TheOtherRoles/TheOtherRoles.csproj @@ -1,12 +1,13 @@  - netstandard2.1 - 4.1.7 + net6.0 + 4.2.0 TheOtherRoles Eisbison latest true true + x86 @@ -14,13 +15,30 @@ - - - + + + + + + + + + + + + + + + + + + + + - + diff --git a/TheOtherRoles/Utilities/HandleGuesser.cs b/TheOtherRoles/Utilities/HandleGuesser.cs new file mode 100644 index 0000000..2e6b8c1 --- /dev/null +++ b/TheOtherRoles/Utilities/HandleGuesser.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TheOtherRoles.CustomGameModes; +using UnityEngine; + +namespace TheOtherRoles.Utilities { + public static class HandleGuesser { + private static Sprite targetSprite; + public static bool isGuesserGm = false; + public static bool hasMultipleShotsPerMeeting = false; + public static bool killsThroughShield = true; + public static bool evilGuesserCanGuessSpy = true; + public static bool guesserCantGuessSnitch = false; + + public static Sprite getTargetSprite() { + if (targetSprite) return targetSprite; + targetSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TargetIcon.png", 150f); + return targetSprite; + } + + public static bool isGuesser(byte playerId) { + if (isGuesserGm) return GuesserGM.isGuesser(playerId); + return Guesser.isGuesser(playerId); + } + + public static void clear(byte playerId) { + if (isGuesserGm) GuesserGM.clear(playerId); + else Guesser.clear(playerId); + } + + public static int remainingShots(byte playerId, bool shoot = false) { + if (isGuesserGm) return GuesserGM.remainingShots(playerId, shoot); + return Guesser.remainingShots(playerId, shoot); + } + + public static void clearAndReload() { + Guesser.clearAndReload(); + GuesserGM.clearAndReload(); + isGuesserGm = MapOptions.gameMode == CustomGamemodes.Guesser; + if (isGuesserGm) { + guesserCantGuessSnitch = CustomOptionHolder.guesserGamemodeCantGuessSnitchIfTaksDone.getBool(); + hasMultipleShotsPerMeeting = CustomOptionHolder.guesserGamemodeHasMultipleShotsPerMeeting.getBool(); + killsThroughShield = CustomOptionHolder.guesserGamemodeKillsThroughShield.getBool(); + evilGuesserCanGuessSpy = CustomOptionHolder.guesserGamemodeEvilCanKillSpy.getBool(); + } else { + guesserCantGuessSnitch = CustomOptionHolder.guesserCantGuessSnitchIfTaksDone.getBool(); + hasMultipleShotsPerMeeting = CustomOptionHolder.guesserHasMultipleShotsPerMeeting.getBool(); + killsThroughShield = CustomOptionHolder.guesserKillsThroughShield.getBool(); + evilGuesserCanGuessSpy = CustomOptionHolder.guesserEvilCanKillSpy.getBool(); + } + + } + } +} diff --git a/TheOtherRoles/packages.lock.json b/TheOtherRoles/packages.lock.json index 8a1d1d4..145f601 100644 --- a/TheOtherRoles/packages.lock.json +++ b/TheOtherRoles/packages.lock.json @@ -1,53 +1,67 @@ { "version": 1, "dependencies": { - ".NETStandard,Version=v2.1": { + "net6.0": { "AmongUs.GameLibs.Steam": { "type": "Direct", - "requested": "[2022.8.23, )", - "resolved": "2022.8.23", - "contentHash": "baY5JO6dbdOAZ0VTtGX0xre6/BlDdreJDAH8boCKZt3JTAcn+eeH+l8mUZRC9eCJOODUik2C0TTAt5AqBmYtZg==" + "requested": "[2022.10.25, )", + "resolved": "2022.10.25", + "contentHash": "PWgkH/VvMlShVhZB8G432q0tnMaNVZrbIdbEMCZN/fETUlT/ch+qQj4PdTcZhPHREbnJHH3ijHxlca1d7QMBcQ==" }, - "BepInEx.IL2CPP": { + "BepInEx.IL2CPP.MSBuild": { + "type": "Direct", + "requested": "[2.0.1, )", + "resolved": "2.0.1", + "contentHash": "hUZjCV0t8dkw/Z/zq4Zl405QP7frBk/LVfMdUmUz+7XOhGd7lYPKrR48A5vbGl2QeHudMxlkXzAr9WM3Duq2SQ==" + }, + "BepInEx.Unity.IL2CPP": { "type": "Direct", - "requested": "[6.0.0-be.559, )", - "resolved": "6.0.0-be.559", - "contentHash": "s0PSNq7Gr1/OWh+Yw2SGu8QYMow5lp6iP5hozxReMaJIGctnb/lUPj1pjeVP33pWWDomXwsWN76dCAW2qIY0tg==", + "requested": "[6.0.0-be.662, )", + "resolved": "6.0.0-be.662", + "contentHash": "fn7jQ88IBmjssoY9XjGBTg3cULkS+N/le/eUijYf2VV8raWok/6cU/GbU9bejkJS8g+2MvzIwnROAhAxvrGI2Q==", "dependencies": { - "BepInEx.Core": "6.0.0-be.559", - "BepInEx.Il2Cpp.TlsAdapter": "1.0.1", + "BepInEx.Core": "6.0.0-be.662", + "BepInEx.Unity.Common": "6.0.0-be.662", "HarmonyX": "2.10.0", "Iced": "1.17.0", - "Il2CppAssemblyUnhollower.BaseLib": "0.4.33", - "Il2CppAssemblyUnhollower.BaseLib.Legacy": "0.4.33", - "Il2CppAssemblyUnhollower.Lib": "0.4.33", - "Il2CppDumper": "6.6.4", - "MonoMod.RuntimeDetour": "22.3.23.4", - "Samboy063.Cpp2IL.Core": "2022.0.4" + "Il2CppInterop.Generator": "1.3.0", + "Il2CppInterop.HarmonySupport": "1.3.0", + "Il2CppInterop.Runtime": "1.3.0", + "MonoMod.RuntimeDetour": "22.5.1.1", + "Samboy063.Cpp2IL.Core": "2022.0.7.2" } }, - "BepInEx.IL2CPP.MSBuild": { + "Reactor": { "type": "Direct", - "requested": "[1.1.1, )", - "resolved": "1.1.1", - "contentHash": "uUSo4TuamyEIVg+ICNeJMSSW/arKWHGpfBFINc4fdF5kppfHFrsDURKzNPmDvxivtLsiuGthck9J/5uPXgIcTg==" + "requested": "[2.0.0, )", + "resolved": "2.0.0", + "contentHash": "7Bq0QksAUVqhZ++2jdMvjbbZSBjb4eYN5BAp2Q/b8h2PiFfuvVd/oVooBMjwm6Mooa0URFcISA1tBXNIJEcoTw==", + "dependencies": { + "BepInEx.Unity.IL2CPP": "6.0.0-be.662" + } + }, + "AssetRipper.VersionUtilities": { + "type": "Transitive", + "resolved": "1.2.1", + "contentHash": "knQ/W1YwTZS2AbdaZh+nhyyBEZQPQDcahhSOnh88i+cdg4/FEoFJiCwaavgFvCMNuUeHTbcZc4qttF8WQ8xLIA==" }, "BepInEx.Core": { "type": "Transitive", - "resolved": "6.0.0-be.559", - "contentHash": "pjqwrVxD3JKz/Qz4gn8phzi0mEJucb1EVVcF/2A4oQ1+ytRnBUcOYldg1GCkzLoVYwbuhE0J2bXfUOGawRUwog==", + "resolved": "6.0.0-be.662", + "contentHash": "VVBeCD/KQVy0/TK3V6SD8QZ/es35Xb1mEz/+PbBs3ST1yUC1r7k2JOymhSnmghVqf6UsvlMglOO9pljqhuE/Pw==", "dependencies": { "HarmonyX": "2.10.0", - "MonoMod.Utils": "22.3.23.4", + "MonoMod.Utils": "22.5.1.1", "SemanticVersioning": "2.0.2" } }, - "BepInEx.Il2Cpp.TlsAdapter": { + "BepInEx.Unity.Common": { "type": "Transitive", - "resolved": "1.0.1", - "contentHash": "kTfX6312X0Fvwks+t7Ul3YO2jMSbqNS2Jx/85Cgl6lbAjEJnKzsmx7Krmd66dbnx0eY/QMjaV2r6LyWmQARtdA==", + "resolved": "6.0.0-be.662", + "contentHash": "BgETw6+snwY//3xjQxLiiEAFRmXaMTcSkg+1UQz6fVBG0YA5Usm/m4GflhTc3nwF61KO3Iznn1AORi5CwLJoUw==", "dependencies": { - "HarmonyX": "2.3.1" + "AssetRipper.VersionUtilities": "1.2.1", + "MonoMod.Utils": "22.5.1.1" } }, "HarmonyX": { @@ -64,38 +78,41 @@ "resolved": "1.17.0", "contentHash": "8x+HCVTl/HHTGpscH3vMBhV8sknN/muZFw9s3TsI8SA6+c43cOTCi2+jE4KsU8pNLbJ++iF2ZFcpcXHXtDglnw==" }, - "Il2CppAssemblyUnhollower.BaseLib": { + "Il2CppInterop.Common": { "type": "Transitive", - "resolved": "0.4.33", - "contentHash": "QiltA4HJt6DvrFCcS4vQnirPV4WmQKZNG8M4lCn51k7yyx5LW0xix8HqE4Xt1IVo1Ei+60t5aLRga4W2ktve5w==", + "resolved": "1.3.0", + "contentHash": "IR3BrZXvL2nvJAn4pruPMgC78Y87hVa5xTYZCuHN67OzRD6WDl5dTO55vtvTl8dXFALKvlN+spja2tXhVuquOw==", "dependencies": { - "Iced": "1.15.0" + "Iced": "1.17.0", + "Microsoft.Extensions.Logging.Abstractions": "6.0.1", + "System.Net.Http": "4.3.4" } }, - "Il2CppAssemblyUnhollower.BaseLib.Legacy": { + "Il2CppInterop.Generator": { "type": "Transitive", - "resolved": "0.4.33", - "contentHash": "4o2Z44uqmI/6VBNpyhKJ1JksuqtrOgNrNkbv00ljITs3KedrcqZqGXCP+RaJF2ej4Fkr67fGsATshZ9B8f7izQ==", + "resolved": "1.3.0", + "contentHash": "FuhPxbh/K4K94U3xac/ClMaaBp+MZ4aIaqi5dR0QihyGQoombT+OM/A22RwAIzbaCTbFbvabWaAuOSgB69ZwWQ==", "dependencies": { - "Il2CppAssemblyUnhollower.BaseLib": "0.4.33" + "Il2CppInterop.Common": "1.3.0", + "Mono.Cecil": "0.11.3" } }, - "Il2CppAssemblyUnhollower.Lib": { + "Il2CppInterop.HarmonySupport": { "type": "Transitive", - "resolved": "0.4.33", - "contentHash": "1l7tqjAdzRyq+FP6h//Lojlr0PviEc5adfvsit9BZBQtmBUZoifAccAfXAiU0zLv5pbvHiAguspgmeRdUT1yUw==", + "resolved": "1.3.0", + "contentHash": "VppBnXtbMNGFhX8a1gg/85E883t2pW/+FZyV7eOaOXMNQyorg++u+kUFOIYwNpzTVqxWmMeoPS0DH0toVpjoKA==", "dependencies": { - "Il2CppAssemblyUnhollower.BaseLib": "0.4.33", - "Il2CppAssemblyUnhollower.BaseLib.Legacy": "0.4.33", - "Mono.Cecil": "0.11.3" + "HarmonyX": "2.10.0", + "Il2CppInterop.Runtime": "1.3.0" } }, - "Il2CppDumper": { + "Il2CppInterop.Runtime": { "type": "Transitive", - "resolved": "6.6.4", - "contentHash": "aqjmlyigdZ5FS9ElMNlaCExlgSmAiho4BeIcVwkmzHGnomBzopjOCxqIX9vc/575TQ87TKoaGW2KP+3b5xI15A==", + "resolved": "1.3.0", + "contentHash": "8A+N2yvja960Ap+s2RpW7Nb6HSpyuXJy2ipIlU+1erYU9SWHMyU9JZzUZGOhwWXJChKGvRmDdTZAg12k1+72dg==", "dependencies": { - "Mono.Cecil": "0.11.3" + "Iced": "1.17.0", + "Il2CppInterop.Common": "1.3.0" } }, "IndexRange": { @@ -108,10 +125,15 @@ "resolved": "2.1.0", "contentHash": "YYpq7NM50bSSVUDjXyV/eiITk6syXqItPjKBOb3jEWS6RFsk0DNhpWMPW6b3hKDmArARuWDU6S2pVu1IeVrvIA==" }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "dzB2Cgg+JmrouhjkcQGzSFjjvpwlq353i8oBQO2GWNjCXSzhbtBRUf28HSauWe7eib3wYOdb3tItdjRwAdwCSg==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + "resolved": "1.1.1", + "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -125,11 +147,11 @@ }, "MonoMod.RuntimeDetour": { "type": "Transitive", - "resolved": "22.3.23.4", - "contentHash": "2nwwFfwYDSgUzhi+1MutTg1gyUgXoMS4+E04vHOFp84Vxs0oBkkn4mGAfhkr272y0j3vqIBzo2aT9pXRlCd/ZA==", + "resolved": "22.5.1.1", + "contentHash": "WG5zLRuAr8KJEnIkKTuIG1OJCrGIT3pjvnAD43E0FDchHf3Dp985MF/8tmIQvAzCZV87JNs6QKeGGqmwDmpXpw==", "dependencies": { "Mono.Cecil": "0.11.4", - "MonoMod.Utils": "22.3.23.4", + "MonoMod.Utils": "22.5.1.1", "System.Collections.NonGeneric": "4.3.0", "System.ComponentModel.TypeConverter": "4.3.0", "System.IO.FileSystem.Primitives": "4.3.0", @@ -140,8 +162,8 @@ }, "MonoMod.Utils": { "type": "Transitive", - "resolved": "22.3.23.4", - "contentHash": "xso1rHFfcsPy3X1kj2uDk115z9tLTMxPEnba/zdNB6WazvrLRWGPuV9d4hHwr1iVz5quTFlZCbLBlE5fS2TqLA==", + "resolved": "22.5.1.1", + "contentHash": "vBObO1atXxhi2C4AuC53y87Bqeogi9xIrD1Ym3YWr5uxc8Nrm8SrsFeD1i3x04R999+XQ3FdeHrbXScyWYEHqQ==", "dependencies": { "Mono.Cecil": "0.11.4", "System.Collections.NonGeneric": "4.3.0", @@ -152,16 +174,114 @@ "System.Reflection.TypeExtensions": "4.7.0" } }, + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7VSGO0URRKoMEAq0Sc9cRz8mb6zbyx/BZDEWhgPdzzpmFhkam3fJ1DAGWFXBI4nGlma+uPKpfuMQP5LXRnOH5g==" + }, + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "0oAaTAm6e2oVH+/Zttt0cuhGaePQYKII1dY8iaqP7CvOpVKgLybKRFvQjXR2LtxXOXTVPNv14j0ot8uV+HrUmw==" + }, + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "G24ibsCNi5Kbz0oXWynBoRgtGvsw5ZSVEWjv13/KiCAM8C6wz9zzcCniMeQFIkJ2tasjo2kXlvlBZhplL51kGg==" + }, + "runtime.native.System": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", + "dependencies": { + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" + } + }, + "runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "QR1OwtwehHxSeQvZKXe+iSd+d3XZNkEcuWMFYa2i0aG1l+lR739HPicKMlTbJst3spmeekDVBUS7SeS26s4U/g==", + "dependencies": { + "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2", + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "I+GNKGg2xCHueRd1m9PzeEW7WLbNNLznmTuEi8/vZX71HudUbx1UTwlGkiwMri7JLl8hGaIAWnA/GONhu+LOyQ==" + }, + "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "1Z3TAq1ytS1IBRtPXJvEUZdVsfWfeNEhBkbiOCGEl9wwAfsjP2lz3ZFDx5tq8p60/EqbS0HItG5piHuB71RjoA==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==" + }, + "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "6mU/cVmmHtQiDXhnzUImxIcDL48GbTk+TsptXyJA+MIOG9LRjPoAQC/qBFB7X+UNyK86bmvGwC8t+M66wsYC8w==" + }, + "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "vjwG0GGcTW/PPg6KVud8F9GLWYuAV1rrw1BKAqY0oh4jcUqg15oYF1+qkGR2x2ZHM4DQnWKQ7cJgYbfncz/lYg==" + }, + "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "7KMFpTkHC/zoExs+PwP8jDCWcrK9H6L7soowT80CUx3e+nxP/AFnq0AQAW5W76z2WYbLAYCRyPfwYFG6zkvQRw==" + }, + "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "xrlmRCnKZJLHxyyLIqkZjNXqgxnKdZxfItrPkjI+6pkRo5lHX8YvSZlWrSI5AVwLMi4HbNWP7064hcAWeZKp5w==" + }, + "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.2", + "contentHash": "leXiwfiIkW7Gmn7cgnNcdtNAU70SjmKW3jxGj1iKHOvdn0zRWsgv/l2OJUO5zdGdiv2VRFnAsxxhDgMzofPdWg==" + }, "Samboy063.Cpp2IL.Core": { "type": "Transitive", - "resolved": "2022.0.4", - "contentHash": "6fLZDkAmnClogPwDWCkB5NYG1uqUShOkdNap4ud3m5cLI57BiBRzBoUBEKV+9SNtSirUrEAOUP+0kIfduTLYWQ==", + "resolved": "2022.0.7.2", + "contentHash": "e17CAhKSxdAp7vFcYEVxjSc7t7rcvVGBipGhgE9tAjRXY85wZXLlHEVO0AEvMRkLmf9U+if4wK/nZEC1AX6VqQ==", "dependencies": { "HarmonyX": "2.5.5", "Iced": "1.11.1", "IndexRange": "1.0.0", "Mono.Cecil": "0.11.4", - "Samboy063.LibCpp2IL": "2022.0.4", + "Samboy063.LibCpp2IL": "2022.0.7.2", "System.Runtime.InteropServices": "4.3.0", "System.ValueTuple": "4.5.0", "js6pak.Gee.External.Capstone": "2.1.0" @@ -169,8 +289,8 @@ }, "Samboy063.LibCpp2IL": { "type": "Transitive", - "resolved": "2022.0.4", - "contentHash": "KxUvnPgGSIgmLqVbK2fMCUmL+RJK0Ccvhkj2boVMtkE9feEFxtriHX7YkqZGQtXs55CpyETm8nZfYhj1m2bYkQ==", + "resolved": "2022.0.7.2", + "contentHash": "DpVeU0dQkUJCpbw0f1xKOG8vimdK2oYhrVABC7HDE3/ikGjhnMZd3i5b1joKDHZ+AiL90TWOaEXPcy24rCZ0dA==", "dependencies": { "IndexRange": "1.0.0", "Samboy063.WasmDisassembler": "2022.0.2" @@ -196,6 +316,23 @@ "System.Runtime": "4.3.0" } }, + "System.Collections.Concurrent": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, "System.Collections.NonGeneric": { "type": "Transitive", "resolved": "4.3.0", @@ -273,6 +410,28 @@ "System.Runtime": "4.3.0" } }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", + "dependencies": { + "System.Collections": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Diagnostics.Tracing": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, "System.Globalization": { "type": "Transitive", "resolved": "4.3.0", @@ -283,6 +442,17 @@ "System.Runtime": "4.3.0" } }, + "System.Globalization.Calendars": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Globalization": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Globalization.Extensions": { "type": "Transitive", "resolved": "4.3.0", @@ -308,6 +478,21 @@ "System.Threading.Tasks": "4.3.0" } }, + "System.IO.FileSystem": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, "System.IO.FileSystem.Primitives": { "type": "Transitive", "resolved": "4.3.0", @@ -328,6 +513,50 @@ "System.Runtime.Extensions": "4.3.0" } }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.1", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Diagnostics.DiagnosticSource": "4.3.0", + "System.Diagnostics.Tracing": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Extensions": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.Net.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Security.Cryptography.X509Certificates": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.2" + } + }, + "System.Net.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Runtime.Handles": "4.3.0" + } + }, "System.Reflection": { "type": "Transitive", "resolved": "4.3.0", @@ -435,6 +664,161 @@ "System.Runtime.Handles": "4.3.0" } }, + "System.Runtime.Numerics": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", + "dependencies": { + "System.Globalization": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.Apple": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Cng": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0" + } + }, + "System.Security.Cryptography.Csp": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Collections.Concurrent": "4.3.0", + "System.Linq": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.OpenSsl": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", + "dependencies": { + "System.Collections": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", + "dependencies": { + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.IO": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Threading": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "System.Collections": "4.3.0", + "System.Diagnostics.Debug": "4.3.0", + "System.Globalization": "4.3.0", + "System.Globalization.Calendars": "4.3.0", + "System.IO": "4.3.0", + "System.IO.FileSystem": "4.3.0", + "System.IO.FileSystem.Primitives": "4.3.0", + "System.Resources.ResourceManager": "4.3.0", + "System.Runtime": "4.3.0", + "System.Runtime.Extensions": "4.3.0", + "System.Runtime.Handles": "4.3.0", + "System.Runtime.InteropServices": "4.3.0", + "System.Runtime.Numerics": "4.3.0", + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Cng": "4.3.0", + "System.Security.Cryptography.Csp": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.OpenSsl": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading": "4.3.0", + "runtime.native.System": "4.3.0", + "runtime.native.System.Net.Http": "4.3.0", + "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" + } + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", diff --git a/nuget.config b/nuget.config index 9a4ac66..f64757a 100644 --- a/nuget.config +++ b/nuget.config @@ -3,5 +3,6 @@ + \ No newline at end of file