From: EoF-1141 <61620805+EoF-1141@users.noreply.github.com> Date: Thu, 20 May 2021 16:25:50 +0000 (+0200) Subject: Version v2.6.4 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=7691501a6915779ca090530231be43649974e563;p=rhonda%2Ftheotherroles.git Version v2.6.4 --- diff --git a/Images/Banner.png b/Images/Banner.png deleted file mode 100644 index 12c3612..0000000 Binary files a/Images/Banner.png and /dev/null differ diff --git a/Images/Colors.png b/Images/Colors.png deleted file mode 100644 index a61a660..0000000 Binary files a/Images/Colors.png and /dev/null differ diff --git a/Images/Preview_2.6.0.png b/Images/Preview_2.6.0.png deleted file mode 100644 index 51d5f53..0000000 Binary files a/Images/Preview_2.6.0.png and /dev/null differ diff --git a/Images/TOR_colors.png b/Images/TOR_colors.png new file mode 100644 index 0000000..78ee632 Binary files /dev/null and b/Images/TOR_colors.png differ diff --git a/Images/TOR_logo.png b/Images/TOR_logo.png new file mode 100644 index 0000000..86e01f9 Binary files /dev/null and b/Images/TOR_logo.png differ diff --git a/Images/TOR_roles.png b/Images/TOR_roles.png new file mode 100644 index 0000000..51d24a5 Binary files /dev/null and b/Images/TOR_roles.png differ diff --git a/Images/TOR_server.png b/Images/TOR_server.png new file mode 100644 index 0000000..7d8f5c0 Binary files /dev/null and b/Images/TOR_server.png differ diff --git a/README.md b/README.md index f491fb7..3ec2f2a 100644 --- a/README.md +++ b/README.md @@ -1,966 +1,983 @@ -[![Discord](https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg)](https://discord.gg/77RkMJHWsM) -![eisbison banner](./Images/Banner.png) -![eisbison infographic](./Images/Preview_2.6.0.png) - - -# The Other Roles - -The **The Other Roles**, is a mod for [Amongs Us](https://store.steampowered.com/app/945360/Among_Us) which adds many new roles, new [Settings](#settings), new [Custom Hats](#custom-hats) and support for [10+ Player Lobbies](#Custom-Servers-and-10+-Players) to the game. -Even more roles are coming soon :) - -| Impostors | Crewmates | Neutral | -|----------|-------------|-----------------| -| [Bad Child](#child) | [Child](#child) | [Arsonist](#arsonist) | -| [Camouflager](#camouflager) | [Detective](#detective) | [Jester](#jester) | -| [Cleaner](#cleaner) | [Engineer](#engineer) | [Jackal](#jackal) | -| [Eraser](#eraser) | [Hacker](#hacker) | [Sidekick](#sidekick) | -| [ImpLover](#lovers) | [Lover](#lovers) | | -| [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | | -| [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | | -| [Janitor (Mafia)](#mafia) | [Medic](#medic) | | -| [Morphling](#morphling) | [Security Guard](#security-guard) | | -| [Trickster](#trickster) | [Seer](#seer) | | -| [Vampire](#vampire) | [Sheriff](#sheriff) | | -| [Warlock](#warlock) | [Shifter](#shifter) | | -| | [Snitch](#snitch) | | -| | [Spy](#spy) | | -| | [Swapper](#swapper) | | -| | [Time Master](#time-master) | | -| | [Tracker](#tracker) | | - -The [Role Assignment](#role-assignment) sections explains how the roles are being distributed among the players. - -# Releases -| Among Us - Version| Mod Version | Link | -|----------|-------------|-----------------| -| 2021.5.10s| v2.6.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.3/TheOtherRoles.zip) -| 2021.5.10s| v2.6.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.2/TheOtherRoles.zip) -| 2021.4.14s| v2.6.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.1/TheOtherRoles.zip) -| 2021.4.14s| v2.6.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.0/TheOtherRoles.zip) -| 2021.4.14s| v2.5.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.1/TheOtherRoles.zip) -| 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip) -| 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip) -| 2021.4.14s| v2.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.3.0/TheOtherRoles.zip) -| 2021.4.14s| v2.2.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.2/TheOtherRoles.zip) -| 2021.4.12s| v2.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.1/TheOtherRoles.zip) -| 2021.4.12s| v2.2.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.0/TheOtherRoles.zip) -| 2021.3.31.3s| v2.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.1.0/TheOtherRoles.zip) -| 2021.3.31.3s| v2.0.1 beta| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.0.1/TheOtherRoles.zip) -| 2021.3.31.3s | v2.0.0 beta | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.0.0/TheOtherRoles.zip) -| 2021.3.5s | v1.8.2 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.8.2/TheOtherRoles.zip) -| 2021.3.5s | v1.8.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.8.1/TheOtherRoles.zip) -| 2021.3.5s | v1.8 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.8/TheOtherRoles.zip) -| 2021.3.5s | v1.7 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.7/TheOtherRoles.zip) -| 2021.3.5s | v1.6 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.6/TheOtherRoles.zip) -| 2021.3.5s | v1.5 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.5/TheOtherRoles.zip) -| 2021.3.5s | v1.4 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip) -| 2021.3.5s | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip) -| 2020.12.19s | v1.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.1/TheOtherRoles.zip) -| 2020.12.19s | v1.0 | [Download](https://github.com/Eisbison/TheOtherRoles/files/6097191/TheOtherRoles.zip) - -# Changelog -
- Click to show the Changelog - -**Version 2.6.3** -- Changed the role limits options to allow for minimum and maximum bounds -- Changed the role assignment to be more random when assigning roles (previously assigned the neutral roles before assigning the crewmate roles) -- Added new `flip` option to [Custom Hats](#custom-hats) - -**Version 2.6.2** -- The Other Roles now supports the new Among Us version **2021.5.10s** -- Added a chat command to kick players as the host of a lobby (/kick playerName) - -**Version 2.6.1** -- Fixed a bug where the Sheriff was unable to kill the Arsonist -- Fixed a bug in the role assignment system -- Added the option to select the Dleks map -- Improved the overlay of the Arsonist - -**Version 2.6.0** -- **New Role:** [Arsonist](#arsonist) -- Added an Ingame Updater, to make it easier to update the Mod -- Added synchronization for Airship toilet doors. Doors now open/close for everyone -- Changed Shifter to also die when shifting a neutral role (Jester, Arsonist, Jackal, ...) -- Changed the option "Jester Can Die To Sheriff" to "Neutrals Can Die To Sheriff" -- Changed the role assignment system. You can now set how many neutral roles you want in your game -- Changed Hacker to see colors more clearly on Admin Table -- Changed version handshake to give more clear info -- Fixed a problem with the Hat Tab leaving too much space between categories -- Fixed an Among Us bug, which made the selected region always show "North America" -- Fixed an Among Us bug, which made the disconnect info be off-screen. (hopefully) - -**Version 2.5.1** -- **New Hats:** We added the support for custom hats and there are already a few hats inside the game. We can add new hats without updating the mod and we're awaiting your hat designs on our discord server. -- Changed Lovers to ignore Lover's Tasks for Taskwin, while an ImpLover is alive -- Fixed a bug where garlic was not visible in some places -- The Security Guard can't place cameras on MiraHQ anymore -- Fixed a bug on there Airship, where the view of the cameras that the Security Guard placed wasn't centered on the camera. - -**Version 2.5.0** -- **New Role:** [Security Guard](#security-guard) -- Fixed a bug where the game would stop after the first meeting -- Fixed a bug where killing with the hotkey Q ignored shields - -**Version 2.4.0** -- **New Role:** [Warlock](#warlock) -- Added an option that allows ghosts to see the roles and remaining tasks of other players -- Added options to configure Morph & Camo duration -- Added hotkeys to the custom buttons (**Q** for the buttons that are on the same place as the kill button, **F** for the buttons that are above the kill button) -- Fixed an oversight which made StreamerMode only work as host -- Fixed an oversight which required Jackals to finish Tasks, after Sidekick was promoted -- Fixed an oversight which made Sidekicks not promote, if the Jackal disconnected -- Fixed a bug where the Trickster box was invisible -- Fixed a bug where changes to the server ip and port would only be applied if the game was restarted -- Added a way to get the 2 Hidden [Colors](#colors) - -**Version 2.3.0** -- **New Role:** [Cleaner](#cleaner) -- Added 12 new [Colors](#colors) -- We added support for creating [Custom Hats](#custom-hats). New hats are coming with the next version, but you can already create and submit your own hats on [Discord](https://discord.gg/77RkMJHWsM). -- Added the option to hide the name of players with an unknown role -- Added Trickster Box vent animation. Thanks to [Drakoni](https://twitter.com/Drakoni13) -- You can now change the custom server ip/port right inside the game -- The Jackal, the Sidekick and the Jester now have fake tasks -- Added outlines, to show who you're targetting with your ability. Thanks to [Sihaack](https://github.com/sihaack) for part of the code. -- Added a streamer mode to Among Us, which hides lobby codes, the ip of your custom server and the port of your custom server. You can also modify the text that replaces the lobby code, check [Settings](#settings) for more details. -- Changed Meeting Hud Layout when playing with more than 10 players -- Fixed a bug where **ImpLovers** would hardly spawn -- Fixed a bug where players could get stuck on ladders/platforms when being rewinded -- Fixed a bug where players could only use quickchat -- Fixed a bug which prevented to play in Freeplay mode -- Fixed a bug which moved the Ping info off-screen - -**Version 2.2.2** -- Among Us version 2021.4.14s compatibility -- Improved the block votes on emergency meeting option - -**Version 2.2.1** -- Trickster: The vent button now has a custom texture. Fixed a bug where the Trickster could clip out of bounds when his box was close to a wall. -- Fixed a bug where the Bad Child's kill button went on cooldown when someone else performed a kill -- Fixed a few bugs with footprints, Seer souls and the Vampire delayed kill -- Fixed a bug where the Child was banned for hacking (because of its reduced kill cooldown) -- Improved the version handshake - -**Version 2.2.0** -- **Works with the latest Among Us version (2021.4.12s)** -- **Added support for 10+ player lobbies on custom servers:** Check the [Custom Servers and 10+ Players](#Custom-Servers-and-10+-Players) section. During meetings use the up/down keys, on vitals use the left/right keys. -- **Added a new Impostor role: The Trickster** check the [Trickster](#trickster) section for more infos -- You can now set how long the Time Master shield lasts -- The host now sees for how long the lobby will remain open -- We changed the look/layout of the settings -- Added a new option that deactivates skipping in meetings (if the player does not vote, he votes himself) -- You can now choose whether the Eraser is able to erase the Spy/Impostors or not -- Fixed a bug where a Lovers win wasn't displayed properly -- Fixed the Among Us bug where people were unable to move after meetings -- We added a version checking system: The host can only start the game if everyone in his lobby has the same version of the mod installed (he will see, who is using a wrong version). This prevents hacking in public lobbies and bugs because of version missmatches. -- Fixed a bug where the Child Impostor had the same cooldowns as normal Impostors -- Fixed a bug where the Vampire/Janitor/Mafioso would lose their kill button after being erased -- The Child is now able to use ladders and it can do all the tasks right away - -**Version 2.1.0** -- **New Role:** [Spy](#spy) -- **Eraser:** The Eraser can now also remove the role of other Impostors. This enables him to reveal the Spy, but might result in removing the special ability of his partner. -- **Camouflager:** The Child age/size will now also be hidden, to allow the Child Impostor to kill during camouflage - -**Hotfix 2.0.1** -- Fixed a bug where camouflaged players would get stuck on ladders/platforms on the airship -- Introduced a one second cooldown after the Morphling sampled another player -- The Child can now always reach all usables (ladders, tasks, ...) -- We removed a bug, where some footprints remained on the ground forever -- We removed a bug, where the Detective didn't see the right color type when reporting a player -- We changed the Jester win and Child lose conditions, they're not being affected by server delays anymore - -**Changes in 2.0.0** -- **New button art** created by **Bavari** -- **New mod updater/installer tool** created by [Narua](https://github.com/Narua2010) and [Jolle](https://github.com/joelweih). Check the [Installation](#installation) section for more details. -- **Custom options:** Introduced customizable presets. Starting with 2.0.0, settings can be copied and used with higher versions (2.0.0). -- **Time Master rework:** Check [Time Master](#time-master) for more information -- **Medic:** The Medic report changed, it only shows the time since death (see Detective) -- **Detective:** The Detective now sees the name/color type of the killer when he reports a dead body (ability moved from the Medic to the Detective) -- **Lighter:** We changed and tried to nerf the Lighter, see the [Lighter](#lighter) section for more details. -- **Seer:** As the role didn't work the way it was, we completely changed it. We're still working on the role, for now we're trying a few things. Check the [Seer](#seer) section to get more details about the new Seer. -- **Shifter:** We reworked the Shifter, he is now part of the crew. Check out the [Shifter](#shifter) sections for more details. -- **Hacker:** The Hacker is basically the old Spy. We added the option to only show the color type instead of the color on the admin table. -- **Camouflager:** Now also overrides the information of other roles, check the [Camouflager](#camouflager) section for more details. -- **Morphling:** Now also overrides the information of other roles, check the [Morphling](#morphling) section for more details -- **Child:** The Child can now be a Crewmate Child or an Impostor Child, check the [Child](#child) section for more details -- **Eraser:** The Eraser, a new Impostor role, is now part of the mod. Check the [Eraser](#eraser) section for more details -- **New options:** - - You can now set the maximum number of meetings in a game: Every player still only has one meeting. The Mayor can always use his meeting (even if the maximum number of meetings was reached). Impostor/Jackal meetings also count. - -**Hotfix 1.8.2** -- Add map and Impostor count to lobby settings. -- Fixed bugs where changing players to be the Sidekick didn't reset all the effects of their previous role. - -**Hotfix 1.8.1** Resolves bugs that occured when the Jackal recruited a Medic, Swapper and Tracker\ -\ -**Changes in v1.8:** -- **New Roles:** Added the Jackal and Sidekick roles -- Vampire: Medic report shows the right info now. A bitten Swapper is not able to swap if he dies at the beginning of a meeting. One can now set the cooldown and whether a normal kill is possible when a target is next to a garlic or not. -- Lover: New option that sets how often an ImpLover appears. If a Lover is exiled, his partner doesn't spawn a dead body anymore. -- Cooldowns now stop cooling down, if a player sits inside a vent. -- Fixed a bug that prevented the game from continuing after a meeting (for an edge case with Lovers) -- If two players try to kill each other at the same time both should die (e.g. Sheriff vs Impostor) -- We added a description for your current role right above the task list -- Added a description for the [Role Assignment System](#role-assignment) - -\ -**Changes in v1.7:** -- **New Roles:** The Vampire, the Tracker and the Snitch are now in the game -- The role assignment system has been changed -- Impostors now see a blue outline around all vents of the map, if the Engineer sits inside one of them - -\ -**Changes in v1.6:** -- This update is a small hotfix, fixing the bug where some people were unable to join lobbies. -- The Child can't be voted out anymore before it turns 18, hence games can't end anymore because the Child died. -- Footprints are no longer visible to the Detective, if players are inside vents. - -\ -**Changes in v1.5:** -- Time Master - Buff: He is not affected by his rewind anymore, which gives him more utility. Players will now be rewinded out of vents. -- Child - Nerf: The Child now grows up (see [Child](#child)) and becomes a normal Crewmate at some point. A growing Child is not killable anymore. Some tasks are still not doable for the small Child, we are working on that. But eventually when growing up it can do all the tasks as it's size increases. -- Seer - Nerf: Added an option that sets how often the Seer mistakes the player for another. -- Hacker - Nerf: The Hacker now only sees the additional information when he activates his "Hacker mode". That should stops the Hacker from camping the admin table/vitals. -- Other: Camouflager/Morphling cooldowns were fixed. Custom regions code was removed to enable 3rd party tools. Some minor bugfixes. - -**Changes in v1.4:** -- Fixing a Camoflager/Morphling animation bug -- Fixing a bug where the Swapper could swap votes even if they are dead -- The custom cooldown buttons now render the cooldown progress (the grey overlay) in the right way (v1.3 introduced the bug) -- Players in vents are not targetable anymore by the role actions, the button does not activate (e.g. Seer revealing, Morphling sample). Exception: Impostor killing an Engineer in a vent - -**Changes in v1.3:** -- Adds support for the Among Us version **2021.3.5s** -- Fixes a bug where a an edge case caused all players to start the game with the camouflaged look -- There might be a few bugs, since I focused on getting the update out fast. A new version resolving the bugs will be published tomorrow. - -**Changes in v1.1:** -- Morphling: The color of pet now also morphs. The skin animation now starts at the right point. -- The game over screen now shows if the Jester/Child/Lovers won. -- A bug was removed where the Jester won together with the Crewmates. -- A bug was removed where the game of the Lovers crashed if they were the last players killed by the host of the lobby. -
- -# Installation -## Windows Auto Installation/Updating Using The AmongUsModUpdater -This tool was made by [Narua](https://github.com/Narua2010) and [Jolle](https://github.com/joelweih). It automatically installs/updates the TheOtherRoles mod to the newest version, runs the game and -syncs your game options automatically. - -1. Simply get the newest [release](https://github.com/Narua2010/AmongUsModUpdater/releases/latest) of the [AmongUsModUpdater](https://github.com/Narua2010/AmongUsModUpdater) -2. Unzip the folder and run the **setup.exe** - -If you have problems with the AmongUsModUpdater, make sure To [contact us](https://discord.gg/csa3pHYdPU). - -## Windows Manual Installation -1. Download and unzip the newest [release](https://github.com/Eisbison/TheOtherRoles/releases/latest) -2. Find the folder of your game, for Steam players you can right click in Steam, on the game, a menu will appear proposing you to go to the folders. -3. Make a copy of your game, it's not obligatory but advised, put it where you want. -4. Drag or extract the files from the zip into your game, at the .exe level. -5. Run the game (the first launch might take a while) - -Not working? You might want to install the dependency [vc_redist](https://aka.ms/vs/16/release/vc_redist.x86.exe) - -![Install](https://i.imgur.com/pvBAyZN.png) - -**Linux Manual** -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://bepinex.github.io/bepinex_docs/master/articles/advanced/steam_interop.html#protonwine) -4. Launch the game via Steam - -# Custom Servers and 10+ Players -We always recommend you to play on custom servers rather than on the official ones. If you want to play with more than 10 players in one lobby, you're required to use a custom server. Credits for the original implementation that allowed 10+ player lobbies go to the creaters of the [Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod). - -**Setup the Clients:** -1. Open the region menu where you can choose between the different regions -2. Select the region *Custom* -3. Enter the ip/domain and the port of your custom server in the corresponding text fields - -**Setup the Server:** -1. Get the [Impostor](https://github.com/Impostor/Impostor) release for the Among Us version **2021.3.31 - 2021.4.2** -2. Follow the steps (using the server release you just downloaded) on the official [Impostor-Documentation](https://github.com/Impostor/Impostor/wiki/Running-the-server) -3. Make sure to set the following values to false in the *config.json* file: -``` - ... - "AntiCheat": { - "Enabled": false, - "BanIpFromGame": false - } -``` -4. Make sure to forward the right ports on the hosting machine -5. Run the server and setup the client - -**Setting up Server as Docker Container:** \ -If you want to run the server as a docker container you'll need to use the image -``` -aeonlucid/impostor:nightly -``` -(Currently only the "nightly" tag is starting a server supporting 2021.3.31 or later) - -In addition to running it we need to set the environment variables to disable the AntiCheat feature. -``` -IMPOSTOR_AntiCheat__Enabled=false -IMPOSTOR_AntiCheat__BanIpFromGame=false -``` - -Example to docker run command: -``` -docker run -p 22023:22023/udp --env IMPOSTOR_AntiCheat__Enabled=false --env IMPOSTOR_AntiCheat__BanIpFromGame=false aeonlucid/impostor:nightly -``` - -Or use to run it in the background -``` -docker run -d -p 22023:22023/udp --env IMPOSTOR_AntiCheat__Enabled=false --env IMPOSTOR_AntiCheat__BanIpFromGame=false aeonlucid/impostor:nightly -``` - - -# Credits & Resources -[OxygenFilter](https://github.com/NuclearPowered/Reactor.OxygenFilter) - For all versions above v2.3.0, we're 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\ -[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 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**\ -[Town-Of-Impostors](https://github.com/AJMix/TownOfImpostors) - Idea for the Arsonist role comes from **AJMix** - -# 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 Impostors:** The number of Impostor count be set inside a lobby -- **Map:** The map can be changed inside a lobby -- **Maximum Number Of Meetings:** You can set the maximum number of meetings that can be called in total (every player still has personal maximum of buttons, but if the maximum number of meetings is reached you can't use your meetings even if you have some left, Impostor and Jackal meetings also count) -- **Allow Skips On Emergency Meetings:** If set to false, there will not be a skip button in emergency meetings. If a player does not vote, he'll vote himself. -- **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 alse see the name of the Spy and everyone can still see the age of the child. -- **Ghosts Can See Roles And Remaining Tasks:** If set to true, ghosts can see the role and the number of remaining tasks of each player. -- **Dleks:** You are now able to select the Dleks map. - -# Custom Hats -## Create and submit new hat designs -We're awaiting your creative hat designs and we'll integrate all the good ones in our mod. -Here are a few instructions, on how to create a custom hat: - -- **Creation:** A hat consists of up to three textures. The aspect ratio of the textures has to be `4:5`, we recommend `300px:375px`: - - `Main texture (required)`: - - This is the main texture of your hat. It will usually be rendered in front of the player, if you set the `behind` parameter it will be rendered behind the player. - - The name of the texture needs to follow the pattern *hatname.png*, but you can also set some additional parameters in the file name by adding *_parametername* to the file name (before the *.png*). - - Parameter `bounce`: This parameter determines whether the hat will bounce while you're walking or not. - - Parameter `adaptive`: If this parameter is set, the Among Us coloring shader will be applied (the shader that replaces some colors with the colors that your character is wearing in the game). The color red (#ff0000) will be replaced with the primary color of your player and the color blue (#0000ff) with the secondary color. Also other colors will be affected and changed, you can have a look at the texture of the [Crewmate Hat](https://static.wikia.nocookie.net/among-us-wiki/images/e/e0/Crewmate_hat.png) to see how this feature should be used. - - Parameter `behind`: If this parameter is set, the main texture will be rendered behind the player. - - `Flipped texture (optional)`: - - This texture will be rendered instead of the Main texture, when facing the left. - - The name of the texture needs to follow the pattern *hatname_flip.png*. - - `Back texture (optional)`: - - This texture will be rendered behind the player. - - The name of the texture needs to follow the pattern *hatname_back.png*. - - `Flipped Back texture (optional)`: - - This texture will be rendered instead of the Back texture, when facing the left. - - The name of the texture needs to follow the pattern *hatname_back_flip.png*. - - `Climb texture (optional)`: - - This texture will be rendered in front of the player, when he's climbing. - - The name of the texture needs to follow the pattern *hatname_climb.png*. -- **Testing:** You can test your hat design by putting all the files in the *\TheOtherHats\Test* subfolder of your mod folder. Then whenever you start a freeplay game, you and all the dummies will be wearing the new hat. You don't need to restart Among Us if you change the hat files, just exit and reenter the freeplay mode. - -- **Submission:** If you got a hat design, you can submit it on our [Discord server](https://discord.gg/77RkMJHWsM). We'll look at all the hats and add all the good ones to the game. - -# Colors -drawing - -# Roles - -## Role Assignment -We are still improving the role assignment system. It's not that intuitive right now, but it's more flexible than the older one -if you're using it right. - -First you need to choose how many special roles of each kind (Impostor/Neutral/Crewmate) you want in the game. -The count you set will only be reached, if there are enough Crewmates/Impostors in the game and if enough roles are set to be in the game (i.e. they are set to > 0%). The roles are then being distributed as follows: -- First all roles that are set to 100% are being assigned to arbitrary players -- After that each role that has 10%-90% selected adds 1-9 tickets to a ticket pool (there exists a ticket pool for Crewmates, Neutrals and Impostors). Then the roles will be selected randomly from the pools as long it's possible (until the selected number is reached, until there are no more Crewmates/Impostors or until there are no more tickets). If a role is selected from the pool, obviously all the tickets of that role are being removed. -- The Mafia, Lovers and Child are being selected independently (without using the ticket system) according to the spawn chance you selected. After that the Crewmate, Neutral and Impostor roles are selected and assigned in a random order. - -**Example:**\ -Settings: 2 special Crewmate roles, Snitch: 100%, Hacker: 10%, Tracker: 30%\ -Result: Snitch is assigned, then one role out of the pool [Hacker, Tracker, Tracker, Tracker] is being selected\ -Note: Changing the settings to Hacker: 20%, Tracker: 60% would statistically result in the same outcome . - - -## Mafia -### **Team: Impostors** -The Mafia are a group of three Impostors.\ -The Godfather works like a normal Impostor.\ -The Mafioso is an Impostor who cannot kill nor sabotage until the Godfather is dead.\ -The Janitor is an Impostor who cannot kill nor sabotage, but they can hide dead bodies instead.\ -\ -**NOTE:** -- There have to be 3 Impostors activated for the mafia to spawn. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Mafia Spawn Chance | - -| Janitor Cooldown | - ------------------------ - -## Morphling -### **Team: Impostors** -The Morphling is an Impostor which can additionally scan the appearance of a player. After an arbitrary time they can take on that appearance for 10s. -\ -**NOTE:** -- They shrink to the size of the Child when they copies its look. -- The Hacker sees the new color on the admin table. -- The color of the footprints changes accordingly (also the ones that were already on the ground). -- The other Impostor still sees that they are an Impostor (the name remains red). -- The shield indicator changes accordingly (the Morphling gains or loses the shield indicator) -- Tracker and Snitch arrows keep working. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Morphling Spawn Chance | - -| Morphling Cooldown | - -| Morph Duration | Time the Morphling stays morphed ------------------------ - -## Camouflager -### **Team: Impostors** -The Camouflager is an Impostor which can additionally activate a camouflage mode. -The camouflage mode lasts for 10s and while it is active, all player names/pets/hats -are hidden and all players have the same color.\ -\ -**NOTE** -- The Child will look like all the other players -- The color of the footprints turns gray (also the ones that were already on the ground). -- The Hacker sees gray icons on the admin table -- The shield is not visible anymore -- Tracker and Snitch arrows keep working - -### Game Options -| Name | Description | -|----------|:-------------:| -| Camouflager Spawn Chance | - -| Camouflager Cooldown | - -| Camo Duration | Time players stay camouflaged ------------------------ - -## Vampire -### **Team: Impostors** -The Vampire is an Impostor, that can bite other player. Bitten players die after a configurable amount of time.\ -If the Vampire spawn chance is greater 0 (even if there is no Vampire in the game), all players can place one garlic.\ -If a victim is near a garlic, the "Bite Button" turns into the default "Kill Button" and the Vampire can only perform a normal kill.\ -\ -**NOTE:** -- If a bitten player is still alive when a meeting is being called, he dies at the start of the meeting. -- The cooldown is the same as the default kill cooldown (+ the kill delay if the Vampire bites the target). -- If there is a Vampire in the game, there can't be a Warlock - -### Game Options -| Name | Description | -|----------|:-------------:| -| Vampire Spawn Chance | - -| Vampire Kill Delay | - -| Vampire Cooldown | Sets the kill/bite cooldown -| Vampire Can Kill Near Garlics | The Vampire can never bite when his victim is near a garlic. If this option is set to true, he can still perform a normal kill there. ------------------------ - -## Eraser -### **Team: Impostors** -The Eraser is an Impostor that can erase the role of every player.\ -The targeted players will lose their role after the meeting right before a player is exiled.\ -After every erase, the cooldown increases by 10 seconds.\ -The erase will be performed, even if the Eraser or his target die before the next meeting.\ -By default the Eraser can erase everyone but the Spy and other Impostors. Depending on the options -he 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 his target, depending on who the Eraser erased) -- Erasing a Lover automatically erases the other Lover as well (if the second Lover is an ImpLover, he 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 erasing is being triggered before the eject 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. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Eraser Spawn Chance | - -| Eraser Cooldown | The Erasers cooldown will increase by 10 seconds after every erase. -| Eraser Can Erase Anyone | If set to false, he can't erase the Spy and other Impostors ------------------------ - -## Trickster -### **Team: Impostors** -The Trickster is an Impostor that can place 3 jack-in-the-boxes that are invisible at first to other players. \ -If the Trickster has placed all of his boxes they will be converted into a vent network usable only by the Trickster himself, but the boxes are revealed to the others. \ -If the boxes are converted to a vent network, the Trickster gains a new ability "Lights out" to limit the visibility of Non-Impostors, that cannot be fixed by other players. Lights are automatically restored after a while. \ - -\ -**NOTE:** -- Impostors will get a text indicator at the bottom of the screen to notify them if the lights are out due to the Trickster ability, as there is no sabotage arrows or task to sabotage text to otherwise notify them about it. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Trickster Spawn Chance | - -| Trickster Box Cooldown | Cooldown for placing jack-in-the-boxes -| Trickster Lights Out Cooldown | Cooldown for his "lights out" ability -| Trickster Lights Out Duration | Duration after which the light is automatically restored ------------------------ - -## Cleaner -### **Team: Impostors** -The Cleaner is an Impostor who has the ability to clean up dead bodies. \ - -\ -**NOTE:** -- The Kill and Clean cooldown are shared, preventing him from immediately cleaning his own kills. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Cleaner Spawn Chance | - -| Cleaner Cooldown | Cooldown for cleaning dead bodies ------------------------ - - -## Warlock -### **Team: Impostors** -The Warlock is an Impostor, that can curse another player (the cursed player doesn't get notified).\ -If the cursed person stands next to another player, the Warlock is able to kill that player (no matter how far away he is).\ -Performing a kill with the help of a cursed player, will lift the curse and it will result in the Warlock being unable to move for a configurable amount of time. -The Warlock can still perform normal kills, but the two buttons share the same cooldown. - -\ -**NOTE:** -- The Warlock can always kill his Impostor mates (and even himselfs) using the "cursed kill" -- If there is a Warlock in the game, there can't be a Vampire -- Performing a normal kill, doesn't lift the curse - -### Game Options -| Name | Description | -|----------|:-------------:| -| Warlock Spawn Chance | - -| Warlock Cooldown | Cooldown for using the Curse and curse Kill -| Warlock Root Time | Time the Warlock is rooted in place after killing using the curse ------------------------ - -## Lovers -### **Team: Either Crewmates or Impostors** -There are always two Lovers which are linked together.\ -Their goal is it to stay alive together until the end of the game.\ -If one Lover dies (and the option is activated), the other Lover suicides.\ -You can specify the chance of one Lover being an Impostor.\ -The Lovers never know the role of their partner, they only see who their partner is.\ -The Lovers win if they are both alive among the last 3 players, however they can also win with their respective role.\ -If both Lovers are Crewmates, they can achieve a "double victory" but the Crewmates then also win.\ -If one Lover is an Impostor and one a Crewmate, they can only get a "Lovers solo win" where only they win (even if there is another Impostor alive).\ -If there's an ImpLover in the game, the tasks of the Crewmate Lover won't be counted (for a task win) as long as he's alive. If the Lover dies, his tasks will also be counted.\ -\ -**NOTE:** -- If there is an equal amount of Impostors and Crewmates left in the game and one of the Lovers is an Impostor, the game is not automatically over since the Lovers can still achieve a solo win. E.g. if there are the following roles Impostor + ImpLover + Lover + Crewmate left, the game will not end and the next kill will decide if the Impostors or Lovers win. -- The Lovers can change if the Shifter takes the role of a Lovers - -### Game Options -| Name | Description | -|----------|:-------------:| -| Lovers Spawn Chance | - -| Chance That One Lover Is Impostor | - -| Both Lovers Die | Whether the second Lover suicides, if the first one dies ------------------------ - - -## Sheriff -### **Team: Crewmates** -The Sheriff has the ability to kill Impostors. -If they try to kill a Crewmate, they die instead. - -**NOTE** -- If the Sheriff shoots the person the Medic shielded, the Sheriff and the shielded person **both remain unharmed**. -- If the Sheriff shoots a Child Impostor, the Sheriff dies if the Child is still growing up. If it's 18, the Child Impostor dies. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Sheriff Spawn Chance | - -| Sheriff Cooldown | - -| Neutrals Can Die To Sheriff | - ------------------------ - -## Jester -### **Team: Neutral** -The Jester does not have any tasks. They win the game as a solo, if they get voted out during a meeting. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Jester Spawn Chance | - -| Jester can call emergency meeting | Option to disable the emergency button for the Jester ------------------------ - -## Arsonist -### **Team: Neutral** -The Arsonist does not have any tasks, he has to win the game as a solo.\ -The Arsonist can douse other players by pressing the douse button and remaining next to the player for a few seconds.\ -If the player that the Arsonist douses walks out of range, the cooldown will reset to 0.\ -After dousing everyone alive the Arsonist can ignite all the players which results in an Arsonist win. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Arsonist Spawn Chance | - -| Arsonist Countdown | - -| Arsonist Douse Duration | The time it takes to douse a player ------------------------ - -## Seer -### **Team: Crewmates** -The Seer has two abilities (one can activate one of them or both in the options). -The Seer sees the souls of players that died a round earlier, the souls slowy fade away. -The Seer gets a blue flash on his screen, if a player dies somewhere on the map. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Seer Spawn Chance | - -| Seer Mode | Options: Show death flash and souls, show death flash, show souls -| Seer Limit Soul Duration | Toggle if souls should turn invisible after a while -| Seer Soul Duration | Sets how long it will take the souls to turn invisible after a meeting ------------------------ - -## Engineer -### **Team: Crewmates** -The Engineer (if alive) can fix one sabotage per game from anywhere on the map.\ -The Engineer can use vents. If the Engineer is inside a vent, the Impostors will see a blue outline -around all vents on the map (in order to warn them). -Because of the vents the Engineer might not be able to start some tasks using the "Use" button, -you can double click on the tasks instead. -\ -**NOTE:** -- The kill button of Impostors activates if they stand next to a vent where the Engineer is. They can also kill them there. No other action (e.g. Morphling sample, Shifter shift, ...) can affect players inside vents. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Engineer Spawn Chance | - ------------------------ - -## Detective -### **Team: Crewmates** -The Detective can see footprints that other players leave behind. -The Detective's other feature shows when they report a corpse: they recieve clues about the killer's identity. The type of information they get is based on the time it took them to find the corpse. -\ -**NOTE** -- When people change their colors (because of a morph or camouflage), all the footprints also change their colors (also the ones that were already on the ground). If the effects are over, all footprints switch back to the original color. -- The Detective does not see footprints of players that sit in vents -- More information about the [colors](#colors) - -### Game Options -| Name | Description | -|----------|:-------------:| -| Detective Spawn Chance | - -| Anonymous Footprints | If set to true, all footprints will have the same color. Otherwise they will have the color of the respective player. -| Footprint Intervall | The intervall between two footprints -| Footprint Duration | Sets how long the footprints remain visible. -| Time Where Detective Reports Will Have Name | The amount of time that the Detective will have to report the body since death to get the killer's name. | -| Time Where Detective Reports Will Have Color Type| The amount of time that the Detective will have to report the body since death to get the killer's color type. | ------------------------ - -## Lighter -### **Team: Crewmates** -The Lighter can turn on his Lighter every now and then, which increases his vision by a costumizable amount. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Lighter Spawn Chance | - -| Lighter Mode Vision On Lights On | The vision the Lighter has when the lights are on and the Lighter mode is on -| Lighter Mode Vision On Lights Off | The vision the Lighter has when the lights are down and the Lighter mode is on -| Lighter Cooldown | - -| Lighter Duration | - ------------------------ - -## Child -### **Team: Crewmates or Impostors** -The Child can be a Crewmate (67% chance) or an Impostor (33% chance).\ -The Child's character is smaller and hence visible to everyone in the game.\ -The Child cannot be killed until it turns 18 years old, however it can be voted out.\ -**Impostor Child:** - - While growing up the kill cooldown is doubled. When it's fully grown up its kill cooldown is 2/3 of the default one. - - If it gets thrown out of the ship, everything is fine. - -**Crewmate Child:** - - The Crewmate Child aims to play out the strength its invincibility in the early game. - - If it gets thrown out of the ship before it turns 18, everyone loses. So think twice before you vote out a Child. - -**NOTE:** -- Impostors can't kill the Child (the button does not work) until it turns 18 -- The Sheriff can kill the Impostor Child, but only if it's fully grown up - -### Game Options -| Name | Description | -|----------|:-------------:| -| Child Spawn Chance | - -| Child | Child Growing Up Duration ------------------------ - -## Medic -### **Team: Crewmates** -The Medic can shield (highlighted by an outline around the player) one player per game, which makes the player unkillable.\ -The shielded player can still be voted out and might also be an Impostor.\ -If set in the options, the shielded player will get a red flash on their screen if someone (Impostor, Sheriff, ...) tried to murder them. -If the Medic dies, the shield disappears with them.\ -The Sheriff will not die if they try to kill a shielded Crewmate and won't perform a kill if they try to kill a shielded Impostor.\ -The Medic's other feature shows when they report a corpse: they will see how long ago the player died -\ -**NOTE** -- If the shielded player is a Lover and the other Lover dies, they nevertheless kill themselves. -- If the Shifter has a shield or their target has a Shield, the shielded player switches. - - -### Game Options -| Name | Description | Options | -|----------|:-------------:|:-------------:| -| Medic Spawn Chance | - | - -| Show Shielded Player | Sets who sees if a player has a shield | "Everyone", "Shielded + Medic", "Medic" -| Shielded Player Sees Murder Attempt| Whether a shielded player sees if someone tries to kill him | True/false | ------------------------ - -## Mayor -### **Team: Crewmates** -The Mayor leades the Crewmates by having a vote that counts twice.\ -The Mayor can always use his meeting, even if the maximum number of meetings was reached. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Mayor Spawn Chance | - ------------------------ - -## Hacker -### **Team: Crewmates** -If the Hacker activates the "Hacker mode", the Hacker gets more information than others from the admin table and vitals for a set duration.\ -Otherwise he sees the same information as everyone else. -**Admin table:** The Hacker can see the colors (or color types) of the players on the table.\ -**Vitals**: The Hacker can see how long dead players have been dead for.\ -\ -**NOTE:** -- If the Morphling morphes or the Camouflager camouflages, the colors on the admin table change accordingly -- More information about the [colors](#colors) - -### Game Options -| Name | Description | -|----------|:-------------:| -| Hacker Spawn Chance | - -| Hacker Cooldown| - -| Hacker Duration | Sets how long the "Hacker mode" remains active -| Hacker Only Sees Color Type | Sets if the Hacker sees the player colors on the admin table or only white/gray (for Lighter and darker colors) ------------------------ - - -## 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 possiblity to act like a Shifter.\ -The **special interactions** with the Shifter are noted in the chapters of the respective roles.\ -\ -**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 his target, depending on who 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 used them before) - -### Game Options -| Name | Description -|----------|:-------------:| -| Shifter Spawn Chance | - ------------------------ - -## Time Master -### **Team: Crewmates** -The Time Master has a time shield which he can activate. The time shield remains active for a configurable amount of time.\ -If a player trys to kill the Time Master while the time shield is active, the kill won't happen and the -time will rewind for a set amount of time.\ -The kill cooldown of the killer won't be reset, so the Time Master -has to make sure that the game won't result in the same situation.\ -The Time Master won't be affected by the rewind.\ -\ -**NOTE:** -- Only the movement is affected by the rewind -- A Vampire bite will trigger the rewind. If the Time Master misses shielding the bite, he can still shield the kill which happens a few seconds later. -- If the Time Master was bitten and has his shield active before when a meeting is called, he survives but the time won't be rewinded. -- If the Time Master has a Medic shield, he won't rewind. -- The shield itself ends immediately when triggered. So the timemaster can be attacked again as soon as the rewind ends. - -### Game Options -| Name | Description | -|----------|:-------------:| -| Time Master Spawn Chance | - | -| Time Master Cooldown | - | -| Rewind Duration | How much time to rewind | -| Time Master Shield Duration | ------------------------ - -## Swapper -### **Team: Crewmates** -During meetings the Swapper can exchange votes that two people get (i.e. all votes -that player A got will be given to player B and vice versa).\ -Because of the Swappers strength in meetings, they might not start emergency meetings -and can't fix lights and comms. - -### Game Options -| Name | Description -|----------|:-------------:| -| Swapper Spawn Chance | - -| Swapper can call emergency meeting | Option to disable the emergency button for the Swapper ------------------------ - -## Tracker -### **Team: Crewmates** -The Tracker can select one player in a game and tracks this player for the rest of the game. -An arrow points to the last tracked position of the player. -The arrow updates it's position every few seconds (configurable). - -### Game Options -| Name | Description -|----------|:-------------:| -| Tracker Spawn Chance | - -| Tracker Update Intervall | Sets how often the position is being updated ------------------------ - -## Snitch -### **Team: Crewmates** -When the Snitch finishes all the tasks, arrows will appear (only visible to the Snitch) that point to the Impostors. -When the Snitch has one task left (configurable) the Snitch will be revealed to the Impostors, also with an arrow. - -### Game Options -| Name | Description -|----------|:-------------:| -| Snitch Spawn Chance | - -| Task Count Where Impostors See Snitch | - ------------------------ - -## Jackal -### **Team: Jackal** -The Jackal is part of an extra team, that tries to eliminate all the other players.\ -The Jackal has no tasks and can kill Impostors, Crewmates and Neutrals.\ -The Jackal (if allowed by the options) can select another player to be his Sidekick. -Creating a Sidekick removes all tasks of the Sidekick and adds him to the team Jackal. The Sidekick loses his current role (except if he's a Lover, then he plays in two teams). -The "Create Sidekick Action" may only be used once per Jackal or once per game (depending on the options). -The Jackal can also promote Impostors to be his Sidekick, but depending on the options the Impostor will either really turn into the Sidekick and leave the team Impostors or he will just look like the Sidekick to the Jackal an remain as he was.\ -\ -The team Jackal enables multiple new outcomes of the game, listing some examples here: -- The Impostors could be eliminated and then the crew plays against the team Jackal. -- The Crew could be eliminated, then the Team Jackal fight against the Impostors (the Crew can still make a task win in this scenario) - -The priority of the win conditions is the following: -1. Crewmate Child lose by vote -2. Jester wins by vote -3. Arsonist win -4. Team Impostor wins by sabotage -5. Team Crew wins by tasks (also possible if the whole Crew is dead) -6. Lovers among the last three players win -7. Team Jackal wins by outnumbering (When the team Jackal contains an equal or greater amount of players than the Crew and there are 0 Impostors left and team Jackal contains no Lover) -8. Team Impostor wins by outnumbering (When the team Impostors contains an equal or greater amount of players than the Crew and there are 0 players of the team Jackal left and team Impostors contains no Lover) -9. Team Crew wins by outnumbering (When there is no player of the team Jackal and the team Impostrs left) - -**NOTE:** -- The Jackal (and his Sidekick) may be killed by a Sheriff. -- A Jackal cannot target the Child, while it's growing up. After that he can kill it or select it as its Sidekick -- The Crew can still win, even if all of their members are dead, if they finish their tasks fast enough (that's why converting the last Crewmate with tasks left into a Sidekick results in a task win for the crew) - -If both Impostors and Jackals are in the game the game continues even if all Crewmates are dead. Crewmates may still win in this case by completing their tasks. Jackal and Impostor have to kill each other. - - - -### Game Options -| Name | Description -|----------|:-------------:| -| Jackal Spawn Chance | - | -| Jackal/Sidekick Kill Cooldown | Kill cooldown | -| Jackal Create Sidekick Cooldown | Cooldown before a Sidekick can be created | -| Jackal can use vents | Yes/No | -| Jackal can create a Sidekick | Yes/No | -| Jackals promoted from Sidekick can create a Sidekick | Yes/No (to prevent the Jackal team from growing) | -| Jackals can make an Impostor to his Sidekick | Yes/No (to prevent a Jackal from turning an Impostor into a Sidekick, if he uses the ability on an Impostor he sees the Impostor as Sidekick, but the Impostor isn't converted to Sidekick. If this option is set to "No" Jackal and Sidekick can kill each other ) | ------------------------ - -## Sidekick -### **Team: Jackal** -Gets assigned to a player during the game by the "Create Sidekick Action" of the Jackal and joins the Jackal in his quest to eliminate all other players.\ -Upon the death of the Jackal (depending on the options), he might get promoted to Jackal himself and potentially even assign a Sidekick of his own. -\ -**NOTE:** -- A player that converts into a Sidekick loses his previous role and tasks (if he had one), except the Lover role. -- The Sidekick may be killed by a Sheriff. -- The Sidekick cannot target the Child, while it's growing up. - -### Game Options -| Name | Description -|----------|:-------------:| -| Jackal/Sidekick Kill Cooldown | Uses the same kill cooldown setting as the Jackal | -| Sidekick gets promoted to Jackal on Jackal death | Yes/No | -| Sidekick can kill | Yes/No | -| Sidekick can use vents | Yes/No | ------------------------ - -## Spy -### **Team: Crewmates** -The Spy is a Crewmate, which has no special abilities.\ -The Spy looks like an additional Impostor to the Impostors, they can't tell the difference.\ -There are two possibilities (depending on the set options): -- The Impostors can't kill the Spy (because otherwise their kill button would reveal, who the Spy is) -- The Impostors can kill the Spy but they can also kill their Impostor partner (if they mistake another Impostor for the Spy) -You can set whether the Sheriff can kill the Spy or not (in order to keep the lie alive). - -### Game Options -| Name | Description -|----------|:-------------:| -| Spy Spawn Chance | -| Spy Can Die To Sheriff | -| Impostors Can Kill Anyone If There Is A Spy | This allows the Impostors to kill both the Spy and their Impostor partners ------------------------ - -## Security Guard -### **Team: Crewmates** -The Security Guard is a Crewmate that has a certain amount of screws that he can use for either sealing vents or for placing new cameras.\ -Placing a new camera and sealing vents takes a configurable amount of screws. The total number of screws that a Security Guard has can also be configured.\ -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:** -- Tickster boxes can't be sealed -- The Security Guard can't place Cameras on MiraHQ -- The remaining number of screws can be seen above his special button -- On Skeld the four cameras will be replaced every 3 seconds (with the next four cameras). You can also navigate manually using the arrow keys. - - -### Game Options -| Name | Description -|----------|:-------------:| -| Security Guard Spawn Chance | -| Security Guard Cooldown | -| Security Guard Number Of Screws | The number of screws that a Security Guard can use in a game -| Number Of Screws Per Cam | The number of screws it takes to place a camera -| Number Of Screws Per Vent | The number of screws it takes to seal a vent ------------------------ - - -# Source code -It's bad I know, this is a side project and my second week of modding. So there are no best practices around here. -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. - -# Bugs, suggestions and requests -If you found any bugs, have an idea for a new role or any other request, join our [Discord server](https://discord.gg/77RkMJHWsM) +![eisbison banner](./Images/TOR_logo.png) +[![Discord](./Images/TOR_server.png)](https://discord.gg/77RkMJHWsM) +![eisbison infographic](./Images/TOR_roles.png) + + +# 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), new [Custom Hats](#custom-hats) and support for [10+ Player Lobbies](#Custom-Servers-and-10+-Players) to the game. +Even more roles are coming soon :) + +| Impostors | Crewmates | Neutral | +|----------|-------------|-----------------| +| [Bad Child](#child) | [Child](#child) | [Arsonist](#arsonist) | +| [Camouflager](#camouflager) | [Detective](#detective) | [Jester](#jester) | +| [Cleaner](#cleaner) | [Engineer](#engineer) | [Jackal](#jackal) | +| [Eraser](#eraser) | [Hacker](#hacker) | [Sidekick](#sidekick) | +| [ImpLover](#lovers) | [Lover](#lovers) | | +| [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | | +| [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | | +| [Janitor (Mafia)](#mafia) | [Medic](#medic) | | +| [Morphling](#morphling) | [Security Guard](#security-guard) | | +| [Trickster](#trickster) | [Seer](#seer) | | +| [Vampire](#vampire) | [Sheriff](#sheriff) | | +| [Warlock](#warlock) | [Shifter](#shifter) | | +| | [Snitch](#snitch) | | +| | [Spy](#spy) | | +| | [Swapper](#swapper) | | +| | [Time Master](#time-master) | | +| | [Tracker](#tracker) | | + +The [Role Assignment](#role-assignment) sections explains how the roles are being distributed among the players. + +# Releases +| Among Us - Version| Mod Version | Link | +|----------|-------------|-----------------| +| 2021.5.10s| v2.6.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.4/TheOtherRoles.zip) +| 2021.5.10s| v2.6.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.3/TheOtherRoles.zip) +| 2021.5.10s| v2.6.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.2/TheOtherRoles.zip) +| 2021.4.14s| v2.6.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.1/TheOtherRoles.zip) +| 2021.4.14s| v2.6.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.0/TheOtherRoles.zip) +| 2021.4.14s| v2.5.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.1/TheOtherRoles.zip) +| 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip) +| 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip) +| 2021.4.14s| v2.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.3.0/TheOtherRoles.zip) +| 2021.4.14s| v2.2.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.2/TheOtherRoles.zip) +| 2021.4.12s| v2.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.1/TheOtherRoles.zip) +| 2021.4.12s| v2.2.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.0/TheOtherRoles.zip) +| 2021.3.31.3s| v2.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.1.0/TheOtherRoles.zip) +| 2021.3.31.3s| v2.0.1 beta| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.0.1/TheOtherRoles.zip) +| 2021.3.31.3s | v2.0.0 beta | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.0.0/TheOtherRoles.zip) +| 2021.3.5s | v1.8.2 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.8.2/TheOtherRoles.zip) +| 2021.3.5s | v1.8.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.8.1/TheOtherRoles.zip) +| 2021.3.5s | v1.8 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.8/TheOtherRoles.zip) +| 2021.3.5s | v1.7 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.7/TheOtherRoles.zip) +| 2021.3.5s | v1.6 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.6/TheOtherRoles.zip) +| 2021.3.5s | v1.5 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.5/TheOtherRoles.zip) +| 2021.3.5s | v1.4 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip) +| 2021.3.5s | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip) +| 2020.12.19s | v1.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.1/TheOtherRoles.zip) +| 2020.12.19s | v1.0 | [Download](https://github.com/Eisbison/TheOtherRoles/files/6097191/TheOtherRoles.zip) + +# Changelog +
+ Click to show the Changelog + +**Version 2.6.4** +- **[Lovers](#lovers):** You can now select that Lovers may have a second role (could be a Crewmate, Neutral or Impostor role) +- **[Seer](#seer):** Fixed souls and flash sometimes not being visible (Thanks to [orangeNKeks](https://github.com/orangeNKeks)) +- New option: [Swapper](#swapper) can only swap others +- New option: Ghosts can see votes +- New option: [Jackal](#jackal) and [Sidekick](#sidekick) have Impostor vision +- New option: [Jester](#jester) can sabotage +- Changed FreePlay mode to not assign custom roles anymore +- Fixed a bug with directional hats not using their flip image after a while + +**Version 2.6.3** +- Changed the role limits options to allow for minimum and maximum bounds +- Changed the role assignment to be more random when assigning roles (previously assigned the neutral roles before assigning the crewmate roles) +- Added new `flip` option to [Custom Hats](#custom-hats) + +**Version 2.6.2** +- The Other Roles now supports the new Among Us version **2021.5.10s** +- Added a chat command to kick players as the host of a lobby (`/kick playerName`) + +**Version 2.6.1** +- Fixed a bug where the Sheriff was unable to kill the Arsonist +- Fixed a bug in the role assignment system +- Added the option to select the Dleks map +- Improved the overlay of the Arsonist + +**Version 2.6.0** +- **New Role:** [Arsonist](#arsonist) +- Added an Ingame Updater, to make it easier to update the Mod +- Added synchronization for Airship toilet doors. Doors now open/close for everyone +- Changed Shifter to also die when shifting a neutral role (Jester, Arsonist, Jackal, ...) +- Changed the option "Jester Can Die To Sheriff" to "Neutrals Can Die To Sheriff" +- Changed the role assignment system. You can now set how many neutral roles you want in your game +- Changed Hacker to see colors more clearly on Admin Table +- Changed version handshake to give more clear info +- Fixed a problem with the Hat Tab leaving too much space between categories +- Fixed an Among Us bug, which made the selected region always show "North America" +- Fixed an Among Us bug, which made the disconnect info be off-screen. (hopefully) + +**Version 2.5.1** +- **New Hats:** We added the support for custom hats and there are already a few hats inside the game. We can add new hats without updating the mod and we're awaiting your hat designs on our discord server. +- Changed Lovers to ignore Lover's Tasks for Taskwin, while an ImpLover is alive +- Fixed a bug where garlic was not visible in some places +- The Security Guard can't place cameras on MiraHQ anymore +- Fixed a bug on there Airship, where the view of the cameras that the Security Guard placed wasn't centered on the camera. + +**Version 2.5.0** +- **New Role:** [Security Guard](#security-guard) +- Fixed a bug where the game would stop after the first meeting +- Fixed a bug where killing with the hotkey Q ignored shields + +**Version 2.4.0** +- **New Role:** [Warlock](#warlock) +- Added an option that allows ghosts to see the roles and remaining tasks of other players +- Added options to configure Morph & Camo duration +- Added hotkeys to the custom buttons (**Q** for the buttons that are on the same place as the kill button, **F** for the buttons that are above the kill button) +- Fixed an oversight which made StreamerMode only work as host +- Fixed an oversight which required Jackals to finish Tasks, after Sidekick was promoted +- Fixed an oversight which made Sidekicks not promote, if the Jackal disconnected +- Fixed a bug where the Trickster box was invisible +- Fixed a bug where changes to the server ip and port would only be applied if the game was restarted +- Added a way to get the 2 Hidden [Colors](#colors) + +**Version 2.3.0** +- **New Role:** [Cleaner](#cleaner) +- Added 12 new [Colors](#colors) +- We added support for creating [Custom Hats](#custom-hats). New hats are coming with the next version, but you can already create and submit your own hats on [Discord](https://discord.gg/77RkMJHWsM). +- Added the option to hide the name of players with an unknown role +- Added Trickster Box vent animation. Thanks to [Drakoni](https://twitter.com/Drakoni13) +- You can now change the custom server ip/port right inside the game +- The Jackal, the Sidekick and the Jester now have fake tasks +- Added outlines, to show who you're targetting with your ability. Thanks to [Sihaack](https://github.com/sihaack) for part of the code. +- Added a streamer mode to Among Us, which hides lobby codes, the ip of your custom server and the port of your custom server. You can also modify the text that replaces the lobby code, check [Settings](#settings) for more details. +- Changed Meeting Hud Layout when playing with more than 10 players +- Fixed a bug where **ImpLovers** would hardly spawn +- Fixed a bug where players could get stuck on ladders/platforms when being rewinded +- Fixed a bug where players could only use quickchat +- Fixed a bug which prevented to play in Freeplay mode +- Fixed a bug which moved the Ping info off-screen + +**Version 2.2.2** +- Among Us version 2021.4.14s compatibility +- Improved the block votes on emergency meeting option + +**Version 2.2.1** +- Trickster: The vent button now has a custom texture. Fixed a bug where the Trickster could clip out of bounds when his box was close to a wall. +- Fixed a bug where the Bad Child's kill button went on cooldown when someone else performed a kill +- Fixed a few bugs with footprints, Seer souls and the Vampire delayed kill +- Fixed a bug where the Child was banned for hacking (because of its reduced kill cooldown) +- Improved the version handshake + +**Version 2.2.0** +- **Works with the latest Among Us version (2021.4.12s)** +- **Added support for 10+ player lobbies on custom servers:** Check the [Custom Servers and 10+ Players](#Custom-Servers-and-10+-Players) section. During meetings use the up/down keys, on vitals use the left/right keys. +- **Added a new Impostor role: The Trickster** check the [Trickster](#trickster) section for more infos +- You can now set how long the Time Master shield lasts +- The host now sees for how long the lobby will remain open +- We changed the look/layout of the settings +- Added a new option that deactivates skipping in meetings (if the player does not vote, he votes himself) +- You can now choose whether the Eraser is able to erase the Spy/Impostors or not +- Fixed a bug where a Lovers win wasn't displayed properly +- Fixed the Among Us bug where people were unable to move after meetings +- We added a version checking system: The host can only start the game if everyone in his lobby has the same version of the mod installed (he will see, who is using a wrong version). This prevents hacking in public lobbies and bugs because of version missmatches. +- Fixed a bug where the Child Impostor had the same cooldowns as normal Impostors +- Fixed a bug where the Vampire/Janitor/Mafioso would lose their kill button after being erased +- The Child is now able to use ladders and it can do all the tasks right away + +**Version 2.1.0** +- **New Role:** [Spy](#spy) +- **Eraser:** The Eraser can now also remove the role of other Impostors. This enables him to reveal the Spy, but might result in removing the special ability of his partner. +- **Camouflager:** The Child age/size will now also be hidden, to allow the Child Impostor to kill during camouflage + +**Hotfix 2.0.1** +- Fixed a bug where camouflaged players would get stuck on ladders/platforms on the airship +- Introduced a one second cooldown after the Morphling sampled another player +- The Child can now always reach all usables (ladders, tasks, ...) +- We removed a bug, where some footprints remained on the ground forever +- We removed a bug, where the Detective didn't see the right color type when reporting a player +- We changed the Jester win and Child lose conditions, they're not being affected by server delays anymore + +**Changes in 2.0.0** +- **New button art** created by **Bavari** +- **New mod updater/installer tool** created by [Narua](https://github.com/Narua2010) and [Jolle](https://github.com/joelweih). Check the [Installation](#installation) section for more details. +- **Custom options:** Introduced customizable presets. Starting with 2.0.0, settings can be copied and used with higher versions (2.0.0). +- **Time Master rework:** Check [Time Master](#time-master) for more information +- **Medic:** The Medic report changed, it only shows the time since death (see Detective) +- **Detective:** The Detective now sees the name/color type of the killer when he reports a dead body (ability moved from the Medic to the Detective) +- **Lighter:** We changed and tried to nerf the Lighter, see the [Lighter](#lighter) section for more details. +- **Seer:** As the role didn't work the way it was, we completely changed it. We're still working on the role, for now we're trying a few things. Check the [Seer](#seer) section to get more details about the new Seer. +- **Shifter:** We reworked the Shifter, he is now part of the crew. Check out the [Shifter](#shifter) sections for more details. +- **Hacker:** The Hacker is basically the old Spy. We added the option to only show the color type instead of the color on the admin table. +- **Camouflager:** Now also overrides the information of other roles, check the [Camouflager](#camouflager) section for more details. +- **Morphling:** Now also overrides the information of other roles, check the [Morphling](#morphling) section for more details +- **Child:** The Child can now be a Crewmate Child or an Impostor Child, check the [Child](#child) section for more details +- **Eraser:** The Eraser, a new Impostor role, is now part of the mod. Check the [Eraser](#eraser) section for more details +- **New options:** + - You can now set the maximum number of meetings in a game: Every player still only has one meeting. The Mayor can always use his meeting (even if the maximum number of meetings was reached). Impostor/Jackal meetings also count. + +**Hotfix 1.8.2** +- Add map and Impostor count to lobby settings. +- Fixed bugs where changing players to be the Sidekick didn't reset all the effects of their previous role. + +**Hotfix 1.8.1** Resolves bugs that occured when the Jackal recruited a Medic, Swapper and Tracker\ +\ +**Changes in v1.8:** +- **New Roles:** Added the Jackal and Sidekick roles +- Vampire: Medic report shows the right info now. A bitten Swapper is not able to swap if he dies at the beginning of a meeting. One can now set the cooldown and whether a normal kill is possible when a target is next to a garlic or not. +- Lover: New option that sets how often an ImpLover appears. If a Lover is exiled, his partner doesn't spawn a dead body anymore. +- Cooldowns now stop cooling down, if a player sits inside a vent. +- Fixed a bug that prevented the game from continuing after a meeting (for an edge case with Lovers) +- If two players try to kill each other at the same time both should die (e.g. Sheriff vs Impostor) +- We added a description for your current role right above the task list +- Added a description for the [Role Assignment System](#role-assignment) + +\ +**Changes in v1.7:** +- **New Roles:** The Vampire, the Tracker and the Snitch are now in the game +- The role assignment system has been changed +- Impostors now see a blue outline around all vents of the map, if the Engineer sits inside one of them + +\ +**Changes in v1.6:** +- This update is a small hotfix, fixing the bug where some people were unable to join lobbies. +- The Child can't be voted out anymore before it turns 18, hence games can't end anymore because the Child died. +- Footprints are no longer visible to the Detective, if players are inside vents. + +\ +**Changes in v1.5:** +- Time Master - Buff: He is not affected by his rewind anymore, which gives him more utility. Players will now be rewinded out of vents. +- Child - Nerf: The Child now grows up (see [Child](#child)) and becomes a normal Crewmate at some point. A growing Child is not killable anymore. Some tasks are still not doable for the small Child, we are working on that. But eventually when growing up it can do all the tasks as it's size increases. +- Seer - Nerf: Added an option that sets how often the Seer mistakes the player for another. +- Hacker - Nerf: The Hacker now only sees the additional information when he activates his "Hacker mode". That should stops the Hacker from camping the admin table/vitals. +- Other: Camouflager/Morphling cooldowns were fixed. Custom regions code was removed to enable 3rd party tools. Some minor bugfixes. + +**Changes in v1.4:** +- Fixing a Camoflager/Morphling animation bug +- Fixing a bug where the Swapper could swap votes even if they are dead +- The custom cooldown buttons now render the cooldown progress (the grey overlay) in the right way (v1.3 introduced the bug) +- Players in vents are not targetable anymore by the role actions, the button does not activate (e.g. Seer revealing, Morphling sample). Exception: Impostor killing an Engineer in a vent + +**Changes in v1.3:** +- Adds support for the Among Us version **2021.3.5s** +- Fixes a bug where a an edge case caused all players to start the game with the camouflaged look +- There might be a few bugs, since I focused on getting the update out fast. A new version resolving the bugs will be published tomorrow. + +**Changes in v1.1:** +- Morphling: The color of pet now also morphs. The skin animation now starts at the right point. +- The game over screen now shows if the Jester/Child/Lovers won. +- A bug was removed where the Jester won together with the Crewmates. +- A bug was removed where the game of the Lovers crashed if they were the last players killed by the host of the lobby. +
+ +# Installation +## Windows Auto Installation/Updating Using The AmongUsModUpdater +This tool was made by [Narua](https://github.com/Narua2010) and [Jolle](https://github.com/joelweih). It automatically installs/updates the TheOtherRoles mod to the newest version, runs the game and +syncs your game options automatically. + +1. Simply get the newest [release](https://github.com/Narua2010/AmongUsModUpdater/releases/latest) of the [AmongUsModUpdater](https://github.com/Narua2010/AmongUsModUpdater) +2. Unzip the folder and run the **setup.exe** + +If you have problems with the AmongUsModUpdater, make sure to [contact us](https://discord.gg/csa3pHYdPU). + +## Windows Manual Installation +1. Download and unzip the newest [release](https://github.com/Eisbison/TheOtherRoles/releases/latest) +2. Find the folder of your game, for Steam players you can right click in Steam, on the game, a menu will appear proposing you to go to the folders. +3. Make a copy of your game, it's not obligatory but advised, put it where you want. +4. Drag or extract the files from the zip into your game, at the `.exe` level. +5. Run the game (the first launch might take a while) + +Not working? You might want to install the dependency [vc_redist](https://aka.ms/vs/16/release/vc_redist.x86.exe) + +![Install](https://i.imgur.com/pvBAyZN.png) + +**Linux Manual** +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://bepinex.github.io/bepinex_docs/master/articles/advanced/steam_interop.html#protonwine) +4. Launch the game via Steam + +# Custom Servers and 10+ Players +We always recommend you to play on custom servers rather than on the official ones. If you want to play with more than 10 players in one lobby, you're required to use a custom server. Credits for the original implementation that allowed 10+ player lobbies go to the creaters of the [CrowdedMod](https://github.com/CrowdedMods/CrowdedMod). + +**Setup the Clients:** +1. Open the region menu where you can choose between the different regions +2. Select the region *Custom* +3. Enter the ip/domain and the port of your custom server in the corresponding text fields + +**Setup the Server:** +1. Get the [Impostor](https://github.com/Impostor/Impostor) release for the Among Us version **2021.3.31 - 2021.4.2** +2. Follow the steps (using the server release you just downloaded) on the official [Impostor-Documentation](https://github.com/Impostor/Impostor/wiki/Running-the-server) +3. Make sure to set the following values to false in the `config.json` file: +``` + ... + "AntiCheat": { + "Enabled": false, + "BanIpFromGame": false + } +``` +4. Make sure to forward the right ports on the hosting machine +5. Run the server and setup the client + +**Setting up Server as Docker Container:** \ +If you want to run the server as a docker container you'll need to use the image +``` +aeonlucid/impostor:nightly +``` +(Currently only the "nightly" tag is starting a server supporting 2021.3.31 or later) + +In addition to running it, the environment variables to disable the AntiCheat feature need to be set. +``` +IMPOSTOR_AntiCheat__Enabled=false +IMPOSTOR_AntiCheat__BanIpFromGame=false +``` + +Example to docker run command: +``` +docker run -p 22023:22023/udp --env IMPOSTOR_AntiCheat__Enabled=false --env IMPOSTOR_AntiCheat__BanIpFromGame=false aeonlucid/impostor:nightly +``` + +Or use to run it in the background +``` +docker run -d -p 22023:22023/udp --env IMPOSTOR_AntiCheat__Enabled=false --env IMPOSTOR_AntiCheat__BanIpFromGame=false aeonlucid/impostor:nightly +``` + + +# 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\ +[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**\ + +# 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 Impostors:** The number of Impostor count be set inside a lobby +- **Map:** The map can be changed inside a lobby +- **Maximum Number Of Meetings:** You can set the maximum number of meetings that can be called in total (every player still has personal maximum of buttons, but if the maximum number of meetings is reached you can't use your meetings even if you have some left, Impostor and Jackal meetings also count) +- **Allow Skips On Emergency Meetings:** If set to false, there will not be a skip button in emergency meetings. If a player does not vote, he'll vote himself. +- **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 alse see the name of the Spy and everyone can still see the age of the child. +- **Ghosts Can See Roles** +- **Ghosts Can See Votes** +- **Ghosts Can See The Number Ofe Remaining Tasks** +- **Dleks:** You are now able to select the Dleks map. + +# Custom Hats +## Create and submit new hat designs +We're awaiting your creative hat designs and we'll integrate all the good ones in our mod. +Here are a few instructions, on how to create a custom hat: + +- **Creation:** A hat consists of up to three textures. The aspect ratio of the textures has to be `4:5`, we recommend `300px:375px`: + - `Main texture (required)`: + - This is the main texture of your hat. It will usually be rendered in front of the player, if you set the `behind` parameter it will be rendered behind the player. + - The name of the texture needs to follow the pattern *hatname.png*, but you can also set some additional parameters in the file name by adding `_parametername` to the file name (before the *.png*). + - Parameter `bounce`: This parameter determines whether the hat will bounce while you're walking or not. + - Parameter `adaptive`: If this parameter is set, the Among Us coloring shader will be applied (the shader that replaces some colors with the colors that your character is wearing in the game). The color red (#ff0000) will be replaced with the primary color of your player and the color blue (#0000ff) with the secondary color. Also other colors will be affected and changed, you can have a look at the texture of the [Crewmate Hat](https://static.wikia.nocookie.net/among-us-wiki/images/e/e0/Crewmate_hat.png) to see how this feature should be used. + - Parameter `behind`: If this parameter is set, the main texture will be rendered behind the player. + - `Flipped texture (optional)`: + - This texture will be rendered instead of the Main texture, when facing the left. + - The name of the texture needs to follow the pattern `hatname_flip.png`. + - `Back texture (optional)`: + - This texture will be rendered behind the player. + - The name of the texture needs to follow the pattern `hatname_back.png`. + - `Flipped Back texture (optional)`: + - This texture will be rendered instead of the Back texture, when facing the left. + - The name of the texture needs to follow the pattern `hatname_back_flip.png`. + - `Climb texture (optional)`: + - This texture will be rendered in front of the player, when he's climbing. + - The name of the texture needs to follow the pattern `hatname_climb.png`. +- **Testing:** You can test your hat design by putting all the files in the `\TheOtherHats\Test` subfolder of your mod folder. Then whenever you start a freeplay game, you and all the dummies will be wearing the new hat. You don't need to restart Among Us if you change the hat files, just exit and reenter the freeplay mode. + +- **Submission:** If you got a hat design, you can submit it on our [Discord server](https://discord.gg/77RkMJHWsM). We'll look at all the hats and add all the good ones to the game. + +# Colors +![TOR Colors](./Images/TOR_colors.png) + +# Roles + +## Role Assignment +We are still improving the role assignment system. It's not that intuitive right now, but it's more flexible than the older one +if you're using it right. + +First you need to choose how many special roles of each kind (Impostor/Neutral/Crewmate) you want in the game. +The count you set will only be reached, if there are enough Crewmates/Impostors in the game and if enough roles are set to be in the game (i.e. they are set to > 0%). The roles are then being distributed as follows: +- First all roles that are set to 100% are being assigned to arbitrary players +- After that each role that has 10%-90% selected adds 1-9 tickets to a ticket pool (there exists a ticket pool for Crewmates, Neutrals and Impostors). Then the roles will be selected randomly from the pools as long it's possible (until the selected number is reached, until there are no more Crewmates/Impostors or until there are no more tickets). If a role is selected from the pool, obviously all the tickets of that role are being removed. +- The Mafia, Lovers and Child are being selected independently (without using the ticket system) according to the spawn chance you selected. After that the Crewmate, Neutral and Impostor roles are selected and assigned in a random order. + +**Example:**\ +Settings: 2 special Crewmate roles, Snitch: 100%, Hacker: 10%, Tracker: 30%\ +Result: Snitch is assigned, then one role out of the pool [Hacker, Tracker, Tracker, Tracker] is being selected\ +Note: Changing the settings to Hacker: 20%, Tracker: 60% would statistically result in the same outcome . + + +## Mafia +### **Team: Impostors** +The Mafia are a group of three Impostors.\ +The Godfather works like a normal Impostor.\ +The Mafioso is an Impostor who cannot kill nor sabotage until the Godfather is dead.\ +The Janitor is an Impostor who cannot kill nor sabotage, but they can hide dead bodies instead.\ +\ +**NOTE:** +- There have to be 3 Impostors activated for the mafia to spawn. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Mafia Spawn Chance | - +| Janitor Cooldown | - +----------------------- + +## Morphling +### **Team: Impostors** +The Morphling is an Impostor which can additionally scan the appearance of a player. After an arbitrary time they can take on that appearance for 10s. +\ +**NOTE:** +- They shrink to the size of the Child when they copies its look. +- The Hacker sees the new color on the admin table. +- The color of the footprints changes accordingly (also the ones that were already on the ground). +- The other Impostor still sees that they are an Impostor (the name remains red). +- The shield indicator changes accordingly (the Morphling gains or loses the shield indicator) +- Tracker and Snitch arrows keep working. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Morphling Spawn Chance | - +| Morphling Cooldown | - +| Morph Duration | Time the Morphling stays morphed +----------------------- + +## Camouflager +### **Team: Impostors** +The Camouflager is an Impostor which can additionally activate a camouflage mode. +The camouflage mode lasts for 10s and while it is active, all player names/pets/hats +are hidden and all players have the same color.\ +\ +**NOTE:** +- The Child will look like all the other players +- The color of the footprints turns gray (also the ones that were already on the ground). +- The Hacker sees gray icons on the admin table +- The shield is not visible anymore +- Tracker and Snitch arrows keep working + +### Game Options +| Name | Description | +|----------|:-------------:| +| Camouflager Spawn Chance | - +| Camouflager Cooldown | - +| Camo Duration | Time players stay camouflaged +----------------------- + +## Vampire +### **Team: Impostors** +The Vampire is an Impostor, that can bite other player. Bitten players die after a configurable amount of time.\ +If the Vampire spawn chance is greater 0 (even if there is no Vampire in the game), all players can place one garlic.\ +If a victim is near a garlic, the "Bite Button" turns into the default "Kill Button" and the Vampire can only perform a normal kill.\ +\ +**NOTE:** +- If a bitten player is still alive when a meeting is being called, he dies at the start of the meeting. +- The cooldown is the same as the default kill cooldown (+ the kill delay if the Vampire bites the target). +- If there is a Vampire in the game, there can't be a Warlock + +### Game Options +| Name | Description | +|----------|:-------------:| +| Vampire Spawn Chance | - +| Vampire Kill Delay | - +| Vampire Cooldown | Sets the kill/bite cooldown +| Vampire Can Kill Near Garlics | The Vampire can never bite when his victim is near a garlic. If this option is set to true, he can still perform a normal kill there. +----------------------- + +## Eraser +### **Team: Impostors** +The Eraser is an Impostor that can erase the role of every player.\ +The targeted players will lose their role after the meeting right before a player is exiled.\ +After every erase, the cooldown increases by 10 seconds.\ +The erase will be performed, even if the Eraser or his target die before the next meeting.\ +By default the Eraser can erase everyone but the Spy and other Impostors. Depending on the options +he 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 his target, depending on who the Eraser erased) +- Erasing a Lover automatically erases the other Lover as well (if the second Lover is an ImpLover, he 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 erasing is being triggered before the eject 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. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Eraser Spawn Chance | - +| Eraser Cooldown | The Erasers cooldown will increase by 10 seconds after every erase. +| Eraser Can Erase Anyone | If set to false, he can't erase the Spy and other Impostors +----------------------- + +## Trickster +### **Team: Impostors** +The Trickster is an Impostor that can place 3 jack-in-the-boxes that are invisible at first to other players. \ +If the Trickster has placed all of his boxes they will be converted into a vent network usable only by the Trickster himself, but the boxes are revealed to the others. \ +If the boxes are converted to a vent network, the Trickster gains a new ability "Lights out" to limit the visibility of Non-Impostors, that cannot be fixed by other players. Lights are automatically restored after a while. \ + +\ +**NOTE:** +- Impostors will get a text indicator at the bottom of the screen to notify them if the lights are out due to the Trickster ability, as there is no sabotage arrows or task to sabotage text to otherwise notify them about it. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Trickster Spawn Chance | - +| Trickster Box Cooldown | Cooldown for placing jack-in-the-boxes +| Trickster Lights Out Cooldown | Cooldown for his "lights out" ability +| Trickster Lights Out Duration | Duration after which the light is automatically restored +----------------------- + +## Cleaner +### **Team: Impostors** +The Cleaner is an Impostor who has the ability to clean up dead bodies. \ + +\ +**NOTE:** +- The Kill and Clean cooldown are shared, preventing him from immediately cleaning his own kills. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Cleaner Spawn Chance | - +| Cleaner Cooldown | Cooldown for cleaning dead bodies +----------------------- + + +## Warlock +### **Team: Impostors** +The Warlock is an Impostor, that can curse another player (the cursed player doesn't get notified).\ +If the cursed person stands next to another player, the Warlock is able to kill that player (no matter how far away he is).\ +Performing a kill with the help of a cursed player, will lift the curse and it will result in the Warlock being unable to move for a configurable amount of time. +The Warlock can still perform normal kills, but the two buttons share the same cooldown. + +\ +**NOTE:** +- The Warlock can always kill his Impostor mates (and even himselfs) using the "cursed kill" +- If there is a Warlock in the game, there can't be a Vampire +- Performing a normal kill, doesn't lift the curse + +### Game Options +| Name | Description | +|----------|:-------------:| +| Warlock Spawn Chance | - +| Warlock Cooldown | Cooldown for using the Curse and curse Kill +| Warlock Root Time | Time the Warlock is rooted in place after killing using the curse +----------------------- + +## Lovers +### **Team: Lovers (and secondary team)** +There are always two Lovers which are linked together.\ +Their primary goal is it to stay alive together until the end of the game.\ +If one Lover dies (and the option is activated), the other Lover suicides.\ +You can select if Lovers are able to have a second role (could be a Neutral, Crewmate or Impostor Role)\ +You can specify the chance of one Lover being an Impostor.\ +The Lovers never know the role of their partner, they only see who their partner is.\ +The Lovers win, if they are both alive when the game ends. They can also win with their original team (e.g. a dead Impostor Lover can win with the Impostors, an Arsonist Lover can still achieve an Arsonist win)\ +If one of the Lovers is a killer (i.e. Jackal/Sidekick/Impostor), they can achieve a "Lovers solo win" where only the Lovers win.\ +If there is no killer among the Lovers (e.g. an Arsonist Lover + Crewmate Lover) and they are both alive when the game ends, they win together with the Crewmates.\ +If there's an team Impostor/Jackal Lover in the game, the tasks of a Crewmate Lover won't be counted (for a task win) as long as he's alive. If the Lover dies, his tasks will also be counted.\ +\ +**NOTE:** +- In a 2 Cremates vs 2 Impostors (or 2 members of team Jackal) and the Lovers are not in the same team, the game is not automatically over since the Lovers can still achieve a solo win. E.g. if there are the following roles Impostor + ImpLover + Lover + Crewmate left, the game will not end and the next kill will decide if the Impostors or Lovers win. +- The Lovers can change if the Shifter takes the role of a Lovers + +### Game Options +| Name | Description | +|----------|:-------------:| +| Lovers Spawn Chance | - +| Chance That One Lover Is Impostor | - +| Both Lovers Die | Whether the second Lover suicides, if the first one dies +| Lovers Can Have Another Role | If set to true, the Lovers can have a second role +----------------------- + + +## Sheriff +### **Team: Crewmates** +The Sheriff has the ability to kill Impostors. +If they try to kill a Crewmate, they die instead. + +**NOTE:** +- If the Sheriff shoots the person the Medic shielded, the Sheriff and the shielded person **both remain unharmed**. +- If the Sheriff shoots a Child Impostor, the Sheriff dies if the Child is still growing up. If it's 18, the Child Impostor dies. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Sheriff Spawn Chance | - +| Sheriff Cooldown | - +| Neutrals Can Die To Sheriff | - +----------------------- + +## Jester +### **Team: Neutral** +The Jester does not have any tasks. They win the game as a solo, if they get voted out during a meeting. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Jester Spawn Chance | - +| Jester can call emergency meeting | Option to disable the emergency button for the Jester +| Jester can sabotage | Option to allow the Jester to sabotage +----------------------- + +## Arsonist +### **Team: Neutral** +The Arsonist does not have any tasks, he has to win the game as a solo.\ +The Arsonist can douse other players by pressing the douse button and remaining next to the player for a few seconds.\ +If the player that the Arsonist douses walks out of range, the cooldown will reset to 0.\ +After dousing everyone alive the Arsonist can ignite all the players which results in an Arsonist win. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Arsonist Spawn Chance | - +| Arsonist Countdown | - +| Arsonist Douse Duration | The time it takes to douse a player +----------------------- + +## Seer +### **Team: Crewmates** +The Seer has two abilities (one can activate one of them or both in the options). +The Seer sees the souls of players that died a round earlier, the souls slowy fade away. +The Seer gets a blue flash on his screen, if a player dies somewhere on the map. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Seer Spawn Chance | - +| Seer Mode | Options: Show death flash and souls, show death flash, show souls +| Seer Limit Soul Duration | Toggle if souls should turn invisible after a while +| Seer Soul Duration | Sets how long it will take the souls to turn invisible after a meeting +----------------------- + +## Engineer +### **Team: Crewmates** +The Engineer (if alive) can fix one sabotage per game from anywhere on the map.\ +The Engineer can use vents. If the Engineer is inside a vent, the Impostors will see a blue outline +around all vents on the map (in order to warn them). +Because of the vents the Engineer might not be able to start some tasks using the "Use" button, +you can double click on the tasks instead. +\ +**NOTE:** +- The kill button of Impostors activates if they stand next to a vent where the Engineer is. They can also kill them there. No other action (e.g. Morphling sample, Shifter shift, ...) can affect players inside vents. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Engineer Spawn Chance | - +----------------------- + +## Detective +### **Team: Crewmates** +The Detective can see footprints that other players leave behind. +The Detective's other feature shows when they report a corpse: they recieve clues about the killer's identity. The type of information they get is based on the time it took them to find the corpse. +\ +**NOTE:** +- When people change their colors (because of a morph or camouflage), all the footprints also change their colors (also the ones that were already on the ground). If the effects are over, all footprints switch back to the original color. +- The Detective does not see footprints of players that sit in vents +- More information about the [colors](#colors) + +### Game Options +| Name | Description | +|----------|:-------------:| +| Detective Spawn Chance | - +| Anonymous Footprints | If set to true, all footprints will have the same color. Otherwise they will have the color of the respective player. +| Footprint Intervall | The intervall between two footprints +| Footprint Duration | Sets how long the footprints remain visible. +| Time Where Detective Reports Will Have Name | The amount of time that the Detective will have to report the body since death to get the killer's name. | +| Time Where Detective Reports Will Have Color Type| The amount of time that the Detective will have to report the body since death to get the killer's color type. | +----------------------- + +## Lighter +### **Team: Crewmates** +The Lighter can turn on his Lighter every now and then, which increases his vision by a costumizable amount. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Lighter Spawn Chance | - +| Lighter Mode Vision On Lights On | The vision the Lighter has when the lights are on and the Lighter mode is on +| Lighter Mode Vision On Lights Off | The vision the Lighter has when the lights are down and the Lighter mode is on +| Lighter Cooldown | - +| Lighter Duration | - +----------------------- + +## Child +### **Team: Crewmates or Impostors** +The Child can be a Crewmate (67% chance) or an Impostor (33% chance).\ +The Child's character is smaller and hence visible to everyone in the game.\ +The Child cannot be killed until it turns 18 years old, however it can be voted out.\ +**Impostor Child:** + - While growing up the kill cooldown is doubled. When it's fully grown up its kill cooldown is 2/3 of the default one. + - If it gets thrown out of the ship, everything is fine. + +**Crewmate Child:** + - The Crewmate Child aims to play out the strength its invincibility in the early game. + - If it gets thrown out of the ship before it turns 18, everyone loses. So think twice before you vote out a Child. + +**NOTE:** +- Impostors can't kill the Child (the button does not work) until it turns 18 +- The Sheriff can kill the Impostor Child, but only if it's fully grown up + +### Game Options +| Name | Description | +|----------|:-------------:| +| Child Spawn Chance | - +| Child | Child Growing Up Duration +----------------------- + +## Medic +### **Team: Crewmates** +The Medic can shield (highlighted by an outline around the player) one player per game, which makes the player unkillable.\ +The shielded player can still be voted out and might also be an Impostor.\ +If set in the options, the shielded player will get a red flash on their screen if someone (Impostor, Sheriff, ...) tried to murder them. +If the Medic dies, the shield disappears with them.\ +The Sheriff will not die if they try to kill a shielded Crewmate and won't perform a kill if they try to kill a shielded Impostor.\ +The Medic's other feature shows when they report a corpse: they will see how long ago the player died +\ +**NOTE:** +- If the shielded player is a Lover and the other Lover dies, they nevertheless kill themselves. +- If the Shifter has a shield or their target has a Shield, the shielded player switches. + + +### Game Options +| Name | Description | Options | +|----------|:-------------:|:-------------:| +| Medic Spawn Chance | - | - +| Show Shielded Player | Sets who sees if a player has a shield | "Everyone", "Shielded + Medic", "Medic" +| Shielded Player Sees Murder Attempt| Whether a shielded player sees if someone tries to kill him | True/false | +----------------------- + +## Mayor +### **Team: Crewmates** +The Mayor leades the Crewmates by having a vote that counts twice.\ +The Mayor can always use his meeting, even if the maximum number of meetings was reached. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Mayor Spawn Chance | - +----------------------- + +## Hacker +### **Team: Crewmates** +If the Hacker activates the "Hacker mode", the Hacker gets more information than others from the admin table and vitals for a set duration.\ +Otherwise he sees the same information as everyone else. +**Admin table:** The Hacker can see the colors (or color types) of the players on the table.\ +**Vitals**: The Hacker can see how long dead players have been dead for.\ +\ +**NOTE:** +- If the Morphling morphes or the Camouflager camouflages, the colors on the admin table change accordingly +- More information about the [colors](#colors) + +### Game Options +| Name | Description | +|----------|:-------------:| +| Hacker Spawn Chance | - +| Hacker Cooldown| - +| Hacker Duration | Sets how long the "Hacker mode" remains active +| Hacker Only Sees Color Type | Sets if the Hacker sees the player colors on the admin table or only white/gray (for Lighter and darker colors) +----------------------- + + +## 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 possiblity to act like a Shifter.\ +The **special interactions** with the Shifter are noted in the chapters of the respective roles.\ +\ +**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 his target, depending on who 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 used them before) + +### Game Options +| Name | Description +|----------|:-------------:| +| Shifter Spawn Chance | - +----------------------- + +## Time Master +### **Team: Crewmates** +The Time Master has a time shield which he can activate. The time shield remains active for a configurable amount of time.\ +If a player trys to kill the Time Master while the time shield is active, the kill won't happen and the +time will rewind for a set amount of time.\ +The kill cooldown of the killer won't be reset, so the Time Master +has to make sure that the game won't result in the same situation.\ +The Time Master won't be affected by the rewind.\ +\ +**NOTE:** +- Only the movement is affected by the rewind +- A Vampire bite will trigger the rewind. If the Time Master misses shielding the bite, he can still shield the kill which happens a few seconds later. +- If the Time Master was bitten and has his shield active before when a meeting is called, he survives but the time won't be rewinded. +- If the Time Master has a Medic shield, he won't rewind. +- The shield itself ends immediately when triggered. So the timemaster can be attacked again as soon as the rewind ends. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Time Master Spawn Chance | - | +| Time Master Cooldown | - | +| Rewind Duration | How much time to rewind | +| Time Master Shield Duration | +----------------------- + +## Swapper +### **Team: Crewmates** +During meetings the Swapper can exchange votes that two people get (i.e. all votes +that player A got will be given to player B and vice versa).\ +Because of the Swappers strength in meetings, they might not start emergency meetings +and can't fix lights and comms. + +### Game Options +| Name | Description +|----------|:-------------:| +| Swapper Spawn Chance | - +| Swapper can call emergency meeting | Option to disable the emergency button for the Swapper +| Swapper can only swap others | Sets whether the Swapper can swap himself or not +----------------------- + +## Tracker +### **Team: Crewmates** +The Tracker can select one player in a game and tracks this player for the rest of the game. +An arrow points to the last tracked position of the player. +The arrow updates it's position every few seconds (configurable). + +### Game Options +| Name | Description +|----------|:-------------:| +| Tracker Spawn Chance | - +| Tracker Update Intervall | Sets how often the position is being updated +----------------------- + +## Snitch +### **Team: Crewmates** +When the Snitch finishes all the tasks, arrows will appear (only visible to the Snitch) that point to the Impostors. +When the Snitch has one task left (configurable) the Snitch will be revealed to the Impostors, also with an arrow. + +### Game Options +| Name | Description +|----------|:-------------:| +| Snitch Spawn Chance | - +| Task Count Where Impostors See Snitch | - +----------------------- + +## Jackal +### **Team: Jackal** +The Jackal is part of an extra team, that tries to eliminate all the other players.\ +The Jackal has no tasks and can kill Impostors, Crewmates and Neutrals.\ +The Jackal (if allowed by the options) can select another player to be his Sidekick. +Creating a Sidekick removes all tasks of the Sidekick and adds him to the team Jackal. The Sidekick loses his current role (except if he's a Lover, then he plays in two teams). +The "Create Sidekick Action" may only be used once per Jackal or once per game (depending on the options). +The Jackal can also promote Impostors to be his Sidekick, but depending on the options the Impostor will either really turn into the Sidekick and leave the team Impostors or he will just look like the Sidekick to the Jackal an remain as he was.\ +\ +The team Jackal enables multiple new outcomes of the game, listing some examples here: +- The Impostors could be eliminated and then the crew plays against the team Jackal. +- The Crew could be eliminated, then the Team Jackal fight against the Impostors (the Crew can still make a task win in this scenario) + +The priority of the win conditions is the following: +1. Crewmate Child lose by vote +2. Jester wins by vote +3. Arsonist win +4. Team Impostor wins by sabotage +5. Team Crew wins by tasks (also possible if the whole Crew is dead) +6. Lovers among the last three players win +7. Team Jackal wins by outnumbering (When the team Jackal contains an equal or greater amount of players than the Crew and there are 0 Impostors left and team Jackal contains no Lover) +8. Team Impostor wins by outnumbering (When the team Impostors contains an equal or greater amount of players than the Crew and there are 0 players of the team Jackal left and team Impostors contains no Lover) +9. Team Crew wins by outnumbering (When there is no player of the team Jackal and the team Impostrs left) + +**NOTE:** +- The Jackal (and his Sidekick) may be killed by a Sheriff. +- A Jackal cannot target the Child, while it's growing up. After that he can kill it or select it as its Sidekick +- The Crew can still win, even if all of their members are dead, if they finish their tasks fast enough (that's why converting the last Crewmate with tasks left into a Sidekick results in a task win for the crew) + +If both Impostors and Jackals are in the game the game continues even if all Crewmates are dead. Crewmates may still win in this case by completing their tasks. Jackal and Impostor have to kill each other. + + + +### Game Options +| Name | Description +|----------|:-------------:| +| Jackal Spawn Chance | - | +| Jackal/Sidekick Kill Cooldown | Kill cooldown | +| Jackal Create Sidekick Cooldown | Cooldown before a Sidekick can be created | +| Jackal can use vents | Yes/No | +| Jackal can create a Sidekick | Yes/No | +| Jackals promoted from Sidekick can create a Sidekick | Yes/No (to prevent the Jackal team from growing) | +| Jackals can make an Impostor to his Sidekick | Yes/No (to prevent a Jackal from turning an Impostor into a Sidekick, if he uses the ability on an Impostor he sees the Impostor as Sidekick, but the Impostor isn't converted to Sidekick. If this option is set to "No" Jackal and Sidekick can kill each other ) | +| Jackal and Sidekick have Impostor vision | - | +----------------------- + +## Sidekick +### **Team: Jackal** +Gets assigned to a player during the game by the "Create Sidekick Action" of the Jackal and joins the Jackal in his quest to eliminate all other players.\ +Upon the death of the Jackal (depending on the options), he might get promoted to Jackal himself and potentially even assign a Sidekick of his own. +\ +**NOTE:** +- A player that converts into a Sidekick loses his previous role and tasks (if he had one), except the Lover role. +- The Sidekick may be killed by a Sheriff. +- The Sidekick cannot target the Child, while it's growing up. + +### Game Options +| Name | Description +|----------|:-------------:| +| Jackal/Sidekick Kill Cooldown | Uses the same kill cooldown setting as the Jackal | +| Sidekick gets promoted to Jackal on Jackal death | Yes/No | +| Sidekick can kill | Yes/No | +| Sidekick can use vents | Yes/No | +----------------------- + +## Spy +### **Team: Crewmates** +The Spy is a Crewmate, which has no special abilities.\ +The Spy looks like an additional Impostor to the Impostors, they can't tell the difference.\ +There are two possibilities (depending on the set options): +- The Impostors can't kill the Spy (because otherwise their kill button would reveal, who the Spy is) +- The Impostors can kill the Spy but they can also kill their Impostor partner (if they mistake another Impostor for the Spy) +You can set whether the Sheriff can kill the Spy or not (in order to keep the lie alive). + +### Game Options +| Name | Description +|----------|:-------------:| +| Spy Spawn Chance | +| Spy Can Die To Sheriff | +| Impostors Can Kill Anyone If There Is A Spy | This allows the Impostors to kill both the Spy and their Impostor partners +----------------------- + +## Security Guard +### **Team: Crewmates** +The Security Guard is a Crewmate that has a certain amount of screws that he can use for either sealing vents or for placing new cameras.\ +Placing a new camera and sealing vents takes a configurable amount of screws. The total number of screws that a Security Guard has can also be configured.\ +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:** +- Tickster boxes can't be sealed +- The Security Guard can't place Cameras on MiraHQ +- The remaining number of screws can be seen above his special button +- On Skeld the four cameras will be replaced every 3 seconds (with the next four cameras). You can also navigate manually using the arrow keys. + + +### Game Options +| Name | Description +|----------|:-------------:| +| Security Guard Spawn Chance | +| Security Guard Cooldown | +| Security Guard Number Of Screws | The number of screws that a Security Guard can use in a game +| Number Of Screws Per Cam | The number of screws it takes to place a camera +| Number Of Screws Per Vent | The number of screws it takes to seal a vent +----------------------- + + +# Source code +It's bad I know, this is a side project and my second week of modding. So there are no best practices around here. +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. + +# Bugs, suggestions and requests +If you found any bugs, have an idea for a new role or any other request, join our [Discord server](https://discord.gg/77RkMJHWsM) diff --git a/Source Code/ClientOptionsPatch.cs b/Source Code/ClientOptionsPatch.cs index fff8aa8..681e7e8 100644 --- a/Source Code/ClientOptionsPatch.cs +++ b/Source Code/ClientOptionsPatch.cs @@ -14,6 +14,10 @@ namespace TheOtherRoles { private static ToggleButtonBehaviour streamerModeButton; private static ToggleButtonBehaviour ghostsSeeTasksButton; private static ToggleButtonBehaviour ghostsSeeRolesButton; + private static ToggleButtonBehaviour ghostsSeeVotesButton; + + public static float xOffset = 1.75f; + public static float yOffset = -0.5f; private static void updateToggle(ToggleButtonBehaviour button, string text, bool on) { if (button == null || button.gameObject == null) return; @@ -41,11 +45,12 @@ namespace TheOtherRoles { public static void Postfix(OptionsMenuBehaviour __instance) { if (__instance.CensorChatButton != null) { if (origin == null) origin = __instance.CensorChatButton.transform.localPosition + Vector3.up * 0.25f; - __instance.CensorChatButton.transform.localPosition = origin.Value + Vector3.left * 1.3f; + __instance.CensorChatButton.transform.localPosition = origin.Value + Vector3.left * xOffset; + __instance.CensorChatButton.transform.localScale = Vector3.one * 2f / 3f; } if ((streamerModeButton == null || streamerModeButton.gameObject == null)) { - streamerModeButton = createCustomToggle("Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value, Vector3.right * 1.3f, (UnityEngine.Events.UnityAction)streamerModeToggle, __instance); + streamerModeButton = createCustomToggle("Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value, Vector3.zero, (UnityEngine.Events.UnityAction)streamerModeToggle, __instance); void streamerModeToggle() { TheOtherRolesPlugin.StreamerMode.Value = !TheOtherRolesPlugin.StreamerMode.Value; @@ -54,7 +59,7 @@ namespace TheOtherRoles { } if ((ghostsSeeTasksButton == null || ghostsSeeTasksButton.gameObject == null)) { - ghostsSeeTasksButton = createCustomToggle("Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value, new Vector2(-1.3f, -0.5f), (UnityEngine.Events.UnityAction)ghostsSeeTaskToggle, __instance); + ghostsSeeTasksButton = createCustomToggle("Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value, Vector3.right * xOffset, (UnityEngine.Events.UnityAction)ghostsSeeTaskToggle, __instance); void ghostsSeeTaskToggle() { TheOtherRolesPlugin.GhostsSeeTasks.Value = !TheOtherRolesPlugin.GhostsSeeTasks.Value; @@ -64,7 +69,7 @@ namespace TheOtherRoles { } if ((ghostsSeeRolesButton == null || ghostsSeeRolesButton.gameObject == null)) { - ghostsSeeRolesButton = createCustomToggle("Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value, new Vector2(1.3f, -0.5f), (UnityEngine.Events.UnityAction)ghostsSeeRolesToggle, __instance); + ghostsSeeRolesButton = createCustomToggle("Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value, new Vector2(-xOffset, yOffset), (UnityEngine.Events.UnityAction)ghostsSeeRolesToggle, __instance); void ghostsSeeRolesToggle() { TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value; @@ -72,6 +77,16 @@ namespace TheOtherRoles { updateToggle(ghostsSeeRolesButton, "Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value); } } + + if ((ghostsSeeVotesButton == null || ghostsSeeVotesButton.gameObject == null)) { + ghostsSeeVotesButton = createCustomToggle("Ghosts See votes: ", TheOtherRolesPlugin.GhostsSeeVotes.Value, new Vector2(0, yOffset), (UnityEngine.Events.UnityAction)ghostsSeeVotesToggle, __instance); + + void ghostsSeeVotesToggle() { + TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value; + MapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value; + updateToggle(ghostsSeeVotesButton, "Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeVotes.Value); + } + } } } diff --git a/Source Code/CustomHats.cs b/Source Code/CustomHats.cs index 8345329..32563e8 100644 --- a/Source Code/CustomHats.cs +++ b/Source Code/CustomHats.cs @@ -121,7 +121,14 @@ namespace TheOtherRoles { private static Sprite CreateHatSprite(string path, bool fromDisk = false) { Texture2D texture = fromDisk ? Helpers.loadTextureFromDisk(path) : Helpers.loadTextureFromResources(path); - return texture == null ? null : Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.8f), texture.width * 0.75f); + if (texture == null) + return null; + Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.8f), texture.width * 0.75f); + if (sprite == null) + return null; + texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; + sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset; + return sprite; } private static HatBehaviour CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) { diff --git a/Source Code/CustomOptions.cs b/Source Code/CustomOptions.cs index 1d26fd8..5845606 100644 --- a/Source Code/CustomOptions.cs +++ b/Source Code/CustomOptions.cs @@ -48,9 +48,11 @@ namespace TheOtherRoles { public static CustomOption loversSpawnRate; public static CustomOption loversImpLoverRate; public static CustomOption loversBothDie; + public static CustomOption loversCanHaveAnotherRole; public static CustomOption jesterSpawnRate; public static CustomOption jesterCanCallEmergency; + public static CustomOption jesterCanSabotage; public static CustomOption arsonistSpawnRate; public static CustomOption arsonistCooldown; @@ -66,6 +68,7 @@ namespace TheOtherRoles { public static CustomOption sidekickCanUseVents; public static CustomOption jackalPromotedFromSidekickCanCreateSidekick; public static CustomOption jackalCanCreateSidekickFromImpostor; + public static CustomOption jackalAndSidekickHaveImpostorVision; public static CustomOption shifterSpawnRate; @@ -101,6 +104,7 @@ namespace TheOtherRoles { public static CustomOption swapperSpawnRate; public static CustomOption swapperCanCallEmergency; + public static CustomOption swapperCanOnlySwapOthers; public static CustomOption seerSpawnRate; public static CustomOption seerMode; @@ -207,12 +211,14 @@ namespace TheOtherRoles { loversSpawnRate = CustomOption.Create(50, cs(Lovers.color, "Lovers"), rates, null, true); loversImpLoverRate = CustomOption.Create(51, "Chance That One Lover Is Impostor", rates, loversSpawnRate); loversBothDie = CustomOption.Create(52, "Both Lovers Die", true, loversSpawnRate); + loversCanHaveAnotherRole = CustomOption.Create(53, "Lovers Can Have Another Role", true, loversSpawnRate); jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true); jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate); + jesterCanSabotage = CustomOption.Create(62, "Jester can sabotage", true, jesterSpawnRate); arsonistSpawnRate = CustomOption.Create(290, cs(Arsonist.color, "Arsonist"), rates, null, true); - arsonistCooldown = CustomOption.Create(291, "Arsonist Cooldown", 30f, 5f, 60f, 2.5f, arsonistSpawnRate); + arsonistCooldown = CustomOption.Create(291, "Arsonist Cooldown", 12.5f, 2.5f, 60f, 2.5f, arsonistSpawnRate); arsonistDuration = CustomOption.Create(292, "Arsonist Douse Duration", 3f, 1f, 10f, 1f, arsonistSpawnRate); jackalSpawnRate = CustomOption.Create(220, cs(Jackal.color, "Jackal"), rates, null, true); @@ -225,6 +231,7 @@ namespace TheOtherRoles { sidekickCanUseVents = CustomOption.Create(227, "Sidekick Can Use Vents", true, jackalSpawnRate); jackalPromotedFromSidekickCanCreateSidekick = CustomOption.Create(228, "Jackals Promoted From Sidekick Can Create A Sidekick", true, jackalSpawnRate); jackalCanCreateSidekickFromImpostor = CustomOption.Create(229, "Jackals Can Make An Impostor To His Sidekick", true, jackalSpawnRate); + jackalAndSidekickHaveImpostorVision = CustomOption.Create(430, "Jackal And Sidekick Have Impostor Vision", false, jackalSpawnRate); shifterSpawnRate = CustomOption.Create(70, cs(Shifter.color, "Shifter"), rates, null, true); @@ -261,6 +268,7 @@ namespace TheOtherRoles { swapperSpawnRate = CustomOption.Create(150, cs(Swapper.color, "Swapper"), rates, null, true); swapperCanCallEmergency = CustomOption.Create(151, "Swapper can call emergency meeting", false, swapperSpawnRate); + swapperCanOnlySwapOthers = CustomOption.Create(152, "Swapper can only swap others", false, swapperSpawnRate); seerSpawnRate = CustomOption.Create(160, cs(Seer.color, "Seer"), rates, null, true); seerMode = CustomOption.Create(161, "Seer Mode", new string[]{ "Show Death Flash + Souls", "Show Death Flash", "Show Souls"}, seerSpawnRate); diff --git a/Source Code/EndGamePatch.cs b/Source Code/EndGamePatch.cs index d3aaf23..e1e7c26 100644 --- a/Source Code/EndGamePatch.cs +++ b/Source Code/EndGamePatch.cs @@ -33,13 +33,10 @@ namespace TheOtherRoles { static class AdditionalTempData { // Should be implemented using a proper GameOverReason in the future public static WinCondition winCondition = WinCondition.Default; - public static bool localIsLover = false; public static void clear() { winCondition = WinCondition.Default; - localIsLover = false; - } } @@ -71,7 +68,7 @@ namespace TheOtherRoles { bool jesterWin = Jester.jester != null && gameOverReason == (GameOverReason)CustomGameOverReason.JesterWin; bool arsonistWin = Arsonist.arsonist != null && gameOverReason == (GameOverReason)CustomGameOverReason.ArsonistWin; bool childLose = Child.child != null && gameOverReason == (GameOverReason)CustomGameOverReason.ChildLose; - bool loversWin = Lovers.existingAndAlive() && (gameOverReason == (GameOverReason)CustomGameOverReason.LoversWin || (TempData.DidHumansWin(gameOverReason) && Lovers.existingAndCrewLovers())); // 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 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)); // Child lose @@ -101,9 +98,8 @@ namespace TheOtherRoles { // Lovers win conditions else if (loversWin) { - AdditionalTempData.localIsLover = (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2); // Double win for lovers, crewmates also win - if (Lovers.existingAndCrewLovers()) { + if (!Lovers.existingWithKiller()) { AdditionalTempData.winCondition = WinCondition.LoversTeamWin; TempData.winners = new Il2CppSystem.Collections.Generic.List(); foreach (PlayerControl p in PlayerControl.AllPlayerControls) { @@ -167,9 +163,6 @@ namespace TheOtherRoles { textRenderer.color = Arsonist.color; } else if (AdditionalTempData.winCondition == WinCondition.LoversTeamWin) { - if (AdditionalTempData.localIsLover) { - __instance.WinText.text = "Double Victory"; - } textRenderer.text = "Lovers And Crewmates Win"; textRenderer.color = Lovers.color; __instance.BackgroundBar.material.SetColor("_Color", Lovers.color); diff --git a/Source Code/GameStartManagerPatch.cs b/Source Code/GameStartManagerPatch.cs index ebe2017..45430d6 100644 --- a/Source Code/GameStartManagerPatch.cs +++ b/Source Code/GameStartManagerPatch.cs @@ -97,7 +97,7 @@ namespace TheOtherRoles { if (diff > 0) { message += $"{client.Character.Data.PlayerName} has an older version of The Other Roles (v{playerVersions[client.Id].ToString()})\n"; blockStart = true; - } else if (diff > 0) { + } else if (diff < 0) { message += $"{client.Character.Data.PlayerName} has a newer version of The Other Roles (v{playerVersions[client.Id].ToString()}) \n"; blockStart = true; } diff --git a/Source Code/IntroPatch.cs b/Source Code/IntroPatch.cs index 50a2c1b..182fd64 100644 --- a/Source Code/IntroPatch.cs +++ b/Source Code/IntroPatch.cs @@ -36,16 +36,14 @@ namespace TheOtherRoles } } - [HarmonyPatch(typeof(IntroCutscene._CoBegin_d__11), nameof(IntroCutscene._CoBegin_d__11.MoveNext))] - class IntroPatch - { - static void Prefix(IntroCutscene._CoBegin_d__11 __instance) - { + [HarmonyPatch] + class IntroPatch { + public static void setupIntroTeam(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { // Intro solo teams if (PlayerControl.LocalPlayer == Jester.jester || PlayerControl.LocalPlayer == Jackal.jackal || PlayerControl.LocalPlayer == Arsonist.arsonist) { var soloTeam = new Il2CppSystem.Collections.Generic.List(); soloTeam.Add(PlayerControl.LocalPlayer); - __instance.yourTeam = soloTeam; + yourTeam = soloTeam; } // Add the Spy to the Impostor team (for the Impostors) @@ -56,33 +54,49 @@ namespace TheOtherRoles if (p == Spy.spy || p.Data.IsImpostor) fakeImpostorTeam.Add(p); } - __instance.yourTeam = fakeImpostorTeam; + yourTeam = fakeImpostorTeam; } } - // Intro display role - static void Postfix(IntroCutscene._CoBegin_d__11 __instance) - { + public static void setupIntroRole(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { List infos = RoleInfo.getRoleInfoForPlayer(PlayerControl.LocalPlayer); - if (infos.Count == 0) return; - RoleInfo roleInfo = infos[0]; + RoleInfo roleInfo = infos.Where(info => info.roleId != RoleId.Lover).FirstOrDefault(); - if (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2) - { + if (roleInfo != null) { + __instance.Title.text = roleInfo.name; + __instance.Title.color = roleInfo.color; + __instance.ImpostorText.gameObject.SetActive(true); + __instance.ImpostorText.text = roleInfo.introDescription; + __instance.BackgroundBar.material.color = roleInfo.color; + } + + if (infos.Any(info => info.roleId == RoleId.Lover)) { + var loversText = UnityEngine.Object.Instantiate(__instance.ImpostorText, __instance.ImpostorText.transform.parent); + loversText.transform.localPosition += Vector3.down * 3f; PlayerControl otherLover = PlayerControl.LocalPlayer == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1; - __instance.__4__this.Title.text = PlayerControl.LocalPlayer.Data.IsImpostor ? "ImpLover" : "Lover"; - __instance.__4__this.Title.color = PlayerControl.LocalPlayer.Data.IsImpostor ? Color.white : Lovers.color; - __instance.__4__this.ImpostorText.text = "You are in Love with " + (otherLover?.Data?.PlayerName ?? "") + ""; - __instance.__4__this.ImpostorText.gameObject.SetActive(true); - __instance.__4__this.BackgroundBar.material.color = Lovers.color; + loversText.text = Helpers.cs(Lovers.color, $"❤ You are in lover with {otherLover?.Data?.PlayerName ?? ""} ❤"); + } + } + + [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))] + class BeginCrewmatePatch { + public static void Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { + setupIntroTeam(__instance, ref yourTeam); } - else if (roleInfo.name == "Crewmate" || roleInfo.name == "Impostor") {} - else { - __instance.__4__this.Title.text = roleInfo.name; - __instance.__4__this.Title.color = roleInfo.color; - __instance.__4__this.ImpostorText.gameObject.SetActive(true); - __instance.__4__this.ImpostorText.text = roleInfo.introDescription; - __instance.__4__this.BackgroundBar.material.color = roleInfo.color; + + public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { + setupIntroRole(__instance, ref yourTeam); + } + } + + [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginImpostor))] + class BeginImpostorPatch { + public static void Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { + setupIntroTeam(__instance, ref yourTeam); + } + + public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List yourTeam) { + setupIntroRole(__instance, ref yourTeam); } } } diff --git a/Source Code/Main.cs b/Source Code/Main.cs index 0fe7b20..c8522ad 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -19,7 +19,7 @@ namespace TheOtherRoles public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string VersionString = "2.6.3"; + public const string VersionString = "2.6.4"; public static System.Version Version = System.Version.Parse(VersionString); public Harmony Harmony { get; } = new Harmony(Id); @@ -31,7 +31,7 @@ namespace TheOtherRoles public static ConfigEntry StreamerMode { get; set; } public static ConfigEntry GhostsSeeTasks { get; set; } public static ConfigEntry GhostsSeeRoles { get; set; } - public static ConfigEntry HostSeesVotesLog { get; set; } + public static ConfigEntry GhostsSeeVotes{ get; set; } public static ConfigEntry StreamerModeReplacementText { get; set; } public static ConfigEntry StreamerModeReplacementColor { get; set; } public static ConfigEntry Ip { get; set; } @@ -55,7 +55,7 @@ namespace TheOtherRoles StreamerMode = Config.Bind("Custom", "Enable Streamer Mode", false); GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true); GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true); - HostSeesVotesLog = Config.Bind("Custom", "Host Sees Votes Log", false); + GhostsSeeVotes = Config.Bind("Custom", "Ghosts See Votes", true); StreamerModeReplacementText = Config.Bind("Custom", "Streamer Mode Replacement Text", "\n\nThe Other Roles"); StreamerModeReplacementColor = Config.Bind("Custom", "Streamer Mode Replacement Text Hex Color", "#87AAF5FF"); diff --git a/Source Code/MapOptions.cs b/Source Code/MapOptions.cs index 7a20b66..2deb0c5 100644 --- a/Source Code/MapOptions.cs +++ b/Source Code/MapOptions.cs @@ -13,6 +13,7 @@ namespace TheOtherRoles{ public static bool hidePlayerNames = false; public static bool ghostsSeeRoles = true; public static bool ghostsSeeTasks = true; + public static bool ghostsSeeVotes = true; // Updating values public static int meetingsCount = 0; @@ -30,6 +31,7 @@ namespace TheOtherRoles{ hidePlayerNames = CustomOptionHolder.hidePlayerNames.getBool(); ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value; ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value; + ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value; } } } \ No newline at end of file diff --git a/Source Code/MeetingPatch.cs b/Source Code/MeetingPatch.cs index 2e5d815..4e5389d 100644 --- a/Source Code/MeetingPatch.cs +++ b/Source Code/MeetingPatch.cs @@ -170,14 +170,11 @@ namespace TheOtherRoles if (votedFor == (int)playerVoteArea.TargetPlayerId) { SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); - if (PlayerControl.GameOptions.AnonymousVotes) - { - PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer); - } - else - { + if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes)) PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer); - } + else + PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer); + spriteRenderer.transform.SetParent(playerVoteArea.transform); spriteRenderer.transform.localPosition = __instance.CounterOrigin + new Vector3(votesXOffset + __instance.CounterOffsets.x * (float)(num2), 0f, 0f); spriteRenderer.transform.localScale = Vector3.zero; @@ -188,14 +185,10 @@ namespace TheOtherRoles else if (i == 0 && votedFor == -1) { SpriteRenderer spriteRenderer2 = UnityEngine.Object.Instantiate(__instance.PlayerVotePrefab); - if (PlayerControl.GameOptions.AnonymousVotes) - { - PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer2); - } - else - { + if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes)) PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer2); - } + else + PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer2); spriteRenderer2.transform.SetParent(__instance.SkippedVoting.transform); spriteRenderer2.transform.localPosition = __instance.CounterOrigin + new Vector3(votesXOffset + __instance.CounterOffsets.x * (float)(num), 0f, 0f); spriteRenderer2.transform.localScale = Vector3.zero; @@ -233,9 +226,7 @@ namespace TheOtherRoles } - static void onClick(int i, MeetingHud __instance) - { - if (Swapper.swapper == null || PlayerControl.LocalPlayer != Swapper.swapper || Swapper.swapper.Data.IsDead) return; + static void onClick(int i, MeetingHud __instance) { if (__instance.state == MeetingHud.VoteStates.Results) return; if (__instance.playerStates[i].isDead) return; @@ -310,9 +301,10 @@ namespace TheOtherRoles selections = new bool[__instance.playerStates.Length]; renderers = new SpriteRenderer[__instance.playerStates.Length]; - for (int i = 0; i < __instance.playerStates.Length; i++) - { + for (int i = 0; i < __instance.playerStates.Length; i++) { PlayerVoteArea playerVoteArea = __instance.playerStates[i]; + if (playerVoteArea.isDead || (playerVoteArea.TargetPlayerId == Swapper.swapper.PlayerId && Swapper.canOnlySwapOthers)) continue; + GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject; GameObject checkbox = UnityEngine.Object.Instantiate(template); checkbox.transform.SetParent(playerVoteArea.transform); @@ -373,26 +365,6 @@ namespace TheOtherRoles __instance.SkipVoteButton.gameObject.SetActive(false); } } - - [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CastVote))] - class MeetingHudCastVotePatch { - static void Postfix([HarmonyArgument(0)]byte srcPlayerId, [HarmonyArgument(1)]sbyte suspectPlayerId) { - var source = Helpers.playerById(srcPlayerId); - if (source != null && source.Data != null && AmongUsClient.Instance.AmHost && TheOtherRolesPlugin.HostSeesVotesLog.Value) { - string target = null; - if (suspectPlayerId == -2) target = "didn't vote"; - else if (suspectPlayerId == -1) target = "skipped"; - else if (suspectPlayerId >= 0) { - System.Console.WriteLine(suspectPlayerId); - System.Console.WriteLine((byte)suspectPlayerId); - var targetPlayer = Helpers.playerById((byte)suspectPlayerId); - if (targetPlayer != null && targetPlayer.Data != null) target = $"voted {targetPlayer.Data.PlayerName}"; - } - - if (target != null) System.Console.WriteLine($"{source.Data.PlayerName} {target}"); - } - } - } } [HarmonyPatch(typeof(ExileController), "Begin")] @@ -411,10 +383,10 @@ namespace TheOtherRoles if (Eraser.eraser != null && AmongUsClient.Instance.AmHost && Eraser.futureErased != null) { // We need to send the RPC from the host here, to make sure that the order of shifting and erasing is correct (for that reason the futureShifted and futureErased are being synced) foreach (PlayerControl target in Eraser.futureErased) { if (target != null) { - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ErasePlayerRole, Hazel.SendOption.Reliable, -1); + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ErasePlayerRoles, Hazel.SendOption.Reliable, -1); writer.Write(target.PlayerId); AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.erasePlayerRole(target.PlayerId); + RPCProcedure.erasePlayerRoles(target.PlayerId); } } } @@ -506,95 +478,18 @@ namespace TheOtherRoles } [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), new Type[] { typeof(StringNames), typeof(Il2CppReferenceArray) })] - class MeetingExiledTextPatch - { - static void Postfix(ref string __result, [HarmonyArgument(0)]StringNames id, [HarmonyArgument(1)]Il2CppReferenceArray parts) - { - if (ExileController.Instance != null && ExileController.Instance.exiled != null) - { - // Exile role text for roles that are being assigned to crewmates - if (id == StringNames.ExileTextPN || id == StringNames.ExileTextSN) - { - if( Jester.jester != null && ExileController.Instance.exiled.Object.PlayerId == Jester.jester.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Jester."; - else if(Mayor.mayor != null && ExileController.Instance.exiled.Object.PlayerId == Mayor.mayor.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Mayor."; - else if(Engineer.engineer != null && ExileController.Instance.exiled.Object.PlayerId == Engineer.engineer.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Engineer."; - else if(Sheriff.sheriff != null && ExileController.Instance.exiled.Object.PlayerId == Sheriff.sheriff.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Sheriff."; - else if(Lighter.lighter != null && ExileController.Instance.exiled.Object.PlayerId == Lighter.lighter.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Lighter."; - else if(Detective.detective != null && ExileController.Instance.exiled.Object.PlayerId == Detective.detective.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Detective."; - else if(TimeMaster.timeMaster != null && ExileController.Instance.exiled.Object.PlayerId == TimeMaster.timeMaster.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Time Master."; - else if(Medic.medic != null && ExileController.Instance.exiled.Object.PlayerId == Medic.medic.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Medic."; - else if(Shifter.shifter != null && ExileController.Instance.exiled.Object.PlayerId == Shifter.shifter.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Shifter."; - else if(Swapper.swapper != null && ExileController.Instance.exiled.Object.PlayerId == Swapper.swapper.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Swapper."; - else if(Lovers.lover1 != null && ExileController.Instance.exiled.Object.PlayerId == Lovers.lover1.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Lover."; - else if(Lovers.lover2 != null && ExileController.Instance.exiled.Object.PlayerId == Lovers.lover2.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Lover."; - else if(Seer.seer != null && ExileController.Instance.exiled.Object.PlayerId == Seer.seer.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Seer."; - else if(Hacker.hacker != null && ExileController.Instance.exiled.Object.PlayerId == Hacker.hacker.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Hacker."; - else if(Child.child != null && ExileController.Instance.exiled.Object.PlayerId == Child.child.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Child."; - else if(Tracker.tracker != null && ExileController.Instance.exiled.Object.PlayerId == Tracker.tracker.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Tracker."; - else if(Snitch.snitch != null && ExileController.Instance.exiled.Object.PlayerId == Snitch.snitch.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Snitch."; - else if(Jackal.jackal != null && ExileController.Instance.exiled.Object.PlayerId == Jackal.jackal.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Jackal."; - else if(Sidekick.sidekick != null && ExileController.Instance.exiled.Object.PlayerId == Sidekick.sidekick.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Sidekick."; - else if(Spy.spy != null && ExileController.Instance.exiled.Object.PlayerId == Spy.spy.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Spy."; - else if(SecurityGuard.securityGuard != null && ExileController.Instance.exiled.Object.PlayerId == SecurityGuard.securityGuard.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Security Guard."; - else if(Arsonist.arsonist != null && ExileController.Instance.exiled.Object.PlayerId == Arsonist.arsonist.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Arsonist."; - else - __result = ExileController.Instance.exiled.PlayerName + " was not The Impostor."; + class ExileControllerMessagePatch { + static void Postfix(ref string __result, [HarmonyArgument(0)]StringNames id, [HarmonyArgument(1)]Il2CppReferenceArray parts) { + if (ExileController.Instance != null && ExileController.Instance.exiled != null) { + PlayerControl player = Helpers.playerById(ExileController.Instance.exiled.Object.PlayerId); + if (player == null) return; + // Exile role text + if (id == StringNames.ExileTextPN || id == StringNames.ExileTextSN || id == StringNames.ExileTextPP || id == StringNames.ExileTextSP) { + __result = player.Data.PlayerName + " was The " + String.Join(" ", RoleInfo.getRoleInfoForPlayer(player).Select(x => x.name).ToArray()); } - // Exile role text for roles that are being assigned to impostors - if (id == StringNames.ExileTextPP || id == StringNames.ExileTextSP) { - if(Godfather.godfather != null && ExileController.Instance.exiled.Object.PlayerId == Godfather.godfather.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Godfather."; - else if(Mafioso.mafioso != null && ExileController.Instance.exiled.Object.PlayerId == Mafioso.mafioso.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Mafioso."; - else if(Janitor.janitor != null && ExileController.Instance.exiled.Object.PlayerId == Janitor.janitor.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Janitor."; - else if(Morphling.morphling != null && ExileController.Instance.exiled.Object.PlayerId == Morphling.morphling.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Morphling."; - else if(Camouflager.camouflager != null && ExileController.Instance.exiled.Object.PlayerId == Camouflager.camouflager.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Camouflager."; - else if(Lovers.lover1 != null && ExileController.Instance.exiled.Object.PlayerId == Lovers.lover1.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The ImpLover."; - else if(Lovers.lover2 != null && ExileController.Instance.exiled.Object.PlayerId == Lovers.lover2.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The ImpLover."; - else if(Vampire.vampire != null && ExileController.Instance.exiled.Object.PlayerId == Vampire.vampire.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Vampire."; - else if (Eraser.eraser != null && ExileController.Instance.exiled.Object.PlayerId == Eraser.eraser.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Eraser."; - else if (Trickster.trickster != null && ExileController.Instance.exiled.Object.PlayerId == Trickster.trickster.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Trickster."; - else if (Cleaner.cleaner != null && ExileController.Instance.exiled.Object.PlayerId == Cleaner.cleaner.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Cleaner."; - else if (Warlock.warlock != null && ExileController.Instance.exiled.Object.PlayerId == Warlock.warlock.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The Warlock."; - } - // Hide number of remaining impostors on Jester win - if (id == StringNames.ImpostorsRemainP || id == StringNames.ImpostorsRemainS) - { - if (Jester.jester != null && ExileController.Instance.exiled.Object.PlayerId == Jester.jester.PlayerId) - __result = ""; + if (id == StringNames.ImpostorsRemainP || id == StringNames.ImpostorsRemainS) { + if (Jester.jester != null && player.PlayerId == Jester.jester.PlayerId) __result = ""; } } } diff --git a/Source Code/ModUpdater.cs b/Source Code/ModUpdater.cs index bbb54e6..5ddda3a 100644 --- a/Source Code/ModUpdater.cs +++ b/Source Code/ModUpdater.cs @@ -49,14 +49,12 @@ namespace TheOtherRoles { TwitchManager man = DestroyableSingleton.Instance; ModUpdater.InfoPopup = UnityEngine.Object.Instantiate(man.TwitchPopup); + ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f; + ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false; void onClick() { ModUpdater.ExecuteUpdate(); button.SetActive(false); - ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f; - ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false; - ModUpdater.InfoPopup.Show("Updating The Other Roles\nPlease wait..."); - __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { ModUpdater.setPopupText("Updating The Other Roles\nPlease wait..."); }))); } } } @@ -64,7 +62,7 @@ namespace TheOtherRoles { public class ModUpdater { public static bool running = false; public static bool hasUpdate = false; - public static string updateurl = null; + public static string updateURI = null; private static Task updateTask = null; public static GenericPopup InfoPopup; @@ -76,13 +74,18 @@ namespace TheOtherRoles { } public static void ExecuteUpdate() { + string info = "Updating The Other Roles\nPlease wait..."; + ModUpdater.InfoPopup.Show(info); // Show originally if (updateTask == null) { - if (updateurl != null) { + if (updateURI != null) { updateTask = downloadUpdate(); } else { - showPopup("This update has to be done manually"); + info = "Unable to auto-update\nPlease update manually"; } + } else { + info = "Update might already\nbe in progress"; } + ModUpdater.InfoPopup.StartCoroutine(Effects.Lerp(0.01f, new System.Action((p) => { ModUpdater.setPopupText(info); }))); } public static void clearOldVersions() { @@ -127,7 +130,7 @@ namespace TheOtherRoles { if (browser_download_url != null && current["content_type"] != null) { if (current["content_type"].ToString().Equals("application/x-msdownload") && browser_download_url.EndsWith(".dll")) { - updateurl = browser_download_url; + updateURI = browser_download_url; return true; } } @@ -144,7 +147,7 @@ namespace TheOtherRoles { try { HttpClient http = new HttpClient(); http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); - var response = await http.GetAsync(new System.Uri(updateurl), HttpCompletionOption.ResponseContentRead); + var response = await http.GetAsync(new System.Uri(updateURI), HttpCompletionOption.ResponseContentRead); if (response.StatusCode != HttpStatusCode.OK || response.Content == null) { System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); return false; diff --git a/Source Code/PlayerControlPatch.cs b/Source Code/PlayerControlPatch.cs index 4aad0dd..b1e2e24 100644 --- a/Source Code/PlayerControlPatch.cs +++ b/Source Code/PlayerControlPatch.cs @@ -362,7 +362,7 @@ namespace TheOtherRoles { } var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data); - string roleNames = String.Join(", ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray()); + string roleNames = String.Join(" ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray()); string taskInfo = tasksTotal > 0 ? $"({tasksCompleted}/{tasksTotal})" : ""; string playerInfoText = ""; @@ -604,11 +604,8 @@ namespace TheOtherRoles { // Lover suicide trigger on murder if ((Lovers.lover1 != null && target == Lovers.lover1) || (Lovers.lover2 != null && target == Lovers.lover2)) { PlayerControl otherLover = target == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1; - if (PlayerControl.LocalPlayer == target && otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) { // Only the dead lover sends the rpc - MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LoverSuicide, Hazel.SendOption.Reliable, -1); - writer.Write(otherLover.PlayerId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.loverSuicide(otherLover.PlayerId); + if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) { + otherLover.MurderPlayer(otherLover); } } diff --git a/Source Code/RPC.cs b/Source Code/RPC.cs index f78b562..ae643f2 100644 --- a/Source Code/RPC.cs +++ b/Source Code/RPC.cs @@ -25,8 +25,7 @@ namespace TheOtherRoles Medic, Shifter, Swapper, - Lover1, - Lover2, + Lover, Seer, Morphling, Camouflager, @@ -43,7 +42,9 @@ namespace TheOtherRoles Cleaner, Warlock, SecurityGuard, - Arsonist + Arsonist, + Crewmate, + Impostor } enum CustomRPC @@ -74,7 +75,6 @@ namespace TheOtherRoles MorphlingMorph, CamouflagerCamouflage, TrackerUsedTracker, - LoverSuicide, VampireSetBitten, VampireTryKill, PlaceGarlic, @@ -82,7 +82,7 @@ namespace TheOtherRoles SidekickKill, JackalCreatesSidekick, SidekickPromotes, - ErasePlayerRole, + ErasePlayerRoles, SetFutureErased, SetFutureShifted, PlaceJackInTheBox, @@ -123,7 +123,7 @@ namespace TheOtherRoles } } - public static void setRole(byte roleId, byte playerId) { + public static void setRole(byte roleId, byte playerId, byte flag) { foreach (PlayerControl player in PlayerControl.AllPlayerControls) if (player.PlayerId == playerId) { switch((RoleId)roleId) { @@ -166,11 +166,9 @@ namespace TheOtherRoles case RoleId.Swapper: Swapper.swapper = player; break; - case RoleId.Lover1: - Lovers.lover1 = player; - break; - case RoleId.Lover2: - Lovers.lover2 = player; + case RoleId.Lover: + if (flag == 0) Lovers.lover1 = player; + else Lovers.lover2 = player; break; case RoleId.Seer: Seer.seer = player; @@ -362,47 +360,48 @@ namespace TheOtherRoles 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; + // Shift role - if (Jester.jester != null && Jester.jester == player) { + if (Jester.jester != null && Jester.jester == player) Jester.jester = oldShifter; - } else if (Mayor.mayor != null && Mayor.mayor == player) { + if (Mayor.mayor != null && Mayor.mayor == player) Mayor.mayor = oldShifter; - } else if (Engineer.engineer != null && Engineer.engineer == player) { + if (Engineer.engineer != null && Engineer.engineer == player) Engineer.engineer = oldShifter; - } else if (Sheriff.sheriff != null && Sheriff.sheriff == player) { + if (Sheriff.sheriff != null && Sheriff.sheriff == player) Sheriff.sheriff = oldShifter; - } else if (Lighter.lighter != null && Lighter.lighter == player) { + if (Lighter.lighter != null && Lighter.lighter == player) Lighter.lighter = oldShifter; - } else if (Detective.detective != null && Detective.detective == player) { + if (Detective.detective != null && Detective.detective == player) Detective.detective = oldShifter; - } else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == player) { + if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == player) TimeMaster.timeMaster = oldShifter; - } else if (Medic.medic != null && Medic.medic == player) { + if (Medic.medic != null && Medic.medic == player) Medic.medic = oldShifter; - } else if (Swapper.swapper != null && Swapper.swapper == player) { + if (Swapper.swapper != null && Swapper.swapper == player) Swapper.swapper = oldShifter; - } else if (Lovers.lover1 != null && Lovers.lover1 == player) { - Lovers.lover1 = oldShifter; - } else if (Lovers.lover2 != null && Lovers.lover2 == player) { - Lovers.lover2 = oldShifter; - } else if (Seer.seer != null && Seer.seer == player) { + if (Seer.seer != null && Seer.seer == player) Seer.seer = oldShifter; - } else if (Hacker.hacker != null && Hacker.hacker == player) { + if (Hacker.hacker != null && Hacker.hacker == player) Hacker.hacker = oldShifter; - } else if (Child.child != null && Child.child == player) { + if (Child.child != null && Child.child == player) Child.child = oldShifter; - } else if (Tracker.tracker != null && Tracker.tracker == player) { + if (Tracker.tracker != null && Tracker.tracker == player) Tracker.tracker = oldShifter; - } else if (Snitch.snitch != null && Snitch.snitch == player) { + if (Snitch.snitch != null && Snitch.snitch == player) Snitch.snitch = oldShifter; - } else if (Spy.spy != null && Spy.spy == player) { + if (Spy.spy != null && Spy.spy == player) Spy.spy = oldShifter; - } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player) { + if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player) SecurityGuard.securityGuard = oldShifter; - } else if (Arsonist.arsonist != null && Arsonist.arsonist == player) { + if (Arsonist.arsonist != null && Arsonist.arsonist == player) Arsonist.arsonist = oldShifter; - } else { // Crewmate - } // Set cooldowns to max for both players if (PlayerControl.LocalPlayer == oldShifter || PlayerControl.LocalPlayer == player) @@ -430,14 +429,6 @@ namespace TheOtherRoles Camouflager.camouflageTimer = Camouflager.duration; } - public static void loverSuicide(byte remainingLoverId) { - if (Lovers.lover1 != null && !Lovers.lover1.Data.IsDead && Lovers.lover1.PlayerId == remainingLoverId) { - Lovers.lover1.MurderPlayer(Lovers.lover1); - } else if (Lovers.lover2 != null && !Lovers.lover2.Data.IsDead && Lovers.lover2.PlayerId == remainingLoverId) { - Lovers.lover2.MurderPlayer(Lovers.lover2); - } - } - public static void vampireSetBitten(byte targetId, byte reset) { if (reset != 0) { Vampire.bitten = null; @@ -505,7 +496,7 @@ namespace TheOtherRoles return; } player.RemoveInfected(); - if (player != Lovers.lover1 && player != Lovers.lover2) erasePlayerRole(player.PlayerId); + erasePlayerRoles(player.PlayerId, true); Sidekick.sidekick = player; return; @@ -524,7 +515,7 @@ namespace TheOtherRoles return; } - public static void erasePlayerRole(byte playerId) { + public static void erasePlayerRoles(byte playerId, bool ignoreLovers = false) { PlayerControl player = Helpers.playerById(playerId); if (player == null) return; @@ -561,7 +552,7 @@ namespace TheOtherRoles // Other roles if (player == Jester.jester) Jester.clearAndReload(); if (player == Arsonist.arsonist) Arsonist.clearAndReload(); - if (player == Lovers.lover1 || player == Lovers.lover2) { // The whole Lover couple is being erased + if (!ignoreLovers && (player == Lovers.lover1 || player == Lovers.lover2)) { // The whole Lover couple is being erased Lovers.clearAndReload(); } if (player == Jackal.jackal) { // Promote Sidekick and hence override the the Jackal or erase Jackal @@ -681,7 +672,8 @@ namespace TheOtherRoles case (byte)CustomRPC.SetRole: byte roleId = reader.ReadByte(); byte playerId = reader.ReadByte(); - RPCProcedure.setRole(roleId, playerId); + byte flag = reader.ReadByte(); + RPCProcedure.setRole(roleId, playerId, flag); break; case (byte)CustomRPC.SetUncheckedColor: byte c = reader.ReadByte(); @@ -755,9 +747,6 @@ namespace TheOtherRoles case (byte)CustomRPC.CamouflagerCamouflage: RPCProcedure.camouflagerCamouflage(); break; - case (byte)CustomRPC.LoverSuicide: - RPCProcedure.loverSuicide(reader.ReadByte()); - break; case (byte)CustomRPC.VampireSetBitten: byte bittenId = reader.ReadByte(); byte reset = reader.ReadByte(); @@ -784,8 +773,8 @@ namespace TheOtherRoles case (byte)CustomRPC.SidekickPromotes: RPCProcedure.sidekickPromotes(); break; - case (byte)CustomRPC.ErasePlayerRole: - RPCProcedure.erasePlayerRole(reader.ReadByte()); + case (byte)CustomRPC.ErasePlayerRoles: + RPCProcedure.erasePlayerRoles(reader.ReadByte()); break; case (byte)CustomRPC.SetFutureErased: RPCProcedure.setFutureErased(reader.ReadByte()); diff --git a/Source Code/RoleAssignmentPatch.cs b/Source Code/RoleAssignmentPatch.cs index 51aea50..e4a2573 100644 --- a/Source Code/RoleAssignmentPatch.cs +++ b/Source Code/RoleAssignmentPatch.cs @@ -19,7 +19,8 @@ namespace TheOtherRoles AmongUsClient.Instance.FinishRpcImmediately(writer); RPCProcedure.resetVariables(); - assignRoles(); + if (!DestroyableSingleton.InstanceExists) // Don't assign Roles in Tutorial + assignRoles(); } private static void assignRoles() { @@ -106,7 +107,32 @@ namespace TheOtherRoles }; } - private static void assignSpecialRoles(RoleAssignmentData data) { + private static void assignSpecialRoles(RoleAssignmentData data) { + // Assign Lovers + if (rnd.Next(1, 101) <= CustomOptionHolder.loversSpawnRate.getSelection() * 10) { + bool isOnlyRole = !CustomOptionHolder.loversCanHaveAnotherRole.getBool(); + if (data.impostors.Count > 0 && data.crewmates.Count > 0 && (!isOnlyRole || (data.maxCrewmateRoles > 0 && data.maxImpostorRoles > 0)) && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10) { + setRoleToRandomPlayer((byte)RoleId.Lover, data.impostors, 0, isOnlyRole); + setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 1, isOnlyRole); + if (isOnlyRole) { + data.maxCrewmateRoles--; + data.maxImpostorRoles--; + } + } else if (data.crewmates.Count >= 2 && (isOnlyRole || data.maxCrewmateRoles >= 2)) { + byte firstLoverId = setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 0, isOnlyRole); + if (isOnlyRole) { + setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 1); + data.maxCrewmateRoles -= 2; + } else { + var crewmatesWithoutFirstLover = data.crewmates.ToList(); + crewmatesWithoutFirstLover.RemoveAll(p => p.PlayerId == firstLoverId); + setRoleToRandomPlayer((byte)RoleId.Lover, crewmatesWithoutFirstLover, 1, false); + System.Console.WriteLine(crewmatesWithoutFirstLover.Count); + + } + } + } + // Assign Mafia if (data.impostors.Count >= 3 && data.maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10)) { setRoleToRandomPlayer((byte)RoleId.Godfather, data.impostors); @@ -115,20 +141,6 @@ namespace TheOtherRoles data.maxImpostorRoles -= 3; } - // Assign Lovers - if (rnd.Next(1, 101) <= CustomOptionHolder.loversSpawnRate.getSelection() * 10) { - if (data.impostors.Count > 0 && data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10) { - setRoleToRandomPlayer((byte)RoleId.Lover1, data.impostors); - setRoleToRandomPlayer((byte)RoleId.Lover2, data.crewmates); - data.maxCrewmateRoles--; - data.maxImpostorRoles--; - } else if (data.crewmates.Count >= 2 && data.maxCrewmateRoles >= 2) { - setRoleToRandomPlayer((byte)RoleId.Lover1, data.crewmates); - setRoleToRandomPlayer((byte)RoleId.Lover2, data.crewmates); - data.maxCrewmateRoles -= 2; - } - } - // Assign Child if (rnd.Next(1, 101) <= CustomOptionHolder.childSpawnRate.getSelection() * 10) { if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) { @@ -240,22 +252,22 @@ namespace TheOtherRoles } } - private static void setRoleToRandomPlayer(byte roleId, List playerList) { + private static byte setRoleToRandomPlayer(byte roleId, List playerList, byte flag = 0, bool removePlayer = true) { var index = rnd.Next(0, playerList.Count); byte playerId = playerList[index].PlayerId; - playerList.RemoveAt(index); + if (removePlayer) playerList.RemoveAt(index); - setRoleToPlayer(roleId, playerId); - } - - private static void setRoleToPlayer(byte roleId, byte playerId) { MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRole, Hazel.SendOption.Reliable, -1); writer.Write(roleId); writer.Write(playerId); + writer.Write(flag); AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.setRole(roleId, playerId); + RPCProcedure.setRole(roleId, playerId, flag); + return playerId; } + + private class RoleAssignmentData { public List crewmates {get;set;} public List impostors {get;set;} diff --git a/Source Code/RoleInfo.cs b/Source Code/RoleInfo.cs index b7b6760..0d82d7b 100644 --- a/Source Code/RoleInfo.cs +++ b/Source Code/RoleInfo.cs @@ -7,220 +7,259 @@ using UnityEngine; namespace TheOtherRoles { - public class RoleInfo { + class RoleInfo { public Color color; public string name; public string introDescription; public string shortDescription; + public RoleId roleId; - RoleInfo(string name, Color color, string introDescription, string shortDescription) { + RoleInfo(string name, Color color, string introDescription, string shortDescription, RoleId roleId) { this.color = color; this.name = name; this.introDescription = introDescription; this.shortDescription = shortDescription; + this.roleId = roleId; } public static List getRoleInfoForPlayer(PlayerControl p) { List infos = new List(); + // Special roles if (Jester.jester != null && p == Jester.jester) { infos.Add(new RoleInfo("Jester", Jester.color, "Get voted out", - "Get voted out")); + "Get voted out", + RoleId.Jester)); } if (Mayor.mayor != null && p == Mayor.mayor) { infos.Add(new RoleInfo("Mayor", Mayor.color, "Your vote counts twice", - "Your vote counts twice")); + "Your vote counts twice", + RoleId.Mayor)); } if (Engineer.engineer != null && p == Engineer.engineer) { infos.Add(new RoleInfo("Engineer", Engineer.color, "Maintain important systems on the ship", - "Repair the ship")); + "Repair the ship", + RoleId.Engineer)); } if (Sheriff.sheriff != null && p == Sheriff.sheriff) { infos.Add(new RoleInfo("Sheriff", Sheriff.color, "Shoot the Impostors", - "Shoot the Impostors")); + "Shoot the Impostors", + RoleId.Sheriff)); } if (Lighter.lighter != null && p == Lighter.lighter) { infos.Add(new RoleInfo("Lighter", Lighter.color, "Your light never goes out", - "Your light never goes out")); + "Your light never goes out", + RoleId.Lighter)); } if (Godfather.godfather != null && p == Godfather.godfather) { infos.Add(new RoleInfo("Godfather", Godfather.color, "Kill all Crewmates", - "Kill all Crewmates")); + "Kill all Crewmates", + RoleId.Godfather)); ; } if (Mafioso.mafioso != null && p == Mafioso.mafioso) { infos.Add(new RoleInfo("Mafioso", Mafioso.color, "Work with the Mafia to kill the Crewmates", - "Kill all Crewmates")); + "Kill all Crewmates", + RoleId.Mafioso)); } if (Janitor.janitor != null && p == Janitor.janitor) { infos.Add(new RoleInfo("Janitor", Janitor.color, "Work with the Mafia by hiding dead bodies", - "Hide dead bodies")); + "Hide dead bodies", + RoleId.Janitor)); } if (Morphling.morphling != null && p == Morphling.morphling) { infos.Add(new RoleInfo("Morphling", Morphling.color, "Change your look to not get caught", - "Change your look")); + "Change your look", + RoleId.Morphling)); } if (Camouflager.camouflager != null && p == Camouflager.camouflager) { infos.Add(new RoleInfo("Camouflager", Camouflager.color, "Camouflage and kill the Crewmates", - "Hide among others")); + "Hide among others", + RoleId.Camouflager)); } if (Vampire.vampire != null && p == Vampire.vampire) { infos.Add(new RoleInfo("Vampire", Vampire.color, "Kill the Crewmates with your bites", - "Bite your enemies")); + "Bite your enemies", + RoleId.Vampire)); } if (Eraser.eraser != null && p == Eraser.eraser) { infos.Add(new RoleInfo("Eraser", Eraser.color, "Kill the Crewmates and erase their roles", - "Erase the roles of your enemies")); + "Erase the roles of your enemies", + RoleId.Eraser)); } if (Trickster.trickster != null && p == Trickster.trickster) { infos.Add(new RoleInfo("Trickster", Trickster.color, "Use your jack-in-the-boxes to surprise others", - "Surprise your enemies")); + "Surprise your enemies", + RoleId.Trickster)); } if (Cleaner.cleaner != null && p == Cleaner.cleaner) { infos.Add(new RoleInfo("Cleaner", Cleaner.color, "Kill everyone and leave no traces", - "Clean up dead bodies")); + "Clean up dead bodies", + RoleId.Cleaner)); } if (Warlock.warlock != null && p == Warlock.warlock) { infos.Add(new RoleInfo("Warlock", Warlock.color, "Curse other players and kill everyone", - "Curse and kill everyone")); + "Curse and kill everyone", + RoleId.Warlock)); } if (Detective.detective != null && p == Detective.detective) { infos.Add(new RoleInfo("Detective", Detective.color, "Find the Impostors by examining footprints", - "Examine footprints")); + "Examine footprints", + RoleId.Detective)); } if (TimeMaster.timeMaster != null && p == TimeMaster.timeMaster) { infos.Add(new RoleInfo("Time Master", TimeMaster.color, "Save yourself with your time shield", - "Use your time shield")); + "Use your time shield", + RoleId.TimeMaster)); } if (Medic.medic != null && p == Medic.medic) { infos.Add(new RoleInfo("Medic", Medic.color, "Protect someone with your shield", - "Protect other players")); + "Protect other players", + RoleId.Medic)); } if (Shifter.shifter != null && p == Shifter.shifter) { infos.Add(new RoleInfo("Shifter", Shifter.color, "Shift your role", - "Shift your role")); + "Shift your role", + RoleId.Shifter)); } if (Swapper.swapper != null && p == Swapper.swapper) { infos.Add(new RoleInfo("Swapper", Swapper.color, "Swap votes to exile the Impostors", - "Swap votes")); + "Swap votes", + RoleId.Swapper)); } if (Seer.seer != null && p == Seer.seer) { infos.Add(new RoleInfo("Seer", Seer.color, "You will see players die", - "You will see players die")); + "You will see players die", + RoleId.Seer)); } if (Hacker.hacker != null && p == Hacker.hacker) { infos.Add(new RoleInfo("Hacker", Hacker.color, "Hack to find the Impostors", - "Hack to find the Impostors")); + "Hack to find the Impostors", + RoleId.Hacker)); } if (Child.child != null && p == Child.child) { infos.Add(new RoleInfo(p.Data.IsImpostor ? "Bad Child" : "Good Child", p.Data.IsImpostor ? Palette.ImpostorRed : Child.color, "No one will harm you until you grow up", - "No one will harm you")); + "No one will harm you", + RoleId.Child)); } if (Tracker.tracker != null && p == Tracker.tracker) { infos.Add(new RoleInfo("Tracker", Tracker.color, "Track the Impostors down", - "Track the Impostors down")); + "Track the Impostors down", + RoleId.Tracker)); } if (Snitch.snitch != null && p == Snitch.snitch) { infos.Add(new RoleInfo("Snitch", Snitch.color, "Finish your tasks to find the Impostors", - "Finish your tasks")); + "Finish your tasks", + RoleId.Snitch)); } if ((Jackal.jackal != null && p == Jackal.jackal) || (Jackal.formerJackals != null && Jackal.formerJackals.Any(x => x.PlayerId == p.PlayerId))) { infos.Add(new RoleInfo("Jackal", Jackal.color, "Kill all Crewmates and Impostors to win", - "Kill everyone")); + "Kill everyone", + RoleId.Jackal)); } if (Sidekick.sidekick != null && p == Sidekick.sidekick) { infos.Add(new RoleInfo("Sidekick", Sidekick.color, "", - "Help your Jackal to kill everyone")); - } - if ((Lovers.lover1 != null && p == Lovers.lover1) || (Lovers.lover2 != null && p == Lovers.lover2)) { - infos.Add(new RoleInfo(p.Data.IsImpostor ? "ImpLover" : "Lover", - p.Data.IsImpostor ? Palette.ImpostorRed : Lovers.color, - "You are in Love", - "You are in love")); + "Help your Jackal to kill everyone", + RoleId.Sidekick)); } if (Spy.spy != null && p == Spy.spy) { infos.Add(new RoleInfo("Spy", Spy.color, "Confuse the Impostors", - "Confuse the Impostors")); + "Confuse the Impostors", + RoleId.Spy)); } if (SecurityGuard.securityGuard != null && p == SecurityGuard.securityGuard) { infos.Add(new RoleInfo("Security Guard", SecurityGuard.color, "Seal vents and place cameras", - "Seal vents and place cameras")); + "Seal vents and place cameras", + RoleId.SecurityGuard)); } if (Arsonist.arsonist != null && p == Arsonist.arsonist) { infos.Add(new RoleInfo("Arsonist", Arsonist.color, "Let them burn", - "Let them burn")); + "Let them burn", + RoleId.Arsonist)); } + // Default roles if (infos.Count == 0 && p.Data.IsImpostor) { // Just Impostor infos.Add(new RoleInfo("Impostor", Palette.ImpostorRed, - "", - "Sabotage and kill everyone")); + Helpers.cs(Palette.ImpostorRed, "Sabotage and kill everyone"), + "Sabotage and kill everyone", + RoleId.Impostor)); } else if (infos.Count == 0) { // Just Crewmate infos.Add(new RoleInfo("Crewmate", Color.white, - "", - "Find the Impostors")); + "Find the Impostors", + "Find the Impostors", + RoleId.Crewmate)); + } + + // Modifier + if ((Lovers.lover1 != null && p == Lovers.lover1) || (Lovers.lover2 != null && p == Lovers.lover2)) { + infos.Add(new RoleInfo("Lover", + Lovers.color, + "You are in Love", + "You are in love", + RoleId.Lover)); } return infos; diff --git a/Source Code/ShipStatusPatch.cs b/Source Code/ShipStatusPatch.cs index f6487f3..afe5701 100644 --- a/Source Code/ShipStatusPatch.cs +++ b/Source Code/ShipStatusPatch.cs @@ -20,7 +20,9 @@ namespace TheOtherRoles { if (player == null || player.IsDead) // IsDead __result = __instance.MaxLightRadius; - else if (player.IsImpostor) // IsImpostor + else if (player.IsImpostor + || (Jackal.jackal != null && Jackal.jackal.PlayerId == player.PlayerId && Jackal.hasImpostorVision) + || (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == player.PlayerId && Sidekick.hasImpostorVision)) // Impostor or Jackal/Sidekick with Impostor vision __result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod; else if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId && Lighter.lighterTimer > 0f) // if player is Lighter and Lighter has his ability active __result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, num); diff --git a/Source Code/TheOtherRoles.cs b/Source Code/TheOtherRoles.cs index 067c9b3..9b9108a 100644 --- a/Source Code/TheOtherRoles.cs +++ b/Source Code/TheOtherRoles.cs @@ -58,11 +58,13 @@ namespace TheOtherRoles public static bool triggerJesterWin = false; public static bool canCallEmergency = true; + public static bool canSabotage = true; public static void clearAndReload() { jester = null; triggerJesterWin = false; canCallEmergency = CustomOptionHolder.jesterCanCallEmergency.getBool(); + canSabotage = CustomOptionHolder.jesterCanSabotage.getBool(); } } @@ -287,6 +289,7 @@ namespace TheOtherRoles public static Color color = new Color(240f / 255f, 128f / 255f, 72f / 255f, 1); private static Sprite spriteCheck; public static bool canCallEmergency = false; + public static bool canOnlySwapOthers = false; public static byte playerId1 = Byte.MaxValue; public static byte playerId2 = Byte.MaxValue; @@ -302,6 +305,7 @@ namespace TheOtherRoles playerId1 = Byte.MaxValue; playerId2 = Byte.MaxValue; canCallEmergency = CustomOptionHolder.swapperCanCallEmergency.getBool(); + canOnlySwapOthers = CustomOptionHolder.swapperCanOnlySwapOthers.getBool(); } } @@ -314,23 +318,24 @@ namespace TheOtherRoles // Lovers save if next to be exiled is a lover, because RPC of ending game comes before RPC of exiled public static bool notAckedExiledIsLover = false; + public static bool existing() { + return lover1 != null && lover2 != null && !lover1.Data.Disconnected && !lover2.Data.Disconnected; + } + public static bool existingAndAlive() { - return lover1 != null && lover2 != null && !lover1.Data.IsDead && !lover2.Data.IsDead && !lover1.Data.Disconnected && !lover2.Data.Disconnected && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER + return existing() && !lover1.Data.IsDead && !lover2.Data.IsDead && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER } - public static bool existingAndCrewLovers() { - if (lover1 == null || lover2 == null || lover1.Data.Disconnected || lover2.Data.Disconnected) return false; // Null or disconnected - return !(lover1.Data.IsImpostor || lover2.Data.IsImpostor || lover1 == Jackal.jackal || lover2 == Jackal.jackal || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick); // Not Impostor, Sidekick or Jackal + public static bool existingWithKiller() { + return existing() && (lover1 == Jackal.jackal || lover2 == Jackal.jackal + || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick + || lover1.Data.IsImpostor || lover2.Data.IsImpostor); } public static bool hasAliveKillingLover(this PlayerControl player) { - if (!Lovers.existingAndAlive()) - return false; - if (player != null && player != lover1 && player != lover2) // Is Player a Lover? + if (!Lovers.existingAndAlive() || !existingWithKiller()) return false; - return (lover1 == Jackal.jackal || lover2 == Jackal.jackal - || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick - || lover1.Data.IsImpostor || lover2.Data.IsImpostor); // Either of the lovers is a killer + return (player != null && player != lover1 && player != lover2); } public static void clearAndReload() { @@ -362,7 +367,7 @@ namespace TheOtherRoles deadBodyPositions = new List(); limitSoulDuration = CustomOptionHolder.seerLimitSoulDuration.getBool(); soulDuration = CustomOptionHolder.seerSoulDuration.getFloat(); - mode = CustomOptionHolder.medicShowShielded.getSelection(); + mode = CustomOptionHolder.seerMode.getSelection(); } } @@ -616,6 +621,7 @@ namespace TheOtherRoles public static Sprite buttonSprite; public static bool jackalPromotedFromSidekickCanCreateSidekick = true; public static bool canCreateSidekickFromImpostor = true; + public static bool hasImpostorVision = false; public static Sprite getSidekickButtonSprite() { if (buttonSprite) return buttonSprite; @@ -643,6 +649,7 @@ namespace TheOtherRoles jackalPromotedFromSidekickCanCreateSidekick = CustomOptionHolder.jackalPromotedFromSidekickCanCreateSidekick.getBool(); canCreateSidekickFromImpostor = CustomOptionHolder.jackalCanCreateSidekickFromImpostor.getBool(); formerJackals.Clear(); + hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool(); } } @@ -657,6 +664,7 @@ namespace TheOtherRoles public static bool canUseVents = true; public static bool canKill = true; public static bool promotesToJackal = true; + public static bool hasImpostorVision = false; public static void clearAndReload() { sidekick = null; @@ -665,6 +673,7 @@ namespace TheOtherRoles canUseVents = CustomOptionHolder.sidekickCanUseVents.getBool(); canKill = CustomOptionHolder.sidekickCanKill.getBool(); promotesToJackal = CustomOptionHolder.sidekickPromotesToJackal.getBool(); + hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool(); } } diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index 110f4a9..c1b0032 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 2.6.3 + 2.6.4 TheOtherRoles Eisbison diff --git a/Source Code/UpdatePatch.cs b/Source Code/UpdatePatch.cs index 6bdb8ec..7156a5e 100644 --- a/Source Code/UpdatePatch.cs +++ b/Source Code/UpdatePatch.cs @@ -90,10 +90,6 @@ namespace TheOtherRoles setPlayerNameColor(Shifter.shifter, Shifter.color); else if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer) setPlayerNameColor(Swapper.swapper, Swapper.color); - else if (Lovers.lover1 != null && Lovers.lover2 != null && (Lovers.lover1 == PlayerControl.LocalPlayer || Lovers.lover2 == PlayerControl.LocalPlayer)) { - setPlayerNameColor(Lovers.lover1, Lovers.color); - setPlayerNameColor(Lovers.lover2, Lovers.color); - } else if (Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer) setPlayerNameColor(Seer.seer, Seer.color); else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer) @@ -138,7 +134,8 @@ namespace TheOtherRoles // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock and Mafioso } - static void setMafiaNameTags() { + static void setNameTags() { + // Mafia if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.IsImpostor) { foreach (PlayerControl player in PlayerControl.AllPlayerControls) if (Godfather.godfather != null && Godfather.godfather == player) @@ -156,6 +153,18 @@ namespace TheOtherRoles else if (Janitor.janitor != null && Janitor.janitor.PlayerId == player.TargetPlayerId) player.NameText.text = Janitor.janitor.Data.PlayerName + " (J)"; } + + // Lovers + if (Lovers.lover1 != null && Lovers.lover2 != null && (Lovers.lover1 == PlayerControl.LocalPlayer || Lovers.lover2 == PlayerControl.LocalPlayer)) { + string suffix = Helpers.cs(Lovers.color, " ❤"); + Lovers.lover1.nameText.text += suffix; + Lovers.lover2.nameText.text += suffix; + + if (MeetingHud.Instance != null) + foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) + if (Lovers.lover1.PlayerId == player.TargetPlayerId || Lovers.lover2.PlayerId == player.TargetPlayerId) + player.NameText.text += suffix; + } } static void updateShielded() { @@ -316,9 +325,8 @@ namespace TheOtherRoles resetNameTagsAndColors(); setNameColors(); updateShielded(); + setNameTags(); - // Mafia - setMafiaNameTags(); // Impostors updateImpostorKillButton(__instance); // Timer updates diff --git a/Source Code/UsablesPatch.cs b/Source Code/UsablesPatch.cs index 9811555..1d4fff4 100644 --- a/Source Code/UsablesPatch.cs +++ b/Source Code/UsablesPatch.cs @@ -104,6 +104,15 @@ namespace TheOtherRoles } } + // Jester sabotage + if (Jester.canSabotage && Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.CanMove) { + if (!Jester.jester.Data.IsDead && __instance.currentTarget == null) { // no target, so sabotage + __instance.UseButton.sprite = DestroyableSingleton.Instance.GetImage(ImageNames.SabotageButton); + CooldownHelpers.SetCooldownNormalizedUvs(__instance.UseButton); + __instance.UseButton.color = UseButtonManager.EnabledColor; + } + } + // Mafia sabotage button render patch bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer); bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead); @@ -120,6 +129,13 @@ namespace TheOtherRoles static bool Prefix(UseButtonManager __instance) { if (__instance.currentTarget != null) return true; + // Jester sabotage + if (Jester.canSabotage && Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && !Jester.jester.Data.IsDead) { + Action action = m => m.ShowInfectedMap() ; + DestroyableSingleton.Instance.ShowMap(action); + return false; + } + // Mafia sabotage button click patch bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer); bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead);