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