From: Eisbison Date: Sat, 8 May 2021 15:53:26 +0000 (+0200) Subject: Add version 2.6.0 X-Git-Url: https://git.deb.at/?a=commitdiff_plain;h=4b7b51402cfcef91aa6ccd6ce295d42bdfe63cb0;p=rhonda%2Ftheotherroles.git Add version 2.6.0 --- diff --git a/Images/Preview_2.5.1.png b/Images/Preview_2.5.1.png deleted file mode 100644 index c0684b3..0000000 Binary files a/Images/Preview_2.5.1.png and /dev/null differ diff --git a/Images/Preview_2.6.0.png b/Images/Preview_2.6.0.png new file mode 100644 index 0000000..51d5f53 Binary files /dev/null and b/Images/Preview_2.6.0.png differ diff --git a/README.md b/README.md index df97db9..d7c6e0f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Discord](https://discord.com/assets/e4923594e694a21542a489471ecffa50.svg)](https://discord.gg/77RkMJHWsM) ![eisbison banner](./Images/Banner.png) -![eisbison infographic](./Images/Preview_2.5.1.png) +![eisbison infographic](./Images/Preview_2.6.0.png) # The Other Roles @@ -10,10 +10,10 @@ Even more roles are coming soon :) | Impostors | Crewmates | Neutral | |----------|-------------|-----------------| -| [Bad Child](#child) | [Child](#child) | [Jester](#jester) | -| [Camouflager](#camouflager) | [Detective](#detective) | [Jackal](#jackal) | -| [Cleaner](#cleaner) | [Engineer](#engineer) | [Sidekick](#sidekick) | -| [Eraser](#eraser) | [Hacker](#hacker) | | +| [Bad Child](#child) | [Child](#child) | [Arsonist](#arsonist) | +| [Camouflager](#camouflager) | [Detective](#detective) | [Jester](#jester) | +| [Cleaner](#cleaner) | [Engineer](#engineer) | [Jackal](#jackal) | +| [Eraser](#eraser) | [Hacker](#hacker) | [Sidekick](#sidekick) | | [ImpLover](#lovers) | [Lover](#lovers) | | | [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | | | [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | | @@ -33,6 +33,7 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein # Releases | Among Us - Version| Mod Version | Link | |----------|-------------|-----------------| +| 2021.4.14s| v2.6.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.0/TheOtherRoles.zip) | 2021.4.14s| v2.5.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.1/TheOtherRoles.zip) | 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip) | 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip) @@ -58,6 +59,19 @@ The [Role Assignment](#role-assignment) sections explains how the roles are bein
Click to show the Changelog +**Version 2.6.0** +- **New Role:** [Arsonist](#arsonist) +- Added an Ingame Updater, to make it easier to update the Mod +- Added synchronization for Airship toilet doors. Doors now open/close for everyone +- Changed Shifter to also die when shifting a neutral role (Jester, Arsonist, Jackal, ...) +- Changed the option "Jester Can Die To Sheriff" to "Neutrals Can Die To Sheriff" +- Changed the role assignment system. You can now set how many neutral roles you want in your game +- Changed Hacker to see colors more clearly on Admin Table +- Changed version handshake to give more clear info +- Fixed a problem with the Hat Tab leaving too much space between categories +- Fixed an Among Us bug, which made the selected region always show "North America" +- Fixed an Among Us bug, which made the disconnect info be off-screen. (hopefully) + **Version 2.5.1** - **New Hats:** We added the support for custom hats and there are already a few hats inside the game. We can add new hats without updating the mod and we're awaiting your hat designs on our discord server. - Changed Lovers to ignore Lover's Tasks for Taskwin, while an ImpLover is alive @@ -301,7 +315,8 @@ docker run -d -p 22023:22023/udp --env IMPOSTOR_AntiCheat__Enabled=false --env I [TooManyRolesMods](https://github.com/Hardel-DW/TooManyRolesMods) - Idea for the Detective and Time Master roles comes from **Hardel-DW**. Also some code snippets of the implementation were used.\ [TownOfUs](https://github.com/slushiegoose/Town-Of-Us) - Idea for the Swapper, Shifter and a similar Mayor role come from **Slushiegoose**\ [Ottomated](https://twitter.com/ottomated_) - Idea for the Morphling, Snitch and Camouflager role come from **Ottomated**\ -[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies is inspired by the one from the **Crowded Mod Team** +[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies is inspired by the one from the **Crowded Mod Team**\ +[Town-Of-Impostors](https://github.com/AJMix/TownOfImpostors) - Idea for the Arsonist role comes from **AJMix** # Settings The mod adds a few new settings to Among Us (in addition to the role settings): @@ -344,10 +359,11 @@ Here are a few instructions, on how to create a custom hat: We are still improving the role assignment system. It's not that intuitive right now, but it's more flexible than the older one if you're using it right. -First you need to choose how many special roles you want in the game (one option for Impostor roles and one option for the rest). +First you need to choose how many special roles of each kind (Impostor/Neutral/Crewmate) you want in the game. The count you set will only be reached, if there are enough Crewmates/Impostors in the game and if enough roles are set to be in the game (i.e. they are set to > 0%). The roles are then being distributed as follows: - First all roles that are set to 100% are being assigned to arbitrary players -- After that each role that has 10%-90% selected adds 1-9 tickets to a ticket pool (one pool for Impostors, one for Crewmates). Then the roles will be selected randomly from that pool as long as possible (until the selected number is reached, until there are no more Crewmates or until there are no more tickets). If a role is selected from the pool, obviously all the tickets of that role are being removed. +- After that each role that has 10%-90% selected adds 1-9 tickets to a ticket pool (there exists a ticket pool for Crewmates, Neutrals and Impostors). Then the roles will be selected randomly from the pools as long it's possible (until the selected number is reached, until there are no more Crewmates/Impostors or until there are no more tickets). If a role is selected from the pool, obviously all the tickets of that role are being removed. +- The Mafia, Lovers and Child are being selected independently (without using the ticket system) according to the spawn chance you selected. After that all Neutral roles are being selected, then all Crewmate roles and in the very end all Impostor roles. **Example:**\ Settings: 2 special Crewmate roles, Snitch: 100%, Hacker: 10%, Tracker: 30%\ @@ -552,7 +568,7 @@ If they try to kill a Crewmate, they die instead. |----------|:-------------:| | Sheriff Spawn Chance | - | Sheriff Cooldown | - -| Jester Can Die To Sheriff | - +| Neutrals Can Die To Sheriff | - ----------------------- ## Jester @@ -566,6 +582,21 @@ The Jester does not have any tasks. They win the game as a solo, if they get vot | Jester can call emergency meeting | Option to disable the emergency button for the Jester ----------------------- +## Arsonist +### **Team: Neutral** +The Arsonist does not have any tasks, he has to win the game as a solo.\ +The Arsonist can douse other players by pressing the douse button and remaining next to the player for a few seconds.\ +If the player that the Arsonist douses walks out of range, the cooldown will reset to 0.\ +After dousing everyone alive the Arsonist can ignite all the players which results in an Arsonist win. + +### Game Options +| Name | Description | +|----------|:-------------:| +| Arsonist Spawn Chance | - +| Arsonist Countdown | - +| Arsonist Douse Duration | The time it takes to douse a player +----------------------- + ## Seer ### **Team: Crewmates** The Seer has two abilities (one can activate one of them or both in the options). @@ -713,11 +744,10 @@ Otherwise he sees the same information as everyone else. ## Shifter ### **Team: Crewmates** -The Shifter can take over the role of another Crewmate or Neutral, the other player will transform into a Crewmate.\ +The Shifter can take over the role of another Crewmate, the other player will transform into a Crewmate.\ The Shift will always be performed at the end of the next meeting right before a player is exiled. The target needs to be chosen during the round.\ Even if the Shifter or the target dies before the meeting, the Shift will still be performed.\ -Swapping roles with a Crewmate or Neutral gives them their role, the other player becomes a Crewmate.\ -Swapping roles with an Impostor or a custom Impostor role fails and the Shifter commits suicide after the next meeting (there won't be any body).\ +Swapping roles with an Impostor or Neutral fails and the Shifter commits suicide after the next meeting (there won't be any body).\ The Shifter aims to save roles from leaving the game, by e.g. taking over a Sheriff or Medic that is known to the Impostors.\ This works especially well against the Eraser, but also gives the Eraser the possiblity to act like a Shifter.\ The **special interactions** with the Shifter are noted in the chapters of the respective roles.\ @@ -814,12 +844,13 @@ The team Jackal enables multiple new outcomes of the game, listing some examples The priority of the win conditions is the following: 1. Crewmate Child lose by vote 2. Jester wins by vote -3. Team Impostor wins by sabotage -4. Team Crew wins by tasks (also possible if the whole Crew is dead) -5. Lovers among the last three players win -6. Team Jackal wins by outnumbering (When the team Jackal contains an equal or greater amount of players than the Crew and there are 0 Impostors left and team Jackal contains no Lover) -7. Team Impostor wins by outnumbering (When the team Impostors contains an equal or greater amount of players than the Crew and there are 0 players of the team Jackal left and team Impostors contains no Lover) -8. Team Crew wins by outnumbering (When there is no player of the team Jackal and the team Impostrs left) +3. Arsonist win +4. Team Impostor wins by sabotage +5. Team Crew wins by tasks (also possible if the whole Crew is dead) +6. Lovers among the last three players win +7. Team Jackal wins by outnumbering (When the team Jackal contains an equal or greater amount of players than the Crew and there are 0 Impostors left and team Jackal contains no Lover) +8. Team Impostor wins by outnumbering (When the team Impostors contains an equal or greater amount of players than the Crew and there are 0 players of the team Jackal left and team Impostors contains no Lover) +9. Team Crew wins by outnumbering (When there is no player of the team Jackal and the team Impostrs left) **NOTE:** - The Jackal (and his Sidekick) may be killed by a Sheriff. @@ -881,7 +912,7 @@ You can set whether the Sheriff can kill the Spy or not (in order to keep the li ## Security Guard ### **Team: Crewmates** The Security Guard is a Crewmate that has a certain amount of screws that he can use for either sealing vents or for placing new cameras.\ -Placing a new camera and sealing vents takes a configurable amount of screws. The total number of screws that a SecurityGuard has can also be configured.\ +Placing a new camera and sealing vents takes a configurable amount of screws. The total number of screws that a Security Guard has can also be configured.\ The new camera will be visible after the next meeting and accessible by everyone.\ The vents will be sealed after the next meeting, players can't enter or exit sealed vents, but they can still "move to them" underground.\ **NOTE:** diff --git a/Source Code/AUFixes.cs b/Source Code/AUFixes.cs new file mode 100644 index 0000000..74dc6b9 --- /dev/null +++ b/Source Code/AUFixes.cs @@ -0,0 +1,43 @@ +using HarmonyLib; +using Hazel; +using UnityEngine; +using TMPro; +using System; + +namespace TheOtherRoles { + [HarmonyPatch(typeof(OpenDoorConsole), nameof(OpenDoorConsole.Use))] + class ToiletDoorFix { + public static void Prefix(OpenDoorConsole __instance) { + MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.OpenToiletDoor, SendOption.None, -1); + writer.Write(__instance.MyDoor.Id); + AmongUsClient.Instance.FinishRpcImmediately(writer); + } + } + + [HarmonyPatch(typeof(MatchMaker), nameof(MatchMaker.Start))] + public static class MatchMakerPatch { // Reactor Region Fix + public static void Prefix(MatchMaker __instance) { + var parent = __instance.GetComponentInParent().parent; + if (parent == null) return; // Local + var tmps = parent.GetComponentsInChildren(); + foreach (var tmp in tmps) { + if (tmp.name == "RegionText_TMP") { + var region = DestroyableSingleton.Instance.CurrentRegion; + var name = DestroyableSingleton.Instance.GetStringWithDefault(region.TranslateName, region.Name, Array.Empty()); + tmp.text = name; + break; + } + } + } + } + + [HarmonyPatch(typeof(NotificationPopper), nameof(NotificationPopper.Update))] + public static class NotificationPopperUpdatePatch { // Reactor Region Fix + public static void Postfix(NotificationPopper __instance) { + if (__instance.alphaTimer > 0f) { + var pos = __instance.transform.localPosition; + __instance.transform.localPosition = new Vector3(pos.x + 0.5f, pos.y, pos.z); + } + } + } +} \ No newline at end of file diff --git a/Source Code/Buttons.cs b/Source Code/Buttons.cs index 368d18b..3fb0eeb 100644 --- a/Source Code/Buttons.cs +++ b/Source Code/Buttons.cs @@ -31,6 +31,7 @@ namespace TheOtherRoles public static CustomButton cleanerCleanButton; public static CustomButton warlockCurseButton; public static CustomButton securityGuardButton; + public static CustomButton arsonistButton; public static TMPro.TMP_Text securityGuardButtonScrewsText; public static void setCustomButtonCooldowns() { @@ -56,6 +57,7 @@ namespace TheOtherRoles cleanerCleanButton.MaxTimer = Cleaner.cooldown; warlockCurseButton.MaxTimer = Warlock.cooldown; securityGuardButton.MaxTimer = SecurityGuard.cooldown; + arsonistButton.MaxTimer = Arsonist.cooldown; timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration; hackerButton.EffectDuration = Hacker.duration; @@ -63,7 +65,8 @@ namespace TheOtherRoles lighterButton.EffectDuration = Lighter.duration; camouflagerButton.EffectDuration = Camouflager.duration; morphlingButton.EffectDuration = Morphling.duration; - lightsOutButton.EffectDuration = Trickster.lightsOutDuration; + lightsOutButton.EffectDuration = Trickster.lightsOutDuration; + arsonistButton.EffectDuration = Arsonist.duration; // 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; @@ -170,10 +173,8 @@ namespace TheOtherRoles byte targetId = 0; if ((Sheriff.currentTarget.Data.IsImpostor && (Sheriff.currentTarget != Child.child || Child.isGrownUp())) || - Sheriff.currentTarget == Jackal.jackal || - Sheriff.currentTarget == Sidekick.sidekick || - (Sheriff.spyCanDieToSheriff && Spy.spy != null && Spy.spy == Sheriff.currentTarget) || - (Sheriff.jesterCanDieToSheriff && Jester.jester != null && Jester.jester == Sheriff.currentTarget)) { + (Sheriff.spyCanDieToSheriff && Spy.spy == Sheriff.currentTarget) || + (Sheriff.canKillNeutrals && (Jester.jester == Sheriff.currentTarget || Jackal.jackal == Sheriff.currentTarget || Sidekick.sidekick == Sheriff.currentTarget))) { targetId = Sheriff.currentTarget.PlayerId; } else { @@ -679,7 +680,7 @@ namespace TheOtherRoles KeyCode.F ); - // SecurityGuard button + // Security Guard button securityGuardButton = new CustomButton( () => { if (SecurityGuard.ventTarget != null) { // Seal vent @@ -701,7 +702,7 @@ namespace TheOtherRoles } securityGuardButton.Timer = securityGuardButton.MaxTimer; }, - () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; }, + () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews >= Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); }, () => { securityGuardButton.killButtonManager.renderer.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite(); if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}"; @@ -716,14 +717,70 @@ namespace TheOtherRoles __instance, KeyCode.Q ); - - // SecurityGuard button screws counter + + // Security Guard button screws counter securityGuardButtonScrewsText = GameObject.Instantiate(securityGuardButton.killButtonManager.TimerText, securityGuardButton.killButtonManager.TimerText.transform.parent); securityGuardButtonScrewsText.text = ""; securityGuardButtonScrewsText.enableWordWrapping = false; securityGuardButtonScrewsText.transform.localScale = Vector3.one * 0.5f; securityGuardButtonScrewsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0); + // Arsonist button + arsonistButton = new CustomButton( + () => { + bool dousedEveryoneAlive = Arsonist.dousedEveryoneAlive(); + if (dousedEveryoneAlive) { + MessageWriter winWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ArsonistWin, Hazel.SendOption.Reliable, -1); + AmongUsClient.Instance.FinishRpcImmediately(winWriter); + RPCProcedure.arsonistWin(); + arsonistButton.HasEffect = false; + } else if (Arsonist.currentTarget != null) { + Arsonist.douseTarget = Arsonist.currentTarget; + arsonistButton.HasEffect = true; + } + }, + () => { return Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; }, + () => { + bool dousedEveryoneAlive = Arsonist.dousedEveryoneAlive(); + if (dousedEveryoneAlive) arsonistButton.killButtonManager.renderer.sprite = Arsonist.getIgniteSprite(); + + if (arsonistButton.isEffectActive && Arsonist.douseTarget != Arsonist.currentTarget) { + Arsonist.douseTarget = null; + arsonistButton.Timer = 0f; + arsonistButton.isEffectActive = false; + } + + return PlayerControl.LocalPlayer.CanMove && (dousedEveryoneAlive || Arsonist.currentTarget != null); + }, + () => { + arsonistButton.Timer = arsonistButton.MaxTimer; + arsonistButton.isEffectActive = false; + Arsonist.douseTarget = null; + }, + Arsonist.getDouseSprite(), + new Vector3(-1.3f, 0f, 0f), + __instance, + KeyCode.Q, + true, + Arsonist.duration, + () => { + if (Arsonist.douseTarget != null) Arsonist.dousedPlayers.Add(Arsonist.douseTarget); + Arsonist.douseTarget = null; + arsonistButton.Timer = Arsonist.dousedEveryoneAlive() ? 0 : arsonistButton.MaxTimer; + + int playerCounter = 0; + Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z); + bottomLeft += new Vector3(-0.25f, -0.25f, 0); + foreach (PlayerControl p in Arsonist.dousedPlayers) { + if (Arsonist.dousedIcons.ContainsKey(p.PlayerId)) { + Arsonist.dousedIcons[p.PlayerId].gameObject.SetActive(true); + Arsonist.dousedIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * playerCounter * 0.35f; + playerCounter++; + } + } + } + ); + // Set the default (or settings from the previous game) timers/durations when spawning the buttons setCustomButtonCooldowns(); } diff --git a/Source Code/ChatCommands.cs b/Source Code/ChatCommands.cs index 98d3daf..0ff3db2 100644 --- a/Source Code/ChatCommands.cs +++ b/Source Code/ChatCommands.cs @@ -18,21 +18,44 @@ namespace TheOtherRoles { bool handled = false; if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) { using(MD5 md5 = MD5.Create()) { - string hash = System.BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes(text.ToLower()))).Replace("-", "").ToLowerInvariant(); - if (hash.Equals("f92af861c8b7aa5f2d05165abc9ba04f")) { + string hash = System.BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes("tor@" + text.ToLower() + "§eof"))).Replace("-", "").ToLowerInvariant(); + if (hash.Equals("a4eb05314008537d2832e32fa1f33b2e")) { // i am a cheater handled = true; byte colorId = (byte)CustomColors.pickableColors; SaveManager.BodyColor = (byte)colorId; if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } else if (hash.Equals("5a2ee6de2e42d2b326556640f838b0d7")) { + } else if (hash.Equals("80cc70dc5f21bc321b84ce984abd511b")) { // i dont understand hashes handled = true; byte colorId = (byte)(CustomColors.pickableColors + 1); SaveManager.BodyColor = (byte)colorId; if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.CmdCheckColor(colorId); - } - System.Console.WriteLine(hash); + } else if (hash.Equals("3359ffcd0b14ffa39d476a5c96632032")) { // Batch 2 + handled = true; + byte colorId = (byte)(CustomColors.pickableColors + 2); + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) + PlayerControl.LocalPlayer.CmdCheckColor(colorId); + } else if (hash.Equals("14056e0b9e53bc91f0c6a8b1fd5ce8b5")) { + handled = true; + byte colorId = (byte)(CustomColors.pickableColors + 3); + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) + PlayerControl.LocalPlayer.CmdCheckColor(colorId); + } else if (hash.Equals("fb00fb81b0be5177af908576e144d788")) { + handled = true; + byte colorId = (byte)(CustomColors.pickableColors + 4); + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) + PlayerControl.LocalPlayer.CmdCheckColor(colorId); + } else if (hash.Equals("a79e2bd7c9cdc723924bd4d7734ae5da")) { // Eisbison Color + handled = true; + byte colorId = (byte)(CustomColors.pickableColors + 5); + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) + PlayerControl.LocalPlayer.CmdCheckColor(colorId); + } } } if (handled) { diff --git a/Source Code/CredentialsPatch.cs b/Source Code/CredentialsPatch.cs index 8a314f6..df950a2 100644 --- a/Source Code/CredentialsPatch.cs +++ b/Source Code/CredentialsPatch.cs @@ -11,7 +11,7 @@ namespace TheOtherRoles [HarmonyPatch] public static class CredentialsPatch { public static string fullCredentials = -$@"TheOtherRoles v{TheOtherRolesPlugin.Major}.{TheOtherRolesPlugin.Minor}.{TheOtherRolesPlugin.Patch}: +$@"TheOtherRoles v{TheOtherRolesPlugin.Version.ToString()}: - Modded by Eisbison, Thunderstorm584 and EndOfFile - Balanced with Dhalucard diff --git a/Source Code/CustomColors.cs b/Source Code/CustomColors.cs index 1dd2f6d..cc1e49d 100644 --- a/Source Code/CustomColors.cs +++ b/Source Code/CustomColors.cs @@ -81,6 +81,25 @@ namespace TheOtherRoles { color = new Color32(64, 8, 71, 0), shadow = new Color32(24, 32, 116, 0), isLighterColor = false }); + + /** Batch 2 **/ + colors.Add(new CustomColor { longname = "Electric", shortname = "ELEC", + color = new Color32(0xDB, 0xFD, 0x2F, 0), + shadow = new Color32(0x89, 0x9E, 0x1E, 0), + isLighterColor = true }); + colors.Add(new CustomColor { longname = "Signal Orange", shortname = "SIGN", + color = new Color32(0xF7, 0x44, 0x17, 0), + shadow = new Color32(0x9B, 0x2E, 0x0F, 0), + isLighterColor = true }); + colors.Add(new CustomColor { longname = "Navy Blue", shortname = "NAVY", + color = new Color32(0x35, 0x7B, 0x9E, 0), + shadow = new Color32(0x16, 0x2B, 0x62, 0), + isLighterColor = false }); + colors.Add(new CustomColor { longname = "Eisbison", shortname = "EIS", + color = new Color32(0xA8, 0xDF, 0xFF, 0), + shadow = new Color32(0x59, 0x9F, 0xC8, 0), + isLighterColor = true }); + /** Add Colors **/ int id = 50000; foreach (CustomColor cc in colors) { diff --git a/Source Code/CustomHats.cs b/Source Code/CustomHats.cs index a5a29e9..3a74d8d 100644 --- a/Source Code/CustomHats.cs +++ b/Source Code/CustomHats.cs @@ -235,7 +235,7 @@ namespace TheOtherRoles { HatExtension ext = hats[i].Item2; float xpos = __instance.XRange.Lerp((i % __instance.NumPerRow) / (__instance.NumPerRow - 1f)); - float ypos = offset - i / __instance.NumPerRow * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset; + float ypos = offset - (i / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset; ColorChip colorChip = UnityEngine.Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner); if (ext != null) { Transform background = colorChip.transform.FindChild("Background"); @@ -271,7 +271,7 @@ namespace TheOtherRoles { colorChip.Tag = hat; __instance.ColorChips.Add(colorChip); } - return offset - ((hats.Count - 1) / __instance.NumPerRow * (isDefaultPackage ? 1f : 1.5f) + 0.7f) * __instance.YOffset; + return offset - ((hats.Count - 1) / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset - 0.85f; } public static bool Prefix(HatsTab __instance) { @@ -306,12 +306,16 @@ namespace TheOtherRoles { textTemplate = hatButton.transform.FindChild("ButtonText_TMP").GetComponent(); } - var orderedKeys = packages.Keys.OrderBy((string x) => x == innerslothPackageName ? 1000 : 0); + var orderedKeys = packages.Keys.OrderBy((string x) => { + if (x == innerslothPackageName) return 1000; + if (x == "Developer Hats") return 0; + return 500; + }); foreach (string key in orderedKeys) { List> value = packages[key]; - YOffset += createHatPackage(value, key, YOffset, __instance); + YOffset = createHatPackage(value, key, YOffset, __instance); } - __instance.scroller.YBounds.max = (YOffset - 0.6f) * -0.75f; + __instance.scroller.YBounds.max = YOffset * -0.875f; // probably needs to fix up the entire messed math to solve this correctly return false; } } @@ -332,30 +336,30 @@ namespace TheOtherRoles { } public class CustomHatLoader { - public bool running = false; + public static bool running = false; private const string REPO = "https://raw.githubusercontent.com/Eisbison/TheOtherHats/master"; public static List hatdetails = new List(); - - public void LaunchHatFetcher() { - if (this.running) + private static Task hatFetchTask = null; + public static void LaunchHatFetcher() { + if (running) return; - this.running = true; - this.LaunchHatFetcherAsync(); + running = true; + hatFetchTask = LaunchHatFetcherAsync(); } - private async void LaunchHatFetcherAsync() { + private static async Task LaunchHatFetcherAsync() { try { - HttpStatusCode status = await this.FetchHats(); + HttpStatusCode status = await FetchHats(); if (status != HttpStatusCode.OK) System.Console.WriteLine("Custom Hats could not be loaded\n"); } catch (System.Exception e) { System.Console.WriteLine("Unable to fetch hats\n" + e.Message); } - this.running = false; + running = false; } - private string sanitizeResourcePath(string res) { + private static string sanitizeResourcePath(string res) { if (res == null || !res.EndsWith(".png")) return null; @@ -366,91 +370,78 @@ namespace TheOtherRoles { return res; } - public async Task FetchHats() { - using (HttpClient http = new HttpClient()) { - http.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue{ NoCache = true }; - using (HttpRequestMessage msg = new HttpRequestMessage()) { - msg.Method = HttpMethod.Get; - msg.RequestUri = new System.Uri($"{REPO}/CustomHats.json"); - HttpResponseMessage httpResponseMessage = await http.SendAsync(msg); - using (HttpResponseMessage res = httpResponseMessage) { - try { - if (res.Content == null) { - System.Console.WriteLine("Server returned no data: " + res.StatusCode.ToString()); - return HttpStatusCode.ExpectationFailed; - } - if (res.StatusCode != HttpStatusCode.OK) - return res.StatusCode; - - string json = await res.Content.ReadAsStringAsync(); - // System.Console.WriteLine(json); - - JToken jobj = JObject.Parse(json)["hats"]; - if (!jobj.HasValues) - return HttpStatusCode.ExpectationFailed; - - List hatdatas = new List(); - - for (JToken current = jobj.First; current != null; current = current.Next) { - if (current.HasValues) { - CustomHatOnline info = new CustomHatOnline(); - - info.name = current["name"]?.ToString(); - info.resource = sanitizeResourcePath(current["resource"]?.ToString()); - if (info.resource == null || info.name == null) // required - continue; - info.reshasha = current["reshasha"]?.ToString(); - info.backresource = sanitizeResourcePath(current["backresource"]?.ToString()); - info.reshashb = current["reshashb"]?.ToString(); - info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString()); - info.reshashc = current["reshashc"]?.ToString(); - info.author = current["author"]?.ToString(); - info.package = current["package"]?.ToString(); - info.condition = current["condition"]?.ToString(); - info.bounce = current["bounce"] != null; - info.adaptive = current["adaptive"] != null; - info.behind = current["behind"] != null; - hatdatas.Add(info); - } - } - - List markedfordownload = new List(); - - string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; - using (MD5 md5 = MD5.Create()) { - foreach (CustomHatOnline data in hatdatas) { - if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5)) - markedfordownload.Add(data.resource); - if (data.backresource != null && doesResourceRequireDownload(filePath + data.backresource, data.reshashb, md5)) - markedfordownload.Add(data.backresource); - if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5)) - markedfordownload.Add(data.climbresource); - } - } - markedfordownload.AsParallel().ForAll(file => { - System.Uri url = new System.Uri($"{REPO}/hats/{file}"); - // System.Console.WriteLine($"Downloading {file}"); - var fres = http.GetAsync(url, HttpCompletionOption.ResponseContentRead); - if (fres.Result.StatusCode != HttpStatusCode.OK) - return; - using (var resStream = fres.Result.Content.ReadAsStreamAsync().Result) { - using (var fileStream = File.Create($"{filePath}\\{file}")) { - resStream.CopyTo(fileStream); - } - } - }); - - hatdetails = hatdatas; - } catch (System.Exception ex) { - System.Console.WriteLine(ex); + public static async Task FetchHats() { + HttpClient http = new HttpClient(); + http.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue{ NoCache = true }; + var response = await http.GetAsync(new System.Uri($"{REPO}/CustomHats.json"), HttpCompletionOption.ResponseContentRead); + try { + if (response.StatusCode != HttpStatusCode.OK) return response.StatusCode; + if (response.Content == null) { + System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); + return HttpStatusCode.ExpectationFailed; + } + string json = await response.Content.ReadAsStringAsync(); + JToken jobj = JObject.Parse(json)["hats"]; + if (!jobj.HasValues) return HttpStatusCode.ExpectationFailed; + + List hatdatas = new List(); + + for (JToken current = jobj.First; current != null; current = current.Next) { + if (current.HasValues) { + CustomHatOnline info = new CustomHatOnline(); + + info.name = current["name"]?.ToString(); + info.resource = sanitizeResourcePath(current["resource"]?.ToString()); + if (info.resource == null || info.name == null) // required + continue; + info.reshasha = current["reshasha"]?.ToString(); + info.backresource = sanitizeResourcePath(current["backresource"]?.ToString()); + info.reshashb = current["reshashb"]?.ToString(); + info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString()); + info.reshashc = current["reshashc"]?.ToString(); + info.author = current["author"]?.ToString(); + info.package = current["package"]?.ToString(); + info.condition = current["condition"]?.ToString(); + info.bounce = current["bounce"] != null; + info.adaptive = current["adaptive"] != null; + info.behind = current["behind"] != null; + hatdatas.Add(info); + } + } + + List markedfordownload = new List(); + + string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\"; + MD5 md5 = MD5.Create(); + foreach (CustomHatOnline data in hatdatas) { + if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5)) + markedfordownload.Add(data.resource); + if (data.backresource != null && doesResourceRequireDownload(filePath + data.backresource, data.reshashb, md5)) + markedfordownload.Add(data.backresource); + if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5)) + markedfordownload.Add(data.climbresource); + } + + foreach(var file in markedfordownload) { + + var hatFileResponse = await http.GetAsync($"{REPO}/hats/{file}", HttpCompletionOption.ResponseContentRead); + if (hatFileResponse.StatusCode != HttpStatusCode.OK) continue; + using (var responseStream = await hatFileResponse.Content.ReadAsStreamAsync()) { + using (var fileStream = File.Create($"{filePath}\\{file}")) { + responseStream.CopyTo(fileStream); } } } + + hatdetails = hatdatas; + } catch (System.Exception ex) { + TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); + System.Console.WriteLine(ex); } return HttpStatusCode.OK; } - private bool doesResourceRequireDownload(string respath, string reshash, MD5 md5) { + private static bool doesResourceRequireDownload(string respath, string reshash, MD5 md5) { if (reshash == null || !File.Exists(respath)) return true; diff --git a/Source Code/CustomOptions.cs b/Source Code/CustomOptions.cs index fa60fb7..c777838 100644 --- a/Source Code/CustomOptions.cs +++ b/Source Code/CustomOptions.cs @@ -12,12 +12,13 @@ using static TheOtherRoles.TheOtherRoles; namespace TheOtherRoles { public class CustomOptionHolder { public static string[] rates = new string[]{"0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"}; - public static string[] crewmateRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3", "3-4", "4", "4-5", "5", "5-6", "6", "6-7", "7", "7-8", "8", "8-9", "9", "9-10", "10", "10-11", "11", "11-12", "12", "12-13", "13", "13-14", "14", "14-15", "15"}; + public static string[] crewmateAndNeutralRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3", "3-4", "4", "4-5", "5", "5-6", "6", "6-7", "7", "7-8", "8", "8-9", "9", "9-10", "10", "10-11", "11", "11-12", "12", "12-13", "13", "13-14", "14", "14-15", "15"}; public static string[] impostorRoleCaps = new string[]{"0", "0-1", "1", "1-2", "2", "2-3", "3"}; public static string[] presets = new string[]{"Preset 1", "Preset 2", "Preset 3", "Preset 4", "Preset 5"}; public static CustomOption presetSelection; public static CustomOption crewmateRolesCount; + public static CustomOption neutralRolesCount; public static CustomOption impostorRolesCount; public static CustomOption mafiaSpawnRate; @@ -50,6 +51,21 @@ namespace TheOtherRoles { public static CustomOption jesterSpawnRate; public static CustomOption jesterCanCallEmergency; + public static CustomOption arsonistSpawnRate; + public static CustomOption arsonistCooldown; + public static CustomOption arsonistDuration; + + public static CustomOption jackalSpawnRate; + public static CustomOption jackalKillCooldown; + public static CustomOption jackalCreateSidekickCooldown; + public static CustomOption jackalCanUseVents; + public static CustomOption jackalCanCreateSidekick; + public static CustomOption sidekickPromotesToJackal; + public static CustomOption sidekickCanKill; + public static CustomOption sidekickCanUseVents; + public static CustomOption jackalPromotedFromSidekickCanCreateSidekick; + public static CustomOption jackalCanCreateSidekickFromImpostor; + public static CustomOption shifterSpawnRate; public static CustomOption mayorSpawnRate; @@ -58,7 +74,7 @@ namespace TheOtherRoles { public static CustomOption sheriffSpawnRate; public static CustomOption sheriffCooldown; - public static CustomOption jesterCanDieToSheriff; + public static CustomOption sheriffCanKillNeutrals; public static CustomOption lighterSpawnRate; public static CustomOption lighterModeLightsOnVision; @@ -101,17 +117,6 @@ namespace TheOtherRoles { public static CustomOption snitchSpawnRate; public static CustomOption snitchLeftTasksForImpostors; - public static CustomOption jackalSpawnRate; - public static CustomOption jackalKillCooldown; - public static CustomOption jackalCreateSidekickCooldown; - public static CustomOption jackalCanUseVents; - public static CustomOption jackalCanCreateSidekick; - public static CustomOption sidekickPromotesToJackal; - public static CustomOption sidekickCanKill; - public static CustomOption sidekickCanUseVents; - public static CustomOption jackalPromotedFromSidekickCanCreateSidekick; - public static CustomOption jackalCanCreateSidekickFromImpostor; - public static CustomOption spySpawnRate; public static CustomOption spyCanDieToSheriff; public static CustomOption spyImpostorsCanKillAnyone; @@ -155,7 +160,8 @@ namespace TheOtherRoles { // Role Options presetSelection = CustomOption.Create(0, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true); - crewmateRolesCount = CustomOption.Create(1, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Crewmate/Neutral Roles"), crewmateRoleCaps, null, true); + crewmateRolesCount = CustomOption.Create(1, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Crewmate Roles"), crewmateAndNeutralRoleCaps, null, true); + neutralRolesCount = CustomOption.Create(7, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Neutral Roles"), crewmateAndNeutralRoleCaps); impostorRolesCount = CustomOption.Create(2, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Number Of Impostor Roles"), impostorRoleCaps); mafiaSpawnRate = CustomOption.Create(10, cs(Janitor.color, "Mafia"), rates, null, true); @@ -200,6 +206,21 @@ namespace TheOtherRoles { jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true); jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate); + arsonistSpawnRate = CustomOption.Create(290, cs(Arsonist.color, "Arsonist"), rates, null, true); + arsonistCooldown = CustomOption.Create(291, "Arsonist Cooldown", 30f, 5f, 60f, 2.5f, arsonistSpawnRate); + arsonistDuration = CustomOption.Create(292, "Arsonist Douse Duration", 3f, 1f, 10f, 1f, arsonistSpawnRate); + + jackalSpawnRate = CustomOption.Create(220, cs(Jackal.color, "Jackal"), rates, null, true); + jackalKillCooldown = CustomOption.Create(221, "Jackal/Sidekick Kill Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate); + jackalCreateSidekickCooldown = CustomOption.Create(222, "Jackal Create Sidekick Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate); + jackalCanUseVents = CustomOption.Create(223, "Jackal Can Use Vents", true, jackalSpawnRate); + jackalCanCreateSidekick = CustomOption.Create(224, "Jackal Can Create A Sidekick", false, jackalSpawnRate); + sidekickPromotesToJackal = CustomOption.Create(225, "Sidekick Gets Promoted To Jackal On Jackal Death", false, jackalSpawnRate); + sidekickCanKill = CustomOption.Create(226, "Sidekick Can Kill", false, jackalSpawnRate); + sidekickCanUseVents = CustomOption.Create(227, "Sidekick Can Use Vents", true, jackalSpawnRate); + jackalPromotedFromSidekickCanCreateSidekick = CustomOption.Create(228, "Jackals Promoted From Sidekick Can Create A Sidekick", true, jackalSpawnRate); + jackalCanCreateSidekickFromImpostor = CustomOption.Create(229, "Jackals Can Make An Impostor To His Sidekick", true, jackalSpawnRate); + shifterSpawnRate = CustomOption.Create(70, cs(Shifter.color, "Shifter"), rates, null, true); mayorSpawnRate = CustomOption.Create(80, cs(Mayor.color, "Mayor"), rates, null, true); @@ -208,7 +229,7 @@ namespace TheOtherRoles { sheriffSpawnRate = CustomOption.Create(100, cs(Sheriff.color, "Sheriff"), rates, null, true); sheriffCooldown = CustomOption.Create(101, "Sheriff Cooldown", 30f, 10f, 60f, 2.5f, sheriffSpawnRate); - jesterCanDieToSheriff = CustomOption.Create(102, "Sheriff Can Kill The Jester", false, sheriffSpawnRate); + sheriffCanKillNeutrals = CustomOption.Create(102, "Neutrals Can Kill The Jester", false, sheriffSpawnRate); lighterSpawnRate = CustomOption.Create(110, cs(Lighter.color, "Lighter"), rates, null, true); @@ -252,17 +273,6 @@ namespace TheOtherRoles { snitchSpawnRate = CustomOption.Create(210, cs(Snitch.color, "Snitch"), rates, null, true); snitchLeftTasksForImpostors = CustomOption.Create(211, "Task Count Where Impostors See Snitch", 1f, 0f, 5f, 1f, snitchSpawnRate); - jackalSpawnRate = CustomOption.Create(220, cs(Jackal.color, "Jackal"), rates, null, true); - jackalKillCooldown = CustomOption.Create(221, "Jackal/Sidekick Kill Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate); - jackalCreateSidekickCooldown = CustomOption.Create(222, "Jackal Create Sidekick Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate); - jackalCanUseVents = CustomOption.Create(223, "Jackal Can Use Vents", true, jackalSpawnRate); - jackalCanCreateSidekick = CustomOption.Create(224, "Jackal Can Create A Sidekick", false, jackalSpawnRate); - sidekickPromotesToJackal = CustomOption.Create(225, "Sidekick Gets Promoted To Jackal On Jackal Death", false, jackalSpawnRate); - sidekickCanKill = CustomOption.Create(226, "Sidekick Can Kill", false, jackalSpawnRate); - sidekickCanUseVents = CustomOption.Create(227, "Sidekick Can Use Vents", true, jackalSpawnRate); - jackalPromotedFromSidekickCanCreateSidekick = CustomOption.Create(228, "Jackals Promoted From Sidekick Can Create A Sidekick", true, jackalSpawnRate); - jackalCanCreateSidekickFromImpostor = CustomOption.Create(229, "Jackals Can Make An Impostor To His Sidekick", true, jackalSpawnRate); - spySpawnRate = CustomOption.Create(240, cs(Spy.color, "Spy"), rates, null, true); spyCanDieToSheriff = CustomOption.Create(241, "Spy Can Die To Sheriff", false, spySpawnRate); spyImpostorsCanKillAnyone = CustomOption.Create(242, "Impostors Can Kill Anyone If There Is A Spy", true, spySpawnRate); @@ -528,7 +538,7 @@ namespace TheOtherRoles { var hudString = sb.ToString(); int defaultSettingsLines = 19; - int roleSettingsLines = defaultSettingsLines + 30; + int roleSettingsLines = defaultSettingsLines + 32; int detailedSettingsP1 = roleSettingsLines + 34; int detailedSettingsP2 = detailedSettingsP1 + 36; int end1 = hudString.TakeWhile(c => (defaultSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count(); @@ -544,13 +554,13 @@ namespace TheOtherRoles { int gap = 1; int index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index, "\n"); - gap = 4; + gap = 5; index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index, "\n"); - gap = 13; + gap = 16; index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index + 1, "\n"); - gap = 16; + gap = 20; index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count(); hudString = hudString.Insert(index + 1, "\n"); } else if (counter == 2) { diff --git a/Source Code/EndGamePatch.cs b/Source Code/EndGamePatch.cs index 02cfcd5..d3aaf23 100644 --- a/Source Code/EndGamePatch.cs +++ b/Source Code/EndGamePatch.cs @@ -16,7 +16,8 @@ namespace TheOtherRoles { LoversWin = 10, TeamJackalWin = 11, ChildLose = 12, - JesterWin = 13 + JesterWin = 13, + ArsonistWin = 14 } enum WinCondition { @@ -25,7 +26,8 @@ namespace TheOtherRoles { LoversSoloWin, JesterWin, JackalWin, - ChildLose + ChildLose, + ArsonistWin } static class AdditionalTempData { @@ -52,33 +54,22 @@ namespace TheOtherRoles { public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref GameOverReason reason, [HarmonyArgument(1)]bool showAd) { AdditionalTempData.clear(); - // Remove Jester from winners (on Jester win he will be added again, see below) - if (Jester.jester != null) { - WinningPlayerData jesterWinner = null; - foreach (WinningPlayerData winner in TempData.winners) - if (winner.Name == Jester.jester.Data.PlayerName) jesterWinner = winner; - - if (jesterWinner != null) TempData.winners.Remove(jesterWinner); - } - // Remove Jackal and Sidekick from winners (on Jackal win he will be added again, see below) - if (Jackal.jackal != null || Sidekick.sidekick != null) { - List winnersToRemove = new List(); - foreach (WinningPlayerData winner in TempData.winners) { - if (winner.Name == Jackal.jackal?.Data?.PlayerName) winnersToRemove.Add(winner); - if (winner.Name == Sidekick.sidekick?.Data?.PlayerName) winnersToRemove.Add(winner); - foreach(var player in Jackal.formerJackals) { - if (winner.Name == player.Data.PlayerName) { - winnersToRemove.Add(winner); - } - } - } - - foreach (var winner in winnersToRemove) { - TempData.winners.Remove(winner); - } + // Remove Jester, Arsonist, Jackal, former Jackals and Sidekick from winners (if they win, they'll be readded) + List notWinners = new List(); + if (Jester.jester != null) notWinners.Add(Jester.jester); + if (Sidekick.sidekick != null) notWinners.Add(Sidekick.sidekick); + if (Jackal.jackal != null) notWinners.Add(Jackal.jackal); + if (Arsonist.arsonist != null) notWinners.Add(Arsonist.arsonist); + notWinners.AddRange(Jackal.formerJackals); + + List winnersToRemove = new List(); + foreach (WinningPlayerData winner in TempData.winners) { + if (notWinners.Any(x => x.Data.PlayerName == winner.Name)) winnersToRemove.Add(winner); } + foreach (var winner in winnersToRemove) TempData.winners.Remove(winner); bool jesterWin = Jester.jester != null && gameOverReason == (GameOverReason)CustomGameOverReason.JesterWin; + bool arsonistWin = Arsonist.arsonist != null && gameOverReason == (GameOverReason)CustomGameOverReason.ArsonistWin; bool childLose = Child.child != null && gameOverReason == (GameOverReason)CustomGameOverReason.ChildLose; bool loversWin = Lovers.existingAndAlive() && (gameOverReason == (GameOverReason)CustomGameOverReason.LoversWin || (TempData.DidHumansWin(gameOverReason) && Lovers.existingAndCrewLovers())); // Either they win if they are among the last 3 players, or they win if they are both Crewmates and both alive and the Crew wins (Team Imp/Jackal Lovers can only win solo wins) bool teamJackalWin = gameOverReason == (GameOverReason)CustomGameOverReason.TeamJackalWin && ((Jackal.jackal != null && !Jackal.jackal.Data.IsDead) || (Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead)); @@ -100,6 +91,14 @@ namespace TheOtherRoles { AdditionalTempData.winCondition = WinCondition.JesterWin; } + // Arsonist win + else if (arsonistWin) { + TempData.winners = new Il2CppSystem.Collections.Generic.List(); + WinningPlayerData wpd = new WinningPlayerData(Arsonist.arsonist.Data); + TempData.winners.Add(wpd); + AdditionalTempData.winCondition = WinCondition.ArsonistWin; + } + // Lovers win conditions else if (loversWin) { AdditionalTempData.localIsLover = (PlayerControl.LocalPlayer == Lovers.lover1 || PlayerControl.LocalPlayer == Lovers.lover2); @@ -111,7 +110,7 @@ namespace TheOtherRoles { if (p == null) continue; if (p == Lovers.lover1 || p == Lovers.lover2) TempData.winners.Add(new WinningPlayerData(p.Data)); - else if (p != Jester.jester && p != Jackal.jackal && p != Sidekick.sidekick && !p.Data.IsImpostor) + else if (p != Jester.jester && p != Jackal.jackal && p != Sidekick.sidekick && p != Arsonist.arsonist && !Jackal.formerJackals.Contains(p) && !p.Data.IsImpostor) TempData.winners.Add(new WinningPlayerData(p.Data)); } } @@ -163,6 +162,10 @@ namespace TheOtherRoles { textRenderer.text = "Jester Wins"; textRenderer.color = Jester.color; } + else if (AdditionalTempData.winCondition == WinCondition.ArsonistWin) { + textRenderer.text = "Arsonist Wins"; + textRenderer.color = Arsonist.color; + } else if (AdditionalTempData.winCondition == WinCondition.LoversTeamWin) { if (AdditionalTempData.localIsLover) { __instance.WinText.text = "Double Victory"; @@ -198,6 +201,7 @@ namespace TheOtherRoles { var statistics = new PlayerStatistics(__instance); if (CheckAndEndGameForChildLose(__instance)) return false; if (CheckAndEndGameForJesterWin(__instance)) return false; + if (CheckAndEndGameForArsonistWin(__instance)) return false; if (CheckAndEndGameForSabotageWin(__instance)) return false; if (CheckAndEndGameForTaskWin(__instance)) return false; if (CheckAndEndGameForLoverWin(__instance, statistics)) return false; @@ -225,6 +229,15 @@ namespace TheOtherRoles { return false; } + private static bool CheckAndEndGameForArsonistWin(ShipStatus __instance) { + if (Arsonist.triggerArsonistWin) { + __instance.enabled = false; + ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.ArsonistWin, false); + return true; + } + return false; + } + private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) { if (__instance.Systems == null) return false; ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.LifeSupp) ? __instance.Systems[SystemTypes.LifeSupp] : null; diff --git a/Source Code/GameStartManagerPatch.cs b/Source Code/GameStartManagerPatch.cs index 597ace7..ebe2017 100644 --- a/Source Code/GameStartManagerPatch.cs +++ b/Source Code/GameStartManagerPatch.cs @@ -8,7 +8,7 @@ using UnhollowerBaseLib; namespace TheOtherRoles { public class GameStartManagerPatch { - public static Dictionary> playerVersions = new Dictionary>(); + public static Dictionary playerVersions = new Dictionary(); private static float timer = 600f; private static bool versionSent = false; private static string lobbyCodeText = ""; @@ -31,7 +31,9 @@ namespace TheOtherRoles { public class GameStartManagerUpdatePatch { private static bool update = false; private static string currentText = ""; - + private static int kc = 0; + private static KeyCode[] ks = new [] { KeyCode.UpArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.B, KeyCode.A, KeyCode.Return }; + public static void Prefix(GameStartManager __instance) { if (!AmongUsClient.Instance.AmHost || !GameData.Instance) return; // Not host or no instance update = GameData.Instance.PlayerCount != __instance.LastPlayerCount; @@ -42,14 +44,42 @@ namespace TheOtherRoles { if (PlayerControl.LocalPlayer != null && !versionSent) { versionSent = true; MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionHandshake, Hazel.SendOption.Reliable, -1); - writer.Write(TheOtherRolesPlugin.Major); - writer.Write(TheOtherRolesPlugin.Minor); - writer.Write(TheOtherRolesPlugin.Patch); + writer.Write((byte)TheOtherRolesPlugin.Version.Major); + writer.Write((byte)TheOtherRolesPlugin.Version.Minor); + writer.Write((byte)TheOtherRolesPlugin.Version.Build); writer.WritePacked(AmongUsClient.Instance.ClientId); AmongUsClient.Instance.FinishRpcImmediately(writer); - RPCProcedure.versionHandshake(TheOtherRolesPlugin.Major, TheOtherRolesPlugin.Minor, TheOtherRolesPlugin.Patch, AmongUsClient.Instance.ClientId); + RPCProcedure.versionHandshake(TheOtherRolesPlugin.Version.Major, TheOtherRolesPlugin.Version.Minor, TheOtherRolesPlugin.Version.Build, AmongUsClient.Instance.ClientId); + } + + if(kc < ks.Length && Input.GetKeyDown(ks[kc])) { + kc++; + } else if(Input.anyKeyDown) { + kc = 0; } + if(kc == ks.Length) { + kc = 0; + + // Random Color + byte colorId = (byte)TheOtherRoles.rnd.Next(0, Palette.PlayerColors.Length); + SaveManager.BodyColor = (byte)colorId; + if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.CmdCheckColor(colorId); + + // Random Hat + var hats = HatManager.Instance.GetUnlockedHats(); + var unlockedHatIndex = TheOtherRoles.rnd.Next(0, hats.Length); + var hatId = (uint)HatManager.Instance.AllHats.IndexOf(hats[unlockedHatIndex]); + if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.RpcSetHat(hatId); + + // Random Skin + var skins = HatManager.Instance.GetUnlockedSkins(); + var unlockedSkinIndex = TheOtherRoles.rnd.Next(0, skins.Length); + var skinId = (uint)HatManager.Instance.AllSkins.IndexOf(skins[unlockedSkinIndex]); + if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.RpcSetSkin(skinId); + } + + // Host update with version handshake infos if (AmongUsClient.Instance.AmHost) { bool blockStart = false; @@ -62,9 +92,15 @@ namespace TheOtherRoles { else if (!playerVersions.ContainsKey(client.Id)) { blockStart = true; message += $"{client.Character.Data.PlayerName} has a different or no version of The Other Roles\n"; - } else if (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch) { - blockStart = true; - message += $"{client.Character.Data.PlayerName} has a different version (v{playerVersions[client.Id].Item1}.{playerVersions[client.Id].Item2}.{playerVersions[client.Id].Item3}) of The Other Roles\n"; + } else { + int diff = TheOtherRolesPlugin.Version.CompareTo(playerVersions[client.Id]); + if (diff > 0) { + message += $"{client.Character.Data.PlayerName} has an older version of The Other Roles (v{playerVersions[client.Id].ToString()})\n"; + blockStart = true; + } else if (diff > 0) { + message += $"{client.Character.Data.PlayerName} has a newer version of The Other Roles (v{playerVersions[client.Id].ToString()}) \n"; + blockStart = true; + } } } if (blockStart) { diff --git a/Source Code/Helpers.cs b/Source Code/Helpers.cs index 3a5f52a..786aa00 100644 --- a/Source Code/Helpers.cs +++ b/Source Code/Helpers.cs @@ -176,7 +176,7 @@ namespace TheOtherRoles { } public static bool hasFakeTasks(this PlayerControl player) { - return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick); + return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist); } public static void clearAllTasks(this PlayerControl player) { diff --git a/Source Code/IntroPatch.cs b/Source Code/IntroPatch.cs index 1828434..910d619 100644 --- a/Source Code/IntroPatch.cs +++ b/Source Code/IntroPatch.cs @@ -7,21 +7,46 @@ using System.Linq; namespace TheOtherRoles { + [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))] + class IntroCutsceneOnDestroyPatch { + public static void Prefix(IntroCutscene __instance) { + // Arsonist generate player icons + if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer == Arsonist.arsonist && HudManager.Instance != null) { + int playerCounter = 0; + Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z); + bottomLeft += new Vector3(-0.25f, -0.25f, 0); + foreach (PlayerControl player in PlayerControl.AllPlayerControls) { + if (player != PlayerControl.LocalPlayer) { + GameData.PlayerInfo data = player.Data; + PoolablePlayer poolablePlayer = UnityEngine.Object.Instantiate(__instance.PlayerPrefab, HudManager.Instance.transform); + poolablePlayer.transform.localPosition = bottomLeft + Vector3.right * playerCounter * 0.35f; + poolablePlayer.transform.localScale = Vector3.one * 0.3f; + PlayerControl.SetPlayerMaterialColors(data.ColorId, poolablePlayer.Body); + DestroyableSingleton.Instance.SetSkin(poolablePlayer.SkinSlot, data.SkinId); + poolablePlayer.HatSlot.SetHat(data.HatId, data.ColorId); + PlayerControl.SetPetImage(data.PetId, data.ColorId, poolablePlayer.PetSlot); + poolablePlayer.NameText.text = data.PlayerName; + poolablePlayer.SetFlipX(true); + poolablePlayer.gameObject.SetActive(false); + + Arsonist.dousedIcons[player.PlayerId] = poolablePlayer; + playerCounter++; + } + } + } + } + } + [HarmonyPatch(typeof(IntroCutscene.Nested_0), nameof(IntroCutscene.Nested_0.MoveNext))] class IntroPatch { - // Intro special teams static void Prefix(IntroCutscene.Nested_0 __instance) { - if (PlayerControl.LocalPlayer == Jester.jester) - { - var jesterTeam = new Il2CppSystem.Collections.Generic.List(); - jesterTeam.Add(PlayerControl.LocalPlayer); - __instance.yourTeam = jesterTeam; - } else if (PlayerControl.LocalPlayer == Jackal.jackal) { - var jackalTeam = new Il2CppSystem.Collections.Generic.List(); - jackalTeam.Add(PlayerControl.LocalPlayer); - __instance.yourTeam = jackalTeam; + // Intro solo teams + if (PlayerControl.LocalPlayer == Jester.jester || PlayerControl.LocalPlayer == Jackal.jackal || PlayerControl.LocalPlayer == Arsonist.arsonist) { + var soloTeam = new Il2CppSystem.Collections.Generic.List(); + soloTeam.Add(PlayerControl.LocalPlayer); + __instance.yourTeam = soloTeam; } // Add the Spy to the Impostor team (for the Impostors) diff --git a/Source Code/Main.cs b/Source Code/Main.cs index 6483940..c5e5056 100644 --- a/Source Code/Main.cs +++ b/Source Code/Main.cs @@ -14,15 +14,13 @@ using UnityEngine; namespace TheOtherRoles { - [BepInPlugin(Id, "The Other Roles", Version)] + [BepInPlugin(Id, "The Other Roles", VersionString)] [BepInProcess("Among Us.exe")] public class TheOtherRolesPlugin : BasePlugin { public const string Id = "me.eisbison.theotherroles"; - public const string Version = "2.5.1"; - public const byte Major = 2; - public const byte Minor = 5; - public const byte Patch = 1; + public const string VersionString = "2.6.0"; + public static System.Version Version = System.Version.Parse(VersionString); public Harmony Harmony { get; } = new Harmony(Id); public static TheOtherRolesPlugin Instance; @@ -52,8 +50,7 @@ namespace TheOtherRoles } public override void Load() { - new CustomHatLoader().LaunchHatFetcher(); - + DebugMode = Config.Bind("Custom", "Enable Debug Mode", false); StreamerMode = Config.Bind("Custom", "Enable Streamer Mode", false); GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true); diff --git a/Source Code/MeetingPatch.cs b/Source Code/MeetingPatch.cs index a794666..43eb832 100644 --- a/Source Code/MeetingPatch.cs +++ b/Source Code/MeetingPatch.cs @@ -426,16 +426,21 @@ namespace TheOtherRoles } // SecurityGuard vents and cameras - MapOptions.camerasToAdd.ForEach(x => x.gameObject.SetActive(true)); var allCameras = ShipStatus.Instance.AllCameras.ToList(); - allCameras.AddRange(MapOptions.camerasToAdd); + MapOptions.camerasToAdd.ForEach(camera => { + camera.gameObject.SetActive(true); + camera.gameObject.GetComponent().color = Color.white; + allCameras.Add(camera); + }); ShipStatus.Instance.AllCameras = allCameras.ToArray(); MapOptions.camerasToAdd = new List(); foreach (Vent vent in MapOptions.ventsToSeal) { PowerTools.SpriteAnim animator = vent.GetComponent(); animator?.Stop(); + vent.EnterVentAnim = vent.ExitVentAnim = null; vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + vent.myRend.color = Color.white; vent.name = "SealedVent_" + vent.name; } MapOptions.ventsToSeal = new List(); @@ -535,7 +540,9 @@ namespace TheOtherRoles else if(Spy.spy != null && ExileController.Instance.exiled.Object.PlayerId == Spy.spy.PlayerId) __result = ExileController.Instance.exiled.PlayerName + " was The Spy."; else if(SecurityGuard.securityGuard != null && ExileController.Instance.exiled.Object.PlayerId == SecurityGuard.securityGuard.PlayerId) - __result = ExileController.Instance.exiled.PlayerName + " was The SecurityGuard."; + __result = ExileController.Instance.exiled.PlayerName + " was The Security Guard."; + else if(Arsonist.arsonist != null && ExileController.Instance.exiled.Object.PlayerId == Arsonist.arsonist.PlayerId) + __result = ExileController.Instance.exiled.PlayerName + " was The Arsonist."; else __result = ExileController.Instance.exiled.PlayerName + " was not The Impostor."; } diff --git a/Source Code/ModUpdater.cs b/Source Code/ModUpdater.cs new file mode 100644 index 0000000..102ff0a --- /dev/null +++ b/Source Code/ModUpdater.cs @@ -0,0 +1,191 @@ +extern alias Il2CppNewtonsoft; +using System; +using BepInEx; +using BepInEx.Configuration; +using BepInEx.IL2CPP; +using Il2CppSystem; +using Hazel; +using HarmonyLib; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.Events; +using UnhollowerBaseLib; +using System.IO; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Security.Cryptography; +using Il2CppNewtonsoft::Newtonsoft.Json.Linq; +using Il2CppNewtonsoft::Newtonsoft.Json; +using Twitch; + +namespace TheOtherRoles { + [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))] + public class ModUpdaterButton { + private static void Prefix(MainMenuManager __instance) { + CustomHatLoader.LaunchHatFetcher(); + ModUpdater.LaunchUpdater(); + if (!ModUpdater.hasUpdate) return; + var template = GameObject.Find("ExitGameButton"); + if (template == null) return; + + var button = UnityEngine.Object.Instantiate(template, null); + button.transform.localPosition = new Vector3(button.transform.localPosition.x, button.transform.localPosition.y + 0.6f, button.transform.localPosition.z); + + PassiveButton passiveButton = button.GetComponent(); + passiveButton.OnClick = new Button.ButtonClickedEvent(); + passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick); + + var text = button.transform.GetChild(0).GetComponent(); + __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { + text.SetText("Update\nThe Other Roles"); + }))); + + TwitchManager man = DestroyableSingleton.Instance; + ModUpdater.InfoPopup = UnityEngine.Object.Instantiate(man.TwitchPopup); + + void onClick() { + ModUpdater.ExecuteUpdate(); + button.SetActive(false); + ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f; + ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false; + ModUpdater.InfoPopup.Show("Updating The Other Roles\nPlease wait..."); + __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action((p) => { ModUpdater.setPopupText("Updating The Other Roles\nPlease wait..."); }))); + } + } + } + + public class ModUpdater { + public static bool running = false; + public static bool hasUpdate = false; + public static string updateurl = null; + private static Task updateTask = null; + public static GenericPopup InfoPopup; + + public static void LaunchUpdater() { + if (running) return; + running = true; + checkForUpdate().GetAwaiter().GetResult(); + clearOldVersions(); + } + + public static void ExecuteUpdate() { + if (updateTask == null) { + if (updateurl != null) { + updateTask = downloadUpdate(); + } else { + showPopup("This update has to be done manually"); + } + } + } + + public static void clearOldVersions() { + try { + DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(Application.dataPath) + @"\BepInEx\plugins"); + string[] files = d.GetFiles("*.old").Select(x => x.FullName).ToArray(); // Getting old versions + foreach (string f in files) + File.Delete(f); + } catch (System.Exception e) { + System.Console.WriteLine("Exception occured when clearing old versions:\n" + e); + } + } + + public static async Task checkForUpdate() { + try { + HttpClient http = new HttpClient(); + http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); + var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/Eisbison/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead); + // var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/EoF-1141/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead); + if (response.StatusCode != HttpStatusCode.OK || response.Content == null) { + System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); + return false; + } + string json = await response.Content.ReadAsStringAsync(); + JObject data = JObject.Parse(json); + + string tagname = data["tag_name"]?.ToString(); + if (tagname == null) { + return false; // Something went wrong + } + // check version + System.Version ver = System.Version.Parse(tagname.Replace("v", "")); + int diff = TheOtherRolesPlugin.Version.CompareTo(ver); + if (diff < 0) { // Update required + hasUpdate = true; + JToken assets = data["assets"]; + if (!assets.HasValues) + return false; + + for (JToken current = assets.First; current != null; current = current.Next) { + string browser_download_url = current["browser_download_url"]?.ToString(); + if (browser_download_url != null && current["content_type"] != null) { + if (current["content_type"].ToString().Equals("application/x-msdownload") && + browser_download_url.EndsWith(".dll")) { + updateurl = browser_download_url; + return true; + } + } + } + } + } catch (System.Exception ex) { + TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); + System.Console.WriteLine(ex); + } + return false; + } + + public static async Task downloadUpdate() { + try { + HttpClient http = new HttpClient(); + http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater"); + var response = await http.GetAsync(new System.Uri(updateurl), HttpCompletionOption.ResponseContentRead); + if (response.StatusCode != HttpStatusCode.OK || response.Content == null) { + System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString()); + return false; + } + string codeBase = Assembly.GetExecutingAssembly().CodeBase; + System.UriBuilder uri = new System.UriBuilder(codeBase); + string fullname = System.Uri.UnescapeDataString(uri.Path); + if (File.Exists(fullname + ".old")) // Clear old file in case it wasnt; + File.Delete(fullname + ".old"); + + File.Move(fullname, fullname + ".old"); // rename current executable to old + + using (var responseStream = await response.Content.ReadAsStreamAsync()) { + using (var fileStream = File.Create(fullname)) { // probably want to have proper name here + responseStream.CopyTo(fileStream); + } + } + showPopup("The Other Roles\nupdated successfully\nPlease restart the game."); + return true; + } catch (System.Exception ex) { + TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString()); + System.Console.WriteLine(ex); + } + showPopup("Update wasn't successful\nTry again later,\nor update manually."); + return false; + } + private static void showPopup(string message) { + setPopupText(message); + InfoPopup.gameObject.SetActive(true); + } + + public static void setPopupText(string message) { + if (InfoPopup == null) + return; + if (InfoPopup.TextArea != null) { + InfoPopup.TextArea.Text = message; + } + if (InfoPopup.TextAreaTMP != null) { + InfoPopup.TextAreaTMP.text = message; + } + } + } +} \ No newline at end of file diff --git a/Source Code/PlayerControlPatch.cs b/Source Code/PlayerControlPatch.cs index 9d18c56..f0a736f 100644 --- a/Source Code/PlayerControlPatch.cs +++ b/Source Code/PlayerControlPatch.cs @@ -224,9 +224,9 @@ namespace TheOtherRoles { static void eraserSetTarget() { if (Eraser.eraser == null || Eraser.eraser != PlayerControl.LocalPlayer) return; - List untargatables = new List(); - if (Spy.spy != null) untargatables.Add(Spy.spy); - Eraser.currentTarget = setTarget(onlyCrewmates: !Eraser.canEraseAnyone, untargetablePlayers: Eraser.canEraseAnyone ? new List() : untargatables); + List untargetables = new List(); + if (Spy.spy != null) untargetables.Add(Spy.spy); + Eraser.currentTarget = setTarget(onlyCrewmates: !Eraser.canEraseAnyone, untargetablePlayers: Eraser.canEraseAnyone ? new List() : untargetables); setPlayerOutline(Eraser.currentTarget, Eraser.color); } @@ -338,27 +338,27 @@ namespace TheOtherRoles { } } - public static void updateGhostInfo() { + public static void updatePlayerInfo() { foreach (PlayerControl p in PlayerControl.AllPlayerControls) { if (p != PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) continue; - Transform playerGhostInfoTransform = p.transform.FindChild("GhostInfo"); - TMPro.TextMeshPro playerGhostInfo = playerGhostInfoTransform != null ? playerGhostInfoTransform.GetComponent() : null; - if (playerGhostInfo == null) { - playerGhostInfo = UnityEngine.Object.Instantiate(p.nameText, p.nameText.transform.parent); - playerGhostInfo.transform.localPosition += Vector3.up * 0.25f; - playerGhostInfo.fontSize *= 0.75f; - playerGhostInfo.gameObject.name = "GhostInfo"; + Transform playerInfoTransform = p.transform.FindChild("Info"); + TMPro.TextMeshPro playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent() : null; + if (playerInfo == null) { + playerInfo = UnityEngine.Object.Instantiate(p.nameText, p.nameText.transform.parent); + playerInfo.transform.localPosition += Vector3.up * 0.25f; + playerInfo.fontSize *= 0.75f; + playerInfo.gameObject.name = "Info"; } PlayerVoteArea playerVoteArea = MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == p.PlayerId); - Transform meetingGhostInfoTransform = playerVoteArea != null ? playerVoteArea.transform.FindChild("GhostInfo") : null; - TMPro.TextMeshPro meetingGhostInfo = meetingGhostInfoTransform != null ? meetingGhostInfoTransform.GetComponent() : null; - if (meetingGhostInfo == null && playerVoteArea != null) { - meetingGhostInfo = UnityEngine.Object.Instantiate(playerVoteArea.NameText, playerVoteArea.NameText.transform.parent); - meetingGhostInfo.transform.localPosition += Vector3.down * (MeetingHud.Instance.playerStates.Length > 10 ? 0.4f : 0.25f); - meetingGhostInfo.fontSize *= 0.75f; - meetingGhostInfo.gameObject.name = "GhostInfo"; + Transform meetingInfoTransform = playerVoteArea != null ? playerVoteArea.transform.FindChild("Info") : null; + TMPro.TextMeshPro meetingInfo = meetingInfoTransform != null ? meetingInfoTransform.GetComponent() : null; + if (meetingInfo == null && playerVoteArea != null) { + meetingInfo = UnityEngine.Object.Instantiate(playerVoteArea.NameText, playerVoteArea.NameText.transform.parent); + meetingInfo.transform.localPosition += Vector3.down * (MeetingHud.Instance.playerStates.Length > 10 ? 0.4f : 0.25f); + meetingInfo.fontSize *= 0.75f; + meetingInfo.gameObject.name = "Info"; } var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data); @@ -366,16 +366,22 @@ namespace TheOtherRoles { string taskInfo = tasksTotal > 0 ? $"({tasksCompleted}/{tasksTotal})" : ""; string info = ""; - if (p == PlayerControl.LocalPlayer || (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks)) + if (p == PlayerControl.LocalPlayer) { + info = $"{roleNames}"; + if (DestroyableSingleton.InstanceExists) { + TMPro.TextMeshPro tabText = DestroyableSingleton.Instance.tab.transform.FindChild("TabText_TMP").GetComponent(); + tabText.SetText($"Tasks {taskInfo}"); + } + } else if (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks) info = $"{roleNames} {taskInfo}".Trim(); else if (MapOptions.ghostsSeeTasks) info = $"{taskInfo}".Trim(); else if (MapOptions.ghostsSeeRoles) info = $"{roleNames}"; - playerGhostInfo.text = info; - playerGhostInfo.gameObject.SetActive(p.Visible); - if (meetingGhostInfo != null) meetingGhostInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : info; + playerInfo.text = info; + playerInfo.gameObject.SetActive(p.Visible); + if (meetingInfo != null) meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : info; } } @@ -387,7 +393,7 @@ namespace TheOtherRoles { float closestDistance = float.MaxValue; for (int i = 0; i < ShipStatus.Instance.AllVents.Length; i++) { Vent vent = ShipStatus.Instance.AllVents[i]; - if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || vent.gameObject.name.StartsWith("SealedVent_")) continue; + if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || vent.gameObject.name.StartsWith("SealedVent_") || vent.gameObject.name.StartsWith("FutureSealedVent_")) continue; float distance = Vector2.Distance(vent.transform.position, truePosition); if (distance <= vent.UsableDistance && distance < closestDistance) { closestDistance = distance; @@ -397,6 +403,17 @@ namespace TheOtherRoles { SecurityGuard.ventTarget = target; } + public static void arsonistSetTarget() { + if (Arsonist.arsonist == null || Arsonist.arsonist != PlayerControl.LocalPlayer) return; + List untargetables; + if (Arsonist.douseTarget != null) + untargetables = PlayerControl.AllPlayerControls.ToArray().Where(x => x.PlayerId != Arsonist.douseTarget.PlayerId).ToList(); + else + untargetables = Arsonist.dousedPlayers; + Arsonist.currentTarget = setTarget(untargetablePlayers: untargetables); + if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color); + } + public static void Postfix(PlayerControl __instance) { if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return; @@ -410,8 +427,8 @@ namespace TheOtherRoles { // Update Role Description Helpers.refreshRoleDescription(__instance); - // Update Ghost Info - updateGhostInfo(); + // Update Player Info + updatePlayerInfo(); // Time Master bendTimeUpdate(); @@ -448,6 +465,8 @@ namespace TheOtherRoles { sidekickCheckPromotion(); // SecurityGuard securityGuardSetTarget(); + // Arsonist + arsonistSetTarget(); } } } diff --git a/Source Code/RPC.cs b/Source Code/RPC.cs index 6dcdfcd..f78b562 100644 --- a/Source Code/RPC.cs +++ b/Source Code/RPC.cs @@ -42,7 +42,8 @@ namespace TheOtherRoles Trickster, Cleaner, Warlock, - SecurityGuard + SecurityGuard, + Arsonist } enum CustomRPC @@ -57,7 +58,7 @@ namespace TheOtherRoles VersionHandshake, UseUncheckedVent, UncheckedMurderPlayer, - + OpenToiletDoor, // Role functionality EngineerFixLights = 81, @@ -88,7 +89,8 @@ namespace TheOtherRoles LightsOut, WarlockCurseKill, PlaceCamera, - SealVent + SealVent, + ArsonistWin } public static class RPCProcedure { @@ -218,6 +220,9 @@ namespace TheOtherRoles case RoleId.SecurityGuard: SecurityGuard.securityGuard = player; break; + case RoleId.Arsonist: + Arsonist.arsonist = player; + break; } } } @@ -227,8 +232,8 @@ namespace TheOtherRoles if (player != null) player.SetColor(colorId); } - public static void versionHandshake(byte major, byte minor, byte patch, int clientId) { - GameStartManagerPatch.playerVersions[clientId] = new Tuple(major, minor, patch); + public static void versionHandshake(int major, int minor, int build, int clientId) { + GameStartManagerPatch.playerVersions[clientId] = new System.Version(major, minor, build); } public static void useUncheckedVent(int ventId, byte playerId, byte isEnter) { @@ -345,7 +350,7 @@ namespace TheOtherRoles Shifter.clearAndReload(); // Suicide (exile) when impostor or impostor variants - if (player.Data.IsImpostor || player == Jackal.jackal || player == Sidekick.sidekick) { + if (player.Data.IsImpostor || player == Jackal.jackal || player == Sidekick.sidekick || Jackal.formerJackals.Contains(player) || player == Jester.jester || player == Arsonist.arsonist) { oldShifter.Exiled(); return; } @@ -394,6 +399,8 @@ namespace TheOtherRoles Spy.spy = oldShifter; } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player) { SecurityGuard.securityGuard = oldShifter; + } else if (Arsonist.arsonist != null && Arsonist.arsonist == player) { + Arsonist.arsonist = oldShifter; } else { // Crewmate } @@ -553,6 +560,7 @@ namespace TheOtherRoles // Other roles if (player == Jester.jester) Jester.clearAndReload(); + if (player == Arsonist.arsonist) Arsonist.clearAndReload(); if (player == Lovers.lover1 || player == Lovers.lover2) { // The whole Lover couple is being erased Lovers.clearAndReload(); } @@ -617,7 +625,13 @@ namespace TheOtherRoles camera.CamName = $"Security Camera {SecurityGuard.placedCameras}"; camera.Offset = new Vector3(0f, 0f, camera.Offset.z); if (PlayerControl.GameOptions.MapId == 2 || PlayerControl.GameOptions.MapId == 4) camera.transform.localRotation = new Quaternion(0, 0, 1, 1); // Polus and Airship - camera.gameObject.SetActive(false); + + if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) { + camera.gameObject.SetActive(true); + camera.gameObject.GetComponent().color = new Color(1f, 1f, 1f, 0.5f); + } else { + camera.gameObject.SetActive(false); + } MapOptions.camerasToAdd.Add(camera); } @@ -626,8 +640,21 @@ namespace TheOtherRoles if (vent == null) return; SecurityGuard.remainingScrews -= SecurityGuard.ventPrice; + if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) { + PowerTools.SpriteAnim animator = vent.GetComponent(); + animator?.Stop(); + vent.EnterVentAnim = vent.ExitVentAnim = null; + vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite(); + vent.myRend.color = new Color(1f, 1f, 1f, 0.5f); + vent.name = "FutureSealedVent_" + vent.name; + } + MapOptions.ventsToSeal.Add(vent); } + + public static void arsonistWin() { + Arsonist.triggerArsonistWin = true; + } } [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))] @@ -680,6 +707,14 @@ namespace TheOtherRoles RPCProcedure.uncheckedMurderPlayer(source, target); break; + // Fixes + + case (byte)CustomRPC.OpenToiletDoor: + int doorId = reader.ReadInt32(); + PlainDoor door = UnityEngine.Object.FindObjectsOfType().FirstOrDefault(door => door.Id == doorId); + door.SetDoorway(true); + break; + // Role functionality case (byte)CustomRPC.EngineerFixLights: @@ -773,6 +808,9 @@ namespace TheOtherRoles case (byte)CustomRPC.SealVent: RPCProcedure.sealVent(reader.ReadPackedInt32()); break; + case (byte)CustomRPC.ArsonistWin: + RPCProcedure.arsonistWin(); + break; } } } diff --git a/Source Code/Resources/AdminTableIcon.png b/Source Code/Resources/AdminTableIcon.png deleted file mode 100644 index a1b2ee4..0000000 Binary files a/Source Code/Resources/AdminTableIcon.png and /dev/null differ diff --git a/Source Code/Resources/DouseButton.png b/Source Code/Resources/DouseButton.png new file mode 100644 index 0000000..3d894b5 Binary files /dev/null and b/Source Code/Resources/DouseButton.png differ diff --git a/Source Code/Resources/IgniteButton.png b/Source Code/Resources/IgniteButton.png new file mode 100644 index 0000000..7426095 Binary files /dev/null and b/Source Code/Resources/IgniteButton.png differ diff --git a/Source Code/RoleAssignmentPatch.cs b/Source Code/RoleAssignmentPatch.cs index 146a237..ef7ec1d 100644 --- a/Source Code/RoleAssignmentPatch.cs +++ b/Source Code/RoleAssignmentPatch.cs @@ -41,15 +41,19 @@ namespace TheOtherRoles impostors.RemoveAll(x => !x.Data.IsImpostor); float crewCountSettings = (float)CustomOptionHolder.crewmateRolesCount.getSelection() / 2; + float neutralCountSettings = (float)CustomOptionHolder.neutralRolesCount.getSelection() / 2; float impCountSettings = (float)CustomOptionHolder.impostorRolesCount.getSelection() / 2; if (crewCountSettings % 1 == 0.5f) crewCountSettings += 0.5f * (rnd.Next(2) * 2 - 1); + if (neutralCountSettings % 1 == 0.5f) neutralCountSettings += 0.5f * (rnd.Next(2) * 2 - 1); if (impCountSettings % 1 == 0.5f) impCountSettings += 0.5f * (rnd.Next(2) * 2 - 1); int maxCrewmateRoles = Mathf.Min(crewmates.Count, Mathf.RoundToInt(crewCountSettings)); + int maxNeutralRoles = Mathf.Min(crewmates.Count, Mathf.RoundToInt(neutralCountSettings)); int maxImpostorRoles = Mathf.Min(impostors.Count, Mathf.RoundToInt(impCountSettings)); Dictionary impSettings = new Dictionary(); + Dictionary neutralSettings = new Dictionary(); Dictionary crewSettings = new Dictionary(); impSettings.Add((byte)RoleId.Morphling, CustomOptionHolder.morphlingSpawnRate.getSelection()); @@ -60,7 +64,10 @@ namespace TheOtherRoles impSettings.Add((byte)RoleId.Cleaner, CustomOptionHolder.cleanerSpawnRate.getSelection()); impSettings.Add((byte)RoleId.Warlock, CustomOptionHolder.warlockSpawnRate.getSelection()); - crewSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection()); + neutralSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection()); + neutralSettings.Add((byte)RoleId.Arsonist, CustomOptionHolder.arsonistSpawnRate.getSelection()); + neutralSettings.Add((byte)RoleId.Jackal, CustomOptionHolder.jackalSpawnRate.getSelection()); + crewSettings.Add((byte)RoleId.Mayor, CustomOptionHolder.mayorSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Engineer, CustomOptionHolder.engineerSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection()); @@ -75,7 +82,8 @@ namespace TheOtherRoles crewSettings.Add((byte)RoleId.Tracker, CustomOptionHolder.trackerSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Snitch, CustomOptionHolder.snitchSpawnRate.getSelection()); crewSettings.Add((byte)RoleId.Jackal, CustomOptionHolder.jackalSpawnRate.getSelection()); - crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection()); + if (impostors.Count > 1) // Spy is useless with less than 2 Impostors + crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection()); crewSettings.Add((byte)RoleId.SecurityGuard, CustomOptionHolder.securityGuardSpawnRate.getSelection()); // Set special roles @@ -112,8 +120,29 @@ namespace TheOtherRoles // Set tickets and always active roles List crewTickets = new List(); + List neutralTickets = new List(); List impTickets = new List(); + for (int i = 0; i < neutralSettings.Count; i++) { + var entry = neutralSettings.ElementAt(i); + if (entry.Value == 0) { // Never + } else if (entry.Value == 10) { // Always + if (crewmates.Count > 0 && maxNeutralRoles > 0) { + setRoleToRandomPlayer(entry.Key, crewmates); + maxNeutralRoles--; + if (CustomOptionHolder.blockedRolePairings.ContainsKey(entry.Key)) { + foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[entry.Key]) { + if(impSettings.ContainsKey(blockedRoleId)) impSettings[blockedRoleId] = 0; + if (neutralSettings.ContainsKey(blockedRoleId)) neutralSettings[blockedRoleId] = 0; + if(crewSettings.ContainsKey(blockedRoleId)) crewSettings[blockedRoleId] = 0; + } + } + } + } else { // Other + for (int j = 0; j < entry.Value; j++) crewTickets.Add(entry.Key); + } + } + for (int i = 0; i < crewSettings.Count; i++) { var entry = crewSettings.ElementAt(i); if (entry.Value == 0) { // Never @@ -123,12 +152,9 @@ namespace TheOtherRoles maxCrewmateRoles--; if(CustomOptionHolder.blockedRolePairings.ContainsKey(entry.Key)) { foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[entry.Key]) { - if(impSettings.ContainsKey(blockedRoleId)) { - impSettings[blockedRoleId] = 0; // Set Chance to Never for that role - } - if(crewSettings.ContainsKey(blockedRoleId)) { - crewSettings[blockedRoleId] = 0; // Set Chance to Never for that role - } + if (impSettings.ContainsKey(blockedRoleId)) impSettings[blockedRoleId] = 0; + if (neutralSettings.ContainsKey(blockedRoleId)) neutralSettings[blockedRoleId] = 0; + if (crewSettings.ContainsKey(blockedRoleId)) crewSettings[blockedRoleId] = 0; } } } @@ -145,12 +171,9 @@ namespace TheOtherRoles setRoleToRandomPlayer(entry.Key, impostors); if(CustomOptionHolder.blockedRolePairings.ContainsKey(entry.Key)) { foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[entry.Key]) { - if(impSettings.ContainsKey(blockedRoleId)) { - impSettings[blockedRoleId] = 0; // Set Chance to Never for that role - } - if(crewSettings.ContainsKey(blockedRoleId)) { - crewSettings[blockedRoleId] = 0; // Set Chance to Never for that role - } + if (impSettings.ContainsKey(blockedRoleId)) impSettings[blockedRoleId] = 0; + if (neutralSettings.ContainsKey(blockedRoleId)) neutralSettings[blockedRoleId] = 0; + if (crewSettings.ContainsKey(blockedRoleId)) crewSettings[blockedRoleId] = 0; } } maxImpostorRoles--; @@ -162,6 +185,23 @@ namespace TheOtherRoles // Set solo player roles + for (int i = 0; i < maxNeutralRoles; i++) { + if (neutralTickets.Count > 0 && crewmates.Count > 0) { + var index = rnd.Next(0, neutralTickets.Count); + byte roleId = neutralTickets[index]; + neutralTickets.RemoveAll(x => x == roleId); + setRoleToRandomPlayer(roleId, crewmates); + + if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { + foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { + crewTickets.RemoveAll(x => x == blockedRoleId); + neutralTickets.RemoveAll(x => x == blockedRoleId); + impTickets.RemoveAll(x => x == blockedRoleId); + } + } + } + } + for (int i = 0; i < maxCrewmateRoles; i++) { if (crewTickets.Count > 0 && crewmates.Count > 0) { var index = rnd.Next(0, crewTickets.Count); @@ -172,6 +212,7 @@ namespace TheOtherRoles if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { crewTickets.RemoveAll(x => x == blockedRoleId); + neutralTickets.RemoveAll(x => x == blockedRoleId); impTickets.RemoveAll(x => x == blockedRoleId); } } @@ -188,6 +229,7 @@ namespace TheOtherRoles if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) { foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) { crewTickets.RemoveAll(x => x == blockedRoleId); + neutralTickets.RemoveAll(x => x == blockedRoleId); impTickets.RemoveAll(x => x == blockedRoleId); } } diff --git a/Source Code/RoleInfo.cs b/Source Code/RoleInfo.cs index fec354d..b7b6760 100644 --- a/Source Code/RoleInfo.cs +++ b/Source Code/RoleInfo.cs @@ -1,4 +1,5 @@ using HarmonyLib; +using System.Linq; using System; using System.Collections.Generic; using static TheOtherRoles.TheOtherRoles; @@ -173,7 +174,7 @@ namespace TheOtherRoles "Finish your tasks to find the Impostors", "Finish your tasks")); } - if ((Jackal.jackal != null && p == Jackal.jackal) || (Jackal.formerJackals != null && Jackal.formerJackals.Contains(p))) { + if ((Jackal.jackal != null && p == Jackal.jackal) || (Jackal.formerJackals != null && Jackal.formerJackals.Any(x => x.PlayerId == p.PlayerId))) { infos.Add(new RoleInfo("Jackal", Jackal.color, "Kill all Crewmates and Impostors to win", @@ -203,6 +204,12 @@ namespace TheOtherRoles "Seal vents and place cameras", "Seal vents and place cameras")); } + if (Arsonist.arsonist != null && p == Arsonist.arsonist) { + infos.Add(new RoleInfo("Arsonist", + Arsonist.color, + "Let them burn", + "Let them burn")); + } if (infos.Count == 0 && p.Data.IsImpostor) { // Just Impostor infos.Add(new RoleInfo("Impostor", diff --git a/Source Code/TheOtherRoles.cs b/Source Code/TheOtherRoles.cs index 28519b9..644b85b 100644 --- a/Source Code/TheOtherRoles.cs +++ b/Source Code/TheOtherRoles.cs @@ -49,6 +49,7 @@ namespace TheOtherRoles Cleaner.clearAndReload(); Warlock.clearAndReload(); SecurityGuard.clearAndReload(); + Arsonist.clearAndReload(); } public static class Jester { @@ -135,7 +136,7 @@ namespace TheOtherRoles public static Color color = new Color(255f / 255f, 204f / 255f, 0f / 255f, 1); public static float cooldown = 30f; - public static bool jesterCanDieToSheriff = false; + public static bool canKillNeutrals = false; public static bool spyCanDieToSheriff = false; public static PlayerControl currentTarget; @@ -144,7 +145,7 @@ namespace TheOtherRoles sheriff = null; currentTarget = null; cooldown = CustomOptionHolder.sheriffCooldown.getFloat(); - jesterCanDieToSheriff = CustomOptionHolder.jesterCanDieToSheriff.getBool(); + canKillNeutrals = CustomOptionHolder.sheriffCanKillNeutrals.getBool(); spyCanDieToSheriff = CustomOptionHolder.spyCanDieToSheriff.getBool(); } } @@ -456,21 +457,13 @@ namespace TheOtherRoles public static class Hacker { public static PlayerControl hacker; - private static Sprite adminTableIcon; public static Color color = new Color(252f / 255f, 90f / 255f, 30f / 255f, 1); public static float cooldown = 30f; public static float duration = 10f; public static bool onlyColorType = false; - public static float hackerTimer = 0f; - public static Sprite getAdminTableIconSprite() { - if (adminTableIcon) return adminTableIcon; - adminTableIcon = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AdminTableIcon.png", 350f); - return adminTableIcon; - } - private static Sprite buttonSprite; public static Sprite getButtonSprite() { if (buttonSprite) return buttonSprite; @@ -631,7 +624,7 @@ namespace TheOtherRoles } public static void removeCurrentJackal() { - if (!formerJackals.Contains(jackal)) formerJackals.Add(jackal); + if (!formerJackals.Any(x => x.PlayerId == jackal.PlayerId)) formerJackals.Add(jackal); jackal = null; currentTarget = null; fakeSidekick = null; @@ -850,14 +843,14 @@ namespace TheOtherRoles private static Sprite animatedVentSealedSprite; public static Sprite getAnimatedVentSealedSprite() { if (animatedVentSealedSprite) return animatedVentSealedSprite; - animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", 160f); // Change sprite and pixelPerUnit + animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", 160f); return animatedVentSealedSprite; } private static Sprite staticVentSealedSprite; public static Sprite getStaticVentSealedSprite() { if (staticVentSealedSprite) return staticVentSealedSprite; - staticVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StaticVentSealed.png", 160f); // Change sprite and pixelPerUnit + staticVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StaticVentSealed.png", 160f); return staticVentSealedSprite; } @@ -871,4 +864,52 @@ namespace TheOtherRoles ventPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardVentPrice.getFloat()); } } + + public static class Arsonist { + public static PlayerControl arsonist; + public static Color color = new Color(1, 200f/255f, 0, 1f); + + public static float cooldown = 30f; + public static float duration = 3f; + public static bool triggerArsonistWin = false; + + public static PlayerControl currentTarget; + public static PlayerControl douseTarget; + public static List dousedPlayers = new List(); + public static Dictionary dousedIcons = new Dictionary(); + + private static Sprite douseSprite; + public static Sprite getDouseSprite() { + if (douseSprite) return douseSprite; + douseSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DouseButton.png", 115f); + return douseSprite; + } + + private static Sprite igniteSprite; + public static Sprite getIgniteSprite() { + if (igniteSprite) return igniteSprite; + igniteSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.IgniteButton.png", 115f); + return igniteSprite; + } + + public static bool dousedEveryoneAlive() { + return PlayerControl.AllPlayerControls.ToArray().All(x => { return x == Arsonist.arsonist || x.Data.IsDead || Arsonist.dousedPlayers.Any(y => y.PlayerId == x.PlayerId); }); + } + + public static void clearAndReload() { + arsonist = null; + currentTarget = null; + douseTarget = null; + triggerArsonistWin = false; + dousedPlayers = new List(); + foreach (PoolablePlayer p in dousedIcons.Values) { + if (p != null && p.gameObject != null) { + UnityEngine.Object.Destroy(p.gameObject); + } + } + dousedIcons = new Dictionary(); + cooldown = CustomOptionHolder.arsonistCooldown.getFloat(); + duration = CustomOptionHolder.arsonistDuration.getFloat(); + } + } } diff --git a/Source Code/TheOtherRoles.csproj b/Source Code/TheOtherRoles.csproj index 4aa7a90..30d409d 100644 --- a/Source Code/TheOtherRoles.csproj +++ b/Source Code/TheOtherRoles.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 2.5.1 + 2.6.0 TheOtherRoles Eisbison @@ -33,4 +33,7 @@ Il2CppNewtonsoft + + $(NoWarn);MSB3243 + \ No newline at end of file diff --git a/Source Code/UpdatePatch.cs b/Source Code/UpdatePatch.cs index 739b187..6bdb8ec 100644 --- a/Source Code/UpdatePatch.cs +++ b/Source Code/UpdatePatch.cs @@ -116,6 +116,8 @@ namespace TheOtherRoles setPlayerNameColor(Spy.spy, Spy.color); } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer) { setPlayerNameColor(SecurityGuard.securityGuard, SecurityGuard.color); + } else if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) { + setPlayerNameColor(Arsonist.arsonist, Arsonist.color); } // No else if here, as a Lover of team Jackal needs the colors diff --git a/Source Code/UsablesPatch.cs b/Source Code/UsablesPatch.cs index 207f7c3..9811555 100644 --- a/Source Code/UsablesPatch.cs +++ b/Source Code/UsablesPatch.cs @@ -348,8 +348,8 @@ namespace TheOtherRoles [HarmonyPatch(typeof(CounterArea), nameof(CounterArea.UpdateCount))] class CounterAreaUpdateCountPatch { - private static Sprite defaultIcon; - + private static Material defaultMat; + private static Material newMat; static void Postfix(CounterArea __instance) { // Hacker display saved colors on the admin panel bool showHackerInfo = Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0; @@ -361,15 +361,23 @@ namespace TheOtherRoles SpriteRenderer renderer = icon.GetComponent(); if (renderer != null) { - if (defaultIcon == null) defaultIcon = renderer.sprite; - if (showHackerInfo && colors.Count > i && Hacker.getAdminTableIconSprite() != null) { - renderer.sprite = Hacker.getAdminTableIconSprite(); - renderer.color = colors[i]; + if (defaultMat == null) defaultMat = renderer.material; + if (newMat == null) newMat = UnityEngine.Object.Instantiate(defaultMat); + if (showHackerInfo && colors.Count > i) { + renderer.material = newMat; + var color = colors[i]; + renderer.material.SetColor("_BodyColor", color); + var id = Palette.PlayerColors.IndexOf(color); + if (id < 0) { + renderer.material.SetColor("_BackColor", color); + } else { + renderer.material.SetColor("_BackColor", Palette.ShadowColors[id]); + } + renderer.material.SetColor("_VisorColor", Palette.VisorColor); } else { - renderer.sprite = defaultIcon; - renderer.color = Color.white; + renderer.material = defaultMat; } - } + } } } }