The **The Other Roles**, is a mod for [Among Us](https://store.steampowered.com/app/945360/Among_Us) which adds many new roles, new [Settings](#settings), new [Custom Hats](#custom-hats) and support for [10+ Player Lobbies](#Custom-Servers-and-10+-Players) to the game.
Even more roles are coming soon :)
-| Impostors | Crewmates | Neutral |
-|----------|-------------|-----------------|
-| [Evil Mini](#mini) | [Nice Mini](#mini) | [Arsonist](#arsonist) |
-| [Camouflager](#camouflager) | [Detective](#detective) | [Jester](#jester) |
-| [Cleaner](#cleaner) | [Engineer](#engineer) | [Jackal](#jackal) |
-| [Eraser](#eraser) | [Hacker](#hacker) | [Sidekick](#sidekick) |
-| [ImpLover](#lovers) | [Lover](#lovers) | |
-| [Godfather (Mafia)](#mafia) | [Lighter](#lighter) | |
-| [Mafioso (Mafia)](#mafia) | [Mayor](#mayor) | |
-| [Janitor (Mafia)](#mafia) | [Medic](#medic) | |
-| [Morphling](#morphling) | [Security Guard](#security-guard) | |
-| [Trickster](#trickster) | [Seer](#seer) | |
-| [Vampire](#vampire) | [Sheriff](#sheriff) | |
-| [Warlock](#warlock) | [Shifter](#shifter) | |
-| [Evil Guesser](#guesser) | [Nice Guesser](#guesser) | |
-| | [Snitch](#snitch) | |
-| | [Spy](#spy) | |
-| | [Swapper](#swapper) | |
-| | [Time Master](#time-master) | |
-| | [Tracker](#tracker) | |
+| Impostors | Crewmates | Neutral | Secondary |
+|----------|-------------|-----------------|----------------|
+| [Evil Mini](#mini) | [Nice Mini](#mini) | [Arsonist](#arsonist) | [Lover](#lovers) |
+| [Evil Guesser](#guesser) | [Nice Guesser](#guesser) | [Jester](#jester) | |
+| [Bounty Hunter](#bounty-hunter) | [Detective](#detective) | [Jackal](#jackal) | |
+| [Camouflager](#camouflager) | [Engineer](#engineer) | [Sidekick](#sidekick) | |
+| [Cleaner](#cleaner) | [Hacker](#hacker) | | |
+| [Eraser](#eraser) | [Lighter](#lighter) | | |
+| [Godfather (Mafia)](#mafia) | [Mayor](#mayor) | | |
+| [Mafioso (Mafia)](#mafia) | [Medic](#medic) | | |
+| [Janitor (Mafia)](#mafia) | [Security Guard](#security-guard) | | |
+| [Morphling](#morphling) | [Seer](#seer) | | |
+| [Trickster](#trickster) | [Sheriff](#sheriff) | | |
+| [Vampire](#vampire) | [Shifter](#shifter) | | |
+| [Warlock](#warlock) | [Snitch](#snitch) | | |
+| | [Spy](#spy) | | |
+| | [Swapper](#swapper) | | |
+| | [Time Master](#time-master) | | |
+| | [Tracker](#tracker) | | |
The [Role Assignment](#role-assignment) sections explains how the roles are being distributed among the players.
# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| **2021.6.15**| v2.7.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.7.0/TheOtherRoles.zip)
| 2021.5.25.2s| v2.6.7| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.7/TheOtherRoles.zip)
| 2021.5.10s| v2.6.6| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.6/TheOtherRoles.zip)
| 2021.5.10s| v2.6.5| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.6.5/TheOtherRoles.zip)
<details>
<summary>Click to show the Changelog</summary>
+**Version 2.7.0**
+- **New Role:** [Bounty Hunter](#bounty-hunter) created by [Mallöris](https://github.com/Mallaris)
+- Added more new [colors](#colors) (Thanks to [Drakoni](https://twitter.com/Drakoni13) for sorting them)
+- Added a setting to the [Shifter](#shifter), that will prevent [Medic Shield](#medic) & [Lover](#lovers) Roles to be shifted
+- Changed [Jackal](#jackal) & [Sidekick](#sidekick) to always be killable by [Sheriff](#sheriff)
+- Changed [Jackal](#jackal) & [Sidekick](#sidekick) to not be [erasable](#eraser) anymore
+- Changed [Role Assignment](#role-assignment) slightly to make chances more consistent
+- Fixed a bug where votes would still count after the [Guesser](#guesser) or it's target died
+- Fixed a bug where a [lover partner](#lovers) would not be shown as dead when killed by the [Guesser](#guesser)
+- Fixed a bug on the Airship, where the [Jester](#jester) win was not triggered in some cases
+
**Version 2.6.7**
- **New Role:** [Guesser](#guesser)
-- Changed the colors of some roles
-- Changed name of Child to [Mini](#mini)
-- Changed Version check to allow to check for modified versions of the mod. (Forks don't need to use different versioning now)
-- Changed Role Assignment to make [Spy](#spy) incompatible with [Mini](#mini) (former child)
-- Fixed a bug where a [Jester](#jester) win was triggered, when the partner of a [Jester Lover](#lovers) was voted out
-- Fixed a bug where a [Mini](#mini) lose was triggered, when the partner of a Crew [Mini Lover](#lovers) was voted out
+- We changed the colors of some of our roles
+- We renamed the Child to Mini
+- Fixed a bug where a Jester win was triggered, when the partner of a Jester Lover was voted out
+- Fixed a bug where a Mini lose was triggered, when the partner of a Crew Mini Lover was voted out
**Version 2.6.6**
- Fixed a bug introduced in v2.6.5 that caused all player to be able to use vents when the new option for spy was enabled
- **Submission:** If you got a hat design, you can submit it on our [Discord server](https://discord.gg/77RkMJHWsM). We'll look at all the hats and add all the good ones to the game.
# Colors
-
+
# Roles
| Warlock Root Time | Time the Warlock is rooted in place after killing using the curse
-----------------------
+
+## Bounty Hunter
+### **Team: Impostors**
+\
+Created by [Mallöris](https://github.com/Mallaris)\
+\
+The Bounty Hunter is an Impostor, that continuously get bountys (the targeted player doesn't get notified).\
+The target of the Bounty Hunter swaps after every meeting and after a configuarble amount of time.\
+If the Bounty Hunter kills his target, his kill cooldown will be a lot less than usual.\
+Killing a player that's not his current target results in an increased kill cooldown.\
+Depending on the options, there'll be an arrow pointing towards the current target.
+
+\
+**NOTE:**
+- The target won't be an Impostor or a Spy
+- Killing the target resets the timer and a new target will be selected.
+
+### Game Options
+| Name | Description |
+|----------|:-------------:|
+| Bounty Hunter Spawn Chance | -
+| Duration After Which Bounty Changes | -
+| Cooldown After Killing Bounty | -
+| Additional Cooldown After Killing Others | Time will be added to the normal impostor cooldown if the Bounty Hunter kills a not-bounty player
+| Show Arrow Pointing Towards The Bounty | If set to true an arrow will appear (only visiable for the Bounty Hunter)
+| Bounty Hunter Arrow Update Intervall | Sets how often the position is being updated
+-----------------------
+
+
## Guesser
### **Team: Crewmates or Impostors**
The Guesser can be a Crewmate or an Impostor (depending on the settings).\
| Name | Description
|----------|:-------------:|
| Shifter Spawn Chance | -
+| Shifter Shifts Modifiers | Sets if Lovers and/or Medic Shield will be shifted
-----------------------
## Time Master
bin
-obj
\ No newline at end of file
+obj
+.vscode/tasks.json
+.gitignore
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using UnityEngine;
-using TMPro;
-using System;
-
-namespace TheOtherRoles {
-
- [HarmonyPatch(typeof(OpenDoorConsole), nameof(OpenDoorConsole.Use))]
- class ToiletDoorFix { // Synchronize opening toilet doors among clients
- 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(NotificationPopper), nameof(NotificationPopper.Update))]
- public static class NotificationPopperUpdatePatch { // Fix position of notifications (e.g. player disconnected)
- public static void Postfix(NotificationPopper __instance) {
- if (__instance.alphaTimer > 0f) {
- var pos = __instance.transform.localPosition;
- __instance.transform.localPosition += new Vector3(0.5f, 0f, 0f);
- }
- }
- }
-
-}
\ No newline at end of file
byte targetId = 0;
if ((Sheriff.currentTarget.Data.IsImpostor && (Sheriff.currentTarget != Mini.mini || Mini.isGrownUp())) ||
(Sheriff.spyCanDieToSheriff && Spy.spy == Sheriff.currentTarget) ||
- (Sheriff.canKillNeutrals && (Arsonist.arsonist == Sheriff.currentTarget || Jester.jester == Sheriff.currentTarget || Jackal.jackal == Sheriff.currentTarget || Sidekick.sidekick == Sheriff.currentTarget))) {
+ (Sheriff.canKillNeutrals && (Arsonist.arsonist == Sheriff.currentTarget || Jester.jester == Sheriff.currentTarget)) ||
+ (Jackal.jackal == Sheriff.currentTarget || Sidekick.sidekick == Sheriff.currentTarget)) {
targetId = Sheriff.currentTarget.PlayerId;
}
else {
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.jackalCreatesSidekick(Jackal.currentTarget.PlayerId);
},
- () => { return Jackal.canCreateSidekick && Sidekick.sidekick == null && Jackal.fakeSidekick == null && Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return Sidekick.sidekick == null && Jackal.fakeSidekick == null && Jackal.currentTarget != null && PlayerControl.LocalPlayer.CanMove; },
+ () => { return Jackal.canCreateSidekick && Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Jackal.canCreateSidekick && Jackal.currentTarget != null && PlayerControl.LocalPlayer.CanMove; },
() => { jackalSidekickButton.Timer = jackalSidekickButton.MaxTimer;},
Jackal.getSidekickButtonSprite(),
new Vector3(-1.3f, 1.3f, 0f),
arsonistButton.Timer = Arsonist.dousedEveryoneAlive() ? 0 : arsonistButton.MaxTimer;
foreach (PlayerControl p in Arsonist.dousedPlayers) {
- if (Arsonist.dousedIcons.ContainsKey(p.PlayerId)) {
- Arsonist.dousedIcons[p.PlayerId].setSemiTransparent(false);
+ if (MapOptions.playerIcons.ContainsKey(p.PlayerId)) {
+ MapOptions.playerIcons[p.PlayerId].setSemiTransparent(false);
}
}
}
string text = __instance.TextArea.text;
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("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("80cc70dc5f21bc321b84ce984abd511b")) { // i dont understand hashes
- handled = true;
- byte colorId = (byte)(CustomColors.pickableColors + 1);
- SaveManager.BodyColor = (byte)colorId;
- if (PlayerControl.LocalPlayer)
- PlayerControl.LocalPlayer.CmdCheckColor(colorId);
- } 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);
- } else if (text.ToLower().StartsWith("/kick ")) {
+ //using(MD5 md5 = MD5.Create()) {
+ // string hash = System.BitConverter.ToString(md5.ComputeHash(Encoding.UTF8.GetBytes("tor@" + text.ToLower() + "§eof"))).Replace("-", "").ToLowerInvariant();
+ if (text.ToLower().StartsWith("/kick ")) {
string playerName = text.Substring(6);
PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(playerName));
if (target != null && AmongUsClient.Instance != null && AmongUsClient.Instance.CanBan()) {
handled = true;
}
}
+ } else if (text.ToLower().StartsWith("/ban ")) {
+ string playerName = text.Substring(6);
+ PlayerControl target = PlayerControl.AllPlayerControls.ToArray().ToList().FirstOrDefault(x => x.Data.PlayerName.Equals(playerName));
+ if (target != null && AmongUsClient.Instance != null && AmongUsClient.Instance.CanBan()) {
+ var client = AmongUsClient.Instance.GetClient(target.OwnerId);
+ if (client != null) {
+ AmongUsClient.Instance.KickPlayer(client.Id, true);
+ handled = true;
+ }
+ }
}
}
- }
+ //}
if (handled) {
__instance.TextArea.Clear();
__instance.quickChatMenu.ResetGlyphs();
[HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))]
private static class PingTrackerPatch
{
+ private static GameObject modStamp;
+ static void Prefix(PingTracker __instance) {
+ if (modStamp == null) {
+ modStamp = new GameObject("ModStamp");
+ var rend = modStamp.AddComponent<SpriteRenderer>();
+ rend.sprite = TheOtherRolesPlugin.GetModStamp();
+ rend.color = new Color(1, 1, 1, 0.5f);
+ modStamp.transform.parent = __instance.transform.parent;
+ modStamp.transform.localScale *= 0.6f;
+ }
+ float offset = (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) ? 0.75f : 0f;
+ modStamp.transform.position = HudManager.Instance.MapButton.transform.position + Vector3.down * offset;
+ }
+
static void Postfix(PingTracker __instance){
__instance.text.alignment = TMPro.TextAlignmentOptions.TopRight;
if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) {
__instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString()}\n" + __instance.text.text;
if (PlayerControl.LocalPlayer.Data.IsDead) {
- __instance.transform.localPosition = new Vector3(3.45f, 2.675f, __instance.transform.localPosition.z);
+ __instance.transform.localPosition = new Vector3(3.45f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
} else {
- __instance.transform.localPosition = new Vector3(4.2f, 2.675f, __instance.transform.localPosition.z);
+ __instance.transform.localPosition = new Vector3(4.2f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
}
} else {
__instance.text.text = $"{fullCredentials}\n{__instance.text.text}";
- __instance.transform.localPosition = new Vector3(3.5f, 2.675f, __instance.transform.localPosition.z);
+ __instance.transform.localPosition = new Vector3(3.5f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
}
}
}
+++ /dev/null
-// Adapted from https://github.com/CrowdedMods/CrowdedMod
-/*
-MIT License
-
-Copyright (c) 2020 andry08 & CrowdedMods
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
-using System.Collections.Generic;
-using System.Linq;
-using HarmonyLib;
-using UnityEngine;
-using Hazel;
-
-namespace TheOtherRoles {
- [HarmonyPatch]
- static class GameOptionsPatch {
- public static List<SpriteRenderer> additionalButtons = new List<SpriteRenderer>();
-
- [HarmonyPatch(typeof(CreateOptionsPicker), nameof(CreateOptionsPicker.Start))]
- public static class CreateOptionsPickerStartPatch {
- public static void Postfix(CreateOptionsPicker __instance) {
- List<SpriteRenderer> maxPlayerButtons = __instance.MaxPlayerButtons.ToList();
- if (maxPlayerButtons == null || maxPlayerButtons.Count <= 2) return;
- additionalButtons = new List<SpriteRenderer>();
-
- for (int i = 1; i < 6; i++) {
- SpriteRenderer nextButton = Object.Instantiate(maxPlayerButtons.Last(), maxPlayerButtons.Last().transform.parent);
- additionalButtons.Add(nextButton);
- nextButton.enabled = false;
- nextButton.gameObject.name = "1" + i;
- TMPro.TMP_Text text = nextButton.gameObject.GetComponentInChildren<TMPro.TMP_Text>();
- text.text = "1" + i;
- text.color = Helpers.isCustomServer() ? Color.white : Palette.DisabledGrey;
-
- nextButton.transform.position = nextButton.transform.position + new Vector3(i * (maxPlayerButtons[1].transform.position.x - maxPlayerButtons[0].transform.position.x), 0, 0);
- var passiveButton = nextButton.GetComponent<PassiveButton>();
- passiveButton.OnClick.RemoveAllListeners();
-
- void onClick() {
- bool isCustom = Helpers.isCustomServer();
- foreach (SpriteRenderer renderer in additionalButtons) {
- if (renderer != null && renderer.gameObject != null) {
- renderer.enabled = false;
- renderer.gameObject.GetComponentInChildren<TMPro.TMP_Text>().color = isCustom ? Color.white : Palette.DisabledGrey;
- }
- }
-
- if (!isCustom) return;
-
- nextButton.enabled = true;
-
- byte value = byte.Parse(nextButton.name);
- var targetOptions = __instance.GetTargetOptions();
- if (value <= targetOptions.NumImpostors) {
- targetOptions.NumImpostors = value - 1;
- __instance.SetImpostorButtons(targetOptions.NumImpostors);
- }
- __instance.SetMaxPlayersButtons(value);
- }
- passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick);
- }
- }
- }
-
- public static void setLegalSettings() {
- GameOptionsData hostOptions = SaveManager.GameHostOptions;
- GameOptionsData searchOptions = SaveManager.GameSearchOptions;
- if (searchOptions.MapId == 0) searchOptions.ToggleMapFilter(0); // ToggleMapFilter
-
- hostOptions.NumImpostors = searchOptions.NumImpostors = 1; // NumImpostors
- hostOptions.MaxPlayers = searchOptions.MaxPlayers = 4; // MaxPlayers
-
- SaveManager.GameHostOptions = hostOptions;
- SaveManager.GameSearchOptions = searchOptions;
- }
-
- [HarmonyPatch(typeof(FindGameButton), nameof(FindGameButton.OnClick))]
- class FindGameButtonOnClickPatch {
- public static void Prefix() {
- // Set legal settings
- setLegalSettings();
- }
- }
-
- [HarmonyPatch(typeof(JoinGameButton), nameof(JoinGameButton.OnClick))]
- class JoinGameButtonOnClickPatch {
- public static void Prefix() {
- // Set legal settings
- setLegalSettings();
- }
- }
- }
-
- [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.GameHostOptions), MethodType.Getter)]
- public static class SaveManagerGetHostOptions
- {
- public static bool Prefix(out GameOptionsData __result)
- {
- SaveManager.hostOptionsData ??= SaveManager.LoadGameOptions("gameHostOptions");
-
- SaveManager.hostOptionsData.NumImpostors = Mathf.Clamp(SaveManager.hostOptionsData.NumImpostors, 1, SaveManager.hostOptionsData.MaxPlayers - 1);
- SaveManager.hostOptionsData.KillDistance = Mathf.Clamp(SaveManager.hostOptionsData.KillDistance, 0, 2);
-
- __result = SaveManager.hostOptionsData;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(GameData), nameof(GameData.GetAvailableId))]
- public static class GameDataGetAvailableIdPatch {
- public static bool Prefix(ref GameData __instance, out sbyte __result) {
- for (sbyte i = 0; i <= 15; i++)
- if (!__instance.AllPlayers.ToArray().Any(p => p.PlayerId == i)) {
- __result = i;
- return false;
- }
- __result = -1;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdCheckColor))]
- public static class PlayerControlCmdCheckColorPatch {
- public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)]byte bodyColor) {
- if (!Helpers.isCustomServer() || Palette.PlayerColors.Count >= PlayerControl.AllPlayerControls.Count)
- return true;
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetUncheckedColor, Hazel.SendOption.Reliable, -1);
- writer.Write(bodyColor);
- writer.Write(__instance.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.setUncheckedColor(bodyColor, __instance.PlayerId);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.UpdateAvailableColors))]
- public static class PlayerTabUpdateAvailableColorsPatch {
- public static bool Prefix(PlayerTab __instance) {
- if (!Helpers.isCustomServer() || Palette.PlayerColors.Count >= PlayerControl.AllPlayerControls.Count)
- return true;
-
- PlayerControl.SetPlayerMaterialColors(PlayerControl.LocalPlayer.Data.ColorId, __instance.DemoImage);
- for (int i = 0; i < Palette.PlayerColors.Length; i++)
- __instance.AvailableColors.Add(i);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(SecurityLogger), nameof(SecurityLogger.Awake))]
- public static class SecurityLoggerAwakePatch
- {
- public static void Postfix(SecurityLogger __instance) {
- __instance.Timers = new float[15];
- }
- }
-
- [HarmonyPatch(typeof(KeyMinigame),nameof(KeyMinigame.Start))]
- public static class KeyMinigameStartPatch
- {
- public static bool Prefix(KeyMinigame __instance)
- {
- __instance.targetSlotId = (PlayerControl.LocalPlayer != null) ? PlayerControl.LocalPlayer.PlayerId % 10 : 0;
- __instance.Slots[__instance.targetSlotId].Image.sprite = __instance.Slots[__instance.targetSlotId].Highlit;
- return false;
- }
- }
-}
public class CustomColors {
protected static Dictionary<int, string> ColorStrings = new Dictionary<int, string>();
public static List<int> lighterColors = new List<int>(){ 3, 4, 5, 7, 10, 11};
- public static uint pickableColors = 12;
+ public static uint pickableColors = (uint)Palette.ColorNames.Length;
+ /* version 1
+ private static readonly List<int> ORDER = new List<int>() { 7, 17, 5, 33, 4,
+ 30, 0, 19, 27, 3,
+ 13, 25, 18, 15, 23,
+ 8, 32, 1, 21, 31,
+ 10, 34, 12, 14, 28,
+ 22, 29, 11, 26, 2,
+ 20, 24, 9, 16, 6 }; */
+ private static readonly List<int> ORDER = new List<int>() { 7, 14, 5, 33, 4,
+ 30, 0, 19, 27, 3,
+ 17, 25, 18, 13, 23,
+ 8, 32, 1, 21, 31,
+ 10, 34, 15, 28, 22,
+ 29, 11, 2, 26, 16,
+ 20, 24, 9, 12, 6 };
public static void Load() {
List<StringNames> longlist = Enumerable.ToList<StringNames>(Palette.ColorNames);
- List<StringNames> shortlist = Enumerable.ToList<StringNames>(Palette.ShortColorNames);
List<Color32> colorlist = Enumerable.ToList<Color32>(Palette.PlayerColors);
List<Color32> shadowlist = Enumerable.ToList<Color32>(Palette.ShadowColors);
List<CustomColor> colors = new List<CustomColor>();
- colors.Add(new CustomColor { longname = "Salmon", shortname = "SALMN",
- color = new Color32(239, 191, 192, byte.MaxValue),
- shadow = new Color32(182, 119, 114, byte.MaxValue),
+ /* Custom Colors */
+ colors.Add(new CustomColor { longname = "Salmon",
+ color = new Color32(239, 191, 192, byte.MaxValue), // color = new Color32(0xD8, 0x82, 0x83, byte.MaxValue),
+ shadow = new Color32(182, 119, 114, byte.MaxValue), // shadow = new Color32(0xA5, 0x63, 0x65, byte.MaxValue),
isLighterColor = true });
- colors.Add(new CustomColor { longname = "Bordeaux", shortname = "BRDX",
+ colors.Add(new CustomColor { longname = "Bordeaux",
color = new Color32(109, 7, 26, byte.MaxValue),
shadow = new Color32(54, 2, 11, byte.MaxValue),
isLighterColor = false });
- colors.Add(new CustomColor { longname = "Olive", shortname = "OLIVE",
+ colors.Add(new CustomColor { longname = "Olive",
color = new Color32(154, 140, 61, byte.MaxValue),
shadow = new Color32(104, 95, 40, byte.MaxValue),
isLighterColor = false });
- colors.Add(new CustomColor { longname = "Turqoise", shortname = "TURQ",
+ colors.Add(new CustomColor { longname = "Turqoise",
color = new Color32(22, 132, 176, byte.MaxValue),
shadow = new Color32(15, 89, 117, byte.MaxValue),
isLighterColor = false });
- colors.Add(new CustomColor { longname = "Mint", shortname = "MINT",
+ colors.Add(new CustomColor { longname = "Mint",
color = new Color32(111, 192, 156, byte.MaxValue),
shadow = new Color32(65, 148, 111, byte.MaxValue),
isLighterColor = true });
- colors.Add(new CustomColor { longname = "Lavender", shortname = "LVNDR",
+ colors.Add(new CustomColor { longname = "Lavender",
color = new Color32(173, 126, 201, byte.MaxValue),
shadow = new Color32(131, 58, 203, byte.MaxValue),
isLighterColor = true });
- colors.Add(new CustomColor { longname = "Nougat", shortname = "NOUGT",
+ colors.Add(new CustomColor { longname = "Nougat",
color = new Color32(160, 101, 56, byte.MaxValue),
shadow = new Color32(115, 15, 78, byte.MaxValue),
isLighterColor = false });
- colors.Add(new CustomColor { longname = "Peach", shortname = "PEACH",
+ colors.Add(new CustomColor { longname = "Peach",
color = new Color32(255, 164, 119, byte.MaxValue),
shadow = new Color32(238, 128, 100, byte.MaxValue),
isLighterColor = true });
- colors.Add(new CustomColor { longname = "Wasabi", shortname = "WSBI",
+ colors.Add(new CustomColor { longname = "Wasabi",
color = new Color32(112, 143, 46, byte.MaxValue),
shadow = new Color32(72, 92, 29, byte.MaxValue),
- isLighterColor = true });
- colors.Add(new CustomColor { longname = "Hot Pink", shortname = "HTPNK",
+ isLighterColor = false });
+ colors.Add(new CustomColor { longname = "Hot Pink",
color = new Color32(255, 51, 102, byte.MaxValue),
shadow = new Color32(232, 0, 58, byte.MaxValue),
isLighterColor = true });
- colors.Add(new CustomColor { longname = "Gray", shortname = "GRAY",
- color = new Color32(147, 147, 147, byte.MaxValue),
- shadow = new Color32(120, 120, 120, byte.MaxValue),
- isLighterColor = false });
- colors.Add(new CustomColor { longname = "Petrol", shortname = "PTRL",
+ colors.Add(new CustomColor { longname = "Petrol",
color = new Color32(0, 99, 105, byte.MaxValue),
shadow = new Color32(0, 61, 54, byte.MaxValue),
isLighterColor = false });
-
- pickableColors += (uint)colors.Count; // Colors to show in Tab
- /** Hidden Colors **/
- colors.Add(new CustomColor { longname = "Panda", shortname = "PANDA",
- color = new Color32(255, 255, 255, 0),
- shadow = new Color32(12, 12, 12, 0),
+ colors.Add(new CustomColor { longname = "Lemon",
+ color = new Color32(0xDB, 0xFD, 0x2F, byte.MaxValue),
+ shadow = new Color32(0x74, 0xE5, 0x10, byte.MaxValue),
isLighterColor = true });
- colors.Add(new CustomColor { longname = "Midnight", shortname = "MDNT",
- color = new Color32(64, 8, 71, 0),
- shadow = new Color32(24, 32, 116, 0),
- isLighterColor = false });
+ colors.Add(new CustomColor { longname = "Signal Orange",
+ color = new Color32(0xF7, 0x44, 0x17, byte.MaxValue),
+ shadow = new Color32(0x9B, 0x2E, 0x0F, byte.MaxValue),
+ isLighterColor = true });
+
+ colors.Add(new CustomColor { longname = "Teal",
+ color = new Color32(0x25, 0xB8, 0xBF, byte.MaxValue),
+ shadow = new Color32(0x12, 0x89, 0x86, byte.MaxValue),
+ 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),
+ colors.Add(new CustomColor { longname = "Blurple",
+ color = new Color32(0x59, 0x3C, 0xD6, byte.MaxValue),
+ shadow = new Color32(0x29, 0x17, 0x96, byte.MaxValue),
+ isLighterColor = false });
+
+ colors.Add(new CustomColor { longname = "Sunrise",
+ color = new Color32(0xFF, 0xCA, 0x19, byte.MaxValue),
+ shadow = new Color32(0xDB, 0x44, 0x42, byte.MaxValue),
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 });
+ colors.Add(new CustomColor { longname = "Ice",
+ color = new Color32(0xA8, 0xDF, 0xFF, byte.MaxValue),
+ shadow = new Color32(0x59, 0x9F, 0xC8, byte.MaxValue),
+ isLighterColor = true });
+
+ pickableColors += (uint)colors.Count; // Colors to show in Tab
+ /** Hidden Colors **/
+
/** Add Colors **/
int id = 50000;
foreach (CustomColor cc in colors) {
longlist.Add((StringNames)id);
CustomColors.ColorStrings[id++] = cc.longname;
- shortlist.Add((StringNames)id);
- CustomColors.ColorStrings[id++] = cc.shortname;
colorlist.Add(cc.color);
shadowlist.Add(cc.shadow);
if (cc.isLighterColor)
lighterColors.Add(colorlist.Count - 1);
}
- Palette.ShortColorNames = shortlist.ToArray();
Palette.ColorNames = longlist.ToArray();
Palette.PlayerColors = colorlist.ToArray();
Palette.ShadowColors = shadowlist.ToArray();
- MedScanMinigame.ColorNames = Palette.ColorNames;
- Telemetry.ColorNames = Palette.ColorNames;
}
protected internal struct CustomColor {
public string longname;
- public string shortname;
public Color32 color;
public Color32 shadow;
public bool isLighterColor;
private static class PlayerTabEnablePatch {
public static void Postfix(PlayerTab __instance) { // Replace instead
Il2CppArrayBase<ColorChip> chips = __instance.ColorChips.ToArray();
- int cols = 4; // TODO: Design an algorithm to dynamically position chips to optimally fill space
- for (int i = 0; i < chips.Length; i++) {
- ColorChip chip = chips[i];
- int row = i / cols, col = i % cols; // Dynamically do the positioning
- chip.transform.localPosition = new Vector3(1.46f + (col * 0.6f), -0.43f - (row * 0.55f), chip.transform.localPosition.z);
- chip.transform.localScale *= 0.9f;
- if (i >= pickableColors) {
- chip.transform.localScale *= 0f; // Needs to exist for PlayerTab
- chip.enabled = false;
- chip.Button.enabled = false;
- chip.Button.OnClick.RemoveAllListeners();
- }
+ int cols = 5; // TODO: Design an algorithm to dynamically position chips to optimally fill space
+ for (int i = 0; i < ORDER.Count; i++) {
+ int pos = ORDER[i];
+ if (pos < 0 || pos > chips.Length)
+ continue;
+ ColorChip chip = chips[pos];
+ int row = i / cols, col = i % cols; // Dynamically do the positioning
+ chip.transform.localPosition = new Vector3(-0.975f + (col * 0.485f), 1.475f - (row * 0.49f), chip.transform.localPosition.z);
+ chip.transform.localScale *= 0.78f;
+ }
+ for (int j = ORDER.Count; j < chips.Length; j++) { // If number isn't in order, hide it
+ ColorChip chip = chips[j];
+ chip.transform.localScale *= 0f;
+ chip.enabled = false;
+ chip.Button.enabled = false;
+ chip.Button.OnClick.RemoveAllListeners();
}
}
}
Texture2D texture = fromDisk ? Helpers.loadTextureFromDisk(path) : Helpers.loadTextureFromResources(path);
if (texture == null)
return null;
- Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.8f), texture.width * 0.75f);
+ Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.53f, 0.575f), texture.width * 0.375f);
if (sprite == null)
return null;
texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
public static CustomOption jackalCanCreateSidekickFromImpostor;
public static CustomOption jackalAndSidekickHaveImpostorVision;
+ public static CustomOption bountyHunterSpawnRate;
+ public static CustomOption bountyHunterBountyDuration;
+ public static CustomOption bountyHunterReducedCooldown;
+ public static CustomOption bountyHunterPunishmentTime;
+ public static CustomOption bountyHunterShowArrow;
+ public static CustomOption bountyHunterArrowUpdateIntervall;
+
public static CustomOption shifterSpawnRate;
+ public static CustomOption shifterShiftsModifiers;
public static CustomOption mayorSpawnRate;
warlockSpawnRate = CustomOption.Create(270, cs(Cleaner.color, "Warlock"), rates, null, true);
warlockCooldown = CustomOption.Create(271, "Warlock Cooldown", 30f, 10f, 60f, 2.5f, warlockSpawnRate);
warlockRootTime = CustomOption.Create(272, "Warlock Root Time", 5f, 0f, 15f, 1f, warlockSpawnRate);
-
+
+ bountyHunterSpawnRate = CustomOption.Create(320, cs(BountyHunter.color, "Bounty Hunter"), rates, null, true);
+ bountyHunterBountyDuration = CustomOption.Create(321, "Duration After Which Bounty Changes", 60f, 10f, 180f, 10f, bountyHunterSpawnRate);
+ bountyHunterReducedCooldown = CustomOption.Create(322, "Cooldown After Killing Bounty", 2.5f, 0f, 30f, 2.5f, bountyHunterSpawnRate);
+ bountyHunterPunishmentTime = CustomOption.Create(323, "Additional Cooldown After Killing Others", 20f, 0f, 60f, 2.5f, bountyHunterSpawnRate);
+ bountyHunterShowArrow = CustomOption.Create(324, "Show Arrow Pointing Towards The Bounty", true, bountyHunterSpawnRate);
+ bountyHunterArrowUpdateIntervall = CustomOption.Create(325, "Arrow Update Intervall", 15f, 2.5f, 60f, 2.5f, bountyHunterShowArrow);
+
+
miniSpawnRate = CustomOption.Create(180, cs(Mini.color, "Mini"), rates, null, true);
miniGrowingUpDuration = CustomOption.Create(181, "Mini Growing Up Duration", 400f, 100f, 1500f, 100f, miniSpawnRate);
jackalAndSidekickHaveImpostorVision = CustomOption.Create(430, "Jackal And Sidekick Have Impostor Vision", false, jackalSpawnRate);
shifterSpawnRate = CustomOption.Create(70, cs(Shifter.color, "Shifter"), rates, null, true);
+ shifterShiftsModifiers = CustomOption.Create(71, "Shifter Shifts Modifiers", false, shifterSpawnRate);
mayorSpawnRate = CustomOption.Create(80, cs(Mayor.color, "Mayor"), rates, null, true);
blockedRolePairings.Add((byte)RoleId.Vampire, new [] { (byte)RoleId.Warlock});
blockedRolePairings.Add((byte)RoleId.Warlock, new [] { (byte)RoleId.Vampire});
+ blockedRolePairings.Add((byte)RoleId.Spy, new [] { (byte)RoleId.Mini});
+ blockedRolePairings.Add((byte)RoleId.Mini, new [] { (byte)RoleId.Spy});
}
}
var hudString = sb.ToString();
int defaultSettingsLines = 19;
- int roleSettingsLines = defaultSettingsLines + 33;
- int detailedSettingsP1 = roleSettingsLines + 34;
- int detailedSettingsP2 = detailedSettingsP1 + 35;
+ int roleSettingsLines = defaultSettingsLines + 34;
+ int detailedSettingsP1 = roleSettingsLines + 37;
+ int detailedSettingsP2 = detailedSettingsP1 + 38;
int end1 = hudString.TakeWhile(c => (defaultSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count();
int end2 = hudString.TakeWhile(c => (roleSettingsLines -= (c == '\n' ? 1 : 0)) > 0).Count();
int end3 = hudString.TakeWhile(c => (detailedSettingsP1 -= (c == '\n' ? 1 : 0)) > 0).Count();
gap = 5;
index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
hudString = hudString.Insert(index, "\n");
- gap = 17;
+ gap = 18;
index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
hudString = hudString.Insert(index + 1, "\n");
- gap = 21;
+ gap = 22;
index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
hudString = hudString.Insert(index + 1, "\n");
} else if (counter == 2) {
textRenderer.color = Mini.color;
}
- if(MapOptions.showRoleSummary) {
+ if (MapOptions.showRoleSummary) {
var position = Camera.main.ViewportToWorldPoint(new Vector3(0f, 1f, Camera.main.nearClipPlane));
GameObject roleSummary = UnityEngine.Object.Instantiate(__instance.WinText.gameObject);
roleSummary.transform.position = new Vector3(__instance.ExitButton.transform.position.x + 0.1f, position.y - 0.1f, -14f);
// Eraser erase
if (Eraser.eraser != null && AmongUsClient.Instance.AmHost && Eraser.futureErased != null) { // We need to send the RPC from the host here, to make sure that the order of shifting and erasing is correct (for that reason the futureShifted and futureErased are being synced)
foreach (PlayerControl target in Eraser.futureErased) {
- if (target != null) {
+ if (target != null && target.canBeErased()) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ErasePlayerRoles, Hazel.SendOption.Reliable, -1);
writer.Write(target.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
}
- [HarmonyPatch(typeof(UnityEngine.Object), nameof(UnityEngine.Object.Destroy), new Type[] { typeof(UnityEngine.Object) })]
- class ExileControllerDestroyPatch {
- static void Prefix(UnityEngine.Object obj) {
- if (ExileController.Instance == null || obj != ExileController.Instance.gameObject) return;
- var exiled = ExileController.Instance.exiled;
+ [HarmonyPatch]
+ class ExileControllerWrapUpPatch {
+ [HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))]
+ class BaseExileControllerPatch {
+ public static void Postfix(ExileController __instance) {
+ WrapUpPostfix(__instance.exiled);
+ }
+ }
+
+ [HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))]
+ class AirshipExileControllerPatch {
+ public static void Postfix(AirshipExileController __instance) {
+ WrapUpPostfix(__instance.exiled);
+ }
+ }
+
+ static void WrapUpPostfix(GameData.PlayerInfo exiled) {
// Mini exile lose condition
if (exiled != null && Mini.mini != null && Mini.mini.PlayerId == exiled.PlayerId && !Mini.isGrownUp() && !Mini.mini.Data.IsImpostor) {
Mini.triggerMiniLose = true;
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 PlayerControl.AllPlayerControls) {
- if (!Arsonist.dousedIcons.ContainsKey(p.PlayerId)) continue;
+ if (!MapOptions.playerIcons.ContainsKey(p.PlayerId)) continue;
if (p.Data.IsDead || p.Data.Disconnected) {
- Arsonist.dousedIcons[p.PlayerId].gameObject.SetActive(false);
+ MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
} else {
- Arsonist.dousedIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f;
+ MapOptions.playerIcons[p.PlayerId].transform.localPosition = bottomLeft + Vector3.right * visibleCounter * 0.35f;
visibleCounter++;
}
}
}
+
+ // Force Bounty Hunter Bounty Update
+ if (BountyHunter.bountyHunter != null && BountyHunter.bountyHunter == PlayerControl.LocalPlayer)
+ BountyHunter.bountyUpdateTimer = 0f;
}
}
}
public static bool hasFakeTasks(this PlayerControl player) {
- return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist);
+ return (player == Jester.jester || player == Jackal.jackal || player == Sidekick.sidekick || player == Arsonist.arsonist || Jackal.formerJackals.Contains(player));
+ }
+
+ public static bool canBeErased(this PlayerControl player) {
+ return (player != Jackal.jackal && player != Sidekick.sidekick && !Jackal.formerJackals.Contains(player));
}
public static void clearAllTasks(this PlayerControl player) {
f = Mathf.Clamp01(f);
return (byte)(f * 255);
}
+
+ public static KeyValuePair<byte, int> MaxPair(this Dictionary<byte, int> self, out bool tie) {
+ tie = true;
+ KeyValuePair<byte, int> result = new KeyValuePair<byte, int>(byte.MaxValue, int.MinValue);
+ foreach (KeyValuePair<byte, int> keyValuePair in self)
+ {
+ if (keyValuePair.Value > result.Value)
+ {
+ result = keyValuePair;
+ tie = false;
+ }
+ else if (keyValuePair.Value == result.Value)
+ {
+ tie = true;
+ }
+ }
+ return result;
+ }
}
}
namespace TheOtherRoles
{
[HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))]
- class IntroCutsceneOnDestroyPatch {
+ 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;
+ // Generate and initialize player icons
+ int playerCounter = 0;
+ if (PlayerControl.LocalPlayer != null && HudManager.Instance != null) {
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.setSemiTransparent(true);
- Arsonist.dousedIcons[player.PlayerId] = poolablePlayer;
- playerCounter++;
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ GameData.PlayerInfo data = p.Data;
+ PoolablePlayer player = UnityEngine.Object.Instantiate<PoolablePlayer>(__instance.PlayerPrefab, HudManager.Instance.transform);
+ PlayerControl.SetPlayerMaterialColors(data.ColorId, player.Body);
+ DestroyableSingleton<HatManager>.Instance.SetSkin(player.SkinSlot, data.SkinId);
+ player.HatSlot.SetHat(data.HatId, data.ColorId);
+ PlayerControl.SetPetImage(data.PetId, data.ColorId, player.PetSlot);
+ player.NameText.text = data.PlayerName;
+ player.SetFlipX(true);
+ MapOptions.playerIcons[p.PlayerId] = player;
+
+ if (PlayerControl.LocalPlayer == Arsonist.arsonist && p != Arsonist.arsonist) {
+ player.transform.localPosition = bottomLeft + new Vector3(-0.25f, -0.25f, 0) + Vector3.right * playerCounter++ * 0.35f;
+ player.transform.localScale = Vector3.one * 0.2f;
+ player.setSemiTransparent(true);
+ player.gameObject.SetActive(true);
+ } else if (PlayerControl.LocalPlayer == BountyHunter.bountyHunter) {
+ player.transform.localPosition = bottomLeft + new Vector3(-0.25f, 0f, 0);
+ player.transform.localScale = Vector3.one * 0.4f;
+ player.gameObject.SetActive(false);
+ } else {
+ player.gameObject.SetActive(false);
}
}
}
+
+ // Force Bounty Hunter to load a new Bounty when the Intro is over
+ if (BountyHunter.bounty != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) {
+ BountyHunter.bountyUpdateTimer = 0f;
+ if (HudManager.Instance != null) {
+ Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z) + new Vector3(-0.25f, 1f, 0);
+ BountyHunter.cooldownText = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(HudManager.Instance.KillButton.TimerText, HudManager.Instance.transform);
+ BountyHunter.cooldownText.alignment = TMPro.TextAlignmentOptions.Center;
+ BountyHunter.cooldownText.transform.localPosition = bottomLeft + new Vector3(0f, -1f, -1f);
+ BountyHunter.cooldownText.gameObject.SetActive(true);
+ }
+ }
}
}
}
}
}
+
using UnityEngine;
using System.Linq;
-namespace TheOtherRoles{
-
+namespace TheOtherRoles {
+
public class JackInTheBox {
public static System.Collections.Generic.List<JackInTheBox> AllJackInTheBoxes = new System.Collections.Generic.List<JackInTheBox>();
public static int JackInTheBoxLimit = 3;
public JackInTheBox(Vector2 p) {
gameObject = new GameObject("JackInTheBox");
- Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f);
+ Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f);
position += (Vector3)PlayerControl.LocalPlayer.Collider.offset; // Add collider offset that DoMove moves the player up at a valid position
// Create the marker
gameObject.transform.position = position;
boxRenderer.sprite = getBoxAnimationSprite(0);
// Create the vent
- var referenceVent = UnityEngine.Object.FindObjectOfType<Vent>();
+ var referenceVent = UnityEngine.Object.FindObjectOfType<Vent>();
vent = UnityEngine.Object.Instantiate<Vent>(referenceVent);
vent.transform.position = gameObject.transform.position;
vent.Left = null;
public static void UpdateStates() {
if (boxesConvertedToVents == true) return;
- foreach(var box in AllJackInTheBoxes) {
+ foreach (var box in AllJackInTheBoxes) {
var playerIsTrickster = PlayerControl.LocalPlayer == Trickster.trickster;
box.gameObject.SetActive(playerIsTrickster);
}
}
public static void convertToVents() {
- foreach(var box in AllJackInTheBoxes) {
+ foreach (var box in AllJackInTheBoxes) {
box.convertToVent();
}
connectVents();
}
private static void connectVents() {
- for(var i = 0;i < AllJackInTheBoxes.Count - 1;i++) {
+ for (var i = 0; i < AllJackInTheBoxes.Count - 1; i++) {
var a = AllJackInTheBoxes[i];
var b = AllJackInTheBoxes[i + 1];
a.vent.Right = b.vent;
boxesConvertedToVents = false;
AllJackInTheBoxes = new List<JackInTheBox>();
}
-
+
}
}
\ No newline at end of file
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "2.6.7";
+ public const string VersionString = "2.7.0";
public static System.Version Version = System.Version.Parse(VersionString);
public Harmony Harmony { get; } = new Harmony(Id);
public static ConfigEntry<string> Ip { get; set; }
public static ConfigEntry<ushort> Port { get; set; }
+ public static Sprite ModStamp;
+
public static IRegionInfo[] defaultRegions;
public static void UpdateRegions() {
ServerManager serverManager = DestroyableSingleton<ServerManager>.Instance;
Harmony.PatchAll();
}
+ public static Sprite GetModStamp() {
+ if (ModStamp) return ModStamp;
+ return ModStamp = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ModStamp.png", 150f);
+ }
}
// Deactivate bans, since I always leave my local testing game and ban myself
public static int meetingsCount = 0;
public static List<SurvCamera> camerasToAdd = new List<SurvCamera>();
public static List<Vent> ventsToSeal = new List<Vent>();
+ public static Dictionary<byte, PoolablePlayer> playerIcons = new Dictionary<byte, PoolablePlayer>();
- public static void clearAndReloadMapOptions() {
+public static void clearAndReloadMapOptions() {
meetingsCount = 0;
camerasToAdd = new List<SurvCamera>();
ventsToSeal = new List<Vent>();
+ playerIcons = new Dictionary<byte, PoolablePlayer>(); ;
maxNumberOfMeetings = Mathf.RoundToInt(CustomOptionHolder.maxNumberOfMeetings.getSelection());
blockSkippingInEmergencyMeetings = CustomOptionHolder.blockSkippingInEmergencyMeetings.getBool();
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))]
class MeetingCalculateVotesPatch {
- private static byte[] calculateVotes(MeetingHud __instance) {
- byte[] array = new byte[16];
- for (int i = 0; i < __instance.playerStates.Length; i++)
- {
+ private static Dictionary<byte, int> CalculateVotes(MeetingHud __instance) {
+ Dictionary<byte, int> dictionary = new Dictionary<byte, int>();
+ for (int i = 0; i < __instance.playerStates.Length; i++) {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
- if (playerVoteArea.didVote)
- {
- int num = (int)(playerVoteArea.votedFor + 1);
- if (num >= 0 && num < array.Length)
- {
- byte[] array2 = array;
- int num2 = num;
- // Mayor count vote twice
- if (Mayor.mayor != null && playerVoteArea.TargetPlayerId == (sbyte)Mayor.mayor.PlayerId)
- array2[num2] += 2;
- else
- array2[num2] += 1;
- }
+ if (playerVoteArea.VotedFor != 252 && playerVoteArea.VotedFor != 255 && playerVoteArea.VotedFor != 254) {
+ PlayerControl player = Helpers.playerById((byte)playerVoteArea.TargetPlayerId);
+ if (player == null || player.Data == null || player.Data.IsDead || player.Data.Disconnected) continue;
+
+ int currentVotes;
+ int additionalVotes = (Mayor.mayor != null && Mayor.mayor.PlayerId == playerVoteArea.TargetPlayerId) ? 2 : 1; // Mayor vote
+ if (dictionary.TryGetValue(playerVoteArea.VotedFor, out currentVotes))
+ dictionary[playerVoteArea.VotedFor] = currentVotes + additionalVotes;
+ else
+ dictionary[playerVoteArea.VotedFor] = additionalVotes;
}
}
-
// Swapper swap votes
PlayerVoteArea swapped1 = null;
PlayerVoteArea swapped2 = null;
-
foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) {
if (playerVoteArea.TargetPlayerId == Swapper.playerId1) swapped1 = playerVoteArea;
if (playerVoteArea.TargetPlayerId == Swapper.playerId2) swapped2 = playerVoteArea;
}
- if (swapped1 != null && swapped2 != null && swapped1.TargetPlayerId + 1 >= 0 && swapped1.TargetPlayerId + 1 < array.Length && swapped2.TargetPlayerId + 1 >= 0 && swapped2.TargetPlayerId + 1 < array.Length) {
- byte tmp = array[swapped1.TargetPlayerId + 1];
- array[swapped1.TargetPlayerId + 1] = array[swapped2.TargetPlayerId + 1];
- array[swapped2.TargetPlayerId + 1] = tmp;
+ if (swapped1 != null && swapped2 != null && dictionary.ContainsKey(swapped1.TargetPlayerId) && dictionary.ContainsKey(swapped2.TargetPlayerId)) {
+ int tmp = dictionary[swapped1.TargetPlayerId];
+ dictionary[swapped1.TargetPlayerId] = dictionary[swapped2.TargetPlayerId];
+ dictionary[swapped2.TargetPlayerId] = tmp;
}
- return array;
- }
- private static int IndexOfMax(byte[] self, Func<byte, int> comparer, out bool tie) {
- tie = false;
- int num = int.MinValue;
- int result = -1;
- for (int i = 0; i < self.Length; i++)
- {
- int num2 = comparer(self[i]);
- if (num2 > num)
- {
- result = i;
- num = num2;
- tie = false;
- }
- else if (num2 == num)
- {
- tie = true;
- result = -1;
- }
- }
- return result;
+ return dictionary;
}
+
static bool Prefix(MeetingHud __instance) {
- if (__instance.playerStates.All((PlayerVoteArea ps) => ps.isDead || ps.didVote)) {
+ if (__instance.playerStates.All((PlayerVoteArea ps) => ps.AmDead || ps.DidVote)) {
// If skipping is disabled, replace skipps/no-votes with self vote
if (target == null && blockSkippingInEmergencyMeetings && noVoteIsSelfVote) {
foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) {
- if (playerVoteArea.votedFor < 0) playerVoteArea.votedFor = playerVoteArea.TargetPlayerId; // TargetPlayerId
+ if (playerVoteArea.VotedFor < 0) playerVoteArea.VotedFor = playerVoteArea.TargetPlayerId; // TargetPlayerId
}
}
- byte[] self = calculateVotes(__instance);
+ Dictionary<byte, int> self = CalculateVotes(__instance);
bool tie;
- int maxIdx = IndexOfMax(self, (byte p) => (int)p, out tie) - 1;
- GameData.PlayerInfo exiled = null;
- foreach (GameData.PlayerInfo pi in GameData.Instance.AllPlayers) {
- if (pi.PlayerId == maxIdx && !pi.IsDead) {
- exiled = pi;
- break;
- }
- }
- byte[] array = new byte[15];
- for (int i = 0; i < __instance.playerStates.Length; i++) {
+ KeyValuePair<byte, int> max = self.MaxPair(out tie);
+ GameData.PlayerInfo exiled = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(v => !tie && v.PlayerId == max.Key && !v.IsDead);
+
+ MeetingHud.VoterState[] array = new MeetingHud.VoterState[__instance.playerStates.Length];
+ for (int i = 0; i < __instance.playerStates.Length; i++)
+ {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
- array[(int)playerVoteArea.TargetPlayerId] = playerVoteArea.GetState();
+ array[i] = new MeetingHud.VoterState {
+ VoterId = playerVoteArea.TargetPlayerId,
+ VotedForId = playerVoteArea.VotedFor
+ };
}
// RPCVotingComplete
- if (AmongUsClient.Instance.AmClient)
- __instance.VotingComplete(array, exiled, tie);
- MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(__instance.NetId, 23, Hazel.SendOption.Reliable);
- messageWriter.WriteBytesAndSize(array);
- messageWriter.Write((exiled != null) ? exiled.PlayerId : byte.MaxValue);
- messageWriter.Write(tie);
- messageWriter.EndMessage();
+ __instance.RpcVotingComplete(array, exiled, tie);
}
return false;
}
}
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.BloopAVoteIcon))]
+ class MeetingHudBloopAVoteIconPatch {
+ public static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]GameData.PlayerInfo voterPlayer, [HarmonyArgument(1)]int index, [HarmonyArgument(2)]Transform parent) {
+ SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate<SpriteRenderer>(__instance.PlayerVotePrefab);
+ if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes))
+ PlayerControl.SetPlayerMaterialColors(voterPlayer.ColorId, spriteRenderer);
+ else
+ PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer);
+ spriteRenderer.transform.SetParent(parent);
+ spriteRenderer.transform.localScale = Vector3.zero;
+ __instance.StartCoroutine(Effects.Bloop((float)index * 0.3f, spriteRenderer.transform, 1f, 0.5f));
+ parent.GetComponent<VoteSpreader>().AddVote(spriteRenderer);
+ return false;
+ }
+ }
+
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.PopulateResults))]
- class MeetingPopulateVotesPatch {
- static bool Prefix(MeetingHud __instance, [HarmonyArgument(0)]Il2CppStructArray<byte> states)
- {
- // Swapper swap votes
+ class MeetingHudPopulateVotesPatch {
+
+ static bool Prefix(MeetingHud __instance, Il2CppStructArray<MeetingHud.VoterState> states) {
+ // Swapper swap
PlayerVoteArea swapped1 = null;
PlayerVoteArea swapped2 = null;
-
foreach (PlayerVoteArea playerVoteArea in __instance.playerStates) {
if (playerVoteArea.TargetPlayerId == Swapper.playerId1) swapped1 = playerVoteArea;
if (playerVoteArea.TargetPlayerId == Swapper.playerId2) swapped2 = playerVoteArea;
}
-
bool doSwap = swapped1 != null && swapped2 != null;
- float delay = 0f;
if (doSwap) {
- delay = 2f;
- __instance.StartCoroutine(Effects.Slide3D(swapped1.transform, swapped1.transform.localPosition, swapped2.transform.localPosition, 2f));
- __instance.StartCoroutine(Effects.Slide3D(swapped2.transform, swapped2.transform.localPosition, swapped1.transform.localPosition, 2f));
+ __instance.StartCoroutine(Effects.Slide3D(swapped1.transform, swapped1.transform.localPosition, swapped2.transform.localPosition, 1.5f));
+ __instance.StartCoroutine(Effects.Slide3D(swapped2.transform, swapped2.transform.localPosition, swapped1.transform.localPosition, 1.5f));
}
- float votesXOffset = 0f;
- float votesFinalSize = 1f;
- if (__instance.playerStates.Length > 10) {
- votesXOffset = 0.1f;
- votesFinalSize = scale;
- }
- // Mayor display vote twice
__instance.TitleText.text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.MeetingVotingResults, new Il2CppReferenceArray<Il2CppSystem.Object>(0));
int num = 0;
- for (int i = 0; i < __instance.playerStates.Length; i++)
- {
+ for (int i = 0; i < __instance.playerStates.Length; i++) {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
+ byte targetPlayerId = playerVoteArea.TargetPlayerId;
+ // Swapper change playerVoteArea that gets the votes
+ if (doSwap && playerVoteArea.TargetPlayerId == swapped1.TargetPlayerId) playerVoteArea = swapped2;
+ else if (doSwap && playerVoteArea.TargetPlayerId == swapped2.TargetPlayerId) playerVoteArea = swapped1;
+
playerVoteArea.ClearForResults();
int num2 = 0;
bool mayorFirstVoteDisplayed = false;
+ for (int j = 0; j < states.Length; j++) {
+ MeetingHud.VoterState voterState = states[j];
+ GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById(voterState.VoterId);
+ if (playerById == null) {
+ Debug.LogError(string.Format("Couldn't find player info for voter: {0}", voterState.VoterId));
+ } else if (i == 0 && voterState.SkippedVote && !playerById.IsDead) {
+ __instance.BloopAVoteIcon(playerById, num, __instance.SkippedVoting.transform);
+ num++;
+ }
+ else if (voterState.VotedForId == targetPlayerId && !playerById.IsDead) {
+ __instance.BloopAVoteIcon(playerById, num2, playerVoteArea.transform);
+ num2++;
+ }
- for (int j = 0; j < __instance.playerStates.Length; j++)
- {
- PlayerVoteArea playerVoteArea2 = __instance.playerStates[j];
- byte self = states[(int)playerVoteArea2.TargetPlayerId];
-
- if (!((self & 128) > 0))
- {
- GameData.PlayerInfo playerById = GameData.Instance.GetPlayerById((byte)playerVoteArea2.TargetPlayerId);
- int votedFor = (int)PlayerVoteArea.GetVotedFor(self);
- if (votedFor == (int)playerVoteArea.TargetPlayerId)
- {
- SpriteRenderer spriteRenderer = UnityEngine.Object.Instantiate<SpriteRenderer>(__instance.PlayerVotePrefab);
- if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes))
- PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer);
- else
- PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer);
-
- spriteRenderer.transform.SetParent(playerVoteArea.transform);
- spriteRenderer.transform.localPosition = __instance.CounterOrigin + new Vector3(votesXOffset + __instance.CounterOffsets.x * (float)(num2), 0f, 0f);
- spriteRenderer.transform.localScale = Vector3.zero;
- spriteRenderer.transform.SetParent(playerVoteArea.transform.parent); // Reparent votes so they don't move with their playerVoteArea
- __instance.StartCoroutine(Effects.Bloop((float)(num2) * 0.5f + delay, spriteRenderer.transform, votesFinalSize, 0.5f));
- num2++;
- }
- else if (i == 0 && votedFor == -1)
- {
- SpriteRenderer spriteRenderer2 = UnityEngine.Object.Instantiate<SpriteRenderer>(__instance.PlayerVotePrefab);
- if (!PlayerControl.GameOptions.AnonymousVotes || (PlayerControl.LocalPlayer.Data.IsDead && MapOptions.ghostsSeeVotes))
- PlayerControl.SetPlayerMaterialColors((int)playerById.ColorId, spriteRenderer2);
- else
- PlayerControl.SetPlayerMaterialColors(Palette.DisabledGrey, spriteRenderer2);
- spriteRenderer2.transform.SetParent(__instance.SkippedVoting.transform);
- spriteRenderer2.transform.localPosition = __instance.CounterOrigin + new Vector3(votesXOffset + __instance.CounterOffsets.x * (float)(num), 0f, 0f);
- spriteRenderer2.transform.localScale = Vector3.zero;
- spriteRenderer2.transform.SetParent(playerVoteArea.transform.parent); // Reparent votes so they don't move with their playerVoteArea
- __instance.StartCoroutine(Effects.Bloop((float)num * 0.5f + delay, spriteRenderer2.transform, votesFinalSize, 0.5f));
- num++;
- }
-
- // Major vote, redo this iteration to place a second vote
- if (Mayor.mayor != null && playerVoteArea2.TargetPlayerId == (sbyte)Mayor.mayor.PlayerId && !mayorFirstVoteDisplayed) {
- mayorFirstVoteDisplayed = true;
- j--;
- }
+ // Major vote, redo this iteration to place a second vote
+ if (Mayor.mayor != null && voterState.VoterId == (sbyte)Mayor.mayor.PlayerId && !mayorFirstVoteDisplayed) {
+ mayorFirstVoteDisplayed = true;
+ j--;
}
}
}
-
return false;
}
}
static void swapperOnClick(int i, MeetingHud __instance) {
if (__instance.state == MeetingHud.VoteStates.Results) return;
- if (__instance.playerStates[i].isDead) return;
+ if (__instance.playerStates[i].AmDead) return;
int selectedCount = selections.Where(b => b).Count();
SpriteRenderer renderer = renderers[i];
var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton");
var textTemplate = __instance.playerStates[0].NameText;
-
Transform exitButton = UnityEngine.Object.Instantiate(smallButtonTemplate.transform, container);
- exitButton.transform.localPosition = new Vector3(2.5f, 2.25f, -5);
+ exitButton.transform.localPosition = new Vector3(-2.725f, 2.1f, -10f);
exitButton.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
UnityEngine.Object.Destroy(container.gameObject);
}));
- List<Transform> confirmButtons = new List<Transform>();
+ List<Transform> buttons = new List<Transform>();
+ Transform selectedButton = null;
foreach (RoleInfo roleInfo in RoleInfo.allRoleInfos) {
if (roleInfo.roleId == RoleId.Lover || roleInfo.roleId == RoleId.Guesser || roleInfo == RoleInfo.niceMini) continue; // Not guessable roles
Transform button = UnityEngine.Object.Instantiate(buttonTemplate.transform, container);
- Transform confirm = UnityEngine.Object.Instantiate(smallButtonTemplate.transform, button);
- confirmButtons.Add(confirm);
+ buttons.Add(button);
TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button);
int row = i/4, col = i%4;
- button.localPosition = new Vector3(-3 + 1.83f * col, 1.5f - 0.4f * row, -5);
- button.localScale = new Vector3(0.4f, 0.4f, 1f);
- confirm.localScale = new Vector3(1.5f, 1.5f, 1f);
- confirm.localPosition = new Vector3(0, 0, confirm.localPosition.z);
- confirm.GetComponent<SpriteRenderer>().sprite = Guesser.getTargetSprite();
- confirm.GetComponent<SpriteRenderer>().color = Color.black;
- confirm.gameObject.SetActive(false);
+ button.localPosition = new Vector3(-2.725f + 1.83f * col, 1.5f - 0.45f * row, -5);
+ button.localScale = new Vector3(0.55f, 0.55f, 1f);
label.text = Helpers.cs(roleInfo.color, roleInfo.name);
label.alignment = TMPro.TextAlignmentOptions.Center;
label.transform.localPosition = new Vector3(0, 0, label.transform.localPosition.z);
- label.transform.localScale *= 2;
+ label.transform.localScale *= 1.7f;
int copiedIndex = i;
button.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
button.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
- confirmButtons.ForEach(x => x.gameObject.SetActive(false));
- confirm.gameObject.SetActive(true);
- }));
-
-
- confirm.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
- confirm.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
- PlayerControl target = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId);
- if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || target == null || Guesser.remainingShots <= 0 ) return;
+ if (selectedButton != button) {
+ selectedButton = button;
+ buttons.ForEach(x => x.GetComponent<SpriteRenderer>().color = x == selectedButton ? Color.red : Color.white);
+ } else {
+ PlayerControl target = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId);
+ if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || target == null || Guesser.remainingShots <= 0 ) return;
- var mainRoleInfo = RoleInfo.getRoleInfoForPlayer(target).FirstOrDefault();
- if (mainRoleInfo == null) return;
+ var mainRoleInfo = RoleInfo.getRoleInfoForPlayer(target).FirstOrDefault();
+ if (mainRoleInfo == null) return;
- target = (mainRoleInfo == roleInfo) ? target : PlayerControl.LocalPlayer;
+ target = (mainRoleInfo == roleInfo) ? target : PlayerControl.LocalPlayer;
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuesserShoot, Hazel.SendOption.Reliable, -1);
- writer.Write(target.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.guesserShoot(target.PlayerId);
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuesserShoot, Hazel.SendOption.Reliable, -1);
+ writer.Write(target.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.guesserShoot(target.PlayerId);
- UnityEngine.Object.Destroy(container.gameObject);
- __instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); });
+ UnityEngine.Object.Destroy(container.gameObject);
+ __instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); });
+ }
}));
i++;
for (int i = 0; i < __instance.playerStates.Length; i++) {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
- if (playerVoteArea.isDead || (playerVoteArea.TargetPlayerId == Swapper.swapper.PlayerId && Swapper.canOnlySwapOthers)) continue;
+ if (playerVoteArea.AmDead || (playerVoteArea.TargetPlayerId == Swapper.swapper.PlayerId && Swapper.canOnlySwapOthers)) continue;
GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject;
GameObject checkbox = UnityEngine.Object.Instantiate(template);
checkbox.transform.SetParent(playerVoteArea.transform);
checkbox.transform.position = template.transform.position;
- checkbox.transform.localPosition = new Vector3(0f, 0.03f, template.transform.localPosition.z);
+ checkbox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f);
SpriteRenderer renderer = checkbox.GetComponent<SpriteRenderer>();
renderer.sprite = Swapper.getCheckSprite();
renderer.color = Color.red;
if (Guesser.guesser != null && PlayerControl.LocalPlayer == Guesser.guesser && !Guesser.guesser.Data.IsDead && Guesser.remainingShots >= 0) {
for (int i = 0; i < __instance.playerStates.Length; i++) {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
- if (playerVoteArea.isDead || playerVoteArea.TargetPlayerId == Guesser.guesser.PlayerId) continue;
+ if (playerVoteArea.AmDead || playerVoteArea.TargetPlayerId == Guesser.guesser.PlayerId) continue;
GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject;
GameObject targetBox = UnityEngine.Object.Instantiate(template, playerVoteArea.transform);
targetBox.name = "ShootButton";
- targetBox.transform.localPosition = new Vector3(0f, 0.03f, template.transform.localPosition.z);
+ targetBox.transform.localPosition = new Vector3(-0.95f, 0.03f, -1f);
SpriteRenderer renderer = targetBox.GetComponent<SpriteRenderer>();
renderer.sprite = Guesser.getTargetSprite();
PassiveButton button = targetBox.GetComponent<PassiveButton>();
button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => guesserOnClick(copiedIndex, __instance)));
}
}
-
- // Change buttons if there are more than 10 players
- if (__instance.playerStates != null && __instance.playerStates.Length > 10) {
- PlayerVoteArea[] playerStates = __instance.playerStates.OrderBy((PlayerVoteArea p) => p.isDead ? 50 : 0)
- .ThenBy((PlayerVoteArea p) => p.TargetPlayerId)
- .ToArray<PlayerVoteArea>();
- for (int i = 0; i < playerStates.Length; i++) {
- PlayerVoteArea area = playerStates[i];
-
- int row = i/3, col = i%3;
-
- // Update scalings
- area.Overlay.transform.localScale = area.PlayerButton.transform.localScale = new Vector3(1, 1/scale, 1);
- area.NameText.transform.localScale = new Vector3(1/scale, 1/scale, 1);
- area.gameObject.transform.localScale = new Vector3(scale, scale, 1);
- GameObject megaphoneWrapper = new GameObject();
- megaphoneWrapper.transform.SetParent(area.transform);
- megaphoneWrapper.transform.localScale = Vector3.one * 1/scale;
- area.Megaphone.transform.SetParent(megaphoneWrapper.transform);
-
- // Update positions
- area.Megaphone.transform.localPosition += Vector3.left * 0.1f;
- area.NameText.transform.localPosition += new Vector3(0.25f, 0.043f, 0f);
- area.transform.localPosition = new Vector3(-3.63f + 2.43f * col, 1.5f - 0.76f * row, -0.9f - 0.01f * row);
- }
- }
}
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.ServerStart))]
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;
}
foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
if (p != PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) continue;
- Transform playerInfoTransform = p.transform.FindChild("Info");
+ Transform playerInfoTransform = p.nameText.transform.parent.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.transform.localPosition += Vector3.up * 0.5f;
playerInfo.fontSize *= 0.75f;
playerInfo.gameObject.name = "Info";
}
PlayerVoteArea playerVoteArea = MeetingHud.Instance?.playerStates?.FirstOrDefault(x => x.TargetPlayerId == p.PlayerId);
- Transform meetingInfoTransform = playerVoteArea != null ? playerVoteArea.transform.FindChild("Info") : null;
+ Transform meetingInfoTransform = playerVoteArea != null ? playerVoteArea.NameText.transform.parent.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.transform.localPosition += Vector3.down * 0.20f;
meetingInfo.fontSize *= 0.75f;
meetingInfo.gameObject.name = "Info";
}
if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color);
}
+ static void snitchUpdate()
+ {
+ if (Snitch.localArrows == null) return;
+
+ foreach (Arrow arrow in Snitch.localArrows) arrow.arrow.SetActive(false);
+
+ if (Snitch.snitch == null || Snitch.snitch.Data.IsDead) return;
+
+ var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data);
+ int numberOfTasks = playerTotal - playerCompleted;
+
+ if (PlayerControl.LocalPlayer.Data.IsImpostor && numberOfTasks <= Snitch.taskCountForImpostors)
+ {
+ if (Snitch.localArrows.Count == 0) Snitch.localArrows.Add(new Arrow(Color.blue));
+ if (Snitch.localArrows.Count != 0 && Snitch.localArrows[0] != null)
+ {
+ Snitch.localArrows[0].arrow.SetActive(true);
+ Snitch.localArrows[0].Update(Snitch.snitch.transform.position);
+ }
+ }
+ else if (PlayerControl.LocalPlayer == Snitch.snitch && numberOfTasks == 0)
+ {
+ int arrowIndex = 0;
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls)
+ {
+ if (p.Data.IsImpostor && !p.Data.IsDead)
+ {
+ if (arrowIndex >= Snitch.localArrows.Count) Snitch.localArrows.Add(new Arrow(Color.blue));
+ if (arrowIndex < Snitch.localArrows.Count && Snitch.localArrows[arrowIndex] != null)
+ {
+ Snitch.localArrows[arrowIndex].arrow.SetActive(true);
+ Snitch.localArrows[arrowIndex].Update(p.transform.position);
+ }
+ arrowIndex++;
+ }
+ }
+ }
+ }
+
+ static void bountyHunterUpdate() {
+ if (BountyHunter.bountyHunter == null || PlayerControl.LocalPlayer != BountyHunter.bountyHunter) return;
+
+ if (BountyHunter.bountyHunter.Data.IsDead) {
+ if (BountyHunter.arrow != null || BountyHunter.arrow.arrow != null) UnityEngine.Object.Destroy(BountyHunter.arrow.arrow);
+ BountyHunter.arrow = null;
+ if (BountyHunter.cooldownText != null && BountyHunter.cooldownText.gameObject != null) UnityEngine.Object.Destroy(BountyHunter.cooldownText.gameObject);
+ BountyHunter.cooldownText = null;
+ BountyHunter.bounty = null;
+ foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
+ if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
+ }
+ return;
+ }
+
+ BountyHunter.arrowUpdateTimer -= Time.fixedDeltaTime;
+ BountyHunter.bountyUpdateTimer -= Time.fixedDeltaTime;
+
+ if (BountyHunter.bounty == null || BountyHunter.bountyUpdateTimer <= 0f) {
+ // Set new bounty
+ BountyHunter.bounty = null;
+ BountyHunter.arrowUpdateTimer = 0f; // Force arrow to update
+ BountyHunter.bountyUpdateTimer = BountyHunter.bountyDuration;
+ var possibleTargets = new List<PlayerControl>();
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ if (!p.Data.IsDead && !p.Data.Disconnected && p != p.Data.IsImpostor && p != Spy.spy && (p != Mini.mini || Mini.isGrownUp())) possibleTargets.Add(p);
+ }
+ BountyHunter.bounty = possibleTargets[TheOtherRoles.rnd.Next(0, possibleTargets.Count)];
+ if (BountyHunter.bounty == null) return;
+
+ // Show poolable player
+ if (HudManager.Instance != null && HudManager.Instance.UseButton != null) {
+ foreach (PoolablePlayer pp in MapOptions.playerIcons.Values) pp.gameObject.SetActive(false);
+ if (MapOptions.playerIcons.ContainsKey(BountyHunter.bounty.PlayerId) && MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject != null)
+ MapOptions.playerIcons[BountyHunter.bounty.PlayerId].gameObject.SetActive(true);
+ }
+ }
+
+ // Update Cooldown Text
+ if (BountyHunter.cooldownText != null) {
+ BountyHunter.cooldownText.text = Mathf.CeilToInt(Mathf.Clamp(BountyHunter.bountyUpdateTimer, 0, BountyHunter.bountyDuration)).ToString();
+ }
+
+ // Update Arrow
+ if (BountyHunter.showArrow && BountyHunter.bounty != null) {
+ if (BountyHunter.arrow == null) BountyHunter.arrow = new Arrow(Color.red);
+ if (BountyHunter.arrowUpdateTimer <= 0f) {
+ BountyHunter.arrow.Update(BountyHunter.bounty.transform.position);
+ BountyHunter.arrowUpdateTimer = BountyHunter.arrowUpdateIntervall;
+ }
+ BountyHunter.arrow.Update();
+ }
+ }
+
public static void Postfix(PlayerControl __instance) {
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
securityGuardSetTarget();
// Arsonist
arsonistSetTarget();
+ // Snitch
+ snitchUpdate();
+ // BountyHunter
+ bountyHunterUpdate();
}
}
}
class RpcMurderPlayer {
public static bool Prefix([HarmonyArgument(0)]PlayerControl target) {
if (Helpers.handleMurderAttempt(target)) { // Custom checks
- if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini) { // Not checked by official servers
+ if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini || BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) { // Not checked by official servers
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
writer.Write(PlayerControl.LocalPlayer.PlayerId);
writer.Write(target.PlayerId);
var multiplier = Mini.isGrownUp() ? 0.66f : 2f;
Mini.mini.SetKillTimer(PlayerControl.GameOptions.KillCooldown * multiplier);
}
+
+ // Set bountyHunter cooldown
+ if (BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter && __instance == BountyHunter.bountyHunter) {
+ if (target == BountyHunter.bounty) {
+ BountyHunter.bountyHunter.SetKillTimer(BountyHunter.bountyKillCooldown);
+ BountyHunter.bountyUpdateTimer = 0f; // Force bounty update
+ }
+ else
+ BountyHunter.bountyHunter.SetKillTimer(PlayerControl.GameOptions.KillCooldown + BountyHunter.punishmentTime);
+ }
}
}
public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)]float time) {
if (PlayerControl.GameOptions.KillCooldown <= 0f) return false;
float multiplier = 1f;
+ float addition = 0f;
if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini && Mini.mini.Data.IsImpostor) multiplier = Mini.isGrownUp() ? 0.66f : 2f;
+ if (BountyHunter.bountyHunter != null && PlayerControl.LocalPlayer == BountyHunter.bountyHunter) addition = BountyHunter.punishmentTime;
- __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier);
- DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier);
+ __instance.killTimer = Mathf.Clamp(time, 0f, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
+ DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
return false;
}
}
SecurityGuard,
Arsonist,
Guesser,
+ BountyHunter,
Crewmate,
Impostor
}
ShareOptionSelection,
ForceEnd,
SetRole,
- SetUncheckedColor,
VersionHandshake,
UseUncheckedVent,
UncheckedMurderPlayer,
- OpenToiletDoor,
// Role functionality
EngineerFixLights = 81,
case RoleId.Guesser:
Guesser.guesser = player;
break;
+ case RoleId.BountyHunter:
+ BountyHunter.bountyHunter = player;
+ break;
}
}
}
- public static void setUncheckedColor(byte colorId, byte playerId) {
- var player = Helpers.playerById(playerId);
- if (player != null) player.SetColor(colorId);
- }
-
public static void versionHandshake(int major, int minor, int build, int revision, Guid guid, int clientId) {
System.Version ver;
if (revision < 0)
return;
}
- // Switch shield
- if (Medic.shielded != null && Medic.shielded == player) {
- Medic.shielded = oldShifter;
- } else if (Medic.shielded != null && Medic.shielded == oldShifter) {
- Medic.shielded = player;
- }
-
- // Shift Lovers Role
- if (Lovers.lover1 != null && oldShifter == Lovers.lover1) Lovers.lover1 = player;
- else if (Lovers.lover1 != null && player == Lovers.lover1) Lovers.lover1 = oldShifter;
+ if (Shifter.shiftModifiers) {
+ // Switch shield
+ if (Medic.shielded != null && Medic.shielded == player) {
+ Medic.shielded = oldShifter;
+ } else if (Medic.shielded != null && Medic.shielded == oldShifter) {
+ Medic.shielded = player;
+ }
+ // Shift Lovers Role
+ if (Lovers.lover1 != null && oldShifter == Lovers.lover1) Lovers.lover1 = player;
+ else if (Lovers.lover1 != null && player == Lovers.lover1) Lovers.lover1 = oldShifter;
- if (Lovers.lover2 != null && oldShifter == Lovers.lover2) Lovers.lover2 = player;
- else if (Lovers.lover2 != null && player == Lovers.lover2) Lovers.lover2 = oldShifter;
+ if (Lovers.lover2 != null && oldShifter == Lovers.lover2) Lovers.lover2 = player;
+ else if (Lovers.lover2 != null && player == Lovers.lover2) Lovers.lover2 = oldShifter;
+ }
// Shift role
if (Mayor.mayor != null && Mayor.mayor == player)
{
if (player.PlayerId == targetId)
{
- if(!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) {
+ if (!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) {
Jackal.fakeSidekick = player;
- return;
+ } else {
+ player.RemoveInfected();
+ erasePlayerRoles(player.PlayerId, true);
+ Sidekick.sidekick = player;
}
- player.RemoveInfected();
- erasePlayerRoles(player.PlayerId, true);
-
- Sidekick.sidekick = player;
+ Jackal.canCreateSidekick = false;
return;
}
}
}
public static void sidekickPromotes() {
- var player = Sidekick.sidekick;
Jackal.removeCurrentJackal();
- Jackal.jackal = player;
- if (Jackal.jackalPromotedFromSidekickCanCreateSidekick == false) {
- Jackal.canCreateSidekick = false;
- }
+ Jackal.jackal = Sidekick.sidekick;
+ Jackal.canCreateSidekick = Jackal.jackalPromotedFromSidekickCanCreateSidekick;
Sidekick.clearAndReload();
return;
}
}
}
if (player == Sidekick.sidekick) Sidekick.clearAndReload();
+ if (player == BountyHunter.bountyHunter) BountyHunter.clearAndReload();
}
public static void setFutureErased(byte playerId) {
PlayerControl player = Helpers.playerById(playerId);
- if (Eraser.futureErased == null) Eraser.futureErased = new List<PlayerControl>();
- if (player != null) Eraser.futureErased.Add(player);
+ if (Eraser.futureErased == null)
+ Eraser.futureErased = new List<PlayerControl>();
+ if (player != null) {
+ Eraser.futureErased.Add(player);
+ }
}
public static void setFutureShifted(byte playerId) {
PlayerControl target = Helpers.playerById(playerId);
if (target == null) return;
target.Exiled();
+ PlayerControl partner = target.getPartner(); // Lover check
+ byte partnerId = partner != null ? partner.PlayerId : playerId;
Guesser.remainingShots = Mathf.Max(0, Guesser.remainingShots - 1);
if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(target.KillSfx, false, 0.8f);
if (MeetingHud.Instance) {
foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) {
- if (pva.TargetPlayerId == playerId) {
- pva.SetDead(playerId == PlayerControl.LocalPlayer.PlayerId, pva.didReport, true);
+ if (pva.TargetPlayerId == playerId || pva.TargetPlayerId == partnerId) {
+ pva.SetDead(pva.DidReport, true);
pva.Overlay.gameObject.SetActive(true);
- pva.Overlay.transform.GetChild(0).gameObject.SetActive(true);
}
}
- if (AmongUsClient.Instance.AmHost) MeetingHud.Instance.CheckForEndVoting();
+ if (AmongUsClient.Instance.AmHost)
+ MeetingHud.Instance.CheckForEndVoting();
}
- if (HudManager.Instance != null && Guesser.guesser != null && PlayerControl.LocalPlayer == target)
- HudManager.Instance.KillOverlay.ShowOne(Guesser.guesser.Data, target.Data);
+ if (HudManager.Instance != null && Guesser.guesser != null)
+ if (PlayerControl.LocalPlayer == target)
+ HudManager.Instance.KillOverlay.ShowKillAnimation(Guesser.guesser.Data, target.Data);
+ else if (partner != null && PlayerControl.LocalPlayer == partner)
+ HudManager.Instance.KillOverlay.ShowKillAnimation(partner.Data, partner.Data);
}
- }
+ }
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))]
class RPCHandlerPatch
byte flag = reader.ReadByte();
RPCProcedure.setRole(roleId, playerId, flag);
break;
- case (byte)CustomRPC.SetUncheckedColor:
- byte c = reader.ReadByte();
- byte p = reader.ReadByte();
- RPCProcedure.setUncheckedColor(c, p);
- break;
case (byte)CustomRPC.VersionHandshake:
byte major = reader.ReadByte();
byte minor = reader.ReadByte();
int versionOwnerId = reader.ReadPackedInt32();
byte revision = 0xFF;
Guid guid;
- if (reader.Length >= 24) {
+ if (reader.Length - reader.Position >= 17) { // enough bytes left to read
revision = reader.ReadByte();
// GUID
byte[] gbytes = reader.ReadBytes(16);
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:
private static void assignRoles() {
var data = getRoleAssignmentData();
assignSpecialRoles(data); // Assign special roles like mafia and lovers first as they assign a role to multiple players and the chances are independent of the ticket system
- if (Mini.mini != null) // Remove Spy from assigns, if Mini exists
- data.crewSettings.Remove((byte)RoleId.Spy);
+ selectFactionForFactionIndependentRoles(data);
assignEnsuredRoles(data); // Assign roles that should always be in the game next
assignChanceRoles(data); // Assign roles that may or may not be in the game last
}
impSettings.Add((byte)RoleId.Trickster, CustomOptionHolder.tricksterSpawnRate.getSelection());
impSettings.Add((byte)RoleId.Cleaner, CustomOptionHolder.cleanerSpawnRate.getSelection());
impSettings.Add((byte)RoleId.Warlock, CustomOptionHolder.warlockSpawnRate.getSelection());
+ impSettings.Add((byte)RoleId.BountyHunter, CustomOptionHolder.bountyHunterSpawnRate.getSelection());
neutralSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection());
neutralSettings.Add((byte)RoleId.Arsonist, CustomOptionHolder.arsonistSpawnRate.getSelection());
setRoleToRandomPlayer((byte)RoleId.Mafioso, data.impostors);
data.maxImpostorRoles -= 3;
}
+ }
- // Assign Mini
- if (rnd.Next(1, 101) <= CustomOptionHolder.miniSpawnRate.getSelection() * 10) {
- if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) {
- setRoleToRandomPlayer((byte)RoleId.Mini, data.impostors);
- data.maxImpostorRoles--;
- } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
- setRoleToRandomPlayer((byte)RoleId.Mini, data.crewmates);
- data.maxCrewmateRoles--;
- }
+ private static void selectFactionForFactionIndependentRoles(RoleAssignmentData data) {
+ // Assign Mini (33% chance impostor / 67% chance crewmate)
+ if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= 33) {
+ data.impSettings.Add((byte)RoleId.Mini, CustomOptionHolder.miniSpawnRate.getSelection());
+ } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
+ data.crewSettings.Add((byte)RoleId.Mini, CustomOptionHolder.miniSpawnRate.getSelection());
}
- // Assign Guesser
- if (rnd.Next(1, 101) <= CustomOptionHolder.guesserSpawnRate.getSelection() * 10) {
- if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10) {
- setRoleToRandomPlayer((byte)RoleId.Guesser, data.impostors);
- data.maxImpostorRoles--;
- } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
- setRoleToRandomPlayer((byte)RoleId.Guesser, data.crewmates);
- data.maxCrewmateRoles--;
- }
+ // Assign Guesser (chance to be impostor based on setting)
+ if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && rnd.Next(1, 101) <= CustomOptionHolder.guesserIsImpGuesserRate.getSelection() * 10) {
+ data.impSettings.Add((byte)RoleId.Guesser, CustomOptionHolder.guesserSpawnRate.getSelection());
+ } else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
+ data.crewSettings.Add((byte)RoleId.Guesser, CustomOptionHolder.guesserSpawnRate.getSelection());
}
}
public static RoleInfo trickster = new RoleInfo("Trickster", Trickster.color, "Use your jack-in-the-boxes to surprise others", "Surprise your enemies", RoleId.Trickster);
public static RoleInfo cleaner = new RoleInfo("Cleaner", Cleaner.color, "Kill everyone and leave no traces", "Clean up dead bodies", RoleId.Cleaner);
public static RoleInfo warlock = new RoleInfo("Warlock", Warlock.color, "Curse other players and kill everyone", "Curse and kill everyone", RoleId.Warlock);
+ public static RoleInfo bountyHunter = new RoleInfo("Bounty Hunter", BountyHunter.color, "Hunt your Bounty down", "Hunt your Bounty down", RoleId.BountyHunter);
public static RoleInfo detective = new RoleInfo("Detective", Detective.color, "Find the <color=#FF1919FF>Impostors</color> by examining footprints", "Examine footprints", RoleId.Detective);
public static RoleInfo timeMaster = new RoleInfo("Time Master", TimeMaster.color, "Save yourself with your time shield", "Use your time shield", RoleId.TimeMaster);
public static RoleInfo medic = new RoleInfo("Medic", Medic.color, "Protect someone with your shield", "Protect other players", RoleId.Medic);
trickster,
cleaner,
warlock,
+ bountyHunter,
niceMini,
evilMini,
goodGuesser,
tracker,
snitch,
spy,
- securityGuard
+ securityGuard,
+ bountyHunter
};
public static List<RoleInfo> getRoleInfoForPlayer(PlayerControl p) {
if (p == SecurityGuard.securityGuard) infos.Add(securityGuard);
if (p == Arsonist.arsonist) infos.Add(arsonist);
if (p == Guesser.guesser) infos.Add(p.Data.IsImpostor ? badGuesser : goodGuesser);
+ if (p == BountyHunter.bountyHunter) infos.Add(bountyHunter);
// Default roles
if (infos.Count == 0 && p.Data.IsImpostor) infos.Add(impostor); // Just Impostor
SecurityGuard.clearAndReload();
Arsonist.clearAndReload();
Guesser.clearAndReload();
+ BountyHunter.clearAndReload();
}
public static class Jester {
public static PlayerControl futureShift;
public static PlayerControl currentTarget;
+ public static bool shiftModifiers = false;
private static Sprite buttonSprite;
public static Sprite getButtonSprite() {
shifter = null;
currentTarget = null;
futureShift = null;
+ shiftModifiers = CustomOptionHolder.shifterShiftsModifiers.getBool();
}
}
notAckedExiledIsLover = false;
bothDie = CustomOptionHolder.loversBothDie.getBool();
}
+
+ public static PlayerControl getPartner(this PlayerControl player) {
+ if (player == null)
+ return null;
+ if (lover1 == player)
+ return lover2;
+ if (lover2 == player)
+ return lover1;
+ return null;
+ }
}
public static class Seer {
public static PlayerControl jackal;
public static Color color = new Color32(0, 180, 235, byte.MaxValue);
public static PlayerControl fakeSidekick;
-
public static PlayerControl currentTarget;
public static List<PlayerControl> formerJackals = new List<PlayerControl>();
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() {
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);
- }
+ foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
+ if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
}
- dousedIcons = new Dictionary<byte, PoolablePlayer>();
cooldown = CustomOptionHolder.arsonistCooldown.getFloat();
duration = CustomOptionHolder.arsonistDuration.getFloat();
}
remainingShots = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat());
}
}
+
+ public static class BountyHunter {
+ public static PlayerControl bountyHunter;
+ public static Color color = Palette.ImpostorRed;
+
+ public static Arrow arrow;
+ public static float bountyDuration = 30f;
+ public static bool showArrow = true;
+ public static float bountyKillCooldown = 0f;
+ public static float punishmentTime = 15f;
+ public static float arrowUpdateIntervall = 10f;
+
+ public static float arrowUpdateTimer = 0f;
+ public static float bountyUpdateTimer = 0f;
+ public static PlayerControl bounty;
+ public static TMPro.TextMeshPro cooldownText;
+
+ public static void clearAndReload() {
+ arrow = new Arrow(color);
+ bountyHunter = null;
+ bounty = null;
+ arrowUpdateTimer = 0f;
+ bountyUpdateTimer = 0f;
+ if (arrow != null && arrow.arrow != null) UnityEngine.Object.Destroy(arrow.arrow);
+ arrow = null;
+ if (cooldownText != null && cooldownText.gameObject != null) UnityEngine.Object.Destroy(cooldownText.gameObject);
+ cooldownText = null;
+ foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
+ if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
+ }
+
+
+ bountyDuration = CustomOptionHolder.bountyHunterBountyDuration.getFloat();
+ bountyKillCooldown = CustomOptionHolder.bountyHunterReducedCooldown.getFloat();
+ punishmentTime = CustomOptionHolder.bountyHunterPunishmentTime.getFloat();
+ showArrow = CustomOptionHolder.bountyHunterShowArrow.getBool();
+ arrowUpdateIntervall = CustomOptionHolder.bountyHunterArrowUpdateIntervall.getFloat();
+ }
+ }
}
\ No newline at end of file
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>2.6.7</Version>
+ <Version>2.7.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>
<PropertyGroup>
- <GameVersion>2021.5.10s</GameVersion>
+ <GameVersion>2021.6.15</GameVersion>
<DefineConstants>$(DefineConstants);STEAM</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
} else if (Guesser.guesser != null && Guesser.guesser == PlayerControl.LocalPlayer) {
setPlayerNameColor(Guesser.guesser, Guesser.guesser.Data.IsImpostor ? Palette.ImpostorRed : Guesser.color);
}
-
+
// No else if here, as a Lover of team Jackal needs the colors
if (Sidekick.sidekick != null && Sidekick.sidekick == PlayerControl.LocalPlayer) {
// Sidekick can see the jackal
}
// Crewmate roles with no changes: Mini
- // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock and Mafioso
+ // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock, BountyHunter and Mafioso
}
static void setNameTags() {
__instance.KillButton.enabled = enabled;
}
- static void snitchUpdate() {
- if (Snitch.localArrows == null) return;
-
- foreach (Arrow arrow in Snitch.localArrows) arrow.arrow.SetActive(false);
-
- if (Snitch.snitch == null || Snitch.snitch.Data.IsDead) return;
-
- var (playerCompleted, playerTotal) = TasksHandler.taskInfo(Snitch.snitch.Data);
- int numberOfTasks = playerTotal - playerCompleted;
-
- if (PlayerControl.LocalPlayer.Data.IsImpostor && numberOfTasks <= Snitch.taskCountForImpostors) {
- if (Snitch.localArrows.Count == 0) Snitch.localArrows.Add(new Arrow(Color.blue));
- if (Snitch.localArrows.Count != 0 && Snitch.localArrows[0] != null) {
- Snitch.localArrows[0].arrow.SetActive(true);
- Snitch.localArrows[0].Update(Snitch.snitch.transform.position);
- }
- } else if (PlayerControl.LocalPlayer == Snitch.snitch && numberOfTasks == 0) {
- int arrowIndex = 0;
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (p.Data.IsImpostor && !p.Data.IsDead) {
- if (arrowIndex >= Snitch.localArrows.Count) Snitch.localArrows.Add(new Arrow(Color.blue));
- if (arrowIndex < Snitch.localArrows.Count && Snitch.localArrows[arrowIndex] != null) {
- Snitch.localArrows[arrowIndex].arrow.SetActive(true);
- Snitch.localArrows[arrowIndex].Update(p.transform.position);
- }
- arrowIndex++;
- }
- }
- }
- }
-
static void Postfix(HudManager __instance)
{
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
camouflageAndMorphActions();
// Mini
miniUpdate();
- // Snitch
- snitchUpdate();
}
}
}
}
}
- [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Begin))]
- class VitalsMinigameBeginPatch {
- static void Postfix(VitalsMinigame __instance) {
-
- if (__instance.vitals.Length > 10) {
- for (int i = 0; i < __instance.vitals.Length; i++) {
- var vitalsPanel = __instance.vitals[i];
- var player = GameData.Instance.AllPlayers[i];
- vitalsPanel.Text.text = player.PlayerName.Length >= 4 ? player.PlayerName.Substring(0, 4).ToUpper() : player.PlayerName.ToUpper();
+ [HarmonyPatch]
+ class VitalsMinigamePatch {
+ private static List<TMPro.TextMeshPro> hackerTexts = new List<TMPro.TextMeshPro>();
+
+ [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Begin))]
+ class VitalsMinigameStartPatch {
+ static void Postfix(VitalsMinigame __instance) {
+ if (Hacker.hacker != null && PlayerControl.LocalPlayer == Hacker.hacker) {
+ hackerTexts = new List<TMPro.TextMeshPro>();
+ foreach (VitalsPanel panel in __instance.vitals) {
+ TMPro.TextMeshPro text = UnityEngine.Object.Instantiate(__instance.SabText, panel.transform);
+ hackerTexts.Add(text);
+ UnityEngine.Object.DestroyImmediate(text.GetComponent<AlphaBlink>());
+ text.gameObject.SetActive(false);
+ text.transform.localScale = Vector3.one * 0.75f;
+ text.transform.localPosition = new Vector3(-0.75f, -0.23f, 0f);
+
+ }
}
}
}
- }
-
- [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Update))]
- class VitalsMinigameUpdatePatch {
+ [HarmonyPatch(typeof(VitalsMinigame), nameof(VitalsMinigame.Update))]
+ class VitalsMinigameUpdatePatch {
- static void Postfix(VitalsMinigame __instance) {
- // Hacker show time since death
- bool showHackerInfo = Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0;
- for (int k = 0; k < __instance.vitals.Length; k++)
- {
- VitalsPanel vitalsPanel = __instance.vitals[k];
- GameData.PlayerInfo player = GameData.Instance.AllPlayers[k];
-
- // Crowded scaling
- float scale = 10f / Mathf.Max(10, __instance.vitals.Length);
- vitalsPanel.transform.localPosition = new Vector3((float)k * 0.6f * scale + -2.7f, 0.2f, -1f);
- vitalsPanel.transform.localScale = new Vector3(scale, scale, vitalsPanel.transform.localScale.z);
-
- // Hacker update
- if (vitalsPanel.IsDead) {
- DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == player?.PlayerId)?.FirstOrDefault();
- if (deadPlayer != null && deadPlayer.timeOfDeath != null) {
- float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds);
-
- if (showHackerInfo)
- vitalsPanel.Text.text = Math.Round(timeSinceDeath / 1000) + "s";
- else if (__instance.vitals.Length > 10)
- vitalsPanel.Text.text = player.PlayerName.Length >= 4 ? player.PlayerName.Substring(0, 4).ToUpper() : player.PlayerName.ToUpper();
- else
- vitalsPanel.Text.text = DestroyableSingleton<TranslationController>.Instance.GetString(Palette.ShortColorNames[(int)player.ColorId], new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(0));
+ static void Postfix(VitalsMinigame __instance) {
+ // Hacker show time since death
+
+ if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0) {
+ for (int k = 0; k < __instance.vitals.Length; k++) {
+ VitalsPanel vitalsPanel = __instance.vitals[k];
+ GameData.PlayerInfo player = GameData.Instance.AllPlayers[k];
+
+ // Hacker update
+ if (vitalsPanel.IsDead) {
+ DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == player?.PlayerId)?.FirstOrDefault();
+ if (deadPlayer != null && deadPlayer.timeOfDeath != null && k < hackerTexts.Count && hackerTexts[k] != null) {
+ float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds);
+ hackerTexts[k].gameObject.SetActive(true);
+ hackerTexts[k].text = Math.Round(timeSinceDeath / 1000) + "s";
+ }
+ }
}
+ } else {
+ foreach (TMPro.TextMeshPro text in hackerTexts)
+ if (text != null && text.gameObject != null)
+ text.gameObject.SetActive(false);
}
- }
+ }
}
- }
+ }
[HarmonyPatch]
class AdminPanelPatch {