[](https://discord.gg/77RkMJHWsM)\r
\r
-\r
+\r
\r
\r
# The Other Roles\r
\r
| Impostors | Crewmates | Neutral |\r
|----------|-------------|-----------------|\r
-| [Bad Child](#child) | [Child](#child) | [Jester](#jester) |\r
-| [Camouflager](#camouflager) | [Detective](#detective) | [Jackal](#jackal) |\r
-| [Cleaner](#cleaner) | [Engineer](#engineer) | [Sidekick](#sidekick) |\r
-| [Eraser](#eraser) | [Hacker](#hacker) | |\r
+| [Bad Child](#child) | [Child](#child) | [Arsonist](#arsonist) |\r
+| [Camouflager](#camouflager) | [Detective](#detective) | [Jester](#jester) |\r
+| [Cleaner](#cleaner) | [Engineer](#engineer) | [Jackal](#jackal) |\r
+| [Eraser](#eraser) | [Hacker](#hacker) | [Sidekick](#sidekick) |\r
| [ImpLover](#lovers) | [Lover](#lovers) | |\r
| [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | |\r
| [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | |\r
# Releases\r
| Among Us - Version| Mod Version | Link |\r
|----------|-------------|-----------------|\r
+| 2021.4.14s| v2.6.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.0/TheOtherRoles.zip)\r
| 2021.4.14s| v2.5.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.1/TheOtherRoles.zip)\r
| 2021.4.14s| v2.5.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.5.0/TheOtherRoles.zip)\r
| 2021.4.14s| v2.4.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.4.0/TheOtherRoles.zip)\r
<details>\r
<summary>Click to show the Changelog</summary>\r
\r
+**Version 2.6.0**\r
+- **New Role:** [Arsonist](#arsonist)\r
+- Added an Ingame Updater, to make it easier to update the Mod\r
+- Added synchronization for Airship toilet doors. Doors now open/close for everyone\r
+- Changed Shifter to also die when shifting a neutral role (Jester, Arsonist, Jackal, ...)\r
+- Changed the option "Jester Can Die To Sheriff" to "Neutrals Can Die To Sheriff"\r
+- Changed the role assignment system. You can now set how many neutral roles you want in your game\r
+- Changed Hacker to see colors more clearly on Admin Table\r
+- Changed version handshake to give more clear info\r
+- Fixed a problem with the Hat Tab leaving too much space between categories\r
+- Fixed an Among Us bug, which made the selected region always show "North America"\r
+- Fixed an Among Us bug, which made the disconnect info be off-screen. (hopefully)\r
+\r
**Version 2.5.1**\r
- **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.\r
- Changed Lovers to ignore Lover's Tasks for Taskwin, while an ImpLover is alive\r
[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.\\r
[TownOfUs](https://github.com/slushiegoose/Town-Of-Us) - Idea for the Swapper, Shifter and a similar Mayor role come from **Slushiegoose**\\r
[Ottomated](https://twitter.com/ottomated_) - Idea for the Morphling, Snitch and Camouflager role come from **Ottomated**\\r
-[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies is inspired by the one from the **Crowded Mod Team**\r
+[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies is inspired by the one from the **Crowded Mod Team**\\r
+[Town-Of-Impostors](https://github.com/AJMix/TownOfImpostors) - Idea for the Arsonist role comes from **AJMix**\r
\r
# Settings\r
The mod adds a few new settings to Among Us (in addition to the role settings):\r
We are still improving the role assignment system. It's not that intuitive right now, but it's more flexible than the older one\r
if you're using it right.\r
\r
-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).\r
+First you need to choose how many special roles of each kind (Impostor/Neutral/Crewmate) you want in the game.\r
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:\r
- First all roles that are set to 100% are being assigned to arbitrary players\r
-- 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.\r
+- 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.\r
+- 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.\r
\r
**Example:**\\r
Settings: 2 special Crewmate roles, Snitch: 100%, Hacker: 10%, Tracker: 30%\\r
|----------|:-------------:|\r
| Sheriff Spawn Chance | -\r
| Sheriff Cooldown | -\r
-| Jester Can Die To Sheriff | -\r
+| Neutrals Can Die To Sheriff | -\r
-----------------------\r
\r
## Jester\r
| Jester can call emergency meeting | Option to disable the emergency button for the Jester\r
-----------------------\r
\r
+## Arsonist\r
+### **Team: Neutral**\r
+The Arsonist does not have any tasks, he has to win the game as a solo.\\r
+The Arsonist can douse other players by pressing the douse button and remaining next to the player for a few seconds.\\r
+If the player that the Arsonist douses walks out of range, the cooldown will reset to 0.\\r
+After dousing everyone alive the Arsonist can ignite all the players which results in an Arsonist win.\r
+\r
+### Game Options\r
+| Name | Description |\r
+|----------|:-------------:|\r
+| Arsonist Spawn Chance | -\r
+| Arsonist Countdown | -\r
+| Arsonist Douse Duration | The time it takes to douse a player\r
+-----------------------\r
+\r
## Seer\r
### **Team: Crewmates**\r
The Seer has two abilities (one can activate one of them or both in the options).\r
\r
## Shifter\r
### **Team: Crewmates**\r
-The Shifter can take over the role of another Crewmate or Neutral, the other player will transform into a Crewmate.\\r
+The Shifter can take over the role of another Crewmate, the other player will transform into a Crewmate.\\r
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.\\r
Even if the Shifter or the target dies before the meeting, the Shift will still be performed.\\r
-Swapping roles with a Crewmate or Neutral gives them their role, the other player becomes a Crewmate.\\r
-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).\\r
+Swapping roles with an Impostor or Neutral fails and the Shifter commits suicide after the next meeting (there won't be any body).\\r
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.\\r
This works especially well against the Eraser, but also gives the Eraser the possiblity to act like a Shifter.\\r
The **special interactions** with the Shifter are noted in the chapters of the respective roles.\\r
The priority of the win conditions is the following:\r
1. Crewmate Child lose by vote\r
2. Jester wins by vote\r
-3. Team Impostor wins by sabotage\r
-4. Team Crew wins by tasks (also possible if the whole Crew is dead)\r
-5. Lovers among the last three players win\r
-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)\r
-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)\r
-8. Team Crew wins by outnumbering (When there is no player of the team Jackal and the team Impostrs left)\r
+3. Arsonist win\r
+4. Team Impostor wins by sabotage\r
+5. Team Crew wins by tasks (also possible if the whole Crew is dead)\r
+6. Lovers among the last three players win\r
+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)\r
+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)\r
+9. Team Crew wins by outnumbering (When there is no player of the team Jackal and the team Impostrs left)\r
\r
**NOTE:**\r
- The Jackal (and his Sidekick) may be killed by a Sheriff.\r
## Security Guard\r
### **Team: Crewmates**\r
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.\\r
-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.\\r
+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.\\r
The new camera will be visible after the next meeting and accessible by everyone.\\r
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.\\r
**NOTE:**\r
--- /dev/null
+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<Transform>().parent;
+ if (parent == null) return; // Local
+ var tmps = parent.GetComponentsInChildren<TextMeshPro>();
+ foreach (var tmp in tmps) {
+ if (tmp.name == "RegionText_TMP") {
+ var region = DestroyableSingleton<ServerManager>.Instance.CurrentRegion;
+ var name = DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(region.TranslateName, region.Name, Array.Empty<Il2CppSystem.Object>());
+ 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
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() {
cleanerCleanButton.MaxTimer = Cleaner.cooldown;
warlockCurseButton.MaxTimer = Warlock.cooldown;
securityGuardButton.MaxTimer = SecurityGuard.cooldown;
+ arsonistButton.MaxTimer = Arsonist.cooldown;
timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
hackerButton.EffectDuration = Hacker.duration;
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;
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 {
KeyCode.F
);
- // SecurityGuard button
+ // Security Guard button
securityGuardButton = new CustomButton(
() => {
if (SecurityGuard.ventTarget != null) { // Seal vent
}
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}";
__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();
}
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) {
[HarmonyPatch]
public static class CredentialsPatch {
public static string fullCredentials =
-$@"<color=#FCCE03FF>TheOtherRoles</color> v{TheOtherRolesPlugin.Major}.{TheOtherRolesPlugin.Minor}.{TheOtherRolesPlugin.Patch}:
+$@"<color=#FCCE03FF>TheOtherRoles</color> v{TheOtherRolesPlugin.Version.ToString()}:
- Modded by <color=#FCCE03FF>Eisbison</color>,
<color=#FFEB91FF>Thunderstorm584</color> and <color=#FFEB91FF>EndOfFile</color>
- Balanced with <color=#FFEB91FF>Dhalucard</color>
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) {
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<ColorChip>(__instance.ColorTabPrefab, __instance.scroller.Inner);
if (ext != null) {
Transform background = colorChip.transform.FindChild("Background");
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) {
textTemplate = hatButton.transform.FindChild("ButtonText_TMP").GetComponent<TMPro.TMP_Text>();
}
- 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<System.Tuple<HatBehaviour, HatExtension>> 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;
}
}
}
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<CustomHatOnline> hatdetails = new List<CustomHatOnline>();
-
- 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;
return res;
}
- public async Task<HttpStatusCode> 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<CustomHatOnline> hatdatas = new List<CustomHatOnline>();
-
- 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<string> markedfordownload = new List<string>();
-
- 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<HttpStatusCode> 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<CustomHatOnline> hatdatas = new List<CustomHatOnline>();
+
+ 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<string> markedfordownload = new List<string>();
+
+ 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;
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;
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;
public static CustomOption sheriffSpawnRate;
public static CustomOption sheriffCooldown;
- public static CustomOption jesterCanDieToSheriff;
+ public static CustomOption sheriffCanKillNeutrals;
public static CustomOption lighterSpawnRate;
public static CustomOption lighterModeLightsOnVision;
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;
// 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);
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);
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);
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);
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();
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) {
LoversWin = 10,
TeamJackalWin = 11,
ChildLose = 12,
- JesterWin = 13
+ JesterWin = 13,
+ ArsonistWin = 14
}
enum WinCondition {
LoversSoloWin,
JesterWin,
JackalWin,
- ChildLose
+ ChildLose,
+ ArsonistWin
}
static class AdditionalTempData {
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<WinningPlayerData> winnersToRemove = new List<WinningPlayerData>();
- 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<PlayerControl> notWinners = new List<PlayerControl>();
+ 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<WinningPlayerData> winnersToRemove = new List<WinningPlayerData>();
+ 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));
AdditionalTempData.winCondition = WinCondition.JesterWin;
}
+ // Arsonist win
+ else if (arsonistWin) {
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ 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);
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));
}
}
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";
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;
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;
namespace TheOtherRoles {
public class GameStartManagerPatch {
- public static Dictionary<int, Tuple<byte, byte, byte>> playerVersions = new Dictionary<int, Tuple<byte, byte, byte>>();
+ public static Dictionary<int, System.Version> playerVersions = new Dictionary<int, System.Version>();
private static float timer = 600f;
private static bool versionSent = false;
private static string lobbyCodeText = "";
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;
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;
else if (!playerVersions.ContainsKey(client.Id)) {
blockStart = true;
message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a different or no version of The Other Roles\n</color>";
- } else if (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch) {
- blockStart = true;
- message += $"<color=#FF0000FF>{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</color>";
+ } else {
+ int diff = TheOtherRolesPlugin.Version.CompareTo(playerVersions[client.Id]);
+ if (diff > 0) {
+ message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has an older version of The Other Roles (v{playerVersions[client.Id].ToString()})\n</color>";
+ blockStart = true;
+ } else if (diff > 0) {
+ message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a newer version of The Other Roles (v{playerVersions[client.Id].ToString()}) \n</color>";
+ blockStart = true;
+ }
}
}
if (blockStart) {
}
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) {
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<PoolablePlayer>(__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<HatManager>.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<PlayerControl>();
- jesterTeam.Add(PlayerControl.LocalPlayer);
- __instance.yourTeam = jesterTeam;
- } else if (PlayerControl.LocalPlayer == Jackal.jackal) {
- var jackalTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
- 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<PlayerControl>();
+ soloTeam.Add(PlayerControl.LocalPlayer);
+ __instance.yourTeam = soloTeam;
}
// Add the Spy to the Impostor team (for the Impostors)
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;
}
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);
}
// 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<SpriteRenderer>().color = Color.white;
+ allCameras.Add(camera);
+ });
ShipStatus.Instance.AllCameras = allCameras.ToArray();
MapOptions.camerasToAdd = new List<SurvCamera>();
foreach (Vent vent in MapOptions.ventsToSeal) {
PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
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<Vent>();
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.";
}
--- /dev/null
+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>();
+ passiveButton.OnClick = new Button.ButtonClickedEvent();
+ passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick);
+
+ var text = button.transform.GetChild(0).GetComponent<TMPro.TMP_Text>();
+ __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => {
+ text.SetText("Update\nThe Other Roles");
+ })));
+
+ TwitchManager man = DestroyableSingleton<TwitchManager>.Instance;
+ ModUpdater.InfoPopup = UnityEngine.Object.Instantiate<GenericPopup>(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<float>((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<bool> 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<bool> 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
static void eraserSetTarget() {
if (Eraser.eraser == null || Eraser.eraser != PlayerControl.LocalPlayer) return;
- List<PlayerControl> untargatables = new List<PlayerControl>();
- if (Spy.spy != null) untargatables.Add(Spy.spy);
- Eraser.currentTarget = setTarget(onlyCrewmates: !Eraser.canEraseAnyone, untargetablePlayers: Eraser.canEraseAnyone ? new List<PlayerControl>() : untargatables);
+ List<PlayerControl> untargetables = new List<PlayerControl>();
+ if (Spy.spy != null) untargetables.Add(Spy.spy);
+ Eraser.currentTarget = setTarget(onlyCrewmates: !Eraser.canEraseAnyone, untargetablePlayers: Eraser.canEraseAnyone ? new List<PlayerControl>() : untargetables);
setPlayerOutline(Eraser.currentTarget, Eraser.color);
}
}
}
- 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<TMPro.TextMeshPro>() : 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<TMPro.TextMeshPro>() : 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<TMPro.TextMeshPro>() : 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<TMPro.TextMeshPro>() : 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);
string taskInfo = tasksTotal > 0 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
string info = "";
- if (p == PlayerControl.LocalPlayer || (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks))
+ if (p == PlayerControl.LocalPlayer) {
+ info = $"{roleNames}";
+ if (DestroyableSingleton<TaskPanelBehaviour>.InstanceExists) {
+ TMPro.TextMeshPro tabText = DestroyableSingleton<TaskPanelBehaviour>.Instance.tab.transform.FindChild("TabText_TMP").GetComponent<TMPro.TextMeshPro>();
+ 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;
}
}
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;
SecurityGuard.ventTarget = target;
}
+ public static void arsonistSetTarget() {
+ if (Arsonist.arsonist == null || Arsonist.arsonist != PlayerControl.LocalPlayer) return;
+ List<PlayerControl> 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;
// Update Role Description
Helpers.refreshRoleDescription(__instance);
- // Update Ghost Info
- updateGhostInfo();
+ // Update Player Info
+ updatePlayerInfo();
// Time Master
bendTimeUpdate();
sidekickCheckPromotion();
// SecurityGuard
securityGuardSetTarget();
+ // Arsonist
+ arsonistSetTarget();
}
}
}
Trickster,
Cleaner,
Warlock,
- SecurityGuard
+ SecurityGuard,
+ Arsonist
}
enum CustomRPC
VersionHandshake,
UseUncheckedVent,
UncheckedMurderPlayer,
-
+ OpenToiletDoor,
// Role functionality
EngineerFixLights = 81,
LightsOut,
WarlockCurseKill,
PlaceCamera,
- SealVent
+ SealVent,
+ ArsonistWin
}
public static class RPCProcedure {
case RoleId.SecurityGuard:
SecurityGuard.securityGuard = player;
break;
+ case RoleId.Arsonist:
+ Arsonist.arsonist = player;
+ break;
}
}
}
if (player != null) player.SetColor(colorId);
}
- public static void versionHandshake(byte major, byte minor, byte patch, int clientId) {
- GameStartManagerPatch.playerVersions[clientId] = new Tuple<byte, byte, byte>(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) {
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;
}
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
}
// 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();
}
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<SpriteRenderer>().color = new Color(1f, 1f, 1f, 0.5f);
+ } else {
+ camera.gameObject.SetActive(false);
+ }
MapOptions.camerasToAdd.Add(camera);
}
if (vent == null) return;
SecurityGuard.remainingScrews -= SecurityGuard.ventPrice;
+ if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) {
+ PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
+ 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))]
RPCProcedure.uncheckedMurderPlayer(source, target);
break;
+ // Fixes
+
+ case (byte)CustomRPC.OpenToiletDoor:
+ int doorId = reader.ReadInt32();
+ PlainDoor door = UnityEngine.Object.FindObjectsOfType<PlainDoor>().FirstOrDefault(door => door.Id == doorId);
+ door.SetDoorway(true);
+ break;
+
// Role functionality
case (byte)CustomRPC.EngineerFixLights:
case (byte)CustomRPC.SealVent:
RPCProcedure.sealVent(reader.ReadPackedInt32());
break;
+ case (byte)CustomRPC.ArsonistWin:
+ RPCProcedure.arsonistWin();
+ break;
}
}
}
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<byte, int> impSettings = new Dictionary<byte, int>();
+ Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
impSettings.Add((byte)RoleId.Morphling, CustomOptionHolder.morphlingSpawnRate.getSelection());
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());
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
// Set tickets and always active roles
List<byte> crewTickets = new List<byte>();
+ List<byte> neutralTickets = new List<byte>();
List<byte> impTickets = new List<byte>();
+ 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
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;
}
}
}
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--;
// 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);
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);
}
}
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);
}
}
using HarmonyLib;
+using System.Linq;
using System;
using System.Collections.Generic;
using static TheOtherRoles.TheOtherRoles;
"Finish your tasks to find the <color=#FF1919FF>Impostors</color>",
"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 <color=#FF1919FF>Impostors</color> to win",
"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",
Cleaner.clearAndReload();
Warlock.clearAndReload();
SecurityGuard.clearAndReload();
+ Arsonist.clearAndReload();
}
public static class Jester {
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;
sheriff = null;
currentTarget = null;
cooldown = CustomOptionHolder.sheriffCooldown.getFloat();
- jesterCanDieToSheriff = CustomOptionHolder.jesterCanDieToSheriff.getBool();
+ canKillNeutrals = CustomOptionHolder.sheriffCanKillNeutrals.getBool();
spyCanDieToSheriff = CustomOptionHolder.spyCanDieToSheriff.getBool();
}
}
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;
}
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;
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;
}
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<PlayerControl> dousedPlayers = new List<PlayerControl>();
+ public static Dictionary<byte, PoolablePlayer> dousedIcons = new Dictionary<byte, PoolablePlayer>();
+
+ 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<PlayerControl>();
+ foreach (PoolablePlayer p in dousedIcons.Values) {
+ if (p != null && p.gameObject != null) {
+ UnityEngine.Object.Destroy(p.gameObject);
+ }
+ }
+ dousedIcons = new Dictionary<byte, PoolablePlayer>();
+ cooldown = CustomOptionHolder.arsonistCooldown.getFloat();
+ duration = CustomOptionHolder.arsonistDuration.getFloat();
+ }
+ }
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>2.5.1</Version>
+ <Version>2.6.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>
<Aliases>Il2CppNewtonsoft</Aliases>
</Reference>
</ItemGroup>
+ <PropertyGroup>
+ <NoWarn>$(NoWarn);MSB3243</NoWarn>
+ </PropertyGroup>
</Project>
\ No newline at end of file
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
[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;
SpriteRenderer renderer = icon.GetComponent<SpriteRenderer>();
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<Material>(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;
}
- }
+ }
}
}
}