--- /dev/null
+.vs
+.vscode/tasks.json
\ No newline at end of file
# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
-| **2021.6.30**| v2.7.2 Beta| [Download](https://github.com/Eisbison/TheOtherRoles/releases/tag/v2.7.2)
-| 2021.6.15| v2.7.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.7.1/TheOtherRoles.zip)
-| 2021.6.15| v2.7.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.7.0/TheOtherRoles.zip)
+| **2021.6.30s**| v2.7.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.7.3/TheOtherRoles.zip)
+| 2021.6.15s| v2.7.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.7.1/TheOtherRoles.zip)
+| 2021.6.15s| 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.3**
+- Updated to Among Us v2021.6.30
+- Updated BepInEx version
+- Updated Credentials
+- Fixed some Colors being considered darker, when they should be lighter
+- Added /size command for Lobby
+- Added /color and /murder command to Freeplay (for the Hat Designers)
+
**Version 2.7.1**
-- Fixed a bug where swapped votes were sometimes counted wrongly
-- Fixed the positioning of the player name while morphed
-- Fixed a bug where the window of the Guesser sometimes showed no "close button"
-- Fixed a bug where the garlics were not displayed properly
+- Fixed a bug where [swapped](#swapper) votes were sometimes counted wrongly
+- Fixed the positioning of the player name while [morphed](#morphling)
+- Fixed a bug where the window of the [Guesser](#guesser) sometimes showed no "close button"
+- Fixed a bug where the [garlics](#vampire) were not displayed properly
**Version 2.7.0**
- **New Role:** [Bounty Hunter](#bounty-hunter) created by [Mallöris](https://github.com/Mallaris)
**Linux Manual**
1. Install Among Us via Steam
2. Download newest [release](https://github.com/Eisbison/TheOtherRoles/releases/latest) and extract it to ~/.steam/steam/steamapps/common/Among Us
-3. Enable `winhttp.dll` via the proton winecfg (https://bepinex.github.io/bepinex_docs/master/articles/advanced/steam_interop.html#protonwine)
+3. Enable `winhttp.dll` via the proton winecfg (https://docs.bepinex.dev/articles/advanced/steam_interop.html#open-winecfg-for-the-target-game)
4. Launch the game via Steam
# Custom Servers and 10+ Players
### **Team: Crewmates or Impostors**
The Guesser can be a Crewmate or an Impostor (depending on the settings).\
The Guesser can shoot a player during the meeting, by guessing its role. If the guess is wrong, the Guesser dies instead.\
-Only one person can be shot per meeting and you can set a maximum number of shots.\
+You can select how many players can be shot per meeting and how many players can be shot per game.\
The guesses Impostor and Crewmate are only right, if the player is part of the corresponding team and has no special role.\
You can only shoot during the voting time.
|----------|:-------------:|
| Guesser Spawn Chance | -
| Chance That The Guesser Is An Impostor | -
-| Guesser Number Of Shots | -
+| Guesser Number Of Shots Per Game| -
+| Guesser Number Of Shots Per Meeting| -
-----------------------
+++ /dev/null
-bin
-obj
-.vscode/tasks.json
-.gitignore
\ No newline at end of file
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Collections;
-using UnityEngine;
-
-namespace TheOtherRoles {
- public class Arrow {
- public float perc = 0.925f;
- public SpriteRenderer image;
- public GameObject arrow;
- private Vector3 oldTarget;
-
- private static Sprite sprite;
- public static Sprite getSprite() {
- if (sprite) return sprite;
- sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Arrow.png", 200f);
- return sprite;
- }
-
-
- public Arrow(Color color) {
- arrow = new GameObject("Arrow");
- arrow.layer = 5;
- image = arrow.AddComponent<SpriteRenderer>();
- image.sprite = getSprite();
- image.color = color;
- }
-
- public void Update() {
- Vector3 target = oldTarget;
- if (target == null) target = Vector3.zero;
- Update(target);
- }
-
- public void Update(Vector3 target)
- {
- if (arrow == null) return;
- oldTarget = target;
-
- Camera main = Camera.main;
- Vector2 vector = target - main.transform.position;
- float num = vector.magnitude / (main.orthographicSize * perc);
- image.enabled = ((double)num > 0.3);
- Vector2 vector2 = main.WorldToViewportPoint(target);
- if (Between(vector2.x, 0f, 1f) && Between(vector2.y, 0f, 1f))
- {
- arrow.transform.position = target - (Vector3)vector.normalized * 0.6f;
- float num2 = Mathf.Clamp(num, 0f, 1f);
- arrow.transform.localScale = new Vector3(num2, num2, num2);
- }
- else
- {
- Vector2 vector3 = new Vector2(Mathf.Clamp(vector2.x * 2f - 1f, -1f, 1f), Mathf.Clamp(vector2.y * 2f - 1f, -1f, 1f));
- float orthographicSize = main.orthographicSize;
- float num3 = main.orthographicSize * main.aspect;
- Vector3 vector4 = new Vector3(Mathf.LerpUnclamped(0f, num3 * 0.88f, vector3.x), Mathf.LerpUnclamped(0f, orthographicSize * 0.79f, vector3.y), 0f);
- arrow.transform.position = main.transform.position + vector4;
- arrow.transform.localScale = Vector3.one;
- }
-
- LookAt2d(arrow.transform, target);
- }
-
- private void LookAt2d(Transform transform, Vector3 target) {
- Vector3 vector = target - transform.position;
- vector.Normalize();
- float num = Mathf.Atan2(vector.y, vector.x);
- if (transform.lossyScale.x < 0f)
- num += 3.1415927f;
- transform.rotation = Quaternion.Euler(0f, 0f, num * 57.29578f);
- }
-
- private bool Between(float value, float min, float max) {
- return value > min && value < max;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using System;
-using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch(typeof(HudManager), nameof(HudManager.Start))]
- static class HudManagerStartPatch
- {
- private static CustomButton engineerRepairButton;
- private static CustomButton janitorCleanButton;
- private static CustomButton sheriffKillButton;
- private static CustomButton timeMasterShieldButton;
- private static CustomButton medicShieldButton;
- private static CustomButton shifterShiftButton;
- private static CustomButton morphlingButton;
- private static CustomButton camouflagerButton;
- private static CustomButton hackerButton;
- private static CustomButton trackerButton;
- private static CustomButton vampireKillButton;
- private static CustomButton garlicButton;
- private static CustomButton jackalKillButton;
- private static CustomButton sidekickKillButton;
- private static CustomButton jackalSidekickButton;
- private static CustomButton lighterButton;
- private static CustomButton eraserButton;
- private static CustomButton placeJackInTheBoxButton;
- private static CustomButton lightsOutButton;
- public static CustomButton cleanerCleanButton;
- public static CustomButton warlockCurseButton;
- public static CustomButton securityGuardButton;
- public static CustomButton arsonistButton;
- public static TMPro.TMP_Text securityGuardButtonScrewsText;
-
- public static void setCustomButtonCooldowns() {
- engineerRepairButton.MaxTimer = 0f;
- janitorCleanButton.MaxTimer = Janitor.cooldown;
- sheriffKillButton.MaxTimer = Sheriff.cooldown;
- timeMasterShieldButton.MaxTimer = TimeMaster.cooldown;
- medicShieldButton.MaxTimer = 0f;
- shifterShiftButton.MaxTimer = 0f;
- morphlingButton.MaxTimer = Morphling.cooldown;
- camouflagerButton.MaxTimer = Camouflager.cooldown;
- hackerButton.MaxTimer = Hacker.cooldown;
- vampireKillButton.MaxTimer = Vampire.cooldown;
- trackerButton.MaxTimer = 0f;
- garlicButton.MaxTimer = 0f;
- jackalKillButton.MaxTimer = Jackal.cooldown;
- sidekickKillButton.MaxTimer = Sidekick.cooldown;
- jackalSidekickButton.MaxTimer = Jackal.createSidekickCooldown;
- lighterButton.MaxTimer = Lighter.cooldown;
- eraserButton.MaxTimer = Eraser.cooldown;
- placeJackInTheBoxButton.MaxTimer = Trickster.placeBoxCooldown;
- lightsOutButton.MaxTimer = Trickster.lightsOutCooldown;
- cleanerCleanButton.MaxTimer = Cleaner.cooldown;
- warlockCurseButton.MaxTimer = Warlock.cooldown;
- securityGuardButton.MaxTimer = SecurityGuard.cooldown;
- arsonistButton.MaxTimer = Arsonist.cooldown;
-
- timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
- hackerButton.EffectDuration = Hacker.duration;
- vampireKillButton.EffectDuration = Vampire.delay;
- lighterButton.EffectDuration = Lighter.duration;
- camouflagerButton.EffectDuration = Camouflager.duration;
- morphlingButton.EffectDuration = Morphling.duration;
- lightsOutButton.EffectDuration = Trickster.lightsOutDuration;
- arsonistButton.EffectDuration = Arsonist.duration;
-
- // Already set the timer to the max, as the button is enabled during the game and not available at the start
- lightsOutButton.Timer = lightsOutButton.MaxTimer;
- }
-
- public static void resetTimeMasterButton() {
- timeMasterShieldButton.Timer = timeMasterShieldButton.MaxTimer;
- timeMasterShieldButton.isEffectActive = false;
- timeMasterShieldButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- }
-
- public static void Postfix(HudManager __instance)
- {
- // Engineer Repair
- engineerRepairButton = new CustomButton(
- () => {
- engineerRepairButton.Timer = 0f;
-
- MessageWriter usedRepairWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerUsedRepair, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(usedRepairWriter);
- RPCProcedure.engineerUsedRepair();
-
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) {
- if (task.TaskType == TaskTypes.FixLights) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerFixLights, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.engineerFixLights();
- } else if (task.TaskType == TaskTypes.RestoreOxy) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, 0 | 64);
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, 1 | 64);
- } else if (task.TaskType == TaskTypes.ResetReactor) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 16);
- } else if (task.TaskType == TaskTypes.ResetSeismic) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Laboratory, 16);
- } else if (task.TaskType == TaskTypes.FixComms) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 16 | 0);
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 16 | 1);
- } else if (task.TaskType == TaskTypes.StopCharles) {
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 0 | 16);
- ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 1 | 16);
- }
- }
- },
- () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => {
- bool sabotageActive = false;
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
- if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles)
- sabotageActive = true;
- return sabotageActive && !Engineer.usedRepair && PlayerControl.LocalPlayer.CanMove;
- },
- () => {},
- Engineer.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- // Janitor Clean
- janitorCleanButton = new CustomButton(
- () => {
- foreach (Collider2D collider2D in Physics2D.OverlapCircleAll(PlayerControl.LocalPlayer.GetTruePosition(), PlayerControl.LocalPlayer.MaxReportDistance, Constants.PlayersOnlyMask)) {
- if (collider2D.tag == "DeadBody")
- {
- DeadBody component = collider2D.GetComponent<DeadBody>();
- if (component && !component.Reported)
- {
- Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
- Vector2 truePosition2 = component.TruePosition;
- if (Vector2.Distance(truePosition2, truePosition) <= PlayerControl.LocalPlayer.MaxReportDistance && PlayerControl.LocalPlayer.CanMove && !PhysicsHelpers.AnythingBetween(truePosition, truePosition2, Constants.ShipAndObjectsMask, false))
- {
- GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
- writer.Write(playerInfo.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.cleanBody(playerInfo.PlayerId);
- janitorCleanButton.Timer = janitorCleanButton.MaxTimer;
-
- break;
- }
- }
- }
- }
- },
- () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return __instance.ReportButton.renderer.color == Palette.EnabledColor && PlayerControl.LocalPlayer.CanMove; },
- () => { janitorCleanButton.Timer = janitorCleanButton.MaxTimer; },
- Janitor.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- // Sheriff Kill
- sheriffKillButton = new CustomButton(
- () => {
- if (Medic.shielded != null && Medic.shielded == Sheriff.currentTarget) {
- MessageWriter attemptWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(attemptWriter);
- RPCProcedure.shieldedMurderAttempt();
- return;
- }
-
- 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)) {
- targetId = Sheriff.currentTarget.PlayerId;
- }
- else {
- targetId = PlayerControl.LocalPlayer.PlayerId;
- }
- MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SheriffKill, Hazel.SendOption.Reliable, -1);
- killWriter.Write(targetId);
- AmongUsClient.Instance.FinishRpcImmediately(killWriter);
- RPCProcedure.sheriffKill(targetId);
-
- sheriffKillButton.Timer = sheriffKillButton.MaxTimer;
- Sheriff.currentTarget = null;
- },
- () => { return Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return Sheriff.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- () => { sheriffKillButton.Timer = sheriffKillButton.MaxTimer;},
- __instance.KillButton.renderer.sprite,
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- // Time Master Rewind Time
- timeMasterShieldButton = new CustomButton(
- () => {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TimeMasterShield, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.timeMasterShield();
- },
- () => { return TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return PlayerControl.LocalPlayer.CanMove; },
- () => {
- timeMasterShieldButton.Timer = timeMasterShieldButton.MaxTimer;
- timeMasterShieldButton.isEffectActive = false;
- timeMasterShieldButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- },
- TimeMaster.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q,
- true,
- TimeMaster.shieldDuration,
- () => { timeMasterShieldButton.Timer = timeMasterShieldButton.MaxTimer; }
- );
-
- // Medic Shield
- medicShieldButton = new CustomButton(
- () => {
- medicShieldButton.Timer = 0f;
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MedicSetShielded, Hazel.SendOption.Reliable, -1);
- writer.Write(Medic.currentTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.medicSetShielded(Medic.currentTarget.PlayerId);
- },
- () => { return Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return !Medic.usedShield && Medic.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- () => {},
- Medic.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
-
- // Shifter shift
- shifterShiftButton = new CustomButton(
- () => {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetFutureShifted, Hazel.SendOption.Reliable, -1);
- writer.Write(Shifter.currentTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.setFutureShifted(Shifter.currentTarget.PlayerId);
- },
- () => { return Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return Shifter.currentTarget && Shifter.futureShift == null && PlayerControl.LocalPlayer.CanMove; },
- () => { },
- Shifter.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- // Morphling morph
- morphlingButton = new CustomButton(
- () => {
- if (Morphling.sampledTarget != null) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MorphlingMorph, Hazel.SendOption.Reliable, -1);
- writer.Write(Morphling.sampledTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.morphlingMorph(Morphling.sampledTarget.PlayerId);
- Morphling.sampledTarget = null;
- morphlingButton.EffectDuration = Morphling.duration;
- } else if (Morphling.currentTarget != null) {
- Morphling.sampledTarget = Morphling.currentTarget;
- morphlingButton.Sprite = Morphling.getMorphSprite();
- morphlingButton.EffectDuration = 1f;
- }
- },
- () => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return (Morphling.currentTarget || Morphling.sampledTarget) && PlayerControl.LocalPlayer.CanMove; },
- () => {
- morphlingButton.Timer = morphlingButton.MaxTimer;
- morphlingButton.Sprite = Morphling.getSampleSprite();
- morphlingButton.isEffectActive = false;
- morphlingButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- Morphling.sampledTarget = null;
- },
- Morphling.getSampleSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F,
- true,
- Morphling.duration,
- () => {
- if (Morphling.sampledTarget == null) {
- morphlingButton.Timer = morphlingButton.MaxTimer;
- morphlingButton.Sprite = Morphling.getSampleSprite();
- }
- }
- );
-
- // Camouflager camouflage
- camouflagerButton = new CustomButton(
- () => {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CamouflagerCamouflage, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.camouflagerCamouflage();
- },
- () => { return Camouflager.camouflager != null && Camouflager.camouflager == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return PlayerControl.LocalPlayer.CanMove; },
- () => {
- camouflagerButton.Timer = camouflagerButton.MaxTimer;
- camouflagerButton.isEffectActive = false;
- camouflagerButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- },
- Camouflager.getButtonSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F,
- true,
- Camouflager.duration,
- () => { camouflagerButton.Timer = camouflagerButton.MaxTimer; }
- );
-
- // Hacker button
- hackerButton = new CustomButton(
- () => {
- Hacker.hackerTimer = Hacker.duration;
- },
- () => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return PlayerControl.LocalPlayer.CanMove; },
- () => {
- hackerButton.Timer = hackerButton.MaxTimer;
- hackerButton.isEffectActive = false;
- hackerButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- },
- Hacker.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q,
- true,
- 0f,
- () => {
- hackerButton.Timer = hackerButton.MaxTimer;
- }
- );
-
- // Tracker button
- trackerButton = new CustomButton(
- () => {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TrackerUsedTracker, Hazel.SendOption.Reliable, -1);
- writer.Write(Tracker.currentTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.trackerUsedTracker(Tracker.currentTarget.PlayerId);
- },
- () => { return Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return PlayerControl.LocalPlayer.CanMove && Tracker.currentTarget != null && !Tracker.usedTracker; },
- () => { },
- Tracker.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- vampireKillButton = new CustomButton(
- () => {
- if (Helpers.handleMurderAttempt(Vampire.currentTarget)) {
- if (Vampire.targetNearGarlic) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
- writer.Write(Vampire.vampire.PlayerId);
- writer.Write(Vampire.currentTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.uncheckedMurderPlayer(Vampire.vampire.PlayerId, Vampire.currentTarget.PlayerId);
-
- vampireKillButton.HasEffect = false; // Block effect on this click
- vampireKillButton.Timer = vampireKillButton.MaxTimer;
- } else {
- Vampire.bitten = Vampire.currentTarget;
- // Notify players about bitten
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
- writer.Write(Vampire.bitten.PlayerId);
- writer.Write(0);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0);
-
- HudManager.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action<float>((p) => { // Delayed action
- if (p == 1f) {
- if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.bitten)) {
- // Perform kill
- MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireTryKill, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(killWriter);
- RPCProcedure.vampireTryKill();
- } else {
- // Notify players about clearing bitten
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
- writer.Write(byte.MaxValue);
- writer.Write(byte.MaxValue);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue);
- }
- }
- })));
-
- vampireKillButton.HasEffect = true; // Trigger effect on this click
- }
- } else {
- vampireKillButton.HasEffect = false; // Block effect if no action was fired
- }
- },
- () => { return Vampire.vampire != null && Vampire.vampire == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => {
- if (Vampire.targetNearGarlic && Vampire.canKillNearGarlics)
- vampireKillButton.killButtonManager.renderer.sprite = __instance.KillButton.renderer.sprite;
- else
- vampireKillButton.killButtonManager.renderer.sprite = Vampire.getButtonSprite();
- return Vampire.currentTarget != null && PlayerControl.LocalPlayer.CanMove && (!Vampire.targetNearGarlic || Vampire.canKillNearGarlics);
- },
- () => {
- vampireKillButton.Timer = vampireKillButton.MaxTimer;
- vampireKillButton.isEffectActive = false;
- vampireKillButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- },
- Vampire.getButtonSprite(),
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q,
- false,
- 0f,
- () => {
- vampireKillButton.Timer = vampireKillButton.MaxTimer;
- }
- );
-
- garlicButton = new CustomButton(
- () => {
- Vampire.localPlacedGarlic = true;
- var pos = PlayerControl.LocalPlayer.transform.position;
- byte[] buff = new byte[sizeof(float) * 2];
- Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
- Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
-
- MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceGarlic, Hazel.SendOption.Reliable);
- writer.WriteBytesAndSize(buff);
- writer.EndMessage();
- RPCProcedure.placeGarlic(buff);
- },
- () => { return !Vampire.localPlacedGarlic && !PlayerControl.LocalPlayer.Data.IsDead && Vampire.garlicsActive; },
- () => { return PlayerControl.LocalPlayer.CanMove && !Vampire.localPlacedGarlic; },
- () => { },
- Vampire.getGarlicButtonSprite(),
- Vector3.zero,
- __instance,
- null,
- true
- );
-
-
- // Jackal Sidekick Button
- jackalSidekickButton = new CustomButton(
- () => {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalCreatesSidekick, Hazel.SendOption.Reliable, -1);
- writer.Write(Jackal.currentTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.jackalCreatesSidekick(Jackal.currentTarget.PlayerId);
- },
- () => { 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),
- __instance,
- KeyCode.F
- );
-
- // Jackal Kill
- jackalKillButton = new CustomButton(
- () => {
- if (!Helpers.handleMurderAttempt(Jackal.currentTarget)) return;
- byte targetId = Jackal.currentTarget.PlayerId;
- MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalKill, Hazel.SendOption.Reliable, -1);
- killWriter.Write(targetId);
- AmongUsClient.Instance.FinishRpcImmediately(killWriter);
- RPCProcedure.jackalKill(targetId);
- jackalKillButton.Timer = jackalKillButton.MaxTimer;
- Jackal.currentTarget = null;
- },
- () => { return Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return Jackal.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- () => { jackalKillButton.Timer = jackalKillButton.MaxTimer;},
- __instance.KillButton.renderer.sprite,
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- // Sidekick Kill
- sidekickKillButton = new CustomButton(
- () => {
- if (!Helpers.handleMurderAttempt(Sidekick.currentTarget)) return;
- byte targetId = Sidekick.currentTarget.PlayerId;
- MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickKill, Hazel.SendOption.Reliable, -1);
- killWriter.Write(targetId);
- AmongUsClient.Instance.FinishRpcImmediately(killWriter);
- RPCProcedure.sidekickKill(targetId);
-
- sidekickKillButton.Timer = sidekickKillButton.MaxTimer;
- Sidekick.currentTarget = null;
- },
- () => { return Sidekick.canKill && Sidekick.sidekick != null && Sidekick.sidekick == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return Sidekick.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- () => { sidekickKillButton.Timer = sidekickKillButton.MaxTimer;},
- __instance.KillButton.renderer.sprite,
- new Vector3(-1.3f, 0, 0),
- __instance,
- KeyCode.Q
- );
-
- // Lighter light
- lighterButton = new CustomButton(
- () => {
- Lighter.lighterTimer = Lighter.duration;
- },
- () => { return Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return PlayerControl.LocalPlayer.CanMove; },
- () => {
- lighterButton.Timer = lighterButton.MaxTimer;
- lighterButton.isEffectActive = false;
- lighterButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- },
- Lighter.getButtonSprite(),
- new Vector3(-1.3f, 0f, 0f),
- __instance,
- KeyCode.Q,
- true,
- Lighter.duration,
- () => { lighterButton.Timer = lighterButton.MaxTimer; }
- );
-
- // Eraser erase button
- eraserButton = new CustomButton(
- () => {
- eraserButton.MaxTimer += 10;
- eraserButton.Timer = eraserButton.MaxTimer;
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetFutureErased, Hazel.SendOption.Reliable, -1);
- writer.Write(Eraser.currentTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.setFutureErased(Eraser.currentTarget.PlayerId);
- },
- () => { return Eraser.eraser != null && Eraser.eraser == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return PlayerControl.LocalPlayer.CanMove && Eraser.currentTarget != null; },
- () => { eraserButton.Timer = eraserButton.MaxTimer;},
- Eraser.getButtonSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F
- );
-
- placeJackInTheBoxButton = new CustomButton(
- () => {
- placeJackInTheBoxButton.Timer = placeJackInTheBoxButton.MaxTimer;
-
- var pos = PlayerControl.LocalPlayer.transform.position;
- byte[] buff = new byte[sizeof(float) * 2];
- Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
- Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
-
- MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceJackInTheBox, Hazel.SendOption.Reliable);
- writer.WriteBytesAndSize(buff);
- writer.EndMessage();
- RPCProcedure.placeJackInTheBox(buff);
- },
- () => { return Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && !JackInTheBox.hasJackInTheBoxLimitReached(); },
- () => { return PlayerControl.LocalPlayer.CanMove && !JackInTheBox.hasJackInTheBoxLimitReached(); },
- () => { placeJackInTheBoxButton.Timer = placeJackInTheBoxButton.MaxTimer;},
- Trickster.getPlaceBoxButtonSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F
- );
-
- lightsOutButton = new CustomButton(
- () => {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LightsOut, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.lightsOut();
- },
- () => { return Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; },
- () => { return PlayerControl.LocalPlayer.CanMove && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; },
- () => {
- lightsOutButton.Timer = lightsOutButton.MaxTimer;
- lightsOutButton.isEffectActive = false;
- lightsOutButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- },
- Trickster.getLightsOutButtonSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F,
- true,
- Trickster.lightsOutDuration,
- () => { lightsOutButton.Timer = lightsOutButton.MaxTimer; }
- );
- // Cleaner Clean
- cleanerCleanButton = new CustomButton(
- () => {
- foreach (Collider2D collider2D in Physics2D.OverlapCircleAll(PlayerControl.LocalPlayer.GetTruePosition(), PlayerControl.LocalPlayer.MaxReportDistance, Constants.PlayersOnlyMask)) {
- if (collider2D.tag == "DeadBody")
- {
- DeadBody component = collider2D.GetComponent<DeadBody>();
- if (component && !component.Reported)
- {
- Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
- Vector2 truePosition2 = component.TruePosition;
- if (Vector2.Distance(truePosition2, truePosition) <= PlayerControl.LocalPlayer.MaxReportDistance && PlayerControl.LocalPlayer.CanMove && !PhysicsHelpers.AnythingBetween(truePosition, truePosition2, Constants.ShipAndObjectsMask, false))
- {
- GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
- writer.Write(playerInfo.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.cleanBody(playerInfo.PlayerId);
-
- Cleaner.cleaner.killTimer = cleanerCleanButton.Timer = cleanerCleanButton.MaxTimer;
- break;
- }
- }
- }
- }
- },
- () => { return Cleaner.cleaner != null && Cleaner.cleaner == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return __instance.ReportButton.renderer.color == Palette.EnabledColor && PlayerControl.LocalPlayer.CanMove; },
- () => { cleanerCleanButton.Timer = cleanerCleanButton.MaxTimer; },
- Cleaner.getButtonSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F
- );
-
- // Warlock curse
- warlockCurseButton = new CustomButton(
- () => {
- if (Warlock.curseVictim == null) {
- // Apply Curse
- Warlock.curseVictim = Warlock.currentTarget;
- warlockCurseButton.Sprite = Warlock.getCurseKillButtonSprite();
- warlockCurseButton.Timer = 1f;
- } else if (Warlock.curseVictim != null && Warlock.curseVictimTarget != null && Helpers.handleMurderAttempt(Warlock.curseVictimTarget)) {
- // Curse Kill
- Warlock.curseKillTarget = Warlock.curseVictimTarget;
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.WarlockCurseKill, Hazel.SendOption.Reliable, -1);
- writer.Write(Warlock.curseKillTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.warlockCurseKill(Warlock.curseKillTarget.PlayerId);
-
- Warlock.curseVictim = null;
- Warlock.curseVictimTarget = null;
- warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
- Warlock.warlock.killTimer = warlockCurseButton.Timer = warlockCurseButton.MaxTimer;
-
- if(Warlock.rootTime > 0) {
- PlayerControl.LocalPlayer.moveable = false;
- PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement so the warlock is not just running straight into the next object
- HudManager.Instance.StartCoroutine(Effects.Lerp(Warlock.rootTime, new Action<float>((p) => { // Delayed action
- if (p == 1f) {
- PlayerControl.LocalPlayer.moveable = true;
- }
- })));
- }
- }
- },
- () => { return Warlock.warlock != null && Warlock.warlock == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => { return ((Warlock.curseVictim == null && Warlock.currentTarget != null) || (Warlock.curseVictim != null && Warlock.curseVictimTarget != null)) && PlayerControl.LocalPlayer.CanMove; },
- () => {
- warlockCurseButton.Timer = warlockCurseButton.MaxTimer;
- warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
- Warlock.curseVictim = null;
- Warlock.curseVictimTarget = null;
- },
- Warlock.getCurseButtonSprite(),
- new Vector3(-1.3f, 1.3f, 0f),
- __instance,
- KeyCode.F
- );
-
- // Security Guard button
- securityGuardButton = new CustomButton(
- () => {
- if (SecurityGuard.ventTarget != null) { // Seal vent
- MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SealVent, Hazel.SendOption.Reliable);
- writer.WritePacked(SecurityGuard.ventTarget.Id);
- writer.EndMessage();
- RPCProcedure.sealVent(SecurityGuard.ventTarget.Id);
- SecurityGuard.ventTarget = null;
- } else if (PlayerControl.GameOptions.MapId != 1) { // Place camera if there's no vent and it's not MiraHQ
- var pos = PlayerControl.LocalPlayer.transform.position;
- byte[] buff = new byte[sizeof(float) * 2];
- Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
- Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
-
- MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceCamera, Hazel.SendOption.Reliable);
- writer.WriteBytesAndSize(buff);
- writer.EndMessage();
- RPCProcedure.placeCamera(buff);
- }
- securityGuardButton.Timer = securityGuardButton.MaxTimer;
- },
- () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews >= Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); },
- () => {
- securityGuardButton.killButtonManager.renderer.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite();
- if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}";
-
- if (SecurityGuard.ventTarget != null)
- return SecurityGuard.remainingScrews >= SecurityGuard.ventPrice && PlayerControl.LocalPlayer.CanMove;
- return PlayerControl.GameOptions.MapId != 1 && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove;
- },
- () => { securityGuardButton.Timer = securityGuardButton.MaxTimer; },
- SecurityGuard.getPlaceCameraButtonSprite(),
- new Vector3(-1.3f, 0f, 0f),
- __instance,
- KeyCode.Q
- );
-
- // Security Guard button screws counter
- securityGuardButtonScrewsText = GameObject.Instantiate(securityGuardButton.killButtonManager.TimerText, securityGuardButton.killButtonManager.TimerText.transform.parent);
- securityGuardButtonScrewsText.text = "";
- securityGuardButtonScrewsText.enableWordWrapping = false;
- securityGuardButtonScrewsText.transform.localScale = Vector3.one * 0.5f;
- securityGuardButtonScrewsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
-
- // Arsonist button
- arsonistButton = new CustomButton(
- () => {
- bool dousedEveryoneAlive = Arsonist.dousedEveryoneAlive();
- if (dousedEveryoneAlive) {
- MessageWriter winWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ArsonistWin, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(winWriter);
- RPCProcedure.arsonistWin();
- arsonistButton.HasEffect = false;
- } else if (Arsonist.currentTarget != null) {
- Arsonist.douseTarget = Arsonist.currentTarget;
- arsonistButton.HasEffect = true;
- }
- },
- () => { return Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
- () => {
- bool dousedEveryoneAlive = Arsonist.dousedEveryoneAlive();
- if (dousedEveryoneAlive) arsonistButton.killButtonManager.renderer.sprite = Arsonist.getIgniteSprite();
-
- if (arsonistButton.isEffectActive && Arsonist.douseTarget != Arsonist.currentTarget) {
- Arsonist.douseTarget = null;
- arsonistButton.Timer = 0f;
- arsonistButton.isEffectActive = false;
- }
-
- return PlayerControl.LocalPlayer.CanMove && (dousedEveryoneAlive || Arsonist.currentTarget != null);
- },
- () => {
- arsonistButton.Timer = arsonistButton.MaxTimer;
- arsonistButton.isEffectActive = false;
- Arsonist.douseTarget = null;
- },
- Arsonist.getDouseSprite(),
- new Vector3(-1.3f, 0f, 0f),
- __instance,
- KeyCode.Q,
- true,
- Arsonist.duration,
- () => {
- if (Arsonist.douseTarget != null) Arsonist.dousedPlayers.Add(Arsonist.douseTarget);
- Arsonist.douseTarget = null;
- arsonistButton.Timer = Arsonist.dousedEveryoneAlive() ? 0 : arsonistButton.MaxTimer;
-
- foreach (PlayerControl p in Arsonist.dousedPlayers) {
- if (MapOptions.playerIcons.ContainsKey(p.PlayerId)) {
- MapOptions.playerIcons[p.PlayerId].setSemiTransparent(false);
- }
- }
- }
- );
-
- // Set the default (or settings from the previous game) timers/durations when spawning the buttons
- setCustomButtonCooldowns();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using System;
-using System.Security.Cryptography;
-using System.Text;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
-using HarmonyLib;
-using UnityEngine;
-using System.Linq;
-using UnhollowerBaseLib;
-
-namespace TheOtherRoles {
- [HarmonyPatch]
- public static class ChatCommands {
- [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))]
- private static class SendChatPatch {
- static bool Prefix(ChatController __instance) {
- 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 (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()) {
- var client = AmongUsClient.Instance.GetClient(target.OwnerId);
- if (client != null) {
- AmongUsClient.Instance.KickPlayer(client.Id, false);
- 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();
- }
- return !handled;
- }
- }
- }
-}
+++ /dev/null
-
-using HarmonyLib;
-using UnityEngine;
-using System.Collections.Generic;
-using Hazel;
-using System;
-using UnityEngine.UI;
-using UnityEngine.Events;
-
-namespace TheOtherRoles {
- [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))]
- public class OptionsMenuBehaviourStartPatch {
- private static Vector3? origin;
- private static ToggleButtonBehaviour streamerModeButton;
- private static ToggleButtonBehaviour ghostsSeeTasksButton;
- private static ToggleButtonBehaviour ghostsSeeRolesButton;
- private static ToggleButtonBehaviour ghostsSeeVotesButton;
- private static ToggleButtonBehaviour showRoleSummaryButton;
-
- public static float xOffset = 1.75f;
- public static float yOffset = -0.5f;
-
- private static void updateToggle(ToggleButtonBehaviour button, string text, bool on) {
- if (button == null || button.gameObject == null) return;
-
- Color color = on ? new Color(0f, 1f, 0.16470589f, 1f) : Color.white;
- button.Background.color = color;
- button.Text.text = $"{text}{(on ? "On" : "Off")}";
- if (button.Rollover) button.Rollover.ChangeOutColor(color);
- }
-
- private static ToggleButtonBehaviour createCustomToggle(string text, bool on, Vector3 offset, UnityEngine.Events.UnityAction onClick, OptionsMenuBehaviour __instance) {
- if (__instance.CensorChatButton != null) {
- var button = UnityEngine.Object.Instantiate(__instance.CensorChatButton, __instance.CensorChatButton.transform.parent);
- button.transform.localPosition = (origin ?? Vector3.zero) + offset;
- PassiveButton passiveButton = button.GetComponent<PassiveButton>();
- passiveButton.OnClick = new Button.ButtonClickedEvent();
- passiveButton.OnClick.AddListener(onClick);
- updateToggle(button, text, on);
-
- return button;
- }
- return null;
- }
-
- public static void Postfix(OptionsMenuBehaviour __instance) {
- if (__instance.CensorChatButton != null) {
- if (origin == null) origin = __instance.CensorChatButton.transform.localPosition + Vector3.up * 0.25f;
- __instance.CensorChatButton.transform.localPosition = origin.Value + Vector3.left * xOffset;
- __instance.CensorChatButton.transform.localScale = Vector3.one * 2f / 3f;
- }
-
- if ((streamerModeButton == null || streamerModeButton.gameObject == null)) {
- streamerModeButton = createCustomToggle("Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value, Vector3.zero, (UnityEngine.Events.UnityAction)streamerModeToggle, __instance);
-
- void streamerModeToggle() {
- TheOtherRolesPlugin.StreamerMode.Value = !TheOtherRolesPlugin.StreamerMode.Value;
- updateToggle(streamerModeButton, "Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value);
- }
- }
-
- if ((ghostsSeeTasksButton == null || ghostsSeeTasksButton.gameObject == null)) {
- ghostsSeeTasksButton = createCustomToggle("Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value, Vector3.right * xOffset, (UnityEngine.Events.UnityAction)ghostsSeeTaskToggle, __instance);
-
- void ghostsSeeTaskToggle() {
- TheOtherRolesPlugin.GhostsSeeTasks.Value = !TheOtherRolesPlugin.GhostsSeeTasks.Value;
- MapOptions.ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value;
- updateToggle(ghostsSeeTasksButton, "Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value);
- }
- }
-
- if ((ghostsSeeRolesButton == null || ghostsSeeRolesButton.gameObject == null)) {
- ghostsSeeRolesButton = createCustomToggle("Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value, new Vector2(-xOffset, yOffset), (UnityEngine.Events.UnityAction)ghostsSeeRolesToggle, __instance);
-
- void ghostsSeeRolesToggle() {
- TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value;
- MapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value;
- updateToggle(ghostsSeeRolesButton, "Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value);
- }
- }
-
- if ((ghostsSeeVotesButton == null || ghostsSeeVotesButton.gameObject == null)) {
- ghostsSeeVotesButton = createCustomToggle("Ghosts See Votes: ", TheOtherRolesPlugin.GhostsSeeVotes.Value, new Vector2(0, yOffset), (UnityEngine.Events.UnityAction)ghostsSeeVotesToggle, __instance);
-
- void ghostsSeeVotesToggle() {
- TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value;
- MapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value;
- updateToggle(ghostsSeeVotesButton, "Ghosts See Votes: ", TheOtherRolesPlugin.GhostsSeeVotes.Value);
- }
- }
-
- if ((showRoleSummaryButton == null || showRoleSummaryButton.gameObject == null)) {
- showRoleSummaryButton = createCustomToggle("Role Summary: ", TheOtherRolesPlugin.ShowRoleSummary.Value, new Vector2(xOffset, yOffset), (UnityEngine.Events.UnityAction)showRoleSummaryToggle, __instance);
-
- void showRoleSummaryToggle() {
- TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value;
- MapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value;
- updateToggle(showRoleSummaryButton, "Role Summary: ", TheOtherRolesPlugin.ShowRoleSummary.Value);
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))]
- public static class HiddenTextPatch
- {
- private static void Postfix(TextBoxTMP __instance)
- {
- bool flag = TheOtherRolesPlugin.StreamerMode.Value && (__instance.name == "GameIdText" || __instance.name == "IpTextBox" || __instance.name == "PortTextBox");
- if (flag) __instance.outputText.text = new string('*', __instance.text.Length);
- }
- }
-}
+++ /dev/null
-using HarmonyLib;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using UnityEngine;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch]
- public static class CredentialsPatch {
- public static string fullCredentials =
-$@"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString()}
-<size=80%>Modded by <color=#FCCE03FF>Eisbison</color>,
-<color=#FCCE03FF>Thunderstorm584</color> & <color=#FCCE03FF>EndOfFile</color>
-Balanced with <color=#FCCE03FF>Dhalucard</color>
-Button design by <color=#FCCE03FF>Bavari</color></size>";
-
- public static string mainMenuCredentials =
-$@"Modded by <color=#FCCE03FF>Eisbison</color>, <color=#FCCE03FF>Thunderstorm584</color> & <color=#FCCE03FF>EndOfFile</color>
-Balanced with <color=#FCCE03FF>Dhalucard</color> Design by <color=#FCCE03FF>Bavari</color>";
-
- [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
- private static class VersionShowerPatch
- {
- static void Postfix(VersionShower __instance) {
- var amongUsLogo = GameObject.Find("bannerLogo_AmongUs");
- if (amongUsLogo == null) return;
-
- var credentials = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(__instance.text);
- credentials.transform.position = new Vector3(0, 0.1f, 0);
- credentials.SetText(mainMenuCredentials);
- credentials.alignment = TMPro.TextAlignmentOptions.Center;
- credentials.fontSize *= 0.75f;
-
- var version = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(credentials);
- version.transform.position = new Vector3(0, -0.25f, 0);
- version.SetText($"v{TheOtherRolesPlugin.Version.ToString()}");
-
- credentials.transform.SetParent(amongUsLogo.transform);
- version.transform.SetParent(amongUsLogo.transform);
- }
- }
-
- [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, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
- } else {
- __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, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
- }
- }
- }
-
- [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))]
- private static class LogoPatch
- {
- static void Postfix(PingTracker __instance) {
- var amongUsLogo = GameObject.Find("bannerLogo_AmongUs");
- if (amongUsLogo != null) {
- amongUsLogo.transform.localScale *= 0.6f;
- amongUsLogo.transform.position += Vector3.up * 0.25f;
- }
-
- var torLogo = new GameObject("bannerLogo_TOR");
- torLogo.transform.position = Vector3.up;
- var renderer = torLogo.AddComponent<SpriteRenderer>();
- renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);
- }
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using UnityEngine;
-using UnityEngine.UI;
-
-public class CustomButton
-{
- public static List<CustomButton> buttons = new List<CustomButton>();
- public KillButtonManager killButtonManager;
- public Vector3 PositionOffset;
- public float MaxTimer = float.MaxValue;
- public float Timer = 0f;
- private Action OnClick;
- private Action OnMeetingEnds;
- private Func<bool> HasButton;
- private Func<bool> CouldUse;
- private Action OnEffectEnds;
- public bool HasEffect;
- public bool isEffectActive = false;
- public float EffectDuration;
- public Sprite Sprite;
- private HudManager hudManager;
- private bool mirror;
- private KeyCode? hotkey;
-
- public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false)
- {
- this.hudManager = hudManager;
- this.OnClick = OnClick;
- this.HasButton = HasButton;
- this.CouldUse = CouldUse;
- this.PositionOffset = PositionOffset;
- this.OnMeetingEnds = OnMeetingEnds;
- this.HasEffect = HasEffect;
- this.EffectDuration = EffectDuration;
- this.OnEffectEnds = OnEffectEnds;
- this.Sprite = Sprite;
- this.mirror = mirror;
- this.hotkey = hotkey;
- Timer = 16.2f;
- buttons.Add(this);
- killButtonManager = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.transform);
- PassiveButton button = killButtonManager.GetComponent<PassiveButton>();
- button.OnClick = new Button.ButtonClickedEvent();
- button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent);
-
- setActive(false);
- }
-
- public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool mirror = false)
- : this(OnClick, HasButton, CouldUse, OnMeetingEnds, Sprite, PositionOffset, hudManager, hotkey, false, 0f, () => {}, mirror) { }
-
- void onClickEvent()
- {
- if (this.Timer < 0f && HasButton() && CouldUse())
- {
- killButtonManager.renderer.color = new Color(1f, 1f, 1f, 0.3f);
- this.OnClick();
-
- if (this.HasEffect && !this.isEffectActive) {
- this.Timer = this.EffectDuration;
- killButtonManager.TimerText.color = new Color(0F, 0.8F, 0F);
- this.isEffectActive = true;
- }
- }
- }
-
- public static void HudUpdate()
- {
- buttons.RemoveAll(item => item.killButtonManager == null);
-
- for (int i = 0; i < buttons.Count; i++)
- {
- try
- {
- buttons[i].Update();
- }
- catch (NullReferenceException)
- {
- System.Console.WriteLine("[WARNING] NullReferenceException from HudUpdate().HasButton(), if theres only one warning its fine");
- }
- }
- }
-
- public static void MeetingEndedUpdate() {
- buttons.RemoveAll(item => item.killButtonManager == null);
- for (int i = 0; i < buttons.Count; i++)
- {
- try
- {
- buttons[i].OnMeetingEnds();
- buttons[i].Update();
- }
- catch (NullReferenceException)
- {
- System.Console.WriteLine("[WARNING] NullReferenceException from MeetingEndedUpdate().HasButton(), if theres only one warning its fine");
- }
- }
- }
-
- public static void ResetAllCooldowns() {
- for (int i = 0; i < buttons.Count; i++)
- {
- try
- {
- buttons[i].Timer = buttons[i].MaxTimer;
- buttons[i].Update();
- }
- catch (NullReferenceException)
- {
- System.Console.WriteLine("[WARNING] NullReferenceException from MeetingEndedUpdate().HasButton(), if theres only one warning its fine");
- }
- }
- }
-
- public void setActive(bool isActive) {
- if (isActive) {
- killButtonManager.gameObject.SetActive(true);
- killButtonManager.renderer.enabled = true;
- } else {
- killButtonManager.gameObject.SetActive(false);
- killButtonManager.renderer.enabled = false;
- }
- }
-
- private void Update()
- {
- if (PlayerControl.LocalPlayer.Data == null || MeetingHud.Instance || ExileController.Instance || !HasButton()) {
- setActive(false);
- return;
- }
- setActive(hudManager.UseButton.isActiveAndEnabled);
-
- killButtonManager.renderer.sprite = Sprite;
- if (hudManager.UseButton != null) {
- Vector3 pos = hudManager.UseButton.transform.localPosition;
- if (mirror) pos = new Vector3(-pos.x, pos.y, pos.z);
- killButtonManager.transform.localPosition = pos + PositionOffset;
- if (hudManager.KillButton != null) hudManager.KillButton.transform.localPosition = hudManager.UseButton.transform.localPosition - new Vector3(1.3f, 0, 0); // Align the kill button (because it's on another position depending on the screen resolution)
- }
- if (CouldUse()) {
- killButtonManager.renderer.color = Palette.EnabledColor;
- killButtonManager.renderer.material.SetFloat("_Desat", 0f);
- } else {
- killButtonManager.renderer.color = Palette.DisabledClear;
- killButtonManager.renderer.material.SetFloat("_Desat", 1f);
- }
-
- if (Timer >= 0) {
- if (HasEffect && isEffectActive)
- Timer -= Time.deltaTime;
- else if (!PlayerControl.LocalPlayer.inVent && PlayerControl.LocalPlayer.moveable)
- Timer -= Time.deltaTime;
- }
-
- if (Timer <= 0 && HasEffect && isEffectActive) {
- isEffectActive = false;
- killButtonManager.TimerText.color = Palette.EnabledColor;
- OnEffectEnds();
- }
-
- killButtonManager.SetCoolDown(Timer, (HasEffect && isEffectActive) ? EffectDuration : MaxTimer);
-
- // Trigger OnClickEvent if the hotkey is being pressed down
- if (hotkey.HasValue && Input.GetKeyDown(hotkey.Value)) onClickEvent();
- }
-}
\ No newline at end of file
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using UnityEngine;
-using Il2CppSystem;
-using HarmonyLib;
-using UnhollowerBaseLib;
-using Assets.CoreScripts;
-
-namespace TheOtherRoles {
- 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 = (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<Color32> colorlist = Enumerable.ToList<Color32>(Palette.PlayerColors);
- List<Color32> shadowlist = Enumerable.ToList<Color32>(Palette.ShadowColors);
-
- List<CustomColor> colors = new List<CustomColor>();
-
- /* 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",
- color = new Color32(109, 7, 26, byte.MaxValue),
- shadow = new Color32(54, 2, 11, byte.MaxValue),
- isLighterColor = false });
- 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",
- color = new Color32(22, 132, 176, byte.MaxValue),
- shadow = new Color32(15, 89, 117, byte.MaxValue),
- isLighterColor = false });
- 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",
- color = new Color32(173, 126, 201, byte.MaxValue),
- shadow = new Color32(131, 58, 203, byte.MaxValue),
- isLighterColor = true });
- 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",
- color = new Color32(255, 164, 119, byte.MaxValue),
- shadow = new Color32(238, 128, 100, byte.MaxValue),
- isLighterColor = true });
- colors.Add(new CustomColor { longname = "Wasabi",
- color = new Color32(112, 143, 46, byte.MaxValue),
- shadow = new Color32(72, 92, 29, byte.MaxValue),
- 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 = "Petrol",
- color = new Color32(0, 99, 105, byte.MaxValue),
- shadow = new Color32(0, 61, 54, byte.MaxValue),
- isLighterColor = false });
- 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 = "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 });
-
- 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 = "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;
- colorlist.Add(cc.color);
- shadowlist.Add(cc.shadow);
- if (cc.isLighterColor)
- lighterColors.Add(colorlist.Count - 1);
- }
-
- Palette.ColorNames = longlist.ToArray();
- Palette.PlayerColors = colorlist.ToArray();
- Palette.ShadowColors = shadowlist.ToArray();
- }
-
- protected internal struct CustomColor {
- public string longname;
- public Color32 color;
- public Color32 shadow;
- public bool isLighterColor;
- }
-
- [HarmonyPatch]
- public static class CustomColorPatches {
- [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), new[] {
- typeof(StringNames),
- typeof(Il2CppReferenceArray<Il2CppSystem.Object>)
- })]
- private class ColorStringPatch {
- public static bool Prefix(ref string __result, [HarmonyArgument(0)] StringNames name) {
- if ((int)name >= 50000) {
- string text = CustomColors.ColorStrings[(int)name];
- if (text != null) {
- __result = text;
- return false;
- }
- }
- return true;
- }
- }
- [HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.OnEnable))]
- private static class PlayerTabEnablePatch {
- public static void Postfix(PlayerTab __instance) { // Replace instead
- Il2CppArrayBase<ColorChip> chips = __instance.ColorChips.ToArray();
-
- 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();
- }
- }
- }
- [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.LoadPlayerPrefs))]
- private static class LoadPlayerPrefsPatch { // Fix Potential issues with broken colors
- private static bool needsPatch = false;
- public static void Prefix([HarmonyArgument(0)] bool overrideLoad) {
- if (!SaveManager.loaded || overrideLoad)
- needsPatch = true;
- }
- public static void Postfix() {
- if (!needsPatch) return;
- SaveManager.colorConfig %= CustomColors.pickableColors;
- needsPatch = false;
- }
- }
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckColor))]
- private static class PlayerControlCheckColorPatch {
- private static bool isTaken(PlayerControl player, uint color) {
- foreach (GameData.PlayerInfo p in GameData.Instance.AllPlayers)
- if (!p.Disconnected && p.PlayerId != player.PlayerId && p.ColorId == color)
- return true;
- return false;
- }
- public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte bodyColor) { // Fix incorrect color assignment
- uint color = (uint)bodyColor;
- if (isTaken(__instance, color) || color >= Palette.PlayerColors.Length) {
- int num = 0;
- while (num++ < 50 && (color >= CustomColors.pickableColors || isTaken(__instance, color))) {
- color = (color + 1) % CustomColors.pickableColors;
- }
- }
- __instance.RpcSetColor((byte)color);
- return false;
- }
- }
- }
- }
-}
+++ /dev/null
-using System;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
-using Il2CppSystem;
-using HarmonyLib;
-using UnityEngine;
-using UnhollowerBaseLib;
-using System.IO;
-using System.Reflection;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using System.Security.Cryptography;
-using Newtonsoft.Json.Linq;
-using Newtonsoft.Json;
-
-namespace TheOtherRoles {
- [HarmonyPatch]
- public class CustomHats {
- private static bool LOADED = false;
- public static Material hatShader;
-
- public static Dictionary<string, HatExtension> CustomHatRegistry = new Dictionary<string, HatExtension>();
- public static HatExtension TestExt = null;
-
- public class HatExtension {
- public string author { get; set;}
- public string package { get; set;}
- public string condition { get; set;}
- public Sprite FlipImage { get; set;}
- public Sprite BackFlipImage { get; set;}
-
- public bool isUnlocked() {
- if (condition == null || condition.ToLower() == "none")
- return true;
- return false;
- }
- }
-
- public class CustomHat {
- public string author { get; set;}
- public string package { get; set;}
- public string condition { get; set;}
- public string name { get; set;}
- public string resource { get; set;}
- public string flipresource { get; set;}
- public string backflipresource { get; set;}
- public string backresource { get; set;}
- public string climbresource { get; set;}
- public bool bounce { get; set;}
- public bool adaptive { get; set;}
- public bool behind { get; set;}
- }
-
- private static List<CustomHat> createCustomHatDetails(string[] hats, bool fromDisk = false) {
- Dictionary<string, CustomHat> fronts = new Dictionary<string, CustomHat>();
- Dictionary<string, string> backs = new Dictionary<string, string>();
- Dictionary<string, string> flips = new Dictionary<string, string>();
- Dictionary<string, string> backflips = new Dictionary<string, string>();
- Dictionary<string, string> climbs = new Dictionary<string, string>();
-
- for (int i = 0; i < hats.Length; i++) {
- string s = fromDisk ? hats[i].Substring(hats[i].LastIndexOf("\\") + 1).Split('.')[0] : hats[i].Split('.')[3];
- string[] p = s.Split('_');
-
- HashSet<string> options = new HashSet<string>();
- for (int j = 1; j < p.Length; j++)
- options.Add(p[j]);
-
- if (options.Contains("back") && options.Contains("flip"))
- backflips.Add(p[0], hats[i]);
- else if (options.Contains("climb"))
- climbs.Add(p[0], hats[i]);
- else if (options.Contains("back"))
- backs.Add(p[0], hats[i]);
- else if (options.Contains("flip"))
- flips.Add(p[0], hats[i]);
- else {
- CustomHat custom = new CustomHat { resource = hats[i] };
- custom.name = p[0].Replace('-', ' ');
- custom.bounce = options.Contains("bounce");
- custom.adaptive = options.Contains("adaptive");
- custom.behind = options.Contains("behind");
-
- fronts.Add(p[0], custom);
- }
- }
-
- List<CustomHat> customhats = new List<CustomHat>();
-
- foreach (string k in fronts.Keys) {
- CustomHat hat = fronts[k];
- string br, cr, fr, bfr;
- backs.TryGetValue(k, out br);
- climbs.TryGetValue(k, out cr);
- flips.TryGetValue(k, out fr);
- backflips.TryGetValue(k, out bfr);
- if (br != null)
- hat.backresource = br;
- if (cr != null)
- hat.climbresource = cr;
- if (fr != null)
- hat.flipresource = fr;
- if (bfr != null)
- hat.backflipresource = bfr;
- if (hat.backresource != null)
- hat.behind = true;
-
- customhats.Add(hat);
- }
-
- return customhats;
- }
-
- private static Sprite CreateHatSprite(string path, bool fromDisk = false) {
- 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.53f, 0.575f), texture.width * 0.375f);
- if (sprite == null)
- return null;
- texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
- sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
- return sprite;
- }
-
- private static HatBehaviour CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) {
- if (hatShader == null && DestroyableSingleton<HatManager>.InstanceExists) {
- foreach (HatBehaviour h in DestroyableSingleton<HatManager>.Instance.AllHats) {
- if (h.AltShader != null) {
- hatShader = h.AltShader;
- break;
- }
- }
- }
-
- HatBehaviour hat = new HatBehaviour();
- hat.MainImage = CreateHatSprite(ch.resource, fromDisk);
- if (ch.backresource != null) {
- hat.BackImage = CreateHatSprite(ch.backresource, fromDisk);
- ch.behind = true; // Required to view backresource
- }
- if (ch.climbresource != null)
- hat.ClimbImage = CreateHatSprite(ch.climbresource, fromDisk);
- hat.name = ch.name;
- hat.Order = 99;
- hat.ProductId = "hat_" + ch.name.Replace(' ', '_');
- hat.InFront = !ch.behind;
- hat.NoBounce = !ch.bounce;
- hat.ChipOffset = new Vector2(0f, 0.2f);
-
- if (ch.adaptive && hatShader != null)
- hat.AltShader = hatShader;
-
- HatExtension extend = new HatExtension();
- extend.author = ch.author != null ? ch.author : "Unknown";
- extend.package = ch.package != null ? ch.package : "Misc.";
- extend.condition = ch.condition != null ? ch.condition : "none";
-
- if (ch.flipresource != null)
- extend.FlipImage = CreateHatSprite(ch.flipresource, fromDisk);
- if (ch.backflipresource != null)
- extend.BackFlipImage = CreateHatSprite(ch.backflipresource, fromDisk);
-
- if (testOnly) {
- TestExt = extend;
- TestExt.condition = hat.name;
- } else {
- CustomHatRegistry.Add(hat.name, extend);
- }
-
- return hat;
- }
-
- private static HatBehaviour CreateHatBehaviour(CustomHatLoader.CustomHatOnline chd) {
- string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\";
- chd.resource = filePath + chd.resource;
- if (chd.backresource != null)
- chd.backresource = filePath + chd.backresource;
- if (chd.climbresource != null)
- chd.climbresource = filePath + chd.climbresource;
- if (chd.flipresource != null)
- chd.flipresource = filePath + chd.flipresource;
- if (chd.backflipresource != null)
- chd.backflipresource = filePath + chd.backflipresource;
- return CreateHatBehaviour(chd, true);
- }
-
- [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))]
- private static class HatManagerPatch {
- static bool Prefix(HatManager __instance) {
- try {
- if (!LOADED) {
- Assembly assembly = Assembly.GetExecutingAssembly();
- string hatres = $"{assembly.GetName().Name}.Resources.CustomHats";
- string[] hats = (from r in assembly.GetManifestResourceNames()
- where r.StartsWith(hatres) && r.EndsWith(".png")
- select r).ToArray<string>();
-
- List<CustomHat> customhats = createCustomHatDetails(hats);
- foreach (CustomHat ch in customhats)
- __instance.AllHats.Add(CreateHatBehaviour(ch));
-
- while (CustomHatLoader.hatdetails.Count > 0) {
- __instance.AllHats.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0]));
- CustomHatLoader.hatdetails.RemoveAt(0);
- }
-
- LOADED = true;
- }
- return true;
- } catch (System.Exception e) {
- System.Console.WriteLine("Unable to add Custom Hats\n" + e);
- return false;
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleAnimation))]
- private static class PlayerPhysicsHandleAnimationPatch {
- private static void Postfix(PlayerPhysics __instance) {
- AnimationClip currentAnimation = __instance.Animator.GetCurrentAnimation();
- if (currentAnimation == __instance.ClimbAnim || currentAnimation == __instance.ClimbDownAnim) return;
- HatParent hp = __instance.myPlayer.HatRenderer;
- if (hp.Hat == null) return;
- HatExtension extend = hp.Hat.getHatExtension();
- if (extend == null) return;
- if (extend.FlipImage != null) {
- if (__instance.rend.flipX) {
- hp.FrontLayer.sprite = extend.FlipImage;
- } else {
- hp.FrontLayer.sprite = hp.Hat.MainImage;
- }
- }
- if (extend.BackFlipImage != null) {
- if (__instance.rend.flipX) {
- hp.BackLayer.sprite = extend.BackFlipImage;
- } else {
- hp.BackLayer.sprite = hp.Hat.BackImage;
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), new System.Type[] { typeof(uint), typeof(int) })]
- private static class HatParentSetHatPatch {
- static void Postfix(HatParent __instance, [HarmonyArgument(0)]uint hatId, [HarmonyArgument(1)]int color) {
- if (DestroyableSingleton<TutorialManager>.InstanceExists) {
- try {
- string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test";
- DirectoryInfo d = new DirectoryInfo(filePath);
- string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Text files
- List<CustomHat> hats = createCustomHatDetails(filePaths, true);
- if (hats.Count > 0) {
- __instance.Hat = CreateHatBehaviour(hats[0], true, true);
- __instance.SetHat(color);
- }
- } catch (System.Exception e) {
- System.Console.WriteLine("Unable to create test hat\n" + e);
- }
- }
- }
- }
-
- private static List<TMPro.TMP_Text> hatsTabCustomTexts = new List<TMPro.TMP_Text>();
-
- [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.OnEnable))]
- public class HatsTabOnEnablePatch {
- public static string innerslothPackageName = "Innersloth Hats";
- private static TMPro.TMP_Text textTemplate;
-
- public static float createHatPackage(List<System.Tuple<HatBehaviour, HatExtension>> hats, string packageName, float YStart, HatsTab __instance) {
- bool isDefaultPackage = innerslothPackageName == packageName;
- float offset = YStart;
-
- if (textTemplate != null) {
- TMPro.TMP_Text title = UnityEngine.Object.Instantiate<TMPro.TMP_Text>(textTemplate, __instance.scroller.Inner);
- title.transform.localPosition = new Vector3(2.25f, YStart, -1f);
- title.transform.localScale = Vector3.one * 1.5f;
- // title.currentFontSize
- title.fontSize *= 0.5f;
- title.enableAutoSizing = false;
- __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => { title.SetText(packageName); })));
- offset -= 0.8f * __instance.YOffset;
- hatsTabCustomTexts.Add(title);
- }
- for (int i = 0; i < hats.Count; i++) {
- HatBehaviour hat = hats[i].Item1;
- HatExtension ext = hats[i].Item2;
-
- float xpos = __instance.XRange.Lerp((i % __instance.NumPerRow) / (__instance.NumPerRow - 1f));
- float ypos = offset - (i / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset;
- ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(__instance.ColorTabPrefab, __instance.scroller.Inner);
- if (ext != null) {
- Transform background = colorChip.transform.FindChild("Background");
- Transform foreground = colorChip.transform.FindChild("ForeGround");
-
- if (background != null) {
- background.localScale = new Vector3(1, 1.5f, 1);
- background.localPosition = Vector3.down * 0.243f;
- }
- if (foreground != null) {
- foreground.localPosition = Vector3.down * 0.243f;
- }
-
- if (textTemplate != null) {
- TMPro.TMP_Text description = UnityEngine.Object.Instantiate<TMPro.TMP_Text>(textTemplate, colorChip.transform);
- description.transform.localPosition = new Vector3(0f, -0.75f, -1f);
- description.transform.localScale = Vector3.one * 0.7f;
- __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => { description.SetText($"{hat.name}\nby {ext.author}"); })));
- hatsTabCustomTexts.Add(description);
- }
-
- if (!ext.isUnlocked()) { // Hat is locked
- UnityEngine.Object.Destroy(colorChip.Button);
- var overlay = UnityEngine.Object.Instantiate(colorChip.InUseForeground, colorChip.transform);
- overlay.SetActive(true);
- }
- }
-
- colorChip.transform.localPosition = new Vector3(xpos, ypos, -1f);
- colorChip.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { __instance.SelectHat(hat); }));
- colorChip.Inner.SetHat(hat, PlayerControl.LocalPlayer.Data.ColorId);
- colorChip.Inner.transform.localPosition = hat.ChipOffset;
- colorChip.Tag = hat;
- __instance.ColorChips.Add(colorChip);
- }
- return offset - ((hats.Count - 1) / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset - 0.85f;
- }
-
- public static bool Prefix(HatsTab __instance) {
- PlayerControl.SetPlayerMaterialColors(PlayerControl.LocalPlayer.Data.ColorId, __instance.DemoImage);
- __instance.HatImage.SetHat(SaveManager.LastHat, PlayerControl.LocalPlayer.Data.ColorId);
- PlayerControl.SetSkinImage(SaveManager.LastSkin, __instance.SkinImage);
- PlayerControl.SetPetImage(SaveManager.LastPet, PlayerControl.LocalPlayer.Data.ColorId, __instance.PetImage);
-
- HatBehaviour[] unlockedHats = DestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
- Dictionary<string, List<System.Tuple<HatBehaviour, HatExtension>>> packages = new Dictionary<string, List<System.Tuple<HatBehaviour, HatExtension>>>();
- hatsTabCustomTexts = new List<TMPro.TMP_Text>();
-
- foreach (HatBehaviour hatBehaviour in unlockedHats) {
- HatExtension ext = hatBehaviour.getHatExtension();
-
- if (ext != null) {
- if (!packages.ContainsKey(ext.package))
- packages[ext.package] = new List<System.Tuple<HatBehaviour, HatExtension>>();
- packages[ext.package].Add(new System.Tuple<HatBehaviour, HatExtension>(hatBehaviour, ext));
- } else {
- if (!packages.ContainsKey(innerslothPackageName))
- packages[innerslothPackageName] = new List<System.Tuple<HatBehaviour, HatExtension>>();
- packages[innerslothPackageName].Add(new System.Tuple<HatBehaviour, HatExtension>(hatBehaviour, null));
- }
- }
-
- float YOffset = __instance.YStart;
-
- var hatButton = GameObject.Find("HatButton");
-
- if (hatButton != null && hatButton.transform.FindChild("ButtonText_TMP") != null) {
- textTemplate = hatButton.transform.FindChild("ButtonText_TMP").GetComponent<TMPro.TMP_Text>();
- }
-
- var orderedKeys = packages.Keys.OrderBy((string x) => {
- if (x == innerslothPackageName) return 1000;
- if (x == "Developer Hats") return 0;
- return 500;
- });
- foreach (string key in orderedKeys) {
- List<System.Tuple<HatBehaviour, HatExtension>> value = packages[key];
- YOffset = createHatPackage(value, key, YOffset, __instance);
- }
-
- // __instance.scroller.YBounds.max = -(__instance.YStart - (float)(unlockedHats.Length / this.NumPerRow) * this.YOffset) - 3f;
- // __instance.scroller.YBounds.max = YOffset * -0.875f; // probably needs to fix up the entire messed math to solve this correctly
- __instance.scroller.YBounds.max = -(YOffset + 4.1f);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))]
- public class HatsTabUpdatePatch {
- public static void Postfix(HatsTab __instance) {
- // Manually hide all custom TMPro.TMP_Text objects that are outside the ScrollRect
- foreach (TMPro.TMP_Text customText in hatsTabCustomTexts) {
- if (customText != null && customText.transform != null && customText.gameObject != null) {
- bool active = customText.transform.position.y <= 3.75f && customText.transform.position.y >= 0.3f;
- float epsilon = Mathf.Min(Mathf.Abs(customText.transform.position.y - 3.75f), Mathf.Abs(customText.transform.position.y - 0.35f));
- if (active != customText.gameObject.active && epsilon > 0.1f) customText.gameObject.SetActive(active);
- }
- }
- }
- }
- }
-
- public class CustomHatLoader {
- public static bool running = false;
- private const string REPO = "https://raw.githubusercontent.com/Eisbison/TheOtherHats/master";
-
- public static List<CustomHatOnline> hatdetails = new List<CustomHatOnline>();
- private static Task hatFetchTask = null;
- public static void LaunchHatFetcher() {
- if (running)
- return;
- running = true;
- hatFetchTask = LaunchHatFetcherAsync();
- }
-
- private static async Task LaunchHatFetcherAsync() {
- try {
- HttpStatusCode status = await FetchHats();
- if (status != HttpStatusCode.OK)
- System.Console.WriteLine("Custom Hats could not be loaded\n");
- } catch (System.Exception e) {
- System.Console.WriteLine("Unable to fetch hats\n" + e.Message);
- }
- running = false;
- }
-
- private static string sanitizeResourcePath(string res) {
- if (res == null || !res.EndsWith(".png"))
- return null;
-
- res = res.Replace("\\", "")
- .Replace("/", "")
- .Replace("*", "")
- .Replace("..", "");
- return res;
- }
-
- public static async Task<HttpStatusCode> FetchHats() {
- HttpClient http = new HttpClient();
- http.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue{ NoCache = true };
- var response = await http.GetAsync(new System.Uri($"{REPO}/CustomHats.json"), HttpCompletionOption.ResponseContentRead);
- try {
- if (response.StatusCode != HttpStatusCode.OK) return response.StatusCode;
- if (response.Content == null) {
- System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString());
- return HttpStatusCode.ExpectationFailed;
- }
- string json = await response.Content.ReadAsStringAsync();
- JToken jobj = JObject.Parse(json)["hats"];
- if (!jobj.HasValues) return HttpStatusCode.ExpectationFailed;
-
- List<CustomHatOnline> hatdatas = new List<CustomHatOnline>();
-
- for (JToken current = jobj.First; current != null; current = current.Next) {
- if (current.HasValues) {
- CustomHatOnline info = new CustomHatOnline();
-
- info.name = current["name"]?.ToString();
- info.resource = sanitizeResourcePath(current["resource"]?.ToString());
- if (info.resource == null || info.name == null) // required
- continue;
- info.reshasha = current["reshasha"]?.ToString();
- info.backresource = sanitizeResourcePath(current["backresource"]?.ToString());
- info.reshashb = current["reshashb"]?.ToString();
- info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString());
- info.reshashc = current["reshashc"]?.ToString();
- info.flipresource = sanitizeResourcePath(current["flipresource"]?.ToString());
- info.reshashf = current["reshashf"]?.ToString();
- info.backflipresource = sanitizeResourcePath(current["backflipresource"]?.ToString());
- info.reshashbf = current["reshashbf"]?.ToString();
-
- info.author = current["author"]?.ToString();
- info.package = current["package"]?.ToString();
- info.condition = current["condition"]?.ToString();
- info.bounce = current["bounce"] != null;
- info.adaptive = current["adaptive"] != null;
- info.behind = current["behind"] != null;
- hatdatas.Add(info);
- }
- }
-
- List<string> markedfordownload = new List<string>();
-
- string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\";
- MD5 md5 = MD5.Create();
- foreach (CustomHatOnline data in hatdatas) {
- if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5))
- markedfordownload.Add(data.resource);
- if (data.backresource != null && doesResourceRequireDownload(filePath + data.backresource, data.reshashb, md5))
- markedfordownload.Add(data.backresource);
- if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5))
- markedfordownload.Add(data.climbresource);
- if (data.flipresource != null && doesResourceRequireDownload(filePath + data.flipresource, data.reshashf, md5))
- markedfordownload.Add(data.flipresource);
- if (data.backflipresource != null && doesResourceRequireDownload(filePath + data.backflipresource, data.reshashbf, md5))
- markedfordownload.Add(data.backflipresource);
- }
-
- foreach(var file in markedfordownload) {
-
- var hatFileResponse = await http.GetAsync($"{REPO}/hats/{file}", HttpCompletionOption.ResponseContentRead);
- if (hatFileResponse.StatusCode != HttpStatusCode.OK) continue;
- using (var responseStream = await hatFileResponse.Content.ReadAsStreamAsync()) {
- using (var fileStream = File.Create($"{filePath}\\{file}")) {
- responseStream.CopyTo(fileStream);
- }
- }
- }
-
- hatdetails = hatdatas;
- } catch (System.Exception ex) {
- TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
- System.Console.WriteLine(ex);
- }
- return HttpStatusCode.OK;
- }
-
- private static bool doesResourceRequireDownload(string respath, string reshash, MD5 md5) {
- if (reshash == null || !File.Exists(respath))
- return true;
-
- using (var stream = File.OpenRead(respath)) {
- var hash = System.BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
- return !reshash.Equals(hash);
- }
- }
-
- public class CustomHatOnline : CustomHats.CustomHat {
- public string reshasha { get; set;}
- public string reshashb { get; set;}
- public string reshashc { get; set;}
- public string reshashf { get; set;}
- public string reshashbf { get; set;}
- }
- }
- public static class CustomHatExtensions {
- public static CustomHats.HatExtension getHatExtension(this HatBehaviour hat) {
- CustomHats.HatExtension ret = null;
- if (CustomHats.TestExt != null && CustomHats.TestExt.condition.Equals(hat.name)) {
- return CustomHats.TestExt;
- }
- CustomHats.CustomHatRegistry.TryGetValue(hat.name, out ret);
- return ret;
- }
- }
-}
+++ /dev/null
-using UnityEngine;
-using System.Collections.Generic;
-using System;
-
-namespace TheOtherRoles{
-
- public class CustomMessage {
-
- private TMPro.TMP_Text text;
- private static List<CustomMessage> customMessages = new List<CustomMessage>();
-
- public CustomMessage(string message, float duration) {
- RoomTracker roomTracker = HudManager.Instance?.roomTracker;
- if (roomTracker != null) {
- GameObject gameObject = UnityEngine.Object.Instantiate(roomTracker.gameObject);
-
- gameObject.transform.SetParent(HudManager.Instance.transform);
- UnityEngine.Object.DestroyImmediate(gameObject.GetComponent<RoomTracker>());
- text = gameObject.GetComponent<TMPro.TMP_Text>();
- text.text = message;
-
- // Use local position to place it in the player's view instead of the world location
- gameObject.transform.localPosition = new Vector3(0, -1.8f, gameObject.transform.localPosition.z);
- customMessages.Add(this);
-
- HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
- bool even = ((int)(p * duration / 0.25f)) % 2 == 0; // Bool flips every 0.25 seconds
- string prefix = (even ? "<color=#FCBA03FF>" : "<color=#FF0000FF>");
- text.text = prefix + message + "</color>";
- if (text != null) text.color = even ? Color.yellow : Color.red;
- if (p == 1f && text != null && text.gameObject != null) {
- UnityEngine.Object.Destroy(text.gameObject);
- customMessages.Remove(this);
- }
- })));
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using System.Collections.Generic;
-using UnityEngine;
-using BepInEx.Configuration;
-using System;
-using System.Linq;
-using HarmonyLib;
-using Hazel;
-using System.Reflection;
-using System.Text;
-using static TheOtherRoles.TheOtherRoles;
-
-namespace TheOtherRoles {
- public class CustomOptionHolder {
- public static string[] rates = new string[]{"0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"};
- public static string[] presets = new string[]{"Preset 1", "Preset 2", "Preset 3", "Preset 4", "Preset 5"};
-
- public static CustomOption presetSelection;
- public static CustomOption crewmateRolesCountMin;
- public static CustomOption crewmateRolesCountMax;
- public static CustomOption neutralRolesCountMin;
- public static CustomOption neutralRolesCountMax;
- public static CustomOption impostorRolesCountMin;
- public static CustomOption impostorRolesCountMax;
-
- public static CustomOption mafiaSpawnRate;
- public static CustomOption janitorCooldown;
-
- public static CustomOption morphlingSpawnRate;
- public static CustomOption morphlingCooldown;
- public static CustomOption morphlingDuration;
-
- public static CustomOption camouflagerSpawnRate;
- public static CustomOption camouflagerCooldown;
- public static CustomOption camouflagerDuration;
-
- public static CustomOption vampireSpawnRate;
- public static CustomOption vampireKillDelay;
- public static CustomOption vampireCooldown;
- public static CustomOption vampireCanKillNearGarlics;
-
- public static CustomOption eraserSpawnRate;
- public static CustomOption eraserCooldown;
- public static CustomOption eraserCanEraseAnyone;
-
- public static CustomOption miniSpawnRate;
- public static CustomOption miniGrowingUpDuration;
-
- public static CustomOption loversSpawnRate;
- public static CustomOption loversImpLoverRate;
- public static CustomOption loversBothDie;
- public static CustomOption loversCanHaveAnotherRole;
-
- public static CustomOption guesserSpawnRate;
- public static CustomOption guesserIsImpGuesserRate;
- public static CustomOption guesserNumberOfShots;
-
- public static CustomOption jesterSpawnRate;
- public static CustomOption jesterCanCallEmergency;
- public static CustomOption jesterCanSabotage;
-
- public static CustomOption arsonistSpawnRate;
- public static CustomOption arsonistCooldown;
- public static CustomOption arsonistDuration;
-
- public static CustomOption jackalSpawnRate;
- public static CustomOption jackalKillCooldown;
- public static CustomOption jackalCreateSidekickCooldown;
- public static CustomOption jackalCanUseVents;
- public static CustomOption jackalCanCreateSidekick;
- public static CustomOption sidekickPromotesToJackal;
- public static CustomOption sidekickCanKill;
- public static CustomOption sidekickCanUseVents;
- public static CustomOption jackalPromotedFromSidekickCanCreateSidekick;
- public static CustomOption jackalCanCreateSidekickFromImpostor;
- public static CustomOption 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;
-
- public static CustomOption engineerSpawnRate;
-
- public static CustomOption sheriffSpawnRate;
- public static CustomOption sheriffCooldown;
- public static CustomOption sheriffCanKillNeutrals;
-
- public static CustomOption lighterSpawnRate;
- public static CustomOption lighterModeLightsOnVision;
- public static CustomOption lighterModeLightsOffVision;
- public static CustomOption lighterCooldown;
- public static CustomOption lighterDuration;
-
- public static CustomOption detectiveSpawnRate;
- public static CustomOption detectiveAnonymousFootprints;
- public static CustomOption detectiveFootprintIntervall;
- public static CustomOption detectiveFootprintDuration;
- public static CustomOption detectiveReportNameDuration;
- public static CustomOption detectiveReportColorDuration;
-
- public static CustomOption timeMasterSpawnRate;
- public static CustomOption timeMasterCooldown;
- public static CustomOption timeMasterRewindTime;
- public static CustomOption timeMasterShieldDuration;
-
- public static CustomOption medicSpawnRate;
- public static CustomOption medicShowShielded;
- public static CustomOption medicShowAttemptToShielded;
-
- public static CustomOption swapperSpawnRate;
- public static CustomOption swapperCanCallEmergency;
- public static CustomOption swapperCanOnlySwapOthers;
-
- public static CustomOption seerSpawnRate;
- public static CustomOption seerMode;
- public static CustomOption seerSoulDuration;
- public static CustomOption seerLimitSoulDuration;
-
- public static CustomOption hackerSpawnRate;
- public static CustomOption hackerCooldown;
- public static CustomOption hackerHackeringDuration;
- public static CustomOption hackerOnlyColorType;
-
- public static CustomOption trackerSpawnRate;
- public static CustomOption trackerUpdateIntervall;
-
- public static CustomOption snitchSpawnRate;
- public static CustomOption snitchLeftTasksForImpostors;
-
- public static CustomOption spySpawnRate;
- public static CustomOption spyCanDieToSheriff;
- public static CustomOption spyImpostorsCanKillAnyone;
- public static CustomOption spyCanEnterVents;
- public static CustomOption spyHasImpostorVision;
-
- public static CustomOption tricksterSpawnRate;
- public static CustomOption tricksterPlaceBoxCooldown;
- public static CustomOption tricksterLightsOutCooldown;
- public static CustomOption tricksterLightsOutDuration;
-
- public static CustomOption cleanerSpawnRate;
- public static CustomOption cleanerCooldown;
-
- public static CustomOption warlockSpawnRate;
- public static CustomOption warlockCooldown;
- public static CustomOption warlockRootTime;
-
- public static CustomOption securityGuardSpawnRate;
- public static CustomOption securityGuardCooldown;
- public static CustomOption securityGuardTotalScrews;
- public static CustomOption securityGuardCamPrice;
- public static CustomOption securityGuardVentPrice;
-
- public static CustomOption maxNumberOfMeetings;
- public static CustomOption blockSkippingInEmergencyMeetings;
- public static CustomOption noVoteIsSelfVote;
- public static CustomOption hidePlayerNames;
-
- internal static Dictionary<byte, byte[]> blockedRolePairings = new Dictionary<byte, byte[]>();
-
- public static string cs(Color c, string s) {
- return string.Format("<color=#{0:X2}{1:X2}{2:X2}{3:X2}>{4}</color>", ToByte(c.r), ToByte(c.g), ToByte(c.b), ToByte(c.a), s);
- }
-
- private static byte ToByte(float f) {
- f = Mathf.Clamp01(f);
- return (byte)(f * 255);
- }
-
- public static void Load() {
-
- // Role Options
- presetSelection = CustomOption.Create(0, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true);
-
- // Using new id's for the options to not break compatibilty with older versions
- crewmateRolesCountMin = CustomOption.Create(300, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 0f, 0f, 15f, 1f, null, true);
- crewmateRolesCountMax = CustomOption.Create(301, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 0f, 0f, 15f, 1f);
- neutralRolesCountMin = CustomOption.Create(302, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Neutral Roles"), 0f, 0f, 15f, 1f);
- neutralRolesCountMax = CustomOption.Create(303, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Neutral Roles"), 0f, 0f, 15f, 1f);
- impostorRolesCountMin = CustomOption.Create(304, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Impostor Roles"), 0f, 0f, 3f, 1f);
- impostorRolesCountMax = CustomOption.Create(305, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Impostor Roles"), 0f, 0f, 3f, 1f);
-
- mafiaSpawnRate = CustomOption.Create(10, cs(Janitor.color, "Mafia"), rates, null, true);
- janitorCooldown = CustomOption.Create(11, "Janitor Cooldown", 30f, 10f, 60f, 2.5f, mafiaSpawnRate);
-
- morphlingSpawnRate = CustomOption.Create(20, cs(Morphling.color, "Morphling"), rates, null, true);
- morphlingCooldown = CustomOption.Create(21, "Morphling Cooldown", 30f, 10f, 60f, 2.5f, morphlingSpawnRate);
- morphlingDuration = CustomOption.Create(22, "Morph Duration", 10f, 1f, 20f, 0.5f, morphlingSpawnRate);
-
- camouflagerSpawnRate = CustomOption.Create(30, cs(Camouflager.color, "Camouflager"), rates, null, true);
- camouflagerCooldown = CustomOption.Create(31, "Camouflager Cooldown", 30f, 10f, 60f, 2.5f, camouflagerSpawnRate);
- camouflagerDuration = CustomOption.Create(32, "Camo Duration", 10f, 1f, 20f, 0.5f, camouflagerSpawnRate);
-
- vampireSpawnRate = CustomOption.Create(40, cs(Vampire.color, "Vampire"), rates, null, true);
- vampireKillDelay = CustomOption.Create(41, "Vampire Kill Delay", 10f, 1f, 20f, 1f, vampireSpawnRate);
- vampireCooldown = CustomOption.Create(42, "Vampire Cooldown", 30f, 10f, 60f, 2.5f, vampireSpawnRate);
- vampireCanKillNearGarlics = CustomOption.Create(43, "Vampire Can Kill Near Garlics", true, vampireSpawnRate);
-
- eraserSpawnRate = CustomOption.Create(230, cs(Eraser.color, "Eraser"), rates, null, true);
- eraserCooldown = CustomOption.Create(231, "Eraser Cooldown", 30f, 10f, 120f, 5f, eraserSpawnRate);
- eraserCanEraseAnyone = CustomOption.Create(232, "Eraser Can Erase Anyone", false, eraserSpawnRate);
-
- tricksterSpawnRate = CustomOption.Create(250, cs(Trickster.color, "Trickster"), rates, null, true);
- tricksterPlaceBoxCooldown = CustomOption.Create(251, "Trickster Box Cooldown", 10f, 0f, 30f, 2.5f, tricksterSpawnRate);
- tricksterLightsOutCooldown = CustomOption.Create(252, "Trickster Lights Out Cooldown", 30f, 10f, 60f, 5f, tricksterSpawnRate);
- tricksterLightsOutDuration = CustomOption.Create(253, "Trickster Lights Out Duration", 15f, 5f, 60f, 2.5f, tricksterSpawnRate);
-
- cleanerSpawnRate = CustomOption.Create(260, cs(Cleaner.color, "Cleaner"), rates, null, true);
- cleanerCooldown = CustomOption.Create(261, "Cleaner Cooldown", 30f, 10f, 60f, 2.5f, cleanerSpawnRate);
-
- 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);
-
- loversSpawnRate = CustomOption.Create(50, cs(Lovers.color, "Lovers"), rates, null, true);
- loversImpLoverRate = CustomOption.Create(51, "Chance That One Lover Is Impostor", rates, loversSpawnRate);
- loversBothDie = CustomOption.Create(52, "Both Lovers Die", true, loversSpawnRate);
- loversCanHaveAnotherRole = CustomOption.Create(53, "Lovers Can Have Another Role", true, loversSpawnRate);
-
- guesserSpawnRate = CustomOption.Create(310, cs(Guesser.color, "Guesser"), rates, null, true);
- guesserIsImpGuesserRate = CustomOption.Create(311, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate);
- guesserNumberOfShots = CustomOption.Create(312, "Guesser Number Of Shots", 2f, 1f, 15f, 1f, guesserSpawnRate);
-
- jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true);
- jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate);
- jesterCanSabotage = CustomOption.Create(62, "Jester can sabotage", true, jesterSpawnRate);
-
- arsonistSpawnRate = CustomOption.Create(290, cs(Arsonist.color, "Arsonist"), rates, null, true);
- arsonistCooldown = CustomOption.Create(291, "Arsonist Cooldown", 12.5f, 2.5f, 60f, 2.5f, arsonistSpawnRate);
- arsonistDuration = CustomOption.Create(292, "Arsonist Douse Duration", 3f, 1f, 10f, 1f, arsonistSpawnRate);
-
- jackalSpawnRate = CustomOption.Create(220, cs(Jackal.color, "Jackal"), rates, null, true);
- jackalKillCooldown = CustomOption.Create(221, "Jackal/Sidekick Kill Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate);
- jackalCreateSidekickCooldown = CustomOption.Create(222, "Jackal Create Sidekick Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate);
- jackalCanUseVents = CustomOption.Create(223, "Jackal Can Use Vents", true, jackalSpawnRate);
- jackalCanCreateSidekick = CustomOption.Create(224, "Jackal Can Create A Sidekick", false, jackalSpawnRate);
- sidekickPromotesToJackal = CustomOption.Create(225, "Sidekick Gets Promoted To Jackal On Jackal Death", false, jackalSpawnRate);
- sidekickCanKill = CustomOption.Create(226, "Sidekick Can Kill", false, jackalSpawnRate);
- sidekickCanUseVents = CustomOption.Create(227, "Sidekick Can Use Vents", true, jackalSpawnRate);
- jackalPromotedFromSidekickCanCreateSidekick = CustomOption.Create(228, "Jackals Promoted From Sidekick Can Create A Sidekick", true, jackalSpawnRate);
- jackalCanCreateSidekickFromImpostor = CustomOption.Create(229, "Jackals Can Make An Impostor To His Sidekick", true, jackalSpawnRate);
- 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);
-
- engineerSpawnRate = CustomOption.Create(90, cs(Engineer.color, "Engineer"), rates, null, true);
-
- sheriffSpawnRate = CustomOption.Create(100, cs(Sheriff.color, "Sheriff"), rates, null, true);
- sheriffCooldown = CustomOption.Create(101, "Sheriff Cooldown", 30f, 10f, 60f, 2.5f, sheriffSpawnRate);
- sheriffCanKillNeutrals = CustomOption.Create(102, "Sheriff Can Kill Neutrals", false, sheriffSpawnRate);
-
-
- lighterSpawnRate = CustomOption.Create(110, cs(Lighter.color, "Lighter"), rates, null, true);
- lighterModeLightsOnVision = CustomOption.Create(111, "Lighter Mode Vision On Lights On", 2f, 0.25f, 5f, 0.25f, lighterSpawnRate);
- lighterModeLightsOffVision = CustomOption.Create(112, "Lighter Mode Vision On Lights Off", 0.75f, 0.25f, 5f, 0.25f, lighterSpawnRate);
- lighterCooldown = CustomOption.Create(113, "Lighter Cooldown", 30f, 5f, 120f, 5f, lighterSpawnRate);
- lighterDuration = CustomOption.Create(114, "Lighter Duration", 5f, 2.5f, 60f, 2.5f, lighterSpawnRate);
-
- detectiveSpawnRate = CustomOption.Create(120, cs(Detective.color, "Detective"), rates, null, true);
- detectiveAnonymousFootprints = CustomOption.Create(121, "Anonymous Footprints", false, detectiveSpawnRate);
- detectiveFootprintIntervall = CustomOption.Create(122, "Footprint Intervall", 0.5f, 0.25f, 10f, 0.25f, detectiveSpawnRate);
- detectiveFootprintDuration = CustomOption.Create(123, "Footprint Duration", 5f, 0.25f, 10f, 0.25f, detectiveSpawnRate);
- detectiveReportNameDuration = CustomOption.Create(124, "Time Where Detective Reports Will Have Name", 0, 0, 60, 2.5f, detectiveSpawnRate);
- detectiveReportColorDuration = CustomOption.Create(125, "Time Where Detective Reports Will Have Color Type", 20, 0, 120, 2.5f, detectiveSpawnRate);
-
- timeMasterSpawnRate = CustomOption.Create(130, cs(TimeMaster.color, "Time Master"), rates, null, true);
- timeMasterCooldown = CustomOption.Create(131, "Time Master Cooldown", 30f, 10f, 120f, 2.5f, timeMasterSpawnRate);
- timeMasterRewindTime = CustomOption.Create(132, "Rewind Time", 3f, 1f, 10f, 1f, timeMasterSpawnRate);
- timeMasterShieldDuration = CustomOption.Create(133, "Time Master Shield Duration", 3f, 1f, 20f, 1f, timeMasterSpawnRate);
-
- medicSpawnRate = CustomOption.Create(140, cs(Medic.color, "Medic"), rates, null, true);
- medicShowShielded = CustomOption.Create(143, "Show Shielded Player", new string[] {"Everyone", "Shielded + Medic", "Medic"}, medicSpawnRate);
- medicShowAttemptToShielded = CustomOption.Create(144, "Shielded Player Sees Murder Attempt", false, medicSpawnRate);
-
- swapperSpawnRate = CustomOption.Create(150, cs(Swapper.color, "Swapper"), rates, null, true);
- swapperCanCallEmergency = CustomOption.Create(151, "Swapper can call emergency meeting", false, swapperSpawnRate);
- swapperCanOnlySwapOthers = CustomOption.Create(152, "Swapper can only swap others", false, swapperSpawnRate);
-
- seerSpawnRate = CustomOption.Create(160, cs(Seer.color, "Seer"), rates, null, true);
- seerMode = CustomOption.Create(161, "Seer Mode", new string[]{ "Show Death Flash + Souls", "Show Death Flash", "Show Souls"}, seerSpawnRate);
- seerLimitSoulDuration = CustomOption.Create(163, "Seer Limit Soul Duration", false, seerSpawnRate);
- seerSoulDuration = CustomOption.Create(162, "Seer Soul Duration", 15f, 0f, 60f, 5f, seerLimitSoulDuration);
-
- hackerSpawnRate = CustomOption.Create(170, cs(Hacker.color, "Hacker"), rates, null, true);
- hackerCooldown = CustomOption.Create(171, "Hacker Cooldown", 30f, 0f, 60f, 5f, hackerSpawnRate);
- hackerHackeringDuration = CustomOption.Create(172, "Hacker Duration", 10f, 2.5f, 60f, 2.5f, hackerSpawnRate);
- hackerOnlyColorType = CustomOption.Create(173, "Hacker Only Sees Color Type", false, hackerSpawnRate);
-
- trackerSpawnRate = CustomOption.Create(200, cs(Tracker.color, "Tracker"), rates, null, true);
- trackerUpdateIntervall = CustomOption.Create(201, "Tracker Update Intervall", 5f, 2.5f, 30f, 2.5f, trackerSpawnRate);
-
- snitchSpawnRate = CustomOption.Create(210, cs(Snitch.color, "Snitch"), rates, null, true);
- snitchLeftTasksForImpostors = CustomOption.Create(211, "Task Count Where Impostors See Snitch", 1f, 0f, 5f, 1f, snitchSpawnRate);
-
- spySpawnRate = CustomOption.Create(240, cs(Spy.color, "Spy"), rates, null, true);
- spyCanDieToSheriff = CustomOption.Create(241, "Spy Can Die To Sheriff", false, spySpawnRate);
- spyImpostorsCanKillAnyone = CustomOption.Create(242, "Impostors Can Kill Anyone If There Is A Spy", true, spySpawnRate);
- spyCanEnterVents = CustomOption.Create(243, "Spy Can Enter Vents", false, spySpawnRate);
- spyHasImpostorVision = CustomOption.Create(244, "Spy Has Impostor Vision", false, spySpawnRate);
-
- securityGuardSpawnRate = CustomOption.Create(280, cs(SecurityGuard.color, "Security Guard"), rates, null, true);
- securityGuardCooldown = CustomOption.Create(281, "Security Guard Cooldown", 30f, 10f, 60f, 2.5f, securityGuardSpawnRate);
- securityGuardTotalScrews = CustomOption.Create(282, "Security Guard Number Of Screws", 7f, 1f, 15f, 1f, securityGuardSpawnRate);
- securityGuardCamPrice = CustomOption.Create(283, "Number Of Screws Per Cam", 2f, 1f, 15f, 1f, securityGuardSpawnRate);
- securityGuardVentPrice = CustomOption.Create(284, "Number Of Screws Per Vent", 1f, 1f, 15f, 1f, securityGuardSpawnRate);
-
- // Other options
- maxNumberOfMeetings = CustomOption.Create(3, "Number Of Meetings (excluding Mayor meeting)", 10, 0, 15, 1, null, true);
- blockSkippingInEmergencyMeetings = CustomOption.Create(4, "Block Skipping In Emergency Meetings", false);
- noVoteIsSelfVote = CustomOption.Create(5, "No Vote Is Self Vote", false, blockSkippingInEmergencyMeetings);
- hidePlayerNames = CustomOption.Create(6, "Hide Player Names", false);
-
- 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});
-
- }
- }
-
- public class CustomOption {
- public static List<CustomOption> options = new List<CustomOption>();
- public static int preset = 0;
-
- public int id;
- public string name;
- public System.Object[] selections;
-
- public int defaultSelection;
- public ConfigEntry<int> entry;
- public int selection;
- public OptionBehaviour optionBehaviour;
- public CustomOption parent;
- public bool isHeader;
-
- // Option creation
-
- public CustomOption(int id, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader) {
- this.id = id;
- this.name = parent == null ? name : "- " + name;
- this.selections = selections;
- int index = Array.IndexOf(selections, defaultValue);
- this.defaultSelection = index >= 0 ? index : 0;
- this.parent = parent;
- this.isHeader = isHeader;
- selection = 0;
- if (id != 0) {
- entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", id.ToString(), defaultSelection);
- selection = Mathf.Clamp(entry.Value, 0, selections.Length - 1);
- }
- options.Add(this);
- }
-
- public static CustomOption Create(int id, string name, string[] selections, CustomOption parent = null, bool isHeader = false) {
- return new CustomOption(id, name, selections, "", parent, isHeader);
- }
-
- public static CustomOption Create(int id, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false) {
- List<float> selections = new List<float>();
- for (float s = min; s <= max; s += step)
- selections.Add(s);
- return new CustomOption(id, name, selections.Cast<object>().ToArray(), defaultValue, parent, isHeader);
- }
-
- public static CustomOption Create(int id, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false) {
- return new CustomOption(id, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader);
- }
-
- // Static behaviour
-
- public static void switchPreset(int newPreset) {
- CustomOption.preset = newPreset;
- foreach (CustomOption option in CustomOption.options) {
- if (option.id == 0) continue;
-
- option.entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", option.id.ToString(), option.defaultSelection);
- option.selection = Mathf.Clamp(option.entry.Value, 0, option.selections.Length - 1);
- if (option.optionBehaviour != null && option.optionBehaviour is StringOption stringOption) {
- stringOption.oldValue = stringOption.Value = option.selection;
- stringOption.ValueText.text = option.selections[option.selection].ToString();
- }
- }
- }
-
- public static void ShareOptionSelections() {
- if (PlayerControl.AllPlayerControls.Count <= 1 || AmongUsClient.Instance?.AmHost == false && PlayerControl.LocalPlayer == null) return;
- foreach (CustomOption option in CustomOption.options) {
- MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShareOptionSelection, Hazel.SendOption.Reliable);
- messageWriter.WritePacked((uint)option.id);
- messageWriter.WritePacked((uint)Convert.ToUInt32(option.selection));
- messageWriter.EndMessage();
- }
- }
-
- // Getter
-
- public int getSelection() {
- return selection;
- }
-
- public bool getBool() {
- return selection > 0;
- }
-
- public float getFloat() {
- return (float)selections[selection];
- }
-
- // Option changes
-
- public void updateSelection(int newSelection) {
- selection = Mathf.Clamp((newSelection + selections.Length) % selections.Length, 0, selections.Length - 1);
- if (optionBehaviour != null && optionBehaviour is StringOption stringOption) {
- stringOption.oldValue = stringOption.Value = selection;
- stringOption.ValueText.text = selections[selection].ToString();
-
- if (AmongUsClient.Instance?.AmHost == true && PlayerControl.LocalPlayer) {
- if (id == 0) switchPreset(selection); // Switch presets
- else if (entry != null) entry.Value = selection; // Save selection to config
-
- ShareOptionSelections();// Share all selections
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))]
- class GameOptionsMenuStartPatch {
- public static void Postfix(GameOptionsMenu __instance) {
- var template = UnityEngine.Object.FindObjectsOfType<StringOption>().FirstOrDefault();
- if (template == null) return;
-
- List<OptionBehaviour> allOptions = __instance.Children.ToList();
- for (int i = 0; i < CustomOption.options.Count; i++) {
- CustomOption option = CustomOption.options[i];
- if (option.optionBehaviour == null) {
- StringOption stringOption = UnityEngine.Object.Instantiate(template, template.transform.parent);
- allOptions.Add(stringOption);
-
- stringOption.OnValueChanged = new Action<OptionBehaviour>((o) => {});
- stringOption.TitleText.text = option.name;
- stringOption.Value = stringOption.oldValue = option.selection;
- stringOption.ValueText.text = option.selections[option.selection].ToString();
-
- option.optionBehaviour = stringOption;
- }
- option.optionBehaviour.gameObject.SetActive(true);
- }
-
- var commonTasksOption = allOptions.FirstOrDefault(x => x.name == "NumCommonTasks").TryCast<NumberOption>();
- if(commonTasksOption != null) commonTasksOption.ValidRange = new FloatRange(0f, 4f);
-
- var shortTasksOption = allOptions.FirstOrDefault(x => x.name == "NumShortTasks").TryCast<NumberOption>();
- if(shortTasksOption != null) shortTasksOption.ValidRange = new FloatRange(0f, 23f);
-
- var longTasksOption = allOptions.FirstOrDefault(x => x.name == "NumLongTasks").TryCast<NumberOption>();
- if(longTasksOption != null) longTasksOption.ValidRange = new FloatRange(0f, 15f);
-
- __instance.Children = allOptions.ToArray();
- }
- }
-
- [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))]
- public class StringOptionEnablePatch {
- public static bool Prefix(StringOption __instance) {
- CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
- if (option == null) return true;
-
- __instance.OnValueChanged = new Action<OptionBehaviour>((o) => {});
- __instance.TitleText.text = option.name;
- __instance.Value = __instance.oldValue = option.selection;
- __instance.ValueText.text = option.selections[option.selection].ToString();
-
- return false;
- }
- }
-
- [HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))]
- public class StringOptionIncreasePatch
- {
- public static bool Prefix(StringOption __instance)
- {
- CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
- if (option == null) return true;
- option.updateSelection(option.selection + 1);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))]
- public class StringOptionDecreasePatch
- {
- public static bool Prefix(StringOption __instance)
- {
- CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
- if (option == null) return true;
- option.updateSelection(option.selection - 1);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))]
- public class RpcSyncSettingsPatch
- {
- public static void Postfix()
- {
- CustomOption.ShareOptionSelections();
- }
- }
-
-
- [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))]
- class GameOptionsMenuUpdatePatch
- {
- private static float timer = 1f;
- public static void Postfix(GameOptionsMenu __instance) {
- __instance.GetComponentInParent<Scroller>().YBounds.max = -0.5F + __instance.Children.Length * 0.55F;
- timer += Time.deltaTime;
- if (timer < 0.1f) return;
- timer = 0f;
-
- float offset = -7.85f;
- foreach (CustomOption option in CustomOption.options) {
- if (option?.optionBehaviour != null && option.optionBehaviour.gameObject != null) {
- bool enabled = true;
- var parent = option.parent;
- while (parent != null && enabled) {
- enabled = parent.selection != 0;
- parent = parent.parent;
- }
- option.optionBehaviour.gameObject.SetActive(enabled);
- if (enabled) {
- offset -= option.isHeader ? 0.75f : 0.5f;
- option.optionBehaviour.transform.localPosition = new Vector3(option.optionBehaviour.transform.localPosition.x, offset, option.optionBehaviour.transform.localPosition.z);
- }
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(GameSettingMenu), "OnEnable")]
- class GameSettingMenuPatch {
- public static void Prefix(GameSettingMenu __instance) {
- __instance.HideForOnline = new Transform[]{};
- }
-
- public static void Postfix(GameSettingMenu __instance) {
- var mapNameTransform = __instance.AllItems.FirstOrDefault(x => x.gameObject.activeSelf && x.name.Equals("MapName", StringComparison.OrdinalIgnoreCase));
- if (mapNameTransform == null) return;
-
- var options = new Il2CppSystem.Collections.Generic.List<Il2CppSystem.Collections.Generic.KeyValuePair<string, int>>();
- for (int i = 0; i < GameOptionsData.MapNames.Length; i++) {
- var kvp = new Il2CppSystem.Collections.Generic.KeyValuePair<string, int>();
- kvp.key = GameOptionsData.MapNames[i];
- kvp.value = i;
- options.Add(kvp);
- }
- mapNameTransform.GetComponent<KeyValueOption>().Values = options;
- }
- }
-
- [HarmonyPatch(typeof(Constants), nameof(Constants.ShouldFlipSkeld))]
- class ConstantsShouldFlipSkeldPatch {
- public static bool Prefix(ref bool __result) {
- if (PlayerControl.GameOptions == null) return true;
- __result = PlayerControl.GameOptions.MapId == 3;
- return false;
- }
- }
-
- [HarmonyPatch]
- class GameOptionsDataPatch
- {
- private static IEnumerable<MethodBase> TargetMethods() {
- return typeof(GameOptionsData).GetMethods().Where(x => x.ReturnType == typeof(string) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(int));
- }
-
- private static void Postfix(ref string __result)
- {
- StringBuilder sb = new StringBuilder(__result);
- foreach (CustomOption option in CustomOption.options) {
- if (option.parent == null) {
- if (option == CustomOptionHolder.crewmateRolesCountMin) {
- var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Crewmate Roles");
- var min = CustomOptionHolder.crewmateRolesCountMin.getSelection();
- var max = CustomOptionHolder.crewmateRolesCountMax.getSelection();
- if (min > max) min = max;
- var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
- sb.AppendLine($"{optionName}: {optionValue}");
- } else if (option == CustomOptionHolder.neutralRolesCountMin) {
- var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Neutral Roles");
- var min = CustomOptionHolder.neutralRolesCountMin.getSelection();
- var max = CustomOptionHolder.neutralRolesCountMax.getSelection();
- if (min > max) min = max;
- var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
- sb.AppendLine($"{optionName}: {optionValue}");
- } else if (option == CustomOptionHolder.impostorRolesCountMin) {
- var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Impostor Roles");
- var min = CustomOptionHolder.impostorRolesCountMin.getSelection();
- var max = CustomOptionHolder.impostorRolesCountMax.getSelection();
- if (min > max) min = max;
- var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
- sb.AppendLine($"{optionName}: {optionValue}");
- } else if ((option == CustomOptionHolder.crewmateRolesCountMax) || (option == CustomOptionHolder.neutralRolesCountMax) || (option == CustomOptionHolder.impostorRolesCountMax)) {
- continue;
- } else {
- sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}");
- }
-
- }
- }
- CustomOption parent = null;
- foreach (CustomOption option in CustomOption.options)
- if (option.parent != null) {
- if (option.parent != parent) {
- sb.AppendLine();
- parent = option.parent;
- }
- sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}");
- }
-
- var hudString = sb.ToString();
-
- int defaultSettingsLines = 19;
- 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();
- int end4 = hudString.TakeWhile(c => (detailedSettingsP2 -= (c == '\n' ? 1 : 0)) > 0).Count();
- int counter = TheOtherRolesPlugin.optionsPage;
- if (counter == 0) {
- hudString = hudString.Substring(0, end1) + "\n";
- } else if (counter == 1) {
- hudString = hudString.Substring(end1 + 1, end2 - end1);
- // Temporary fix, should add a new CustomOption for spaces
- int gap = 1;
- int index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
- hudString = hudString.Insert(index, "\n");
- gap = 5;
- index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
- hudString = hudString.Insert(index, "\n");
- gap = 18;
- index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
- hudString = hudString.Insert(index + 1, "\n");
- gap = 22;
- index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
- hudString = hudString.Insert(index + 1, "\n");
- } else if (counter == 2) {
- hudString = hudString.Substring(end2 + 1, end3 - end2);
- } else if (counter == 3) {
- hudString = hudString.Substring(end3 + 1, end4 - end3);
- } else if (counter == 4) {
- hudString = hudString.Substring(end4 + 1);
- }
-
- hudString += $"\n Press tab for more... ({counter+1}/5)";
- __result = hudString;
- }
- }
-
- [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))]
- public static class GameOptionsNextPagePatch
- {
- public static void Postfix(KeyboardJoystick __instance)
- {
- if(Input.GetKeyDown(KeyCode.Tab)) {
- TheOtherRolesPlugin.optionsPage = (TheOtherRolesPlugin.optionsPage + 1) % 5;
- }
- }
- }
-
-
- [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
- public class GameSettingsScalePatch {
- public static void Prefix(HudManager __instance) {
- if (__instance.GameSettings != null) __instance.GameSettings.fontSize = 1.2f;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-
-using HarmonyLib;
-using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.GameHistory;
-using System.Collections;
-using System.Collections.Generic;
-using UnityEngine;
-using System.Linq;
-using Hazel;
-using UnhollowerBaseLib;
-using System;
-using System.Text;
-
-namespace TheOtherRoles {
- enum CustomGameOverReason {
- LoversWin = 10,
- TeamJackalWin = 11,
- MiniLose = 12,
- JesterWin = 13,
- ArsonistWin = 14
- }
-
- enum WinCondition {
- Default,
- LoversTeamWin,
- LoversSoloWin,
- JesterWin,
- JackalWin,
- MiniLose,
- ArsonistWin
- }
-
- static class AdditionalTempData {
- // Should be implemented using a proper GameOverReason in the future
- public static WinCondition winCondition = WinCondition.Default;
- public static List<PlayerRoleInfo> playerRoles = new List<PlayerRoleInfo>();
-
- public static void clear() {
- playerRoles.Clear();
- winCondition = WinCondition.Default;
- }
-
- internal class PlayerRoleInfo {
- public string PlayerName { get; set; }
- public List<RoleInfo> Roles {get;set;}
- public int TasksCompleted {get;set;}
- public int TasksTotal {get;set;}
- }
- }
-
-
- [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
- public class OnGameEndPatch {
- private static GameOverReason gameOverReason;
- public static void Prefix(AmongUsClient __instance, [HarmonyArgument(0)]ref GameOverReason reason, [HarmonyArgument(1)]bool showAd) {
- gameOverReason = reason;
- if ((int)reason >= 10) reason = GameOverReason.ImpostorByKill;
- }
-
- public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref GameOverReason reason, [HarmonyArgument(1)]bool showAd) {
- AdditionalTempData.clear();
-
- foreach(var playerControl in PlayerControl.AllPlayerControls) {
- var roles = RoleInfo.getRoleInfoForPlayer(playerControl);
- var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(playerControl.Data);
- AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted });
- }
-
- // Remove Jester, Arsonist, Jackal, former Jackals and Sidekick from winners (if they win, they'll be readded)
- List<PlayerControl> notWinners = new List<PlayerControl>();
- if (Jester.jester != null) notWinners.Add(Jester.jester);
- if (Sidekick.sidekick != null) notWinners.Add(Sidekick.sidekick);
- if (Jackal.jackal != null) notWinners.Add(Jackal.jackal);
- if (Arsonist.arsonist != null) notWinners.Add(Arsonist.arsonist);
- notWinners.AddRange(Jackal.formerJackals);
-
- List<WinningPlayerData> winnersToRemove = new List<WinningPlayerData>();
- foreach (WinningPlayerData winner in TempData.winners) {
- if (notWinners.Any(x => x.Data.PlayerName == winner.Name)) winnersToRemove.Add(winner);
- }
- foreach (var winner in winnersToRemove) TempData.winners.Remove(winner);
-
- bool jesterWin = Jester.jester != null && gameOverReason == (GameOverReason)CustomGameOverReason.JesterWin;
- bool arsonistWin = Arsonist.arsonist != null && gameOverReason == (GameOverReason)CustomGameOverReason.ArsonistWin;
- bool miniLose = Mini.mini != null && gameOverReason == (GameOverReason)CustomGameOverReason.MiniLose;
- bool loversWin = Lovers.existingAndAlive() && (gameOverReason == (GameOverReason)CustomGameOverReason.LoversWin || (TempData.DidHumansWin(gameOverReason) && !Lovers.existingWithKiller())); // Either they win if they are among the last 3 players, or they win if they are both Crewmates and both alive and the Crew wins (Team Imp/Jackal Lovers can only win solo wins)
- bool teamJackalWin = gameOverReason == (GameOverReason)CustomGameOverReason.TeamJackalWin && ((Jackal.jackal != null && !Jackal.jackal.Data.IsDead) || (Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead));
-
- // Mini lose
- if (miniLose) {
- TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- WinningPlayerData wpd = new WinningPlayerData(Mini.mini.Data);
- wpd.IsYou = false; // If "no one is the Mini", it will display the Mini, but also show defeat to everyone
- TempData.winners.Add(wpd);
- AdditionalTempData.winCondition = WinCondition.MiniLose;
- }
-
- // Jester win
- else if (jesterWin) {
- TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- WinningPlayerData wpd = new WinningPlayerData(Jester.jester.Data);
- TempData.winners.Add(wpd);
- AdditionalTempData.winCondition = WinCondition.JesterWin;
- }
-
- // Arsonist win
- else if (arsonistWin) {
- TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- WinningPlayerData wpd = new WinningPlayerData(Arsonist.arsonist.Data);
- TempData.winners.Add(wpd);
- AdditionalTempData.winCondition = WinCondition.ArsonistWin;
- }
-
- // Lovers win conditions
- else if (loversWin) {
- // Double win for lovers, crewmates also win
- if (!Lovers.existingWithKiller()) {
- AdditionalTempData.winCondition = WinCondition.LoversTeamWin;
- TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (p == null) continue;
- if (p == Lovers.lover1 || p == Lovers.lover2)
- TempData.winners.Add(new WinningPlayerData(p.Data));
- else if (p != Jester.jester && p != Jackal.jackal && p != Sidekick.sidekick && p != Arsonist.arsonist && !Jackal.formerJackals.Contains(p) && !p.Data.IsImpostor)
- TempData.winners.Add(new WinningPlayerData(p.Data));
- }
- }
- // Lovers solo win
- else {
- AdditionalTempData.winCondition = WinCondition.LoversSoloWin;
- TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- TempData.winners.Add(new WinningPlayerData(Lovers.lover1.Data));
- TempData.winners.Add(new WinningPlayerData(Lovers.lover2.Data));
- }
- }
-
- // Jackal win condition (should be implemented using a proper GameOverReason in the future)
- else if (teamJackalWin) {
- // Jackal wins if nobody except jackal is alive
- AdditionalTempData.winCondition = WinCondition.JackalWin;
- TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
- WinningPlayerData wpd = new WinningPlayerData(Jackal.jackal.Data);
- wpd.IsImpostor = false;
- TempData.winners.Add(wpd);
- // If there is a sidekick. The sidekick also wins
- if (Sidekick.sidekick != null) {
- WinningPlayerData wpdSidekick = new WinningPlayerData(Sidekick.sidekick.Data);
- wpdSidekick.IsImpostor = false;
- TempData.winners.Add(wpdSidekick);
- }
- foreach(var player in Jackal.formerJackals) {
- WinningPlayerData wpdFormerJackal = new WinningPlayerData(player.Data);
- wpdFormerJackal.IsImpostor = false;
- TempData.winners.Add(wpdFormerJackal);
- }
- }
-
- // Reset Settings
- RPCProcedure.resetVariables();
- }
- }
-
- [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
- public class EndGameManagerSetUpPatch {
- public static void Postfix(EndGameManager __instance) {
- GameObject bonusText = UnityEngine.Object.Instantiate(__instance.WinText.gameObject);
- bonusText.transform.position = new Vector3(__instance.WinText.transform.position.x, __instance.WinText.transform.position.y - 0.8f, __instance.WinText.transform.position.z);
- bonusText.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
- TMPro.TMP_Text textRenderer = bonusText.GetComponent<TMPro.TMP_Text>();
- textRenderer.text = "";
-
- if (AdditionalTempData.winCondition == WinCondition.JesterWin) {
- textRenderer.text = "Jester Wins";
- textRenderer.color = Jester.color;
- }
- else if (AdditionalTempData.winCondition == WinCondition.ArsonistWin) {
- textRenderer.text = "Arsonist Wins";
- textRenderer.color = Arsonist.color;
- }
- else if (AdditionalTempData.winCondition == WinCondition.LoversTeamWin) {
- textRenderer.text = "Lovers And Crewmates Win";
- textRenderer.color = Lovers.color;
- __instance.BackgroundBar.material.SetColor("_Color", Lovers.color);
- }
- else if (AdditionalTempData.winCondition == WinCondition.LoversSoloWin) {
- textRenderer.text = "Lovers Win";
- textRenderer.color = Lovers.color;
- __instance.BackgroundBar.material.SetColor("_Color", Lovers.color);
- }
- else if (AdditionalTempData.winCondition == WinCondition.JackalWin) {
- textRenderer.text = "Team Jackal Wins";
- textRenderer.color = Jackal.color;
- }
- else if (AdditionalTempData.winCondition == WinCondition.MiniLose) {
- textRenderer.text = "Mini died";
- textRenderer.color = Mini.color;
- }
-
- 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);
- roleSummary.transform.localScale = new Vector3(1f, 1f, 1f);
-
- var roleSummaryText = new StringBuilder();
- roleSummaryText.AppendLine("Players and roles at the end of the game:");
- foreach(var data in AdditionalTempData.playerRoles) {
- var roles = string.Join(" ", data.Roles.Select(x => Helpers.cs(x.color, x.name)));
- var taskInfo = data.TasksTotal > 0 ? $" - <color=#FAD934FF>({data.TasksCompleted}/{data.TasksTotal})</color>" : "";
- roleSummaryText.AppendLine($"{data.PlayerName} - {roles}{taskInfo}");
- }
- TMPro.TMP_Text roleSummaryTextMesh = roleSummary.GetComponent<TMPro.TMP_Text>();
- roleSummaryTextMesh.alignment = TMPro.TextAlignmentOptions.TopLeft;
- roleSummaryTextMesh.color = Color.white;
- roleSummaryTextMesh.fontSizeMin = 1.5f;
- roleSummaryTextMesh.fontSizeMax = 1.5f;
- roleSummaryTextMesh.fontSize = 1.5f;
-
- var roleSummaryTextMeshRectTransform = roleSummaryTextMesh.GetComponent<RectTransform>();
- roleSummaryTextMeshRectTransform.anchoredPosition = new Vector2(position.x + 3.5f, position.y - 0.1f);
- roleSummaryTextMesh.text = roleSummaryText.ToString();
- }
- AdditionalTempData.clear();
- }
- }
-
- [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))]
- class CheckEndCriteriaPatch {
- public static bool Prefix(ShipStatus __instance) {
- if (!GameData.Instance) return false;
- if (DestroyableSingleton<TutorialManager>.InstanceExists) // InstanceExists | Don't check Custom Criteria when in Tutorial
- return true;
- var statistics = new PlayerStatistics(__instance);
- if (CheckAndEndGameForMiniLose(__instance)) return false;
- if (CheckAndEndGameForJesterWin(__instance)) return false;
- if (CheckAndEndGameForArsonistWin(__instance)) return false;
- if (CheckAndEndGameForSabotageWin(__instance)) return false;
- if (CheckAndEndGameForTaskWin(__instance)) return false;
- if (CheckAndEndGameForLoverWin(__instance, statistics)) return false;
- if (CheckAndEndGameForJackalWin(__instance, statistics)) return false;
- if (CheckAndEndGameForImpostorWin(__instance, statistics)) return false;
- if (CheckAndEndGameForCrewmateWin(__instance, statistics)) return false;
- return false;
- }
-
- private static bool CheckAndEndGameForMiniLose(ShipStatus __instance) {
- if (Mini.triggerMiniLose) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.MiniLose, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForJesterWin(ShipStatus __instance) {
- if (Jester.triggerJesterWin) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.JesterWin, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForArsonistWin(ShipStatus __instance) {
- if (Arsonist.triggerArsonistWin) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.ArsonistWin, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) {
- if (__instance.Systems == null) return false;
- ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.LifeSupp) ? __instance.Systems[SystemTypes.LifeSupp] : null;
- if (systemType != null) {
- LifeSuppSystemType lifeSuppSystemType = systemType.TryCast<LifeSuppSystemType>();
- if (lifeSuppSystemType != null && lifeSuppSystemType.Countdown < 0f) {
- EndGameForSabotage(__instance);
- lifeSuppSystemType.Countdown = 10000f;
- return true;
- }
- }
- ISystemType systemType2 = __instance.Systems.ContainsKey(SystemTypes.Reactor) ? __instance.Systems[SystemTypes.Reactor] : null;
- if (systemType2 == null) {
- systemType2 = __instance.Systems.ContainsKey(SystemTypes.Laboratory) ? __instance.Systems[SystemTypes.Laboratory] : null;
- }
- if (systemType2 != null) {
- ICriticalSabotage criticalSystem = systemType2.TryCast<ICriticalSabotage>();
- if (criticalSystem != null && criticalSystem.Countdown < 0f) {
- EndGameForSabotage(__instance);
- criticalSystem.ClearSabotage();
- return true;
- }
- }
- return false;
- }
-
- private static bool CheckAndEndGameForTaskWin(ShipStatus __instance) {
- if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForLoverWin(ShipStatus __instance, PlayerStatistics statistics) {
- if (statistics.TeamLoversAlive == 2 && statistics.TotalAlive <= 3) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.LoversWin, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForJackalWin(ShipStatus __instance, PlayerStatistics statistics) {
- if (statistics.TeamJackalAlive >= statistics.TotalAlive - statistics.TeamJackalAlive && statistics.TeamImpostorsAlive == 0 && !(statistics.TeamJackalHasAliveLover && statistics.TeamLoversAlive == 2)) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.TeamJackalWin, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerStatistics statistics) {
- if (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && statistics.TeamJackalAlive == 0 && !(statistics.TeamImpostorHasAliveLover && statistics.TeamLoversAlive == 2)) {
- __instance.enabled = false;
- GameOverReason endReason;
- switch (TempData.LastDeathReason) {
- case DeathReason.Exile:
- endReason = GameOverReason.ImpostorByVote;
- break;
- case DeathReason.Kill:
- endReason = GameOverReason.ImpostorByKill;
- break;
- default:
- endReason = GameOverReason.ImpostorByVote;
- break;
- }
- ShipStatus.RpcEndGame(endReason, false);
- return true;
- }
- return false;
- }
-
- private static bool CheckAndEndGameForCrewmateWin(ShipStatus __instance, PlayerStatistics statistics) {
- if (statistics.TeamImpostorsAlive == 0 && statistics.TeamJackalAlive == 0) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false);
- return true;
- }
- return false;
- }
-
- private static void EndGameForSabotage(ShipStatus __instance) {
- __instance.enabled = false;
- ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, false);
- return;
- }
-
- }
-
- internal class PlayerStatistics {
- public int TeamImpostorsAlive {get;set;}
- public int TeamJackalAlive {get;set;}
- public int TeamLoversAlive {get;set;}
- public int TotalAlive {get;set;}
- public bool TeamImpostorHasAliveLover {get;set;}
- public bool TeamJackalHasAliveLover {get;set;}
-
- public PlayerStatistics(ShipStatus __instance) {
- GetPlayerCounts();
- }
-
- private bool isLover(GameData.PlayerInfo p) {
- return (Lovers.lover1 != null && Lovers.lover1.PlayerId == p.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == p.PlayerId);
- }
-
- private void GetPlayerCounts() {
- int numJackalAlive = 0;
- int numImpostorsAlive = 0;
- int numLoversAlive = 0;
- int numTotalAlive = 0;
- bool impLover = false;
- bool jackalLover = false;
-
- for (int i = 0; i < GameData.Instance.PlayerCount; i++)
- {
- GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
- if (!playerInfo.Disconnected)
- {
- if (!playerInfo.IsDead)
- {
- numTotalAlive++;
-
- bool lover = isLover(playerInfo);
- if (lover) numLoversAlive++;
-
- if (playerInfo.IsImpostor) {
- numImpostorsAlive++;
- if (lover) impLover = true;
- }
- if (Jackal.jackal != null && Jackal.jackal.PlayerId == playerInfo.PlayerId) {
- numJackalAlive++;
- if (lover) jackalLover = true;
- }
- if (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == playerInfo.PlayerId) {
- numJackalAlive++;
- if (lover) jackalLover = true;
- }
- }
- }
- }
-
- TeamJackalAlive = numJackalAlive;
- TeamImpostorsAlive = numImpostorsAlive;
- TeamLoversAlive = numLoversAlive;
- TotalAlive = numTotalAlive;
- TeamImpostorHasAliveLover = impLover;
- TeamJackalHasAliveLover = jackalLover;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using System.Collections.Generic;
-using System.Linq;
-using UnhollowerBaseLib;
-using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.MapOptions;
-using System.Collections;
-using System;
-using System.Text;
-using UnityEngine;
-using System.Reflection;
-
-namespace TheOtherRoles {
- [HarmonyPatch(typeof(ExileController), "Begin")]
- class ExileControllerBeginPatch {
- public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref GameData.PlayerInfo exiled, [HarmonyArgument(1)]bool tie) {
- // Shifter shift
- if (Shifter.shifter != null && AmongUsClient.Instance.AmHost && Shifter.futureShift != 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)
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShifterShift, Hazel.SendOption.Reliable, -1);
- writer.Write(Shifter.futureShift.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.shifterShift(Shifter.futureShift.PlayerId);
- }
- Shifter.futureShift = null;
-
- // 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 && 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);
- RPCProcedure.erasePlayerRoles(target.PlayerId);
- }
- }
- }
- Eraser.futureErased = new List<PlayerControl>();
-
- // Trickster boxes
- if (Trickster.trickster != null && JackInTheBox.hasJackInTheBoxLimitReached()) {
- JackInTheBox.convertToVents();
- }
-
- // SecurityGuard vents and cameras
- var allCameras = ShipStatus.Instance.AllCameras.ToList();
- MapOptions.camerasToAdd.ForEach(camera => {
- camera.gameObject.SetActive(true);
- camera.gameObject.GetComponent<SpriteRenderer>().color = Color.white;
- allCameras.Add(camera);
- });
- ShipStatus.Instance.AllCameras = allCameras.ToArray();
- MapOptions.camerasToAdd = new List<SurvCamera>();
-
- foreach (Vent vent in MapOptions.ventsToSeal) {
- PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
- animator?.Stop();
- vent.EnterVentAnim = vent.ExitVentAnim = null;
- vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
- vent.myRend.color = Color.white;
- vent.name = "SealedVent_" + vent.name;
- }
- MapOptions.ventsToSeal = new List<Vent>();
- }
- }
-
- [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;
- }
- // Jester win condition
- else if (exiled != null && Jester.jester != null && Jester.jester.PlayerId == exiled.PlayerId) {
- Jester.triggerJesterWin = true;
- }
-
- // Reset custom button timers where necessary
- CustomButton.MeetingEndedUpdate();
-
- // Mini set adapted cooldown
- if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini && Mini.mini.Data.IsImpostor) {
- var multiplier = Mini.isGrownUp() ? 0.66f : 2f;
- Mini.mini.SetKillTimer(PlayerControl.GameOptions.KillCooldown * multiplier);
- }
-
- // Seer spawn souls
- if (Seer.deadBodyPositions != null && Seer.seer != null && PlayerControl.LocalPlayer == Seer.seer && (Seer.mode == 0 || Seer.mode == 2)) {
- foreach (Vector3 pos in Seer.deadBodyPositions) {
- GameObject soul = new GameObject();
- soul.transform.position = pos;
- soul.layer = 5;
- var rend = soul.AddComponent<SpriteRenderer>();
- rend.sprite = Seer.getSoulSprite();
-
- if(Seer.limitSoulDuration) {
- HudManager.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action<float>((p) => {
- if (rend != null) {
- var tmp = rend.color;
- tmp.a = Mathf.Clamp01(1 - p);
- rend.color = tmp;
- }
- if (p == 1f && rend != null && rend.gameObject != null) UnityEngine.Object.Destroy(rend.gameObject);
- })));
- }
- }
- Seer.deadBodyPositions = new List<Vector3>();
- }
-
- // Arsonist deactivate dead poolable players
- if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) {
- int visibleCounter = 0;
- Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z);
- bottomLeft += new Vector3(-0.25f, -0.25f, 0);
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (!MapOptions.playerIcons.ContainsKey(p.PlayerId)) continue;
- if (p.Data.IsDead || p.Data.Disconnected) {
- MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
- } else {
- 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;
- }
- }
-
- [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), new Type[] { typeof(StringNames), typeof(Il2CppReferenceArray<Il2CppSystem.Object>) })]
- class ExileControllerMessagePatch {
- static void Postfix(ref string __result, [HarmonyArgument(0)]StringNames id, [HarmonyArgument(1)]Il2CppReferenceArray<Il2CppSystem.Object> parts) {
- if (ExileController.Instance != null && ExileController.Instance.exiled != null) {
- PlayerControl player = Helpers.playerById(ExileController.Instance.exiled.Object.PlayerId);
- if (player == null) return;
- // Exile role text
- if (id == StringNames.ExileTextPN || id == StringNames.ExileTextSN || id == StringNames.ExileTextPP || id == StringNames.ExileTextSP) {
- __result = player.Data.PlayerName + " was The " + String.Join(" ", RoleInfo.getRoleInfoForPlayer(player).Select(x => x.name).ToArray());
- }
- // Hide number of remaining impostors on Jester win
- if (id == StringNames.ImpostorsRemainP || id == StringNames.ImpostorsRemainS) {
- if (Jester.jester != null && player.PlayerId == Jester.jester.PlayerId) __result = "";
- }
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Collections;
-using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
-
-namespace TheOtherRoles{
- class Footprint {
- private static List<Footprint> footprints = new List<Footprint>();
- private static Sprite sprite;
- private Color color;
- private GameObject footprint;
- private SpriteRenderer spriteRenderer;
- private PlayerControl owner;
- private bool anonymousFootprints;
-
- public static Sprite getFootprintSprite() {
- if (sprite) return sprite;
- sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Footprint.png", 600f);
- return sprite;
- }
-
- public Footprint(float footprintDuration, bool anonymousFootprints, PlayerControl player) {
- this.owner = player;
- this.anonymousFootprints = anonymousFootprints;
- if (anonymousFootprints)
- this.color = Palette.PlayerColors[6];
- else
- this.color = Palette.PlayerColors[(int) player.Data.ColorId];
-
- footprint = new GameObject("Footprint");
- Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.z + 1f);
- footprint.transform.position = position;
- footprint.transform.localPosition = position;
- footprint.transform.SetParent(player.transform.parent);
-
- footprint.transform.Rotate(0.0f, 0.0f, UnityEngine.Random.Range(0.0f, 360.0f));
-
-
- spriteRenderer = footprint.AddComponent<SpriteRenderer>();
- spriteRenderer.sprite = getFootprintSprite();
- spriteRenderer.color = color;
-
- footprint.SetActive(true);
- footprints.Add(this);
-
- HudManager.Instance.StartCoroutine(Effects.Lerp(footprintDuration, new Action<float>((p) => {
- Color c = color;
- if (!anonymousFootprints && owner != null) {
- if (owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget?.Data != null)
- c = Palette.ShadowColors[Morphling.morphTarget.Data.ColorId];
- else if (Camouflager.camouflageTimer > 0)
- c = Palette.PlayerColors[6];
- }
-
- if (spriteRenderer) spriteRenderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01(1 - p));
-
- if (p == 1f && footprint != null) {
- UnityEngine.Object.Destroy(footprint);
- footprints.Remove(this);
- }
- })));
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using System.Collections.Generic;
-using System.Collections;
-using System;
-using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
-
-namespace TheOtherRoles{
- public class DeadPlayer
- {
- public PlayerControl player;
- public DateTime timeOfDeath;
- public DeathReason deathReason;
- public PlayerControl killerIfExisting;
-
- public DeadPlayer(PlayerControl player, DateTime timeOfDeath, DeathReason deathReason, PlayerControl killerIfExisting) {
- this.player = player;
- this.timeOfDeath = timeOfDeath;
- this.deathReason = deathReason;
- this.killerIfExisting = killerIfExisting;
- }
- }
-
- static class GameHistory {
- public static List<Tuple<Vector3, bool>> localPlayerPositions = new List<Tuple<Vector3, bool>>();
- public static List<DeadPlayer> deadPlayers = new List<DeadPlayer>();
-
- public static void clearGameHistory() {
- localPlayerPositions = new List<Tuple<Vector3, bool>>();
- deadPlayers = new List<DeadPlayer>();
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-
-using HarmonyLib;
-using UnityEngine;
-using System.Reflection;
-using System.Collections.Generic;
-using Hazel;
-using System;
-using UnhollowerBaseLib;
-
-namespace TheOtherRoles {
- public class GameStartManagerPatch {
- public static Dictionary<int, PlayerVersion> playerVersions = new Dictionary<int, PlayerVersion>();
- private static float timer = 600f;
- private static bool versionSent = false;
- private static string lobbyCodeText = "";
-
- [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))]
- public class GameStartManagerStartPatch {
- public static void Postfix(GameStartManager __instance) {
- // Trigger version refresh
- versionSent = false;
- // Reset lobby countdown timer
- timer = 600f;
- // Copy lobby code
- string code = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId);
- GUIUtility.systemCopyBuffer = code;
- lobbyCodeText = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.RoomCode, new Il2CppReferenceArray<Il2CppSystem.Object>(0)) + "\r\n" + code;
- }
- }
-
- [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
- public class GameStartManagerUpdatePatch {
- private static bool update = false;
- private static string currentText = "";
- private static int kc = 0;
- private static KeyCode[] ks = new [] { KeyCode.UpArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.B, KeyCode.A, KeyCode.Return };
-
- public static void Prefix(GameStartManager __instance) {
- if (!AmongUsClient.Instance.AmHost || !GameData.Instance) return; // Not host or no instance
- update = GameData.Instance.PlayerCount != __instance.LastPlayerCount;
- }
-
- public static void Postfix(GameStartManager __instance) {
- // Send version as soon as PlayerControl.LocalPlayer exists
- if (PlayerControl.LocalPlayer != null && !versionSent) {
- versionSent = true;
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionHandshake, Hazel.SendOption.Reliable, -1);
- writer.Write((byte)TheOtherRolesPlugin.Version.Major);
- writer.Write((byte)TheOtherRolesPlugin.Version.Minor);
- writer.Write((byte)TheOtherRolesPlugin.Version.Build);
- writer.WritePacked(AmongUsClient.Instance.ClientId);
- writer.Write((byte)(TheOtherRolesPlugin.Version.Revision < 0 ? 0xFF : TheOtherRolesPlugin.Version.Revision));
- writer.Write(Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.ToByteArray());
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.versionHandshake(TheOtherRolesPlugin.Version.Major, TheOtherRolesPlugin.Version.Minor, TheOtherRolesPlugin.Version.Build, TheOtherRolesPlugin.Version.Revision, Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId, AmongUsClient.Instance.ClientId);
- }
-
- if(kc < ks.Length && Input.GetKeyDown(ks[kc])) {
- kc++;
- } else if(Input.anyKeyDown) {
- kc = 0;
- }
-
- if(kc == ks.Length) {
- kc = 0;
-
- // Random Color
- byte colorId = (byte)TheOtherRoles.rnd.Next(0, Palette.PlayerColors.Length);
- SaveManager.BodyColor = (byte)colorId;
- if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.CmdCheckColor(colorId);
-
- // Random Hat
- var hats = HatManager.Instance.GetUnlockedHats();
- var unlockedHatIndex = TheOtherRoles.rnd.Next(0, hats.Length);
- var hatId = (uint)HatManager.Instance.AllHats.IndexOf(hats[unlockedHatIndex]);
- if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.RpcSetHat(hatId);
-
- // Random Skin
- var skins = HatManager.Instance.GetUnlockedSkins();
- var unlockedSkinIndex = TheOtherRoles.rnd.Next(0, skins.Length);
- var skinId = (uint)HatManager.Instance.AllSkins.IndexOf(skins[unlockedSkinIndex]);
- if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.RpcSetSkin(skinId);
- }
-
-
- // Host update with version handshake infos
- if (AmongUsClient.Instance.AmHost) {
- bool blockStart = false;
- string message = "";
- foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients.ToArray()) {
- if (client.Character == null) continue;
- var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
- if (dummyComponent != null && dummyComponent.enabled)
- continue;
- else if (!playerVersions.ContainsKey(client.Id)) {
- blockStart = true;
- message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a different or no version of The Other Roles\n</color>";
- } else {
- PlayerVersion PV = playerVersions[client.Id];
- int diff = TheOtherRolesPlugin.Version.CompareTo(PV.version);
- if (diff > 0) {
- message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has an older version of The Other Roles (v{playerVersions[client.Id].version.ToString()})\n</color>";
- blockStart = true;
- } else if (diff < 0) {
- message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a newer version of The Other Roles (v{playerVersions[client.Id].version.ToString()})\n</color>";
- blockStart = true;
- } else if (!PV.GuidMatches()) { // version presumably matches, check if Guid matches
- message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a modified version of TOR v{playerVersions[client.Id].version.ToString()} <size=30%>({PV.guid.ToString()})</size>\n</color>";
- blockStart = true;
- }
- }
- }
- if (blockStart) {
- // __instance.StartButton.color = Palette.DisabledClear; // Allow the start for this version to test the feature, blocking it with the next version
- __instance.GameStartText.text = message;
- __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2;
- } else {
- // __instance.StartButton.color = ((__instance.LastPlayerCount >= __instance.MinPlayers) ? Palette.EnabledColor : Palette.DisabledClear); // Allow the start for this version to test the feature, blocking it with the next version
- __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition;
- }
- }
-
- // Lobby code replacement
- __instance.GameRoomName.text = TheOtherRolesPlugin.StreamerMode.Value ? $"<color={TheOtherRolesPlugin.StreamerModeReplacementColor.Value}>{TheOtherRolesPlugin.StreamerModeReplacementText.Value}</color>" : lobbyCodeText;
-
- // Lobby timer
- if (!AmongUsClient.Instance.AmHost || !GameData.Instance) return; // Not host or no instance
-
- if (update) currentText = __instance.PlayerCounter.text;
-
- timer = Mathf.Max(0f, timer -= Time.deltaTime);
- int minutes = (int)timer / 60;
- int seconds = (int)timer % 60;
- string suffix = $" ({minutes:00}:{seconds:00})";
-
- __instance.PlayerCounter.text = currentText + suffix;
- __instance.PlayerCounter.autoSizeTextContainer = true;
-
- }
- }
-
- [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.BeginGame))]
- public class GameStartManagerBeginGame {
- public static bool Prefix(GameStartManager __instance) {
- // Block game start if not everyone has the same mod version
- bool continueStart = true;
-
- // Allow the start for this version to test the feature, blocking it with the next version
- // if (AmongUsClient.Instance.AmHost) {
- // foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) {
- // if (client.Character == null) continue;
- // var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
- // if (dummyComponent != null && dummyComponent.enabled) continue;
- // if (!playerVersions.ContainsKey(client.Id) || (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch))
- // continueStart = false;
- // }
- // }
- return continueStart;
- }
- }
-
- public class PlayerVersion {
- public readonly Version version;
- public readonly Guid guid;
-
- public PlayerVersion(Version version, Guid guid) {
- this.version = version;
- this.guid = guid;
- }
-
- public bool GuidMatches() {
- return Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.Equals(this.guid);
- }
- }
- }
-}
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Collections;
-using UnityEngine;
-
-namespace TheOtherRoles{
- class Garlic {
- public static List<Garlic> garlics = new List<Garlic>();
-
- public GameObject garlic;
- private GameObject background;
-
- private static Sprite garlicSprite;
- public static Sprite getGarlicSprite() {
- if (garlicSprite) return garlicSprite;
- garlicSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Garlic.png", 300f);
- return garlicSprite;
- }
-
- private static Sprite backgroundSprite;
- public static Sprite getBackgroundSprite() {
- if (backgroundSprite) return backgroundSprite;
- backgroundSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.GarlicBackground.png", 60f);
- return backgroundSprite;
- }
-
- public Garlic(Vector2 p) {
- garlic = new GameObject("Garlic");
- background = new GameObject("Background");
- background.transform.SetParent(garlic.transform);
- Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player
- garlic.transform.position = position;
- garlic.transform.localPosition = position;
- background.transform.localPosition = new Vector3(0 , 0, -0.01f); // before player
-
- var garlicRenderer = garlic.AddComponent<SpriteRenderer>();
- garlicRenderer.sprite = getGarlicSprite();
- var backgroundRenderer = background.AddComponent<SpriteRenderer>();
- backgroundRenderer.sprite = getBackgroundSprite();
-
-
- garlic.SetActive(true);
- garlics.Add(this);
- }
-
- public static void clearGarlics() {
- garlics = new List<Garlic>();
- }
-
- public static void UpdateAll() {
- foreach (Garlic garlic in garlics) {
- if (garlic != null)
- garlic.Update();
- }
- }
-
- public void Update() {
- if (background != null)
- background.transform.Rotate(Vector3.forward * 6 * Time.fixedDeltaTime);
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Collections;
-using UnhollowerBaseLib;
-using UnityEngine;
-using System.Linq;
-using static TheOtherRoles.TheOtherRoles;
-using HarmonyLib;
-using Hazel;
-
-namespace TheOtherRoles {
- public static class Helpers {
-
- public static Sprite loadSpriteFromResources(string path, float pixelsPerUnit) {
- try {
- Texture2D texture = loadTextureFromResources(path);
- return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
- } catch {
- System.Console.WriteLine("Error loading sprite from path: " + path);
- }
- return null;
- }
-
- public static Texture2D loadTextureFromResources(string path) {
- try {
- Texture2D texture = new Texture2D(2, 2, TextureFormat.ARGB32, true);
- Assembly assembly = Assembly.GetExecutingAssembly();
- Stream stream = assembly.GetManifestResourceStream(path);
- var byteTexture = new byte[stream.Length];
- var read = stream.Read(byteTexture, 0, (int) stream.Length);
- LoadImage(texture, byteTexture, false);
- return texture;
- } catch {
- System.Console.WriteLine("Error loading texture from resources: " + path);
- }
- return null;
- }
-
- public static Texture2D loadTextureFromDisk(string path) {
- try {
- if (File.Exists(path)) {
- Texture2D texture = new Texture2D(2, 2, TextureFormat.ARGB32, true);
- byte[] byteTexture = File.ReadAllBytes(path);
- LoadImage(texture, byteTexture, false);
- return texture;
- }
- } catch {
- System.Console.WriteLine("Error loading texture from disk: " + path);
- }
- return null;
- }
-
- internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
- internal static d_LoadImage iCall_LoadImage;
- private static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable) {
- if (iCall_LoadImage == null)
- iCall_LoadImage = IL2CPP.ResolveICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
- var il2cppArray = (Il2CppStructArray<byte>) data;
- return iCall_LoadImage.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
- }
-
- public static PlayerControl playerById(byte id)
- {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- if (player.PlayerId == id)
- return player;
- return null;
- }
-
- public static Dictionary<byte, PlayerControl> allPlayersById()
- {
- Dictionary<byte, PlayerControl> res = new Dictionary<byte, PlayerControl>();
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- res.Add(player.PlayerId, player);
- return res;
- }
-
- public static void setSkinWithAnim(PlayerPhysics playerPhysics, uint SkinId) {
- SkinData nextSkin = DestroyableSingleton<HatManager>.Instance.AllSkins[(int)SkinId];
- AnimationClip clip = null;
- var spriteAnim = playerPhysics.Skin.animator;
- var anim = spriteAnim.m_animator;
- var skinLayer = playerPhysics.Skin;
-
- var currentPhysicsAnim = playerPhysics.Animator.GetCurrentAnimation();
- if (currentPhysicsAnim == playerPhysics.RunAnim) clip = nextSkin.RunAnim;
- else if (currentPhysicsAnim == playerPhysics.SpawnAnim) clip = nextSkin.SpawnAnim;
- else if (currentPhysicsAnim == playerPhysics.EnterVentAnim) clip = nextSkin.EnterVentAnim;
- else if (currentPhysicsAnim == playerPhysics.ExitVentAnim) clip = nextSkin.ExitVentAnim;
- else if (currentPhysicsAnim == playerPhysics.IdleAnim) clip = nextSkin.IdleAnim;
- else clip = nextSkin.IdleAnim;
-
- float progress = playerPhysics.Animator.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
- skinLayer.skin = nextSkin;
-
- spriteAnim.Play(clip, 1f);
- anim.Play("a", 0, progress % 1);
- anim.Update(0f);
- }
-
- public static bool handleMurderAttempt(PlayerControl target, bool isMeetingStart = false) {
- // Block impostor shielded kill
- if (Medic.shielded != null && Medic.shielded == target) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.shieldedMurderAttempt();
-
- return false;
- }
- // Block impostor not fully grown mini kill
- else if (Mini.mini != null && target == Mini.mini && !Mini.isGrownUp()) {
- return false;
- }
- // Block Time Master with time shield kill
- else if (TimeMaster.shieldActive && TimeMaster.timeMaster != null && TimeMaster.timeMaster == target) {
- if (!isMeetingStart) { // Only rewind the attempt was not called because a meeting startet
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TimeMasterRewindTime, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.timeMasterRewindTime();
- }
- return false;
- }
- return true;
- }
-
-
- public static void refreshRoleDescription(PlayerControl player) {
- if (player == null) return;
-
- List<RoleInfo> infos = RoleInfo.getRoleInfoForPlayer(player);
-
- var toRemove = new List<PlayerTask>();
- foreach (PlayerTask t in player.myTasks) {
- var textTask = t.gameObject.GetComponent<ImportantTextTask>();
- if (textTask != null) {
- var info = infos.FirstOrDefault(x => textTask.Text.StartsWith(x.name));
- if (info != null)
- infos.Remove(info); // TextTask for this RoleInfo does not have to be added, as it already exists
- else
- toRemove.Add(t); // TextTask does not have a corresponding RoleInfo and will hence be deleted
- }
- }
-
- foreach (PlayerTask t in toRemove) {
- t.OnRemove();
- player.myTasks.Remove(t);
- UnityEngine.Object.Destroy(t.gameObject);
- }
-
- // Add TextTask for remaining RoleInfos
- foreach (RoleInfo roleInfo in infos) {
- var task = new GameObject("RoleTask").AddComponent<ImportantTextTask>();
- task.transform.SetParent(player.transform, false);
-
- if (roleInfo.name == "Jackal") {
- var getSidekickText = Jackal.canCreateSidekick ? " and recruit a Sidekick" : "";
- task.Text = cs(roleInfo.color, $"{roleInfo.name}: Kill everyone{getSidekickText}");
- } else {
- task.Text = cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription}");
- }
-
- player.myTasks.Insert(0, task);
- }
- }
-
- public static bool isLighterColor(int colorId) {
- return CustomColors.lighterColors.Contains(colorId);
- }
-
- public static bool isCustomServer() {
- if (DestroyableSingleton<ServerManager>.Instance == null) return false;
- StringNames n = DestroyableSingleton<ServerManager>.Instance.CurrentRegion.TranslateName;
- return n != StringNames.ServerNA && n != StringNames.ServerEU && n != StringNames.ServerAS;
- }
-
- public static bool hasFakeTasks(this PlayerControl player) {
- 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) {
- if (player == null) return;
- for (int i = 0; i < player.myTasks.Count; i++) {
- PlayerTask playerTask = player.myTasks[i];
- playerTask.OnRemove();
- UnityEngine.Object.Destroy(playerTask.gameObject);
- }
- player.myTasks.Clear();
-
- if (player.Data != null && player.Data.Tasks != null)
- player.Data.Tasks.Clear();
- }
-
- public static void setSemiTransparent(this PoolablePlayer player, bool value) {
- float alpha = value ? 0.25f : 1f;
- foreach (SpriteRenderer r in player.gameObject.GetComponentsInChildren<SpriteRenderer>())
- r.color = new Color(r.color.r, r.color.g, r.color.b, alpha);
- player.NameText.color = new Color(player.NameText.color.r, player.NameText.color.g, player.NameText.color.b, alpha);
- }
-
- public static string cs(Color c, string s) {
- return string.Format("<color=#{0:X2}{1:X2}{2:X2}{3:X2}>{4}</color>", ToByte(c.r), ToByte(c.g), ToByte(c.b), ToByte(c.a), s);
- }
-
- private static byte ToByte(float f) {
- 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;
- }
- }
-}
+++ /dev/null
-using HarmonyLib;
-using System;
-using static TheOtherRoles.TheOtherRoles;
-using UnityEngine;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))]
- class IntroCutsceneOnDestroyPatch
- {
- public static void Prefix(IntroCutscene __instance) {
- // 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);
- 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);
- }
- }
- }
- }
-
- [HarmonyPatch]
- class IntroPatch {
- public static void setupIntroTeam(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
- // Intro solo teams
- if (PlayerControl.LocalPlayer == Jester.jester || PlayerControl.LocalPlayer == Jackal.jackal || PlayerControl.LocalPlayer == Arsonist.arsonist) {
- var soloTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
- soloTeam.Add(PlayerControl.LocalPlayer);
- yourTeam = soloTeam;
- }
-
- // Add the Spy to the Impostor team (for the Impostors)
- if (Spy.spy != null && PlayerControl.LocalPlayer.Data.IsImpostor) {
- List<PlayerControl> players = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
- var fakeImpostorTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
- foreach (PlayerControl p in players) {
- if (p == Spy.spy || p.Data.IsImpostor)
- fakeImpostorTeam.Add(p);
- }
- yourTeam = fakeImpostorTeam;
- }
- }
-
- public static void setupIntroRole(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
- List<RoleInfo> infos = RoleInfo.getRoleInfoForPlayer(PlayerControl.LocalPlayer);
- RoleInfo roleInfo = infos.Where(info => info.roleId != RoleId.Lover).FirstOrDefault();
-
- if (roleInfo != null) {
- __instance.Title.text = roleInfo.name;
- __instance.ImpostorText.gameObject.SetActive(true);
- __instance.ImpostorText.text = roleInfo.introDescription;
- if (roleInfo.roleId != RoleId.Crewmate && roleInfo.roleId != RoleId.Impostor) {
- // For native Crewmate or Impostor do not modify the colors
- __instance.Title.color = roleInfo.color;
- __instance.BackgroundBar.material.color = roleInfo.color;
- }
- }
-
- if (infos.Any(info => info.roleId == RoleId.Lover)) {
- var loversText = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(__instance.ImpostorText, __instance.ImpostorText.transform.parent);
- loversText.transform.localPosition += Vector3.down * 3f;
- PlayerControl otherLover = PlayerControl.LocalPlayer == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
- loversText.text = Helpers.cs(Lovers.color, $"❤ You are in love with {otherLover?.Data?.PlayerName ?? ""} ❤");
- }
- }
-
- [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))]
- class BeginCrewmatePatch {
- public static void Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
- setupIntroTeam(__instance, ref yourTeam);
- }
-
- public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
- setupIntroRole(__instance, ref yourTeam);
- }
- }
-
- [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginImpostor))]
- class BeginImpostorPatch {
- public static void Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
- setupIntroTeam(__instance, ref yourTeam);
- }
-
- public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
- setupIntroRole(__instance, ref yourTeam);
- }
- }
- }
-}
-
+++ /dev/null
-using System;
-using System.Collections.Generic;
-using System.Collections;
-using UnityEngine;
-using System.Linq;
-
-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 static bool boxesConvertedToVents = false;
- public static Sprite[] boxAnimationSprites = new Sprite[18];
-
- public static Sprite getBoxAnimationSprite(int index) {
- if (boxAnimationSprites == null || boxAnimationSprites.Length == 0) return null;
- index = Mathf.Clamp(index, 0, boxAnimationSprites.Length - 1);
- if (boxAnimationSprites[index] == null)
- boxAnimationSprites[index] = (Helpers.loadSpriteFromResources($"TheOtherRoles.Resources.TricksterAnimation.trickster_box_00{(index + 1):00}.png", 175f));
- return boxAnimationSprites[index];
- }
-
- public static void startAnimation(int ventId) {
- JackInTheBox box = AllJackInTheBoxes.FirstOrDefault((x) => x?.vent != null && x.vent.Id == ventId);
- if (box == null) return;
- Vent vent = box.vent;
-
- HudManager.Instance.StartCoroutine(Effects.Lerp(0.6f, new Action<float>((p) => {
- if (vent != null && vent.myRend != null) {
- vent.myRend.sprite = getBoxAnimationSprite((int)(p * boxAnimationSprites.Length));
- if (p == 1f) vent.myRend.sprite = getBoxAnimationSprite(0);
- }
- })));
- }
-
- private GameObject gameObject;
- public Vent vent;
-
- public JackInTheBox(Vector2 p) {
- gameObject = new GameObject("JackInTheBox");
- 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;
- var boxRenderer = gameObject.AddComponent<SpriteRenderer>();
- boxRenderer.sprite = getBoxAnimationSprite(0);
-
- // Create the vent
- var referenceVent = UnityEngine.Object.FindObjectOfType<Vent>();
- vent = UnityEngine.Object.Instantiate<Vent>(referenceVent);
- vent.transform.position = gameObject.transform.position;
- vent.Left = null;
- vent.Right = null;
- vent.Center = null;
- vent.EnterVentAnim = null;
- vent.ExitVentAnim = null;
- vent.Offset = new Vector3(0f, 0.25f, 0f);
- vent.GetComponent<PowerTools.SpriteAnim>()?.Stop();
- vent.Id = ShipStatus.Instance.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id
- var ventRenderer = vent.GetComponent<SpriteRenderer>();
- ventRenderer.sprite = getBoxAnimationSprite(0);
- vent.myRend = ventRenderer;
- var allVentsList = ShipStatus.Instance.AllVents.ToList();
- allVentsList.Add(vent);
- ShipStatus.Instance.AllVents = allVentsList.ToArray();
- vent.gameObject.SetActive(false);
- vent.name = "JackInTheBoxVent_" + vent.Id;
-
- // Only render the box for the Trickster
- var playerIsTrickster = PlayerControl.LocalPlayer == Trickster.trickster;
- gameObject.SetActive(playerIsTrickster);
-
- AllJackInTheBoxes.Add(this);
- }
-
- public static void UpdateStates() {
- if (boxesConvertedToVents == true) return;
- foreach (var box in AllJackInTheBoxes) {
- var playerIsTrickster = PlayerControl.LocalPlayer == Trickster.trickster;
- box.gameObject.SetActive(playerIsTrickster);
- }
- }
-
- public void convertToVent() {
- gameObject.SetActive(false);
- vent.gameObject.SetActive(true);
- return;
- }
-
- public static void convertToVents() {
- foreach (var box in AllJackInTheBoxes) {
- box.convertToVent();
- }
- connectVents();
- boxesConvertedToVents = true;
- return;
- }
-
- public static bool hasJackInTheBoxLimitReached() {
- return (AllJackInTheBoxes.Count >= JackInTheBoxLimit);
- }
-
- private static void connectVents() {
- for (var i = 0; i < AllJackInTheBoxes.Count - 1; i++) {
- var a = AllJackInTheBoxes[i];
- var b = AllJackInTheBoxes[i + 1];
- a.vent.Right = b.vent;
- b.vent.Left = a.vent;
- }
- // Connect first with last
- AllJackInTheBoxes.First().vent.Left = AllJackInTheBoxes.Last().vent;
- AllJackInTheBoxes.Last().vent.Right = AllJackInTheBoxes.First().vent;
- }
-
- public static void clearJackInTheBoxes() {
- boxesConvertedToVents = false;
- AllJackInTheBoxes = new List<JackInTheBox>();
- }
-
- }
-
-}
\ No newline at end of file
+++ /dev/null
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
-using HarmonyLib;
-using Hazel;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Linq;
-using System.Net;
-using System.IO;
-using System;
-using System.Reflection;
-using UnhollowerBaseLib;
-using UnityEngine;
-
-namespace TheOtherRoles
-{
- [BepInPlugin(Id, "The Other Roles", VersionString)]
- [BepInProcess("Among Us.exe")]
- public class TheOtherRolesPlugin : BasePlugin
- {
- public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "2.7.1";
- public static System.Version Version = System.Version.Parse(VersionString);
-
- public Harmony Harmony { get; } = new Harmony(Id);
- public static TheOtherRolesPlugin Instance;
-
- public static int optionsPage = 1;
-
- public static ConfigEntry<bool> DebugMode { get; private set; }
- public static ConfigEntry<bool> StreamerMode { get; set; }
- public static ConfigEntry<bool> GhostsSeeTasks { get; set; }
- public static ConfigEntry<bool> GhostsSeeRoles { get; set; }
- public static ConfigEntry<bool> GhostsSeeVotes{ get; set; }
- public static ConfigEntry<bool> ShowRoleSummary { get; set; }
- public static ConfigEntry<string> StreamerModeReplacementText { get; set; }
- public static ConfigEntry<string> StreamerModeReplacementColor { get; set; }
- 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;
- IRegionInfo[] regions = defaultRegions;
-
- var CustomRegion = new DnsRegionInfo(Ip.Value, "Custom", StringNames.NoTranslation, Ip.Value, Port.Value);
- regions = regions.Concat(new IRegionInfo[] { CustomRegion.Cast<IRegionInfo>() }).ToArray();
- ServerManager.DefaultRegions = regions;
- serverManager.AvailableRegions = regions;
- }
-
- public override void Load() {
-
- DebugMode = Config.Bind("Custom", "Enable Debug Mode", false);
- StreamerMode = Config.Bind("Custom", "Enable Streamer Mode", false);
- GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true);
- GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true);
- GhostsSeeVotes = Config.Bind("Custom", "Ghosts See Votes", true);
- ShowRoleSummary = Config.Bind("Custom", "Show Role Summary", true);
- StreamerModeReplacementText = Config.Bind("Custom", "Streamer Mode Replacement Text", "\n\nThe Other Roles");
- StreamerModeReplacementColor = Config.Bind("Custom", "Streamer Mode Replacement Text Hex Color", "#87AAF5FF");
-
-
- Ip = Config.Bind("Custom", "Custom Server IP", "127.0.0.1");
- Port = Config.Bind("Custom", "Custom Server Port", (ushort)22023);
- defaultRegions = ServerManager.DefaultRegions;
-
- UpdateRegions();
-
- GameOptionsData.RecommendedImpostors = GameOptionsData.MaxImpostors = Enumerable.Repeat(3, 16).ToArray(); // Max Imp = Recommended Imp = 3
- GameOptionsData.MinPlayers = Enumerable.Repeat(4, 15).ToArray(); // Min Players = 4
-
- DebugMode = Config.Bind("Custom", "Enable Debug Mode", false);
- Instance = this;
- CustomOptionHolder.Load();
- CustomColors.Load();
-
- 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
- [HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
- public static class AmBannedPatch
- {
- public static void Postfix(out bool __result)
- {
- __result = false;
- }
- }
- [HarmonyPatch(typeof(ChatController), nameof(ChatController.Awake))]
- public static class ChatControllerAwakePatch {
- private static void Prefix() {
- if (!EOSManager.Instance.IsMinor()) {
- SaveManager.chatModeType = 1;
- SaveManager.isGuest = false;
- }
- }
- }
-
- // Debugging tools
- [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))]
- public static class DebugManager
- {
- private static readonly System.Random random = new System.Random((int)DateTime.Now.Ticks);
- private static List<PlayerControl> bots = new List<PlayerControl>();
-
- public static void Postfix(KeyboardJoystick __instance)
- {
- if (!TheOtherRolesPlugin.DebugMode.Value) return;
-
- // Spawn dummys
- if (Input.GetKeyDown(KeyCode.F)) {
- var playerControl = UnityEngine.Object.Instantiate(AmongUsClient.Instance.PlayerPrefab);
- var i = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
-
- bots.Add(playerControl);
- GameData.Instance.AddPlayer(playerControl);
- AmongUsClient.Instance.Spawn(playerControl, -2, InnerNet.SpawnFlags.None);
-
- playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
- playerControl.GetComponent<DummyBehaviour>().enabled = true;
- playerControl.NetTransform.enabled = false;
- playerControl.SetName(RandomString(10));
- playerControl.SetColor((byte) random.Next(Palette.PlayerColors.Length));
- playerControl.SetHat((uint) random.Next(HatManager.Instance.AllHats.Count), playerControl.Data.ColorId);
- playerControl.SetPet((uint) random.Next(HatManager.Instance.AllPets.Count));
- playerControl.SetSkin((uint) random.Next(HatManager.Instance.AllSkins.Count));
- GameData.Instance.RpcSetTasks(playerControl.PlayerId, new byte[0]);
- }
-
- // Terminate round
- if(Input.GetKeyDown(KeyCode.L)) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ForceEnd, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.forceEnd();
- }
- }
-
- public static string RandomString(int length)
- {
- const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
- return new string(Enumerable.Repeat(chars, length)
- .Select(s => s[random.Next(s.Length)]).ToArray());
- }
- }
-}
+++ /dev/null
-using System.Collections.Generic;
-using System.Collections;
-using System;
-using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
-
-namespace TheOtherRoles{
- static class MapOptions {
- // Set values
- public static int maxNumberOfMeetings = 10;
- public static bool blockSkippingInEmergencyMeetings = false;
- public static bool noVoteIsSelfVote = false;
- public static bool hidePlayerNames = false;
- public static bool ghostsSeeRoles = true;
- public static bool ghostsSeeTasks = true;
- public static bool ghostsSeeVotes = true;
- public static bool showRoleSummary = true;
-
- // Updating values
- 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() {
- 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();
- noVoteIsSelfVote = CustomOptionHolder.noVoteIsSelfVote.getBool();
- hidePlayerNames = CustomOptionHolder.hidePlayerNames.getBool();
- ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value;
- ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value;
- ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value;
- showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value;
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using System.Collections.Generic;
-using System.Linq;
-using UnhollowerBaseLib;
-using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.MapOptions;
-using System.Collections;
-using System;
-using System.Text;
-using UnityEngine;
-using System.Reflection;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch]
- class MeetingHudPatch {
- static bool[] selections;
- static SpriteRenderer[] renderers;
- private static GameData.PlayerInfo target = null;
- private const float scale = 0.65f;
-
- [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))]
- class MeetingCalculateVotesPatch {
- 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.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) {
- if (!dictionary.ContainsKey(swapped1.TargetPlayerId)) dictionary[swapped1.TargetPlayerId] = 0;
- if (!dictionary.ContainsKey(swapped2.TargetPlayerId)) dictionary[swapped2.TargetPlayerId] = 0;
- int tmp = dictionary[swapped1.TargetPlayerId];
- dictionary[swapped1.TargetPlayerId] = dictionary[swapped2.TargetPlayerId];
- dictionary[swapped2.TargetPlayerId] = tmp;
- }
-
- return dictionary;
- }
-
-
- static bool Prefix(MeetingHud __instance) {
- 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
- }
- }
-
- Dictionary<byte, int> self = CalculateVotes(__instance);
- bool tie;
- 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[i] = new MeetingHud.VoterState {
- VoterId = playerVoteArea.TargetPlayerId,
- VotedForId = playerVoteArea.VotedFor
- };
- }
-
- // RPCVotingComplete
- __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 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;
- if (doSwap) {
- __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));
- }
-
-
- __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++) {
- 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++;
- }
-
- // 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;
- }
- }
-
- [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.VotingComplete))]
- class MeetingHudVotingCompletedPatch {
- static void Postfix(MeetingHud __instance, [HarmonyArgument(0)]byte[] states, [HarmonyArgument(1)]GameData.PlayerInfo exiled, [HarmonyArgument(2)]bool tie)
- {
- // Reset swapper values
- Swapper.playerId1 = Byte.MaxValue;
- Swapper.playerId2 = Byte.MaxValue;
-
- // Lovers save next to be exiled, because RPC of ending game comes before RPC of exiled
- Lovers.notAckedExiledIsLover = false;
- if (exiled != null)
- Lovers.notAckedExiledIsLover = ((Lovers.lover1 != null && Lovers.lover1.PlayerId == exiled.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == exiled.PlayerId));
- }
- }
-
-
- static void swapperOnClick(int i, MeetingHud __instance) {
- if (__instance.state == MeetingHud.VoteStates.Results) return;
- if (__instance.playerStates[i].AmDead) return;
-
- int selectedCount = selections.Where(b => b).Count();
- SpriteRenderer renderer = renderers[i];
-
- if (selectedCount == 0) {
- renderer.color = Color.green;
- selections[i] = true;
- } else if (selectedCount == 1) {
- if (selections[i]) {
- renderer.color = Color.red;
- selections[i] = false;
- } else {
- selections[i] = true;
- renderer.color = Color.green;
-
- PlayerVoteArea firstPlayer = null;
- PlayerVoteArea secondPlayer = null;
- for (int A = 0; A < selections.Length; A++) {
- if (selections[A]) {
- if (firstPlayer != null) {
- secondPlayer = __instance.playerStates[A];
- break;
- } else {
- firstPlayer = __instance.playerStates[A];
- }
- }
- }
-
- if (firstPlayer != null && secondPlayer != null) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SwapperSwap, Hazel.SendOption.Reliable, -1);
- writer.Write((byte)firstPlayer.TargetPlayerId);
- writer.Write((byte)secondPlayer.TargetPlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
-
- RPCProcedure.swapperSwap((byte)firstPlayer.TargetPlayerId, (byte)secondPlayer.TargetPlayerId);
- }
- }
- }
- }
-
- private static GameObject guesserUI;
- static void guesserOnClick(int buttonTarget, MeetingHud __instance) {
- if (guesserUI != null || !(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted)) return;
-
- Transform container = UnityEngine.Object.Instantiate(__instance.transform.FindChild("Background"), __instance.transform);
- container.transform.localPosition = new Vector3(0, 0, -5f);
- guesserUI = container.gameObject;
-
- int i = 0;
- var buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase");
- var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton");
- var textTemplate = __instance.playerStates[0].NameText;
-
- Transform exitButton = UnityEngine.Object.Instantiate(buttonTemplate.transform, container);
- exitButton.transform.localPosition = new Vector3(2.725f, 2.1f, -5);
- exitButton.transform.localScale = new Vector3(0.25f, 0.9f, 1);
- exitButton.gameObject.GetComponent<SpriteRenderer>().sprite = smallButtonTemplate.GetComponent<SpriteRenderer>().sprite;
- exitButton.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
- exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
- UnityEngine.Object.Destroy(container.gameObject);
- }));
-
- 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);
- buttons.Add(button);
- TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button);
- int row = i/4, col = i%4;
- 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 *= 1.7f;
- int copiedIndex = i;
-
- button.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
- button.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
- 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;
-
- 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);
-
- 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++;
- }
- container.transform.localScale *= 0.75f;
- }
-
- [HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Select))]
- class PlayerVoteAreaSelectPatch {
- static bool Prefix(MeetingHud __instance) {
- return !(PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer == Guesser.guesser && guesserUI != null);
- }
- }
-
-
- static void populateButtonsPostfix(MeetingHud __instance) {
- // Add Swapper Buttons
- if (Swapper.swapper != null && PlayerControl.LocalPlayer == Swapper.swapper && !Swapper.swapper.Data.IsDead) {
- selections = new bool[__instance.playerStates.Length];
- renderers = new SpriteRenderer[__instance.playerStates.Length];
-
- for (int i = 0; i < __instance.playerStates.Length; i++) {
- PlayerVoteArea playerVoteArea = __instance.playerStates[i];
- 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(-0.95f, 0.03f, -1f);
- SpriteRenderer renderer = checkbox.GetComponent<SpriteRenderer>();
- renderer.sprite = Swapper.getCheckSprite();
- renderer.color = Color.red;
-
- PassiveButton button = checkbox.GetComponent<PassiveButton>();
- button.OnClick.RemoveAllListeners();
- int copiedIndex = i;
- button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => swapperOnClick(copiedIndex, __instance)));
-
- selections[i] = false;
- renderers[i] = renderer;
- }
- }
-
- // Add Guesser Buttons
- 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.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(-0.95f, 0.03f, -1f);
- SpriteRenderer renderer = targetBox.GetComponent<SpriteRenderer>();
- renderer.sprite = Guesser.getTargetSprite();
- PassiveButton button = targetBox.GetComponent<PassiveButton>();
- button.OnClick.RemoveAllListeners();
- int copiedIndex = i;
- button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => guesserOnClick(copiedIndex, __instance)));
- }
- }
- }
-
- [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.ServerStart))]
- class MeetingServerStartPatch {
- static void Postfix(MeetingHud __instance)
- {
- populateButtonsPostfix(__instance);
- }
- }
-
- [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Deserialize))]
- class MeetingDeserializePatch {
- static void Postfix(MeetingHud __instance, [HarmonyArgument(0)]MessageReader reader, [HarmonyArgument(1)]bool initialState)
- {
- // Add swapper buttons
- if (initialState) {
- populateButtonsPostfix(__instance);
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoStartMeeting))]
- class StartMeetingPatch {
- public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]GameData.PlayerInfo meetingTarget) {
- // Reset vampire bitten
- Vampire.bitten = null;
- // Count meetings
- if (meetingTarget == null) meetingsCount++;
- // Save the meeting target
- target = meetingTarget;
- }
- }
-
- [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))]
- class MeetingHudUpdatePatch {
- static void Postfix(MeetingHud __instance) {
- // Deactivate skip Button if skipping on emergency meetings is disabled
- if (target == null && blockSkippingInEmergencyMeetings)
- __instance.SkipVoteButton.gameObject.SetActive(false);
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using System;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
-using Il2CppSystem;
-using Hazel;
-using HarmonyLib;
-using UnityEngine;
-using UnityEngine.UI;
-using UnityEngine.Events;
-using UnhollowerBaseLib;
-using System.IO;
-using System.Reflection;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using System.Security.Cryptography;
-using Newtonsoft.Json.Linq;
-using Newtonsoft.Json;
-using Twitch;
-
-namespace TheOtherRoles {
- [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))]
- public class ModUpdaterButton {
- private static void Prefix(MainMenuManager __instance) {
- CustomHatLoader.LaunchHatFetcher();
- ModUpdater.LaunchUpdater();
- if (!ModUpdater.hasUpdate) return;
- var template = GameObject.Find("ExitGameButton");
- if (template == null) return;
-
- var button = UnityEngine.Object.Instantiate(template, null);
- button.transform.localPosition = new Vector3(button.transform.localPosition.x, button.transform.localPosition.y + 0.6f, button.transform.localPosition.z);
-
- PassiveButton passiveButton = button.GetComponent<PassiveButton>();
- passiveButton.OnClick = new Button.ButtonClickedEvent();
- passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick);
-
- var text = button.transform.GetChild(0).GetComponent<TMPro.TMP_Text>();
- __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => {
- text.SetText("Update\nThe Other Roles");
- })));
-
- TwitchManager man = DestroyableSingleton<TwitchManager>.Instance;
- ModUpdater.InfoPopup = UnityEngine.Object.Instantiate<GenericPopup>(man.TwitchPopup);
- ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f;
- ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false;
-
- void onClick() {
- ModUpdater.ExecuteUpdate();
- button.SetActive(false);
- }
- }
- }
-
- public class ModUpdater {
- public static bool running = false;
- public static bool hasUpdate = false;
- public static string updateURI = null;
- private static Task updateTask = null;
- public static GenericPopup InfoPopup;
-
- public static void LaunchUpdater() {
- if (running) return;
- running = true;
- checkForUpdate().GetAwaiter().GetResult();
- clearOldVersions();
- }
-
- public static void ExecuteUpdate() {
- string info = "Updating The Other Roles\nPlease wait...";
- ModUpdater.InfoPopup.Show(info); // Show originally
- if (updateTask == null) {
- if (updateURI != null) {
- updateTask = downloadUpdate();
- } else {
- info = "Unable to auto-update\nPlease update manually";
- }
- } else {
- info = "Update might already\nbe in progress";
- }
- ModUpdater.InfoPopup.StartCoroutine(Effects.Lerp(0.01f, new System.Action<float>((p) => { ModUpdater.setPopupText(info); })));
- }
-
- public static void clearOldVersions() {
- try {
- DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(Application.dataPath) + @"\BepInEx\plugins");
- string[] files = d.GetFiles("*.old").Select(x => x.FullName).ToArray(); // Getting old versions
- foreach (string f in files)
- File.Delete(f);
- } catch (System.Exception e) {
- System.Console.WriteLine("Exception occured when clearing old versions:\n" + e);
- }
- }
-
- public static async Task<bool> checkForUpdate() {
- try {
- HttpClient http = new HttpClient();
- http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater");
- var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/Eisbison/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead);
- // var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/EoF-1141/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead);
- if (response.StatusCode != HttpStatusCode.OK || response.Content == null) {
- System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString());
- return false;
- }
- string json = await response.Content.ReadAsStringAsync();
- JObject data = JObject.Parse(json);
-
- string tagname = data["tag_name"]?.ToString();
- if (tagname == null) {
- return false; // Something went wrong
- }
- // check version
- System.Version ver = System.Version.Parse(tagname.Replace("v", ""));
- int diff = TheOtherRolesPlugin.Version.CompareTo(ver);
- if (diff < 0) { // Update required
- hasUpdate = true;
- JToken assets = data["assets"];
- if (!assets.HasValues)
- return false;
-
- for (JToken current = assets.First; current != null; current = current.Next) {
- string browser_download_url = current["browser_download_url"]?.ToString();
- if (browser_download_url != null && current["content_type"] != null) {
- if (current["content_type"].ToString().Equals("application/x-msdownload") &&
- browser_download_url.EndsWith(".dll")) {
- updateURI = browser_download_url;
- return true;
- }
- }
- }
- }
- } catch (System.Exception ex) {
- TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
- System.Console.WriteLine(ex);
- }
- return false;
- }
-
- public static async Task<bool> downloadUpdate() {
- try {
- HttpClient http = new HttpClient();
- http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater");
- var response = await http.GetAsync(new System.Uri(updateURI), HttpCompletionOption.ResponseContentRead);
- if (response.StatusCode != HttpStatusCode.OK || response.Content == null) {
- System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString());
- return false;
- }
- string codeBase = Assembly.GetExecutingAssembly().CodeBase;
- System.UriBuilder uri = new System.UriBuilder(codeBase);
- string fullname = System.Uri.UnescapeDataString(uri.Path);
- if (File.Exists(fullname + ".old")) // Clear old file in case it wasnt;
- File.Delete(fullname + ".old");
-
- File.Move(fullname, fullname + ".old"); // rename current executable to old
-
- using (var responseStream = await response.Content.ReadAsStreamAsync()) {
- using (var fileStream = File.Create(fullname)) { // probably want to have proper name here
- responseStream.CopyTo(fileStream);
- }
- }
- showPopup("The Other Roles\nupdated successfully\nPlease restart the game.");
- return true;
- } catch (System.Exception ex) {
- TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
- System.Console.WriteLine(ex);
- }
- showPopup("Update wasn't successful\nTry again later,\nor update manually.");
- return false;
- }
- private static void showPopup(string message) {
- setPopupText(message);
- InfoPopup.gameObject.SetActive(true);
- }
-
- public static void setPopupText(string message) {
- if (InfoPopup == null)
- return;
- if (InfoPopup.TextAreaTMP != null) {
- InfoPopup.TextAreaTMP.text = message;
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-
-namespace TheOtherRoles {
- [Harmony]
- public class AccountManagerPatch {
- [HarmonyPatch(typeof(AccountManager), nameof(AccountManager.RandomizeName))]
- public static class RandomizeNamePatch {
- static bool Prefix(AccountManager __instance) {
- if (SaveManager.lastPlayerName == null)
- return true;
- SaveManager.PlayerName = SaveManager.lastPlayerName;
- __instance.accountTab.UpdateNameDisplay();
- return false; // Don't execute original
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.GameHistory;
-using UnityEngine;
-
-namespace TheOtherRoles {
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
- public static class PlayerControlFixedUpdatePatch
- {
- // Helpers
-
- static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List<PlayerControl> untargetablePlayers = null, PlayerControl targetingPlayer = null) {
- PlayerControl result = null;
- float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)];
- if (!ShipStatus.Instance) return result;
- if (targetingPlayer == null) targetingPlayer = PlayerControl.LocalPlayer;
- if (targetingPlayer.Data.IsDead) return result;
-
- Vector2 truePosition = targetingPlayer.GetTruePosition();
- Il2CppSystem.Collections.Generic.List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
- for (int i = 0; i < allPlayers.Count; i++)
- {
- GameData.PlayerInfo playerInfo = allPlayers[i];
- if (!playerInfo.Disconnected && playerInfo.PlayerId != targetingPlayer.PlayerId && !playerInfo.IsDead && (!onlyCrewmates || !playerInfo.IsImpostor))
- {
- PlayerControl @object = playerInfo.Object;
- if(untargetablePlayers != null && untargetablePlayers.Any(x => x == @object)) {
- // if that player is not targetable: skip check
- continue;
- }
-
- if (@object && (!@object.inVent || targetPlayersInVents))
- {
- Vector2 vector = @object.GetTruePosition() - truePosition;
- float magnitude = vector.magnitude;
- if (magnitude <= num && !PhysicsHelpers.AnyNonTriggersBetween(truePosition, vector.normalized, magnitude, Constants.ShipAndObjectsMask))
- {
- result = @object;
- num = magnitude;
- }
- }
- }
- }
- return result;
- }
-
- static void setPlayerOutline(PlayerControl target, Color color) {
- if (target == null || target.myRend == null) return;
-
- target.myRend.material.SetFloat("_Outline", 1f);
- target.myRend.material.SetColor("_OutlineColor", color);
- }
-
- // Update functions
-
- static void setBasePlayerOutlines() {
- foreach (PlayerControl target in PlayerControl.AllPlayerControls) {
- if (target == null || target.myRend == null) continue;
-
- bool isMorphedMorphling = target == Morphling.morphling && Morphling.morphTarget != null && Morphling.morphTimer > 0f;
- bool hasVisibleShield = false;
- if (Camouflager.camouflageTimer <= 0f && Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) {
- hasVisibleShield = Medic.showShielded == 0 // Everyone
- || (Medic.showShielded == 1 && (PlayerControl.LocalPlayer == Medic.shielded || PlayerControl.LocalPlayer == Medic.medic)) // Shielded + Medic
- || (Medic.showShielded == 2 && PlayerControl.LocalPlayer == Medic.medic); // Medic only
- }
-
- if (hasVisibleShield) {
- target.myRend.material.SetFloat("_Outline", 1f);
- target.myRend.material.SetColor("_OutlineColor", Medic.shieldedColor);
- } else {
- target.myRend.material.SetFloat("_Outline", 0f);
- }
- }
- }
-
- public static void bendTimeUpdate() {
- if (TimeMaster.isRewinding) {
- if (localPlayerPositions.Count > 0) {
- // Set position
- var next = localPlayerPositions[0];
- if (next.Item2 == true) {
- // Exit current vent if necessary
- if (PlayerControl.LocalPlayer.inVent) {
- foreach (Vent vent in ShipStatus.Instance.AllVents) {
- bool canUse;
- bool couldUse;
- vent.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
- if (canUse) {
- PlayerControl.LocalPlayer.MyPhysics.RpcExitVent(vent.Id);
- vent.SetButtons(false);
- }
- }
- }
- // Set position
- PlayerControl.LocalPlayer.transform.position = next.Item1;
- } else if (localPlayerPositions.Any(x => x.Item2 == true)) {
- PlayerControl.LocalPlayer.transform.position = next.Item1;
- }
-
- localPlayerPositions.RemoveAt(0);
-
- if (localPlayerPositions.Count > 1) localPlayerPositions.RemoveAt(0); // Skip every second position to rewinde twice as fast, but never skip the last position
- } else {
- TimeMaster.isRewinding = false;
- PlayerControl.LocalPlayer.moveable = true;
- }
- } else {
- while (localPlayerPositions.Count >= Mathf.Round(TimeMaster.rewindTime / Time.fixedDeltaTime)) localPlayerPositions.RemoveAt(localPlayerPositions.Count - 1);
- localPlayerPositions.Insert(0, new Tuple<Vector3, bool>(PlayerControl.LocalPlayer.transform.position, PlayerControl.LocalPlayer.CanMove)); // CanMove = CanMove
- }
- }
-
- static void medicSetTarget() {
- if (Medic.medic == null || Medic.medic != PlayerControl.LocalPlayer) return;
- Medic.currentTarget = setTarget();
- if (!Medic.usedShield) setPlayerOutline(Medic.currentTarget, Medic.shieldedColor);
- }
-
- static void shifterSetTarget() {
- if (Shifter.shifter == null || Shifter.shifter != PlayerControl.LocalPlayer) return;
- Shifter.currentTarget = setTarget();
- if (Shifter.futureShift == null) setPlayerOutline(Shifter.currentTarget, Shifter.color);
- }
-
-
- static void morphlingSetTarget() {
- if (Morphling.morphling == null || Morphling.morphling != PlayerControl.LocalPlayer) return;
- Morphling.currentTarget = setTarget();
- setPlayerOutline(Morphling.currentTarget, Morphling.color);
- }
-
- static void sheriffSetTarget() {
- if (Sheriff.sheriff == null || Sheriff.sheriff != PlayerControl.LocalPlayer) return;
- Sheriff.currentTarget = setTarget();
- setPlayerOutline(Sheriff.currentTarget, Sheriff.color);
- }
-
- static void trackerSetTarget() {
- if (Tracker.tracker == null || Tracker.tracker != PlayerControl.LocalPlayer) return;
- Tracker.currentTarget = setTarget();
- if (!Tracker.usedTracker) setPlayerOutline(Tracker.currentTarget, Tracker.color);
- }
-
- static void detectiveUpdateFootPrints() {
- if (Detective.detective == null || Detective.detective != PlayerControl.LocalPlayer) return;
-
- Detective.timer -= Time.fixedDeltaTime;
- if (Detective.timer <= 0f) {
- Detective.timer = Detective.footprintIntervall;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- if (player != null && player != PlayerControl.LocalPlayer && !player.Data.IsDead && !player.inVent) {
- new Footprint(Detective.footprintDuration, Detective.anonymousFootprints, player);
- }
- }
- }
- }
-
- static void vampireSetTarget() {
- if (Vampire.vampire == null || Vampire.vampire != PlayerControl.LocalPlayer) return;
-
- PlayerControl target = null;
- if (Spy.spy != null) {
- if (Spy.impostorsCanKillAnyone) {
- target = setTarget(false, true);
- } else {
- target = setTarget(true, true, new List<PlayerControl>() { Spy.spy });
- }
- } else {
- target = setTarget(true, true);
- }
-
- bool targetNearGarlic = false;
- if (target != null) {
- foreach (Garlic garlic in Garlic.garlics) {
- if (Vector2.Distance(garlic.garlic.transform.position, target.transform.position) <= 1.91f) {
- targetNearGarlic = true;
- }
- }
- }
- Vampire.targetNearGarlic = targetNearGarlic;
- Vampire.currentTarget = target;
- setPlayerOutline(Vampire.currentTarget, Vampire.color);
- }
-
- static void jackalSetTarget() {
- if (Jackal.jackal == null || Jackal.jackal != PlayerControl.LocalPlayer) return;
- var untargetablePlayers = new List<PlayerControl>();
- if(Jackal.canCreateSidekickFromImpostor) {
- // Only exclude sidekick from beeing targeted if the jackal can create sidekicks from impostors
- if(Sidekick.sidekick != null) untargetablePlayers.Add(Sidekick.sidekick);
- }
- if(Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); // Exclude Jackal from targeting the Mini unless it has grown up
- Jackal.currentTarget = setTarget(untargetablePlayers : untargetablePlayers);
- setPlayerOutline(Jackal.currentTarget, Palette.ImpostorRed);
- }
-
- static void sidekickSetTarget() {
- if (Sidekick.sidekick == null || Sidekick.sidekick != PlayerControl.LocalPlayer) return;
- var untargetablePlayers = new List<PlayerControl>();
- if(Jackal.jackal != null) untargetablePlayers.Add(Jackal.jackal);
- if(Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); // Exclude Sidekick from targeting the Mini unless it has grown up
- Sidekick.currentTarget = setTarget(untargetablePlayers : untargetablePlayers);
- if (Sidekick.canKill) setPlayerOutline(Sidekick.currentTarget, Palette.ImpostorRed);
- }
-
- static void sidekickCheckPromotion() {
- // If LocalPlayer is Sidekick, the Jackal is disconnected and Sidekick promotion is enabled, then trigger promotion
- if (Sidekick.sidekick == null || Sidekick.sidekick != PlayerControl.LocalPlayer) return;
- if (Sidekick.sidekick.Data.IsDead == true || !Sidekick.promotesToJackal) return;
- if (Jackal.jackal == null || Jackal.jackal?.Data?.Disconnected == true) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.sidekickPromotes();
- }
- }
-
- static void eraserSetTarget() {
- if (Eraser.eraser == null || Eraser.eraser != PlayerControl.LocalPlayer) return;
-
- List<PlayerControl> untargetables = new List<PlayerControl>();
- if (Spy.spy != null) untargetables.Add(Spy.spy);
- Eraser.currentTarget = setTarget(onlyCrewmates: !Eraser.canEraseAnyone, untargetablePlayers: Eraser.canEraseAnyone ? new List<PlayerControl>() : untargetables);
- setPlayerOutline(Eraser.currentTarget, Eraser.color);
- }
-
- static void engineerUpdate() {
- if (PlayerControl.LocalPlayer.Data.IsImpostor && ShipStatus.Instance?.AllVents != null) {
- foreach (Vent vent in ShipStatus.Instance.AllVents) {
- try {
- if (vent?.myRend?.material != null) {
- if (Engineer.engineer != null && Engineer.engineer.inVent) {
- vent.myRend.material.SetFloat("_Outline", 1f);
- vent.myRend.material.SetColor("_OutlineColor", Engineer.color);
- } else if (vent.myRend.material.GetColor("_AddColor") != Color.red) {
- vent.myRend.material.SetFloat("_Outline", 0);
- }
- }
- } catch {}
- }
- }
- }
-
- static void impostorSetTarget() {
- if (!PlayerControl.LocalPlayer.Data.IsImpostor ||!PlayerControl.LocalPlayer.CanMove || PlayerControl.LocalPlayer.Data.IsDead) { // !isImpostor || !canMove || isDead
- HudManager.Instance.KillButton.SetTarget(null);
- return;
- }
-
- PlayerControl target = null;
- if (Spy.spy != null) {
- if (Spy.impostorsCanKillAnyone) {
- target = setTarget(false, true);
- } else {
- target = setTarget(true, true, new List<PlayerControl>() { Spy.spy });
- }
- } else {
- target = setTarget(true, true);
- }
-
- HudManager.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed);
- }
-
- static void warlockSetTarget() {
- if (Warlock.warlock == null || Warlock.warlock != PlayerControl.LocalPlayer) return;
- if (Warlock.curseVictim != null && (Warlock.curseVictim.Data.Disconnected || Warlock.curseVictim.Data.IsDead)) {
- // If the cursed victim is disconnected or dead reset the curse so a new curse can be applied
- Warlock.resetCurse();
- }
- if (Warlock.curseVictim == null) {
- Warlock.currentTarget = setTarget();
- setPlayerOutline(Warlock.currentTarget, Warlock.color);
- } else {
- Warlock.curseVictimTarget = setTarget(targetingPlayer: Warlock.curseVictim);
- setPlayerOutline(Warlock.curseVictimTarget, Warlock.color);
- }
- }
-
- static void trackerUpdate() {
- if (Tracker.arrow?.arrow == null) return;
-
- if (Tracker.tracker == null || PlayerControl.LocalPlayer != Tracker.tracker) {
- Tracker.arrow.arrow.SetActive(false);
- return;
- }
-
- if (Tracker.tracker != null && Tracker.tracked != null && PlayerControl.LocalPlayer == Tracker.tracker && !Tracker.tracker.Data.IsDead) {
- Tracker.timeUntilUpdate -= Time.fixedDeltaTime;
-
- if (Tracker.timeUntilUpdate <= 0f) {
- bool trackedOnMap = !Tracker.tracked.Data.IsDead;
- Vector3 position = Tracker.tracked.transform.position;
- if (!trackedOnMap) { // Check for dead body
- DeadBody body = UnityEngine.Object.FindObjectsOfType<DeadBody>().FirstOrDefault(b => b.ParentId == Tracker.tracked.PlayerId);
- if (body != null) {
- trackedOnMap = true;
- position = body.transform.position;
- }
- }
-
- Tracker.arrow.Update(position);
- Tracker.arrow.arrow.SetActive(trackedOnMap);
- Tracker.timeUntilUpdate = Tracker.updateIntervall;
- } else {
- Tracker.arrow.Update();
- }
- }
- }
-
- public static void playerSizeUpdate(PlayerControl p) {
- // Set default player size
- CircleCollider2D collider = p.GetComponent<CircleCollider2D>();
-
- p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
- collider.radius = Mini.defaultColliderRadius;
- collider.offset = Mini.defaultColliderOffset * Vector2.down;
-
- // Set adapted player size to Mini and Morphling
- if (Mini.mini == null || Camouflager.camouflageTimer > 0f) return;
-
- float growingProgress = Mini.growingProgress();
- float scale = growingProgress * 0.35f + 0.35f;
- float correctedColliderRadius = Mini.defaultColliderRadius * 0.7f / scale; // scale / 0.7f is the factor by which we decrease the player size, hence we need to increase the collider size by 0.7f / scale
-
- if (p == Mini.mini) {
- p.transform.localScale = new Vector3(scale, scale, 1f);
- collider.radius = correctedColliderRadius;
- }
- if (Morphling.morphling != null && p == Morphling.morphling && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f) {
- p.transform.localScale = new Vector3(scale, scale, 1f);
- collider.radius = correctedColliderRadius;
- }
- }
-
- public static void updatePlayerInfo() {
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (p != PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) continue;
-
- 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.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.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 * 0.20f;
- meetingInfo.fontSize *= 0.75f;
- meetingInfo.gameObject.name = "Info";
- }
-
- var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data);
- string roleNames = String.Join(" ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray());
- string taskInfo = tasksTotal > 0 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
-
- string playerInfoText = "";
- string meetingInfoText ="";
- if (p == PlayerControl.LocalPlayer) {
- playerInfoText = $"{roleNames}";
- if (DestroyableSingleton<TaskPanelBehaviour>.InstanceExists) {
- TMPro.TextMeshPro tabText = DestroyableSingleton<TaskPanelBehaviour>.Instance.tab.transform.FindChild("TabText_TMP").GetComponent<TMPro.TextMeshPro>();
- tabText.SetText($"Tasks {taskInfo}");
- }
- meetingInfoText = $"{roleNames} {taskInfo}".Trim();
- }
- else if (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks) {
- playerInfoText = $"{roleNames} {taskInfo}".Trim();
- meetingInfoText = playerInfoText;
- }
- else if (MapOptions.ghostsSeeTasks) {
- playerInfoText = $"{taskInfo}".Trim();
- meetingInfoText = playerInfoText;
- }
- else if (MapOptions.ghostsSeeRoles) {
- playerInfoText = $"{roleNames}";
- meetingInfoText = playerInfoText;
- }
-
- playerInfo.text = playerInfoText;
- playerInfo.gameObject.SetActive(p.Visible);
- if (meetingInfo != null) meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : meetingInfoText;
- }
- }
-
- public static void securityGuardSetTarget() {
- if (SecurityGuard.securityGuard == null || SecurityGuard.securityGuard != PlayerControl.LocalPlayer || ShipStatus.Instance == null || ShipStatus.Instance.AllVents == null) return;
-
- Vent target = null;
- Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
- float closestDistance = float.MaxValue;
- for (int i = 0; i < ShipStatus.Instance.AllVents.Length; i++) {
- Vent vent = ShipStatus.Instance.AllVents[i];
- if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || vent.gameObject.name.StartsWith("SealedVent_") || vent.gameObject.name.StartsWith("FutureSealedVent_")) continue;
- float distance = Vector2.Distance(vent.transform.position, truePosition);
- if (distance <= vent.UsableDistance && distance < closestDistance) {
- closestDistance = distance;
- target = vent;
- }
- }
- SecurityGuard.ventTarget = target;
- }
-
- public static void arsonistSetTarget() {
- if (Arsonist.arsonist == null || Arsonist.arsonist != PlayerControl.LocalPlayer) return;
- List<PlayerControl> untargetables;
- if (Arsonist.douseTarget != null)
- untargetables = PlayerControl.AllPlayerControls.ToArray().Where(x => x.PlayerId != Arsonist.douseTarget.PlayerId).ToList();
- else
- untargetables = Arsonist.dousedPlayers;
- Arsonist.currentTarget = setTarget(untargetablePlayers: untargetables);
- if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color);
- }
-
- 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;
-
- // Mini and Morphling shrink
- playerSizeUpdate(__instance);
-
- if (PlayerControl.LocalPlayer == __instance) {
- // Update player outlines
- setBasePlayerOutlines();
-
- // Update Role Description
- Helpers.refreshRoleDescription(__instance);
-
- // Update Player Info
- updatePlayerInfo();
-
- // Time Master
- bendTimeUpdate();
- // Morphling
- morphlingSetTarget();
- // Medic
- medicSetTarget();
- // Shifter
- shifterSetTarget();
- // Sheriff
- sheriffSetTarget();
- // Detective
- detectiveUpdateFootPrints();
- // Tracker
- trackerSetTarget();
- // Vampire
- vampireSetTarget();
- Garlic.UpdateAll();
- // Eraser
- eraserSetTarget();
- // Engineer
- engineerUpdate();
- // Tracker
- trackerUpdate();
- // Jackal
- jackalSetTarget();
- // Sidekick
- sidekickSetTarget();
- // Impostor
- impostorSetTarget();
- // Warlock
- warlockSetTarget();
- // Check for sidekick promotion on Jackal disconnect
- sidekickCheckPromotion();
- // SecurityGuard
- securityGuardSetTarget();
- // Arsonist
- arsonistSetTarget();
- // Snitch
- snitchUpdate();
- // BountyHunter
- bountyHunterUpdate();
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.WalkPlayerTo))]
- class PlayerPhysicsWalkPlayerToPatch {
- private static Vector2 offset = Vector2.zero;
- public static void Prefix(PlayerPhysics __instance) {
- bool correctOffset = Camouflager.camouflageTimer <= 0f && (__instance.myPlayer == Mini.mini || (Morphling.morphling != null && __instance.myPlayer == Morphling.morphling && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f));
- if (correctOffset) {
- float currentScaling = (Mini.growingProgress() + 1) * 0.5f;
- __instance.myPlayer.Collider.offset = currentScaling * Mini.defaultColliderOffset * Vector2.down;
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
- class PlayerControlCmdReportDeadBodyPatch {
- public static void Prefix(PlayerControl __instance) {
- // Murder the bitten player before the meeting starts or reset the bitten player
- if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.bitten, true)) {
- MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireTryKill, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(killWriter);
- RPCProcedure.vampireTryKill();
- } else {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
- writer.Write(byte.MaxValue);
- writer.Write(byte.MaxValue);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue);
- }
- }
- }
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcMurderPlayer))]
- class RpcMurderPlayer {
- public static bool Prefix([HarmonyArgument(0)]PlayerControl target) {
- if (Helpers.handleMurderAttempt(target)) { // Custom checks
- 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);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, target.PlayerId);
- } else { // Checked by official servers
- return true;
- }
- }
- return false;
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.LocalPlayer.CmdReportDeadBody))]
- class BodyReportPatch
- {
- static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]GameData.PlayerInfo target)
- {
- // Medic or Detective report
- bool isMedicReport = Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && __instance.PlayerId == Medic.medic.PlayerId;
- bool isDetectiveReport = Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer && __instance.PlayerId == Detective.detective.PlayerId;
- if (isMedicReport || isDetectiveReport)
- {
- DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == target?.PlayerId)?.FirstOrDefault();
-
- if (deadPlayer != null && deadPlayer.killerIfExisting != null) {
- float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds);
- string msg = "";
-
- if (isMedicReport) {
- msg = $"Body Report: Killed {Math.Round(timeSinceDeath / 1000)}s ago!";
- } else if (isDetectiveReport) {
- if (timeSinceDeath < Detective.reportNameDuration * 1000) {
- msg = $"Body Report: The killer appears to be {deadPlayer.killerIfExisting.name}!";
- } else if (timeSinceDeath < Detective.reportColorDuration * 1000) {
- var typeOfColor = Helpers.isLighterColor(deadPlayer.killerIfExisting.Data.ColorId) ? "lighter" : "darker";
- msg = $"Body Report: The killer appears to be a {typeOfColor} color!";
- } else {
- msg = $"Body Report: The corpse is too old to gain information from!";
- }
- }
-
- if (!string.IsNullOrWhiteSpace(msg))
- {
- if (AmongUsClient.Instance.AmClient && DestroyableSingleton<HudManager>.Instance)
- {
- DestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, msg);
- }
- if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
- {
- DestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
- }
- }
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
- public static class MurderPlayerPatch
- {
- public static bool resetToCrewmate = false;
- public static bool resetToDead = false;
-
- public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]PlayerControl target)
- {
- // Allow everyone to murder players
- resetToCrewmate = !__instance.Data.IsImpostor;
- resetToDead = __instance.Data.IsDead;
- __instance.Data.IsImpostor = true;
- __instance.Data.IsDead = false;
- }
-
- public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]PlayerControl target)
- {
- // Collect dead player info
- DeadPlayer deadPlayer = new DeadPlayer(target, DateTime.UtcNow, DeathReason.Kill, __instance);
- GameHistory.deadPlayers.Add(deadPlayer);
-
- // Reset killer to crewmate if resetToCrewmate
- if (resetToCrewmate) __instance.Data.IsImpostor = false;
- if (resetToDead) __instance.Data.IsDead = true;
-
- // Remove fake tasks when player dies
- if (target.hasFakeTasks())
- target.clearAllTasks();
-
- // Lover suicide trigger on murder
- if ((Lovers.lover1 != null && target == Lovers.lover1) || (Lovers.lover2 != null && target == Lovers.lover2)) {
- PlayerControl otherLover = target == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
- if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) {
- otherLover.MurderPlayer(otherLover);
- }
- }
-
- // Sidekick promotion trigger on murder
- if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && target == Jackal.jackal && Jackal.jackal == PlayerControl.LocalPlayer) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.sidekickPromotes();
- }
-
- // Cleaner Button Sync
- if (Cleaner.cleaner != null && PlayerControl.LocalPlayer == Cleaner.cleaner && __instance == Cleaner.cleaner && HudManagerStartPatch.cleanerCleanButton != null)
- HudManagerStartPatch.cleanerCleanButton.Timer = Cleaner.cleaner.killTimer;
-
- // Warlock Button Sync
- if (Warlock.warlock != null && PlayerControl.LocalPlayer == Warlock.warlock && __instance == Warlock.warlock && HudManagerStartPatch.warlockCurseButton != null) {
- if(Warlock.warlock.killTimer > HudManagerStartPatch.warlockCurseButton.Timer) {
- HudManagerStartPatch.warlockCurseButton.Timer = Warlock.warlock.killTimer;
- }
- }
-
- // Seer show flash and add dead player position
- if (Seer.seer != null && PlayerControl.LocalPlayer == Seer.seer && !Seer.seer.Data.IsDead && Seer.seer != target && Seer.mode <= 1) {
- HudManager.Instance.FullScreen.enabled = true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action<float>((p) => {
- var renderer = HudManager.Instance.FullScreen;
- if (p < 0.5) {
- if (renderer != null)
- renderer.color = new Color(42f / 255f, 187f / 255f, 245f / 255f, Mathf.Clamp01(p * 2 * 0.75f));
- } else {
- if (renderer != null)
- renderer.color = new Color(42f / 255f, 187f / 255f, 245f / 255f, Mathf.Clamp01((1-p) * 2 * 0.75f));
- }
- if (p == 1f && renderer != null) renderer.enabled = false;
- })));
- }
- if (Seer.deadBodyPositions != null) Seer.deadBodyPositions.Add(target.transform.position);
-
- // Mini set adapted kill cooldown
- if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini && Mini.mini.Data.IsImpostor && Mini.mini == __instance) {
- 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);
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetKillTimer))]
- class PlayerControlSetCoolDownPatch {
- 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 + addition);
- DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
- class KillAnimationCoPerformKillPatch {
- public static void Prefix(KillAnimation __instance, [HarmonyArgument(0)]ref PlayerControl source, [HarmonyArgument(1)]ref PlayerControl target) {
- if (Vampire.vampire != null && Vampire.vampire == source && Vampire.bitten != null && Vampire.bitten == target)
- source = target;
-
- if (Warlock.warlock != null && Warlock.warlock == source && Warlock.curseKillTarget != null && Warlock.curseKillTarget == target) {
- source = target;
- Warlock.curseKillTarget = null; // Reset here
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Exiled))]
- public static class ExilePlayerPatch
- {
- public static void Postfix(PlayerControl __instance)
- {
- // Collect dead player info
- DeadPlayer deadPlayer = new DeadPlayer(__instance, DateTime.UtcNow, DeathReason.Exile, null);
- GameHistory.deadPlayers.Add(deadPlayer);
-
- // Remove fake tasks when player dies
- if (__instance.hasFakeTasks())
- __instance.clearAllTasks();
-
- // Lover suicide trigger on exile
- if ((Lovers.lover1 != null && __instance == Lovers.lover1) || (Lovers.lover2 != null && __instance == Lovers.lover2)) {
- PlayerControl otherLover = __instance == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
- if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie)
- otherLover.Exiled();
- }
-
- // Sidekick promotion trigger on exile
- if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && __instance == Jackal.jackal && Jackal.jackal == PlayerControl.LocalPlayer) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.sidekickPromotes();
- }
- }
- }
-
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CanMove), MethodType.Getter)]
- class PlayerControlCanMovePatch {
- public static bool Prefix(PlayerControl __instance, ref bool __result)
- {
- __result = __instance.moveable &&
- !Minigame.Instance &&
- (!DestroyableSingleton<HudManager>.InstanceExists || (!DestroyableSingleton<HudManager>.Instance.Chat.IsOpen && !DestroyableSingleton<HudManager>.Instance.KillOverlay.IsOpen && !DestroyableSingleton<HudManager>.Instance.GameMenu.IsOpen)) &&
- (!MapBehaviour.Instance || !MapBehaviour.Instance.IsOpenStopped) &&
- !MeetingHud.Instance &&
- !CustomPlayerMenu.Instance &&
- !ExileController.Instance &&
- !IntroCutscene.Instance;
- return false;
- }
- }
-}
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.HudManagerStartPatch;
-using static TheOtherRoles.GameHistory;
-using static TheOtherRoles.MapOptions;
-using System.Collections.Generic;
-using System.Linq;
-using UnityEngine;
-using System;
-
-namespace TheOtherRoles
-{
- enum RoleId {
- Jester,
- Mayor,
- Engineer,
- Sheriff,
- Lighter,
- Godfather,
- Mafioso,
- Janitor,
- Detective,
- TimeMaster,
- Medic,
- Shifter,
- Swapper,
- Lover,
- Seer,
- Morphling,
- Camouflager,
- Hacker,
- Mini,
- Tracker,
- Vampire,
- Snitch,
- Jackal,
- Sidekick,
- Eraser,
- Spy,
- Trickster,
- Cleaner,
- Warlock,
- SecurityGuard,
- Arsonist,
- Guesser,
- BountyHunter,
- Crewmate,
- Impostor
- }
-
- enum CustomRPC
- {
- // Main Controls
-
- ResetVaribles = 50,
- ShareOptionSelection,
- ForceEnd,
- SetRole,
- VersionHandshake,
- UseUncheckedVent,
- UncheckedMurderPlayer,
- // Role functionality
-
- EngineerFixLights = 81,
- EngineerUsedRepair,
- CleanBody,
- SheriffKill,
- MedicSetShielded,
- ShieldedMurderAttempt,
- TimeMasterShield,
- TimeMasterRewindTime,
- ShifterShift,
- SwapperSwap,
- MorphlingMorph,
- CamouflagerCamouflage,
- TrackerUsedTracker,
- VampireSetBitten,
- VampireTryKill,
- PlaceGarlic,
- JackalKill,
- SidekickKill,
- JackalCreatesSidekick,
- SidekickPromotes,
- ErasePlayerRoles,
- SetFutureErased,
- SetFutureShifted,
- PlaceJackInTheBox,
- LightsOut,
- WarlockCurseKill,
- PlaceCamera,
- SealVent,
- ArsonistWin,
- GuesserShoot
- }
-
- public static class RPCProcedure {
-
- // Main Controls
-
- public static void resetVariables() {
- Garlic.clearGarlics();
- JackInTheBox.clearJackInTheBoxes();
- clearAndReloadMapOptions();
- clearAndReloadRoles();
- clearGameHistory();
- setCustomButtonCooldowns();
- }
-
- public static void shareOptionSelection(uint id, uint selection) {
- CustomOption option = CustomOption.options.FirstOrDefault(option => option.id == (int)id);
- option.updateSelection((int)selection);
- }
-
- public static void forceEnd() {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- {
- if (!player.Data.IsImpostor)
- {
- player.RemoveInfected();
- player.MurderPlayer(player);
- player.Data.IsDead = true;
- }
- }
- }
-
- public static void setRole(byte roleId, byte playerId, byte flag) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- if (player.PlayerId == playerId) {
- switch((RoleId)roleId) {
- case RoleId.Jester:
- Jester.jester = player;
- break;
- case RoleId.Mayor:
- Mayor.mayor = player;
- break;
- case RoleId.Engineer:
- Engineer.engineer = player;
- break;
- case RoleId.Sheriff:
- Sheriff.sheriff = player;
- break;
- case RoleId.Lighter:
- Lighter.lighter = player;
- break;
- case RoleId.Godfather:
- Godfather.godfather = player;
- break;
- case RoleId.Mafioso:
- Mafioso.mafioso = player;
- break;
- case RoleId.Janitor:
- Janitor.janitor = player;
- break;
- case RoleId.Detective:
- Detective.detective = player;
- break;
- case RoleId.TimeMaster:
- TimeMaster.timeMaster = player;
- break;
- case RoleId.Medic:
- Medic.medic = player;
- break;
- case RoleId.Shifter:
- Shifter.shifter = player;
- break;
- case RoleId.Swapper:
- Swapper.swapper = player;
- break;
- case RoleId.Lover:
- if (flag == 0) Lovers.lover1 = player;
- else Lovers.lover2 = player;
- break;
- case RoleId.Seer:
- Seer.seer = player;
- break;
- case RoleId.Morphling:
- Morphling.morphling = player;
- break;
- case RoleId.Camouflager:
- Camouflager.camouflager = player;
- break;
- case RoleId.Hacker:
- Hacker.hacker = player;
- break;
- case RoleId.Mini:
- Mini.mini = player;
- break;
- case RoleId.Tracker:
- Tracker.tracker = player;
- break;
- case RoleId.Vampire:
- Vampire.vampire = player;
- break;
- case RoleId.Snitch:
- Snitch.snitch = player;
- break;
- case RoleId.Jackal:
- Jackal.jackal = player;
- break;
- case RoleId.Sidekick:
- Sidekick.sidekick = player;
- break;
- case RoleId.Eraser:
- Eraser.eraser = player;
- break;
- case RoleId.Spy:
- Spy.spy = player;
- break;
- case RoleId.Trickster:
- Trickster.trickster = player;
- break;
- case RoleId.Cleaner:
- Cleaner.cleaner = player;
- break;
- case RoleId.Warlock:
- Warlock.warlock = player;
- break;
- case RoleId.SecurityGuard:
- SecurityGuard.securityGuard = player;
- break;
- case RoleId.Arsonist:
- Arsonist.arsonist = player;
- break;
- case RoleId.Guesser:
- Guesser.guesser = player;
- break;
- case RoleId.BountyHunter:
- BountyHunter.bountyHunter = player;
- break;
- }
- }
- }
-
- public static void versionHandshake(int major, int minor, int build, int revision, Guid guid, int clientId) {
- System.Version ver;
- if (revision < 0)
- ver = new System.Version(major, minor, build);
- else
- ver = new System.Version(major, minor, build, revision);
-
- GameStartManagerPatch.playerVersions[clientId] = new GameStartManagerPatch.PlayerVersion(ver, guid);
- }
-
- public static void useUncheckedVent(int ventId, byte playerId, byte isEnter) {
- PlayerControl player = Helpers.playerById(playerId);
- if (player == null) return;
- // Fill dummy MessageReader and call MyPhysics.HandleRpc as the corountines cannot be accessed
- MessageReader reader = new MessageReader();
- byte[] bytes = BitConverter.GetBytes(ventId);
- if (!BitConverter.IsLittleEndian)
- Array.Reverse(bytes);
- reader.Buffer = bytes;
- reader.Length = bytes.Length;
-
- JackInTheBox.startAnimation(ventId);
- player.MyPhysics.HandleRpc(isEnter != 0 ? (byte)19 : (byte)20, reader);
- }
-
- public static void uncheckedMurderPlayer(byte sourceId, byte targetId) {
- PlayerControl source = Helpers.playerById(sourceId);
- PlayerControl target = Helpers.playerById(targetId);
- if (source != null && target != null) source.MurderPlayer(target);
- }
-
- // Role functionality
-
- public static void engineerFixLights() {
- SwitchSystem switchSystem = ShipStatus.Instance.Systems[SystemTypes.Electrical].Cast<SwitchSystem>();
- switchSystem.ActualSwitches = switchSystem.ExpectedSwitches;
- }
-
- public static void engineerUsedRepair() {
- Engineer.usedRepair = true;
- }
-
- public static void cleanBody(byte playerId) {
- DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
- for (int i = 0; i < array.Length; i++) {
- if (GameData.Instance.GetPlayerById(array[i].ParentId).PlayerId == playerId)
- UnityEngine.Object.Destroy(array[i].gameObject);
- }
- }
-
- public static void sheriffKill(byte targetId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- {
- if (player.PlayerId == targetId)
- {
- Sheriff.sheriff.MurderPlayer(player);
- return;
- }
- }
- }
-
- public static void timeMasterRewindTime() {
- TimeMaster.shieldActive = false; // Shield is no longer active when rewinding
- if(TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer) {
- resetTimeMasterButton();
- }
- HudManager.Instance.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f);
- HudManager.Instance.FullScreen.enabled = true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.rewindTime / 2, new Action<float>((p) => {
- if (p == 1f) HudManager.Instance.FullScreen.enabled = false;
- })));
-
- if (TimeMaster.timeMaster == null || PlayerControl.LocalPlayer == TimeMaster.timeMaster) return; // Time Master himself does not rewind
-
- TimeMaster.isRewinding = true;
-
- if (MapBehaviour.Instance)
- MapBehaviour.Instance.Close();
- if (Minigame.Instance)
- Minigame.Instance.ForceClose();
- PlayerControl.LocalPlayer.moveable = false;
- }
-
- public static void timeMasterShield() {
- TimeMaster.shieldActive = true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.shieldDuration, new Action<float>((p) => {
- if (p == 1f) TimeMaster.shieldActive = false;
- })));
- }
-
- public static void medicSetShielded(byte shieldedId) {
- Medic.usedShield = true;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- if (player.PlayerId == shieldedId)
- Medic.shielded = player;
- }
-
- public static void shieldedMurderAttempt() {
- if (Medic.shielded != null && Medic.shielded == PlayerControl.LocalPlayer && Medic.showAttemptToShielded && HudManager.Instance?.FullScreen != null) {
- HudManager.Instance.FullScreen.enabled = true;
- HudManager.Instance.StartCoroutine(Effects.Lerp(0.5f, new Action<float>((p) => {
- var renderer = HudManager.Instance.FullScreen;
- Color c = Palette.ImpostorRed;
- if (p < 0.5) {
- if (renderer != null)
- renderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01(p * 2 * 0.75f));
- } else {
- if (renderer != null)
- renderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01((1-p) * 2 * 0.75f));
- }
- if (p == 1f && renderer != null) renderer.enabled = false;
- })));
- }
- }
-
- public static void shifterShift(byte targetId) {
- PlayerControl oldShifter = Shifter.shifter;
- PlayerControl player = Helpers.playerById(targetId);
- if (player == null || oldShifter == null) return;
-
- Shifter.futureShift = null;
- Shifter.clearAndReload();
-
- // Suicide (exile) when impostor or impostor variants
- if (player.Data.IsImpostor || player == Jackal.jackal || player == Sidekick.sidekick || Jackal.formerJackals.Contains(player) || player == Jester.jester || player == Arsonist.arsonist) {
- oldShifter.Exiled();
- return;
- }
-
- 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;
- }
-
- // Shift role
- if (Mayor.mayor != null && Mayor.mayor == player)
- Mayor.mayor = oldShifter;
- if (Engineer.engineer != null && Engineer.engineer == player)
- Engineer.engineer = oldShifter;
- if (Sheriff.sheriff != null && Sheriff.sheriff == player)
- Sheriff.sheriff = oldShifter;
- if (Lighter.lighter != null && Lighter.lighter == player)
- Lighter.lighter = oldShifter;
- if (Detective.detective != null && Detective.detective == player)
- Detective.detective = oldShifter;
- if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == player)
- TimeMaster.timeMaster = oldShifter;
- if (Medic.medic != null && Medic.medic == player)
- Medic.medic = oldShifter;
- if (Swapper.swapper != null && Swapper.swapper == player)
- Swapper.swapper = oldShifter;
- if (Seer.seer != null && Seer.seer == player)
- Seer.seer = oldShifter;
- if (Hacker.hacker != null && Hacker.hacker == player)
- Hacker.hacker = oldShifter;
- if (Mini.mini != null && Mini.mini == player)
- Mini.mini = oldShifter;
- if (Tracker.tracker != null && Tracker.tracker == player)
- Tracker.tracker = oldShifter;
- if (Snitch.snitch != null && Snitch.snitch == player)
- Snitch.snitch = oldShifter;
- if (Spy.spy != null && Spy.spy == player)
- Spy.spy = oldShifter;
- if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player)
- SecurityGuard.securityGuard = oldShifter;
- if (Guesser.guesser != null && Guesser.guesser == player)
- Guesser.guesser = oldShifter;
-
- // Set cooldowns to max for both players
- if (PlayerControl.LocalPlayer == oldShifter || PlayerControl.LocalPlayer == player)
- CustomButton.ResetAllCooldowns();
- }
-
- public static void swapperSwap(byte playerId1, byte playerId2) {
- if (MeetingHud.Instance) {
- Swapper.playerId1 = playerId1;
- Swapper.playerId2 = playerId2;
- }
- }
-
- public static void morphlingMorph(byte playerId) {
- PlayerControl target = Helpers.playerById(playerId);
- if (Morphling.morphling == null || target == null) return;
-
- Morphling.morphTimer = Morphling.duration;
- Morphling.morphTarget = target;
- }
-
- public static void camouflagerCamouflage() {
- if (Camouflager.camouflager == null) return;
-
- Camouflager.camouflageTimer = Camouflager.duration;
- }
-
- public static void vampireSetBitten(byte targetId, byte reset) {
- if (reset != 0) {
- Vampire.bitten = null;
- return;
- }
-
- if (Vampire.vampire == null) return;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- if (player.PlayerId == targetId && !player.Data.IsDead) {
- Vampire.bitten = player;
- }
- }
- }
-
- public static void vampireTryKill() {
- if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead) {
- Vampire.vampire.MurderPlayer(Vampire.bitten);
- }
- Vampire.bitten = null;
- }
-
- public static void placeGarlic(byte[] buff) {
- Vector3 position = Vector3.zero;
- position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
- position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
- new Garlic(position);
- }
-
- public static void trackerUsedTracker(byte targetId) {
- Tracker.usedTracker = true;
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- if (player.PlayerId == targetId)
- Tracker.tracked = player;
- }
-
- public static void jackalKill(byte targetId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- {
- if (player.PlayerId == targetId)
- {
- Jackal.jackal.MurderPlayer(player);
- return;
- }
- }
- }
-
- public static void sidekickKill(byte targetId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- {
- if (player.PlayerId == targetId)
- {
- Sidekick.sidekick.MurderPlayer(player);
- return;
- }
- }
- }
-
- public static void jackalCreatesSidekick(byte targetId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- {
- if (player.PlayerId == targetId)
- {
- if (!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) {
- Jackal.fakeSidekick = player;
- } else {
- player.RemoveInfected();
- erasePlayerRoles(player.PlayerId, true);
- Sidekick.sidekick = player;
- }
- Jackal.canCreateSidekick = false;
- return;
- }
- }
- }
-
- public static void sidekickPromotes() {
- Jackal.removeCurrentJackal();
- Jackal.jackal = Sidekick.sidekick;
- Jackal.canCreateSidekick = Jackal.jackalPromotedFromSidekickCanCreateSidekick;
- Sidekick.clearAndReload();
- return;
- }
-
- public static void erasePlayerRoles(byte playerId, bool ignoreLovers = false) {
- PlayerControl player = Helpers.playerById(playerId);
- if (player == null) return;
-
- // Crewmate roles
- if (player == Mayor.mayor) Mayor.clearAndReload();
- if (player == Engineer.engineer) Engineer.clearAndReload();
- if (player == Sheriff.sheriff) Sheriff.clearAndReload();
- if (player == Lighter.lighter) Lighter.clearAndReload();
- if (player == Detective.detective) Detective.clearAndReload();
- if (player == TimeMaster.timeMaster) TimeMaster.clearAndReload();
- if (player == Medic.medic) Medic.clearAndReload();
- if (player == Shifter.shifter) Shifter.clearAndReload();
- if (player == Seer.seer) Seer.clearAndReload();
- if (player == Hacker.hacker) Hacker.clearAndReload();
- if (player == Mini.mini) Mini.clearAndReload();
- if (player == Tracker.tracker) Tracker.clearAndReload();
- if (player == Snitch.snitch) Snitch.clearAndReload();
- if (player == Swapper.swapper) Swapper.clearAndReload();
- if (player == Spy.spy) Spy.clearAndReload();
- if (player == SecurityGuard.securityGuard) SecurityGuard.clearAndReload();
-
- // Impostor roles
- if (player == Morphling.morphling) Morphling.clearAndReload();
- if (player == Camouflager.camouflager) Camouflager.clearAndReload();
- if (player == Godfather.godfather) Godfather.clearAndReload();
- if (player == Mafioso.mafioso) Mafioso.clearAndReload();
- if (player == Janitor.janitor) Janitor.clearAndReload();
- if (player == Vampire.vampire) Vampire.clearAndReload();
- if (player == Eraser.eraser) Eraser.clearAndReload();
- if (player == Trickster.trickster) Trickster.clearAndReload();
- if (player == Cleaner.cleaner) Cleaner.clearAndReload();
- if (player == Warlock.warlock) Warlock.clearAndReload();
-
- // Other roles
- if (player == Jester.jester) Jester.clearAndReload();
- if (player == Arsonist.arsonist) Arsonist.clearAndReload();
- if (player == Guesser.guesser) Guesser.clearAndReload();
- if (!ignoreLovers && (player == Lovers.lover1 || player == Lovers.lover2)) { // The whole Lover couple is being erased
- Lovers.clearAndReload();
- }
- if (player == Jackal.jackal) { // Promote Sidekick and hence override the the Jackal or erase Jackal
- if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead) {
- RPCProcedure.sidekickPromotes();
- } else {
- Jackal.clearAndReload();
- }
- }
- 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);
- }
- }
-
- public static void setFutureShifted(byte playerId) {
- Shifter.futureShift = Helpers.playerById(playerId);
- }
-
- public static void placeJackInTheBox(byte[] buff) {
- Vector3 position = Vector3.zero;
- position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
- position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
- new JackInTheBox(position);
- }
-
- public static void lightsOut() {
- Trickster.lightsOutTimer = Trickster.lightsOutDuration;
- // If the local player is impostor indicate lights out
- if(PlayerControl.LocalPlayer.Data.IsImpostor) {
- new CustomMessage("Lights are out", Trickster.lightsOutDuration);
- }
- }
-
- public static void warlockCurseKill(byte targetId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- if (player.PlayerId == targetId) {
- Warlock.curseKillTarget = player;
- Warlock.warlock.MurderPlayer(player);
- return;
- }
- }
- }
-
- public static void placeCamera(byte[] buff) {
- var referenceCamera = UnityEngine.Object.FindObjectOfType<SurvCamera>();
- if (referenceCamera == null) return; // Mira HQ
-
- SecurityGuard.remainingScrews -= SecurityGuard.camPrice;
- SecurityGuard.placedCameras++;
-
- Vector3 position = Vector3.zero;
- position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
- position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
-
- var camera = UnityEngine.Object.Instantiate<SurvCamera>(referenceCamera);
- camera.transform.position = new Vector3(position.x, position.y, referenceCamera.transform.position.z - 1f);
- camera.CamName = $"Security Camera {SecurityGuard.placedCameras}";
- camera.Offset = new Vector3(0f, 0f, camera.Offset.z);
- if (PlayerControl.GameOptions.MapId == 2 || PlayerControl.GameOptions.MapId == 4) camera.transform.localRotation = new Quaternion(0, 0, 1, 1); // Polus and Airship
-
- if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) {
- camera.gameObject.SetActive(true);
- camera.gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 0.5f);
- } else {
- camera.gameObject.SetActive(false);
- }
- MapOptions.camerasToAdd.Add(camera);
- }
-
- public static void sealVent(int ventId) {
- Vent vent = ShipStatus.Instance.AllVents.FirstOrDefault((x) => x != null && x.Id == ventId);
- if (vent == null) return;
-
- SecurityGuard.remainingScrews -= SecurityGuard.ventPrice;
- if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) {
- PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
- animator?.Stop();
- vent.EnterVentAnim = vent.ExitVentAnim = null;
- vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
- vent.myRend.color = new Color(1f, 1f, 1f, 0.5f);
- vent.name = "FutureSealedVent_" + vent.name;
- }
-
- MapOptions.ventsToSeal.Add(vent);
- }
-
- public static void arsonistWin() {
- Arsonist.triggerArsonistWin = true;
- }
-
- public static void guesserShoot(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.TargetPlayerId == partnerId) {
- pva.SetDead(pva.DidReport, true);
- pva.Overlay.gameObject.SetActive(true);
- }
- }
- if (AmongUsClient.Instance.AmHost)
- MeetingHud.Instance.CheckForEndVoting();
- }
- 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
- {
- static void Postfix([HarmonyArgument(0)]byte callId, [HarmonyArgument(1)]MessageReader reader)
- {
- byte packetId = callId;
- switch (packetId) {
-
- // Main Controls
-
- case (byte)CustomRPC.ResetVaribles:
- RPCProcedure.resetVariables();
- break;
- case (byte)CustomRPC.ShareOptionSelection:
- uint id = reader.ReadPackedUInt32();
- uint selection = reader.ReadPackedUInt32();
- RPCProcedure.shareOptionSelection(id, selection);
- break;
- case (byte)CustomRPC.ForceEnd:
- RPCProcedure.forceEnd();
- break;
- case (byte)CustomRPC.SetRole:
- byte roleId = reader.ReadByte();
- byte playerId = reader.ReadByte();
- byte flag = reader.ReadByte();
- RPCProcedure.setRole(roleId, playerId, flag);
- break;
- case (byte)CustomRPC.VersionHandshake:
- byte major = reader.ReadByte();
- byte minor = reader.ReadByte();
- byte patch = reader.ReadByte();
- int versionOwnerId = reader.ReadPackedInt32();
- byte revision = 0xFF;
- Guid guid;
- if (reader.Length - reader.Position >= 17) { // enough bytes left to read
- revision = reader.ReadByte();
- // GUID
- byte[] gbytes = reader.ReadBytes(16);
- guid = new Guid(gbytes);
- } else {
- guid = new Guid(new byte[16]);
- }
- RPCProcedure.versionHandshake(major, minor, patch, revision == 0xFF ? -1 : revision, guid, versionOwnerId);
- break;
- case (byte)CustomRPC.UseUncheckedVent:
- int ventId = reader.ReadPackedInt32();
- byte ventingPlayer = reader.ReadByte();
- byte isEnter = reader.ReadByte();
- RPCProcedure.useUncheckedVent(ventId, ventingPlayer, isEnter);
- break;
- case (byte)CustomRPC.UncheckedMurderPlayer:
- byte source = reader.ReadByte();
- byte target = reader.ReadByte();
- RPCProcedure.uncheckedMurderPlayer(source, target);
- break;
-
- // Role functionality
-
- case (byte)CustomRPC.EngineerFixLights:
- RPCProcedure.engineerFixLights();
- break;
- case (byte)CustomRPC.EngineerUsedRepair:
- RPCProcedure.engineerUsedRepair();
- break;
- case (byte)CustomRPC.CleanBody:
- RPCProcedure.cleanBody(reader.ReadByte());
- break;
- case (byte)CustomRPC.SheriffKill:
- RPCProcedure.sheriffKill(reader.ReadByte());
- break;
- case (byte)CustomRPC.TimeMasterRewindTime:
- RPCProcedure.timeMasterRewindTime();
- break;
- case (byte)CustomRPC.TimeMasterShield:
- RPCProcedure.timeMasterShield();
- break;
- case (byte)CustomRPC.MedicSetShielded:
- RPCProcedure.medicSetShielded(reader.ReadByte());
- break;
- case (byte)CustomRPC.ShieldedMurderAttempt:
- RPCProcedure.shieldedMurderAttempt();
- break;
- case (byte)CustomRPC.ShifterShift:
- RPCProcedure.shifterShift(reader.ReadByte());
- break;
- case (byte)CustomRPC.SwapperSwap:
- byte playerId1 = reader.ReadByte();
- byte playerId2 = reader.ReadByte();
- RPCProcedure.swapperSwap(playerId1, playerId2);
- break;
- case (byte)CustomRPC.MorphlingMorph:
- RPCProcedure.morphlingMorph(reader.ReadByte());
- break;
- case (byte)CustomRPC.CamouflagerCamouflage:
- RPCProcedure.camouflagerCamouflage();
- break;
- case (byte)CustomRPC.VampireSetBitten:
- byte bittenId = reader.ReadByte();
- byte reset = reader.ReadByte();
- RPCProcedure.vampireSetBitten(bittenId, reset);
- break;
- case (byte)CustomRPC.VampireTryKill:
- RPCProcedure.vampireTryKill();
- break;
- case (byte)CustomRPC.PlaceGarlic:
- RPCProcedure.placeGarlic(reader.ReadBytesAndSize());
- break;
- case (byte)CustomRPC.TrackerUsedTracker:
- RPCProcedure.trackerUsedTracker(reader.ReadByte());
- break;
- case (byte)CustomRPC.JackalKill:
- RPCProcedure.jackalKill(reader.ReadByte());
- break;
- case (byte)CustomRPC.SidekickKill:
- RPCProcedure.sidekickKill(reader.ReadByte());
- break;
- case (byte)CustomRPC.JackalCreatesSidekick:
- RPCProcedure.jackalCreatesSidekick(reader.ReadByte());
- break;
- case (byte)CustomRPC.SidekickPromotes:
- RPCProcedure.sidekickPromotes();
- break;
- case (byte)CustomRPC.ErasePlayerRoles:
- RPCProcedure.erasePlayerRoles(reader.ReadByte());
- break;
- case (byte)CustomRPC.SetFutureErased:
- RPCProcedure.setFutureErased(reader.ReadByte());
- break;
- case (byte)CustomRPC.SetFutureShifted:
- RPCProcedure.setFutureShifted(reader.ReadByte());
- break;
- case (byte)CustomRPC.PlaceJackInTheBox:
- RPCProcedure.placeJackInTheBox(reader.ReadBytesAndSize());
- break;
- case (byte)CustomRPC.LightsOut:
- RPCProcedure.lightsOut();
- break;
- case (byte)CustomRPC.WarlockCurseKill:
- RPCProcedure.warlockCurseKill(reader.ReadByte());
- break;
- case (byte)CustomRPC.PlaceCamera:
- RPCProcedure.placeCamera(reader.ReadBytesAndSize());
- break;
- case (byte)CustomRPC.SealVent:
- RPCProcedure.sealVent(reader.ReadPackedInt32());
- break;
- case (byte)CustomRPC.ArsonistWin:
- RPCProcedure.arsonistWin();
- break;
- case (byte)CustomRPC.GuesserShoot:
- RPCProcedure.guesserShoot(reader.ReadByte());
- break;
- }
- }
- }
-}
+++ /dev/null
-// Adapted from https://github.com/MoltenMods/Unify
-/*
-MIT License
-
-Copyright (c) 2021 Daemon
-
-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 HarmonyLib;
-using UnityEngine;
-using UnityEngine.UI;
-using System;
-using UnityEngine.Events;
-
-namespace TheOtherRoles {
- [HarmonyPatch(typeof(RegionMenu), nameof(RegionMenu.Open))]
- public static class RegionMenuOpenPatch
- {
- private static TextBoxTMP ipField;
- private static TextBoxTMP portField;
-
- public static void Postfix(RegionMenu __instance)
- {
- var template = DestroyableSingleton<JoinGameButton>.Instance;
-
- if (ipField == null || ipField.gameObject == null) {
- ipField = UnityEngine.Object.Instantiate(template.GameIdText, __instance.transform);
- ipField.gameObject.name = "IpTextBox";
- UnityEngine.Object.DestroyImmediate(ipField.transform.FindChild("arrowEnter").gameObject);
-
- ipField.transform.localPosition = new Vector3(0, -1f, -100f);
- ipField.characterLimit = 30;
- ipField.AllowSymbols = true;
- ipField.ForceUppercase = false;
- ipField.SetText(TheOtherRolesPlugin.Ip.Value);
- __instance.StartCoroutine(Effects.Lerp(0.1f, new Action<float>((p) => {
- ipField.outputText.SetText(TheOtherRolesPlugin.Ip.Value);
- ipField.SetText(TheOtherRolesPlugin.Ip.Value);
- })));
-
- ipField.ClearOnFocus = false;
- ipField.OnEnter = ipField.OnChange = new Button.ButtonClickedEvent();
- ipField.OnFocusLost = new Button.ButtonClickedEvent();
- ipField.OnChange.AddListener((UnityAction)onEnterOrIpChange);
- ipField.OnFocusLost.AddListener((UnityAction)onFocusLost);
-
- void onEnterOrIpChange() {
- TheOtherRolesPlugin.Ip.Value = ipField.text;
- }
-
- void onFocusLost() {
- TheOtherRolesPlugin.UpdateRegions();
- __instance.ChooseOption(ServerManager.DefaultRegions[ServerManager.DefaultRegions.Length - 1]);
- }
- }
- if (portField == null || portField.gameObject == null) {
- portField = UnityEngine.Object.Instantiate(template.GameIdText, __instance.transform);
- portField.gameObject.name = "PortTextBox";
- UnityEngine.Object.DestroyImmediate(portField.transform.FindChild("arrowEnter").gameObject);
-
- portField.transform.localPosition = new Vector3(0, -1.75f, -100f);
- portField.characterLimit = 5;
- portField.SetText(TheOtherRolesPlugin.Port.Value.ToString());
- __instance.StartCoroutine(Effects.Lerp(0.1f, new Action<float>((p) => {
- portField.outputText.SetText(TheOtherRolesPlugin.Port.Value.ToString());
- portField.SetText(TheOtherRolesPlugin.Port.Value.ToString());
- })));
-
-
- portField.ClearOnFocus = false;
- portField.OnEnter = portField.OnChange = new Button.ButtonClickedEvent();
- portField.OnFocusLost = new Button.ButtonClickedEvent();
- portField.OnChange.AddListener((UnityAction)onEnterOrPortFieldChange);
- portField.OnFocusLost.AddListener((UnityAction)onFocusLost);
-
- void onEnterOrPortFieldChange() {
- ushort port = 0;
- if (ushort.TryParse(portField.text, out port)) {
- TheOtherRolesPlugin.Port.Value = port;
- portField.outputText.color = Color.white;
- } else {
- portField.outputText.color = Color.red;
- }
- }
-
- void onFocusLost() {
- TheOtherRolesPlugin.UpdateRegions();
- __instance.ChooseOption(ServerManager.DefaultRegions[ServerManager.DefaultRegions.Length - 1]);
- }
- }
- }
- }
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using Hazel;
-using System.Collections.Generic;
-using System.Linq;
-using UnhollowerBaseLib;
-using UnityEngine;
-using System;
-using static TheOtherRoles.TheOtherRoles;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetInfected))]
- class SetInfectedPatch
- {
-
- public static void Postfix([HarmonyArgument(0)]Il2CppReferenceArray<GameData.PlayerInfo> infected)
- {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.resetVariables();
-
- if (!DestroyableSingleton<TutorialManager>.InstanceExists) // Don't assign Roles in Tutorial
- assignRoles();
- }
-
- 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
- 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
- }
-
- private static RoleAssignmentData getRoleAssignmentData() {
- // Get the players that we want to assign the roles to. Crewmate and Neutral roles are assigned to natural crewmates. Impostor roles to impostors.
- List<PlayerControl> crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
- crewmates.RemoveAll(x => x.Data.IsImpostor);
- List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
- impostors.RemoveAll(x => !x.Data.IsImpostor);
-
- var crewmateMin = CustomOptionHolder.crewmateRolesCountMin.getSelection();
- var crewmateMax = CustomOptionHolder.crewmateRolesCountMax.getSelection();
- var neutralMin = CustomOptionHolder.neutralRolesCountMin.getSelection();
- var neutralMax = CustomOptionHolder.neutralRolesCountMax.getSelection();
- var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection();
- var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection();
-
- // Make sure min is less or equal to max
- if (crewmateMin > crewmateMax) crewmateMin = crewmateMax;
- if (neutralMin > neutralMax) neutralMin = neutralMax;
- if (impostorMin > impostorMax) impostorMin = impostorMax;
-
- // Get the maximum allowed count of each role type based on the minimum and maximum option
- int crewCountSettings = rnd.Next(crewmateMin, crewmateMax + 1);
- int neutralCountSettings = rnd.Next(neutralMin, neutralMax + 1);
- int impCountSettings = rnd.Next(impostorMin, impostorMax + 1);
-
- // Potentially lower the actual maximum to the assignable players
- int maxCrewmateRoles = Mathf.Min(crewmates.Count, crewCountSettings);
- int maxNeutralRoles = Mathf.Min(crewmates.Count, neutralCountSettings);
- int maxImpostorRoles = Mathf.Min(impostors.Count, impCountSettings);
-
- // Fill in the lists with the roles that should be assigned to players. Note that the special roles (like Mafia or Lovers) are NOT included in these lists
- Dictionary<byte, int> impSettings = new Dictionary<byte, int>();
- Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
- Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
-
- impSettings.Add((byte)RoleId.Morphling, CustomOptionHolder.morphlingSpawnRate.getSelection());
- impSettings.Add((byte)RoleId.Camouflager, CustomOptionHolder.camouflagerSpawnRate.getSelection());
- impSettings.Add((byte)RoleId.Vampire, CustomOptionHolder.vampireSpawnRate.getSelection());
- impSettings.Add((byte)RoleId.Eraser, CustomOptionHolder.eraserSpawnRate.getSelection());
- 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());
- neutralSettings.Add((byte)RoleId.Jackal, CustomOptionHolder.jackalSpawnRate.getSelection());
-
- crewSettings.Add((byte)RoleId.Mayor, CustomOptionHolder.mayorSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Engineer, CustomOptionHolder.engineerSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Lighter, CustomOptionHolder.lighterSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Detective, CustomOptionHolder.detectiveSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.TimeMaster, CustomOptionHolder.timeMasterSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Medic, CustomOptionHolder.medicSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Shifter, CustomOptionHolder.shifterSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Swapper,CustomOptionHolder.swapperSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Seer, CustomOptionHolder.seerSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Hacker, CustomOptionHolder.hackerSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Tracker, CustomOptionHolder.trackerSpawnRate.getSelection());
- crewSettings.Add((byte)RoleId.Snitch, CustomOptionHolder.snitchSpawnRate.getSelection());
- if (impostors.Count > 1) {
- // Only add Spy if more than 1 impostor as the spy role is otherwise useless
- crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection());
- }
- crewSettings.Add((byte)RoleId.SecurityGuard, CustomOptionHolder.securityGuardSpawnRate.getSelection());
-
- return new RoleAssignmentData {
- crewmates = crewmates,
- impostors = impostors,
- crewSettings = crewSettings,
- neutralSettings = neutralSettings,
- impSettings = impSettings,
- maxCrewmateRoles = maxCrewmateRoles,
- maxNeutralRoles = maxNeutralRoles,
- maxImpostorRoles = maxImpostorRoles
- };
- }
-
- private static void assignSpecialRoles(RoleAssignmentData data) {
- // Assign Lovers
- if (rnd.Next(1, 101) <= CustomOptionHolder.loversSpawnRate.getSelection() * 10) {
- bool isOnlyRole = !CustomOptionHolder.loversCanHaveAnotherRole.getBool();
- if (data.impostors.Count > 0 && data.crewmates.Count > 0 && (!isOnlyRole || (data.maxCrewmateRoles > 0 && data.maxImpostorRoles > 0)) && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10) {
- setRoleToRandomPlayer((byte)RoleId.Lover, data.impostors, 0, isOnlyRole);
- setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 1, isOnlyRole);
- if (isOnlyRole) {
- data.maxCrewmateRoles--;
- data.maxImpostorRoles--;
- }
- } else if (data.crewmates.Count >= 2 && (isOnlyRole || data.maxCrewmateRoles >= 2)) {
- byte firstLoverId = setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 0, isOnlyRole);
- if (isOnlyRole) {
- setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 1);
- data.maxCrewmateRoles -= 2;
- } else {
- var crewmatesWithoutFirstLover = data.crewmates.ToList();
- crewmatesWithoutFirstLover.RemoveAll(p => p.PlayerId == firstLoverId);
- setRoleToRandomPlayer((byte)RoleId.Lover, crewmatesWithoutFirstLover, 1, false);
- }
- }
- }
-
- // Assign Mafia
- if (data.impostors.Count >= 3 && data.maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10)) {
- setRoleToRandomPlayer((byte)RoleId.Godfather, data.impostors);
- setRoleToRandomPlayer((byte)RoleId.Janitor, data.impostors);
- setRoleToRandomPlayer((byte)RoleId.Mafioso, data.impostors);
- data.maxImpostorRoles -= 3;
- }
- }
-
- 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 (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());
- }
- }
-
- private static void assignEnsuredRoles(RoleAssignmentData data) {
- // Get all roles where the chance to occur is set to 100%
- List<byte> ensuredCrewmateRoles = data.crewSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
- List<byte> ensuredNeutralRoles = data.neutralSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
- List<byte> ensuredImpostorRoles = data.impSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
-
- // Assign roles until we run out of either players we can assign roles to or run out of roles we can assign to players
- while (
- (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0) ||
- (data.crewmates.Count > 0 && (
- (data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0) ||
- (data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0)
- ))) {
-
- Dictionary<RoleType, List<byte>> rolesToAssign = new Dictionary<RoleType, List<byte>>();
- if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0) rolesToAssign.Add(RoleType.Crewmate, ensuredCrewmateRoles);
- if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0) rolesToAssign.Add(RoleType.Neutral, ensuredNeutralRoles);
- if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0) rolesToAssign.Add(RoleType.Impostor, ensuredImpostorRoles);
-
- // Randomly select a pool of roles to assign a role from next (Crewmate role, Neutral role or Impostor role)
- // then select one of the roles from the selected pool to a player
- // and remove the role (and any potentially blocked role pairings) from the pool(s)
- var roleType = rolesToAssign.Keys.ElementAt(rnd.Next(0, rolesToAssign.Keys.Count()));
- var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors;
- var index = rnd.Next(0, rolesToAssign[roleType].Count);
- var roleId = rolesToAssign[roleType][index];
- setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
- rolesToAssign[roleType].RemoveAt(index);
-
- if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) {
- foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) {
- // Set chance for the blocked roles to 0 for chances less than 100%
- if (data.impSettings.ContainsKey(blockedRoleId)) data.impSettings[blockedRoleId] = 0;
- if (data.neutralSettings.ContainsKey(blockedRoleId)) data.neutralSettings[blockedRoleId] = 0;
- if (data.crewSettings.ContainsKey(blockedRoleId)) data.crewSettings[blockedRoleId] = 0;
- // Remove blocked roles even if the chance was 100%
- foreach(var ensuredRolesList in rolesToAssign.Values) {
- ensuredRolesList.RemoveAll(x => x == blockedRoleId);
- }
- }
- }
-
- // Adjust the role limit
- switch (roleType) {
- case RoleType.Crewmate: data.maxCrewmateRoles--; break;
- case RoleType.Neutral: data.maxNeutralRoles--;break;
- case RoleType.Impostor: data.maxImpostorRoles--;break;
- }
- }
- }
-
-
- private static void assignChanceRoles(RoleAssignmentData data) {
- // Get all roles where the chance to occur is set grater than 0% but not 100% and build a ticket pool based on their weight
- List<byte> crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
- List<byte> neutralTickets = data.neutralSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
- List<byte> impostorTickets = data.impSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
-
- // Assign roles until we run out of either players we can assign roles to or run out of roles we can assign to players
- while (
- (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0) ||
- (data.crewmates.Count > 0 && (
- (data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) ||
- (data.maxNeutralRoles > 0 && neutralTickets.Count > 0)
- ))) {
-
- Dictionary<RoleType, List<byte>> rolesToAssign = new Dictionary<RoleType, List<byte>>();
- if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) rolesToAssign.Add(RoleType.Crewmate, crewmateTickets);
- if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && neutralTickets.Count > 0) rolesToAssign.Add(RoleType.Neutral, neutralTickets);
- if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0) rolesToAssign.Add(RoleType.Impostor, impostorTickets);
-
- // Randomly select a pool of role tickets to assign a role from next (Crewmate role, Neutral role or Impostor role)
- // then select one of the roles from the selected pool to a player
- // and remove all tickets of this role (and any potentially blocked role pairings) from the pool(s)
- var roleType = rolesToAssign.Keys.ElementAt(rnd.Next(0, rolesToAssign.Keys.Count()));
- var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors;
- var index = rnd.Next(0, rolesToAssign[roleType].Count);
- var roleId = rolesToAssign[roleType][index];
- setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
- rolesToAssign[roleType].RemoveAll(x => x == roleId);
-
- if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) {
- foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) {
- // Remove tickets of blocked roles from all pools
- crewmateTickets.RemoveAll(x => x == blockedRoleId);
- neutralTickets.RemoveAll(x => x == blockedRoleId);
- impostorTickets.RemoveAll(x => x == blockedRoleId);
- }
- }
-
- // Adjust the role limit
- switch (roleType) {
- case RoleType.Crewmate: data.maxCrewmateRoles--; break;
- case RoleType.Neutral: data.maxNeutralRoles--;break;
- case RoleType.Impostor: data.maxImpostorRoles--;break;
- }
- }
- }
-
- private static byte setRoleToRandomPlayer(byte roleId, List<PlayerControl> playerList, byte flag = 0, bool removePlayer = true) {
- var index = rnd.Next(0, playerList.Count);
- byte playerId = playerList[index].PlayerId;
- if (removePlayer) playerList.RemoveAt(index);
-
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRole, Hazel.SendOption.Reliable, -1);
- writer.Write(roleId);
- writer.Write(playerId);
- writer.Write(flag);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.setRole(roleId, playerId, flag);
- return playerId;
- }
-
-
-
- private class RoleAssignmentData {
- public List<PlayerControl> crewmates {get;set;}
- public List<PlayerControl> impostors {get;set;}
- public Dictionary<byte, int> impSettings = new Dictionary<byte, int>();
- public Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
- public Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
- public int maxCrewmateRoles {get;set;}
- public int maxNeutralRoles {get;set;}
- public int maxImpostorRoles {get;set;}
- }
-
- private enum RoleType {
- Crewmate = 0,
- Neutral = 1,
- Impostor = 2
- }
-
- }
-}
+++ /dev/null
-using HarmonyLib;
-using System.Linq;
-using System;
-using System.Collections.Generic;
-using static TheOtherRoles.TheOtherRoles;
-using UnityEngine;
-
-namespace TheOtherRoles
-{
- class RoleInfo {
- public Color color;
- public string name;
- public string introDescription;
- public string shortDescription;
- public RoleId roleId;
-
- RoleInfo(string name, Color color, string introDescription, string shortDescription, RoleId roleId) {
- this.color = color;
- this.name = name;
- this.introDescription = introDescription;
- this.shortDescription = shortDescription;
- this.roleId = roleId;
- }
-
- public static RoleInfo jester = new RoleInfo("Jester", Jester.color, "Get voted out", "Get voted out", RoleId.Jester);
- public static RoleInfo mayor = new RoleInfo("Mayor", Mayor.color, "Your vote counts twice", "Your vote counts twice", RoleId.Mayor);
- public static RoleInfo engineer = new RoleInfo("Engineer", Engineer.color, "Maintain important systems on the ship", "Repair the ship", RoleId.Engineer);
- public static RoleInfo sheriff = new RoleInfo("Sheriff", Sheriff.color, "Shoot the <color=#FF1919FF>Impostors</color>", "Shoot the Impostors", RoleId.Sheriff);
- public static RoleInfo lighter = new RoleInfo("Lighter", Lighter.color, "Your light never goes out", "Your light never goes out", RoleId.Lighter);
- public static RoleInfo godfather = new RoleInfo("Godfather", Godfather.color, "Kill all Crewmates", "Kill all Crewmates", RoleId.Godfather);
- public static RoleInfo mafioso = new RoleInfo("Mafioso", Mafioso.color, "Work with the <color=#FF1919FF>Mafia</color> to kill the Crewmates", "Kill all Crewmates", RoleId.Mafioso);
- public static RoleInfo janitor = new RoleInfo("Janitor", Janitor.color, "Work with the <color=#FF1919FF>Mafia</color> by hiding dead bodies", "Hide dead bodies", RoleId.Janitor);
- public static RoleInfo morphling = new RoleInfo("Morphling", Morphling.color, "Change your look to not get caught", "Change your look", RoleId.Morphling);
- public static RoleInfo camouflager = new RoleInfo("Camouflager", Camouflager.color, "Camouflage and kill the Crewmates", "Hide among others", RoleId.Camouflager);
- public static RoleInfo vampire = new RoleInfo("Vampire", Vampire.color, "Kill the Crewmates with your bites", "Bite your enemies", RoleId.Vampire);
- public static RoleInfo eraser = new RoleInfo("Eraser", Eraser.color, "Kill the Crewmates and erase their roles", "Erase the roles of your enemies", RoleId.Eraser);
- 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);
- public static RoleInfo shifter = new RoleInfo("Shifter", Shifter.color, "Shift your role", "Shift your role", RoleId.Shifter);
- public static RoleInfo swapper = new RoleInfo("Swapper", Swapper.color, "Swap votes to exile the <color=#FF1919FF>Impostors</color>", "Swap votes", RoleId.Swapper);
- public static RoleInfo seer = new RoleInfo("Seer", Seer.color, "You will see players die", "You will see players die", RoleId.Seer);
- public static RoleInfo hacker = new RoleInfo("Hacker", Hacker.color, "Hack systems to find the <color=#FF1919FF>Impostors</color>", "Hack to find the Impostors", RoleId.Hacker);
- public static RoleInfo niceMini = new RoleInfo("Nice Mini", Mini.color, "No one will harm you until you grow up", "No one will harm you", RoleId.Mini);
- public static RoleInfo evilMini = new RoleInfo("Evil Mini", Palette.ImpostorRed, "No one will harm you until you grow up", "No one will harm you", RoleId.Mini);
- public static RoleInfo tracker = new RoleInfo("Tracker", Tracker.color, "Track the <color=#FF1919FF>Impostors</color> down", "Track the Impostors down", RoleId.Tracker);
- public static RoleInfo snitch = new RoleInfo("Snitch", Snitch.color, "Finish your tasks to find the <color=#FF1919FF>Impostors</color>", "Finish your tasks", RoleId.Snitch);
- public static RoleInfo jackal = new RoleInfo("Jackal", Jackal.color, "Kill all Crewmates and <color=#FF1919FF>Impostors</color> to win", "Kill everyone", RoleId.Jackal);
- public static RoleInfo sidekick = new RoleInfo("Sidekick", Sidekick.color, "Help your Jackal to kill everyone", "Help your Jackal to kill everyone", RoleId.Sidekick);
- public static RoleInfo spy = new RoleInfo("Spy", Spy.color, "Confuse the <color=#FF1919FF>Impostors</color>", "Confuse the Impostors", RoleId.Spy);
- public static RoleInfo securityGuard = new RoleInfo("Security Guard", SecurityGuard.color, "Seal vents and place cameras", "Seal vents and place cameras", RoleId.SecurityGuard);
- public static RoleInfo arsonist = new RoleInfo("Arsonist", Arsonist.color, "Let them burn", "Let them burn", RoleId.Arsonist);
- public static RoleInfo goodGuesser = new RoleInfo("Nice Guesser", Guesser.color, "Guess and shoot", "Guess and shoot", RoleId.Guesser);
- public static RoleInfo badGuesser = new RoleInfo("Evil Guesser", Palette.ImpostorRed, "Guess and shoot", "Guess and shoot", RoleId.Guesser);
- public static RoleInfo impostor = new RoleInfo("Impostor", Palette.ImpostorRed, Helpers.cs(Palette.ImpostorRed, "Sabotage and kill everyone"), "Sabotage and kill everyone", RoleId.Impostor);
- public static RoleInfo crewmate = new RoleInfo("Crewmate", Color.white, "Find the Impostors", "Find the Impostors", RoleId.Crewmate);
- public static RoleInfo lover = new RoleInfo("Lover", Lovers.color, $"You are in love", $"You are in love", RoleId.Lover);
-
- public static List<RoleInfo> allRoleInfos = new List<RoleInfo>() {
- impostor,
- godfather,
- mafioso,
- janitor,
- morphling,
- camouflager,
- vampire,
- eraser,
- trickster,
- cleaner,
- warlock,
- bountyHunter,
- niceMini,
- evilMini,
- goodGuesser,
- badGuesser,
- lover,
- jester,
- arsonist,
- jackal,
- sidekick,
- crewmate,
- shifter,
- mayor,
- engineer,
- sheriff,
- lighter,
- detective,
- timeMaster,
- medic,
- swapper,
- seer,
- hacker,
- tracker,
- snitch,
- spy,
- securityGuard,
- bountyHunter
- };
-
- public static List<RoleInfo> getRoleInfoForPlayer(PlayerControl p) {
- List<RoleInfo> infos = new List<RoleInfo>();
- if (p == null) return infos;
-
- // Special roles
- if (p == Jester.jester) infos.Add(jester);
- if (p == Mayor.mayor) infos.Add(mayor);
- if (p == Engineer.engineer) infos.Add(engineer);
- if (p == Sheriff.sheriff) infos.Add(sheriff);
- if (p == Lighter.lighter) infos.Add(lighter);
- if (p == Godfather.godfather) infos.Add(godfather);
- if (p == Mafioso.mafioso) infos.Add(mafioso);
- if (p == Janitor.janitor) infos.Add(janitor);
- if (p == Morphling.morphling) infos.Add(morphling);
- if (p == Camouflager.camouflager) infos.Add(camouflager);
- if (p == Vampire.vampire) infos.Add(vampire);
- if (p == Eraser.eraser) infos.Add(eraser);
- if (p == Trickster.trickster) infos.Add(trickster);
- if (p == Cleaner.cleaner) infos.Add(cleaner);
- if (p == Warlock.warlock) infos.Add(warlock);
- if (p == Detective.detective) infos.Add(detective);
- if (p == TimeMaster.timeMaster) infos.Add(timeMaster);
- if (p == Medic.medic) infos.Add(medic);
- if (p == Shifter.shifter) infos.Add(shifter);
- if (p == Swapper.swapper) infos.Add(swapper);
- if (p == Seer.seer) infos.Add(seer);
- if (p == Hacker.hacker) infos.Add(hacker);
- if (p == Mini.mini) infos.Add(p.Data.IsImpostor ? evilMini : niceMini);
- if (p == Tracker.tracker) infos.Add(tracker);
- if (p == Snitch.snitch) infos.Add(snitch);
- if (p == Jackal.jackal || (Jackal.formerJackals != null && Jackal.formerJackals.Any(x => x.PlayerId == p.PlayerId))) infos.Add(jackal);
- if (p == Sidekick.sidekick) infos.Add(sidekick);
- if (p == Spy.spy) infos.Add(spy);
- 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
- if (infos.Count == 0 && !p.Data.IsImpostor) infos.Add(crewmate); // Just Crewmate
-
- // Modifier
- if (p == Lovers.lover1|| p == Lovers.lover2) infos.Add(lover);
-
- return infos;
- }
- }
-}
+++ /dev/null
-using HarmonyLib;
-using static TheOtherRoles.TheOtherRoles;
-using UnityEngine;
-
-namespace TheOtherRoles {
-
- [HarmonyPatch(typeof(ShipStatus))]
- public class ShipStatusPatch {
-
- [HarmonyPostfix]
- [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CalculateLightRadius))]
- public static bool Prefix(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo player) {
- ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.Electrical) ? __instance.Systems[SystemTypes.Electrical] : null;
- if (systemType == null) return true;
- SwitchSystem switchSystem = systemType.TryCast<SwitchSystem>();
- if (switchSystem == null) return true;
-
- float num = (float)switchSystem.Value / 255f;
-
- if (player == null || player.IsDead) // IsDead
- __result = __instance.MaxLightRadius;
- else if (player.IsImpostor
- || (Jackal.jackal != null && Jackal.jackal.PlayerId == player.PlayerId && Jackal.hasImpostorVision)
- || (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == player.PlayerId && Sidekick.hasImpostorVision)
- || (Spy.spy != null && Spy.spy.PlayerId == player.PlayerId && Spy.hasImpostorVision)) // Impostor, Jackal/Sidekick or Spy with Impostor vision
- __result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod;
- else if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId && Lighter.lighterTimer > 0f) // if player is Lighter and Lighter has his ability active
- __result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, num);
- else if (Trickster.trickster != null && Trickster.lightsOutTimer > 0f) {
- float lerpValue = 1f;
- if (Trickster.lightsOutDuration - Trickster.lightsOutTimer < 0.5f) lerpValue = Mathf.Clamp01((Trickster.lightsOutDuration - Trickster.lightsOutTimer) * 2);
- else if (Trickster.lightsOutTimer < 0.5) lerpValue = Mathf.Clamp01(Trickster.lightsOutTimer*2);
- __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, 1 - lerpValue) * PlayerControl.GameOptions.CrewLightMod; // Instant lights out? Maybe add a smooth transition?
- }
- else
- __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, num) * PlayerControl.GameOptions.CrewLightMod;
- return false;
- }
-
- [HarmonyPostfix]
- [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.IsGameOverDueToDeath))]
- public static void Postfix2(ShipStatus __instance, ref bool __result)
- {
- __result = false;
- }
-
- private static int originalNumCommonTasksOption = 0;
- private static int originalNumShortTasksOption = 0;
- private static int originalNumLongTasksOption = 0;
-
- [HarmonyPrefix]
- [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))]
- public static bool Prefix(ShipStatus __instance)
- {
- var commonTaskCount = __instance.CommonTasks.Count;
- var normalTaskCount = __instance.NormalTasks.Count;
- var longTaskCount = __instance.LongTasks.Count;
- originalNumCommonTasksOption = PlayerControl.GameOptions.NumCommonTasks;
- originalNumShortTasksOption = PlayerControl.GameOptions.NumShortTasks;
- originalNumLongTasksOption = PlayerControl.GameOptions.NumLongTasks;
- if(PlayerControl.GameOptions.NumCommonTasks > commonTaskCount) PlayerControl.GameOptions.NumCommonTasks = commonTaskCount;
- if(PlayerControl.GameOptions.NumShortTasks > normalTaskCount) PlayerControl.GameOptions.NumShortTasks = normalTaskCount;
- if(PlayerControl.GameOptions.NumLongTasks > longTaskCount) PlayerControl.GameOptions.NumLongTasks = longTaskCount;
- return true;
- }
-
- [HarmonyPostfix]
- [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))]
- public static void Postfix3(ShipStatus __instance)
- {
- // Restore original settings after the tasks have been selected
- PlayerControl.GameOptions.NumCommonTasks = originalNumCommonTasksOption;
- PlayerControl.GameOptions.NumShortTasks = originalNumShortTasksOption;
- PlayerControl.GameOptions.NumLongTasks = originalNumLongTasksOption;
- }
-
- }
-
-}
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using static TheOtherRoles.TheOtherRoles;
-using System.Collections;
-using System.Collections.Generic;
-using System;
-
-namespace TheOtherRoles {
- [HarmonyPatch]
- public static class TasksHandler {
-
- public static Tuple<int, int> taskInfo(GameData.PlayerInfo playerInfo) {
- int TotalTasks = 0;
- int CompletedTasks = 0;
- if (!playerInfo.Disconnected && playerInfo.Tasks != null &&
- playerInfo.Object &&
- (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) &&
- !playerInfo.IsImpostor &&
- !playerInfo.Object.hasFakeTasks()
- ) {
-
- for (int j = 0; j < playerInfo.Tasks.Count; j++) {
- TotalTasks++;
- if (playerInfo.Tasks[j].Complete) {
- CompletedTasks++;
- }
- }
- }
- return Tuple.Create(CompletedTasks, TotalTasks);
- }
-
- [HarmonyPatch(typeof(GameData), nameof(GameData.RecomputeTaskCounts))]
- private static class GameDataRecomputeTaskCountsPatch {
- private static bool Prefix(GameData __instance) {
- __instance.TotalTasks = 0;
- __instance.CompletedTasks = 0;
- for (int i = 0; i < __instance.AllPlayers.Count; i++) {
- GameData.PlayerInfo playerInfo = __instance.AllPlayers[i];
- if (playerInfo.Object && playerInfo.Object.hasAliveKillingLover())
- continue;
- var (playerCompleted, playerTotal) = taskInfo(playerInfo);
- __instance.TotalTasks += playerTotal;
- __instance.CompletedTasks += playerCompleted;
- }
- return false;
- }
- }
-
- }
-}
+++ /dev/null
-using System.Net;
-using System.Linq;
-using BepInEx;
-using BepInEx.Configuration;
-using BepInEx.IL2CPP;
-using HarmonyLib;
-using Hazel;
-using System;
-using System.Collections.Generic;
-using System.Collections;
-using System.IO;
-using UnityEngine;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch]
- public static class TheOtherRoles
- {
- public static System.Random rnd = new System.Random((int)DateTime.Now.Ticks);
-
- public static void clearAndReloadRoles() {
- Jester.clearAndReload();
- Mayor.clearAndReload();
- Engineer.clearAndReload();
- Sheriff.clearAndReload();
- Lighter.clearAndReload();
- Godfather.clearAndReload();
- Mafioso.clearAndReload();
- Janitor.clearAndReload();
- Detective.clearAndReload();
- TimeMaster.clearAndReload();
- Medic.clearAndReload();
- Shifter.clearAndReload();
- Swapper.clearAndReload();
- Lovers.clearAndReload();
- Seer.clearAndReload();
- Morphling.clearAndReload();
- Camouflager.clearAndReload();
- Hacker.clearAndReload();
- Mini.clearAndReload();
- Tracker.clearAndReload();
- Vampire.clearAndReload();
- Snitch.clearAndReload();
- Jackal.clearAndReload();
- Sidekick.clearAndReload();
- Eraser.clearAndReload();
- Spy.clearAndReload();
- Trickster.clearAndReload();
- Cleaner.clearAndReload();
- Warlock.clearAndReload();
- SecurityGuard.clearAndReload();
- Arsonist.clearAndReload();
- Guesser.clearAndReload();
- BountyHunter.clearAndReload();
- }
-
- public static class Jester {
- public static PlayerControl jester;
- public static Color color = new Color32(236, 98, 165, byte.MaxValue);
-
- public static bool triggerJesterWin = false;
- public static bool canCallEmergency = true;
- public static bool canSabotage = true;
-
- public static void clearAndReload() {
- jester = null;
- triggerJesterWin = false;
- canCallEmergency = CustomOptionHolder.jesterCanCallEmergency.getBool();
- canSabotage = CustomOptionHolder.jesterCanSabotage.getBool();
- }
- }
-
- public static class Mayor {
- public static PlayerControl mayor;
- public static Color color = new Color32(32, 77, 66, byte.MaxValue);
-
- public static void clearAndReload() {
- mayor = null;
- }
- }
-
- public static class Engineer {
- public static PlayerControl engineer;
- public static Color color = new Color32(0, 40, 245, byte.MaxValue);
- public static bool usedRepair;
- private static Sprite buttonSprite;
-
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.RepairButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- engineer = null;
- usedRepair = false;
- }
- }
-
- public static class Godfather {
- public static PlayerControl godfather;
- public static Color color = Palette.ImpostorRed;
-
- public static void clearAndReload() {
- godfather = null;
- }
- }
-
- public static class Mafioso {
- public static PlayerControl mafioso;
- public static Color color = Palette.ImpostorRed;
-
- public static void clearAndReload() {
- mafioso = null;
- }
- }
-
-
- public static class Janitor {
- public static PlayerControl janitor;
- public static Color color = Palette.ImpostorRed;
-
- public static float cooldown = 30f;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CleanButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- janitor = null;
- cooldown = CustomOptionHolder.janitorCooldown.getFloat();
- }
- }
-
- public static class Sheriff {
- public static PlayerControl sheriff;
- public static Color color = new Color32(248, 205, 70, byte.MaxValue);
-
- public static float cooldown = 30f;
- public static bool canKillNeutrals = false;
- public static bool spyCanDieToSheriff = false;
-
- public static PlayerControl currentTarget;
-
- public static void clearAndReload() {
- sheriff = null;
- currentTarget = null;
- cooldown = CustomOptionHolder.sheriffCooldown.getFloat();
- canKillNeutrals = CustomOptionHolder.sheriffCanKillNeutrals.getBool();
- spyCanDieToSheriff = CustomOptionHolder.spyCanDieToSheriff.getBool();
- }
- }
-
- public static class Lighter {
- public static PlayerControl lighter;
- public static Color color = new Color32(238, 229, 190, byte.MaxValue);
-
- public static float lighterModeLightsOnVision = 2f;
- public static float lighterModeLightsOffVision = 0.75f;
-
- public static float cooldown = 30f;
- public static float duration = 5f;
-
- public static float lighterTimer = 0f;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LighterButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- lighter = null;
- lighterTimer = 0f;
- cooldown = CustomOptionHolder.lighterCooldown.getFloat();
- duration = CustomOptionHolder.lighterDuration.getFloat();
- lighterModeLightsOnVision = CustomOptionHolder.lighterModeLightsOnVision.getFloat();
- lighterModeLightsOffVision = CustomOptionHolder.lighterModeLightsOffVision.getFloat();
- }
- }
-
- public static class Detective {
- public static PlayerControl detective;
- public static Color color = new Color32(45, 106, 165, byte.MaxValue);
-
- public static float footprintIntervall = 1f;
- public static float footprintDuration = 1f;
- public static bool anonymousFootprints = false;
- public static float reportNameDuration = 0f;
- public static float reportColorDuration = 20f;
- public static float timer = 6.2f;
-
- public static void clearAndReload() {
- detective = null;
- anonymousFootprints = CustomOptionHolder.detectiveAnonymousFootprints.getBool();
- footprintIntervall = CustomOptionHolder.detectiveFootprintIntervall.getFloat();
- footprintDuration = CustomOptionHolder.detectiveFootprintDuration.getFloat();
- reportNameDuration = CustomOptionHolder.detectiveReportNameDuration.getFloat();
- reportColorDuration = CustomOptionHolder.detectiveReportColorDuration.getFloat();
- timer = 6.2f;
- }
- }
- }
-
- public static class TimeMaster {
- public static PlayerControl timeMaster;
- public static Color color = new Color32(112, 142, 239, byte.MaxValue);
-
- public static bool reviveDuringRewind = false;
- public static float rewindTime = 3f;
- public static float shieldDuration = 3f;
- public static float cooldown = 30f;
-
- public static bool shieldActive = false;
- public static bool isRewinding = false;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TimeShieldButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- timeMaster = null;
- isRewinding = false;
- shieldActive = false;
- rewindTime = CustomOptionHolder.timeMasterRewindTime.getFloat();
- shieldDuration = CustomOptionHolder.timeMasterShieldDuration.getFloat();
- cooldown = CustomOptionHolder.timeMasterCooldown.getFloat();
- }
- }
-
- public static class Medic {
- public static PlayerControl medic;
- public static PlayerControl shielded;
- public static Color color = new Color32(126, 251, 194, byte.MaxValue);
- public static bool usedShield;
-
- public static int showShielded = 0;
- public static bool showAttemptToShielded = false;
-
- public static Color shieldedColor = new Color32(0, 221, 255, byte.MaxValue);
- public static PlayerControl currentTarget;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ShieldButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- medic = null;
- shielded = null;
- currentTarget = null;
- usedShield = false;
- showShielded = CustomOptionHolder.medicShowShielded.getSelection();
- showAttemptToShielded = CustomOptionHolder.medicShowAttemptToShielded.getBool();
- }
- }
-
- public static class Shifter {
- public static PlayerControl shifter;
- public static Color color = new Color32(102, 102, 102, byte.MaxValue);
-
- public static PlayerControl futureShift;
- public static PlayerControl currentTarget;
- public static bool shiftModifiers = false;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ShiftButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- shifter = null;
- currentTarget = null;
- futureShift = null;
- shiftModifiers = CustomOptionHolder.shifterShiftsModifiers.getBool();
- }
- }
-
- public static class Swapper {
- public static PlayerControl swapper;
- public static Color color = new Color32(134, 55, 86, byte.MaxValue);
- private static Sprite spriteCheck;
- public static bool canCallEmergency = false;
- public static bool canOnlySwapOthers = false;
-
- public static byte playerId1 = Byte.MaxValue;
- public static byte playerId2 = Byte.MaxValue;
-
- public static Sprite getCheckSprite() {
- if (spriteCheck) return spriteCheck;
- spriteCheck = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SwapperCheck.png", 150f);
- return spriteCheck;
- }
-
- public static void clearAndReload() {
- swapper = null;
- playerId1 = Byte.MaxValue;
- playerId2 = Byte.MaxValue;
- canCallEmergency = CustomOptionHolder.swapperCanCallEmergency.getBool();
- canOnlySwapOthers = CustomOptionHolder.swapperCanOnlySwapOthers.getBool();
- }
- }
-
- public static class Lovers {
- public static PlayerControl lover1;
- public static PlayerControl lover2;
- public static Color color = new Color32(232, 57, 185, byte.MaxValue);
-
- public static bool bothDie = true;
- // Lovers save if next to be exiled is a lover, because RPC of ending game comes before RPC of exiled
- public static bool notAckedExiledIsLover = false;
-
- public static bool existing() {
- return lover1 != null && lover2 != null && !lover1.Data.Disconnected && !lover2.Data.Disconnected;
- }
-
- public static bool existingAndAlive() {
- return existing() && !lover1.Data.IsDead && !lover2.Data.IsDead && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER
- }
-
- public static bool existingWithKiller() {
- return existing() && (lover1 == Jackal.jackal || lover2 == Jackal.jackal
- || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick
- || lover1.Data.IsImpostor || lover2.Data.IsImpostor);
- }
-
- public static bool hasAliveKillingLover(this PlayerControl player) {
- if (!Lovers.existingAndAlive() || !existingWithKiller())
- return false;
- return (player != null && (player == lover1 || player == lover2));
- }
-
- public static void clearAndReload() {
- lover1 = null;
- lover2 = null;
- 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 seer;
- public static Color color = new Color32(97, 178, 108, byte.MaxValue);
- public static List<Vector3> deadBodyPositions = new List<Vector3>();
-
- public static float soulDuration = 15f;
- public static bool limitSoulDuration = false;
- public static int mode = 0;
-
- private static Sprite soulSprite;
- public static Sprite getSoulSprite() {
- if (soulSprite) return soulSprite;
- soulSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Soul.png", 500f);
- return soulSprite;
- }
-
- public static void clearAndReload() {
- seer = null;
- deadBodyPositions = new List<Vector3>();
- limitSoulDuration = CustomOptionHolder.seerLimitSoulDuration.getBool();
- soulDuration = CustomOptionHolder.seerSoulDuration.getFloat();
- mode = CustomOptionHolder.seerMode.getSelection();
- }
- }
-
- public static class Morphling {
- public static PlayerControl morphling;
- public static Color color = Palette.ImpostorRed;
- private static Sprite sampleSprite;
- private static Sprite morphSprite;
-
- public static float cooldown = 30f;
- public static float duration = 10f;
-
- public static PlayerControl currentTarget;
- public static PlayerControl sampledTarget;
- public static PlayerControl morphTarget;
- public static float morphTimer = 0f;
-
- public static void resetMorph() {
- morphTarget = null;
- morphTimer = 0f;
- if (morphling == null) return;
- morphling.SetName(morphling.Data.PlayerName);
- morphling.SetHat(morphling.Data.HatId, (int)morphling.Data.ColorId);
- Helpers.setSkinWithAnim(morphling.MyPhysics, morphling.Data.SkinId);
- morphling.SetPet(morphling.Data.PetId);
- morphling.CurrentPet.Visible = morphling.Visible;
- morphling.SetColor(morphling.Data.ColorId);
- }
-
- public static void clearAndReload() {
- resetMorph();
- morphling = null;
- currentTarget = null;
- sampledTarget = null;
- morphTarget = null;
- morphTimer = 0f;
- cooldown = CustomOptionHolder.morphlingCooldown.getFloat();
- duration = CustomOptionHolder.morphlingDuration.getFloat();
- }
-
- public static Sprite getSampleSprite() {
- if (sampleSprite) return sampleSprite;
- sampleSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SampleButton.png", 115f);
- return sampleSprite;
- }
-
- public static Sprite getMorphSprite() {
- if (morphSprite) return morphSprite;
- morphSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.MorphButton.png", 115f);
- return morphSprite;
- }
- }
-
- public static class Camouflager {
- public static PlayerControl camouflager;
- public static Color color = Palette.ImpostorRed;
-
- public static float cooldown = 30f;
- public static float duration = 10f;
- public static float camouflageTimer = 0f;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CamoButton.png", 115f);
- return buttonSprite;
- }
-
- public static void resetCamouflage() {
- camouflageTimer = 0f;
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (p == null) continue;
- if (Morphling.morphling == null || Morphling.morphling != p) {
- p.SetName(p.Data.PlayerName);
- p.SetHat(p.Data.HatId, (int)p.Data.ColorId);
- Helpers.setSkinWithAnim(p.MyPhysics, p.Data.SkinId);
- p.SetPet(p.Data.PetId);
- p.CurrentPet.Visible = p.Visible;
- p.SetColor(p.Data.ColorId);
- }
- }
- }
-
- public static void clearAndReload() {
- resetCamouflage();
- camouflager = null;
- camouflageTimer = 0f;
- cooldown = CustomOptionHolder.camouflagerCooldown.getFloat();
- duration = CustomOptionHolder.camouflagerDuration.getFloat();
- }
- }
-
- public static class Hacker {
- public static PlayerControl hacker;
- public static Color color = new Color32(117, 250, 76, byte.MaxValue);
-
- public static float cooldown = 30f;
- public static float duration = 10f;
- public static bool onlyColorType = false;
- public static float hackerTimer = 0f;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.HackerButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- hacker = null;
- hackerTimer = 0f;
- cooldown = CustomOptionHolder.hackerCooldown.getFloat();
- duration = CustomOptionHolder.hackerHackeringDuration.getFloat();
- onlyColorType = CustomOptionHolder.hackerOnlyColorType.getBool();
- }
- }
-
- public static class Mini {
- public static PlayerControl mini;
- public static Color color = Color.white;
- public const float defaultColliderRadius = 0.2233912f;
- public const float defaultColliderOffset = 0.3636057f;
-
- public static float growingUpDuration = 400f;
- public static DateTime timeOfGrowthStart = DateTime.UtcNow;
- public static bool triggerMiniLose = false;
-
- public static void clearAndReload() {
- mini = null;
- triggerMiniLose = false;
- growingUpDuration = CustomOptionHolder.miniGrowingUpDuration.getFloat();
- timeOfGrowthStart = DateTime.UtcNow;
- }
-
- public static float growingProgress() {
- if (timeOfGrowthStart == null) return 0f;
-
- float timeSinceStart = (float)(DateTime.UtcNow - timeOfGrowthStart).TotalMilliseconds;
- return Mathf.Clamp(timeSinceStart/(growingUpDuration*1000), 0f, 1f);
- }
-
- public static bool isGrownUp() {
- return growingProgress() == 1f;
- }
- }
-
- public static class Tracker {
- public static PlayerControl tracker;
- public static Color color = new Color32(100, 58, 220, byte.MaxValue);
-
- public static float updateIntervall = 5f;
-
- public static PlayerControl currentTarget;
- public static PlayerControl tracked;
- public static bool usedTracker = false;
- public static float timeUntilUpdate = 0f;
- public static Arrow arrow = new Arrow(Color.blue);
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TrackerButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- tracker = null;
- currentTarget = null;
- tracked = null;
- usedTracker = false;
- timeUntilUpdate = 0f;
- updateIntervall = CustomOptionHolder.trackerUpdateIntervall.getFloat();
- if (arrow?.arrow != null) UnityEngine.Object.Destroy(arrow.arrow);
- arrow = new Arrow(Color.blue);
- if (arrow.arrow != null) arrow.arrow.SetActive(false);
- }
- }
-
- public static class Vampire {
- public static PlayerControl vampire;
- public static Color color = Palette.ImpostorRed;
-
- public static float delay = 10f;
- public static float cooldown = 30f;
- public static bool canKillNearGarlics = true;
- public static bool localPlacedGarlic = false;
- public static bool garlicsActive = true;
-
- public static PlayerControl currentTarget;
- public static PlayerControl bitten;
- public static bool targetNearGarlic = false;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.VampireButton.png", 115f);
- return buttonSprite;
- }
-
- private static Sprite garlicButtonSprite;
- public static Sprite getGarlicButtonSprite() {
- if (garlicButtonSprite) return garlicButtonSprite;
- garlicButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.GarlicButton.png", 115f);
- return garlicButtonSprite;
- }
-
- public static void clearAndReload() {
- vampire = null;
- bitten = null;
- targetNearGarlic = false;
- localPlacedGarlic = false;
- currentTarget = null;
- garlicsActive = CustomOptionHolder.vampireSpawnRate.getSelection() > 0;
- delay = CustomOptionHolder.vampireKillDelay.getFloat();
- cooldown = CustomOptionHolder.vampireCooldown.getFloat();
- canKillNearGarlics = CustomOptionHolder.vampireCanKillNearGarlics.getBool();
- }
- }
-
- public static class Snitch {
- public static PlayerControl snitch;
- public static Color color = new Color32(184, 251, 79, byte.MaxValue);
-
- public static List<Arrow> localArrows = new List<Arrow>();
- public static int taskCountForImpostors = 1;
-
- public static void clearAndReload() {
- if (localArrows != null) {
- foreach (Arrow arrow in localArrows)
- if (arrow?.arrow != null)
- UnityEngine.Object.Destroy(arrow.arrow);
- }
- localArrows = new List<Arrow>();
- taskCountForImpostors = Mathf.RoundToInt(CustomOptionHolder.snitchLeftTasksForImpostors.getFloat());
- snitch = null;
- }
- }
-
- public static class Jackal {
- 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 float cooldown = 30f;
- public static float createSidekickCooldown = 30f;
- public static bool canUseVents = true;
- public static bool canCreateSidekick = true;
- public static Sprite buttonSprite;
- public static bool jackalPromotedFromSidekickCanCreateSidekick = true;
- public static bool canCreateSidekickFromImpostor = true;
- public static bool hasImpostorVision = false;
-
- public static Sprite getSidekickButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SidekickButton.png", 115f);
- return buttonSprite;
- }
-
- public static void removeCurrentJackal() {
- if (!formerJackals.Any(x => x.PlayerId == jackal.PlayerId)) formerJackals.Add(jackal);
- jackal = null;
- currentTarget = null;
- fakeSidekick = null;
- cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
- createSidekickCooldown = CustomOptionHolder.jackalCreateSidekickCooldown.getFloat();
- }
-
- public static void clearAndReload() {
- jackal = null;
- currentTarget = null;
- fakeSidekick = null;
- cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
- createSidekickCooldown = CustomOptionHolder.jackalCreateSidekickCooldown.getFloat();
- canUseVents = CustomOptionHolder.jackalCanUseVents.getBool();
- canCreateSidekick = CustomOptionHolder.jackalCanCreateSidekick.getBool();
- jackalPromotedFromSidekickCanCreateSidekick = CustomOptionHolder.jackalPromotedFromSidekickCanCreateSidekick.getBool();
- canCreateSidekickFromImpostor = CustomOptionHolder.jackalCanCreateSidekickFromImpostor.getBool();
- formerJackals.Clear();
- hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool();
- }
-
- }
-
- public static class Sidekick {
- public static PlayerControl sidekick;
- public static Color color = new Color32(0, 180, 235, byte.MaxValue);
-
- public static PlayerControl currentTarget;
-
- public static float cooldown = 30f;
- public static bool canUseVents = true;
- public static bool canKill = true;
- public static bool promotesToJackal = true;
- public static bool hasImpostorVision = false;
-
- public static void clearAndReload() {
- sidekick = null;
- currentTarget = null;
- cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
- canUseVents = CustomOptionHolder.sidekickCanUseVents.getBool();
- canKill = CustomOptionHolder.sidekickCanKill.getBool();
- promotesToJackal = CustomOptionHolder.sidekickPromotesToJackal.getBool();
- hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool();
- }
- }
-
- public static class Eraser {
- public static PlayerControl eraser;
- public static Color color = Palette.ImpostorRed;
-
- public static List<PlayerControl> futureErased = new List<PlayerControl>();
- public static PlayerControl currentTarget;
- public static float cooldown = 30f;
- public static bool canEraseAnyone = false;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EraserButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- eraser = null;
- futureErased = new List<PlayerControl>();
- currentTarget = null;
- cooldown = CustomOptionHolder.eraserCooldown.getFloat();
- canEraseAnyone = CustomOptionHolder.eraserCanEraseAnyone.getBool();
- }
- }
-
- public static class Spy {
- public static PlayerControl spy;
- public static Color color = Palette.ImpostorRed;
-
- public static bool impostorsCanKillAnyone = true;
- public static bool canEnterVents = false;
- public static bool hasImpostorVision = false;
-
- public static void clearAndReload() {
- spy = null;
- impostorsCanKillAnyone = CustomOptionHolder.spyImpostorsCanKillAnyone.getBool();
- canEnterVents = CustomOptionHolder.spyCanEnterVents.getBool();
- hasImpostorVision = CustomOptionHolder.spyHasImpostorVision.getBool();
- }
- }
-
- public static class Trickster {
- public static PlayerControl trickster;
- public static Color color = Palette.ImpostorRed;
- public static float placeBoxCooldown = 30f;
- public static float lightsOutCooldown = 30f;
- public static float lightsOutDuration = 10f;
- public static float lightsOutTimer = 0f;
-
- private static Sprite placeBoxButtonSprite;
- private static Sprite lightOutButtonSprite;
- private static Sprite tricksterVentButtonSprite;
-
- public static Sprite getPlaceBoxButtonSprite() {
- if (placeBoxButtonSprite) return placeBoxButtonSprite;
- placeBoxButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlaceJackInTheBoxButton.png", 115f);
- return placeBoxButtonSprite;
- }
-
- public static Sprite getLightsOutButtonSprite() {
- if (lightOutButtonSprite) return lightOutButtonSprite;
- lightOutButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LightsOutButton.png", 115f);
- return lightOutButtonSprite;
- }
-
- public static Sprite getTricksterVentButtonSprite() {
- if (tricksterVentButtonSprite) return tricksterVentButtonSprite;
- tricksterVentButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TricksterVentButton.png", 115f);
- return tricksterVentButtonSprite;
- }
-
- public static void clearAndReload() {
- trickster = null;
- lightsOutTimer = 0f;
- placeBoxCooldown = CustomOptionHolder.tricksterPlaceBoxCooldown.getFloat();
- lightsOutCooldown = CustomOptionHolder.tricksterLightsOutCooldown.getFloat();
- lightsOutDuration = CustomOptionHolder.tricksterLightsOutDuration.getFloat();
- JackInTheBox.UpdateStates(); // if the role is erased, we might have to update the state of the created objects
- }
-
- }
-
- public static class Cleaner {
- public static PlayerControl cleaner;
- public static Color color = Palette.ImpostorRed;
-
- public static float cooldown = 30f;
-
- private static Sprite buttonSprite;
- public static Sprite getButtonSprite() {
- if (buttonSprite) return buttonSprite;
- buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CleanButton.png", 115f);
- return buttonSprite;
- }
-
- public static void clearAndReload() {
- cleaner = null;
- cooldown = CustomOptionHolder.cleanerCooldown.getFloat();
- }
- }
-
- public static class Warlock {
-
- public static PlayerControl warlock;
- public static Color color = Palette.ImpostorRed;
-
- public static PlayerControl currentTarget;
- public static PlayerControl curseVictim;
- public static PlayerControl curseVictimTarget;
- public static PlayerControl curseKillTarget;
-
- public static float cooldown = 30f;
- public static float rootTime = 5f;
-
- private static Sprite curseButtonSprite;
- private static Sprite curseKillButtonSprite;
-
- public static Sprite getCurseButtonSprite() {
- if (curseButtonSprite) return curseButtonSprite;
- curseButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CurseButton.png", 115f);
- return curseButtonSprite;
- }
-
- public static Sprite getCurseKillButtonSprite() {
- if (curseKillButtonSprite) return curseKillButtonSprite;
- curseKillButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CurseKillButton.png", 115f);
- return curseKillButtonSprite;
- }
-
- public static void clearAndReload() {
- warlock = null;
- currentTarget = null;
- curseVictim = null;
- curseVictimTarget = null;
- curseKillTarget = null;
- cooldown = CustomOptionHolder.warlockCooldown.getFloat();
- rootTime = CustomOptionHolder.warlockRootTime.getFloat();
- }
-
- public static void resetCurse() {
- HudManagerStartPatch.warlockCurseButton.Timer = HudManagerStartPatch.warlockCurseButton.MaxTimer;
- HudManagerStartPatch.warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
- HudManagerStartPatch.warlockCurseButton.killButtonManager.TimerText.color = Palette.EnabledColor;
- currentTarget = null;
- curseVictim = null;
- curseVictimTarget = null;
- curseKillTarget = null;
- }
- }
-
- public static class SecurityGuard {
- public static PlayerControl securityGuard;
- public static Color color = new Color32(195, 178, 95, byte.MaxValue);
-
- public static float cooldown = 30f;
- public static int remainingScrews = 7;
- public static int totalScrews = 7;
- public static int ventPrice = 1;
- public static int camPrice = 2;
- public static int placedCameras = 0;
- public static Vent ventTarget = null;
-
- private static Sprite closeVentButtonSprite;
- public static Sprite getCloseVentButtonSprite() {
- if (closeVentButtonSprite) return closeVentButtonSprite;
- closeVentButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CloseVentButton.png", 115f);
- return closeVentButtonSprite;
- }
-
- private static Sprite placeCameraButtonSprite;
- public static Sprite getPlaceCameraButtonSprite() {
- if (placeCameraButtonSprite) return placeCameraButtonSprite;
- placeCameraButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlaceCameraButton.png", 115f);
- return placeCameraButtonSprite;
- }
-
- private static Sprite animatedVentSealedSprite;
- public static Sprite getAnimatedVentSealedSprite() {
- if (animatedVentSealedSprite) return animatedVentSealedSprite;
- animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", 160f);
- return animatedVentSealedSprite;
- }
-
- private static Sprite staticVentSealedSprite;
- public static Sprite getStaticVentSealedSprite() {
- if (staticVentSealedSprite) return staticVentSealedSprite;
- staticVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StaticVentSealed.png", 160f);
- return staticVentSealedSprite;
- }
-
- public static void clearAndReload() {
- securityGuard = null;
- ventTarget = null;
- placedCameras = 0;
- cooldown = CustomOptionHolder.securityGuardCooldown.getFloat();
- totalScrews = remainingScrews = Mathf.RoundToInt(CustomOptionHolder.securityGuardTotalScrews.getFloat());
- camPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamPrice.getFloat());
- ventPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardVentPrice.getFloat());
- }
- }
-
- public static class Arsonist {
- public static PlayerControl arsonist;
- public static Color color = new Color32(238, 112, 46, byte.MaxValue);
-
- public static float cooldown = 30f;
- public static float duration = 3f;
- public static bool triggerArsonistWin = false;
-
- public static PlayerControl currentTarget;
- public static PlayerControl douseTarget;
- public static List<PlayerControl> dousedPlayers = new List<PlayerControl>();
-
- private static Sprite douseSprite;
- public static Sprite getDouseSprite() {
- if (douseSprite) return douseSprite;
- douseSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DouseButton.png", 115f);
- return douseSprite;
- }
-
- private static Sprite igniteSprite;
- public static Sprite getIgniteSprite() {
- if (igniteSprite) return igniteSprite;
- igniteSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.IgniteButton.png", 115f);
- return igniteSprite;
- }
-
- public static bool dousedEveryoneAlive() {
- return PlayerControl.AllPlayerControls.ToArray().All(x => { return x == Arsonist.arsonist || x.Data.IsDead || x.Data.Disconnected || Arsonist.dousedPlayers.Any(y => y.PlayerId == x.PlayerId); });
- }
-
- public static void clearAndReload() {
- arsonist = null;
- currentTarget = null;
- douseTarget = null;
- triggerArsonistWin = false;
- dousedPlayers = new List<PlayerControl>();
- foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
- if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
- }
- cooldown = CustomOptionHolder.arsonistCooldown.getFloat();
- duration = CustomOptionHolder.arsonistDuration.getFloat();
- }
- }
-
- public static class Guesser {
- public static PlayerControl guesser;
- public static Color color = new Color32(255, 255, 0, byte.MaxValue);
- private static Sprite targetSprite;
-
- public static int remainingShots = 2;
-
- public static Sprite getTargetSprite() {
- if (targetSprite) return targetSprite;
- targetSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TargetIcon.png", 150f);
- return targetSprite;
- }
-
- public static void clearAndReload() {
- guesser = null;
-
- 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
+++ /dev/null
-<Project Sdk="Microsoft.NET.Sdk">
- <PropertyGroup>
- <TargetFramework>netstandard2.1</TargetFramework>
- <Version>2.7.1</Version>
- <Description>TheOtherRoles</Description>
- <Authors>Eisbison</Authors>
- </PropertyGroup>
-
- <PropertyGroup>
- <GameVersion>2021.6.15</GameVersion>
- <DefineConstants>$(DefineConstants);STEAM</DefineConstants>
- <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
- </PropertyGroup>
-
- <ItemGroup>
- <EmbeddedResource Include="Resources\CustomHats\*.png" />
- <EmbeddedResource Include="Resources\*.png" />
- <EmbeddedResource Include="Resources\TricksterAnimation\*.png" />
- </ItemGroup>
-
- <ItemGroup>
- <Reference Include="$(AmongUs)/BepInEx/core/*.dll"/>
- <Reference Include="$(AmongUs)/BepInEx/unhollowed/*.dll"/>
- </ItemGroup>
-
- <Target Name="CopyCustomContent" AfterTargets="AfterBuild">
- <Message Text="Second occurrence" />
- <Copy SourceFiles="$(ProjectDir)\bin\$(Configuration)\netstandard2.1\TheOtherRoles.dll" DestinationFolder="$(AmongUs)/BepInEx/plugins/" />
- </Target>
-</Project>
\ No newline at end of file
+++ /dev/null
-using HarmonyLib;
-using System;
-using System.IO;
-using System.Net.Http;
-using UnityEngine;
-using static TheOtherRoles.TheOtherRoles;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace TheOtherRoles
-{
- [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
- class HudManagerUpdatePatch
- {
- public static bool hidePlayerName(PlayerControl source, PlayerControl target) {
- if (!MapOptions.hidePlayerNames) return false; // All names are visible
- else if (source == null || target == null) return true;
- else if (source == target) return false; // Player sees his own name
- else if (source.Data.IsImpostor && (target.Data.IsImpostor || target == Spy.spy)) return false; // Members of team Impostors see the names of Impostors/Spies
- else if ((source == Lovers.lover1 || source == Lovers.lover2) && (target == Lovers.lover1 || target == Lovers.lover2)) return false; // Members of team Lovers see the names of each other
- else if ((source == Jackal.jackal || source == Sidekick.sidekick) && (target == Jackal.jackal || target == Sidekick.sidekick || target == Jackal.fakeSidekick)) return false; // Members of team Jackal see the names of each other
- return true;
- }
-
- static void resetNameTagsAndColors() {
- Dictionary<byte, PlayerControl> playersById = Helpers.allPlayersById();
-
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- player.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, player) ? "" : player.Data.PlayerName;
- if (PlayerControl.LocalPlayer.Data.IsImpostor && player.Data.IsImpostor) {
- player.nameText.color = Palette.ImpostorRed;
- } else {
- player.nameText.color = Color.white;
- }
- }
- if (MeetingHud.Instance != null) {
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
- PlayerControl playerControl = playersById.ContainsKey((byte)player.TargetPlayerId) ? playersById[(byte)player.TargetPlayerId] : null;
- if (playerControl != null) {
- player.NameText.text = playerControl.Data.PlayerName;
- if (PlayerControl.LocalPlayer.Data.IsImpostor && playerControl.Data.IsImpostor) {
- player.NameText.color = Palette.ImpostorRed;
- } else {
- player.NameText.color = Color.white;
- }
- }
- }
- }
- if (PlayerControl.LocalPlayer.Data.IsImpostor) {
- List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList();
- impostors.RemoveAll(x => !x.Data.IsImpostor);
- foreach (PlayerControl player in impostors)
- player.nameText.color = Palette.ImpostorRed;
- if (MeetingHud.Instance != null)
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
- PlayerControl playerControl = Helpers.playerById((byte)player.TargetPlayerId);
- if (playerControl != null && playerControl.Data.IsImpostor)
- player.NameText.color = Palette.ImpostorRed;
- }
- }
-
- }
-
- static void setPlayerNameColor(PlayerControl p, Color color) {
- p.nameText.color = color;
- if (MeetingHud.Instance != null)
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
- if (player.NameText != null && p.PlayerId == player.TargetPlayerId)
- player.NameText.color = color;
- }
-
- static void setNameColors() {
- if (Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer)
- setPlayerNameColor(Jester.jester, Jester.color);
- else if (Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer)
- setPlayerNameColor(Mayor.mayor, Mayor.color);
- else if (Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer)
- setPlayerNameColor(Engineer.engineer, Engineer.color);
- else if (Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer)
- setPlayerNameColor(Sheriff.sheriff, Sheriff.color);
- else if (Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer)
- setPlayerNameColor(Lighter.lighter, Lighter.color);
- else if (Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer)
- setPlayerNameColor(Detective.detective, Detective.color);
- else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer)
- setPlayerNameColor(TimeMaster.timeMaster, TimeMaster.color);
- else if (Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer)
- setPlayerNameColor(Medic.medic, Medic.color);
- else if (Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer)
- setPlayerNameColor(Shifter.shifter, Shifter.color);
- else if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer)
- setPlayerNameColor(Swapper.swapper, Swapper.color);
- else if (Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer)
- setPlayerNameColor(Seer.seer, Seer.color);
- else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer)
- setPlayerNameColor(Hacker.hacker, Hacker.color);
- else if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer)
- setPlayerNameColor(Tracker.tracker, Tracker.color);
- else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer)
- setPlayerNameColor(Snitch.snitch, Snitch.color);
- else if (Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer) {
- // Jackal can see his sidekick
- setPlayerNameColor(Jackal.jackal, Jackal.color);
- if (Sidekick.sidekick != null) {
- setPlayerNameColor(Sidekick.sidekick, Jackal.color);
- }
- if (Jackal.fakeSidekick != null) {
- setPlayerNameColor(Jackal.fakeSidekick, Jackal.color);
- }
- }
- else if (Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer) {
- setPlayerNameColor(Spy.spy, Spy.color);
- } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer) {
- setPlayerNameColor(SecurityGuard.securityGuard, SecurityGuard.color);
- } else if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) {
- setPlayerNameColor(Arsonist.arsonist, Arsonist.color);
- } 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
- setPlayerNameColor(Sidekick.sidekick, Sidekick.color);
- if (Jackal.jackal != null) {
- setPlayerNameColor(Jackal.jackal, Jackal.color);
- }
- }
-
- // No else if here, as the Impostors need the Spy name to be colored
- if (Spy.spy != null && PlayerControl.LocalPlayer.Data.IsImpostor) {
- setPlayerNameColor(Spy.spy, Spy.color);
- }
-
- // Crewmate roles with no changes: Mini
- // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock, BountyHunter and Mafioso
- }
-
- static void setNameTags() {
- // Mafia
- if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.IsImpostor) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls)
- if (Godfather.godfather != null && Godfather.godfather == player)
- player.nameText.text = player.Data.PlayerName + " (G)";
- else if (Mafioso.mafioso != null && Mafioso.mafioso == player)
- player.nameText.text = player.Data.PlayerName + " (M)";
- else if (Janitor.janitor != null && Janitor.janitor == player)
- player.nameText.text = player.Data.PlayerName + " (J)";
- if (MeetingHud.Instance != null)
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
- if (Godfather.godfather != null && Godfather.godfather.PlayerId == player.TargetPlayerId)
- player.NameText.text = Godfather.godfather.Data.PlayerName + " (G)";
- else if (Mafioso.mafioso != null && Mafioso.mafioso.PlayerId == player.TargetPlayerId)
- player.NameText.text = Mafioso.mafioso.Data.PlayerName + " (M)";
- else if (Janitor.janitor != null && Janitor.janitor.PlayerId == player.TargetPlayerId)
- player.NameText.text = Janitor.janitor.Data.PlayerName + " (J)";
- }
-
- // Lovers
- if (Lovers.lover1 != null && Lovers.lover2 != null && (Lovers.lover1 == PlayerControl.LocalPlayer || Lovers.lover2 == PlayerControl.LocalPlayer)) {
- string suffix = Helpers.cs(Lovers.color, " ❤");
- Lovers.lover1.nameText.text += suffix;
- Lovers.lover2.nameText.text += suffix;
-
- if (MeetingHud.Instance != null)
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
- if (Lovers.lover1.PlayerId == player.TargetPlayerId || Lovers.lover2.PlayerId == player.TargetPlayerId)
- player.NameText.text += suffix;
- }
- }
-
- static void updateShielded() {
- if (Medic.shielded == null) return;
-
- if (Medic.shielded.Data.IsDead || Medic.medic == null || Medic.medic.Data.IsDead) {
- Medic.shielded = null;
- }
- }
-
- static void timerUpdate() {
- Hacker.hackerTimer -= Time.deltaTime;
- Lighter.lighterTimer -= Time.deltaTime;
- Trickster.lightsOutTimer -= Time.deltaTime;
- }
-
- static void camouflageAndMorphActions() {
- float oldCamouflageTimer = Camouflager.camouflageTimer;
- float oldMorphTimer = Morphling.morphTimer;
-
- Camouflager.camouflageTimer -= Time.deltaTime;
- Morphling.morphTimer -= Time.deltaTime;
-
- // Morphling player size not done here
-
- // Set morphling morphed look
- if (Morphling.morphTimer > 0f && Camouflager.camouflageTimer <= 0f) {
- if (Morphling.morphling != null && Morphling.morphTarget != null) {
- Morphling.morphling.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, Morphling.morphling) ? "" : Morphling.morphTarget.Data.PlayerName;
- Morphling.morphling.myRend.material.SetColor("_BackColor", Palette.ShadowColors[Morphling.morphTarget.Data.ColorId]);
- Morphling.morphling.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[Morphling.morphTarget.Data.ColorId]);
- Morphling.morphling.HatRenderer.SetHat(Morphling.morphTarget.Data.HatId, Morphling.morphTarget.Data.ColorId);
- Morphling.morphling.nameText.transform.localPosition = new Vector3(0f, ((Morphling.morphTarget.Data.HatId == 0U) ? 0.7f : 1.05f) * 2f, -0.5f);
-
- if (Morphling.morphling.MyPhysics.Skin.skin.ProdId != DestroyableSingleton<HatManager>.Instance.AllSkins[(int)Morphling.morphTarget.Data.SkinId].ProdId) {
- Helpers.setSkinWithAnim(Morphling.morphling.MyPhysics, Morphling.morphTarget.Data.SkinId);
- }
- if (Morphling.morphling.CurrentPet == null || Morphling.morphling.CurrentPet.ProdId != DestroyableSingleton<HatManager>.Instance.AllPets[(int)Morphling.morphTarget.Data.PetId].ProdId) {
- if (Morphling.morphling.CurrentPet) UnityEngine.Object.Destroy(Morphling.morphling.CurrentPet.gameObject);
- Morphling.morphling.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[(int)Morphling.morphTarget.Data.PetId]);
- Morphling.morphling.CurrentPet.transform.position = Morphling.morphling.transform.position;
- Morphling.morphling.CurrentPet.Source = Morphling.morphling;
- Morphling.morphling.CurrentPet.Visible = Morphling.morphling.Visible;
- PlayerControl.SetPlayerMaterialColors(Morphling.morphTarget.Data.ColorId, Morphling.morphling.CurrentPet.rend);
- } else if (Morphling.morphling.CurrentPet) {
- PlayerControl.SetPlayerMaterialColors(Morphling.morphTarget.Data.ColorId, Morphling.morphling.CurrentPet.rend);
- }
- }
- }
-
- // Set camouflaged look (overrides morphling morphed look if existent)
- if (Camouflager.camouflageTimer > 0f) {
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- p.nameText.text = "";
- p.myRend.material.SetColor("_BackColor", Palette.PlayerColors[6]);
- p.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[6]);
- p.HatRenderer.SetHat(0, 0);
- Helpers.setSkinWithAnim(p.MyPhysics, 0);
- bool spawnPet = false;
- if (p.CurrentPet == null) spawnPet = true;
- else if (p.CurrentPet.ProdId != DestroyableSingleton<HatManager>.Instance.AllPets[0].ProdId) {
- UnityEngine.Object.Destroy(p.CurrentPet.gameObject);
- spawnPet = true;
- }
-
- if (spawnPet) {
- p.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[0]);
- p.CurrentPet.transform.position = p.transform.position;
- p.CurrentPet.Source = p;
- }
- }
- }
-
- // Everyone but morphling reset
- if (oldCamouflageTimer > 0f && Camouflager.camouflageTimer <= 0f) {
- Camouflager.resetCamouflage();
- }
-
- // Morphling reset
- if ((oldMorphTimer > 0f || oldCamouflageTimer > 0f) && Camouflager.camouflageTimer <= 0f && Morphling.morphTimer <= 0f && Morphling.morphling != null) {
- Morphling.resetMorph();
- }
- }
-
- public static void miniUpdate() {
- if (Mini.mini == null || Camouflager.camouflageTimer > 0f) return;
-
- float growingProgress = Mini.growingProgress();
- float scale = growingProgress * 0.35f + 0.35f;
- string suffix = "";
- if (growingProgress != 1f)
- suffix = " <color=#FAD934FF>(" + Mathf.FloorToInt(growingProgress * 18) + ")</color>";
-
- Mini.mini.nameText.text += suffix;
- if (MeetingHud.Instance != null) {
- foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
- if (player.NameText != null && Mini.mini.PlayerId == player.TargetPlayerId)
- player.NameText.text += suffix;
- }
-
- if (Morphling.morphling != null && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f)
- Morphling.morphling.nameText.text += suffix;
- }
-
- static void updateImpostorKillButton(HudManager __instance) {
- if (!PlayerControl.LocalPlayer.Data.IsImpostor) return;
- bool enabled = true;
- if (Vampire.vampire != null && Vampire.vampire == PlayerControl.LocalPlayer)
- enabled = false;
- else if (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead)
- enabled = false;
- else if (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer)
- enabled = false;
- enabled &= __instance.UseButton.isActiveAndEnabled;
-
- __instance.KillButton.gameObject.SetActive(enabled);
- __instance.KillButton.renderer.enabled = enabled;
- __instance.KillButton.isActive = enabled;
- __instance.KillButton.enabled = enabled;
- }
-
- static void Postfix(HudManager __instance)
- {
- if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
-
- CustomButton.HudUpdate();
- resetNameTagsAndColors();
- setNameColors();
- updateShielded();
- setNameTags();
-
- // Impostors
- updateImpostorKillButton(__instance);
- // Timer updates
- timerUpdate();
- // Camouflager and Morphling
- camouflageAndMorphActions();
- // Mini
- miniUpdate();
- }
- }
-}
+++ /dev/null
-using HarmonyLib;
-using System;
-using Hazel;
-using UnityEngine;
-using System.Linq;
-using static TheOtherRoles.TheOtherRoles;
-using static TheOtherRoles.GameHistory;
-using static TheOtherRoles.MapOptions;
-using System.Collections.Generic;
-
-
-namespace TheOtherRoles
-{
-
- [HarmonyPatch(typeof(Vent), "CanUse")]
- public static class VentCanUsePatch
- {
- public static bool Prefix(Vent __instance, ref float __result, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
- {
- float num = float.MaxValue;
- PlayerControl @object = pc.Object;
-
-
- bool roleCouldUse = false;
- if (Engineer.engineer != null && Engineer.engineer == @object)
- roleCouldUse = true;
- else if (Jackal.canUseVents && Jackal.jackal != null && Jackal.jackal == @object)
- roleCouldUse = true;
- else if (Sidekick.canUseVents && Sidekick.sidekick != null && Sidekick.sidekick == @object)
- roleCouldUse = true;
- else if (Spy.canEnterVents && Spy.spy != null && Spy.spy == @object)
- roleCouldUse = true;
- else if (pc.IsImpostor) {
- if (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer)
- roleCouldUse = false;
- else if (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead)
- roleCouldUse = false;
- else
- roleCouldUse = true;
- }
-
- var usableDistance = __instance.UsableDistance;
- if (__instance.name.StartsWith("JackInTheBoxVent_")) {
- if(Trickster.trickster != PlayerControl.LocalPlayer) {
- // Only the Trickster can use the Jack-In-The-Boxes!
- canUse = false;
- couldUse = false;
- __result = num;
- return false;
- } else {
- // Reduce the usable distance to reduce the risk of gettings stuck while trying to jump into the box if it's placed near objects
- usableDistance = 0.4f;
- }
- } else if (__instance.name.StartsWith("SealedVent_")) {
- canUse = couldUse = false;
- __result = num;
- return false;
- }
-
- couldUse = (@object.inVent || roleCouldUse) && !pc.IsDead && (@object.CanMove || @object.inVent);
- canUse = couldUse;
- if (canUse)
- {
- Vector2 truePosition = @object.GetTruePosition();
- Vector3 position = __instance.transform.position;
- num = Vector2.Distance(truePosition, position);
-
- canUse &= (num <= usableDistance && !PhysicsHelpers.AnythingBetween(truePosition, position, Constants.ShipOnlyMask, false));
- }
- __result = num;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(Vent), "Use")]
- public static class VentUsePatch {
- public static bool Prefix(Vent __instance) {
- bool canUse;
- bool couldUse;
- __instance.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
- bool canMoveInVents = true;
- if (!canUse) return false; // No need to execute the native method as using is disallowed anyways
- if (Spy.spy == PlayerControl.LocalPlayer) {
- canMoveInVents = false;
- }
- bool isEnter = !PlayerControl.LocalPlayer.inVent;
-
- if (__instance.name.StartsWith("JackInTheBoxVent_")) {
- __instance.SetButtons(isEnter && canMoveInVents);
- MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UseUncheckedVent, Hazel.SendOption.Reliable);
- writer.WritePacked(__instance.Id);
- writer.Write(PlayerControl.LocalPlayer.PlayerId);
- writer.Write(isEnter ? byte.MaxValue : (byte)0);
- writer.EndMessage();
- RPCProcedure.useUncheckedVent(__instance.Id, PlayerControl.LocalPlayer.PlayerId, isEnter ? byte.MaxValue : (byte)0);
- return false;
- }
-
- if(isEnter) {
- PlayerControl.LocalPlayer.MyPhysics.RpcEnterVent(__instance.Id);
- } else {
- PlayerControl.LocalPlayer.MyPhysics.RpcExitVent(__instance.Id);
- }
- __instance.SetButtons(isEnter && canMoveInVents);
- return false;
- }
- }
-
- [HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.SetTarget))]
- class UseButtonSetTargetPatch {
- static void Postfix(UseButtonManager __instance) {
- // Trickster render special vent button
- if (__instance.currentTarget != null && Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer) {
- Vent possibleVent = __instance.currentTarget.TryCast<Vent>();
- if (possibleVent != null && possibleVent.gameObject != null && possibleVent.gameObject.name.StartsWith("JackInTheBoxVent_")) {
- __instance.UseButton.sprite = Trickster.getTricksterVentButtonSprite();
- }
- }
-
- // Jester sabotage
- if (Jester.canSabotage && Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.CanMove) {
- if (!Jester.jester.Data.IsDead && __instance.currentTarget == null) { // no target, so sabotage
- __instance.UseButton.sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(ImageNames.SabotageButton);
- CooldownHelpers.SetCooldownNormalizedUvs(__instance.UseButton);
- __instance.UseButton.color = UseButtonManager.EnabledColor;
- }
- }
-
- // Mafia sabotage button render patch
- bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer);
- bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead);
- if (__instance.currentTarget == null && (blockSabotageJanitor || blockSabotageMafioso)) {
- __instance.UseButton.sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(ImageNames.UseButton);
- __instance.UseButton.color = new Color(1f, 1f, 1f, 0.3f);
- }
-
- }
- }
-
- [HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.DoClick))]
- class UseButtonDoClickPatch {
- static bool Prefix(UseButtonManager __instance) {
- if (__instance.currentTarget != null) return true;
-
- // Jester sabotage
- if (Jester.canSabotage && Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && !Jester.jester.Data.IsDead) {
- Action<MapBehaviour> action = m => m.ShowInfectedMap() ;
- DestroyableSingleton<HudManager>.Instance.ShowMap(action);
- return false;
- }
-
- // Mafia sabotage button click patch
- bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer);
- bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead);
- if (blockSabotageJanitor || blockSabotageMafioso) return false;
-
- return true;
- }
- }
-
- [HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))]
- class EmergencyMinigameUpdatePatch {
- static void Postfix(EmergencyMinigame __instance) {
- var roleCanCallEmergency = true;
- var statusText = "";
-
- // Deactivate emergency button for Swapper
- if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer && !Swapper.canCallEmergency) {
- roleCanCallEmergency = false;
- statusText = "The Swapper can't start an emergency meeting";
- }
- // Potentially deactivate emergency button for Jester
- if (Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && !Jester.canCallEmergency) {
- roleCanCallEmergency = false;
- statusText = "The Jester can't start an emergency meeting";
- }
-
- if (!roleCanCallEmergency) {
- __instance.StatusText.text = statusText;
- __instance.NumberText.text = string.Empty;
- __instance.ClosedLid.gameObject.SetActive(true);
- __instance.OpenLid.gameObject.SetActive(false);
- __instance.ButtonActive = false;
- return;
- }
-
- // Handle max number of meetings
- if (__instance.state == 1) {
- int localRemaining = PlayerControl.LocalPlayer.RemainingEmergencies;
- int teamRemaining = Mathf.Max(0, maxNumberOfMeetings - meetingsCount);
- int remaining = Mathf.Min(localRemaining, (Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer) ? 1 : teamRemaining);
- __instance.NumberText.text = $"{localRemaining.ToString()} and the ship has {teamRemaining.ToString()}";
- __instance.ButtonActive = remaining > 0;
- __instance.ClosedLid.gameObject.SetActive(!__instance.ButtonActive);
- __instance.OpenLid.gameObject.SetActive(__instance.ButtonActive);
- return;
- }
- }
- }
-
-
- [HarmonyPatch(typeof(Console), nameof(Console.CanUse))]
- public static class ConsoleCanUsePatch {
- public static bool Prefix(ref float __result, Console __instance, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) {
- canUse = couldUse = false;
- if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer)
- return !__instance.TaskTypes.Any(x => x == TaskTypes.FixLights || x == TaskTypes.FixComms);
- if (__instance.AllowImpostor) return true;
- if (!Helpers.hasFakeTasks(pc.Object)) return true;
- __result = float.MaxValue;
- return false;
- }
- }
-
- [HarmonyPatch(typeof(TuneRadioMinigame), nameof(TuneRadioMinigame.Begin))]
- class CommsMinigameBeginPatch {
- static void Postfix(TuneRadioMinigame __instance) {
- // Block Swapper from fixing comms. Still looking for a better way to do this, but deleting the task doesn't seem like a viable option since then the camera, admin table, ... work while comms are out
- if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer) {
- __instance.Close();
- }
- }
- }
-
- [HarmonyPatch(typeof(SwitchMinigame), nameof(SwitchMinigame.Begin))]
- class LightsMinigameBeginPatch {
- static void Postfix(SwitchMinigame __instance) {
- // Block Swapper from fixing lights. One could also just delete the PlayerTask, but I wanted to do it the same way as with coms for now.
- if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer) {
- __instance.Close();
- }
- }
- }
-
- [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 {
-
- 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 {
- static Dictionary<SystemTypes, List<Color>> players = new Dictionary<SystemTypes, List<Color>>();
-
- [HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))]
- class MapCountOverlayUpdatePatch {
- static bool Prefix(MapCountOverlay __instance) {
- // Save colors for the Hacker
- __instance.timer += Time.deltaTime;
- if (__instance.timer < 0.1f)
- {
- return false;
- }
- __instance.timer = 0f;
- players = new Dictionary<SystemTypes, List<Color>>();
- bool commsActive = false;
- foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
- if (task.TaskType == TaskTypes.FixComms) commsActive = true;
-
-
- if (!__instance.isSab && commsActive)
- {
- __instance.isSab = true;
- __instance.BackgroundColor.SetColor(Palette.DisabledGrey);
- __instance.SabotageText.gameObject.SetActive(true);
- return false;
- }
- if (__instance.isSab && !commsActive)
- {
- __instance.isSab = false;
- __instance.BackgroundColor.SetColor(Color.green);
- __instance.SabotageText.gameObject.SetActive(false);
- }
-
- for (int i = 0; i < __instance.CountAreas.Length; i++)
- {
- CounterArea counterArea = __instance.CountAreas[i];
- List<Color> roomColors = new List<Color>();
- players.Add(counterArea.RoomType, roomColors);
-
- if (!commsActive)
- {
- PlainShipRoom plainShipRoom = ShipStatus.Instance.FastRooms[counterArea.RoomType];
-
- if (plainShipRoom != null && plainShipRoom.roomArea)
- {
- int num = plainShipRoom.roomArea.OverlapCollider(__instance.filter, __instance.buffer);
- int num2 = num;
- for (int j = 0; j < num; j++)
- {
- Collider2D collider2D = __instance.buffer[j];
- if (!(collider2D.tag == "DeadBody"))
- {
- PlayerControl component = collider2D.GetComponent<PlayerControl>();
- if (!component || component.Data == null || component.Data.Disconnected || component.Data.IsDead)
- {
- num2--;
- } else if (component?.myRend?.material != null) {
- Color color = component.myRend.material.GetColor("_BodyColor");
- if (Hacker.onlyColorType) {
- var id = Mathf.Max(0, Palette.PlayerColors.IndexOf(color));
- color = Helpers.isLighterColor((byte)id) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
- }
- roomColors.Add(color);
- }
- } else {
- DeadBody component = collider2D.GetComponent<DeadBody>();
- if (component) {
- GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
- if (playerInfo != null) {
- var color = Palette.PlayerColors[playerInfo.ColorId];
- if (Hacker.onlyColorType)
- color = Helpers.isLighterColor(playerInfo.ColorId) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
- roomColors.Add(color);
- }
- }
- }
- }
- counterArea.UpdateCount(num2);
- }
- else
- {
- Debug.LogWarning("Couldn't find counter for:" + counterArea.RoomType);
- }
- }
- else
- {
- counterArea.UpdateCount(0);
- }
- }
- return false;
- }
- }
-
- [HarmonyPatch(typeof(CounterArea), nameof(CounterArea.UpdateCount))]
- class CounterAreaUpdateCountPatch {
- private static Material defaultMat;
- private static Material newMat;
- static void Postfix(CounterArea __instance) {
- // Hacker display saved colors on the admin panel
- bool showHackerInfo = Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0;
- if (players.ContainsKey(__instance.RoomType)) {
- List<Color> colors = players[__instance.RoomType];
-
- for (int i = 0; i < __instance.myIcons.Count; i++) {
- PoolableBehavior icon = __instance.myIcons[i];
- SpriteRenderer renderer = icon.GetComponent<SpriteRenderer>();
-
- if (renderer != null) {
- if (defaultMat == null) defaultMat = renderer.material;
- if (newMat == null) newMat = UnityEngine.Object.Instantiate<Material>(defaultMat);
- if (showHackerInfo && colors.Count > i) {
- renderer.material = newMat;
- var color = colors[i];
- renderer.material.SetColor("_BodyColor", color);
- var id = Palette.PlayerColors.IndexOf(color);
- if (id < 0) {
- renderer.material.SetColor("_BackColor", color);
- } else {
- renderer.material.SetColor("_BackColor", Palette.ShadowColors[id]);
- }
- renderer.material.SetColor("_VisorColor", Palette.VisorColor);
- } else {
- renderer.material = defaultMat;
- }
- }
- }
- }
- }
- }
- }
-
- [HarmonyPatch]
- class SurveillanceMinigamePatch {
- private static int page = 0;
- private static float timer = 0f;
- [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Begin))]
- class SurveillanceMinigameBeginPatch {
- public static void Postfix(SurveillanceMinigame __instance) {
- // Add securityGuard cameras
- page = 0;
- timer = 0;
- if (ShipStatus.Instance.AllCameras.Length > 4 && __instance.FilteredRooms.Length > 0) {
- __instance.textures = __instance.textures.ToList().Concat(new RenderTexture[ShipStatus.Instance.AllCameras.Length - 4]).ToArray();
- for (int i = 4; i < ShipStatus.Instance.AllCameras.Length; i++) {
- SurvCamera surv = ShipStatus.Instance.AllCameras[i];
- Camera camera = UnityEngine.Object.Instantiate<Camera>(__instance.CameraPrefab);
- camera.transform.SetParent(__instance.transform);
- camera.transform.position = new Vector3(surv.transform.position.x, surv.transform.position.y, 8f);
- camera.orthographicSize = 2.35f;
- RenderTexture temporary = RenderTexture.GetTemporary(256, 256, 16, 0);
- __instance.textures[i] = temporary;
- camera.targetTexture = temporary;
- }
- }
- }
- }
-
- [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Update))]
- class SurveillanceMinigameUpdatePatch {
-
- public static bool Prefix(SurveillanceMinigame __instance) {
- // Update normal and securityGuard cameras
- timer += Time.deltaTime;
- int numberOfPages = Mathf.CeilToInt(ShipStatus.Instance.AllCameras.Length / 4f);
-
- bool update = false;
-
- if (timer > 3f || Input.GetKeyDown(KeyCode.RightArrow)) {
- update = true;
- timer = 0f;
- page = (page + 1) % numberOfPages;
- } else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
- page = (page + numberOfPages - 1) % numberOfPages;
- update = true;
- timer = 0f;
- }
-
- if ((__instance.isStatic || update) && !PlayerTask.PlayerHasTaskOfType<IHudOverrideTask>(PlayerControl.LocalPlayer)) {
- __instance.isStatic = false;
- for (int i = 0; i < __instance.ViewPorts.Length; i++) {
- __instance.ViewPorts[i].sharedMaterial = __instance.DefaultMaterial;
- __instance.SabText[i].gameObject.SetActive(false);
- if (page * 4 + i < __instance.textures.Length)
- __instance.ViewPorts[i].material.SetTexture("_MainTex", __instance.textures[page * 4 + i]);
- else
- __instance.ViewPorts[i].sharedMaterial = __instance.StaticMaterial;
- }
- } else if (!__instance.isStatic && PlayerTask.PlayerHasTaskOfType<HudOverrideTask>(PlayerControl.LocalPlayer)) {
- __instance.isStatic = true;
- for (int j = 0; j < __instance.ViewPorts.Length; j++) {
- __instance.ViewPorts[j].sharedMaterial = __instance.StaticMaterial;
- __instance.SabText[j].gameObject.SetActive(true);
- }
- }
- return false;
- }
- }
- }
-}
\ No newline at end of file
--- /dev/null
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TheOtherRoles", "TheOtherRoles\TheOtherRoles.csproj", "{11FBC798-BAF5-4EE5-9511-BE6DB0592F99}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {11FBC798-BAF5-4EE5-9511-BE6DB0592F99}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
--- /dev/null
+using HarmonyLib;
+using Hazel;
+using System;
+using UnityEngine;
+using static TheOtherRoles.TheOtherRoles;
+using TheOtherRoles.Objects;
+
+namespace TheOtherRoles
+{
+ [HarmonyPatch(typeof(HudManager), nameof(HudManager.Start))]
+ static class HudManagerStartPatch
+ {
+ private static CustomButton engineerRepairButton;
+ private static CustomButton janitorCleanButton;
+ private static CustomButton sheriffKillButton;
+ private static CustomButton timeMasterShieldButton;
+ private static CustomButton medicShieldButton;
+ private static CustomButton shifterShiftButton;
+ private static CustomButton morphlingButton;
+ private static CustomButton camouflagerButton;
+ private static CustomButton hackerButton;
+ private static CustomButton trackerButton;
+ private static CustomButton vampireKillButton;
+ private static CustomButton garlicButton;
+ private static CustomButton jackalKillButton;
+ private static CustomButton sidekickKillButton;
+ private static CustomButton jackalSidekickButton;
+ private static CustomButton lighterButton;
+ private static CustomButton eraserButton;
+ private static CustomButton placeJackInTheBoxButton;
+ private static CustomButton lightsOutButton;
+ public static CustomButton cleanerCleanButton;
+ public static CustomButton warlockCurseButton;
+ public static CustomButton securityGuardButton;
+ public static CustomButton arsonistButton;
+ public static TMPro.TMP_Text securityGuardButtonScrewsText;
+
+ public static void setCustomButtonCooldowns() {
+ engineerRepairButton.MaxTimer = 0f;
+ janitorCleanButton.MaxTimer = Janitor.cooldown;
+ sheriffKillButton.MaxTimer = Sheriff.cooldown;
+ timeMasterShieldButton.MaxTimer = TimeMaster.cooldown;
+ medicShieldButton.MaxTimer = 0f;
+ shifterShiftButton.MaxTimer = 0f;
+ morphlingButton.MaxTimer = Morphling.cooldown;
+ camouflagerButton.MaxTimer = Camouflager.cooldown;
+ hackerButton.MaxTimer = Hacker.cooldown;
+ vampireKillButton.MaxTimer = Vampire.cooldown;
+ trackerButton.MaxTimer = 0f;
+ garlicButton.MaxTimer = 0f;
+ jackalKillButton.MaxTimer = Jackal.cooldown;
+ sidekickKillButton.MaxTimer = Sidekick.cooldown;
+ jackalSidekickButton.MaxTimer = Jackal.createSidekickCooldown;
+ lighterButton.MaxTimer = Lighter.cooldown;
+ eraserButton.MaxTimer = Eraser.cooldown;
+ placeJackInTheBoxButton.MaxTimer = Trickster.placeBoxCooldown;
+ lightsOutButton.MaxTimer = Trickster.lightsOutCooldown;
+ cleanerCleanButton.MaxTimer = Cleaner.cooldown;
+ warlockCurseButton.MaxTimer = Warlock.cooldown;
+ securityGuardButton.MaxTimer = SecurityGuard.cooldown;
+ arsonistButton.MaxTimer = Arsonist.cooldown;
+
+ timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
+ hackerButton.EffectDuration = Hacker.duration;
+ vampireKillButton.EffectDuration = Vampire.delay;
+ lighterButton.EffectDuration = Lighter.duration;
+ camouflagerButton.EffectDuration = Camouflager.duration;
+ morphlingButton.EffectDuration = Morphling.duration;
+ lightsOutButton.EffectDuration = Trickster.lightsOutDuration;
+ arsonistButton.EffectDuration = Arsonist.duration;
+
+ // Already set the timer to the max, as the button is enabled during the game and not available at the start
+ lightsOutButton.Timer = lightsOutButton.MaxTimer;
+ }
+
+ public static void resetTimeMasterButton() {
+ timeMasterShieldButton.Timer = timeMasterShieldButton.MaxTimer;
+ timeMasterShieldButton.isEffectActive = false;
+ timeMasterShieldButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ }
+
+ public static void Postfix(HudManager __instance)
+ {
+ // Engineer Repair
+ engineerRepairButton = new CustomButton(
+ () => {
+ engineerRepairButton.Timer = 0f;
+
+ MessageWriter usedRepairWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerUsedRepair, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(usedRepairWriter);
+ RPCProcedure.engineerUsedRepair();
+
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks) {
+ if (task.TaskType == TaskTypes.FixLights) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.EngineerFixLights, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.engineerFixLights();
+ } else if (task.TaskType == TaskTypes.RestoreOxy) {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, 0 | 64);
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.LifeSupp, 1 | 64);
+ } else if (task.TaskType == TaskTypes.ResetReactor) {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 16);
+ } else if (task.TaskType == TaskTypes.ResetSeismic) {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Laboratory, 16);
+ } else if (task.TaskType == TaskTypes.FixComms) {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 16 | 0);
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Comms, 16 | 1);
+ } else if (task.TaskType == TaskTypes.StopCharles) {
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 0 | 16);
+ ShipStatus.Instance.RpcRepairSystem(SystemTypes.Reactor, 1 | 16);
+ }
+ }
+ },
+ () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => {
+ bool sabotageActive = false;
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
+ if (task.TaskType == TaskTypes.FixLights || task.TaskType == TaskTypes.RestoreOxy || task.TaskType == TaskTypes.ResetReactor || task.TaskType == TaskTypes.ResetSeismic || task.TaskType == TaskTypes.FixComms || task.TaskType == TaskTypes.StopCharles)
+ sabotageActive = true;
+ return sabotageActive && !Engineer.usedRepair && PlayerControl.LocalPlayer.CanMove;
+ },
+ () => {},
+ Engineer.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Janitor Clean
+ janitorCleanButton = new CustomButton(
+ () => {
+ foreach (Collider2D collider2D in Physics2D.OverlapCircleAll(PlayerControl.LocalPlayer.GetTruePosition(), PlayerControl.LocalPlayer.MaxReportDistance, Constants.PlayersOnlyMask)) {
+ if (collider2D.tag == "DeadBody")
+ {
+ DeadBody component = collider2D.GetComponent<DeadBody>();
+ if (component && !component.Reported)
+ {
+ Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
+ Vector2 truePosition2 = component.TruePosition;
+ if (Vector2.Distance(truePosition2, truePosition) <= PlayerControl.LocalPlayer.MaxReportDistance && PlayerControl.LocalPlayer.CanMove && !PhysicsHelpers.AnythingBetween(truePosition, truePosition2, Constants.ShipAndObjectsMask, false))
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
+ writer.Write(playerInfo.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.cleanBody(playerInfo.PlayerId);
+ janitorCleanButton.Timer = janitorCleanButton.MaxTimer;
+
+ break;
+ }
+ }
+ }
+ }
+ },
+ () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return __instance.ReportButton.renderer.color == Palette.EnabledColor && PlayerControl.LocalPlayer.CanMove; },
+ () => { janitorCleanButton.Timer = janitorCleanButton.MaxTimer; },
+ Janitor.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Sheriff Kill
+ sheriffKillButton = new CustomButton(
+ () => {
+ if (Medic.shielded != null && Medic.shielded == Sheriff.currentTarget) {
+ MessageWriter attemptWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(attemptWriter);
+ RPCProcedure.shieldedMurderAttempt();
+ return;
+ }
+
+ 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)) {
+ targetId = Sheriff.currentTarget.PlayerId;
+ }
+ else {
+ targetId = PlayerControl.LocalPlayer.PlayerId;
+ }
+ MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SheriffKill, Hazel.SendOption.Reliable, -1);
+ killWriter.Write(targetId);
+ AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+ RPCProcedure.sheriffKill(targetId);
+
+ sheriffKillButton.Timer = sheriffKillButton.MaxTimer;
+ Sheriff.currentTarget = null;
+ },
+ () => { return Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Sheriff.currentTarget && PlayerControl.LocalPlayer.CanMove; },
+ () => { sheriffKillButton.Timer = sheriffKillButton.MaxTimer;},
+ __instance.KillButton.renderer.sprite,
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Time Master Rewind Time
+ timeMasterShieldButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TimeMasterShield, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.timeMasterShield();
+ },
+ () => { return TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ timeMasterShieldButton.Timer = timeMasterShieldButton.MaxTimer;
+ timeMasterShieldButton.isEffectActive = false;
+ timeMasterShieldButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ },
+ TimeMaster.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q,
+ true,
+ TimeMaster.shieldDuration,
+ () => { timeMasterShieldButton.Timer = timeMasterShieldButton.MaxTimer; }
+ );
+
+ // Medic Shield
+ medicShieldButton = new CustomButton(
+ () => {
+ medicShieldButton.Timer = 0f;
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MedicSetShielded, Hazel.SendOption.Reliable, -1);
+ writer.Write(Medic.currentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.medicSetShielded(Medic.currentTarget.PlayerId);
+ },
+ () => { return Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return !Medic.usedShield && Medic.currentTarget && PlayerControl.LocalPlayer.CanMove; },
+ () => {},
+ Medic.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+
+ // Shifter shift
+ shifterShiftButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetFutureShifted, Hazel.SendOption.Reliable, -1);
+ writer.Write(Shifter.currentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.setFutureShifted(Shifter.currentTarget.PlayerId);
+ },
+ () => { return Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Shifter.currentTarget && Shifter.futureShift == null && PlayerControl.LocalPlayer.CanMove; },
+ () => { },
+ Shifter.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Morphling morph
+ morphlingButton = new CustomButton(
+ () => {
+ if (Morphling.sampledTarget != null) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.MorphlingMorph, Hazel.SendOption.Reliable, -1);
+ writer.Write(Morphling.sampledTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.morphlingMorph(Morphling.sampledTarget.PlayerId);
+ Morphling.sampledTarget = null;
+ morphlingButton.EffectDuration = Morphling.duration;
+ } else if (Morphling.currentTarget != null) {
+ Morphling.sampledTarget = Morphling.currentTarget;
+ morphlingButton.Sprite = Morphling.getMorphSprite();
+ morphlingButton.EffectDuration = 1f;
+ }
+ },
+ () => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return (Morphling.currentTarget || Morphling.sampledTarget) && PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ morphlingButton.Timer = morphlingButton.MaxTimer;
+ morphlingButton.Sprite = Morphling.getSampleSprite();
+ morphlingButton.isEffectActive = false;
+ morphlingButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ Morphling.sampledTarget = null;
+ },
+ Morphling.getSampleSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F,
+ true,
+ Morphling.duration,
+ () => {
+ if (Morphling.sampledTarget == null) {
+ morphlingButton.Timer = morphlingButton.MaxTimer;
+ morphlingButton.Sprite = Morphling.getSampleSprite();
+ }
+ }
+ );
+
+ // Camouflager camouflage
+ camouflagerButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CamouflagerCamouflage, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.camouflagerCamouflage();
+ },
+ () => { return Camouflager.camouflager != null && Camouflager.camouflager == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ camouflagerButton.Timer = camouflagerButton.MaxTimer;
+ camouflagerButton.isEffectActive = false;
+ camouflagerButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ },
+ Camouflager.getButtonSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F,
+ true,
+ Camouflager.duration,
+ () => { camouflagerButton.Timer = camouflagerButton.MaxTimer; }
+ );
+
+ // Hacker button
+ hackerButton = new CustomButton(
+ () => {
+ Hacker.hackerTimer = Hacker.duration;
+ },
+ () => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ hackerButton.Timer = hackerButton.MaxTimer;
+ hackerButton.isEffectActive = false;
+ hackerButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ },
+ Hacker.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q,
+ true,
+ 0f,
+ () => {
+ hackerButton.Timer = hackerButton.MaxTimer;
+ }
+ );
+
+ // Tracker button
+ trackerButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TrackerUsedTracker, Hazel.SendOption.Reliable, -1);
+ writer.Write(Tracker.currentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.trackerUsedTracker(Tracker.currentTarget.PlayerId);
+ },
+ () => { return Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove && Tracker.currentTarget != null && !Tracker.usedTracker; },
+ () => { },
+ Tracker.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ vampireKillButton = new CustomButton(
+ () => {
+ if (Helpers.handleMurderAttempt(Vampire.currentTarget)) {
+ if (Vampire.targetNearGarlic) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
+ writer.Write(Vampire.vampire.PlayerId);
+ writer.Write(Vampire.currentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.uncheckedMurderPlayer(Vampire.vampire.PlayerId, Vampire.currentTarget.PlayerId);
+
+ vampireKillButton.HasEffect = false; // Block effect on this click
+ vampireKillButton.Timer = vampireKillButton.MaxTimer;
+ } else {
+ Vampire.bitten = Vampire.currentTarget;
+ // Notify players about bitten
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
+ writer.Write(Vampire.bitten.PlayerId);
+ writer.Write(0);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0);
+
+ HudManager.Instance.StartCoroutine(Effects.Lerp(Vampire.delay, new Action<float>((p) => { // Delayed action
+ if (p == 1f) {
+ if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.bitten)) {
+ // Perform kill
+ MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireTryKill, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+ RPCProcedure.vampireTryKill();
+ } else {
+ // Notify players about clearing bitten
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
+ writer.Write(byte.MaxValue);
+ writer.Write(byte.MaxValue);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue);
+ }
+ }
+ })));
+
+ vampireKillButton.HasEffect = true; // Trigger effect on this click
+ }
+ } else {
+ vampireKillButton.HasEffect = false; // Block effect if no action was fired
+ }
+ },
+ () => { return Vampire.vampire != null && Vampire.vampire == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => {
+ if (Vampire.targetNearGarlic && Vampire.canKillNearGarlics)
+ vampireKillButton.killButtonManager.renderer.sprite = __instance.KillButton.renderer.sprite;
+ else
+ vampireKillButton.killButtonManager.renderer.sprite = Vampire.getButtonSprite();
+ return Vampire.currentTarget != null && PlayerControl.LocalPlayer.CanMove && (!Vampire.targetNearGarlic || Vampire.canKillNearGarlics);
+ },
+ () => {
+ vampireKillButton.Timer = vampireKillButton.MaxTimer;
+ vampireKillButton.isEffectActive = false;
+ vampireKillButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ },
+ Vampire.getButtonSprite(),
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q,
+ false,
+ 0f,
+ () => {
+ vampireKillButton.Timer = vampireKillButton.MaxTimer;
+ }
+ );
+
+ garlicButton = new CustomButton(
+ () => {
+ Vampire.localPlacedGarlic = true;
+ var pos = PlayerControl.LocalPlayer.transform.position;
+ byte[] buff = new byte[sizeof(float) * 2];
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceGarlic, Hazel.SendOption.Reliable);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.placeGarlic(buff);
+ },
+ () => { return !Vampire.localPlacedGarlic && !PlayerControl.LocalPlayer.Data.IsDead && Vampire.garlicsActive; },
+ () => { return PlayerControl.LocalPlayer.CanMove && !Vampire.localPlacedGarlic; },
+ () => { },
+ Vampire.getGarlicButtonSprite(),
+ Vector3.zero,
+ __instance,
+ null,
+ true
+ );
+
+
+ // Jackal Sidekick Button
+ jackalSidekickButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalCreatesSidekick, Hazel.SendOption.Reliable, -1);
+ writer.Write(Jackal.currentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.jackalCreatesSidekick(Jackal.currentTarget.PlayerId);
+ },
+ () => { 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),
+ __instance,
+ KeyCode.F
+ );
+
+ // Jackal Kill
+ jackalKillButton = new CustomButton(
+ () => {
+ if (!Helpers.handleMurderAttempt(Jackal.currentTarget)) return;
+ byte targetId = Jackal.currentTarget.PlayerId;
+ MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.JackalKill, Hazel.SendOption.Reliable, -1);
+ killWriter.Write(targetId);
+ AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+ RPCProcedure.jackalKill(targetId);
+ jackalKillButton.Timer = jackalKillButton.MaxTimer;
+ Jackal.currentTarget = null;
+ },
+ () => { return Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Jackal.currentTarget && PlayerControl.LocalPlayer.CanMove; },
+ () => { jackalKillButton.Timer = jackalKillButton.MaxTimer;},
+ __instance.KillButton.renderer.sprite,
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Sidekick Kill
+ sidekickKillButton = new CustomButton(
+ () => {
+ if (!Helpers.handleMurderAttempt(Sidekick.currentTarget)) return;
+ byte targetId = Sidekick.currentTarget.PlayerId;
+ MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickKill, Hazel.SendOption.Reliable, -1);
+ killWriter.Write(targetId);
+ AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+ RPCProcedure.sidekickKill(targetId);
+
+ sidekickKillButton.Timer = sidekickKillButton.MaxTimer;
+ Sidekick.currentTarget = null;
+ },
+ () => { return Sidekick.canKill && Sidekick.sidekick != null && Sidekick.sidekick == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Sidekick.currentTarget && PlayerControl.LocalPlayer.CanMove; },
+ () => { sidekickKillButton.Timer = sidekickKillButton.MaxTimer;},
+ __instance.KillButton.renderer.sprite,
+ new Vector3(-1.3f, 0, 0),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Lighter light
+ lighterButton = new CustomButton(
+ () => {
+ Lighter.lighterTimer = Lighter.duration;
+ },
+ () => { return Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ lighterButton.Timer = lighterButton.MaxTimer;
+ lighterButton.isEffectActive = false;
+ lighterButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ },
+ Lighter.getButtonSprite(),
+ new Vector3(-1.3f, 0f, 0f),
+ __instance,
+ KeyCode.Q,
+ true,
+ Lighter.duration,
+ () => { lighterButton.Timer = lighterButton.MaxTimer; }
+ );
+
+ // Eraser erase button
+ eraserButton = new CustomButton(
+ () => {
+ eraserButton.MaxTimer += 10;
+ eraserButton.Timer = eraserButton.MaxTimer;
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetFutureErased, Hazel.SendOption.Reliable, -1);
+ writer.Write(Eraser.currentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.setFutureErased(Eraser.currentTarget.PlayerId);
+ },
+ () => { return Eraser.eraser != null && Eraser.eraser == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove && Eraser.currentTarget != null; },
+ () => { eraserButton.Timer = eraserButton.MaxTimer;},
+ Eraser.getButtonSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F
+ );
+
+ placeJackInTheBoxButton = new CustomButton(
+ () => {
+ placeJackInTheBoxButton.Timer = placeJackInTheBoxButton.MaxTimer;
+
+ var pos = PlayerControl.LocalPlayer.transform.position;
+ byte[] buff = new byte[sizeof(float) * 2];
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceJackInTheBox, Hazel.SendOption.Reliable);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.placeJackInTheBox(buff);
+ },
+ () => { return Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && !JackInTheBox.hasJackInTheBoxLimitReached(); },
+ () => { return PlayerControl.LocalPlayer.CanMove && !JackInTheBox.hasJackInTheBoxLimitReached(); },
+ () => { placeJackInTheBoxButton.Timer = placeJackInTheBoxButton.MaxTimer;},
+ Trickster.getPlaceBoxButtonSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F
+ );
+
+ lightsOutButton = new CustomButton(
+ () => {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.LightsOut, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.lightsOut();
+ },
+ () => { return Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; },
+ () => { return PlayerControl.LocalPlayer.CanMove && JackInTheBox.hasJackInTheBoxLimitReached() && JackInTheBox.boxesConvertedToVents; },
+ () => {
+ lightsOutButton.Timer = lightsOutButton.MaxTimer;
+ lightsOutButton.isEffectActive = false;
+ lightsOutButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ },
+ Trickster.getLightsOutButtonSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F,
+ true,
+ Trickster.lightsOutDuration,
+ () => { lightsOutButton.Timer = lightsOutButton.MaxTimer; }
+ );
+ // Cleaner Clean
+ cleanerCleanButton = new CustomButton(
+ () => {
+ foreach (Collider2D collider2D in Physics2D.OverlapCircleAll(PlayerControl.LocalPlayer.GetTruePosition(), PlayerControl.LocalPlayer.MaxReportDistance, Constants.PlayersOnlyMask)) {
+ if (collider2D.tag == "DeadBody")
+ {
+ DeadBody component = collider2D.GetComponent<DeadBody>();
+ if (component && !component.Reported)
+ {
+ Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
+ Vector2 truePosition2 = component.TruePosition;
+ if (Vector2.Distance(truePosition2, truePosition) <= PlayerControl.LocalPlayer.MaxReportDistance && PlayerControl.LocalPlayer.CanMove && !PhysicsHelpers.AnythingBetween(truePosition, truePosition2, Constants.ShipAndObjectsMask, false))
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.CleanBody, Hazel.SendOption.Reliable, -1);
+ writer.Write(playerInfo.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.cleanBody(playerInfo.PlayerId);
+
+ Cleaner.cleaner.killTimer = cleanerCleanButton.Timer = cleanerCleanButton.MaxTimer;
+ break;
+ }
+ }
+ }
+ }
+ },
+ () => { return Cleaner.cleaner != null && Cleaner.cleaner == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return __instance.ReportButton.renderer.color == Palette.EnabledColor && PlayerControl.LocalPlayer.CanMove; },
+ () => { cleanerCleanButton.Timer = cleanerCleanButton.MaxTimer; },
+ Cleaner.getButtonSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F
+ );
+
+ // Warlock curse
+ warlockCurseButton = new CustomButton(
+ () => {
+ if (Warlock.curseVictim == null) {
+ // Apply Curse
+ Warlock.curseVictim = Warlock.currentTarget;
+ warlockCurseButton.Sprite = Warlock.getCurseKillButtonSprite();
+ warlockCurseButton.Timer = 1f;
+ } else if (Warlock.curseVictim != null && Warlock.curseVictimTarget != null && Helpers.handleMurderAttempt(Warlock.curseVictimTarget)) {
+ // Curse Kill
+ Warlock.curseKillTarget = Warlock.curseVictimTarget;
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.WarlockCurseKill, Hazel.SendOption.Reliable, -1);
+ writer.Write(Warlock.curseKillTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.warlockCurseKill(Warlock.curseKillTarget.PlayerId);
+
+ Warlock.curseVictim = null;
+ Warlock.curseVictimTarget = null;
+ warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
+ Warlock.warlock.killTimer = warlockCurseButton.Timer = warlockCurseButton.MaxTimer;
+
+ if(Warlock.rootTime > 0) {
+ PlayerControl.LocalPlayer.moveable = false;
+ PlayerControl.LocalPlayer.NetTransform.Halt(); // Stop current movement so the warlock is not just running straight into the next object
+ HudManager.Instance.StartCoroutine(Effects.Lerp(Warlock.rootTime, new Action<float>((p) => { // Delayed action
+ if (p == 1f) {
+ PlayerControl.LocalPlayer.moveable = true;
+ }
+ })));
+ }
+ }
+ },
+ () => { return Warlock.warlock != null && Warlock.warlock == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return ((Warlock.curseVictim == null && Warlock.currentTarget != null) || (Warlock.curseVictim != null && Warlock.curseVictimTarget != null)) && PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ warlockCurseButton.Timer = warlockCurseButton.MaxTimer;
+ warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
+ Warlock.curseVictim = null;
+ Warlock.curseVictimTarget = null;
+ },
+ Warlock.getCurseButtonSprite(),
+ new Vector3(-1.3f, 1.3f, 0f),
+ __instance,
+ KeyCode.F
+ );
+
+ // Security Guard button
+ securityGuardButton = new CustomButton(
+ () => {
+ if (SecurityGuard.ventTarget != null) { // Seal vent
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SealVent, Hazel.SendOption.Reliable);
+ writer.WritePacked(SecurityGuard.ventTarget.Id);
+ writer.EndMessage();
+ RPCProcedure.sealVent(SecurityGuard.ventTarget.Id);
+ SecurityGuard.ventTarget = null;
+ } else if (PlayerControl.GameOptions.MapId != 1) { // Place camera if there's no vent and it's not MiraHQ
+ var pos = PlayerControl.LocalPlayer.transform.position;
+ byte[] buff = new byte[sizeof(float) * 2];
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.x), 0, buff, 0*sizeof(float), sizeof(float));
+ Buffer.BlockCopy(BitConverter.GetBytes(pos.y), 0, buff, 1*sizeof(float), sizeof(float));
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.PlaceCamera, Hazel.SendOption.Reliable);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.placeCamera(buff);
+ }
+ securityGuardButton.Timer = securityGuardButton.MaxTimer;
+ },
+ () => { return SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead && SecurityGuard.remainingScrews >= Mathf.Min(SecurityGuard.ventPrice, SecurityGuard.camPrice); },
+ () => {
+ securityGuardButton.killButtonManager.renderer.sprite = (SecurityGuard.ventTarget == null && PlayerControl.GameOptions.MapId != 1) ? SecurityGuard.getPlaceCameraButtonSprite() : SecurityGuard.getCloseVentButtonSprite();
+ if (securityGuardButtonScrewsText != null) securityGuardButtonScrewsText.text = $"{SecurityGuard.remainingScrews}/{SecurityGuard.totalScrews}";
+
+ if (SecurityGuard.ventTarget != null)
+ return SecurityGuard.remainingScrews >= SecurityGuard.ventPrice && PlayerControl.LocalPlayer.CanMove;
+ return PlayerControl.GameOptions.MapId != 1 && SecurityGuard.remainingScrews >= SecurityGuard.camPrice && PlayerControl.LocalPlayer.CanMove;
+ },
+ () => { securityGuardButton.Timer = securityGuardButton.MaxTimer; },
+ SecurityGuard.getPlaceCameraButtonSprite(),
+ new Vector3(-1.3f, 0f, 0f),
+ __instance,
+ KeyCode.Q
+ );
+
+ // Security Guard button screws counter
+ securityGuardButtonScrewsText = GameObject.Instantiate(securityGuardButton.killButtonManager.TimerText, securityGuardButton.killButtonManager.TimerText.transform.parent);
+ securityGuardButtonScrewsText.text = "";
+ securityGuardButtonScrewsText.enableWordWrapping = false;
+ securityGuardButtonScrewsText.transform.localScale = Vector3.one * 0.5f;
+ securityGuardButtonScrewsText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
+ // Arsonist button
+ arsonistButton = new CustomButton(
+ () => {
+ bool dousedEveryoneAlive = Arsonist.dousedEveryoneAlive();
+ if (dousedEveryoneAlive) {
+ MessageWriter winWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ArsonistWin, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(winWriter);
+ RPCProcedure.arsonistWin();
+ arsonistButton.HasEffect = false;
+ } else if (Arsonist.currentTarget != null) {
+ Arsonist.douseTarget = Arsonist.currentTarget;
+ arsonistButton.HasEffect = true;
+ }
+ },
+ () => { return Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => {
+ bool dousedEveryoneAlive = Arsonist.dousedEveryoneAlive();
+ if (dousedEveryoneAlive) arsonistButton.killButtonManager.renderer.sprite = Arsonist.getIgniteSprite();
+
+ if (arsonistButton.isEffectActive && Arsonist.douseTarget != Arsonist.currentTarget) {
+ Arsonist.douseTarget = null;
+ arsonistButton.Timer = 0f;
+ arsonistButton.isEffectActive = false;
+ }
+
+ return PlayerControl.LocalPlayer.CanMove && (dousedEveryoneAlive || Arsonist.currentTarget != null);
+ },
+ () => {
+ arsonistButton.Timer = arsonistButton.MaxTimer;
+ arsonistButton.isEffectActive = false;
+ Arsonist.douseTarget = null;
+ },
+ Arsonist.getDouseSprite(),
+ new Vector3(-1.3f, 0f, 0f),
+ __instance,
+ KeyCode.Q,
+ true,
+ Arsonist.duration,
+ () => {
+ if (Arsonist.douseTarget != null) Arsonist.dousedPlayers.Add(Arsonist.douseTarget);
+ Arsonist.douseTarget = null;
+ arsonistButton.Timer = Arsonist.dousedEveryoneAlive() ? 0 : arsonistButton.MaxTimer;
+
+ foreach (PlayerControl p in Arsonist.dousedPlayers) {
+ if (MapOptions.playerIcons.ContainsKey(p.PlayerId)) {
+ MapOptions.playerIcons[p.PlayerId].setSemiTransparent(false);
+ }
+ }
+ }
+ );
+
+ // Set the default (or settings from the previous game) timers/durations when spawning the buttons
+ setCustomButtonCooldowns();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System.Collections.Generic;
+using UnityEngine;
+using BepInEx.Configuration;
+using System;
+using System.Linq;
+using HarmonyLib;
+using Hazel;
+using System.Reflection;
+using System.Text;
+using static TheOtherRoles.TheOtherRoles;
+
+namespace TheOtherRoles {
+ public class CustomOptionHolder {
+ public static string[] rates = new string[]{"0%", "10%", "20%", "30%", "40%", "50%", "60%", "70%", "80%", "90%", "100%"};
+ public static string[] presets = new string[]{"Preset 1", "Preset 2", "Preset 3", "Preset 4", "Preset 5"};
+
+ public static CustomOption presetSelection;
+ public static CustomOption crewmateRolesCountMin;
+ public static CustomOption crewmateRolesCountMax;
+ public static CustomOption neutralRolesCountMin;
+ public static CustomOption neutralRolesCountMax;
+ public static CustomOption impostorRolesCountMin;
+ public static CustomOption impostorRolesCountMax;
+
+ public static CustomOption mafiaSpawnRate;
+ public static CustomOption janitorCooldown;
+
+ public static CustomOption morphlingSpawnRate;
+ public static CustomOption morphlingCooldown;
+ public static CustomOption morphlingDuration;
+
+ public static CustomOption camouflagerSpawnRate;
+ public static CustomOption camouflagerCooldown;
+ public static CustomOption camouflagerDuration;
+
+ public static CustomOption vampireSpawnRate;
+ public static CustomOption vampireKillDelay;
+ public static CustomOption vampireCooldown;
+ public static CustomOption vampireCanKillNearGarlics;
+
+ public static CustomOption eraserSpawnRate;
+ public static CustomOption eraserCooldown;
+ public static CustomOption eraserCanEraseAnyone;
+
+ public static CustomOption miniSpawnRate;
+ public static CustomOption miniGrowingUpDuration;
+
+ public static CustomOption loversSpawnRate;
+ public static CustomOption loversImpLoverRate;
+ public static CustomOption loversBothDie;
+ public static CustomOption loversCanHaveAnotherRole;
+
+ public static CustomOption guesserSpawnRate;
+ public static CustomOption guesserIsImpGuesserRate;
+ public static CustomOption guesserNumberOfShots;
+
+ public static CustomOption jesterSpawnRate;
+ public static CustomOption jesterCanCallEmergency;
+ public static CustomOption jesterCanSabotage;
+
+ public static CustomOption arsonistSpawnRate;
+ public static CustomOption arsonistCooldown;
+ public static CustomOption arsonistDuration;
+
+ public static CustomOption jackalSpawnRate;
+ public static CustomOption jackalKillCooldown;
+ public static CustomOption jackalCreateSidekickCooldown;
+ public static CustomOption jackalCanUseVents;
+ public static CustomOption jackalCanCreateSidekick;
+ public static CustomOption sidekickPromotesToJackal;
+ public static CustomOption sidekickCanKill;
+ public static CustomOption sidekickCanUseVents;
+ public static CustomOption jackalPromotedFromSidekickCanCreateSidekick;
+ public static CustomOption jackalCanCreateSidekickFromImpostor;
+ public static CustomOption 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;
+
+ public static CustomOption engineerSpawnRate;
+
+ public static CustomOption sheriffSpawnRate;
+ public static CustomOption sheriffCooldown;
+ public static CustomOption sheriffCanKillNeutrals;
+
+ public static CustomOption lighterSpawnRate;
+ public static CustomOption lighterModeLightsOnVision;
+ public static CustomOption lighterModeLightsOffVision;
+ public static CustomOption lighterCooldown;
+ public static CustomOption lighterDuration;
+
+ public static CustomOption detectiveSpawnRate;
+ public static CustomOption detectiveAnonymousFootprints;
+ public static CustomOption detectiveFootprintIntervall;
+ public static CustomOption detectiveFootprintDuration;
+ public static CustomOption detectiveReportNameDuration;
+ public static CustomOption detectiveReportColorDuration;
+
+ public static CustomOption timeMasterSpawnRate;
+ public static CustomOption timeMasterCooldown;
+ public static CustomOption timeMasterRewindTime;
+ public static CustomOption timeMasterShieldDuration;
+
+ public static CustomOption medicSpawnRate;
+ public static CustomOption medicShowShielded;
+ public static CustomOption medicShowAttemptToShielded;
+
+ public static CustomOption swapperSpawnRate;
+ public static CustomOption swapperCanCallEmergency;
+ public static CustomOption swapperCanOnlySwapOthers;
+
+ public static CustomOption seerSpawnRate;
+ public static CustomOption seerMode;
+ public static CustomOption seerSoulDuration;
+ public static CustomOption seerLimitSoulDuration;
+
+ public static CustomOption hackerSpawnRate;
+ public static CustomOption hackerCooldown;
+ public static CustomOption hackerHackeringDuration;
+ public static CustomOption hackerOnlyColorType;
+
+ public static CustomOption trackerSpawnRate;
+ public static CustomOption trackerUpdateIntervall;
+
+ public static CustomOption snitchSpawnRate;
+ public static CustomOption snitchLeftTasksForImpostors;
+
+ public static CustomOption spySpawnRate;
+ public static CustomOption spyCanDieToSheriff;
+ public static CustomOption spyImpostorsCanKillAnyone;
+ public static CustomOption spyCanEnterVents;
+ public static CustomOption spyHasImpostorVision;
+
+ public static CustomOption tricksterSpawnRate;
+ public static CustomOption tricksterPlaceBoxCooldown;
+ public static CustomOption tricksterLightsOutCooldown;
+ public static CustomOption tricksterLightsOutDuration;
+
+ public static CustomOption cleanerSpawnRate;
+ public static CustomOption cleanerCooldown;
+
+ public static CustomOption warlockSpawnRate;
+ public static CustomOption warlockCooldown;
+ public static CustomOption warlockRootTime;
+
+ public static CustomOption securityGuardSpawnRate;
+ public static CustomOption securityGuardCooldown;
+ public static CustomOption securityGuardTotalScrews;
+ public static CustomOption securityGuardCamPrice;
+ public static CustomOption securityGuardVentPrice;
+
+ public static CustomOption maxNumberOfMeetings;
+ public static CustomOption blockSkippingInEmergencyMeetings;
+ public static CustomOption noVoteIsSelfVote;
+ public static CustomOption hidePlayerNames;
+
+ internal static Dictionary<byte, byte[]> blockedRolePairings = new Dictionary<byte, byte[]>();
+
+ public static string cs(Color c, string s) {
+ return string.Format("<color=#{0:X2}{1:X2}{2:X2}{3:X2}>{4}</color>", ToByte(c.r), ToByte(c.g), ToByte(c.b), ToByte(c.a), s);
+ }
+
+ private static byte ToByte(float f) {
+ f = Mathf.Clamp01(f);
+ return (byte)(f * 255);
+ }
+
+ public static void Load() {
+
+ // Role Options
+ presetSelection = CustomOption.Create(0, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true);
+
+ // Using new id's for the options to not break compatibilty with older versions
+ crewmateRolesCountMin = CustomOption.Create(300, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Crewmate Roles"), 0f, 0f, 15f, 1f, null, true);
+ crewmateRolesCountMax = CustomOption.Create(301, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Crewmate Roles"), 0f, 0f, 15f, 1f);
+ neutralRolesCountMin = CustomOption.Create(302, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Neutral Roles"), 0f, 0f, 15f, 1f);
+ neutralRolesCountMax = CustomOption.Create(303, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Neutral Roles"), 0f, 0f, 15f, 1f);
+ impostorRolesCountMin = CustomOption.Create(304, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Minimum Impostor Roles"), 0f, 0f, 3f, 1f);
+ impostorRolesCountMax = CustomOption.Create(305, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Maximum Impostor Roles"), 0f, 0f, 3f, 1f);
+
+ mafiaSpawnRate = CustomOption.Create(10, cs(Janitor.color, "Mafia"), rates, null, true);
+ janitorCooldown = CustomOption.Create(11, "Janitor Cooldown", 30f, 10f, 60f, 2.5f, mafiaSpawnRate);
+
+ morphlingSpawnRate = CustomOption.Create(20, cs(Morphling.color, "Morphling"), rates, null, true);
+ morphlingCooldown = CustomOption.Create(21, "Morphling Cooldown", 30f, 10f, 60f, 2.5f, morphlingSpawnRate);
+ morphlingDuration = CustomOption.Create(22, "Morph Duration", 10f, 1f, 20f, 0.5f, morphlingSpawnRate);
+
+ camouflagerSpawnRate = CustomOption.Create(30, cs(Camouflager.color, "Camouflager"), rates, null, true);
+ camouflagerCooldown = CustomOption.Create(31, "Camouflager Cooldown", 30f, 10f, 60f, 2.5f, camouflagerSpawnRate);
+ camouflagerDuration = CustomOption.Create(32, "Camo Duration", 10f, 1f, 20f, 0.5f, camouflagerSpawnRate);
+
+ vampireSpawnRate = CustomOption.Create(40, cs(Vampire.color, "Vampire"), rates, null, true);
+ vampireKillDelay = CustomOption.Create(41, "Vampire Kill Delay", 10f, 1f, 20f, 1f, vampireSpawnRate);
+ vampireCooldown = CustomOption.Create(42, "Vampire Cooldown", 30f, 10f, 60f, 2.5f, vampireSpawnRate);
+ vampireCanKillNearGarlics = CustomOption.Create(43, "Vampire Can Kill Near Garlics", true, vampireSpawnRate);
+
+ eraserSpawnRate = CustomOption.Create(230, cs(Eraser.color, "Eraser"), rates, null, true);
+ eraserCooldown = CustomOption.Create(231, "Eraser Cooldown", 30f, 10f, 120f, 5f, eraserSpawnRate);
+ eraserCanEraseAnyone = CustomOption.Create(232, "Eraser Can Erase Anyone", false, eraserSpawnRate);
+
+ tricksterSpawnRate = CustomOption.Create(250, cs(Trickster.color, "Trickster"), rates, null, true);
+ tricksterPlaceBoxCooldown = CustomOption.Create(251, "Trickster Box Cooldown", 10f, 0f, 30f, 2.5f, tricksterSpawnRate);
+ tricksterLightsOutCooldown = CustomOption.Create(252, "Trickster Lights Out Cooldown", 30f, 10f, 60f, 5f, tricksterSpawnRate);
+ tricksterLightsOutDuration = CustomOption.Create(253, "Trickster Lights Out Duration", 15f, 5f, 60f, 2.5f, tricksterSpawnRate);
+
+ cleanerSpawnRate = CustomOption.Create(260, cs(Cleaner.color, "Cleaner"), rates, null, true);
+ cleanerCooldown = CustomOption.Create(261, "Cleaner Cooldown", 30f, 10f, 60f, 2.5f, cleanerSpawnRate);
+
+ 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);
+
+ loversSpawnRate = CustomOption.Create(50, cs(Lovers.color, "Lovers"), rates, null, true);
+ loversImpLoverRate = CustomOption.Create(51, "Chance That One Lover Is Impostor", rates, loversSpawnRate);
+ loversBothDie = CustomOption.Create(52, "Both Lovers Die", true, loversSpawnRate);
+ loversCanHaveAnotherRole = CustomOption.Create(53, "Lovers Can Have Another Role", true, loversSpawnRate);
+
+ guesserSpawnRate = CustomOption.Create(310, cs(Guesser.color, "Guesser"), rates, null, true);
+ guesserIsImpGuesserRate = CustomOption.Create(311, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate);
+ guesserNumberOfShots = CustomOption.Create(312, "Guesser Number Of Shots", 2f, 1f, 15f, 1f, guesserSpawnRate);
+
+ jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true);
+ jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate);
+ jesterCanSabotage = CustomOption.Create(62, "Jester can sabotage", true, jesterSpawnRate);
+
+ arsonistSpawnRate = CustomOption.Create(290, cs(Arsonist.color, "Arsonist"), rates, null, true);
+ arsonistCooldown = CustomOption.Create(291, "Arsonist Cooldown", 12.5f, 2.5f, 60f, 2.5f, arsonistSpawnRate);
+ arsonistDuration = CustomOption.Create(292, "Arsonist Douse Duration", 3f, 1f, 10f, 1f, arsonistSpawnRate);
+
+ jackalSpawnRate = CustomOption.Create(220, cs(Jackal.color, "Jackal"), rates, null, true);
+ jackalKillCooldown = CustomOption.Create(221, "Jackal/Sidekick Kill Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate);
+ jackalCreateSidekickCooldown = CustomOption.Create(222, "Jackal Create Sidekick Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate);
+ jackalCanUseVents = CustomOption.Create(223, "Jackal Can Use Vents", true, jackalSpawnRate);
+ jackalCanCreateSidekick = CustomOption.Create(224, "Jackal Can Create A Sidekick", false, jackalSpawnRate);
+ sidekickPromotesToJackal = CustomOption.Create(225, "Sidekick Gets Promoted To Jackal On Jackal Death", false, jackalSpawnRate);
+ sidekickCanKill = CustomOption.Create(226, "Sidekick Can Kill", false, jackalSpawnRate);
+ sidekickCanUseVents = CustomOption.Create(227, "Sidekick Can Use Vents", true, jackalSpawnRate);
+ jackalPromotedFromSidekickCanCreateSidekick = CustomOption.Create(228, "Jackals Promoted From Sidekick Can Create A Sidekick", true, jackalSpawnRate);
+ jackalCanCreateSidekickFromImpostor = CustomOption.Create(229, "Jackals Can Make An Impostor To His Sidekick", true, jackalSpawnRate);
+ 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);
+
+ engineerSpawnRate = CustomOption.Create(90, cs(Engineer.color, "Engineer"), rates, null, true);
+
+ sheriffSpawnRate = CustomOption.Create(100, cs(Sheriff.color, "Sheriff"), rates, null, true);
+ sheriffCooldown = CustomOption.Create(101, "Sheriff Cooldown", 30f, 10f, 60f, 2.5f, sheriffSpawnRate);
+ sheriffCanKillNeutrals = CustomOption.Create(102, "Sheriff Can Kill Neutrals", false, sheriffSpawnRate);
+
+
+ lighterSpawnRate = CustomOption.Create(110, cs(Lighter.color, "Lighter"), rates, null, true);
+ lighterModeLightsOnVision = CustomOption.Create(111, "Lighter Mode Vision On Lights On", 2f, 0.25f, 5f, 0.25f, lighterSpawnRate);
+ lighterModeLightsOffVision = CustomOption.Create(112, "Lighter Mode Vision On Lights Off", 0.75f, 0.25f, 5f, 0.25f, lighterSpawnRate);
+ lighterCooldown = CustomOption.Create(113, "Lighter Cooldown", 30f, 5f, 120f, 5f, lighterSpawnRate);
+ lighterDuration = CustomOption.Create(114, "Lighter Duration", 5f, 2.5f, 60f, 2.5f, lighterSpawnRate);
+
+ detectiveSpawnRate = CustomOption.Create(120, cs(Detective.color, "Detective"), rates, null, true);
+ detectiveAnonymousFootprints = CustomOption.Create(121, "Anonymous Footprints", false, detectiveSpawnRate);
+ detectiveFootprintIntervall = CustomOption.Create(122, "Footprint Intervall", 0.5f, 0.25f, 10f, 0.25f, detectiveSpawnRate);
+ detectiveFootprintDuration = CustomOption.Create(123, "Footprint Duration", 5f, 0.25f, 10f, 0.25f, detectiveSpawnRate);
+ detectiveReportNameDuration = CustomOption.Create(124, "Time Where Detective Reports Will Have Name", 0, 0, 60, 2.5f, detectiveSpawnRate);
+ detectiveReportColorDuration = CustomOption.Create(125, "Time Where Detective Reports Will Have Color Type", 20, 0, 120, 2.5f, detectiveSpawnRate);
+
+ timeMasterSpawnRate = CustomOption.Create(130, cs(TimeMaster.color, "Time Master"), rates, null, true);
+ timeMasterCooldown = CustomOption.Create(131, "Time Master Cooldown", 30f, 10f, 120f, 2.5f, timeMasterSpawnRate);
+ timeMasterRewindTime = CustomOption.Create(132, "Rewind Time", 3f, 1f, 10f, 1f, timeMasterSpawnRate);
+ timeMasterShieldDuration = CustomOption.Create(133, "Time Master Shield Duration", 3f, 1f, 20f, 1f, timeMasterSpawnRate);
+
+ medicSpawnRate = CustomOption.Create(140, cs(Medic.color, "Medic"), rates, null, true);
+ medicShowShielded = CustomOption.Create(143, "Show Shielded Player", new string[] {"Everyone", "Shielded + Medic", "Medic"}, medicSpawnRate);
+ medicShowAttemptToShielded = CustomOption.Create(144, "Shielded Player Sees Murder Attempt", false, medicSpawnRate);
+
+ swapperSpawnRate = CustomOption.Create(150, cs(Swapper.color, "Swapper"), rates, null, true);
+ swapperCanCallEmergency = CustomOption.Create(151, "Swapper can call emergency meeting", false, swapperSpawnRate);
+ swapperCanOnlySwapOthers = CustomOption.Create(152, "Swapper can only swap others", false, swapperSpawnRate);
+
+ seerSpawnRate = CustomOption.Create(160, cs(Seer.color, "Seer"), rates, null, true);
+ seerMode = CustomOption.Create(161, "Seer Mode", new string[]{ "Show Death Flash + Souls", "Show Death Flash", "Show Souls"}, seerSpawnRate);
+ seerLimitSoulDuration = CustomOption.Create(163, "Seer Limit Soul Duration", false, seerSpawnRate);
+ seerSoulDuration = CustomOption.Create(162, "Seer Soul Duration", 15f, 0f, 60f, 5f, seerLimitSoulDuration);
+
+ hackerSpawnRate = CustomOption.Create(170, cs(Hacker.color, "Hacker"), rates, null, true);
+ hackerCooldown = CustomOption.Create(171, "Hacker Cooldown", 30f, 0f, 60f, 5f, hackerSpawnRate);
+ hackerHackeringDuration = CustomOption.Create(172, "Hacker Duration", 10f, 2.5f, 60f, 2.5f, hackerSpawnRate);
+ hackerOnlyColorType = CustomOption.Create(173, "Hacker Only Sees Color Type", false, hackerSpawnRate);
+
+ trackerSpawnRate = CustomOption.Create(200, cs(Tracker.color, "Tracker"), rates, null, true);
+ trackerUpdateIntervall = CustomOption.Create(201, "Tracker Update Intervall", 5f, 2.5f, 30f, 2.5f, trackerSpawnRate);
+
+ snitchSpawnRate = CustomOption.Create(210, cs(Snitch.color, "Snitch"), rates, null, true);
+ snitchLeftTasksForImpostors = CustomOption.Create(211, "Task Count Where Impostors See Snitch", 1f, 0f, 5f, 1f, snitchSpawnRate);
+
+ spySpawnRate = CustomOption.Create(240, cs(Spy.color, "Spy"), rates, null, true);
+ spyCanDieToSheriff = CustomOption.Create(241, "Spy Can Die To Sheriff", false, spySpawnRate);
+ spyImpostorsCanKillAnyone = CustomOption.Create(242, "Impostors Can Kill Anyone If There Is A Spy", true, spySpawnRate);
+ spyCanEnterVents = CustomOption.Create(243, "Spy Can Enter Vents", false, spySpawnRate);
+ spyHasImpostorVision = CustomOption.Create(244, "Spy Has Impostor Vision", false, spySpawnRate);
+
+ securityGuardSpawnRate = CustomOption.Create(280, cs(SecurityGuard.color, "Security Guard"), rates, null, true);
+ securityGuardCooldown = CustomOption.Create(281, "Security Guard Cooldown", 30f, 10f, 60f, 2.5f, securityGuardSpawnRate);
+ securityGuardTotalScrews = CustomOption.Create(282, "Security Guard Number Of Screws", 7f, 1f, 15f, 1f, securityGuardSpawnRate);
+ securityGuardCamPrice = CustomOption.Create(283, "Number Of Screws Per Cam", 2f, 1f, 15f, 1f, securityGuardSpawnRate);
+ securityGuardVentPrice = CustomOption.Create(284, "Number Of Screws Per Vent", 1f, 1f, 15f, 1f, securityGuardSpawnRate);
+
+ // Other options
+ maxNumberOfMeetings = CustomOption.Create(3, "Number Of Meetings (excluding Mayor meeting)", 10, 0, 15, 1, null, true);
+ blockSkippingInEmergencyMeetings = CustomOption.Create(4, "Block Skipping In Emergency Meetings", false);
+ noVoteIsSelfVote = CustomOption.Create(5, "No Vote Is Self Vote", false, blockSkippingInEmergencyMeetings);
+ hidePlayerNames = CustomOption.Create(6, "Hide Player Names", false);
+
+ 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});
+
+ }
+ }
+
+ public class CustomOption {
+ public static List<CustomOption> options = new List<CustomOption>();
+ public static int preset = 0;
+
+ public int id;
+ public string name;
+ public System.Object[] selections;
+
+ public int defaultSelection;
+ public ConfigEntry<int> entry;
+ public int selection;
+ public OptionBehaviour optionBehaviour;
+ public CustomOption parent;
+ public bool isHeader;
+
+ // Option creation
+
+ public CustomOption(int id, string name, System.Object[] selections, System.Object defaultValue, CustomOption parent, bool isHeader) {
+ this.id = id;
+ this.name = parent == null ? name : "- " + name;
+ this.selections = selections;
+ int index = Array.IndexOf(selections, defaultValue);
+ this.defaultSelection = index >= 0 ? index : 0;
+ this.parent = parent;
+ this.isHeader = isHeader;
+ selection = 0;
+ if (id != 0) {
+ entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", id.ToString(), defaultSelection);
+ selection = Mathf.Clamp(entry.Value, 0, selections.Length - 1);
+ }
+ options.Add(this);
+ }
+
+ public static CustomOption Create(int id, string name, string[] selections, CustomOption parent = null, bool isHeader = false) {
+ return new CustomOption(id, name, selections, "", parent, isHeader);
+ }
+
+ public static CustomOption Create(int id, string name, float defaultValue, float min, float max, float step, CustomOption parent = null, bool isHeader = false) {
+ List<float> selections = new List<float>();
+ for (float s = min; s <= max; s += step)
+ selections.Add(s);
+ return new CustomOption(id, name, selections.Cast<object>().ToArray(), defaultValue, parent, isHeader);
+ }
+
+ public static CustomOption Create(int id, string name, bool defaultValue, CustomOption parent = null, bool isHeader = false) {
+ return new CustomOption(id, name, new string[]{"Off", "On"}, defaultValue ? "On" : "Off", parent, isHeader);
+ }
+
+ // Static behaviour
+
+ public static void switchPreset(int newPreset) {
+ CustomOption.preset = newPreset;
+ foreach (CustomOption option in CustomOption.options) {
+ if (option.id == 0) continue;
+
+ option.entry = TheOtherRolesPlugin.Instance.Config.Bind($"Preset{preset}", option.id.ToString(), option.defaultSelection);
+ option.selection = Mathf.Clamp(option.entry.Value, 0, option.selections.Length - 1);
+ if (option.optionBehaviour != null && option.optionBehaviour is StringOption stringOption) {
+ stringOption.oldValue = stringOption.Value = option.selection;
+ stringOption.ValueText.text = option.selections[option.selection].ToString();
+ }
+ }
+ }
+
+ public static void ShareOptionSelections() {
+ if (PlayerControl.AllPlayerControls.Count <= 1 || AmongUsClient.Instance?.AmHost == false && PlayerControl.LocalPlayer == null) return;
+ foreach (CustomOption option in CustomOption.options) {
+ MessageWriter messageWriter = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShareOptionSelection, Hazel.SendOption.Reliable);
+ messageWriter.WritePacked((uint)option.id);
+ messageWriter.WritePacked((uint)Convert.ToUInt32(option.selection));
+ messageWriter.EndMessage();
+ }
+ }
+
+ // Getter
+
+ public int getSelection() {
+ return selection;
+ }
+
+ public bool getBool() {
+ return selection > 0;
+ }
+
+ public float getFloat() {
+ return (float)selections[selection];
+ }
+
+ // Option changes
+
+ public void updateSelection(int newSelection) {
+ selection = Mathf.Clamp((newSelection + selections.Length) % selections.Length, 0, selections.Length - 1);
+ if (optionBehaviour != null && optionBehaviour is StringOption stringOption) {
+ stringOption.oldValue = stringOption.Value = selection;
+ stringOption.ValueText.text = selections[selection].ToString();
+
+ if (AmongUsClient.Instance?.AmHost == true && PlayerControl.LocalPlayer) {
+ if (id == 0) switchPreset(selection); // Switch presets
+ else if (entry != null) entry.Value = selection; // Save selection to config
+
+ ShareOptionSelections();// Share all selections
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Start))]
+ class GameOptionsMenuStartPatch {
+ public static void Postfix(GameOptionsMenu __instance) {
+ var template = UnityEngine.Object.FindObjectsOfType<StringOption>().FirstOrDefault();
+ if (template == null) return;
+
+ List<OptionBehaviour> allOptions = __instance.Children.ToList();
+ for (int i = 0; i < CustomOption.options.Count; i++) {
+ CustomOption option = CustomOption.options[i];
+ if (option.optionBehaviour == null) {
+ StringOption stringOption = UnityEngine.Object.Instantiate(template, template.transform.parent);
+ allOptions.Add(stringOption);
+
+ stringOption.OnValueChanged = new Action<OptionBehaviour>((o) => {});
+ stringOption.TitleText.text = option.name;
+ stringOption.Value = stringOption.oldValue = option.selection;
+ stringOption.ValueText.text = option.selections[option.selection].ToString();
+
+ option.optionBehaviour = stringOption;
+ }
+ option.optionBehaviour.gameObject.SetActive(true);
+ }
+
+ var commonTasksOption = allOptions.FirstOrDefault(x => x.name == "NumCommonTasks").TryCast<NumberOption>();
+ if(commonTasksOption != null) commonTasksOption.ValidRange = new FloatRange(0f, 4f);
+
+ var shortTasksOption = allOptions.FirstOrDefault(x => x.name == "NumShortTasks").TryCast<NumberOption>();
+ if(shortTasksOption != null) shortTasksOption.ValidRange = new FloatRange(0f, 23f);
+
+ var longTasksOption = allOptions.FirstOrDefault(x => x.name == "NumLongTasks").TryCast<NumberOption>();
+ if(longTasksOption != null) longTasksOption.ValidRange = new FloatRange(0f, 15f);
+
+ __instance.Children = allOptions.ToArray();
+ }
+ }
+
+ [HarmonyPatch(typeof(StringOption), nameof(StringOption.OnEnable))]
+ public class StringOptionEnablePatch {
+ public static bool Prefix(StringOption __instance) {
+ CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
+ if (option == null) return true;
+
+ __instance.OnValueChanged = new Action<OptionBehaviour>((o) => {});
+ __instance.TitleText.text = option.name;
+ __instance.Value = __instance.oldValue = option.selection;
+ __instance.ValueText.text = option.selections[option.selection].ToString();
+
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(StringOption), nameof(StringOption.Increase))]
+ public class StringOptionIncreasePatch
+ {
+ public static bool Prefix(StringOption __instance)
+ {
+ CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
+ if (option == null) return true;
+ option.updateSelection(option.selection + 1);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(StringOption), nameof(StringOption.Decrease))]
+ public class StringOptionDecreasePatch
+ {
+ public static bool Prefix(StringOption __instance)
+ {
+ CustomOption option = CustomOption.options.FirstOrDefault(option => option.optionBehaviour == __instance);
+ if (option == null) return true;
+ option.updateSelection(option.selection - 1);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))]
+ public class RpcSyncSettingsPatch
+ {
+ public static void Postfix()
+ {
+ CustomOption.ShareOptionSelections();
+ }
+ }
+
+
+ [HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.Update))]
+ class GameOptionsMenuUpdatePatch
+ {
+ private static float timer = 1f;
+ public static void Postfix(GameOptionsMenu __instance) {
+ __instance.GetComponentInParent<Scroller>().YBounds.max = -0.5F + __instance.Children.Length * 0.55F;
+ timer += Time.deltaTime;
+ if (timer < 0.1f) return;
+ timer = 0f;
+
+ float offset = -7.85f;
+ foreach (CustomOption option in CustomOption.options) {
+ if (option?.optionBehaviour != null && option.optionBehaviour.gameObject != null) {
+ bool enabled = true;
+ var parent = option.parent;
+ while (parent != null && enabled) {
+ enabled = parent.selection != 0;
+ parent = parent.parent;
+ }
+ option.optionBehaviour.gameObject.SetActive(enabled);
+ if (enabled) {
+ offset -= option.isHeader ? 0.75f : 0.5f;
+ option.optionBehaviour.transform.localPosition = new Vector3(option.optionBehaviour.transform.localPosition.x, offset, option.optionBehaviour.transform.localPosition.z);
+ }
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(GameSettingMenu), "OnEnable")]
+ class GameSettingMenuPatch {
+ public static void Prefix(GameSettingMenu __instance) {
+ __instance.HideForOnline = new Transform[]{};
+ }
+
+ public static void Postfix(GameSettingMenu __instance) {
+ var mapNameTransform = __instance.AllItems.FirstOrDefault(x => x.gameObject.activeSelf && x.name.Equals("MapName", StringComparison.OrdinalIgnoreCase));
+ if (mapNameTransform == null) return;
+
+ var options = new Il2CppSystem.Collections.Generic.List<Il2CppSystem.Collections.Generic.KeyValuePair<string, int>>();
+ for (int i = 0; i < GameOptionsData.MapNames.Length; i++) {
+ var kvp = new Il2CppSystem.Collections.Generic.KeyValuePair<string, int>();
+ kvp.key = GameOptionsData.MapNames[i];
+ kvp.value = i;
+ options.Add(kvp);
+ }
+ mapNameTransform.GetComponent<KeyValueOption>().Values = options;
+ }
+ }
+
+ [HarmonyPatch(typeof(Constants), nameof(Constants.ShouldFlipSkeld))]
+ class ConstantsShouldFlipSkeldPatch {
+ public static bool Prefix(ref bool __result) {
+ if (PlayerControl.GameOptions == null) return true;
+ __result = PlayerControl.GameOptions.MapId == 3;
+ return false;
+ }
+ }
+
+ [HarmonyPatch]
+ class GameOptionsDataPatch
+ {
+ private static IEnumerable<MethodBase> TargetMethods() {
+ return typeof(GameOptionsData).GetMethods().Where(x => x.ReturnType == typeof(string) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(int));
+ }
+
+ private static void Postfix(ref string __result)
+ {
+ StringBuilder sb = new StringBuilder(__result);
+ foreach (CustomOption option in CustomOption.options) {
+ if (option.parent == null) {
+ if (option == CustomOptionHolder.crewmateRolesCountMin) {
+ var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Crewmate Roles");
+ var min = CustomOptionHolder.crewmateRolesCountMin.getSelection();
+ var max = CustomOptionHolder.crewmateRolesCountMax.getSelection();
+ if (min > max) min = max;
+ var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
+ sb.AppendLine($"{optionName}: {optionValue}");
+ } else if (option == CustomOptionHolder.neutralRolesCountMin) {
+ var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Neutral Roles");
+ var min = CustomOptionHolder.neutralRolesCountMin.getSelection();
+ var max = CustomOptionHolder.neutralRolesCountMax.getSelection();
+ if (min > max) min = max;
+ var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
+ sb.AppendLine($"{optionName}: {optionValue}");
+ } else if (option == CustomOptionHolder.impostorRolesCountMin) {
+ var optionName = CustomOptionHolder.cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Impostor Roles");
+ var min = CustomOptionHolder.impostorRolesCountMin.getSelection();
+ var max = CustomOptionHolder.impostorRolesCountMax.getSelection();
+ if (min > max) min = max;
+ var optionValue = (min == max) ? $"{max}" : $"{min} - {max}";
+ sb.AppendLine($"{optionName}: {optionValue}");
+ } else if ((option == CustomOptionHolder.crewmateRolesCountMax) || (option == CustomOptionHolder.neutralRolesCountMax) || (option == CustomOptionHolder.impostorRolesCountMax)) {
+ continue;
+ } else {
+ sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}");
+ }
+
+ }
+ }
+ CustomOption parent = null;
+ foreach (CustomOption option in CustomOption.options)
+ if (option.parent != null) {
+ if (option.parent != parent) {
+ sb.AppendLine();
+ parent = option.parent;
+ }
+ sb.AppendLine($"{option.name}: {option.selections[option.selection].ToString()}");
+ }
+
+ var hudString = sb.ToString();
+
+ int defaultSettingsLines = 19;
+ 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();
+ int end4 = hudString.TakeWhile(c => (detailedSettingsP2 -= (c == '\n' ? 1 : 0)) > 0).Count();
+ int counter = TheOtherRolesPlugin.optionsPage;
+ if (counter == 0) {
+ hudString = hudString.Substring(0, end1) + "\n";
+ } else if (counter == 1) {
+ hudString = hudString.Substring(end1 + 1, end2 - end1);
+ // Temporary fix, should add a new CustomOption for spaces
+ int gap = 1;
+ int index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
+ hudString = hudString.Insert(index, "\n");
+ gap = 5;
+ index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
+ hudString = hudString.Insert(index, "\n");
+ gap = 18;
+ index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
+ hudString = hudString.Insert(index + 1, "\n");
+ gap = 22;
+ index = hudString.TakeWhile(c => (gap -= (c == '\n' ? 1 : 0)) > 0).Count();
+ hudString = hudString.Insert(index + 1, "\n");
+ } else if (counter == 2) {
+ hudString = hudString.Substring(end2 + 1, end3 - end2);
+ } else if (counter == 3) {
+ hudString = hudString.Substring(end3 + 1, end4 - end3);
+ } else if (counter == 4) {
+ hudString = hudString.Substring(end4 + 1);
+ }
+
+ hudString += $"\n Press tab for more... ({counter+1}/5)";
+ __result = hudString;
+ }
+ }
+
+ [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))]
+ public static class GameOptionsNextPagePatch
+ {
+ public static void Postfix(KeyboardJoystick __instance)
+ {
+ if(Input.GetKeyDown(KeyCode.Tab)) {
+ TheOtherRolesPlugin.optionsPage = (TheOtherRolesPlugin.optionsPage + 1) % 5;
+ }
+ }
+ }
+
+
+ [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
+ public class GameSettingsScalePatch {
+ public static void Prefix(HudManager __instance) {
+ if (__instance.GameSettings != null) __instance.GameSettings.fontSize = 1.2f;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System.Collections.Generic;
+using System.Collections;
+using System;
+using UnityEngine;
+using static TheOtherRoles.TheOtherRoles;
+
+namespace TheOtherRoles {
+ public class DeadPlayer
+ {
+ public PlayerControl player;
+ public DateTime timeOfDeath;
+ public DeathReason deathReason;
+ public PlayerControl killerIfExisting;
+
+ public DeadPlayer(PlayerControl player, DateTime timeOfDeath, DeathReason deathReason, PlayerControl killerIfExisting) {
+ this.player = player;
+ this.timeOfDeath = timeOfDeath;
+ this.deathReason = deathReason;
+ this.killerIfExisting = killerIfExisting;
+ }
+ }
+
+ static class GameHistory {
+ public static List<Tuple<Vector3, bool>> localPlayerPositions = new List<Tuple<Vector3, bool>>();
+ public static List<DeadPlayer> deadPlayers = new List<DeadPlayer>();
+
+ public static void clearGameHistory() {
+ localPlayerPositions = new List<Tuple<Vector3, bool>>();
+ deadPlayers = new List<DeadPlayer>();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Collections;
+using UnhollowerBaseLib;
+using UnityEngine;
+using System.Linq;
+using static TheOtherRoles.TheOtherRoles;
+using TheOtherRoles.Modules;
+using HarmonyLib;
+using Hazel;
+
+namespace TheOtherRoles {
+ public static class Helpers {
+
+ public static Sprite loadSpriteFromResources(string path, float pixelsPerUnit) {
+ try {
+ Texture2D texture = loadTextureFromResources(path);
+ return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
+ } catch {
+ System.Console.WriteLine("Error loading sprite from path: " + path);
+ }
+ return null;
+ }
+
+ public static Texture2D loadTextureFromResources(string path) {
+ try {
+ Texture2D texture = new Texture2D(2, 2, TextureFormat.ARGB32, true);
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ Stream stream = assembly.GetManifestResourceStream(path);
+ var byteTexture = new byte[stream.Length];
+ var read = stream.Read(byteTexture, 0, (int) stream.Length);
+ LoadImage(texture, byteTexture, false);
+ return texture;
+ } catch {
+ System.Console.WriteLine("Error loading texture from resources: " + path);
+ }
+ return null;
+ }
+
+ public static Texture2D loadTextureFromDisk(string path) {
+ try {
+ if (File.Exists(path)) {
+ Texture2D texture = new Texture2D(2, 2, TextureFormat.ARGB32, true);
+ byte[] byteTexture = File.ReadAllBytes(path);
+ LoadImage(texture, byteTexture, false);
+ return texture;
+ }
+ } catch {
+ System.Console.WriteLine("Error loading texture from disk: " + path);
+ }
+ return null;
+ }
+
+ internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
+ internal static d_LoadImage iCall_LoadImage;
+ private static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable) {
+ if (iCall_LoadImage == null)
+ iCall_LoadImage = IL2CPP.ResolveICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
+ var il2cppArray = (Il2CppStructArray<byte>) data;
+ return iCall_LoadImage.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
+ }
+
+ public static PlayerControl playerById(byte id)
+ {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ if (player.PlayerId == id)
+ return player;
+ return null;
+ }
+
+ public static Dictionary<byte, PlayerControl> allPlayersById()
+ {
+ Dictionary<byte, PlayerControl> res = new Dictionary<byte, PlayerControl>();
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ res.Add(player.PlayerId, player);
+ return res;
+ }
+
+ public static void setSkinWithAnim(PlayerPhysics playerPhysics, uint SkinId) {
+ SkinData nextSkin = DestroyableSingleton<HatManager>.Instance.AllSkins[(int)SkinId];
+ AnimationClip clip = null;
+ var spriteAnim = playerPhysics.Skin.animator;
+ var anim = spriteAnim.m_animator;
+ var skinLayer = playerPhysics.Skin;
+
+ var currentPhysicsAnim = playerPhysics.Animator.GetCurrentAnimation();
+ if (currentPhysicsAnim == playerPhysics.RunAnim) clip = nextSkin.RunAnim;
+ else if (currentPhysicsAnim == playerPhysics.SpawnAnim) clip = nextSkin.SpawnAnim;
+ else if (currentPhysicsAnim == playerPhysics.EnterVentAnim) clip = nextSkin.EnterVentAnim;
+ else if (currentPhysicsAnim == playerPhysics.ExitVentAnim) clip = nextSkin.ExitVentAnim;
+ else if (currentPhysicsAnim == playerPhysics.IdleAnim) clip = nextSkin.IdleAnim;
+ else clip = nextSkin.IdleAnim;
+
+ float progress = playerPhysics.Animator.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
+ skinLayer.skin = nextSkin;
+
+ spriteAnim.Play(clip, 1f);
+ anim.Play("a", 0, progress % 1);
+ anim.Update(0f);
+ }
+
+ public static bool handleMurderAttempt(PlayerControl target, bool isMeetingStart = false) {
+ // Block impostor shielded kill
+ if (Medic.shielded != null && Medic.shielded == target) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.shieldedMurderAttempt();
+
+ return false;
+ }
+ // Block impostor not fully grown mini kill
+ else if (Mini.mini != null && target == Mini.mini && !Mini.isGrownUp()) {
+ return false;
+ }
+ // Block Time Master with time shield kill
+ else if (TimeMaster.shieldActive && TimeMaster.timeMaster != null && TimeMaster.timeMaster == target) {
+ if (!isMeetingStart) { // Only rewind the attempt was not called because a meeting startet
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.TimeMasterRewindTime, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.timeMasterRewindTime();
+ }
+ return false;
+ }
+ return true;
+ }
+
+
+ public static void refreshRoleDescription(PlayerControl player) {
+ if (player == null) return;
+
+ List<RoleInfo> infos = RoleInfo.getRoleInfoForPlayer(player);
+
+ var toRemove = new List<PlayerTask>();
+ foreach (PlayerTask t in player.myTasks) {
+ var textTask = t.gameObject.GetComponent<ImportantTextTask>();
+ if (textTask != null) {
+ var info = infos.FirstOrDefault(x => textTask.Text.StartsWith(x.name));
+ if (info != null)
+ infos.Remove(info); // TextTask for this RoleInfo does not have to be added, as it already exists
+ else
+ toRemove.Add(t); // TextTask does not have a corresponding RoleInfo and will hence be deleted
+ }
+ }
+
+ foreach (PlayerTask t in toRemove) {
+ t.OnRemove();
+ player.myTasks.Remove(t);
+ UnityEngine.Object.Destroy(t.gameObject);
+ }
+
+ // Add TextTask for remaining RoleInfos
+ foreach (RoleInfo roleInfo in infos) {
+ var task = new GameObject("RoleTask").AddComponent<ImportantTextTask>();
+ task.transform.SetParent(player.transform, false);
+
+ if (roleInfo.name == "Jackal") {
+ var getSidekickText = Jackal.canCreateSidekick ? " and recruit a Sidekick" : "";
+ task.Text = cs(roleInfo.color, $"{roleInfo.name}: Kill everyone{getSidekickText}");
+ } else {
+ task.Text = cs(roleInfo.color, $"{roleInfo.name}: {roleInfo.shortDescription}");
+ }
+
+ player.myTasks.Insert(0, task);
+ }
+ }
+
+ public static bool isLighterColor(int colorId) {
+ return CustomColors.lighterColors.Contains(colorId);
+ }
+
+ public static bool isCustomServer() {
+ if (DestroyableSingleton<ServerManager>.Instance == null) return false;
+ StringNames n = DestroyableSingleton<ServerManager>.Instance.CurrentRegion.TranslateName;
+ return n != StringNames.ServerNA && n != StringNames.ServerEU && n != StringNames.ServerAS;
+ }
+
+ public static bool hasFakeTasks(this PlayerControl player) {
+ 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) {
+ if (player == null) return;
+ for (int i = 0; i < player.myTasks.Count; i++) {
+ PlayerTask playerTask = player.myTasks[i];
+ playerTask.OnRemove();
+ UnityEngine.Object.Destroy(playerTask.gameObject);
+ }
+ player.myTasks.Clear();
+
+ if (player.Data != null && player.Data.Tasks != null)
+ player.Data.Tasks.Clear();
+ }
+
+ public static void setSemiTransparent(this PoolablePlayer player, bool value) {
+ float alpha = value ? 0.25f : 1f;
+ foreach (SpriteRenderer r in player.gameObject.GetComponentsInChildren<SpriteRenderer>())
+ r.color = new Color(r.color.r, r.color.g, r.color.b, alpha);
+ player.NameText.color = new Color(player.NameText.color.r, player.NameText.color.g, player.NameText.color.b, alpha);
+ }
+
+ public static string GetString(this TranslationController t, StringNames key, params Il2CppSystem.Object[] parts) {
+ return t.GetString(key, parts);
+ }
+
+ public static string cs(Color c, string s) {
+ return string.Format("<color=#{0:X2}{1:X2}{2:X2}{3:X2}>{4}</color>", ToByte(c.r), ToByte(c.g), ToByte(c.b), ToByte(c.a), s);
+ }
+
+ private static byte ToByte(float f) {
+ 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;
+ }
+ }
+}
--- /dev/null
+using BepInEx;
+using BepInEx.Configuration;
+using BepInEx.IL2CPP;
+using HarmonyLib;
+using Hazel;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Linq;
+using System.Net;
+using System.IO;
+using System;
+using System.Reflection;
+using UnhollowerBaseLib;
+using UnityEngine;
+using TheOtherRoles.Modules;
+
+namespace TheOtherRoles
+{
+ [BepInPlugin(Id, "The Other Roles", VersionString)]
+ [BepInProcess("Among Us.exe")]
+ public class TheOtherRolesPlugin : BasePlugin
+ {
+ public const string Id = "me.eisbison.theotherroles";
+ public const string VersionString = "2.7.3";
+ public static System.Version Version = System.Version.Parse(VersionString);
+
+ public Harmony Harmony { get; } = new Harmony(Id);
+ public static TheOtherRolesPlugin Instance;
+
+ public static int optionsPage = 1;
+
+ public static ConfigEntry<bool> DebugMode { get; private set; }
+ public static ConfigEntry<bool> StreamerMode { get; set; }
+ public static ConfigEntry<bool> GhostsSeeTasks { get; set; }
+ public static ConfigEntry<bool> GhostsSeeRoles { get; set; }
+ public static ConfigEntry<bool> GhostsSeeVotes{ get; set; }
+ public static ConfigEntry<bool> ShowRoleSummary { get; set; }
+ public static ConfigEntry<string> StreamerModeReplacementText { get; set; }
+ public static ConfigEntry<string> StreamerModeReplacementColor { get; set; }
+ 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;
+ IRegionInfo[] regions = defaultRegions;
+
+ var CustomRegion = new DnsRegionInfo(Ip.Value, "Custom", StringNames.NoTranslation, Ip.Value, Port.Value);
+ regions = regions.Concat(new IRegionInfo[] { CustomRegion.Cast<IRegionInfo>() }).ToArray();
+ ServerManager.DefaultRegions = regions;
+ serverManager.AvailableRegions = regions;
+ }
+
+ public override void Load() {
+
+ DebugMode = Config.Bind("Custom", "Enable Debug Mode", false);
+ StreamerMode = Config.Bind("Custom", "Enable Streamer Mode", false);
+ GhostsSeeTasks = Config.Bind("Custom", "Ghosts See Remaining Tasks", true);
+ GhostsSeeRoles = Config.Bind("Custom", "Ghosts See Roles", true);
+ GhostsSeeVotes = Config.Bind("Custom", "Ghosts See Votes", true);
+ ShowRoleSummary = Config.Bind("Custom", "Show Role Summary", true);
+ StreamerModeReplacementText = Config.Bind("Custom", "Streamer Mode Replacement Text", "\n\nThe Other Roles");
+ StreamerModeReplacementColor = Config.Bind("Custom", "Streamer Mode Replacement Text Hex Color", "#87AAF5FF");
+
+
+ Ip = Config.Bind("Custom", "Custom Server IP", "127.0.0.1");
+ Port = Config.Bind("Custom", "Custom Server Port", (ushort)22023);
+ defaultRegions = ServerManager.DefaultRegions;
+
+ UpdateRegions();
+
+ GameOptionsData.RecommendedImpostors = GameOptionsData.MaxImpostors = Enumerable.Repeat(3, 16).ToArray(); // Max Imp = Recommended Imp = 3
+ GameOptionsData.MinPlayers = Enumerable.Repeat(4, 15).ToArray(); // Min Players = 4
+
+ DebugMode = Config.Bind("Custom", "Enable Debug Mode", false);
+ Instance = this;
+ CustomOptionHolder.Load();
+ CustomColors.Load();
+
+ 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
+ [HarmonyPatch(typeof(StatsManager), nameof(StatsManager.AmBanned), MethodType.Getter)]
+ public static class AmBannedPatch
+ {
+ public static void Postfix(out bool __result)
+ {
+ __result = false;
+ }
+ }
+ [HarmonyPatch(typeof(ChatController), nameof(ChatController.Awake))]
+ public static class ChatControllerAwakePatch {
+ private static void Prefix() {
+ if (!EOSManager.Instance.IsMinor()) {
+ SaveManager.chatModeType = 1;
+ SaveManager.isGuest = false;
+ }
+ }
+ }
+
+ // Debugging tools
+ [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))]
+ public static class DebugManager
+ {
+ private static readonly System.Random random = new System.Random((int)DateTime.Now.Ticks);
+ private static List<PlayerControl> bots = new List<PlayerControl>();
+
+ public static void Postfix(KeyboardJoystick __instance)
+ {
+ if (!TheOtherRolesPlugin.DebugMode.Value) return;
+
+ // Spawn dummys
+ if (Input.GetKeyDown(KeyCode.F)) {
+ var playerControl = UnityEngine.Object.Instantiate(AmongUsClient.Instance.PlayerPrefab);
+ var i = playerControl.PlayerId = (byte) GameData.Instance.GetAvailableId();
+
+ bots.Add(playerControl);
+ GameData.Instance.AddPlayer(playerControl);
+ AmongUsClient.Instance.Spawn(playerControl, -2, InnerNet.SpawnFlags.None);
+
+ playerControl.transform.position = PlayerControl.LocalPlayer.transform.position;
+ playerControl.GetComponent<DummyBehaviour>().enabled = true;
+ playerControl.NetTransform.enabled = false;
+ playerControl.SetName(RandomString(10));
+ playerControl.SetColor((byte) random.Next(Palette.PlayerColors.Length));
+ playerControl.SetHat((uint) random.Next(HatManager.Instance.AllHats.Count), playerControl.Data.ColorId);
+ playerControl.SetPet((uint) random.Next(HatManager.Instance.AllPets.Count));
+ playerControl.SetSkin((uint) random.Next(HatManager.Instance.AllSkins.Count));
+ GameData.Instance.RpcSetTasks(playerControl.PlayerId, new byte[0]);
+ }
+
+ // Terminate round
+ if(Input.GetKeyDown(KeyCode.L)) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ForceEnd, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.forceEnd();
+ }
+ }
+
+ public static string RandomString(int length)
+ {
+ const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ return new string(Enumerable.Repeat(chars, length)
+ .Select(s => s[random.Next(s.Length)]).ToArray());
+ }
+ }
+}
--- /dev/null
+using System.Collections.Generic;
+using System.Collections;
+using System;
+using UnityEngine;
+using static TheOtherRoles.TheOtherRoles;
+
+namespace TheOtherRoles{
+ static class MapOptions {
+ // Set values
+ public static int maxNumberOfMeetings = 10;
+ public static bool blockSkippingInEmergencyMeetings = false;
+ public static bool noVoteIsSelfVote = false;
+ public static bool hidePlayerNames = false;
+ public static bool ghostsSeeRoles = true;
+ public static bool ghostsSeeTasks = true;
+ public static bool ghostsSeeVotes = true;
+ public static bool showRoleSummary = true;
+
+ // Updating values
+ 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() {
+ 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();
+ noVoteIsSelfVote = CustomOptionHolder.noVoteIsSelfVote.getBool();
+ hidePlayerNames = CustomOptionHolder.hidePlayerNames.getBool();
+ ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value;
+ ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value;
+ ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value;
+ showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Security.Cryptography;
+using System.Text;
+using BepInEx;
+using BepInEx.Configuration;
+using BepInEx.IL2CPP;
+using HarmonyLib;
+using UnityEngine;
+using System.Linq;
+using UnhollowerBaseLib;
+
+namespace TheOtherRoles.Modules {
+ [HarmonyPatch]
+ public static class ChatCommands {
+ [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))]
+ private static class SendChatPatch {
+ static bool Prefix(ChatController __instance) {
+ string text = __instance.TextArea.text;
+ bool handled = false;
+ if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) {
+ 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()) {
+ var client = AmongUsClient.Instance.GetClient(target.OwnerId);
+ if (client != null) {
+ AmongUsClient.Instance.KickPlayer(client.Id, false);
+ 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 (AmongUsClient.Instance.GameMode == GameModes.FreePlay) {
+ if (text.ToLower().Equals("/murder")) {
+ PlayerControl.LocalPlayer.Exiled();
+ HudManager.Instance.KillOverlay.ShowKillAnimation(PlayerControl.LocalPlayer.Data, PlayerControl.LocalPlayer.Data);
+ handled = true;
+ } else if (text.ToLower().StartsWith("/color ")) {
+ handled = true;
+ int col;
+ if (!Int32.TryParse(text.Substring(7), out col)) {
+ __instance.AddChat(PlayerControl.LocalPlayer, "Unable to parse color id\nUsage: /color {id}");
+ }
+ col = Math.Clamp(col, 0, Palette.PlayerColors.Length - 1);
+ PlayerControl.LocalPlayer.SetColor(col);
+ __instance.AddChat(PlayerControl.LocalPlayer, "Changed color succesfully");;
+ }
+ }
+ if (handled) {
+ __instance.TextArea.Clear();
+ __instance.quickChatMenu.ResetGlyphs();
+ }
+ return !handled;
+ }
+ }
+ [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
+ public static class EnableChat {
+ public static void Postfix(HudManager __instance) {
+ if (!__instance.Chat.isActiveAndEnabled && AmongUsClient.Instance.GameMode == GameModes.FreePlay)
+ __instance.Chat.SetVisible(true);
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using UnityEngine;
+using Il2CppSystem;
+using HarmonyLib;
+using UnhollowerBaseLib;
+using Assets.CoreScripts;
+
+namespace TheOtherRoles.Modules {
+ 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, 13, 14, 17 };
+ 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<Color32> colorlist = Enumerable.ToList<Color32>(Palette.PlayerColors);
+ List<Color32> shadowlist = Enumerable.ToList<Color32>(Palette.ShadowColors);
+
+ List<CustomColor> colors = new List<CustomColor>();
+
+ /* 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",
+ color = new Color32(109, 7, 26, byte.MaxValue),
+ shadow = new Color32(54, 2, 11, byte.MaxValue),
+ isLighterColor = false });
+ 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",
+ color = new Color32(22, 132, 176, byte.MaxValue),
+ shadow = new Color32(15, 89, 117, byte.MaxValue),
+ isLighterColor = false });
+ 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",
+ color = new Color32(173, 126, 201, byte.MaxValue),
+ shadow = new Color32(131, 58, 203, byte.MaxValue),
+ isLighterColor = true });
+ 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",
+ color = new Color32(255, 164, 119, byte.MaxValue),
+ shadow = new Color32(238, 128, 100, byte.MaxValue),
+ isLighterColor = true });
+ colors.Add(new CustomColor { longname = "Wasabi",
+ color = new Color32(112, 143, 46, byte.MaxValue),
+ shadow = new Color32(72, 92, 29, byte.MaxValue),
+ 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 = "Petrol",
+ color = new Color32(0, 99, 105, byte.MaxValue),
+ shadow = new Color32(0, 61, 54, byte.MaxValue),
+ isLighterColor = false });
+ 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 = "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 });
+
+ 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 = "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;
+ colorlist.Add(cc.color);
+ shadowlist.Add(cc.shadow);
+ if (cc.isLighterColor)
+ lighterColors.Add(colorlist.Count - 1);
+ }
+
+ Palette.ColorNames = longlist.ToArray();
+ Palette.PlayerColors = colorlist.ToArray();
+ Palette.ShadowColors = shadowlist.ToArray();
+ }
+
+ protected internal struct CustomColor {
+ public string longname;
+ public Color32 color;
+ public Color32 shadow;
+ public bool isLighterColor;
+ }
+
+ [HarmonyPatch]
+ public static class CustomColorPatches {
+ [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), new[] {
+ typeof(StringNames),
+ typeof(Il2CppReferenceArray<Il2CppSystem.Object>)
+ })]
+ private class ColorStringPatch {
+ public static bool Prefix(ref string __result, [HarmonyArgument(0)] StringNames name) {
+ if ((int)name >= 50000) {
+ string text = CustomColors.ColorStrings[(int)name];
+ if (text != null) {
+ __result = text;
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ [HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.OnEnable))]
+ private static class PlayerTabEnablePatch {
+ public static void Postfix(PlayerTab __instance) { // Replace instead
+ Il2CppArrayBase<ColorChip> chips = __instance.ColorChips.ToArray();
+
+ 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();
+ }
+ }
+ }
+ [HarmonyPatch(typeof(SaveManager), nameof(SaveManager.LoadPlayerPrefs))]
+ private static class LoadPlayerPrefsPatch { // Fix Potential issues with broken colors
+ private static bool needsPatch = false;
+ public static void Prefix([HarmonyArgument(0)] bool overrideLoad) {
+ if (!SaveManager.loaded || overrideLoad)
+ needsPatch = true;
+ }
+ public static void Postfix() {
+ if (!needsPatch) return;
+ SaveManager.colorConfig %= CustomColors.pickableColors;
+ needsPatch = false;
+ }
+ }
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CheckColor))]
+ private static class PlayerControlCheckColorPatch {
+ private static bool isTaken(PlayerControl player, uint color) {
+ foreach (GameData.PlayerInfo p in GameData.Instance.AllPlayers)
+ if (!p.Disconnected && p.PlayerId != player.PlayerId && p.ColorId == color)
+ return true;
+ return false;
+ }
+ public static bool Prefix(PlayerControl __instance, [HarmonyArgument(0)] byte bodyColor) { // Fix incorrect color assignment
+ uint color = (uint)bodyColor;
+ if (isTaken(__instance, color) || color >= Palette.PlayerColors.Length) {
+ int num = 0;
+ while (num++ < 50 && (color >= CustomColors.pickableColors || isTaken(__instance, color))) {
+ color = (color + 1) % CustomColors.pickableColors;
+ }
+ }
+ __instance.RpcSetColor((byte)color);
+ return false;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using BepInEx;
+using BepInEx.Configuration;
+using BepInEx.IL2CPP;
+using Il2CppSystem;
+using HarmonyLib;
+using UnityEngine;
+using UnhollowerBaseLib;
+using System.IO;
+using System.Reflection;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Security.Cryptography;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+
+namespace TheOtherRoles.Modules {
+ [HarmonyPatch]
+ public class CustomHats {
+ private static bool LOADED = false;
+ private static bool RUNNING = false;
+ public static Material hatShader;
+
+ public static Dictionary<string, HatExtension> CustomHatRegistry = new Dictionary<string, HatExtension>();
+ public static HatExtension TestExt = null;
+
+ public class HatExtension {
+ public string author { get; set;}
+ public string package { get; set;}
+ public string condition { get; set;}
+ public Sprite FlipImage { get; set;}
+ public Sprite BackFlipImage { get; set;}
+
+ public bool isUnlocked() {
+ if (condition == null || condition.ToLower() == "none")
+ return true;
+ return false;
+ }
+ }
+
+ public class CustomHat {
+ public string author { get; set;}
+ public string package { get; set;}
+ public string condition { get; set;}
+ public string name { get; set;}
+ public string resource { get; set;}
+ public string flipresource { get; set;}
+ public string backflipresource { get; set;}
+ public string backresource { get; set;}
+ public string climbresource { get; set;}
+ public bool bounce { get; set;}
+ public bool adaptive { get; set;}
+ public bool behind { get; set;}
+ }
+
+ private static List<CustomHat> createCustomHatDetails(string[] hats, bool fromDisk = false) {
+ Dictionary<string, CustomHat> fronts = new Dictionary<string, CustomHat>();
+ Dictionary<string, string> backs = new Dictionary<string, string>();
+ Dictionary<string, string> flips = new Dictionary<string, string>();
+ Dictionary<string, string> backflips = new Dictionary<string, string>();
+ Dictionary<string, string> climbs = new Dictionary<string, string>();
+
+ for (int i = 0; i < hats.Length; i++) {
+ string s = fromDisk ? hats[i].Substring(hats[i].LastIndexOf("\\") + 1).Split('.')[0] : hats[i].Split('.')[3];
+ string[] p = s.Split('_');
+
+ HashSet<string> options = new HashSet<string>();
+ for (int j = 1; j < p.Length; j++)
+ options.Add(p[j]);
+
+ if (options.Contains("back") && options.Contains("flip"))
+ backflips.Add(p[0], hats[i]);
+ else if (options.Contains("climb"))
+ climbs.Add(p[0], hats[i]);
+ else if (options.Contains("back"))
+ backs.Add(p[0], hats[i]);
+ else if (options.Contains("flip"))
+ flips.Add(p[0], hats[i]);
+ else {
+ CustomHat custom = new CustomHat { resource = hats[i] };
+ custom.name = p[0].Replace('-', ' ');
+ custom.bounce = options.Contains("bounce");
+ custom.adaptive = options.Contains("adaptive");
+ custom.behind = options.Contains("behind");
+
+ fronts.Add(p[0], custom);
+ }
+ }
+
+ List<CustomHat> customhats = new List<CustomHat>();
+
+ foreach (string k in fronts.Keys) {
+ CustomHat hat = fronts[k];
+ string br, cr, fr, bfr;
+ backs.TryGetValue(k, out br);
+ climbs.TryGetValue(k, out cr);
+ flips.TryGetValue(k, out fr);
+ backflips.TryGetValue(k, out bfr);
+ if (br != null)
+ hat.backresource = br;
+ if (cr != null)
+ hat.climbresource = cr;
+ if (fr != null)
+ hat.flipresource = fr;
+ if (bfr != null)
+ hat.backflipresource = bfr;
+ if (hat.backresource != null)
+ hat.behind = true;
+
+ customhats.Add(hat);
+ }
+
+ return customhats;
+ }
+
+ private static Sprite CreateHatSprite(string path, bool fromDisk = false) {
+ 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.53f, 0.575f), texture.width * 0.375f);
+ if (sprite == null)
+ return null;
+ texture.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
+ sprite.hideFlags |= HideFlags.HideAndDontSave | HideFlags.DontUnloadUnusedAsset;
+ return sprite;
+ }
+
+ private static HatBehaviour CreateHatBehaviour(CustomHat ch, bool fromDisk = false, bool testOnly = false) {
+ if (hatShader == null && DestroyableSingleton<HatManager>.InstanceExists) {
+ foreach (HatBehaviour h in DestroyableSingleton<HatManager>.Instance.AllHats) {
+ if (h.AltShader != null) {
+ hatShader = h.AltShader;
+ break;
+ }
+ }
+ }
+
+ HatBehaviour hat = new HatBehaviour();
+ hat.MainImage = CreateHatSprite(ch.resource, fromDisk);
+ if (ch.backresource != null) {
+ hat.BackImage = CreateHatSprite(ch.backresource, fromDisk);
+ ch.behind = true; // Required to view backresource
+ }
+ if (ch.climbresource != null)
+ hat.ClimbImage = CreateHatSprite(ch.climbresource, fromDisk);
+ hat.name = ch.name;
+ hat.Order = 99;
+ hat.ProductId = "hat_" + ch.name.Replace(' ', '_');
+ hat.InFront = !ch.behind;
+ hat.NoBounce = !ch.bounce;
+ hat.ChipOffset = new Vector2(0f, 0.2f);
+
+ if (ch.adaptive && hatShader != null)
+ hat.AltShader = hatShader;
+
+ HatExtension extend = new HatExtension();
+ extend.author = ch.author != null ? ch.author : "Unknown";
+ extend.package = ch.package != null ? ch.package : "Misc.";
+ extend.condition = ch.condition != null ? ch.condition : "none";
+
+ if (ch.flipresource != null)
+ extend.FlipImage = CreateHatSprite(ch.flipresource, fromDisk);
+ if (ch.backflipresource != null)
+ extend.BackFlipImage = CreateHatSprite(ch.backflipresource, fromDisk);
+
+ if (testOnly) {
+ TestExt = extend;
+ TestExt.condition = hat.name;
+ } else {
+ CustomHatRegistry.Add(hat.name, extend);
+ }
+
+ return hat;
+ }
+
+ private static HatBehaviour CreateHatBehaviour(CustomHatLoader.CustomHatOnline chd) {
+ string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\";
+ chd.resource = filePath + chd.resource;
+ if (chd.backresource != null)
+ chd.backresource = filePath + chd.backresource;
+ if (chd.climbresource != null)
+ chd.climbresource = filePath + chd.climbresource;
+ if (chd.flipresource != null)
+ chd.flipresource = filePath + chd.flipresource;
+ if (chd.backflipresource != null)
+ chd.backflipresource = filePath + chd.backflipresource;
+ return CreateHatBehaviour(chd, true);
+ }
+
+ [HarmonyPatch(typeof(HatManager), nameof(HatManager.GetHatById))]
+ private static class HatManagerPatch {
+ static void Prefix(HatManager __instance) {
+ if (RUNNING) return;
+ RUNNING = true; // prevent simultanious execution
+ try {
+ if (!LOADED) {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ string hatres = $"{assembly.GetName().Name}.Resources.CustomHats";
+ string[] hats = (from r in assembly.GetManifestResourceNames()
+ where r.StartsWith(hatres) && r.EndsWith(".png")
+ select r).ToArray<string>();
+
+ List<CustomHat> customhats = createCustomHatDetails(hats);
+ foreach (CustomHat ch in customhats)
+ __instance.AllHats.Add(CreateHatBehaviour(ch));
+ }
+ while (CustomHatLoader.hatdetails.Count > 0) {
+ __instance.AllHats.Add(CreateHatBehaviour(CustomHatLoader.hatdetails[0]));
+ CustomHatLoader.hatdetails.RemoveAt(0);
+ }
+ } catch (System.Exception e) {
+ if (!LOADED)
+ System.Console.WriteLine("Unable to add Custom Hats\n" + e);
+ }
+ LOADED = true;
+ }
+ static void Postfix(HatManager __instance) {
+ RUNNING = false;
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.HandleAnimation))]
+ private static class PlayerPhysicsHandleAnimationPatch {
+ private static void Postfix(PlayerPhysics __instance) {
+ AnimationClip currentAnimation = __instance.Animator.GetCurrentAnimation();
+ if (currentAnimation == __instance.ClimbAnim || currentAnimation == __instance.ClimbDownAnim) return;
+ HatParent hp = __instance.myPlayer.HatRenderer;
+ if (hp.Hat == null) return;
+ HatExtension extend = hp.Hat.getHatExtension();
+ if (extend == null) return;
+ if (extend.FlipImage != null) {
+ if (__instance.rend.flipX) {
+ hp.FrontLayer.sprite = extend.FlipImage;
+ } else {
+ hp.FrontLayer.sprite = hp.Hat.MainImage;
+ }
+ }
+ if (extend.BackFlipImage != null) {
+ if (__instance.rend.flipX) {
+ hp.BackLayer.sprite = extend.BackFlipImage;
+ } else {
+ hp.BackLayer.sprite = hp.Hat.BackImage;
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(HatParent), nameof(HatParent.SetHat), new System.Type[] { typeof(uint), typeof(int) })]
+ private static class HatParentSetHatPatch {
+ static void Postfix(HatParent __instance, [HarmonyArgument(0)]uint hatId, [HarmonyArgument(1)]int color) {
+ if (DestroyableSingleton<TutorialManager>.InstanceExists) {
+ try {
+ string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\Test";
+ DirectoryInfo d = new DirectoryInfo(filePath);
+ string[] filePaths = d.GetFiles("*.png").Select(x => x.FullName).ToArray(); // Getting Text files
+ List<CustomHat> hats = createCustomHatDetails(filePaths, true);
+ if (hats.Count > 0) {
+ __instance.Hat = CreateHatBehaviour(hats[0], true, true);
+ __instance.SetHat(color);
+ }
+ } catch (System.Exception e) {
+ System.Console.WriteLine("Unable to create test hat\n" + e);
+ }
+ }
+ }
+ }
+
+ private static List<TMPro.TMP_Text> hatsTabCustomTexts = new List<TMPro.TMP_Text>();
+
+ [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.OnEnable))]
+ public class HatsTabOnEnablePatch {
+ public static string innerslothPackageName = "Innersloth Hats";
+ private static TMPro.TMP_Text textTemplate;
+
+ public static float createHatPackage(List<System.Tuple<HatBehaviour, HatExtension>> hats, string packageName, float YStart, HatsTab __instance) {
+ bool isDefaultPackage = innerslothPackageName == packageName;
+ float offset = YStart;
+
+ if (textTemplate != null) {
+ TMPro.TMP_Text title = UnityEngine.Object.Instantiate<TMPro.TMP_Text>(textTemplate, __instance.scroller.Inner);
+ title.transform.localPosition = new Vector3(2.25f, YStart, -1f);
+ title.transform.localScale = Vector3.one * 1.5f;
+ // title.currentFontSize
+ title.fontSize *= 0.5f;
+ title.enableAutoSizing = false;
+ __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => { title.SetText(packageName); })));
+ offset -= 0.8f * __instance.YOffset;
+ hatsTabCustomTexts.Add(title);
+ }
+ for (int i = 0; i < hats.Count; i++) {
+ HatBehaviour hat = hats[i].Item1;
+ HatExtension ext = hats[i].Item2;
+
+ float xpos = __instance.XRange.Lerp((i % __instance.NumPerRow) / (__instance.NumPerRow - 1f));
+ float ypos = offset - (i / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset;
+ ColorChip colorChip = UnityEngine.Object.Instantiate<ColorChip>(__instance.ColorTabPrefab, __instance.scroller.Inner);
+ if (ext != null) {
+ Transform background = colorChip.transform.FindChild("Background");
+ Transform foreground = colorChip.transform.FindChild("ForeGround");
+
+ if (background != null) {
+ background.localScale = new Vector3(1, 1.5f, 1);
+ background.localPosition = Vector3.down * 0.243f;
+ }
+ if (foreground != null) {
+ foreground.localPosition = Vector3.down * 0.243f;
+ }
+
+ if (textTemplate != null) {
+ TMPro.TMP_Text description = UnityEngine.Object.Instantiate<TMPro.TMP_Text>(textTemplate, colorChip.transform);
+ description.transform.localPosition = new Vector3(0f, -0.75f, -1f);
+ description.transform.localScale = Vector3.one * 0.7f;
+ __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => { description.SetText($"{hat.name}\nby {ext.author}"); })));
+ hatsTabCustomTexts.Add(description);
+ }
+
+ if (!ext.isUnlocked()) { // Hat is locked
+ UnityEngine.Object.Destroy(colorChip.Button);
+ var overlay = UnityEngine.Object.Instantiate(colorChip.InUseForeground, colorChip.transform);
+ overlay.SetActive(true);
+ }
+ }
+
+ colorChip.transform.localPosition = new Vector3(xpos, ypos, -1f);
+ colorChip.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => { __instance.SelectHat(hat); }));
+ colorChip.Inner.SetHat(hat, PlayerControl.LocalPlayer.Data.ColorId);
+ colorChip.Inner.transform.localPosition = hat.ChipOffset;
+ colorChip.Tag = hat;
+ __instance.ColorChips.Add(colorChip);
+ }
+ return offset - ((hats.Count - 1) / __instance.NumPerRow) * (isDefaultPackage ? 1f : 1.5f) * __instance.YOffset - 0.85f;
+ }
+
+ public static bool Prefix(HatsTab __instance) {
+ PlayerControl.SetPlayerMaterialColors(PlayerControl.LocalPlayer.Data.ColorId, __instance.DemoImage);
+ __instance.HatImage.SetHat(SaveManager.LastHat, PlayerControl.LocalPlayer.Data.ColorId);
+ PlayerControl.SetSkinImage(SaveManager.LastSkin, __instance.SkinImage);
+ PlayerControl.SetPetImage(SaveManager.LastPet, PlayerControl.LocalPlayer.Data.ColorId, __instance.PetImage);
+
+ HatBehaviour[] unlockedHats = DestroyableSingleton<HatManager>.Instance.GetUnlockedHats();
+ Dictionary<string, List<System.Tuple<HatBehaviour, HatExtension>>> packages = new Dictionary<string, List<System.Tuple<HatBehaviour, HatExtension>>>();
+ hatsTabCustomTexts = new List<TMPro.TMP_Text>();
+
+ foreach (HatBehaviour hatBehaviour in unlockedHats) {
+ HatExtension ext = hatBehaviour.getHatExtension();
+
+ if (ext != null) {
+ if (!packages.ContainsKey(ext.package))
+ packages[ext.package] = new List<System.Tuple<HatBehaviour, HatExtension>>();
+ packages[ext.package].Add(new System.Tuple<HatBehaviour, HatExtension>(hatBehaviour, ext));
+ } else {
+ if (!packages.ContainsKey(innerslothPackageName))
+ packages[innerslothPackageName] = new List<System.Tuple<HatBehaviour, HatExtension>>();
+ packages[innerslothPackageName].Add(new System.Tuple<HatBehaviour, HatExtension>(hatBehaviour, null));
+ }
+ }
+
+ float YOffset = __instance.YStart;
+
+ var hatButton = GameObject.Find("HatButton");
+
+ if (hatButton != null && hatButton.transform.FindChild("ButtonText_TMP") != null) {
+ textTemplate = hatButton.transform.FindChild("ButtonText_TMP").GetComponent<TMPro.TMP_Text>();
+ }
+
+ var orderedKeys = packages.Keys.OrderBy((string x) => {
+ if (x == innerslothPackageName) return 1000;
+ if (x == "Developer Hats") return 0;
+ return 500;
+ });
+ foreach (string key in orderedKeys) {
+ List<System.Tuple<HatBehaviour, HatExtension>> value = packages[key];
+ YOffset = createHatPackage(value, key, YOffset, __instance);
+ }
+
+ // __instance.scroller.YBounds.max = -(__instance.YStart - (float)(unlockedHats.Length / this.NumPerRow) * this.YOffset) - 3f;
+ // __instance.scroller.YBounds.max = YOffset * -0.875f; // probably needs to fix up the entire messed math to solve this correctly
+ __instance.scroller.YBounds.max = -(YOffset + 4.1f);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))]
+ public class HatsTabUpdatePatch {
+ public static void Postfix(HatsTab __instance) {
+ // Manually hide all custom TMPro.TMP_Text objects that are outside the ScrollRect
+ foreach (TMPro.TMP_Text customText in hatsTabCustomTexts) {
+ if (customText != null && customText.transform != null && customText.gameObject != null) {
+ bool active = customText.transform.position.y <= 3.75f && customText.transform.position.y >= 0.3f;
+ float epsilon = Mathf.Min(Mathf.Abs(customText.transform.position.y - 3.75f), Mathf.Abs(customText.transform.position.y - 0.35f));
+ if (active != customText.gameObject.active && epsilon > 0.1f) customText.gameObject.SetActive(active);
+ }
+ }
+ }
+ }
+ }
+
+ public class CustomHatLoader {
+ public static bool running = false;
+ private const string REPO = "https://raw.githubusercontent.com/Eisbison/TheOtherHats/master";
+
+ public static List<CustomHatOnline> hatdetails = new List<CustomHatOnline>();
+ private static Task hatFetchTask = null;
+ public static void LaunchHatFetcher() {
+ if (running)
+ return;
+ running = true;
+ hatFetchTask = LaunchHatFetcherAsync();
+ }
+
+ private static async Task LaunchHatFetcherAsync() {
+ try {
+ HttpStatusCode status = await FetchHats();
+ if (status != HttpStatusCode.OK)
+ System.Console.WriteLine("Custom Hats could not be loaded\n");
+ } catch (System.Exception e) {
+ System.Console.WriteLine("Unable to fetch hats\n" + e.Message);
+ }
+ running = false;
+ }
+
+ private static string sanitizeResourcePath(string res) {
+ if (res == null || !res.EndsWith(".png"))
+ return null;
+
+ res = res.Replace("\\", "")
+ .Replace("/", "")
+ .Replace("*", "")
+ .Replace("..", "");
+ return res;
+ }
+
+ public static async Task<HttpStatusCode> FetchHats() {
+ HttpClient http = new HttpClient();
+ http.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue{ NoCache = true };
+ var response = await http.GetAsync(new System.Uri($"{REPO}/CustomHats.json"), HttpCompletionOption.ResponseContentRead);
+ try {
+ if (response.StatusCode != HttpStatusCode.OK) return response.StatusCode;
+ if (response.Content == null) {
+ System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString());
+ return HttpStatusCode.ExpectationFailed;
+ }
+ string json = await response.Content.ReadAsStringAsync();
+ JToken jobj = JObject.Parse(json)["hats"];
+ if (!jobj.HasValues) return HttpStatusCode.ExpectationFailed;
+
+ List<CustomHatOnline> hatdatas = new List<CustomHatOnline>();
+
+ for (JToken current = jobj.First; current != null; current = current.Next) {
+ if (current.HasValues) {
+ CustomHatOnline info = new CustomHatOnline();
+
+ info.name = current["name"]?.ToString();
+ info.resource = sanitizeResourcePath(current["resource"]?.ToString());
+ if (info.resource == null || info.name == null) // required
+ continue;
+ info.reshasha = current["reshasha"]?.ToString();
+ info.backresource = sanitizeResourcePath(current["backresource"]?.ToString());
+ info.reshashb = current["reshashb"]?.ToString();
+ info.climbresource = sanitizeResourcePath(current["climbresource"]?.ToString());
+ info.reshashc = current["reshashc"]?.ToString();
+ info.flipresource = sanitizeResourcePath(current["flipresource"]?.ToString());
+ info.reshashf = current["reshashf"]?.ToString();
+ info.backflipresource = sanitizeResourcePath(current["backflipresource"]?.ToString());
+ info.reshashbf = current["reshashbf"]?.ToString();
+
+ info.author = current["author"]?.ToString();
+ info.package = current["package"]?.ToString();
+ info.condition = current["condition"]?.ToString();
+ info.bounce = current["bounce"] != null;
+ info.adaptive = current["adaptive"] != null;
+ info.behind = current["behind"] != null;
+ hatdatas.Add(info);
+ }
+ }
+
+ List<string> markedfordownload = new List<string>();
+
+ string filePath = Path.GetDirectoryName(Application.dataPath) + @"\TheOtherHats\";
+ MD5 md5 = MD5.Create();
+ foreach (CustomHatOnline data in hatdatas) {
+ if (doesResourceRequireDownload(filePath + data.resource, data.reshasha, md5))
+ markedfordownload.Add(data.resource);
+ if (data.backresource != null && doesResourceRequireDownload(filePath + data.backresource, data.reshashb, md5))
+ markedfordownload.Add(data.backresource);
+ if (data.climbresource != null && doesResourceRequireDownload(filePath + data.climbresource, data.reshashc, md5))
+ markedfordownload.Add(data.climbresource);
+ if (data.flipresource != null && doesResourceRequireDownload(filePath + data.flipresource, data.reshashf, md5))
+ markedfordownload.Add(data.flipresource);
+ if (data.backflipresource != null && doesResourceRequireDownload(filePath + data.backflipresource, data.reshashbf, md5))
+ markedfordownload.Add(data.backflipresource);
+ }
+
+ foreach(var file in markedfordownload) {
+
+ var hatFileResponse = await http.GetAsync($"{REPO}/hats/{file}", HttpCompletionOption.ResponseContentRead);
+ if (hatFileResponse.StatusCode != HttpStatusCode.OK) continue;
+ using (var responseStream = await hatFileResponse.Content.ReadAsStreamAsync()) {
+ using (var fileStream = File.Create($"{filePath}\\{file}")) {
+ responseStream.CopyTo(fileStream);
+ }
+ }
+ }
+
+ hatdetails = hatdatas;
+ } catch (System.Exception ex) {
+ TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
+ System.Console.WriteLine(ex);
+ }
+ return HttpStatusCode.OK;
+ }
+
+ private static bool doesResourceRequireDownload(string respath, string reshash, MD5 md5) {
+ if (reshash == null || !File.Exists(respath))
+ return true;
+
+ using (var stream = File.OpenRead(respath)) {
+ var hash = System.BitConverter.ToString(md5.ComputeHash(stream)).Replace("-", "").ToLowerInvariant();
+ return !reshash.Equals(hash);
+ }
+ }
+
+ public class CustomHatOnline : CustomHats.CustomHat {
+ public string reshasha { get; set;}
+ public string reshashb { get; set;}
+ public string reshashc { get; set;}
+ public string reshashf { get; set;}
+ public string reshashbf { get; set;}
+ }
+ }
+ public static class CustomHatExtensions {
+ public static CustomHats.HatExtension getHatExtension(this HatBehaviour hat) {
+ CustomHats.HatExtension ret = null;
+ if (CustomHats.TestExt != null && CustomHats.TestExt.condition.Equals(hat.name)) {
+ return CustomHats.TestExt;
+ }
+ CustomHats.CustomHatRegistry.TryGetValue(hat.name, out ret);
+ return ret;
+ }
+ }
+}
--- /dev/null
+using System;
+using HarmonyLib;
+using UnityEngine;
+using Hazel;
+using InnerNet;
+
+namespace TheOtherRoles.Modules {
+ [HarmonyPatch]
+ public static class DynamicLobbies {
+ public static int LobbyLimit = 15;
+ [HarmonyPatch(typeof(ChatController), nameof(ChatController.SendChat))]
+ private static class SendChatPatch {
+ static bool Prefix(ChatController __instance) {
+ string text = __instance.TextArea.text;
+ bool handled = false;
+ if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) {
+ if (text.ToLower().StartsWith("/size ")) { // Unfortunately server holds this - need to do more trickery
+ if (AmongUsClient.Instance.AmHost && AmongUsClient.Instance.CanBan()) { // checking both just cause
+ handled = true;
+ if (!Int32.TryParse(text.Substring(6), out LobbyLimit)) {
+ __instance.AddChat(PlayerControl.LocalPlayer, "Invalid Size\nUsage: /size {amount}");
+ } else {
+ LobbyLimit = Math.Clamp(LobbyLimit, 4, 15);
+ if (LobbyLimit != PlayerControl.GameOptions.MaxPlayers) {
+ PlayerControl.GameOptions.MaxPlayers = LobbyLimit;
+ DestroyableSingleton<GameStartManager>.Instance.LastPlayerCount = LobbyLimit;
+ PlayerControl.LocalPlayer.RpcSyncSettings(PlayerControl.GameOptions);
+ __instance.AddChat(PlayerControl.LocalPlayer, $"Lobby Size changed to {LobbyLimit} players");
+ } else {
+ __instance.AddChat(PlayerControl.LocalPlayer, $"Lobby Size is already {LobbyLimit}");
+ }
+ }
+ }
+ }
+ }
+ if (handled) {
+ __instance.TextArea.Clear();
+ __instance.quickChatMenu.ResetGlyphs();
+ }
+ return !handled;
+ }
+ }
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.HostGame))]
+ public static class InnerNetClientHostPatch {
+ public static void Prefix(InnerNet.InnerNetClient __instance, [HarmonyArgument(0)] GameOptionsData settings) {
+ DynamicLobbies.LobbyLimit = settings.MaxPlayers;
+ settings.MaxPlayers = 15; // Force 15 Player Lobby on Server
+ SaveManager.ChatModeType = InnerNet.QuickChatModes.FreeChatOrQuickChat;
+ }
+ public static void Postfix(InnerNet.InnerNetClient __instance, [HarmonyArgument(0)] GameOptionsData settings) {
+ settings.MaxPlayers = DynamicLobbies.LobbyLimit;
+ }
+ }
+ [HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.JoinGame))]
+ public static class InnerNetClientJoinPatch {
+ public static void Prefix(InnerNet.InnerNetClient __instance) {
+ SaveManager.ChatModeType = InnerNet.QuickChatModes.FreeChatOrQuickChat;
+ }
+ }
+ [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerJoined))]
+ public static class AmongUsClientOnPlayerJoined {
+ public static bool Prefix(AmongUsClient __instance, [HarmonyArgument(0)] ClientData client) {
+ if (LobbyLimit < __instance.allClients.Count) { // TODO: Fix this canceling start
+ DisconnectPlayer(__instance, client.Id);
+ return false;
+ }
+ return true;
+ }
+
+ private static void DisconnectPlayer(InnerNetClient _this, int clientId) {
+ if (!_this.AmHost) {
+ return;
+ }
+ MessageWriter messageWriter = MessageWriter.Get(SendOption.Reliable);
+ messageWriter.StartMessage(4);
+ messageWriter.Write(_this.GameId);
+ messageWriter.WritePacked(clientId);
+ messageWriter.Write((byte)DisconnectReasons.GameFull);
+ messageWriter.EndMessage();
+ _this.SendOrDisconnect(messageWriter);
+ messageWriter.Recycle();
+ }
+ }
+ }
+}
--- /dev/null
+using System;
+using BepInEx;
+using BepInEx.Configuration;
+using BepInEx.IL2CPP;
+using Il2CppSystem;
+using Hazel;
+using HarmonyLib;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityEngine.Events;
+using UnhollowerBaseLib;
+using System.IO;
+using System.Reflection;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Security.Cryptography;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json;
+using Twitch;
+
+namespace TheOtherRoles.Modules {
+ [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))]
+ public class ModUpdaterButton {
+ private static void Prefix(MainMenuManager __instance) {
+ CustomHatLoader.LaunchHatFetcher();
+ ModUpdater.LaunchUpdater();
+ if (!ModUpdater.hasUpdate) return;
+ var template = GameObject.Find("ExitGameButton");
+ if (template == null) return;
+
+ var button = UnityEngine.Object.Instantiate(template, null);
+ button.transform.localPosition = new Vector3(button.transform.localPosition.x, button.transform.localPosition.y + 0.6f, button.transform.localPosition.z);
+
+ PassiveButton passiveButton = button.GetComponent<PassiveButton>();
+ passiveButton.OnClick = new Button.ButtonClickedEvent();
+ passiveButton.OnClick.AddListener((UnityEngine.Events.UnityAction)onClick);
+
+ var text = button.transform.GetChild(0).GetComponent<TMPro.TMP_Text>();
+ __instance.StartCoroutine(Effects.Lerp(0.1f, new System.Action<float>((p) => {
+ text.SetText("Update\nThe Other Roles");
+ })));
+
+ TwitchManager man = DestroyableSingleton<TwitchManager>.Instance;
+ ModUpdater.InfoPopup = UnityEngine.Object.Instantiate<GenericPopup>(man.TwitchPopup);
+ ModUpdater.InfoPopup.TextAreaTMP.fontSize *= 0.7f;
+ ModUpdater.InfoPopup.TextAreaTMP.enableAutoSizing = false;
+
+ void onClick() {
+ ModUpdater.ExecuteUpdate();
+ button.SetActive(false);
+ }
+ }
+ }
+
+ public class ModUpdater {
+ public static bool running = false;
+ public static bool hasUpdate = false;
+ public static string updateURI = null;
+ private static Task updateTask = null;
+ public static GenericPopup InfoPopup;
+
+ public static void LaunchUpdater() {
+ if (running) return;
+ running = true;
+ checkForUpdate().GetAwaiter().GetResult();
+ clearOldVersions();
+ }
+
+ public static void ExecuteUpdate() {
+ string info = "Updating The Other Roles\nPlease wait...";
+ ModUpdater.InfoPopup.Show(info); // Show originally
+ if (updateTask == null) {
+ if (updateURI != null) {
+ updateTask = downloadUpdate();
+ } else {
+ info = "Unable to auto-update\nPlease update manually";
+ }
+ } else {
+ info = "Update might already\nbe in progress";
+ }
+ ModUpdater.InfoPopup.StartCoroutine(Effects.Lerp(0.01f, new System.Action<float>((p) => { ModUpdater.setPopupText(info); })));
+ }
+
+ public static void clearOldVersions() {
+ try {
+ DirectoryInfo d = new DirectoryInfo(Path.GetDirectoryName(Application.dataPath) + @"\BepInEx\plugins");
+ string[] files = d.GetFiles("*.old").Select(x => x.FullName).ToArray(); // Getting old versions
+ foreach (string f in files)
+ File.Delete(f);
+ } catch (System.Exception e) {
+ System.Console.WriteLine("Exception occured when clearing old versions:\n" + e);
+ }
+ }
+
+ public static async Task<bool> checkForUpdate() {
+ try {
+ HttpClient http = new HttpClient();
+ http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater");
+ var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/Eisbison/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead);
+ // var response = await http.GetAsync(new System.Uri("https://api.github.com/repos/EoF-1141/TheOtherRoles/releases/latest"), HttpCompletionOption.ResponseContentRead);
+ if (response.StatusCode != HttpStatusCode.OK || response.Content == null) {
+ System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString());
+ return false;
+ }
+ string json = await response.Content.ReadAsStringAsync();
+ JObject data = JObject.Parse(json);
+
+ string tagname = data["tag_name"]?.ToString();
+ if (tagname == null) {
+ return false; // Something went wrong
+ }
+ // check version
+ System.Version ver = System.Version.Parse(tagname.Replace("v", ""));
+ int diff = TheOtherRolesPlugin.Version.CompareTo(ver);
+ if (diff < 0) { // Update required
+ hasUpdate = true;
+ JToken assets = data["assets"];
+ if (!assets.HasValues)
+ return false;
+
+ for (JToken current = assets.First; current != null; current = current.Next) {
+ string browser_download_url = current["browser_download_url"]?.ToString();
+ if (browser_download_url != null && current["content_type"] != null) {
+ if (current["content_type"].ToString().Equals("application/x-msdownload") &&
+ browser_download_url.EndsWith(".dll")) {
+ updateURI = browser_download_url;
+ return true;
+ }
+ }
+ }
+ }
+ } catch (System.Exception ex) {
+ TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
+ System.Console.WriteLine(ex);
+ }
+ return false;
+ }
+
+ public static async Task<bool> downloadUpdate() {
+ try {
+ HttpClient http = new HttpClient();
+ http.DefaultRequestHeaders.Add("User-Agent", "TheOtherRoles Updater");
+ var response = await http.GetAsync(new System.Uri(updateURI), HttpCompletionOption.ResponseContentRead);
+ if (response.StatusCode != HttpStatusCode.OK || response.Content == null) {
+ System.Console.WriteLine("Server returned no data: " + response.StatusCode.ToString());
+ return false;
+ }
+ string codeBase = Assembly.GetExecutingAssembly().CodeBase;
+ System.UriBuilder uri = new System.UriBuilder(codeBase);
+ string fullname = System.Uri.UnescapeDataString(uri.Path);
+ if (File.Exists(fullname + ".old")) // Clear old file in case it wasnt;
+ File.Delete(fullname + ".old");
+
+ File.Move(fullname, fullname + ".old"); // rename current executable to old
+
+ using (var responseStream = await response.Content.ReadAsStreamAsync()) {
+ using (var fileStream = File.Create(fullname)) { // probably want to have proper name here
+ responseStream.CopyTo(fileStream);
+ }
+ }
+ showPopup("The Other Roles\nupdated successfully\nPlease restart the game.");
+ return true;
+ } catch (System.Exception ex) {
+ TheOtherRolesPlugin.Instance.Log.LogError(ex.ToString());
+ System.Console.WriteLine(ex);
+ }
+ showPopup("Update wasn't successful\nTry again later,\nor update manually.");
+ return false;
+ }
+ private static void showPopup(string message) {
+ setPopupText(message);
+ InfoPopup.gameObject.SetActive(true);
+ }
+
+ public static void setPopupText(string message) {
+ if (InfoPopup == null)
+ return;
+ if (InfoPopup.TextAreaTMP != null) {
+ InfoPopup.TextAreaTMP.text = message;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using UnityEngine;
+
+namespace TheOtherRoles.Objects {
+ public class Arrow {
+ public float perc = 0.925f;
+ public SpriteRenderer image;
+ public GameObject arrow;
+ private Vector3 oldTarget;
+
+ private static Sprite sprite;
+ public static Sprite getSprite() {
+ if (sprite) return sprite;
+ sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Arrow.png", 200f);
+ return sprite;
+ }
+
+
+ public Arrow(Color color) {
+ arrow = new GameObject("Arrow");
+ arrow.layer = 5;
+ image = arrow.AddComponent<SpriteRenderer>();
+ image.sprite = getSprite();
+ image.color = color;
+ }
+
+ public void Update() {
+ Vector3 target = oldTarget;
+ if (target == null) target = Vector3.zero;
+ Update(target);
+ }
+
+ public void Update(Vector3 target)
+ {
+ if (arrow == null) return;
+ oldTarget = target;
+
+ Camera main = Camera.main;
+ Vector2 vector = target - main.transform.position;
+ float num = vector.magnitude / (main.orthographicSize * perc);
+ image.enabled = ((double)num > 0.3);
+ Vector2 vector2 = main.WorldToViewportPoint(target);
+ if (Between(vector2.x, 0f, 1f) && Between(vector2.y, 0f, 1f))
+ {
+ arrow.transform.position = target - (Vector3)vector.normalized * 0.6f;
+ float num2 = Mathf.Clamp(num, 0f, 1f);
+ arrow.transform.localScale = new Vector3(num2, num2, num2);
+ }
+ else
+ {
+ Vector2 vector3 = new Vector2(Mathf.Clamp(vector2.x * 2f - 1f, -1f, 1f), Mathf.Clamp(vector2.y * 2f - 1f, -1f, 1f));
+ float orthographicSize = main.orthographicSize;
+ float num3 = main.orthographicSize * main.aspect;
+ Vector3 vector4 = new Vector3(Mathf.LerpUnclamped(0f, num3 * 0.88f, vector3.x), Mathf.LerpUnclamped(0f, orthographicSize * 0.79f, vector3.y), 0f);
+ arrow.transform.position = main.transform.position + vector4;
+ arrow.transform.localScale = Vector3.one;
+ }
+
+ LookAt2d(arrow.transform, target);
+ }
+
+ private void LookAt2d(Transform transform, Vector3 target) {
+ Vector3 vector = target - transform.position;
+ vector.Normalize();
+ float num = Mathf.Atan2(vector.y, vector.x);
+ if (transform.lossyScale.x < 0f)
+ num += 3.1415927f;
+ transform.rotation = Quaternion.Euler(0f, 0f, num * 57.29578f);
+ }
+
+ private bool Between(float value, float min, float max) {
+ return value > min && value < max;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace TheOtherRoles.Objects {
+ public class CustomButton
+ {
+ public static List<CustomButton> buttons = new List<CustomButton>();
+ public KillButtonManager killButtonManager;
+ public Vector3 PositionOffset;
+ public float MaxTimer = float.MaxValue;
+ public float Timer = 0f;
+ private Action OnClick;
+ private Action OnMeetingEnds;
+ private Func<bool> HasButton;
+ private Func<bool> CouldUse;
+ private Action OnEffectEnds;
+ public bool HasEffect;
+ public bool isEffectActive = false;
+ private bool showButtonText = false;
+ public float EffectDuration;
+ public Sprite Sprite;
+ private HudManager hudManager;
+ private bool mirror;
+ private KeyCode? hotkey;
+
+ public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool HasEffect, float EffectDuration, Action OnEffectEnds, bool mirror = false)
+ {
+ this.hudManager = hudManager;
+ this.OnClick = OnClick;
+ this.HasButton = HasButton;
+ this.CouldUse = CouldUse;
+ this.PositionOffset = PositionOffset;
+ this.OnMeetingEnds = OnMeetingEnds;
+ this.HasEffect = HasEffect;
+ this.EffectDuration = EffectDuration;
+ this.OnEffectEnds = OnEffectEnds;
+ this.Sprite = Sprite;
+ this.mirror = mirror;
+ this.hotkey = hotkey;
+ Timer = 16.2f;
+ buttons.Add(this);
+ killButtonManager = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.transform);
+ this.showButtonText = killButtonManager.renderer.sprite == Sprite;
+ PassiveButton button = killButtonManager.GetComponent<PassiveButton>();
+ button.OnClick = new Button.ButtonClickedEvent();
+ button.OnClick.AddListener((UnityEngine.Events.UnityAction)onClickEvent);
+
+ setActive(false);
+ }
+
+ public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool mirror = false)
+ : this(OnClick, HasButton, CouldUse, OnMeetingEnds, Sprite, PositionOffset, hudManager, hotkey, false, 0f, () => {}, mirror) { }
+
+ void onClickEvent()
+ {
+ if (this.Timer < 0f && HasButton() && CouldUse())
+ {
+ killButtonManager.renderer.color = new Color(1f, 1f, 1f, 0.3f);
+ this.OnClick();
+
+ if (this.HasEffect && !this.isEffectActive) {
+ this.Timer = this.EffectDuration;
+ killButtonManager.TimerText.color = new Color(0F, 0.8F, 0F);
+ this.isEffectActive = true;
+ }
+ }
+ }
+
+ public static void HudUpdate()
+ {
+ buttons.RemoveAll(item => item.killButtonManager == null);
+
+ for (int i = 0; i < buttons.Count; i++)
+ {
+ try
+ {
+ buttons[i].Update();
+ }
+ catch (NullReferenceException)
+ {
+ System.Console.WriteLine("[WARNING] NullReferenceException from HudUpdate().HasButton(), if theres only one warning its fine");
+ }
+ }
+ }
+
+ public static void MeetingEndedUpdate() {
+ buttons.RemoveAll(item => item.killButtonManager == null);
+ for (int i = 0; i < buttons.Count; i++)
+ {
+ try
+ {
+ buttons[i].OnMeetingEnds();
+ buttons[i].Update();
+ }
+ catch (NullReferenceException)
+ {
+ System.Console.WriteLine("[WARNING] NullReferenceException from MeetingEndedUpdate().HasButton(), if theres only one warning its fine");
+ }
+ }
+ }
+
+ public static void ResetAllCooldowns() {
+ for (int i = 0; i < buttons.Count; i++)
+ {
+ try
+ {
+ buttons[i].Timer = buttons[i].MaxTimer;
+ buttons[i].Update();
+ }
+ catch (NullReferenceException)
+ {
+ System.Console.WriteLine("[WARNING] NullReferenceException from MeetingEndedUpdate().HasButton(), if theres only one warning its fine");
+ }
+ }
+ }
+
+ public void setActive(bool isActive) {
+ if (isActive) {
+ killButtonManager.gameObject.SetActive(true);
+ killButtonManager.renderer.enabled = true;
+ } else {
+ killButtonManager.gameObject.SetActive(false);
+ killButtonManager.renderer.enabled = false;
+ }
+ }
+
+ private void Update()
+ {
+ if (PlayerControl.LocalPlayer.Data == null || MeetingHud.Instance || ExileController.Instance || !HasButton()) {
+ setActive(false);
+ return;
+ }
+ setActive(hudManager.UseButton.isActiveAndEnabled);
+
+ killButtonManager.renderer.sprite = Sprite;
+ killButtonManager.killText.enabled = showButtonText; // Only show the text if it's a kill button
+ if (hudManager.UseButton != null) {
+ Vector3 pos = hudManager.UseButton.transform.localPosition;
+ if (mirror) pos = new Vector3(-pos.x, pos.y, pos.z);
+ killButtonManager.transform.localPosition = pos + PositionOffset;
+ if (hudManager.KillButton != null) hudManager.KillButton.transform.localPosition = hudManager.UseButton.transform.localPosition - new Vector3(1.3f, 0, 0); // Align the kill button (because it's on another position depending on the screen resolution)
+ }
+ if (CouldUse()) {
+ killButtonManager.renderer.color = killButtonManager.killText.color = Palette.EnabledColor;
+ killButtonManager.renderer.material.SetFloat("_Desat", 0f);
+ } else {
+ killButtonManager.renderer.color = killButtonManager.killText.color = Palette.DisabledClear;
+ killButtonManager.renderer.material.SetFloat("_Desat", 1f);
+ }
+
+ if (Timer >= 0) {
+ if (HasEffect && isEffectActive)
+ Timer -= Time.deltaTime;
+ else if (!PlayerControl.LocalPlayer.inVent && PlayerControl.LocalPlayer.moveable)
+ Timer -= Time.deltaTime;
+ }
+
+ if (Timer <= 0 && HasEffect && isEffectActive) {
+ isEffectActive = false;
+ killButtonManager.TimerText.color = Palette.EnabledColor;
+ OnEffectEnds();
+ }
+
+ killButtonManager.SetCoolDown(Timer, (HasEffect && isEffectActive) ? EffectDuration : MaxTimer);
+
+ // Trigger OnClickEvent if the hotkey is being pressed down
+ if (hotkey.HasValue && Input.GetKeyDown(hotkey.Value)) onClickEvent();
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using UnityEngine;
+using System.Collections.Generic;
+using System;
+
+namespace TheOtherRoles.Objects {
+
+ public class CustomMessage {
+
+ private TMPro.TMP_Text text;
+ private static List<CustomMessage> customMessages = new List<CustomMessage>();
+
+ public CustomMessage(string message, float duration) {
+ RoomTracker roomTracker = HudManager.Instance?.roomTracker;
+ if (roomTracker != null) {
+ GameObject gameObject = UnityEngine.Object.Instantiate(roomTracker.gameObject);
+
+ gameObject.transform.SetParent(HudManager.Instance.transform);
+ UnityEngine.Object.DestroyImmediate(gameObject.GetComponent<RoomTracker>());
+ text = gameObject.GetComponent<TMPro.TMP_Text>();
+ text.text = message;
+
+ // Use local position to place it in the player's view instead of the world location
+ gameObject.transform.localPosition = new Vector3(0, -1.8f, gameObject.transform.localPosition.z);
+ customMessages.Add(this);
+
+ HudManager.Instance.StartCoroutine(Effects.Lerp(duration, new Action<float>((p) => {
+ bool even = ((int)(p * duration / 0.25f)) % 2 == 0; // Bool flips every 0.25 seconds
+ string prefix = (even ? "<color=#FCBA03FF>" : "<color=#FF0000FF>");
+ text.text = prefix + message + "</color>";
+ if (text != null) text.color = even ? Color.yellow : Color.red;
+ if (p == 1f && text != null && text.gameObject != null) {
+ UnityEngine.Object.Destroy(text.gameObject);
+ customMessages.Remove(this);
+ }
+ })));
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using UnityEngine;
+using static TheOtherRoles.TheOtherRoles;
+
+namespace TheOtherRoles.Objects {
+ class Footprint {
+ private static List<Footprint> footprints = new List<Footprint>();
+ private static Sprite sprite;
+ private Color color;
+ private GameObject footprint;
+ private SpriteRenderer spriteRenderer;
+ private PlayerControl owner;
+ private bool anonymousFootprints;
+
+ public static Sprite getFootprintSprite() {
+ if (sprite) return sprite;
+ sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Footprint.png", 600f);
+ return sprite;
+ }
+
+ public Footprint(float footprintDuration, bool anonymousFootprints, PlayerControl player) {
+ this.owner = player;
+ this.anonymousFootprints = anonymousFootprints;
+ if (anonymousFootprints)
+ this.color = Palette.PlayerColors[6];
+ else
+ this.color = Palette.PlayerColors[(int) player.Data.ColorId];
+
+ footprint = new GameObject("Footprint");
+ Vector3 position = new Vector3(player.transform.position.x, player.transform.position.y, player.transform.position.z + 1f);
+ footprint.transform.position = position;
+ footprint.transform.localPosition = position;
+ footprint.transform.SetParent(player.transform.parent);
+
+ footprint.transform.Rotate(0.0f, 0.0f, UnityEngine.Random.Range(0.0f, 360.0f));
+
+
+ spriteRenderer = footprint.AddComponent<SpriteRenderer>();
+ spriteRenderer.sprite = getFootprintSprite();
+ spriteRenderer.color = color;
+
+ footprint.SetActive(true);
+ footprints.Add(this);
+
+ HudManager.Instance.StartCoroutine(Effects.Lerp(footprintDuration, new Action<float>((p) => {
+ Color c = color;
+ if (!anonymousFootprints && owner != null) {
+ if (owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget?.Data != null)
+ c = Palette.ShadowColors[Morphling.morphTarget.Data.ColorId];
+ else if (Camouflager.camouflageTimer > 0)
+ c = Palette.PlayerColors[6];
+ }
+
+ if (spriteRenderer) spriteRenderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01(1 - p));
+
+ if (p == 1f && footprint != null) {
+ UnityEngine.Object.Destroy(footprint);
+ footprints.Remove(this);
+ }
+ })));
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using UnityEngine;
+
+namespace TheOtherRoles.Objects {
+ class Garlic {
+ public static List<Garlic> garlics = new List<Garlic>();
+
+ public GameObject garlic;
+ private GameObject background;
+
+ private static Sprite garlicSprite;
+ public static Sprite getGarlicSprite() {
+ if (garlicSprite) return garlicSprite;
+ garlicSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Garlic.png", 300f);
+ return garlicSprite;
+ }
+
+ private static Sprite backgroundSprite;
+ public static Sprite getBackgroundSprite() {
+ if (backgroundSprite) return backgroundSprite;
+ backgroundSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.GarlicBackground.png", 60f);
+ return backgroundSprite;
+ }
+
+ public Garlic(Vector2 p) {
+ garlic = new GameObject("Garlic");
+ background = new GameObject("Background");
+ background.transform.SetParent(garlic.transform);
+ Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player
+ garlic.transform.position = position;
+ garlic.transform.localPosition = position;
+ background.transform.localPosition = new Vector3(0 , 0, -0.01f); // before player
+
+ var garlicRenderer = garlic.AddComponent<SpriteRenderer>();
+ garlicRenderer.sprite = getGarlicSprite();
+ var backgroundRenderer = background.AddComponent<SpriteRenderer>();
+ backgroundRenderer.sprite = getBackgroundSprite();
+
+
+ garlic.SetActive(true);
+ garlics.Add(this);
+ }
+
+ public static void clearGarlics() {
+ garlics = new List<Garlic>();
+ }
+
+ public static void UpdateAll() {
+ foreach (Garlic garlic in garlics) {
+ if (garlic != null)
+ garlic.Update();
+ }
+ }
+
+ public void Update() {
+ if (background != null)
+ background.transform.Rotate(Vector3.forward * 6 * Time.fixedDeltaTime);
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using UnityEngine;
+using System.Linq;
+
+namespace TheOtherRoles.Objects {
+
+ public class JackInTheBox {
+ public static System.Collections.Generic.List<JackInTheBox> AllJackInTheBoxes = new System.Collections.Generic.List<JackInTheBox>();
+ public static int JackInTheBoxLimit = 3;
+ public static bool boxesConvertedToVents = false;
+ public static Sprite[] boxAnimationSprites = new Sprite[18];
+
+ public static Sprite getBoxAnimationSprite(int index) {
+ if (boxAnimationSprites == null || boxAnimationSprites.Length == 0) return null;
+ index = Mathf.Clamp(index, 0, boxAnimationSprites.Length - 1);
+ if (boxAnimationSprites[index] == null)
+ boxAnimationSprites[index] = (Helpers.loadSpriteFromResources($"TheOtherRoles.Resources.TricksterAnimation.trickster_box_00{(index + 1):00}.png", 175f));
+ return boxAnimationSprites[index];
+ }
+
+ public static void startAnimation(int ventId) {
+ JackInTheBox box = AllJackInTheBoxes.FirstOrDefault((x) => x?.vent != null && x.vent.Id == ventId);
+ if (box == null) return;
+ Vent vent = box.vent;
+
+ HudManager.Instance.StartCoroutine(Effects.Lerp(0.6f, new Action<float>((p) => {
+ if (vent != null && vent.myRend != null) {
+ vent.myRend.sprite = getBoxAnimationSprite((int)(p * boxAnimationSprites.Length));
+ if (p == 1f) vent.myRend.sprite = getBoxAnimationSprite(0);
+ }
+ })));
+ }
+
+ private GameObject gameObject;
+ public Vent vent;
+
+ public JackInTheBox(Vector2 p) {
+ gameObject = new GameObject("JackInTheBox");
+ 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;
+ var boxRenderer = gameObject.AddComponent<SpriteRenderer>();
+ boxRenderer.sprite = getBoxAnimationSprite(0);
+
+ // Create the vent
+ var referenceVent = UnityEngine.Object.FindObjectOfType<Vent>();
+ vent = UnityEngine.Object.Instantiate<Vent>(referenceVent);
+ vent.transform.position = gameObject.transform.position;
+ vent.Left = null;
+ vent.Right = null;
+ vent.Center = null;
+ vent.EnterVentAnim = null;
+ vent.ExitVentAnim = null;
+ vent.Offset = new Vector3(0f, 0.25f, 0f);
+ vent.GetComponent<PowerTools.SpriteAnim>()?.Stop();
+ vent.Id = ShipStatus.Instance.AllVents.Select(x => x.Id).Max() + 1; // Make sure we have a unique id
+ var ventRenderer = vent.GetComponent<SpriteRenderer>();
+ ventRenderer.sprite = getBoxAnimationSprite(0);
+ vent.myRend = ventRenderer;
+ var allVentsList = ShipStatus.Instance.AllVents.ToList();
+ allVentsList.Add(vent);
+ ShipStatus.Instance.AllVents = allVentsList.ToArray();
+ vent.gameObject.SetActive(false);
+ vent.name = "JackInTheBoxVent_" + vent.Id;
+
+ // Only render the box for the Trickster
+ var playerIsTrickster = PlayerControl.LocalPlayer == Trickster.trickster;
+ gameObject.SetActive(playerIsTrickster);
+
+ AllJackInTheBoxes.Add(this);
+ }
+
+ public static void UpdateStates() {
+ if (boxesConvertedToVents == true) return;
+ foreach (var box in AllJackInTheBoxes) {
+ var playerIsTrickster = PlayerControl.LocalPlayer == Trickster.trickster;
+ box.gameObject.SetActive(playerIsTrickster);
+ }
+ }
+
+ public void convertToVent() {
+ gameObject.SetActive(false);
+ vent.gameObject.SetActive(true);
+ return;
+ }
+
+ public static void convertToVents() {
+ foreach (var box in AllJackInTheBoxes) {
+ box.convertToVent();
+ }
+ connectVents();
+ boxesConvertedToVents = true;
+ return;
+ }
+
+ public static bool hasJackInTheBoxLimitReached() {
+ return (AllJackInTheBoxes.Count >= JackInTheBoxLimit);
+ }
+
+ private static void connectVents() {
+ for (var i = 0; i < AllJackInTheBoxes.Count - 1; i++) {
+ var a = AllJackInTheBoxes[i];
+ var b = AllJackInTheBoxes[i + 1];
+ a.vent.Right = b.vent;
+ b.vent.Left = a.vent;
+ }
+ // Connect first with last
+ AllJackInTheBoxes.First().vent.Left = AllJackInTheBoxes.Last().vent;
+ AllJackInTheBoxes.Last().vent.Right = AllJackInTheBoxes.First().vent;
+ }
+
+ public static void clearJackInTheBoxes() {
+ boxesConvertedToVents = false;
+ AllJackInTheBoxes = new List<JackInTheBox>();
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+
+using HarmonyLib;
+using UnityEngine;
+using System.Collections.Generic;
+using Hazel;
+using System;
+using UnityEngine.UI;
+using UnityEngine.Events;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(OptionsMenuBehaviour), nameof(OptionsMenuBehaviour.Start))]
+ public class OptionsMenuBehaviourStartPatch {
+ private static Vector3? origin;
+ private static ToggleButtonBehaviour streamerModeButton;
+ private static ToggleButtonBehaviour ghostsSeeTasksButton;
+ private static ToggleButtonBehaviour ghostsSeeRolesButton;
+ private static ToggleButtonBehaviour ghostsSeeVotesButton;
+ private static ToggleButtonBehaviour showRoleSummaryButton;
+
+ public static float xOffset = 1.75f;
+ public static float yOffset = -0.5f;
+
+ private static void updateToggle(ToggleButtonBehaviour button, string text, bool on) {
+ if (button == null || button.gameObject == null) return;
+
+ Color color = on ? new Color(0f, 1f, 0.16470589f, 1f) : Color.white;
+ button.Background.color = color;
+ button.Text.text = $"{text}{(on ? "On" : "Off")}";
+ if (button.Rollover) button.Rollover.ChangeOutColor(color);
+ }
+
+ private static ToggleButtonBehaviour createCustomToggle(string text, bool on, Vector3 offset, UnityEngine.Events.UnityAction onClick, OptionsMenuBehaviour __instance) {
+ if (__instance.CensorChatButton != null) {
+ var button = UnityEngine.Object.Instantiate(__instance.CensorChatButton, __instance.CensorChatButton.transform.parent);
+ button.transform.localPosition = (origin ?? Vector3.zero) + offset;
+ PassiveButton passiveButton = button.GetComponent<PassiveButton>();
+ passiveButton.OnClick = new Button.ButtonClickedEvent();
+ passiveButton.OnClick.AddListener(onClick);
+ updateToggle(button, text, on);
+
+ return button;
+ }
+ return null;
+ }
+
+ public static void Postfix(OptionsMenuBehaviour __instance) {
+ if (__instance.CensorChatButton != null) {
+ if (origin == null) origin = __instance.CensorChatButton.transform.localPosition + Vector3.up * 0.25f;
+ __instance.CensorChatButton.transform.localPosition = origin.Value + Vector3.left * xOffset;
+ __instance.CensorChatButton.transform.localScale = Vector3.one * 2f / 3f;
+ }
+
+ if ((streamerModeButton == null || streamerModeButton.gameObject == null)) {
+ streamerModeButton = createCustomToggle("Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value, Vector3.zero, (UnityEngine.Events.UnityAction)streamerModeToggle, __instance);
+
+ void streamerModeToggle() {
+ TheOtherRolesPlugin.StreamerMode.Value = !TheOtherRolesPlugin.StreamerMode.Value;
+ updateToggle(streamerModeButton, "Streamer Mode: ", TheOtherRolesPlugin.StreamerMode.Value);
+ }
+ }
+
+ if ((ghostsSeeTasksButton == null || ghostsSeeTasksButton.gameObject == null)) {
+ ghostsSeeTasksButton = createCustomToggle("Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value, Vector3.right * xOffset, (UnityEngine.Events.UnityAction)ghostsSeeTaskToggle, __instance);
+
+ void ghostsSeeTaskToggle() {
+ TheOtherRolesPlugin.GhostsSeeTasks.Value = !TheOtherRolesPlugin.GhostsSeeTasks.Value;
+ MapOptions.ghostsSeeTasks = TheOtherRolesPlugin.GhostsSeeTasks.Value;
+ updateToggle(ghostsSeeTasksButton, "Ghosts See Remaining Tasks: ", TheOtherRolesPlugin.GhostsSeeTasks.Value);
+ }
+ }
+
+ if ((ghostsSeeRolesButton == null || ghostsSeeRolesButton.gameObject == null)) {
+ ghostsSeeRolesButton = createCustomToggle("Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value, new Vector2(-xOffset, yOffset), (UnityEngine.Events.UnityAction)ghostsSeeRolesToggle, __instance);
+
+ void ghostsSeeRolesToggle() {
+ TheOtherRolesPlugin.GhostsSeeRoles.Value = !TheOtherRolesPlugin.GhostsSeeRoles.Value;
+ MapOptions.ghostsSeeRoles = TheOtherRolesPlugin.GhostsSeeRoles.Value;
+ updateToggle(ghostsSeeRolesButton, "Ghosts See Roles: ", TheOtherRolesPlugin.GhostsSeeRoles.Value);
+ }
+ }
+
+ if ((ghostsSeeVotesButton == null || ghostsSeeVotesButton.gameObject == null)) {
+ ghostsSeeVotesButton = createCustomToggle("Ghosts See Votes: ", TheOtherRolesPlugin.GhostsSeeVotes.Value, new Vector2(0, yOffset), (UnityEngine.Events.UnityAction)ghostsSeeVotesToggle, __instance);
+
+ void ghostsSeeVotesToggle() {
+ TheOtherRolesPlugin.GhostsSeeVotes.Value = !TheOtherRolesPlugin.GhostsSeeVotes.Value;
+ MapOptions.ghostsSeeVotes = TheOtherRolesPlugin.GhostsSeeVotes.Value;
+ updateToggle(ghostsSeeVotesButton, "Ghosts See Votes: ", TheOtherRolesPlugin.GhostsSeeVotes.Value);
+ }
+ }
+
+ if ((showRoleSummaryButton == null || showRoleSummaryButton.gameObject == null)) {
+ showRoleSummaryButton = createCustomToggle("Role Summary: ", TheOtherRolesPlugin.ShowRoleSummary.Value, new Vector2(xOffset, yOffset), (UnityEngine.Events.UnityAction)showRoleSummaryToggle, __instance);
+
+ void showRoleSummaryToggle() {
+ TheOtherRolesPlugin.ShowRoleSummary.Value = !TheOtherRolesPlugin.ShowRoleSummary.Value;
+ MapOptions.showRoleSummary = TheOtherRolesPlugin.ShowRoleSummary.Value;
+ updateToggle(showRoleSummaryButton, "Role Summary: ", TheOtherRolesPlugin.ShowRoleSummary.Value);
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(TextBoxTMP), nameof(TextBoxTMP.SetText))]
+ public static class HiddenTextPatch
+ {
+ private static void Postfix(TextBoxTMP __instance)
+ {
+ bool flag = TheOtherRolesPlugin.StreamerMode.Value && (__instance.name == "GameIdText" || __instance.name == "IpTextBox" || __instance.name == "PortTextBox");
+ if (flag) __instance.outputText.text = new string('*', __instance.text.Length);
+ }
+ }
+}
--- /dev/null
+using HarmonyLib;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnityEngine;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch]
+ public static class CredentialsPatch {
+ public static string fullCredentials =
+$@"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString()}
+<size=80%>Modded by <color=#FCCE03FF>Eisbison</color>,
+<color=#FCCE03FF>Thunderstorm584</color> & <color=#FCCE03FF>EndOfFile</color>
+Button design by <color=#FCCE03FF>Bavari</color></size>";
+
+ public static string mainMenuCredentials =
+$@"Modded by <color=#FCCE03FF>Eisbison</color>, <color=#FCCE03FF>Thunderstorm584</color> & <color=#FCCE03FF>EndOfFile</color>
+Design by <color=#FCCE03FF>Bavari</color>";
+
+ [HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
+ private static class VersionShowerPatch
+ {
+ static void Postfix(VersionShower __instance) {
+ var amongUsLogo = GameObject.Find("bannerLogo_AmongUs");
+ if (amongUsLogo == null) return;
+
+ var credentials = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(__instance.text);
+ credentials.transform.position = new Vector3(0, 0.1f, 0);
+ credentials.SetText(mainMenuCredentials);
+ credentials.alignment = TMPro.TextAlignmentOptions.Center;
+ credentials.fontSize *= 0.75f;
+
+ var version = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(credentials);
+ version.transform.position = new Vector3(0, -0.25f, 0);
+ version.SetText($"v{TheOtherRolesPlugin.Version.ToString()}");
+
+ credentials.transform.SetParent(amongUsLogo.transform);
+ version.transform.SetParent(amongUsLogo.transform);
+ }
+ }
+
+ [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, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
+ } else {
+ __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, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start))]
+ private static class LogoPatch
+ {
+ static void Postfix(PingTracker __instance) {
+ var amongUsLogo = GameObject.Find("bannerLogo_AmongUs");
+ if (amongUsLogo != null) {
+ amongUsLogo.transform.localScale *= 0.6f;
+ amongUsLogo.transform.position += Vector3.up * 0.25f;
+ }
+
+ var torLogo = new GameObject("bannerLogo_TOR");
+ torLogo.transform.position = Vector3.up;
+ var renderer = torLogo.AddComponent<SpriteRenderer>();
+ renderer.sprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Banner.png", 300f);
+ }
+ }
+ }
+}
--- /dev/null
+
+using HarmonyLib;
+using static TheOtherRoles.TheOtherRoles;
+using static TheOtherRoles.GameHistory;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using System.Linq;
+using Hazel;
+using UnhollowerBaseLib;
+using System;
+using System.Text;
+
+namespace TheOtherRoles.Patches {
+ enum CustomGameOverReason {
+ LoversWin = 10,
+ TeamJackalWin = 11,
+ MiniLose = 12,
+ JesterWin = 13,
+ ArsonistWin = 14
+ }
+
+ enum WinCondition {
+ Default,
+ LoversTeamWin,
+ LoversSoloWin,
+ JesterWin,
+ JackalWin,
+ MiniLose,
+ ArsonistWin
+ }
+
+ static class AdditionalTempData {
+ // Should be implemented using a proper GameOverReason in the future
+ public static WinCondition winCondition = WinCondition.Default;
+ public static List<PlayerRoleInfo> playerRoles = new List<PlayerRoleInfo>();
+
+ public static void clear() {
+ playerRoles.Clear();
+ winCondition = WinCondition.Default;
+ }
+
+ internal class PlayerRoleInfo {
+ public string PlayerName { get; set; }
+ public List<RoleInfo> Roles {get;set;}
+ public int TasksCompleted {get;set;}
+ public int TasksTotal {get;set;}
+ }
+ }
+
+
+ [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
+ public class OnGameEndPatch {
+ private static GameOverReason gameOverReason;
+ public static void Prefix(AmongUsClient __instance, [HarmonyArgument(0)]ref GameOverReason reason, [HarmonyArgument(1)]bool showAd) {
+ gameOverReason = reason;
+ if ((int)reason >= 10) reason = GameOverReason.ImpostorByKill;
+ }
+
+ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)]ref GameOverReason reason, [HarmonyArgument(1)]bool showAd) {
+ AdditionalTempData.clear();
+
+ foreach(var playerControl in PlayerControl.AllPlayerControls) {
+ var roles = RoleInfo.getRoleInfoForPlayer(playerControl);
+ var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(playerControl.Data);
+ AdditionalTempData.playerRoles.Add(new AdditionalTempData.PlayerRoleInfo() { PlayerName = playerControl.Data.PlayerName, Roles = roles, TasksTotal = tasksTotal, TasksCompleted = tasksCompleted });
+ }
+
+ // Remove Jester, Arsonist, Jackal, former Jackals and Sidekick from winners (if they win, they'll be readded)
+ List<PlayerControl> notWinners = new List<PlayerControl>();
+ if (Jester.jester != null) notWinners.Add(Jester.jester);
+ if (Sidekick.sidekick != null) notWinners.Add(Sidekick.sidekick);
+ if (Jackal.jackal != null) notWinners.Add(Jackal.jackal);
+ if (Arsonist.arsonist != null) notWinners.Add(Arsonist.arsonist);
+ notWinners.AddRange(Jackal.formerJackals);
+
+ List<WinningPlayerData> winnersToRemove = new List<WinningPlayerData>();
+ foreach (WinningPlayerData winner in TempData.winners) {
+ if (notWinners.Any(x => x.Data.PlayerName == winner.Name)) winnersToRemove.Add(winner);
+ }
+ foreach (var winner in winnersToRemove) TempData.winners.Remove(winner);
+
+ bool jesterWin = Jester.jester != null && gameOverReason == (GameOverReason)CustomGameOverReason.JesterWin;
+ bool arsonistWin = Arsonist.arsonist != null && gameOverReason == (GameOverReason)CustomGameOverReason.ArsonistWin;
+ bool miniLose = Mini.mini != null && gameOverReason == (GameOverReason)CustomGameOverReason.MiniLose;
+ bool loversWin = Lovers.existingAndAlive() && (gameOverReason == (GameOverReason)CustomGameOverReason.LoversWin || (TempData.DidHumansWin(gameOverReason) && !Lovers.existingWithKiller())); // Either they win if they are among the last 3 players, or they win if they are both Crewmates and both alive and the Crew wins (Team Imp/Jackal Lovers can only win solo wins)
+ bool teamJackalWin = gameOverReason == (GameOverReason)CustomGameOverReason.TeamJackalWin && ((Jackal.jackal != null && !Jackal.jackal.Data.IsDead) || (Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead));
+
+ // Mini lose
+ if (miniLose) {
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ WinningPlayerData wpd = new WinningPlayerData(Mini.mini.Data);
+ wpd.IsYou = false; // If "no one is the Mini", it will display the Mini, but also show defeat to everyone
+ TempData.winners.Add(wpd);
+ AdditionalTempData.winCondition = WinCondition.MiniLose;
+ }
+
+ // Jester win
+ else if (jesterWin) {
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ WinningPlayerData wpd = new WinningPlayerData(Jester.jester.Data);
+ TempData.winners.Add(wpd);
+ AdditionalTempData.winCondition = WinCondition.JesterWin;
+ }
+
+ // Arsonist win
+ else if (arsonistWin) {
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ WinningPlayerData wpd = new WinningPlayerData(Arsonist.arsonist.Data);
+ TempData.winners.Add(wpd);
+ AdditionalTempData.winCondition = WinCondition.ArsonistWin;
+ }
+
+ // Lovers win conditions
+ else if (loversWin) {
+ // Double win for lovers, crewmates also win
+ if (!Lovers.existingWithKiller()) {
+ AdditionalTempData.winCondition = WinCondition.LoversTeamWin;
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ if (p == null) continue;
+ if (p == Lovers.lover1 || p == Lovers.lover2)
+ TempData.winners.Add(new WinningPlayerData(p.Data));
+ else if (p != Jester.jester && p != Jackal.jackal && p != Sidekick.sidekick && p != Arsonist.arsonist && !Jackal.formerJackals.Contains(p) && !p.Data.IsImpostor)
+ TempData.winners.Add(new WinningPlayerData(p.Data));
+ }
+ }
+ // Lovers solo win
+ else {
+ AdditionalTempData.winCondition = WinCondition.LoversSoloWin;
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ TempData.winners.Add(new WinningPlayerData(Lovers.lover1.Data));
+ TempData.winners.Add(new WinningPlayerData(Lovers.lover2.Data));
+ }
+ }
+
+ // Jackal win condition (should be implemented using a proper GameOverReason in the future)
+ else if (teamJackalWin) {
+ // Jackal wins if nobody except jackal is alive
+ AdditionalTempData.winCondition = WinCondition.JackalWin;
+ TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
+ WinningPlayerData wpd = new WinningPlayerData(Jackal.jackal.Data);
+ wpd.IsImpostor = false;
+ TempData.winners.Add(wpd);
+ // If there is a sidekick. The sidekick also wins
+ if (Sidekick.sidekick != null) {
+ WinningPlayerData wpdSidekick = new WinningPlayerData(Sidekick.sidekick.Data);
+ wpdSidekick.IsImpostor = false;
+ TempData.winners.Add(wpdSidekick);
+ }
+ foreach(var player in Jackal.formerJackals) {
+ WinningPlayerData wpdFormerJackal = new WinningPlayerData(player.Data);
+ wpdFormerJackal.IsImpostor = false;
+ TempData.winners.Add(wpdFormerJackal);
+ }
+ }
+
+ // Reset Settings
+ RPCProcedure.resetVariables();
+ }
+ }
+
+ [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
+ public class EndGameManagerSetUpPatch {
+ public static void Postfix(EndGameManager __instance) {
+ GameObject bonusText = UnityEngine.Object.Instantiate(__instance.WinText.gameObject);
+ bonusText.transform.position = new Vector3(__instance.WinText.transform.position.x, __instance.WinText.transform.position.y - 0.8f, __instance.WinText.transform.position.z);
+ bonusText.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
+ TMPro.TMP_Text textRenderer = bonusText.GetComponent<TMPro.TMP_Text>();
+ textRenderer.text = "";
+
+ if (AdditionalTempData.winCondition == WinCondition.JesterWin) {
+ textRenderer.text = "Jester Wins";
+ textRenderer.color = Jester.color;
+ }
+ else if (AdditionalTempData.winCondition == WinCondition.ArsonistWin) {
+ textRenderer.text = "Arsonist Wins";
+ textRenderer.color = Arsonist.color;
+ }
+ else if (AdditionalTempData.winCondition == WinCondition.LoversTeamWin) {
+ textRenderer.text = "Lovers And Crewmates Win";
+ textRenderer.color = Lovers.color;
+ __instance.BackgroundBar.material.SetColor("_Color", Lovers.color);
+ }
+ else if (AdditionalTempData.winCondition == WinCondition.LoversSoloWin) {
+ textRenderer.text = "Lovers Win";
+ textRenderer.color = Lovers.color;
+ __instance.BackgroundBar.material.SetColor("_Color", Lovers.color);
+ }
+ else if (AdditionalTempData.winCondition == WinCondition.JackalWin) {
+ textRenderer.text = "Team Jackal Wins";
+ textRenderer.color = Jackal.color;
+ }
+ else if (AdditionalTempData.winCondition == WinCondition.MiniLose) {
+ textRenderer.text = "Mini died";
+ textRenderer.color = Mini.color;
+ }
+
+ 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);
+ roleSummary.transform.localScale = new Vector3(1f, 1f, 1f);
+
+ var roleSummaryText = new StringBuilder();
+ roleSummaryText.AppendLine("Players and roles at the end of the game:");
+ foreach(var data in AdditionalTempData.playerRoles) {
+ var roles = string.Join(" ", data.Roles.Select(x => Helpers.cs(x.color, x.name)));
+ var taskInfo = data.TasksTotal > 0 ? $" - <color=#FAD934FF>({data.TasksCompleted}/{data.TasksTotal})</color>" : "";
+ roleSummaryText.AppendLine($"{data.PlayerName} - {roles}{taskInfo}");
+ }
+ TMPro.TMP_Text roleSummaryTextMesh = roleSummary.GetComponent<TMPro.TMP_Text>();
+ roleSummaryTextMesh.alignment = TMPro.TextAlignmentOptions.TopLeft;
+ roleSummaryTextMesh.color = Color.white;
+ roleSummaryTextMesh.fontSizeMin = 1.5f;
+ roleSummaryTextMesh.fontSizeMax = 1.5f;
+ roleSummaryTextMesh.fontSize = 1.5f;
+
+ var roleSummaryTextMeshRectTransform = roleSummaryTextMesh.GetComponent<RectTransform>();
+ roleSummaryTextMeshRectTransform.anchoredPosition = new Vector2(position.x + 3.5f, position.y - 0.1f);
+ roleSummaryTextMesh.text = roleSummaryText.ToString();
+ }
+ AdditionalTempData.clear();
+ }
+ }
+
+ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CheckEndCriteria))]
+ class CheckEndCriteriaPatch {
+ public static bool Prefix(ShipStatus __instance) {
+ if (!GameData.Instance) return false;
+ if (DestroyableSingleton<TutorialManager>.InstanceExists) // InstanceExists | Don't check Custom Criteria when in Tutorial
+ return true;
+ var statistics = new PlayerStatistics(__instance);
+ if (CheckAndEndGameForMiniLose(__instance)) return false;
+ if (CheckAndEndGameForJesterWin(__instance)) return false;
+ if (CheckAndEndGameForArsonistWin(__instance)) return false;
+ if (CheckAndEndGameForSabotageWin(__instance)) return false;
+ if (CheckAndEndGameForTaskWin(__instance)) return false;
+ if (CheckAndEndGameForLoverWin(__instance, statistics)) return false;
+ if (CheckAndEndGameForJackalWin(__instance, statistics)) return false;
+ if (CheckAndEndGameForImpostorWin(__instance, statistics)) return false;
+ if (CheckAndEndGameForCrewmateWin(__instance, statistics)) return false;
+ return false;
+ }
+
+ private static bool CheckAndEndGameForMiniLose(ShipStatus __instance) {
+ if (Mini.triggerMiniLose) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.MiniLose, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForJesterWin(ShipStatus __instance) {
+ if (Jester.triggerJesterWin) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.JesterWin, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForArsonistWin(ShipStatus __instance) {
+ if (Arsonist.triggerArsonistWin) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.ArsonistWin, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForSabotageWin(ShipStatus __instance) {
+ if (__instance.Systems == null) return false;
+ ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.LifeSupp) ? __instance.Systems[SystemTypes.LifeSupp] : null;
+ if (systemType != null) {
+ LifeSuppSystemType lifeSuppSystemType = systemType.TryCast<LifeSuppSystemType>();
+ if (lifeSuppSystemType != null && lifeSuppSystemType.Countdown < 0f) {
+ EndGameForSabotage(__instance);
+ lifeSuppSystemType.Countdown = 10000f;
+ return true;
+ }
+ }
+ ISystemType systemType2 = __instance.Systems.ContainsKey(SystemTypes.Reactor) ? __instance.Systems[SystemTypes.Reactor] : null;
+ if (systemType2 == null) {
+ systemType2 = __instance.Systems.ContainsKey(SystemTypes.Laboratory) ? __instance.Systems[SystemTypes.Laboratory] : null;
+ }
+ if (systemType2 != null) {
+ ICriticalSabotage criticalSystem = systemType2.TryCast<ICriticalSabotage>();
+ if (criticalSystem != null && criticalSystem.Countdown < 0f) {
+ EndGameForSabotage(__instance);
+ criticalSystem.ClearSabotage();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForTaskWin(ShipStatus __instance) {
+ if (GameData.Instance.TotalTasks <= GameData.Instance.CompletedTasks) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame(GameOverReason.HumansByTask, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForLoverWin(ShipStatus __instance, PlayerStatistics statistics) {
+ if (statistics.TeamLoversAlive == 2 && statistics.TotalAlive <= 3) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.LoversWin, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForJackalWin(ShipStatus __instance, PlayerStatistics statistics) {
+ if (statistics.TeamJackalAlive >= statistics.TotalAlive - statistics.TeamJackalAlive && statistics.TeamImpostorsAlive == 0 && !(statistics.TeamJackalHasAliveLover && statistics.TeamLoversAlive == 2)) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame((GameOverReason)CustomGameOverReason.TeamJackalWin, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForImpostorWin(ShipStatus __instance, PlayerStatistics statistics) {
+ if (statistics.TeamImpostorsAlive >= statistics.TotalAlive - statistics.TeamImpostorsAlive && statistics.TeamJackalAlive == 0 && !(statistics.TeamImpostorHasAliveLover && statistics.TeamLoversAlive == 2)) {
+ __instance.enabled = false;
+ GameOverReason endReason;
+ switch (TempData.LastDeathReason) {
+ case DeathReason.Exile:
+ endReason = GameOverReason.ImpostorByVote;
+ break;
+ case DeathReason.Kill:
+ endReason = GameOverReason.ImpostorByKill;
+ break;
+ default:
+ endReason = GameOverReason.ImpostorByVote;
+ break;
+ }
+ ShipStatus.RpcEndGame(endReason, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static bool CheckAndEndGameForCrewmateWin(ShipStatus __instance, PlayerStatistics statistics) {
+ if (statistics.TeamImpostorsAlive == 0 && statistics.TeamJackalAlive == 0) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame(GameOverReason.HumansByVote, false);
+ return true;
+ }
+ return false;
+ }
+
+ private static void EndGameForSabotage(ShipStatus __instance) {
+ __instance.enabled = false;
+ ShipStatus.RpcEndGame(GameOverReason.ImpostorBySabotage, false);
+ return;
+ }
+
+ }
+
+ internal class PlayerStatistics {
+ public int TeamImpostorsAlive {get;set;}
+ public int TeamJackalAlive {get;set;}
+ public int TeamLoversAlive {get;set;}
+ public int TotalAlive {get;set;}
+ public bool TeamImpostorHasAliveLover {get;set;}
+ public bool TeamJackalHasAliveLover {get;set;}
+
+ public PlayerStatistics(ShipStatus __instance) {
+ GetPlayerCounts();
+ }
+
+ private bool isLover(GameData.PlayerInfo p) {
+ return (Lovers.lover1 != null && Lovers.lover1.PlayerId == p.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == p.PlayerId);
+ }
+
+ private void GetPlayerCounts() {
+ int numJackalAlive = 0;
+ int numImpostorsAlive = 0;
+ int numLoversAlive = 0;
+ int numTotalAlive = 0;
+ bool impLover = false;
+ bool jackalLover = false;
+
+ for (int i = 0; i < GameData.Instance.PlayerCount; i++)
+ {
+ GameData.PlayerInfo playerInfo = GameData.Instance.AllPlayers[i];
+ if (!playerInfo.Disconnected)
+ {
+ if (!playerInfo.IsDead)
+ {
+ numTotalAlive++;
+
+ bool lover = isLover(playerInfo);
+ if (lover) numLoversAlive++;
+
+ if (playerInfo.IsImpostor) {
+ numImpostorsAlive++;
+ if (lover) impLover = true;
+ }
+ if (Jackal.jackal != null && Jackal.jackal.PlayerId == playerInfo.PlayerId) {
+ numJackalAlive++;
+ if (lover) jackalLover = true;
+ }
+ if (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == playerInfo.PlayerId) {
+ numJackalAlive++;
+ if (lover) jackalLover = true;
+ }
+ }
+ }
+ }
+
+ TeamJackalAlive = numJackalAlive;
+ TeamImpostorsAlive = numImpostorsAlive;
+ TeamLoversAlive = numLoversAlive;
+ TotalAlive = numTotalAlive;
+ TeamImpostorHasAliveLover = impLover;
+ TeamJackalHasAliveLover = jackalLover;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using HarmonyLib;
+using Hazel;
+using System.Collections.Generic;
+using System.Linq;
+using UnhollowerBaseLib;
+using static TheOtherRoles.TheOtherRoles;
+using TheOtherRoles.Objects;
+using static TheOtherRoles.MapOptions;
+using System.Collections;
+using System;
+using System.Text;
+using UnityEngine;
+using System.Reflection;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(ExileController), "Begin")]
+ class ExileControllerBeginPatch {
+ public static void Prefix(ExileController __instance, [HarmonyArgument(0)]ref GameData.PlayerInfo exiled, [HarmonyArgument(1)]bool tie) {
+ // Shifter shift
+ if (Shifter.shifter != null && AmongUsClient.Instance.AmHost && Shifter.futureShift != 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)
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShifterShift, Hazel.SendOption.Reliable, -1);
+ writer.Write(Shifter.futureShift.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.shifterShift(Shifter.futureShift.PlayerId);
+ }
+ Shifter.futureShift = null;
+
+ // 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 && 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);
+ RPCProcedure.erasePlayerRoles(target.PlayerId);
+ }
+ }
+ }
+ Eraser.futureErased = new List<PlayerControl>();
+
+ // Trickster boxes
+ if (Trickster.trickster != null && JackInTheBox.hasJackInTheBoxLimitReached()) {
+ JackInTheBox.convertToVents();
+ }
+
+ // SecurityGuard vents and cameras
+ var allCameras = ShipStatus.Instance.AllCameras.ToList();
+ MapOptions.camerasToAdd.ForEach(camera => {
+ camera.gameObject.SetActive(true);
+ camera.gameObject.GetComponent<SpriteRenderer>().color = Color.white;
+ allCameras.Add(camera);
+ });
+ ShipStatus.Instance.AllCameras = allCameras.ToArray();
+ MapOptions.camerasToAdd = new List<SurvCamera>();
+
+ foreach (Vent vent in MapOptions.ventsToSeal) {
+ PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
+ animator?.Stop();
+ vent.EnterVentAnim = vent.ExitVentAnim = null;
+ vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
+ vent.myRend.color = Color.white;
+ vent.name = "SealedVent_" + vent.name;
+ }
+ MapOptions.ventsToSeal = new List<Vent>();
+ }
+ }
+
+ [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;
+ }
+ // Jester win condition
+ else if (exiled != null && Jester.jester != null && Jester.jester.PlayerId == exiled.PlayerId) {
+ Jester.triggerJesterWin = true;
+ }
+
+ // Reset custom button timers where necessary
+ CustomButton.MeetingEndedUpdate();
+
+ // Mini set adapted cooldown
+ if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini && Mini.mini.Data.IsImpostor) {
+ var multiplier = Mini.isGrownUp() ? 0.66f : 2f;
+ Mini.mini.SetKillTimer(PlayerControl.GameOptions.KillCooldown * multiplier);
+ }
+
+ // Seer spawn souls
+ if (Seer.deadBodyPositions != null && Seer.seer != null && PlayerControl.LocalPlayer == Seer.seer && (Seer.mode == 0 || Seer.mode == 2)) {
+ foreach (Vector3 pos in Seer.deadBodyPositions) {
+ GameObject soul = new GameObject();
+ soul.transform.position = pos;
+ soul.layer = 5;
+ var rend = soul.AddComponent<SpriteRenderer>();
+ rend.sprite = Seer.getSoulSprite();
+
+ if(Seer.limitSoulDuration) {
+ HudManager.Instance.StartCoroutine(Effects.Lerp(Seer.soulDuration, new Action<float>((p) => {
+ if (rend != null) {
+ var tmp = rend.color;
+ tmp.a = Mathf.Clamp01(1 - p);
+ rend.color = tmp;
+ }
+ if (p == 1f && rend != null && rend.gameObject != null) UnityEngine.Object.Destroy(rend.gameObject);
+ })));
+ }
+ }
+ Seer.deadBodyPositions = new List<Vector3>();
+ }
+
+ // Arsonist deactivate dead poolable players
+ if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) {
+ int visibleCounter = 0;
+ Vector3 bottomLeft = new Vector3(-HudManager.Instance.UseButton.transform.localPosition.x, HudManager.Instance.UseButton.transform.localPosition.y, HudManager.Instance.UseButton.transform.localPosition.z);
+ bottomLeft += new Vector3(-0.25f, -0.25f, 0);
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ if (!MapOptions.playerIcons.ContainsKey(p.PlayerId)) continue;
+ if (p.Data.IsDead || p.Data.Disconnected) {
+ MapOptions.playerIcons[p.PlayerId].gameObject.SetActive(false);
+ } else {
+ 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;
+ }
+ }
+
+ [HarmonyPatch(typeof(TranslationController), nameof(TranslationController.GetString), new Type[] { typeof(StringNames), typeof(Il2CppReferenceArray<Il2CppSystem.Object>) })]
+ class ExileControllerMessagePatch {
+ static void Postfix(ref string __result, [HarmonyArgument(0)]StringNames id) {
+ try {
+ if (ExileController.Instance != null && ExileController.Instance.exiled != null) {
+ PlayerControl player = Helpers.playerById(ExileController.Instance.exiled.Object.PlayerId);
+ if (player == null) return;
+ // Exile role text
+ if (id == StringNames.ExileTextPN || id == StringNames.ExileTextSN || id == StringNames.ExileTextPP || id == StringNames.ExileTextSP) {
+ __result = player.Data.PlayerName + " was The " + String.Join(" ", RoleInfo.getRoleInfoForPlayer(player).Select(x => x.name).ToArray());
+ }
+ // Hide number of remaining impostors on Jester win
+ if (id == StringNames.ImpostorsRemainP || id == StringNames.ImpostorsRemainS) {
+ if (Jester.jester != null && player.PlayerId == Jester.jester.PlayerId) __result = "";
+ }
+ }
+ } catch {
+ // pass - Hopefully prevent leaving while exiling to softlock game
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+
+using HarmonyLib;
+using UnityEngine;
+using System.Reflection;
+using System.Collections.Generic;
+using Hazel;
+using System;
+using UnhollowerBaseLib;
+
+namespace TheOtherRoles.Patches {
+ public class GameStartManagerPatch {
+ public static Dictionary<int, PlayerVersion> playerVersions = new Dictionary<int, PlayerVersion>();
+ private static float timer = 600f;
+ private static bool versionSent = false;
+ private static string lobbyCodeText = "";
+
+ [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))]
+ public class GameStartManagerStartPatch {
+ public static void Postfix(GameStartManager __instance) {
+ // Trigger version refresh
+ versionSent = false;
+ // Reset lobby countdown timer
+ timer = 600f;
+ // Copy lobby code
+ string code = InnerNet.GameCode.IntToGameName(AmongUsClient.Instance.GameId);
+ GUIUtility.systemCopyBuffer = code;
+ lobbyCodeText = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.RoomCode, new Il2CppReferenceArray<Il2CppSystem.Object>(0)) + "\r\n" + code;
+ }
+ }
+
+ [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Update))]
+ public class GameStartManagerUpdatePatch {
+ private static bool update = false;
+ private static string currentText = "";
+ private static int kc = 0;
+ private static KeyCode[] ks = new [] { KeyCode.UpArrow, KeyCode.UpArrow, KeyCode.DownArrow, KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.LeftArrow, KeyCode.RightArrow, KeyCode.B, KeyCode.A, KeyCode.Return };
+
+ public static void Prefix(GameStartManager __instance) {
+ if (!AmongUsClient.Instance.AmHost || !GameData.Instance) return; // Not host or no instance
+ update = GameData.Instance.PlayerCount != __instance.LastPlayerCount;
+ }
+
+ public static void Postfix(GameStartManager __instance) {
+ // Send version as soon as PlayerControl.LocalPlayer exists
+ if (PlayerControl.LocalPlayer != null && !versionSent) {
+ versionSent = true;
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VersionHandshake, Hazel.SendOption.Reliable, -1);
+ writer.Write((byte)TheOtherRolesPlugin.Version.Major);
+ writer.Write((byte)TheOtherRolesPlugin.Version.Minor);
+ writer.Write((byte)TheOtherRolesPlugin.Version.Build);
+ writer.WritePacked(AmongUsClient.Instance.ClientId);
+ writer.Write((byte)(TheOtherRolesPlugin.Version.Revision < 0 ? 0xFF : TheOtherRolesPlugin.Version.Revision));
+ writer.Write(Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.ToByteArray());
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.versionHandshake(TheOtherRolesPlugin.Version.Major, TheOtherRolesPlugin.Version.Minor, TheOtherRolesPlugin.Version.Build, TheOtherRolesPlugin.Version.Revision, Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId, AmongUsClient.Instance.ClientId);
+ }
+
+ if(kc < ks.Length && Input.GetKeyDown(ks[kc])) {
+ kc++;
+ } else if(Input.anyKeyDown) {
+ kc = 0;
+ }
+
+ if(kc == ks.Length) {
+ kc = 0;
+
+ // Random Color
+ byte colorId = (byte)TheOtherRoles.rnd.Next(0, Palette.PlayerColors.Length);
+ SaveManager.BodyColor = (byte)colorId;
+ if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.CmdCheckColor(colorId);
+
+ // Random Hat
+ var hats = HatManager.Instance.GetUnlockedHats();
+ var unlockedHatIndex = TheOtherRoles.rnd.Next(0, hats.Length);
+ var hatId = (uint)HatManager.Instance.AllHats.IndexOf(hats[unlockedHatIndex]);
+ if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.RpcSetHat(hatId);
+
+ // Random Skin
+ var skins = HatManager.Instance.GetUnlockedSkins();
+ var unlockedSkinIndex = TheOtherRoles.rnd.Next(0, skins.Length);
+ var skinId = (uint)HatManager.Instance.AllSkins.IndexOf(skins[unlockedSkinIndex]);
+ if (PlayerControl.LocalPlayer) PlayerControl.LocalPlayer.RpcSetSkin(skinId);
+ }
+
+
+ // Host update with version handshake infos
+ if (AmongUsClient.Instance.AmHost) {
+ bool blockStart = false;
+ string message = "";
+ foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients.ToArray()) {
+ if (client.Character == null) continue;
+ var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
+ if (dummyComponent != null && dummyComponent.enabled)
+ continue;
+ else if (!playerVersions.ContainsKey(client.Id)) {
+ blockStart = true;
+ message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a different or no version of The Other Roles\n</color>";
+ } else {
+ PlayerVersion PV = playerVersions[client.Id];
+ int diff = TheOtherRolesPlugin.Version.CompareTo(PV.version);
+ if (diff > 0) {
+ message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has an older version of The Other Roles (v{playerVersions[client.Id].version.ToString()})\n</color>";
+ blockStart = true;
+ } else if (diff < 0) {
+ message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a newer version of The Other Roles (v{playerVersions[client.Id].version.ToString()})\n</color>";
+ blockStart = true;
+ } else if (!PV.GuidMatches()) { // version presumably matches, check if Guid matches
+ message += $"<color=#FF0000FF>{client.Character.Data.PlayerName} has a modified version of TOR v{playerVersions[client.Id].version.ToString()} <size=30%>({PV.guid.ToString()})</size>\n</color>";
+ blockStart = true;
+ }
+ }
+ }
+ if (blockStart) {
+ // __instance.StartButton.color = Palette.DisabledClear; // Allow the start for this version to test the feature, blocking it with the next version
+ __instance.GameStartText.text = message;
+ __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition + Vector3.up * 2;
+ } else {
+ // __instance.StartButton.color = ((__instance.LastPlayerCount >= __instance.MinPlayers) ? Palette.EnabledColor : Palette.DisabledClear); // Allow the start for this version to test the feature, blocking it with the next version
+ __instance.GameStartText.transform.localPosition = __instance.StartButton.transform.localPosition;
+ }
+ }
+
+ // Lobby code replacement
+ __instance.GameRoomName.text = TheOtherRolesPlugin.StreamerMode.Value ? $"<color={TheOtherRolesPlugin.StreamerModeReplacementColor.Value}>{TheOtherRolesPlugin.StreamerModeReplacementText.Value}</color>" : lobbyCodeText;
+
+ // Lobby timer
+ if (!AmongUsClient.Instance.AmHost || !GameData.Instance) return; // Not host or no instance
+
+ if (update) currentText = __instance.PlayerCounter.text;
+
+ timer = Mathf.Max(0f, timer -= Time.deltaTime);
+ int minutes = (int)timer / 60;
+ int seconds = (int)timer % 60;
+ string suffix = $" ({minutes:00}:{seconds:00})";
+
+ __instance.PlayerCounter.text = currentText + suffix;
+ __instance.PlayerCounter.autoSizeTextContainer = true;
+
+ }
+ }
+
+ [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.BeginGame))]
+ public class GameStartManagerBeginGame {
+ public static bool Prefix(GameStartManager __instance) {
+ // Block game start if not everyone has the same mod version
+ bool continueStart = true;
+
+ // Allow the start for this version to test the feature, blocking it with the next version
+ // if (AmongUsClient.Instance.AmHost) {
+ // foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) {
+ // if (client.Character == null) continue;
+ // var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
+ // if (dummyComponent != null && dummyComponent.enabled) continue;
+ // if (!playerVersions.ContainsKey(client.Id) || (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch))
+ // continueStart = false;
+ // }
+ // }
+ return continueStart;
+ }
+ }
+
+ public class PlayerVersion {
+ public readonly Version version;
+ public readonly Guid guid;
+
+ public PlayerVersion(Version version, Guid guid) {
+ this.version = version;
+ this.guid = guid;
+ }
+
+ public bool GuidMatches() {
+ return Assembly.GetExecutingAssembly().ManifestModule.ModuleVersionId.Equals(this.guid);
+ }
+ }
+ }
+}
--- /dev/null
+using HarmonyLib;
+using System;
+using static TheOtherRoles.TheOtherRoles;
+using UnityEngine;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.OnDestroy))]
+ class IntroCutsceneOnDestroyPatch
+ {
+ public static void Prefix(IntroCutscene __instance) {
+ // 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);
+ 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.Skin.layer, 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);
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch]
+ class IntroPatch {
+ public static void setupIntroTeam(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
+ // Intro solo teams
+ if (PlayerControl.LocalPlayer == Jester.jester || PlayerControl.LocalPlayer == Jackal.jackal || PlayerControl.LocalPlayer == Arsonist.arsonist) {
+ var soloTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
+ soloTeam.Add(PlayerControl.LocalPlayer);
+ yourTeam = soloTeam;
+ }
+
+ // Add the Spy to the Impostor team (for the Impostors)
+ if (Spy.spy != null && PlayerControl.LocalPlayer.Data.IsImpostor) {
+ List<PlayerControl> players = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
+ var fakeImpostorTeam = new Il2CppSystem.Collections.Generic.List<PlayerControl>();
+ foreach (PlayerControl p in players) {
+ if (p == Spy.spy || p.Data.IsImpostor)
+ fakeImpostorTeam.Add(p);
+ }
+ yourTeam = fakeImpostorTeam;
+ }
+ }
+
+ public static void setupIntroRole(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
+ List<RoleInfo> infos = RoleInfo.getRoleInfoForPlayer(PlayerControl.LocalPlayer);
+ RoleInfo roleInfo = infos.Where(info => info.roleId != RoleId.Lover).FirstOrDefault();
+
+ if (roleInfo != null) {
+ __instance.Title.text = roleInfo.name;
+ __instance.ImpostorText.gameObject.SetActive(true);
+ __instance.ImpostorText.text = roleInfo.introDescription;
+ if (roleInfo.roleId != RoleId.Crewmate && roleInfo.roleId != RoleId.Impostor) {
+ // For native Crewmate or Impostor do not modify the colors
+ __instance.Title.color = roleInfo.color;
+ __instance.BackgroundBar.material.color = roleInfo.color;
+ }
+ }
+
+ if (infos.Any(info => info.roleId == RoleId.Lover)) {
+ var loversText = UnityEngine.Object.Instantiate<TMPro.TextMeshPro>(__instance.ImpostorText, __instance.ImpostorText.transform.parent);
+ loversText.transform.localPosition += Vector3.down * 3f;
+ PlayerControl otherLover = PlayerControl.LocalPlayer == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
+ loversText.text = Helpers.cs(Lovers.color, $"♥ You are in love with {otherLover?.Data?.PlayerName ?? ""} ♥");
+ }
+ }
+
+ [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginCrewmate))]
+ class BeginCrewmatePatch {
+ public static void Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
+ setupIntroTeam(__instance, ref yourTeam);
+ }
+
+ public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
+ setupIntroRole(__instance, ref yourTeam);
+ }
+ }
+
+ [HarmonyPatch(typeof(IntroCutscene), nameof(IntroCutscene.BeginImpostor))]
+ class BeginImpostorPatch {
+ public static void Prefix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
+ setupIntroTeam(__instance, ref yourTeam);
+ }
+
+ public static void Postfix(IntroCutscene __instance, ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam) {
+ setupIntroRole(__instance, ref yourTeam);
+ }
+ }
+ }
+}
+
--- /dev/null
+using HarmonyLib;
+using Hazel;
+using System.Collections.Generic;
+using System.Linq;
+using UnhollowerBaseLib;
+using static TheOtherRoles.TheOtherRoles;
+using static TheOtherRoles.MapOptions;
+using System.Collections;
+using System;
+using System.Text;
+using UnityEngine;
+using System.Reflection;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch]
+ class MeetingHudPatch {
+ static bool[] selections;
+ static SpriteRenderer[] renderers;
+ private static GameData.PlayerInfo target = null;
+ private const float scale = 0.65f;
+
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.CheckForEndVoting))]
+ class MeetingCalculateVotesPatch {
+ 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.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) {
+ if (!dictionary.ContainsKey(swapped1.TargetPlayerId)) dictionary[swapped1.TargetPlayerId] = 0;
+ if (!dictionary.ContainsKey(swapped2.TargetPlayerId)) dictionary[swapped2.TargetPlayerId] = 0;
+ int tmp = dictionary[swapped1.TargetPlayerId];
+ dictionary[swapped1.TargetPlayerId] = dictionary[swapped2.TargetPlayerId];
+ dictionary[swapped2.TargetPlayerId] = tmp;
+ }
+
+ return dictionary;
+ }
+
+
+ static bool Prefix(MeetingHud __instance) {
+ 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
+ }
+ }
+
+ Dictionary<byte, int> self = CalculateVotes(__instance);
+ bool tie;
+ 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[i] = new MeetingHud.VoterState {
+ VoterId = playerVoteArea.TargetPlayerId,
+ VotedForId = playerVoteArea.VotedFor
+ };
+ }
+
+ // RPCVotingComplete
+ __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 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;
+ if (doSwap) {
+ __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));
+ }
+
+
+ __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++) {
+ 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++;
+ }
+
+ // 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;
+ }
+ }
+
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.VotingComplete))]
+ class MeetingHudVotingCompletedPatch {
+ static void Postfix(MeetingHud __instance, [HarmonyArgument(0)]byte[] states, [HarmonyArgument(1)]GameData.PlayerInfo exiled, [HarmonyArgument(2)]bool tie)
+ {
+ // Reset swapper values
+ Swapper.playerId1 = Byte.MaxValue;
+ Swapper.playerId2 = Byte.MaxValue;
+
+ // Lovers save next to be exiled, because RPC of ending game comes before RPC of exiled
+ Lovers.notAckedExiledIsLover = false;
+ if (exiled != null)
+ Lovers.notAckedExiledIsLover = ((Lovers.lover1 != null && Lovers.lover1.PlayerId == exiled.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == exiled.PlayerId));
+ }
+ }
+
+
+ static void swapperOnClick(int i, MeetingHud __instance) {
+ if (__instance.state == MeetingHud.VoteStates.Results) return;
+ if (__instance.playerStates[i].AmDead) return;
+
+ int selectedCount = selections.Where(b => b).Count();
+ SpriteRenderer renderer = renderers[i];
+
+ if (selectedCount == 0) {
+ renderer.color = Color.green;
+ selections[i] = true;
+ } else if (selectedCount == 1) {
+ if (selections[i]) {
+ renderer.color = Color.red;
+ selections[i] = false;
+ } else {
+ selections[i] = true;
+ renderer.color = Color.green;
+
+ PlayerVoteArea firstPlayer = null;
+ PlayerVoteArea secondPlayer = null;
+ for (int A = 0; A < selections.Length; A++) {
+ if (selections[A]) {
+ if (firstPlayer != null) {
+ secondPlayer = __instance.playerStates[A];
+ break;
+ } else {
+ firstPlayer = __instance.playerStates[A];
+ }
+ }
+ }
+
+ if (firstPlayer != null && secondPlayer != null) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SwapperSwap, Hazel.SendOption.Reliable, -1);
+ writer.Write((byte)firstPlayer.TargetPlayerId);
+ writer.Write((byte)secondPlayer.TargetPlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+
+ RPCProcedure.swapperSwap((byte)firstPlayer.TargetPlayerId, (byte)secondPlayer.TargetPlayerId);
+ }
+ }
+ }
+ }
+
+ private static GameObject guesserUI;
+ static void guesserOnClick(int buttonTarget, MeetingHud __instance) {
+ if (guesserUI != null || !(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted)) return;
+ __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(false));
+
+ Transform container = UnityEngine.Object.Instantiate(__instance.transform.FindChild("Background"), __instance.transform);
+ container.FindChild("BlackBG").gameObject.SetActive(false);
+ container.transform.localPosition = new Vector3(0, 0, -5f);
+ guesserUI = container.gameObject;
+
+ int i = 0;
+ var buttonTemplate = __instance.playerStates[0].transform.FindChild("votePlayerBase");
+ var maskTemplate = __instance.playerStates[0].transform.FindChild("MaskArea");
+ var smallButtonTemplate = __instance.playerStates[0].Buttons.transform.Find("CancelButton");
+ var textTemplate = __instance.playerStates[0].NameText;
+
+ Transform exitButtonParent = (new GameObject()).transform;
+ exitButtonParent.SetParent(container);
+ Transform exitButton = UnityEngine.Object.Instantiate(buttonTemplate.transform, exitButtonParent);
+ Transform exitButtonMask = UnityEngine.Object.Instantiate(maskTemplate, exitButtonParent);
+ exitButton.gameObject.GetComponent<SpriteRenderer>().sprite = smallButtonTemplate.GetComponent<SpriteRenderer>().sprite;
+ exitButtonParent.transform.localPosition = new Vector3(2.725f, 2.1f, -5);
+ exitButtonParent.transform.localScale = new Vector3(0.25f, 0.9f, 1);
+ exitButton.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
+ exitButton.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
+ __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
+ UnityEngine.Object.Destroy(container.gameObject);
+ }));
+
+ 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 buttonParent = (new GameObject()).transform;
+ buttonParent.SetParent(container);
+ Transform button = UnityEngine.Object.Instantiate(buttonTemplate, buttonParent);
+ Transform buttonMask = UnityEngine.Object.Instantiate(maskTemplate, buttonParent);
+ TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button);
+ buttons.Add(button);
+ int row = i/4, col = i%4;
+ buttonParent.localPosition = new Vector3(-2.725f + 1.83f * col, 1.5f - 0.45f * row, -5);
+ buttonParent.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 *= 1.7f;
+ int copiedIndex = i;
+
+ button.GetComponent<PassiveButton>().OnClick.RemoveAllListeners();
+ button.GetComponent<PassiveButton>().OnClick.AddListener((UnityEngine.Events.UnityAction)(() => {
+ 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;
+
+ 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);
+
+ __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
+ 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++;
+ }
+ container.transform.localScale *= 0.75f;
+ }
+
+ [HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Select))]
+ class PlayerVoteAreaSelectPatch {
+ static bool Prefix(MeetingHud __instance) {
+ return !(PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer == Guesser.guesser && guesserUI != null);
+ }
+ }
+
+
+ static void populateButtonsPostfix(MeetingHud __instance) {
+ // Add Swapper Buttons
+ if (Swapper.swapper != null && PlayerControl.LocalPlayer == Swapper.swapper && !Swapper.swapper.Data.IsDead) {
+ selections = new bool[__instance.playerStates.Length];
+ renderers = new SpriteRenderer[__instance.playerStates.Length];
+
+ for (int i = 0; i < __instance.playerStates.Length; i++) {
+ PlayerVoteArea playerVoteArea = __instance.playerStates[i];
+ 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(-0.95f, 0.03f, -1f);
+ SpriteRenderer renderer = checkbox.GetComponent<SpriteRenderer>();
+ renderer.sprite = Swapper.getCheckSprite();
+ renderer.color = Color.red;
+
+ PassiveButton button = checkbox.GetComponent<PassiveButton>();
+ button.OnClick.RemoveAllListeners();
+ int copiedIndex = i;
+ button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => swapperOnClick(copiedIndex, __instance)));
+
+ selections[i] = false;
+ renderers[i] = renderer;
+ }
+ }
+
+ // Add Guesser Buttons
+ 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.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(-0.95f, 0.03f, -1f);
+ SpriteRenderer renderer = targetBox.GetComponent<SpriteRenderer>();
+ renderer.sprite = Guesser.getTargetSprite();
+ PassiveButton button = targetBox.GetComponent<PassiveButton>();
+ button.OnClick.RemoveAllListeners();
+ int copiedIndex = i;
+ button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() => guesserOnClick(copiedIndex, __instance)));
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.ServerStart))]
+ class MeetingServerStartPatch {
+ static void Postfix(MeetingHud __instance)
+ {
+ populateButtonsPostfix(__instance);
+ }
+ }
+
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Deserialize))]
+ class MeetingDeserializePatch {
+ static void Postfix(MeetingHud __instance, [HarmonyArgument(0)]MessageReader reader, [HarmonyArgument(1)]bool initialState)
+ {
+ // Add swapper buttons
+ if (initialState) {
+ populateButtonsPostfix(__instance);
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoStartMeeting))]
+ class StartMeetingPatch {
+ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]GameData.PlayerInfo meetingTarget) {
+ // Reset vampire bitten
+ Vampire.bitten = null;
+ // Count meetings
+ if (meetingTarget == null) meetingsCount++;
+ // Save the meeting target
+ target = meetingTarget;
+ }
+ }
+
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))]
+ class MeetingHudUpdatePatch {
+ static void Postfix(MeetingHud __instance) {
+ // Deactivate skip Button if skipping on emergency meetings is disabled
+ if (target == null && blockSkippingInEmergencyMeetings)
+ __instance.SkipVoteButton.gameObject.SetActive(false);
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using HarmonyLib;
+
+namespace TheOtherRoles.Patches {
+ [Harmony]
+ public class AccountManagerPatch {
+ [HarmonyPatch(typeof(AccountManager), nameof(AccountManager.RandomizeName))]
+ public static class RandomizeNamePatch {
+ static bool Prefix(AccountManager __instance) {
+ if (SaveManager.lastPlayerName == null)
+ return true;
+ SaveManager.PlayerName = SaveManager.lastPlayerName;
+ __instance.accountTab.UpdateNameDisplay();
+ return false; // Don't execute original
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using HarmonyLib;
+using Hazel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static TheOtherRoles.TheOtherRoles;
+using static TheOtherRoles.GameHistory;
+using TheOtherRoles.Objects;
+using UnityEngine;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.FixedUpdate))]
+ public static class PlayerControlFixedUpdatePatch
+ {
+ // Helpers
+
+ static PlayerControl setTarget(bool onlyCrewmates = false, bool targetPlayersInVents = false, List<PlayerControl> untargetablePlayers = null, PlayerControl targetingPlayer = null) {
+ PlayerControl result = null;
+ float num = GameOptionsData.KillDistances[Mathf.Clamp(PlayerControl.GameOptions.KillDistance, 0, 2)];
+ if (!ShipStatus.Instance) return result;
+ if (targetingPlayer == null) targetingPlayer = PlayerControl.LocalPlayer;
+ if (targetingPlayer.Data.IsDead) return result;
+
+ Vector2 truePosition = targetingPlayer.GetTruePosition();
+ Il2CppSystem.Collections.Generic.List<GameData.PlayerInfo> allPlayers = GameData.Instance.AllPlayers;
+ for (int i = 0; i < allPlayers.Count; i++)
+ {
+ GameData.PlayerInfo playerInfo = allPlayers[i];
+ if (!playerInfo.Disconnected && playerInfo.PlayerId != targetingPlayer.PlayerId && !playerInfo.IsDead && (!onlyCrewmates || !playerInfo.IsImpostor))
+ {
+ PlayerControl @object = playerInfo.Object;
+ if(untargetablePlayers != null && untargetablePlayers.Any(x => x == @object)) {
+ // if that player is not targetable: skip check
+ continue;
+ }
+
+ if (@object && (!@object.inVent || targetPlayersInVents))
+ {
+ Vector2 vector = @object.GetTruePosition() - truePosition;
+ float magnitude = vector.magnitude;
+ if (magnitude <= num && !PhysicsHelpers.AnyNonTriggersBetween(truePosition, vector.normalized, magnitude, Constants.ShipAndObjectsMask))
+ {
+ result = @object;
+ num = magnitude;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ static void setPlayerOutline(PlayerControl target, Color color) {
+ if (target == null || target.myRend == null) return;
+
+ target.myRend.material.SetFloat("_Outline", 1f);
+ target.myRend.material.SetColor("_OutlineColor", color);
+ }
+
+ // Update functions
+
+ static void setBasePlayerOutlines() {
+ foreach (PlayerControl target in PlayerControl.AllPlayerControls) {
+ if (target == null || target.myRend == null) continue;
+
+ bool isMorphedMorphling = target == Morphling.morphling && Morphling.morphTarget != null && Morphling.morphTimer > 0f;
+ bool hasVisibleShield = false;
+ if (Camouflager.camouflageTimer <= 0f && Medic.shielded != null && ((target == Medic.shielded && !isMorphedMorphling) || (isMorphedMorphling && Morphling.morphTarget == Medic.shielded))) {
+ hasVisibleShield = Medic.showShielded == 0 // Everyone
+ || (Medic.showShielded == 1 && (PlayerControl.LocalPlayer == Medic.shielded || PlayerControl.LocalPlayer == Medic.medic)) // Shielded + Medic
+ || (Medic.showShielded == 2 && PlayerControl.LocalPlayer == Medic.medic); // Medic only
+ }
+
+ if (hasVisibleShield) {
+ target.myRend.material.SetFloat("_Outline", 1f);
+ target.myRend.material.SetColor("_OutlineColor", Medic.shieldedColor);
+ } else {
+ target.myRend.material.SetFloat("_Outline", 0f);
+ }
+ }
+ }
+
+ public static void bendTimeUpdate() {
+ if (TimeMaster.isRewinding) {
+ if (localPlayerPositions.Count > 0) {
+ // Set position
+ var next = localPlayerPositions[0];
+ if (next.Item2 == true) {
+ // Exit current vent if necessary
+ if (PlayerControl.LocalPlayer.inVent) {
+ foreach (Vent vent in ShipStatus.Instance.AllVents) {
+ bool canUse;
+ bool couldUse;
+ vent.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
+ if (canUse) {
+ PlayerControl.LocalPlayer.MyPhysics.RpcExitVent(vent.Id);
+ vent.SetButtons(false);
+ }
+ }
+ }
+ // Set position
+ PlayerControl.LocalPlayer.transform.position = next.Item1;
+ } else if (localPlayerPositions.Any(x => x.Item2 == true)) {
+ PlayerControl.LocalPlayer.transform.position = next.Item1;
+ }
+
+ localPlayerPositions.RemoveAt(0);
+
+ if (localPlayerPositions.Count > 1) localPlayerPositions.RemoveAt(0); // Skip every second position to rewinde twice as fast, but never skip the last position
+ } else {
+ TimeMaster.isRewinding = false;
+ PlayerControl.LocalPlayer.moveable = true;
+ }
+ } else {
+ while (localPlayerPositions.Count >= Mathf.Round(TimeMaster.rewindTime / Time.fixedDeltaTime)) localPlayerPositions.RemoveAt(localPlayerPositions.Count - 1);
+ localPlayerPositions.Insert(0, new Tuple<Vector3, bool>(PlayerControl.LocalPlayer.transform.position, PlayerControl.LocalPlayer.CanMove)); // CanMove = CanMove
+ }
+ }
+
+ static void medicSetTarget() {
+ if (Medic.medic == null || Medic.medic != PlayerControl.LocalPlayer) return;
+ Medic.currentTarget = setTarget();
+ if (!Medic.usedShield) setPlayerOutline(Medic.currentTarget, Medic.shieldedColor);
+ }
+
+ static void shifterSetTarget() {
+ if (Shifter.shifter == null || Shifter.shifter != PlayerControl.LocalPlayer) return;
+ Shifter.currentTarget = setTarget();
+ if (Shifter.futureShift == null) setPlayerOutline(Shifter.currentTarget, Shifter.color);
+ }
+
+
+ static void morphlingSetTarget() {
+ if (Morphling.morphling == null || Morphling.morphling != PlayerControl.LocalPlayer) return;
+ Morphling.currentTarget = setTarget();
+ setPlayerOutline(Morphling.currentTarget, Morphling.color);
+ }
+
+ static void sheriffSetTarget() {
+ if (Sheriff.sheriff == null || Sheriff.sheriff != PlayerControl.LocalPlayer) return;
+ Sheriff.currentTarget = setTarget();
+ setPlayerOutline(Sheriff.currentTarget, Sheriff.color);
+ }
+
+ static void trackerSetTarget() {
+ if (Tracker.tracker == null || Tracker.tracker != PlayerControl.LocalPlayer) return;
+ Tracker.currentTarget = setTarget();
+ if (!Tracker.usedTracker) setPlayerOutline(Tracker.currentTarget, Tracker.color);
+ }
+
+ static void detectiveUpdateFootPrints() {
+ if (Detective.detective == null || Detective.detective != PlayerControl.LocalPlayer) return;
+
+ Detective.timer -= Time.fixedDeltaTime;
+ if (Detective.timer <= 0f) {
+ Detective.timer = Detective.footprintIntervall;
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
+ if (player != null && player != PlayerControl.LocalPlayer && !player.Data.IsDead && !player.inVent) {
+ new Footprint(Detective.footprintDuration, Detective.anonymousFootprints, player);
+ }
+ }
+ }
+ }
+
+ static void vampireSetTarget() {
+ if (Vampire.vampire == null || Vampire.vampire != PlayerControl.LocalPlayer) return;
+
+ PlayerControl target = null;
+ if (Spy.spy != null) {
+ if (Spy.impostorsCanKillAnyone) {
+ target = setTarget(false, true);
+ } else {
+ target = setTarget(true, true, new List<PlayerControl>() { Spy.spy });
+ }
+ } else {
+ target = setTarget(true, true);
+ }
+
+ bool targetNearGarlic = false;
+ if (target != null) {
+ foreach (Garlic garlic in Garlic.garlics) {
+ if (Vector2.Distance(garlic.garlic.transform.position, target.transform.position) <= 1.91f) {
+ targetNearGarlic = true;
+ }
+ }
+ }
+ Vampire.targetNearGarlic = targetNearGarlic;
+ Vampire.currentTarget = target;
+ setPlayerOutline(Vampire.currentTarget, Vampire.color);
+ }
+
+ static void jackalSetTarget() {
+ if (Jackal.jackal == null || Jackal.jackal != PlayerControl.LocalPlayer) return;
+ var untargetablePlayers = new List<PlayerControl>();
+ if(Jackal.canCreateSidekickFromImpostor) {
+ // Only exclude sidekick from beeing targeted if the jackal can create sidekicks from impostors
+ if(Sidekick.sidekick != null) untargetablePlayers.Add(Sidekick.sidekick);
+ }
+ if(Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); // Exclude Jackal from targeting the Mini unless it has grown up
+ Jackal.currentTarget = setTarget(untargetablePlayers : untargetablePlayers);
+ setPlayerOutline(Jackal.currentTarget, Palette.ImpostorRed);
+ }
+
+ static void sidekickSetTarget() {
+ if (Sidekick.sidekick == null || Sidekick.sidekick != PlayerControl.LocalPlayer) return;
+ var untargetablePlayers = new List<PlayerControl>();
+ if(Jackal.jackal != null) untargetablePlayers.Add(Jackal.jackal);
+ if(Mini.mini != null && !Mini.isGrownUp()) untargetablePlayers.Add(Mini.mini); // Exclude Sidekick from targeting the Mini unless it has grown up
+ Sidekick.currentTarget = setTarget(untargetablePlayers : untargetablePlayers);
+ if (Sidekick.canKill) setPlayerOutline(Sidekick.currentTarget, Palette.ImpostorRed);
+ }
+
+ static void sidekickCheckPromotion() {
+ // If LocalPlayer is Sidekick, the Jackal is disconnected and Sidekick promotion is enabled, then trigger promotion
+ if (Sidekick.sidekick == null || Sidekick.sidekick != PlayerControl.LocalPlayer) return;
+ if (Sidekick.sidekick.Data.IsDead == true || !Sidekick.promotesToJackal) return;
+ if (Jackal.jackal == null || Jackal.jackal?.Data?.Disconnected == true) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.sidekickPromotes();
+ }
+ }
+
+ static void eraserSetTarget() {
+ if (Eraser.eraser == null || Eraser.eraser != PlayerControl.LocalPlayer) return;
+
+ List<PlayerControl> untargetables = new List<PlayerControl>();
+ if (Spy.spy != null) untargetables.Add(Spy.spy);
+ Eraser.currentTarget = setTarget(onlyCrewmates: !Eraser.canEraseAnyone, untargetablePlayers: Eraser.canEraseAnyone ? new List<PlayerControl>() : untargetables);
+ setPlayerOutline(Eraser.currentTarget, Eraser.color);
+ }
+
+ static void engineerUpdate() {
+ if (PlayerControl.LocalPlayer.Data.IsImpostor && ShipStatus.Instance?.AllVents != null) {
+ foreach (Vent vent in ShipStatus.Instance.AllVents) {
+ try {
+ if (vent?.myRend?.material != null) {
+ if (Engineer.engineer != null && Engineer.engineer.inVent) {
+ vent.myRend.material.SetFloat("_Outline", 1f);
+ vent.myRend.material.SetColor("_OutlineColor", Engineer.color);
+ } else if (vent.myRend.material.GetColor("_AddColor") != Color.red) {
+ vent.myRend.material.SetFloat("_Outline", 0);
+ }
+ }
+ } catch {}
+ }
+ }
+ }
+
+ static void impostorSetTarget() {
+ if (!PlayerControl.LocalPlayer.Data.IsImpostor ||!PlayerControl.LocalPlayer.CanMove || PlayerControl.LocalPlayer.Data.IsDead) { // !isImpostor || !canMove || isDead
+ HudManager.Instance.KillButton.SetTarget(null);
+ return;
+ }
+
+ PlayerControl target = null;
+ if (Spy.spy != null) {
+ if (Spy.impostorsCanKillAnyone) {
+ target = setTarget(false, true);
+ } else {
+ target = setTarget(true, true, new List<PlayerControl>() { Spy.spy });
+ }
+ } else {
+ target = setTarget(true, true);
+ }
+
+ HudManager.Instance.KillButton.SetTarget(target); // Includes setPlayerOutline(target, Palette.ImpstorRed);
+ }
+
+ static void warlockSetTarget() {
+ if (Warlock.warlock == null || Warlock.warlock != PlayerControl.LocalPlayer) return;
+ if (Warlock.curseVictim != null && (Warlock.curseVictim.Data.Disconnected || Warlock.curseVictim.Data.IsDead)) {
+ // If the cursed victim is disconnected or dead reset the curse so a new curse can be applied
+ Warlock.resetCurse();
+ }
+ if (Warlock.curseVictim == null) {
+ Warlock.currentTarget = setTarget();
+ setPlayerOutline(Warlock.currentTarget, Warlock.color);
+ } else {
+ Warlock.curseVictimTarget = setTarget(targetingPlayer: Warlock.curseVictim);
+ setPlayerOutline(Warlock.curseVictimTarget, Warlock.color);
+ }
+ }
+
+ static void trackerUpdate() {
+ if (Tracker.arrow?.arrow == null) return;
+
+ if (Tracker.tracker == null || PlayerControl.LocalPlayer != Tracker.tracker) {
+ Tracker.arrow.arrow.SetActive(false);
+ return;
+ }
+
+ if (Tracker.tracker != null && Tracker.tracked != null && PlayerControl.LocalPlayer == Tracker.tracker && !Tracker.tracker.Data.IsDead) {
+ Tracker.timeUntilUpdate -= Time.fixedDeltaTime;
+
+ if (Tracker.timeUntilUpdate <= 0f) {
+ bool trackedOnMap = !Tracker.tracked.Data.IsDead;
+ Vector3 position = Tracker.tracked.transform.position;
+ if (!trackedOnMap) { // Check for dead body
+ DeadBody body = UnityEngine.Object.FindObjectsOfType<DeadBody>().FirstOrDefault(b => b.ParentId == Tracker.tracked.PlayerId);
+ if (body != null) {
+ trackedOnMap = true;
+ position = body.transform.position;
+ }
+ }
+
+ Tracker.arrow.Update(position);
+ Tracker.arrow.arrow.SetActive(trackedOnMap);
+ Tracker.timeUntilUpdate = Tracker.updateIntervall;
+ } else {
+ Tracker.arrow.Update();
+ }
+ }
+ }
+
+ public static void playerSizeUpdate(PlayerControl p) {
+ // Set default player size
+ CircleCollider2D collider = p.GetComponent<CircleCollider2D>();
+
+ p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
+ collider.radius = Mini.defaultColliderRadius;
+ collider.offset = Mini.defaultColliderOffset * Vector2.down;
+
+ // Set adapted player size to Mini and Morphling
+ if (Mini.mini == null || Camouflager.camouflageTimer > 0f) return;
+
+ float growingProgress = Mini.growingProgress();
+ float scale = growingProgress * 0.35f + 0.35f;
+ float correctedColliderRadius = Mini.defaultColliderRadius * 0.7f / scale; // scale / 0.7f is the factor by which we decrease the player size, hence we need to increase the collider size by 0.7f / scale
+
+ if (p == Mini.mini) {
+ p.transform.localScale = new Vector3(scale, scale, 1f);
+ collider.radius = correctedColliderRadius;
+ }
+ if (Morphling.morphling != null && p == Morphling.morphling && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f) {
+ p.transform.localScale = new Vector3(scale, scale, 1f);
+ collider.radius = correctedColliderRadius;
+ }
+ }
+
+ public static void updatePlayerInfo() {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ if (p != PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) continue;
+
+ 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.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.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 * 0.20f;
+ meetingInfo.fontSize *= 0.75f;
+ meetingInfo.gameObject.name = "Info";
+ }
+
+ var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data);
+ string roleNames = String.Join(" ", RoleInfo.getRoleInfoForPlayer(p).Select(x => Helpers.cs(x.color, x.name)).ToArray());
+ string taskInfo = tasksTotal > 0 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
+
+ string playerInfoText = "";
+ string meetingInfoText ="";
+ if (p == PlayerControl.LocalPlayer) {
+ playerInfoText = $"{roleNames}";
+ if (DestroyableSingleton<TaskPanelBehaviour>.InstanceExists) {
+ TMPro.TextMeshPro tabText = DestroyableSingleton<TaskPanelBehaviour>.Instance.tab.transform.FindChild("TabText_TMP").GetComponent<TMPro.TextMeshPro>();
+ tabText.SetText($"Tasks {taskInfo}");
+ }
+ meetingInfoText = $"{roleNames} {taskInfo}".Trim();
+ }
+ else if (MapOptions.ghostsSeeRoles && MapOptions.ghostsSeeTasks) {
+ playerInfoText = $"{roleNames} {taskInfo}".Trim();
+ meetingInfoText = playerInfoText;
+ }
+ else if (MapOptions.ghostsSeeTasks) {
+ playerInfoText = $"{taskInfo}".Trim();
+ meetingInfoText = playerInfoText;
+ }
+ else if (MapOptions.ghostsSeeRoles) {
+ playerInfoText = $"{roleNames}";
+ meetingInfoText = playerInfoText;
+ }
+
+ playerInfo.text = playerInfoText;
+ playerInfo.gameObject.SetActive(p.Visible);
+ if (meetingInfo != null) meetingInfo.text = MeetingHud.Instance.state == MeetingHud.VoteStates.Results ? "" : meetingInfoText;
+ }
+ }
+
+ public static void securityGuardSetTarget() {
+ if (SecurityGuard.securityGuard == null || SecurityGuard.securityGuard != PlayerControl.LocalPlayer || ShipStatus.Instance == null || ShipStatus.Instance.AllVents == null) return;
+
+ Vent target = null;
+ Vector2 truePosition = PlayerControl.LocalPlayer.GetTruePosition();
+ float closestDistance = float.MaxValue;
+ for (int i = 0; i < ShipStatus.Instance.AllVents.Length; i++) {
+ Vent vent = ShipStatus.Instance.AllVents[i];
+ if (vent.gameObject.name.StartsWith("JackInTheBoxVent_") || vent.gameObject.name.StartsWith("SealedVent_") || vent.gameObject.name.StartsWith("FutureSealedVent_")) continue;
+ float distance = Vector2.Distance(vent.transform.position, truePosition);
+ if (distance <= vent.UsableDistance && distance < closestDistance) {
+ closestDistance = distance;
+ target = vent;
+ }
+ }
+ SecurityGuard.ventTarget = target;
+ }
+
+ public static void arsonistSetTarget() {
+ if (Arsonist.arsonist == null || Arsonist.arsonist != PlayerControl.LocalPlayer) return;
+ List<PlayerControl> untargetables;
+ if (Arsonist.douseTarget != null)
+ untargetables = PlayerControl.AllPlayerControls.ToArray().Where(x => x.PlayerId != Arsonist.douseTarget.PlayerId).ToList();
+ else
+ untargetables = Arsonist.dousedPlayers;
+ Arsonist.currentTarget = setTarget(untargetablePlayers: untargetables);
+ if (Arsonist.currentTarget != null) setPlayerOutline(Arsonist.currentTarget, Arsonist.color);
+ }
+
+ 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;
+
+ // Mini and Morphling shrink
+ playerSizeUpdate(__instance);
+
+ if (PlayerControl.LocalPlayer == __instance) {
+ // Update player outlines
+ setBasePlayerOutlines();
+
+ // Update Role Description
+ Helpers.refreshRoleDescription(__instance);
+
+ // Update Player Info
+ updatePlayerInfo();
+
+ // Time Master
+ bendTimeUpdate();
+ // Morphling
+ morphlingSetTarget();
+ // Medic
+ medicSetTarget();
+ // Shifter
+ shifterSetTarget();
+ // Sheriff
+ sheriffSetTarget();
+ // Detective
+ detectiveUpdateFootPrints();
+ // Tracker
+ trackerSetTarget();
+ // Vampire
+ vampireSetTarget();
+ Garlic.UpdateAll();
+ // Eraser
+ eraserSetTarget();
+ // Engineer
+ engineerUpdate();
+ // Tracker
+ trackerUpdate();
+ // Jackal
+ jackalSetTarget();
+ // Sidekick
+ sidekickSetTarget();
+ // Impostor
+ impostorSetTarget();
+ // Warlock
+ warlockSetTarget();
+ // Check for sidekick promotion on Jackal disconnect
+ sidekickCheckPromotion();
+ // SecurityGuard
+ securityGuardSetTarget();
+ // Arsonist
+ arsonistSetTarget();
+ // Snitch
+ snitchUpdate();
+ // BountyHunter
+ bountyHunterUpdate();
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerPhysics), nameof(PlayerPhysics.WalkPlayerTo))]
+ class PlayerPhysicsWalkPlayerToPatch {
+ private static Vector2 offset = Vector2.zero;
+ public static void Prefix(PlayerPhysics __instance) {
+ bool correctOffset = Camouflager.camouflageTimer <= 0f && (__instance.myPlayer == Mini.mini || (Morphling.morphling != null && __instance.myPlayer == Morphling.morphling && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f));
+ if (correctOffset) {
+ float currentScaling = (Mini.growingProgress() + 1) * 0.5f;
+ __instance.myPlayer.Collider.offset = currentScaling * Mini.defaultColliderOffset * Vector2.down;
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
+ class PlayerControlCmdReportDeadBodyPatch {
+ public static void Prefix(PlayerControl __instance) {
+ // Murder the bitten player before the meeting starts or reset the bitten player
+ if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead && Helpers.handleMurderAttempt(Vampire.bitten, true)) {
+ MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireTryKill, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+ RPCProcedure.vampireTryKill();
+ } else {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
+ writer.Write(byte.MaxValue);
+ writer.Write(byte.MaxValue);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.vampireSetBitten(byte.MaxValue, byte.MaxValue);
+ }
+ }
+ }
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcMurderPlayer))]
+ class RpcMurderPlayer {
+ public static bool Prefix([HarmonyArgument(0)]PlayerControl target) {
+ if (Helpers.handleMurderAttempt(target)) { // Custom checks
+ 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);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, target.PlayerId);
+ } else { // Checked by official servers
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.LocalPlayer.CmdReportDeadBody))]
+ class BodyReportPatch
+ {
+ static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]GameData.PlayerInfo target)
+ {
+ // Medic or Detective report
+ bool isMedicReport = Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && __instance.PlayerId == Medic.medic.PlayerId;
+ bool isDetectiveReport = Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer && __instance.PlayerId == Detective.detective.PlayerId;
+ if (isMedicReport || isDetectiveReport)
+ {
+ DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == target?.PlayerId)?.FirstOrDefault();
+
+ if (deadPlayer != null && deadPlayer.killerIfExisting != null) {
+ float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds);
+ string msg = "";
+
+ if (isMedicReport) {
+ msg = $"Body Report: Killed {Math.Round(timeSinceDeath / 1000)}s ago!";
+ } else if (isDetectiveReport) {
+ if (timeSinceDeath < Detective.reportNameDuration * 1000) {
+ msg = $"Body Report: The killer appears to be {deadPlayer.killerIfExisting.name}!";
+ } else if (timeSinceDeath < Detective.reportColorDuration * 1000) {
+ var typeOfColor = Helpers.isLighterColor(deadPlayer.killerIfExisting.Data.ColorId) ? "lighter" : "darker";
+ msg = $"Body Report: The killer appears to be a {typeOfColor} color!";
+ } else {
+ msg = $"Body Report: The corpse is too old to gain information from!";
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(msg))
+ {
+ if (AmongUsClient.Instance.AmClient && DestroyableSingleton<HudManager>.Instance)
+ {
+ DestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, msg);
+ }
+ if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
+ {
+ DestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
+ public static class MurderPlayerPatch
+ {
+ public static bool resetToCrewmate = false;
+ public static bool resetToDead = false;
+
+ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)]PlayerControl target)
+ {
+ // Allow everyone to murder players
+ resetToCrewmate = !__instance.Data.IsImpostor;
+ resetToDead = __instance.Data.IsDead;
+ __instance.Data.IsImpostor = true;
+ __instance.Data.IsDead = false;
+ }
+
+ public static void Postfix(PlayerControl __instance, [HarmonyArgument(0)]PlayerControl target)
+ {
+ // Collect dead player info
+ DeadPlayer deadPlayer = new DeadPlayer(target, DateTime.UtcNow, DeathReason.Kill, __instance);
+ GameHistory.deadPlayers.Add(deadPlayer);
+
+ // Reset killer to crewmate if resetToCrewmate
+ if (resetToCrewmate) __instance.Data.IsImpostor = false;
+ if (resetToDead) __instance.Data.IsDead = true;
+
+ // Remove fake tasks when player dies
+ if (target.hasFakeTasks())
+ target.clearAllTasks();
+
+ // Lover suicide trigger on murder
+ if ((Lovers.lover1 != null && target == Lovers.lover1) || (Lovers.lover2 != null && target == Lovers.lover2)) {
+ PlayerControl otherLover = target == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
+ if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie) {
+ otherLover.MurderPlayer(otherLover);
+ }
+ }
+
+ // Sidekick promotion trigger on murder
+ if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && target == Jackal.jackal && Jackal.jackal == PlayerControl.LocalPlayer) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.sidekickPromotes();
+ }
+
+ // Cleaner Button Sync
+ if (Cleaner.cleaner != null && PlayerControl.LocalPlayer == Cleaner.cleaner && __instance == Cleaner.cleaner && HudManagerStartPatch.cleanerCleanButton != null)
+ HudManagerStartPatch.cleanerCleanButton.Timer = Cleaner.cleaner.killTimer;
+
+ // Warlock Button Sync
+ if (Warlock.warlock != null && PlayerControl.LocalPlayer == Warlock.warlock && __instance == Warlock.warlock && HudManagerStartPatch.warlockCurseButton != null) {
+ if(Warlock.warlock.killTimer > HudManagerStartPatch.warlockCurseButton.Timer) {
+ HudManagerStartPatch.warlockCurseButton.Timer = Warlock.warlock.killTimer;
+ }
+ }
+
+ // Seer show flash and add dead player position
+ if (Seer.seer != null && PlayerControl.LocalPlayer == Seer.seer && !Seer.seer.Data.IsDead && Seer.seer != target && Seer.mode <= 1) {
+ HudManager.Instance.FullScreen.enabled = true;
+ HudManager.Instance.StartCoroutine(Effects.Lerp(1f, new Action<float>((p) => {
+ var renderer = HudManager.Instance.FullScreen;
+ if (p < 0.5) {
+ if (renderer != null)
+ renderer.color = new Color(42f / 255f, 187f / 255f, 245f / 255f, Mathf.Clamp01(p * 2 * 0.75f));
+ } else {
+ if (renderer != null)
+ renderer.color = new Color(42f / 255f, 187f / 255f, 245f / 255f, Mathf.Clamp01((1-p) * 2 * 0.75f));
+ }
+ if (p == 1f && renderer != null) renderer.enabled = false;
+ })));
+ }
+ if (Seer.deadBodyPositions != null) Seer.deadBodyPositions.Add(target.transform.position);
+
+ // Mini set adapted kill cooldown
+ if (Mini.mini != null && PlayerControl.LocalPlayer == Mini.mini && Mini.mini.Data.IsImpostor && Mini.mini == __instance) {
+ 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);
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.SetKillTimer))]
+ class PlayerControlSetCoolDownPatch {
+ 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 + addition);
+ DestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(__instance.killTimer, PlayerControl.GameOptions.KillCooldown * multiplier + addition);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
+ class KillAnimationCoPerformKillPatch {
+ public static void Prefix(KillAnimation __instance, [HarmonyArgument(0)]ref PlayerControl source, [HarmonyArgument(1)]ref PlayerControl target) {
+ if (Vampire.vampire != null && Vampire.vampire == source && Vampire.bitten != null && Vampire.bitten == target)
+ source = target;
+
+ if (Warlock.warlock != null && Warlock.warlock == source && Warlock.curseKillTarget != null && Warlock.curseKillTarget == target) {
+ source = target;
+ Warlock.curseKillTarget = null; // Reset here
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Exiled))]
+ public static class ExilePlayerPatch
+ {
+ public static void Postfix(PlayerControl __instance)
+ {
+ // Collect dead player info
+ DeadPlayer deadPlayer = new DeadPlayer(__instance, DateTime.UtcNow, DeathReason.Exile, null);
+ GameHistory.deadPlayers.Add(deadPlayer);
+
+ // Remove fake tasks when player dies
+ if (__instance.hasFakeTasks())
+ __instance.clearAllTasks();
+
+ // Lover suicide trigger on exile
+ if ((Lovers.lover1 != null && __instance == Lovers.lover1) || (Lovers.lover2 != null && __instance == Lovers.lover2)) {
+ PlayerControl otherLover = __instance == Lovers.lover1 ? Lovers.lover2 : Lovers.lover1;
+ if (otherLover != null && !otherLover.Data.IsDead && Lovers.bothDie)
+ otherLover.Exiled();
+ }
+
+ // Sidekick promotion trigger on exile
+ if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead && __instance == Jackal.jackal && Jackal.jackal == PlayerControl.LocalPlayer) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SidekickPromotes, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.sidekickPromotes();
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CanMove), MethodType.Getter)]
+ class PlayerControlCanMovePatch {
+ public static bool Prefix(PlayerControl __instance, ref bool __result)
+ {
+ __result = __instance.moveable &&
+ !Minigame.Instance &&
+ (!DestroyableSingleton<HudManager>.InstanceExists || (!DestroyableSingleton<HudManager>.Instance.Chat.IsOpen && !DestroyableSingleton<HudManager>.Instance.KillOverlay.IsOpen && !DestroyableSingleton<HudManager>.Instance.GameMenu.IsOpen)) &&
+ (!MapBehaviour.Instance || !MapBehaviour.Instance.IsOpenStopped) &&
+ !MeetingHud.Instance &&
+ !CustomPlayerMenu.Instance &&
+ !ExileController.Instance &&
+ !IntroCutscene.Instance;
+ return false;
+ }
+ }
+}
--- /dev/null
+// Adapted from https://github.com/MoltenMods/Unify
+/*
+MIT License
+
+Copyright (c) 2021 Daemon
+
+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 HarmonyLib;
+using UnityEngine;
+using UnityEngine.UI;
+using System;
+using UnityEngine.Events;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(RegionMenu), nameof(RegionMenu.Open))]
+ public static class RegionMenuOpenPatch
+ {
+ private static TextBoxTMP ipField;
+ private static TextBoxTMP portField;
+
+ public static void Postfix(RegionMenu __instance)
+ {
+ var template = DestroyableSingleton<JoinGameButton>.Instance;
+
+ if (ipField == null || ipField.gameObject == null) {
+ ipField = UnityEngine.Object.Instantiate(template.GameIdText, __instance.transform);
+ ipField.gameObject.name = "IpTextBox";
+ UnityEngine.Object.DestroyImmediate(ipField.transform.FindChild("arrowEnter").gameObject);
+
+ ipField.transform.localPosition = new Vector3(0, -1f, -100f);
+ ipField.characterLimit = 30;
+ ipField.AllowSymbols = true;
+ ipField.ForceUppercase = false;
+ ipField.SetText(TheOtherRolesPlugin.Ip.Value);
+ __instance.StartCoroutine(Effects.Lerp(0.1f, new Action<float>((p) => {
+ ipField.outputText.SetText(TheOtherRolesPlugin.Ip.Value);
+ ipField.SetText(TheOtherRolesPlugin.Ip.Value);
+ })));
+
+ ipField.ClearOnFocus = false;
+ ipField.OnEnter = ipField.OnChange = new Button.ButtonClickedEvent();
+ ipField.OnFocusLost = new Button.ButtonClickedEvent();
+ ipField.OnChange.AddListener((UnityAction)onEnterOrIpChange);
+ ipField.OnFocusLost.AddListener((UnityAction)onFocusLost);
+
+ void onEnterOrIpChange() {
+ TheOtherRolesPlugin.Ip.Value = ipField.text;
+ }
+
+ void onFocusLost() {
+ TheOtherRolesPlugin.UpdateRegions();
+ __instance.ChooseOption(ServerManager.DefaultRegions[ServerManager.DefaultRegions.Length - 1]);
+ }
+ }
+ if (portField == null || portField.gameObject == null) {
+ portField = UnityEngine.Object.Instantiate(template.GameIdText, __instance.transform);
+ portField.gameObject.name = "PortTextBox";
+ UnityEngine.Object.DestroyImmediate(portField.transform.FindChild("arrowEnter").gameObject);
+
+ portField.transform.localPosition = new Vector3(0, -1.75f, -100f);
+ portField.characterLimit = 5;
+ portField.SetText(TheOtherRolesPlugin.Port.Value.ToString());
+ __instance.StartCoroutine(Effects.Lerp(0.1f, new Action<float>((p) => {
+ portField.outputText.SetText(TheOtherRolesPlugin.Port.Value.ToString());
+ portField.SetText(TheOtherRolesPlugin.Port.Value.ToString());
+ })));
+
+
+ portField.ClearOnFocus = false;
+ portField.OnEnter = portField.OnChange = new Button.ButtonClickedEvent();
+ portField.OnFocusLost = new Button.ButtonClickedEvent();
+ portField.OnChange.AddListener((UnityAction)onEnterOrPortFieldChange);
+ portField.OnFocusLost.AddListener((UnityAction)onFocusLost);
+
+ void onEnterOrPortFieldChange() {
+ ushort port = 0;
+ if (ushort.TryParse(portField.text, out port)) {
+ TheOtherRolesPlugin.Port.Value = port;
+ portField.outputText.color = Color.white;
+ } else {
+ portField.outputText.color = Color.red;
+ }
+ }
+
+ void onFocusLost() {
+ TheOtherRolesPlugin.UpdateRegions();
+ __instance.ChooseOption(ServerManager.DefaultRegions[ServerManager.DefaultRegions.Length - 1]);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using HarmonyLib;
+using Hazel;
+using System.Collections.Generic;
+using System.Linq;
+using UnhollowerBaseLib;
+using UnityEngine;
+using System;
+using static TheOtherRoles.TheOtherRoles;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSetInfected))]
+ class SetInfectedPatch
+ {
+
+ public static void Postfix([HarmonyArgument(0)]Il2CppReferenceArray<GameData.PlayerInfo> infected)
+ {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ResetVaribles, Hazel.SendOption.Reliable, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.resetVariables();
+
+ if (!DestroyableSingleton<TutorialManager>.InstanceExists) // Don't assign Roles in Tutorial
+ assignRoles();
+ }
+
+ 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
+ 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
+ }
+
+ private static RoleAssignmentData getRoleAssignmentData() {
+ // Get the players that we want to assign the roles to. Crewmate and Neutral roles are assigned to natural crewmates. Impostor roles to impostors.
+ List<PlayerControl> crewmates = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
+ crewmates.RemoveAll(x => x.Data.IsImpostor);
+ List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
+ impostors.RemoveAll(x => !x.Data.IsImpostor);
+
+ var crewmateMin = CustomOptionHolder.crewmateRolesCountMin.getSelection();
+ var crewmateMax = CustomOptionHolder.crewmateRolesCountMax.getSelection();
+ var neutralMin = CustomOptionHolder.neutralRolesCountMin.getSelection();
+ var neutralMax = CustomOptionHolder.neutralRolesCountMax.getSelection();
+ var impostorMin = CustomOptionHolder.impostorRolesCountMin.getSelection();
+ var impostorMax = CustomOptionHolder.impostorRolesCountMax.getSelection();
+
+ // Make sure min is less or equal to max
+ if (crewmateMin > crewmateMax) crewmateMin = crewmateMax;
+ if (neutralMin > neutralMax) neutralMin = neutralMax;
+ if (impostorMin > impostorMax) impostorMin = impostorMax;
+
+ // Get the maximum allowed count of each role type based on the minimum and maximum option
+ int crewCountSettings = rnd.Next(crewmateMin, crewmateMax + 1);
+ int neutralCountSettings = rnd.Next(neutralMin, neutralMax + 1);
+ int impCountSettings = rnd.Next(impostorMin, impostorMax + 1);
+
+ // Potentially lower the actual maximum to the assignable players
+ int maxCrewmateRoles = Mathf.Min(crewmates.Count, crewCountSettings);
+ int maxNeutralRoles = Mathf.Min(crewmates.Count, neutralCountSettings);
+ int maxImpostorRoles = Mathf.Min(impostors.Count, impCountSettings);
+
+ // Fill in the lists with the roles that should be assigned to players. Note that the special roles (like Mafia or Lovers) are NOT included in these lists
+ Dictionary<byte, int> impSettings = new Dictionary<byte, int>();
+ Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
+ Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
+
+ impSettings.Add((byte)RoleId.Morphling, CustomOptionHolder.morphlingSpawnRate.getSelection());
+ impSettings.Add((byte)RoleId.Camouflager, CustomOptionHolder.camouflagerSpawnRate.getSelection());
+ impSettings.Add((byte)RoleId.Vampire, CustomOptionHolder.vampireSpawnRate.getSelection());
+ impSettings.Add((byte)RoleId.Eraser, CustomOptionHolder.eraserSpawnRate.getSelection());
+ 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());
+ neutralSettings.Add((byte)RoleId.Jackal, CustomOptionHolder.jackalSpawnRate.getSelection());
+
+ crewSettings.Add((byte)RoleId.Mayor, CustomOptionHolder.mayorSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Engineer, CustomOptionHolder.engineerSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Sheriff, CustomOptionHolder.sheriffSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Lighter, CustomOptionHolder.lighterSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Detective, CustomOptionHolder.detectiveSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.TimeMaster, CustomOptionHolder.timeMasterSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Medic, CustomOptionHolder.medicSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Shifter, CustomOptionHolder.shifterSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Swapper,CustomOptionHolder.swapperSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Seer, CustomOptionHolder.seerSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Hacker, CustomOptionHolder.hackerSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Tracker, CustomOptionHolder.trackerSpawnRate.getSelection());
+ crewSettings.Add((byte)RoleId.Snitch, CustomOptionHolder.snitchSpawnRate.getSelection());
+ if (impostors.Count > 1) {
+ // Only add Spy if more than 1 impostor as the spy role is otherwise useless
+ crewSettings.Add((byte)RoleId.Spy, CustomOptionHolder.spySpawnRate.getSelection());
+ }
+ crewSettings.Add((byte)RoleId.SecurityGuard, CustomOptionHolder.securityGuardSpawnRate.getSelection());
+
+ return new RoleAssignmentData {
+ crewmates = crewmates,
+ impostors = impostors,
+ crewSettings = crewSettings,
+ neutralSettings = neutralSettings,
+ impSettings = impSettings,
+ maxCrewmateRoles = maxCrewmateRoles,
+ maxNeutralRoles = maxNeutralRoles,
+ maxImpostorRoles = maxImpostorRoles
+ };
+ }
+
+ private static void assignSpecialRoles(RoleAssignmentData data) {
+ // Assign Lovers
+ if (rnd.Next(1, 101) <= CustomOptionHolder.loversSpawnRate.getSelection() * 10) {
+ bool isOnlyRole = !CustomOptionHolder.loversCanHaveAnotherRole.getBool();
+ if (data.impostors.Count > 0 && data.crewmates.Count > 0 && (!isOnlyRole || (data.maxCrewmateRoles > 0 && data.maxImpostorRoles > 0)) && rnd.Next(1, 101) <= CustomOptionHolder.loversImpLoverRate.getSelection() * 10) {
+ setRoleToRandomPlayer((byte)RoleId.Lover, data.impostors, 0, isOnlyRole);
+ setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 1, isOnlyRole);
+ if (isOnlyRole) {
+ data.maxCrewmateRoles--;
+ data.maxImpostorRoles--;
+ }
+ } else if (data.crewmates.Count >= 2 && (isOnlyRole || data.maxCrewmateRoles >= 2)) {
+ byte firstLoverId = setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 0, isOnlyRole);
+ if (isOnlyRole) {
+ setRoleToRandomPlayer((byte)RoleId.Lover, data.crewmates, 1);
+ data.maxCrewmateRoles -= 2;
+ } else {
+ var crewmatesWithoutFirstLover = data.crewmates.ToList();
+ crewmatesWithoutFirstLover.RemoveAll(p => p.PlayerId == firstLoverId);
+ setRoleToRandomPlayer((byte)RoleId.Lover, crewmatesWithoutFirstLover, 1, false);
+ }
+ }
+ }
+
+ // Assign Mafia
+ if (data.impostors.Count >= 3 && data.maxImpostorRoles >= 3 && (rnd.Next(1, 101) <= CustomOptionHolder.mafiaSpawnRate.getSelection() * 10)) {
+ setRoleToRandomPlayer((byte)RoleId.Godfather, data.impostors);
+ setRoleToRandomPlayer((byte)RoleId.Janitor, data.impostors);
+ setRoleToRandomPlayer((byte)RoleId.Mafioso, data.impostors);
+ data.maxImpostorRoles -= 3;
+ }
+ }
+
+ 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 (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());
+ }
+ }
+
+ private static void assignEnsuredRoles(RoleAssignmentData data) {
+ // Get all roles where the chance to occur is set to 100%
+ List<byte> ensuredCrewmateRoles = data.crewSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
+ List<byte> ensuredNeutralRoles = data.neutralSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
+ List<byte> ensuredImpostorRoles = data.impSettings.Where(x => x.Value == 10).Select(x => x.Key).ToList();
+
+ // Assign roles until we run out of either players we can assign roles to or run out of roles we can assign to players
+ while (
+ (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0) ||
+ (data.crewmates.Count > 0 && (
+ (data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0) ||
+ (data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0)
+ ))) {
+
+ Dictionary<RoleType, List<byte>> rolesToAssign = new Dictionary<RoleType, List<byte>>();
+ if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && ensuredCrewmateRoles.Count > 0) rolesToAssign.Add(RoleType.Crewmate, ensuredCrewmateRoles);
+ if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && ensuredNeutralRoles.Count > 0) rolesToAssign.Add(RoleType.Neutral, ensuredNeutralRoles);
+ if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && ensuredImpostorRoles.Count > 0) rolesToAssign.Add(RoleType.Impostor, ensuredImpostorRoles);
+
+ // Randomly select a pool of roles to assign a role from next (Crewmate role, Neutral role or Impostor role)
+ // then select one of the roles from the selected pool to a player
+ // and remove the role (and any potentially blocked role pairings) from the pool(s)
+ var roleType = rolesToAssign.Keys.ElementAt(rnd.Next(0, rolesToAssign.Keys.Count()));
+ var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors;
+ var index = rnd.Next(0, rolesToAssign[roleType].Count);
+ var roleId = rolesToAssign[roleType][index];
+ setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
+ rolesToAssign[roleType].RemoveAt(index);
+
+ if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) {
+ foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) {
+ // Set chance for the blocked roles to 0 for chances less than 100%
+ if (data.impSettings.ContainsKey(blockedRoleId)) data.impSettings[blockedRoleId] = 0;
+ if (data.neutralSettings.ContainsKey(blockedRoleId)) data.neutralSettings[blockedRoleId] = 0;
+ if (data.crewSettings.ContainsKey(blockedRoleId)) data.crewSettings[blockedRoleId] = 0;
+ // Remove blocked roles even if the chance was 100%
+ foreach(var ensuredRolesList in rolesToAssign.Values) {
+ ensuredRolesList.RemoveAll(x => x == blockedRoleId);
+ }
+ }
+ }
+
+ // Adjust the role limit
+ switch (roleType) {
+ case RoleType.Crewmate: data.maxCrewmateRoles--; break;
+ case RoleType.Neutral: data.maxNeutralRoles--;break;
+ case RoleType.Impostor: data.maxImpostorRoles--;break;
+ }
+ }
+ }
+
+
+ private static void assignChanceRoles(RoleAssignmentData data) {
+ // Get all roles where the chance to occur is set grater than 0% but not 100% and build a ticket pool based on their weight
+ List<byte> crewmateTickets = data.crewSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
+ List<byte> neutralTickets = data.neutralSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
+ List<byte> impostorTickets = data.impSettings.Where(x => x.Value > 0 && x.Value < 10).Select(x => Enumerable.Repeat(x.Key, x.Value)).SelectMany(x => x).ToList();
+
+ // Assign roles until we run out of either players we can assign roles to or run out of roles we can assign to players
+ while (
+ (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0) ||
+ (data.crewmates.Count > 0 && (
+ (data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) ||
+ (data.maxNeutralRoles > 0 && neutralTickets.Count > 0)
+ ))) {
+
+ Dictionary<RoleType, List<byte>> rolesToAssign = new Dictionary<RoleType, List<byte>>();
+ if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0 && crewmateTickets.Count > 0) rolesToAssign.Add(RoleType.Crewmate, crewmateTickets);
+ if (data.crewmates.Count > 0 && data.maxNeutralRoles > 0 && neutralTickets.Count > 0) rolesToAssign.Add(RoleType.Neutral, neutralTickets);
+ if (data.impostors.Count > 0 && data.maxImpostorRoles > 0 && impostorTickets.Count > 0) rolesToAssign.Add(RoleType.Impostor, impostorTickets);
+
+ // Randomly select a pool of role tickets to assign a role from next (Crewmate role, Neutral role or Impostor role)
+ // then select one of the roles from the selected pool to a player
+ // and remove all tickets of this role (and any potentially blocked role pairings) from the pool(s)
+ var roleType = rolesToAssign.Keys.ElementAt(rnd.Next(0, rolesToAssign.Keys.Count()));
+ var players = roleType == RoleType.Crewmate || roleType == RoleType.Neutral ? data.crewmates : data.impostors;
+ var index = rnd.Next(0, rolesToAssign[roleType].Count);
+ var roleId = rolesToAssign[roleType][index];
+ setRoleToRandomPlayer(rolesToAssign[roleType][index], players);
+ rolesToAssign[roleType].RemoveAll(x => x == roleId);
+
+ if (CustomOptionHolder.blockedRolePairings.ContainsKey(roleId)) {
+ foreach(var blockedRoleId in CustomOptionHolder.blockedRolePairings[roleId]) {
+ // Remove tickets of blocked roles from all pools
+ crewmateTickets.RemoveAll(x => x == blockedRoleId);
+ neutralTickets.RemoveAll(x => x == blockedRoleId);
+ impostorTickets.RemoveAll(x => x == blockedRoleId);
+ }
+ }
+
+ // Adjust the role limit
+ switch (roleType) {
+ case RoleType.Crewmate: data.maxCrewmateRoles--; break;
+ case RoleType.Neutral: data.maxNeutralRoles--;break;
+ case RoleType.Impostor: data.maxImpostorRoles--;break;
+ }
+ }
+ }
+
+ private static byte setRoleToRandomPlayer(byte roleId, List<PlayerControl> playerList, byte flag = 0, bool removePlayer = true) {
+ var index = rnd.Next(0, playerList.Count);
+ byte playerId = playerList[index].PlayerId;
+ if (removePlayer) playerList.RemoveAt(index);
+
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SetRole, Hazel.SendOption.Reliable, -1);
+ writer.Write(roleId);
+ writer.Write(playerId);
+ writer.Write(flag);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.setRole(roleId, playerId, flag);
+ return playerId;
+ }
+
+
+
+ private class RoleAssignmentData {
+ public List<PlayerControl> crewmates {get;set;}
+ public List<PlayerControl> impostors {get;set;}
+ public Dictionary<byte, int> impSettings = new Dictionary<byte, int>();
+ public Dictionary<byte, int> neutralSettings = new Dictionary<byte, int>();
+ public Dictionary<byte, int> crewSettings = new Dictionary<byte, int>();
+ public int maxCrewmateRoles {get;set;}
+ public int maxNeutralRoles {get;set;}
+ public int maxImpostorRoles {get;set;}
+ }
+
+ private enum RoleType {
+ Crewmate = 0,
+ Neutral = 1,
+ Impostor = 2
+ }
+
+ }
+}
--- /dev/null
+using HarmonyLib;
+using static TheOtherRoles.TheOtherRoles;
+using UnityEngine;
+
+namespace TheOtherRoles.Patches {
+
+ [HarmonyPatch(typeof(ShipStatus))]
+ public class ShipStatusPatch {
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.CalculateLightRadius))]
+ public static bool Prefix(ref float __result, ShipStatus __instance, [HarmonyArgument(0)] GameData.PlayerInfo player) {
+ ISystemType systemType = __instance.Systems.ContainsKey(SystemTypes.Electrical) ? __instance.Systems[SystemTypes.Electrical] : null;
+ if (systemType == null) return true;
+ SwitchSystem switchSystem = systemType.TryCast<SwitchSystem>();
+ if (switchSystem == null) return true;
+
+ float num = (float)switchSystem.Value / 255f;
+
+ if (player == null || player.IsDead) // IsDead
+ __result = __instance.MaxLightRadius;
+ else if (player.IsImpostor
+ || (Jackal.jackal != null && Jackal.jackal.PlayerId == player.PlayerId && Jackal.hasImpostorVision)
+ || (Sidekick.sidekick != null && Sidekick.sidekick.PlayerId == player.PlayerId && Sidekick.hasImpostorVision)
+ || (Spy.spy != null && Spy.spy.PlayerId == player.PlayerId && Spy.hasImpostorVision)) // Impostor, Jackal/Sidekick or Spy with Impostor vision
+ __result = __instance.MaxLightRadius * PlayerControl.GameOptions.ImpostorLightMod;
+ else if (Lighter.lighter != null && Lighter.lighter.PlayerId == player.PlayerId && Lighter.lighterTimer > 0f) // if player is Lighter and Lighter has his ability active
+ __result = Mathf.Lerp(__instance.MaxLightRadius * Lighter.lighterModeLightsOffVision, __instance.MaxLightRadius * Lighter.lighterModeLightsOnVision, num);
+ else if (Trickster.trickster != null && Trickster.lightsOutTimer > 0f) {
+ float lerpValue = 1f;
+ if (Trickster.lightsOutDuration - Trickster.lightsOutTimer < 0.5f) lerpValue = Mathf.Clamp01((Trickster.lightsOutDuration - Trickster.lightsOutTimer) * 2);
+ else if (Trickster.lightsOutTimer < 0.5) lerpValue = Mathf.Clamp01(Trickster.lightsOutTimer*2);
+ __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, 1 - lerpValue) * PlayerControl.GameOptions.CrewLightMod; // Instant lights out? Maybe add a smooth transition?
+ }
+ else
+ __result = Mathf.Lerp(__instance.MinLightRadius, __instance.MaxLightRadius, num) * PlayerControl.GameOptions.CrewLightMod;
+ return false;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.IsGameOverDueToDeath))]
+ public static void Postfix2(ShipStatus __instance, ref bool __result)
+ {
+ __result = false;
+ }
+
+ private static int originalNumCommonTasksOption = 0;
+ private static int originalNumShortTasksOption = 0;
+ private static int originalNumLongTasksOption = 0;
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))]
+ public static bool Prefix(ShipStatus __instance)
+ {
+ var commonTaskCount = __instance.CommonTasks.Count;
+ var normalTaskCount = __instance.NormalTasks.Count;
+ var longTaskCount = __instance.LongTasks.Count;
+ originalNumCommonTasksOption = PlayerControl.GameOptions.NumCommonTasks;
+ originalNumShortTasksOption = PlayerControl.GameOptions.NumShortTasks;
+ originalNumLongTasksOption = PlayerControl.GameOptions.NumLongTasks;
+ if(PlayerControl.GameOptions.NumCommonTasks > commonTaskCount) PlayerControl.GameOptions.NumCommonTasks = commonTaskCount;
+ if(PlayerControl.GameOptions.NumShortTasks > normalTaskCount) PlayerControl.GameOptions.NumShortTasks = normalTaskCount;
+ if(PlayerControl.GameOptions.NumLongTasks > longTaskCount) PlayerControl.GameOptions.NumLongTasks = longTaskCount;
+ return true;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(ShipStatus), nameof(ShipStatus.Begin))]
+ public static void Postfix3(ShipStatus __instance)
+ {
+ // Restore original settings after the tasks have been selected
+ PlayerControl.GameOptions.NumCommonTasks = originalNumCommonTasksOption;
+ PlayerControl.GameOptions.NumShortTasks = originalNumShortTasksOption;
+ PlayerControl.GameOptions.NumLongTasks = originalNumLongTasksOption;
+ }
+
+ }
+
+}
\ No newline at end of file
--- /dev/null
+using HarmonyLib;
+using System;
+using System.IO;
+using System.Net.Http;
+using UnityEngine;
+using static TheOtherRoles.TheOtherRoles;
+using TheOtherRoles.Objects;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace TheOtherRoles.Patches {
+ [HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
+ class HudManagerUpdatePatch
+ {
+ public static bool hidePlayerName(PlayerControl source, PlayerControl target) {
+ if (!MapOptions.hidePlayerNames) return false; // All names are visible
+ else if (source == null || target == null) return true;
+ else if (source == target) return false; // Player sees his own name
+ else if (source.Data.IsImpostor && (target.Data.IsImpostor || target == Spy.spy)) return false; // Members of team Impostors see the names of Impostors/Spies
+ else if ((source == Lovers.lover1 || source == Lovers.lover2) && (target == Lovers.lover1 || target == Lovers.lover2)) return false; // Members of team Lovers see the names of each other
+ else if ((source == Jackal.jackal || source == Sidekick.sidekick) && (target == Jackal.jackal || target == Sidekick.sidekick || target == Jackal.fakeSidekick)) return false; // Members of team Jackal see the names of each other
+ return true;
+ }
+
+ static void resetNameTagsAndColors() {
+ Dictionary<byte, PlayerControl> playersById = Helpers.allPlayersById();
+
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
+ player.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, player) ? "" : player.Data.PlayerName;
+ if (PlayerControl.LocalPlayer.Data.IsImpostor && player.Data.IsImpostor) {
+ player.nameText.color = Palette.ImpostorRed;
+ } else {
+ player.nameText.color = Color.white;
+ }
+ }
+ if (MeetingHud.Instance != null) {
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
+ PlayerControl playerControl = playersById.ContainsKey((byte)player.TargetPlayerId) ? playersById[(byte)player.TargetPlayerId] : null;
+ if (playerControl != null) {
+ player.NameText.text = playerControl.Data.PlayerName;
+ if (PlayerControl.LocalPlayer.Data.IsImpostor && playerControl.Data.IsImpostor) {
+ player.NameText.color = Palette.ImpostorRed;
+ } else {
+ player.NameText.color = Color.white;
+ }
+ }
+ }
+ }
+ if (PlayerControl.LocalPlayer.Data.IsImpostor) {
+ List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList();
+ impostors.RemoveAll(x => !x.Data.IsImpostor);
+ foreach (PlayerControl player in impostors)
+ player.nameText.color = Palette.ImpostorRed;
+ if (MeetingHud.Instance != null)
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
+ PlayerControl playerControl = Helpers.playerById((byte)player.TargetPlayerId);
+ if (playerControl != null && playerControl.Data.IsImpostor)
+ player.NameText.color = Palette.ImpostorRed;
+ }
+ }
+
+ }
+
+ static void setPlayerNameColor(PlayerControl p, Color color) {
+ p.nameText.color = color;
+ if (MeetingHud.Instance != null)
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
+ if (player.NameText != null && p.PlayerId == player.TargetPlayerId)
+ player.NameText.color = color;
+ }
+
+ static void setNameColors() {
+ if (Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Jester.jester, Jester.color);
+ else if (Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Mayor.mayor, Mayor.color);
+ else if (Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Engineer.engineer, Engineer.color);
+ else if (Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Sheriff.sheriff, Sheriff.color);
+ else if (Lighter.lighter != null && Lighter.lighter == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Lighter.lighter, Lighter.color);
+ else if (Detective.detective != null && Detective.detective == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Detective.detective, Detective.color);
+ else if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer)
+ setPlayerNameColor(TimeMaster.timeMaster, TimeMaster.color);
+ else if (Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Medic.medic, Medic.color);
+ else if (Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Shifter.shifter, Shifter.color);
+ else if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Swapper.swapper, Swapper.color);
+ else if (Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Seer.seer, Seer.color);
+ else if (Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Hacker.hacker, Hacker.color);
+ else if (Tracker.tracker != null && Tracker.tracker == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Tracker.tracker, Tracker.color);
+ else if (Snitch.snitch != null && Snitch.snitch == PlayerControl.LocalPlayer)
+ setPlayerNameColor(Snitch.snitch, Snitch.color);
+ else if (Jackal.jackal != null && Jackal.jackal == PlayerControl.LocalPlayer) {
+ // Jackal can see his sidekick
+ setPlayerNameColor(Jackal.jackal, Jackal.color);
+ if (Sidekick.sidekick != null) {
+ setPlayerNameColor(Sidekick.sidekick, Jackal.color);
+ }
+ if (Jackal.fakeSidekick != null) {
+ setPlayerNameColor(Jackal.fakeSidekick, Jackal.color);
+ }
+ }
+ else if (Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer) {
+ setPlayerNameColor(Spy.spy, Spy.color);
+ } else if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == PlayerControl.LocalPlayer) {
+ setPlayerNameColor(SecurityGuard.securityGuard, SecurityGuard.color);
+ } else if (Arsonist.arsonist != null && Arsonist.arsonist == PlayerControl.LocalPlayer) {
+ setPlayerNameColor(Arsonist.arsonist, Arsonist.color);
+ } 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
+ setPlayerNameColor(Sidekick.sidekick, Sidekick.color);
+ if (Jackal.jackal != null) {
+ setPlayerNameColor(Jackal.jackal, Jackal.color);
+ }
+ }
+
+ // No else if here, as the Impostors need the Spy name to be colored
+ if (Spy.spy != null && PlayerControl.LocalPlayer.Data.IsImpostor) {
+ setPlayerNameColor(Spy.spy, Spy.color);
+ }
+
+ // Crewmate roles with no changes: Mini
+ // Impostor roles with no changes: Morphling, Camouflager, Vampire, Godfather, Eraser, Janitor, Cleaner, Warlock, BountyHunter and Mafioso
+ }
+
+ static void setNameTags() {
+ // Mafia
+ if (PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.Data.IsImpostor) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ if (Godfather.godfather != null && Godfather.godfather == player)
+ player.nameText.text = player.Data.PlayerName + " (G)";
+ else if (Mafioso.mafioso != null && Mafioso.mafioso == player)
+ player.nameText.text = player.Data.PlayerName + " (M)";
+ else if (Janitor.janitor != null && Janitor.janitor == player)
+ player.nameText.text = player.Data.PlayerName + " (J)";
+ if (MeetingHud.Instance != null)
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
+ if (Godfather.godfather != null && Godfather.godfather.PlayerId == player.TargetPlayerId)
+ player.NameText.text = Godfather.godfather.Data.PlayerName + " (G)";
+ else if (Mafioso.mafioso != null && Mafioso.mafioso.PlayerId == player.TargetPlayerId)
+ player.NameText.text = Mafioso.mafioso.Data.PlayerName + " (M)";
+ else if (Janitor.janitor != null && Janitor.janitor.PlayerId == player.TargetPlayerId)
+ player.NameText.text = Janitor.janitor.Data.PlayerName + " (J)";
+ }
+
+ // Lovers
+ if (Lovers.lover1 != null && Lovers.lover2 != null && (Lovers.lover1 == PlayerControl.LocalPlayer || Lovers.lover2 == PlayerControl.LocalPlayer)) {
+ string suffix = Helpers.cs(Lovers.color, " ♥");
+ Lovers.lover1.nameText.text += suffix;
+ Lovers.lover2.nameText.text += suffix;
+
+ if (MeetingHud.Instance != null)
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
+ if (Lovers.lover1.PlayerId == player.TargetPlayerId || Lovers.lover2.PlayerId == player.TargetPlayerId)
+ player.NameText.text += suffix;
+ }
+ }
+
+ static void updateShielded() {
+ if (Medic.shielded == null) return;
+
+ if (Medic.shielded.Data.IsDead || Medic.medic == null || Medic.medic.Data.IsDead) {
+ Medic.shielded = null;
+ }
+ }
+
+ static void timerUpdate() {
+ Hacker.hackerTimer -= Time.deltaTime;
+ Lighter.lighterTimer -= Time.deltaTime;
+ Trickster.lightsOutTimer -= Time.deltaTime;
+ }
+
+ static void camouflageAndMorphActions() {
+ float oldCamouflageTimer = Camouflager.camouflageTimer;
+ float oldMorphTimer = Morphling.morphTimer;
+
+ Camouflager.camouflageTimer -= Time.deltaTime;
+ Morphling.morphTimer -= Time.deltaTime;
+
+ // Morphling player size not done here
+
+ // Set morphling morphed look
+ if (Morphling.morphTimer > 0f && Camouflager.camouflageTimer <= 0f) {
+ if (Morphling.morphling != null && Morphling.morphTarget != null) {
+ Morphling.morphling.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, Morphling.morphling) ? "" : Morphling.morphTarget.Data.PlayerName;
+ Morphling.morphling.myRend.material.SetColor("_BackColor", Palette.ShadowColors[Morphling.morphTarget.Data.ColorId]);
+ Morphling.morphling.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[Morphling.morphTarget.Data.ColorId]);
+ Morphling.morphling.HatRenderer.SetHat(Morphling.morphTarget.Data.HatId, Morphling.morphTarget.Data.ColorId);
+ Morphling.morphling.nameText.transform.localPosition = new Vector3(0f, ((Morphling.morphTarget.Data.HatId == 0U) ? 0.7f : 1.05f) * 2f, -0.5f);
+
+ if (Morphling.morphling.MyPhysics.Skin.skin.ProdId != DestroyableSingleton<HatManager>.Instance.AllSkins[(int)Morphling.morphTarget.Data.SkinId].ProdId) {
+ Helpers.setSkinWithAnim(Morphling.morphling.MyPhysics, Morphling.morphTarget.Data.SkinId);
+ }
+ if (Morphling.morphling.CurrentPet == null || Morphling.morphling.CurrentPet.ProdId != DestroyableSingleton<HatManager>.Instance.AllPets[(int)Morphling.morphTarget.Data.PetId].ProdId) {
+ if (Morphling.morphling.CurrentPet) UnityEngine.Object.Destroy(Morphling.morphling.CurrentPet.gameObject);
+ Morphling.morphling.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[(int)Morphling.morphTarget.Data.PetId]);
+ Morphling.morphling.CurrentPet.transform.position = Morphling.morphling.transform.position;
+ Morphling.morphling.CurrentPet.Source = Morphling.morphling;
+ Morphling.morphling.CurrentPet.Visible = Morphling.morphling.Visible;
+ PlayerControl.SetPlayerMaterialColors(Morphling.morphTarget.Data.ColorId, Morphling.morphling.CurrentPet.rend);
+ } else if (Morphling.morphling.CurrentPet) {
+ PlayerControl.SetPlayerMaterialColors(Morphling.morphTarget.Data.ColorId, Morphling.morphling.CurrentPet.rend);
+ }
+ }
+ }
+
+ // Set camouflaged look (overrides morphling morphed look if existent)
+ if (Camouflager.camouflageTimer > 0f) {
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ p.nameText.text = "";
+ p.myRend.material.SetColor("_BackColor", Palette.PlayerColors[6]);
+ p.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[6]);
+ p.HatRenderer.SetHat(0, 0);
+ Helpers.setSkinWithAnim(p.MyPhysics, 0);
+ bool spawnPet = false;
+ if (p.CurrentPet == null) spawnPet = true;
+ else if (p.CurrentPet.ProdId != DestroyableSingleton<HatManager>.Instance.AllPets[0].ProdId) {
+ UnityEngine.Object.Destroy(p.CurrentPet.gameObject);
+ spawnPet = true;
+ }
+
+ if (spawnPet) {
+ p.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[0]);
+ p.CurrentPet.transform.position = p.transform.position;
+ p.CurrentPet.Source = p;
+ }
+ }
+ }
+
+ // Everyone but morphling reset
+ if (oldCamouflageTimer > 0f && Camouflager.camouflageTimer <= 0f) {
+ Camouflager.resetCamouflage();
+ }
+
+ // Morphling reset
+ if ((oldMorphTimer > 0f || oldCamouflageTimer > 0f) && Camouflager.camouflageTimer <= 0f && Morphling.morphTimer <= 0f && Morphling.morphling != null) {
+ Morphling.resetMorph();
+ }
+ }
+
+ public static void miniUpdate() {
+ if (Mini.mini == null || Camouflager.camouflageTimer > 0f) return;
+
+ float growingProgress = Mini.growingProgress();
+ float scale = growingProgress * 0.35f + 0.35f;
+ string suffix = "";
+ if (growingProgress != 1f)
+ suffix = " <color=#FAD934FF>(" + Mathf.FloorToInt(growingProgress * 18) + ")</color>";
+
+ Mini.mini.nameText.text += suffix;
+ if (MeetingHud.Instance != null) {
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
+ if (player.NameText != null && Mini.mini.PlayerId == player.TargetPlayerId)
+ player.NameText.text += suffix;
+ }
+
+ if (Morphling.morphling != null && Morphling.morphTarget == Mini.mini && Morphling.morphTimer > 0f)
+ Morphling.morphling.nameText.text += suffix;
+ }
+
+ static void updateImpostorKillButton(HudManager __instance) {
+ if (!PlayerControl.LocalPlayer.Data.IsImpostor) return;
+ bool enabled = true;
+ if (Vampire.vampire != null && Vampire.vampire == PlayerControl.LocalPlayer)
+ enabled = false;
+ else if (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead)
+ enabled = false;
+ else if (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer)
+ enabled = false;
+ enabled &= __instance.UseButton.isActiveAndEnabled;
+
+ __instance.KillButton.gameObject.SetActive(enabled);
+ __instance.KillButton.renderer.enabled = enabled;
+ __instance.KillButton.isActive = enabled;
+ __instance.KillButton.enabled = enabled;
+ }
+
+ static void Postfix(HudManager __instance)
+ {
+ if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
+
+ CustomButton.HudUpdate();
+ resetNameTagsAndColors();
+ setNameColors();
+ updateShielded();
+ setNameTags();
+
+ // Impostors
+ updateImpostorKillButton(__instance);
+ // Timer updates
+ timerUpdate();
+ // Camouflager and Morphling
+ camouflageAndMorphActions();
+ // Mini
+ miniUpdate();
+ }
+ }
+}
--- /dev/null
+using HarmonyLib;
+using System;
+using Hazel;
+using UnityEngine;
+using System.Linq;
+using static TheOtherRoles.TheOtherRoles;
+using static TheOtherRoles.GameHistory;
+using static TheOtherRoles.MapOptions;
+using System.Collections.Generic;
+
+
+namespace TheOtherRoles.Patches {
+
+ [HarmonyPatch(typeof(Vent), "CanUse")]
+ public static class VentCanUsePatch
+ {
+ public static bool Prefix(Vent __instance, ref float __result, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
+ {
+ float num = float.MaxValue;
+ PlayerControl @object = pc.Object;
+
+
+ bool roleCouldUse = false;
+ if (Engineer.engineer != null && Engineer.engineer == @object)
+ roleCouldUse = true;
+ else if (Jackal.canUseVents && Jackal.jackal != null && Jackal.jackal == @object)
+ roleCouldUse = true;
+ else if (Sidekick.canUseVents && Sidekick.sidekick != null && Sidekick.sidekick == @object)
+ roleCouldUse = true;
+ else if (Spy.canEnterVents && Spy.spy != null && Spy.spy == @object)
+ roleCouldUse = true;
+ else if (pc.IsImpostor) {
+ if (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer)
+ roleCouldUse = false;
+ else if (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead)
+ roleCouldUse = false;
+ else
+ roleCouldUse = true;
+ }
+
+ var usableDistance = __instance.UsableDistance;
+ if (__instance.name.StartsWith("JackInTheBoxVent_")) {
+ if(Trickster.trickster != PlayerControl.LocalPlayer) {
+ // Only the Trickster can use the Jack-In-The-Boxes!
+ canUse = false;
+ couldUse = false;
+ __result = num;
+ return false;
+ } else {
+ // Reduce the usable distance to reduce the risk of gettings stuck while trying to jump into the box if it's placed near objects
+ usableDistance = 0.4f;
+ }
+ } else if (__instance.name.StartsWith("SealedVent_")) {
+ canUse = couldUse = false;
+ __result = num;
+ return false;
+ }
+
+ couldUse = (@object.inVent || roleCouldUse) && !pc.IsDead && (@object.CanMove || @object.inVent);
+ canUse = couldUse;
+ if (canUse)
+ {
+ Vector2 truePosition = @object.GetTruePosition();
+ Vector3 position = __instance.transform.position;
+ num = Vector2.Distance(truePosition, position);
+
+ canUse &= (num <= usableDistance && !PhysicsHelpers.AnythingBetween(truePosition, position, Constants.ShipOnlyMask, false));
+ }
+ __result = num;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(Vent), "Use")]
+ public static class VentUsePatch {
+ public static bool Prefix(Vent __instance) {
+ bool canUse;
+ bool couldUse;
+ __instance.CanUse(PlayerControl.LocalPlayer.Data, out canUse, out couldUse);
+ bool canMoveInVents = true;
+ if (!canUse) return false; // No need to execute the native method as using is disallowed anyways
+ if (Spy.spy == PlayerControl.LocalPlayer) {
+ canMoveInVents = false;
+ }
+ bool isEnter = !PlayerControl.LocalPlayer.inVent;
+
+ if (__instance.name.StartsWith("JackInTheBoxVent_")) {
+ __instance.SetButtons(isEnter && canMoveInVents);
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UseUncheckedVent, Hazel.SendOption.Reliable);
+ writer.WritePacked(__instance.Id);
+ writer.Write(PlayerControl.LocalPlayer.PlayerId);
+ writer.Write(isEnter ? byte.MaxValue : (byte)0);
+ writer.EndMessage();
+ RPCProcedure.useUncheckedVent(__instance.Id, PlayerControl.LocalPlayer.PlayerId, isEnter ? byte.MaxValue : (byte)0);
+ return false;
+ }
+
+ if(isEnter) {
+ PlayerControl.LocalPlayer.MyPhysics.RpcEnterVent(__instance.Id);
+ } else {
+ PlayerControl.LocalPlayer.MyPhysics.RpcExitVent(__instance.Id);
+ }
+ __instance.SetButtons(isEnter && canMoveInVents);
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.SetTarget))]
+ class UseButtonSetTargetPatch {
+ static void Postfix(UseButtonManager __instance) {
+ // Trickster render special vent button
+ if (__instance.currentTarget != null && Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer) {
+ Vent possibleVent = __instance.currentTarget.TryCast<Vent>();
+ if (possibleVent != null && possibleVent.gameObject != null) {
+ var useButton = __instance.currentButtonShown;
+ if (possibleVent.gameObject.name.StartsWith("JackInTheBoxVent_")) {
+ useButton.graphic.sprite = Trickster.getTricksterVentButtonSprite();
+ useButton.text.enabled = false; // clear text;
+ } else {
+ useButton.graphic.sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(ImageNames.VentButton);
+ useButton.text.enabled = false;
+ }
+ }
+ }
+
+ // Jester sabotage
+ if (Jester.canSabotage && Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.CanMove) {
+ var useButton = __instance.currentButtonShown;
+ if (!Jester.jester.Data.IsDead && __instance.currentTarget == null) { // no target, so sabotage
+ useButton.graphic.sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(ImageNames.SabotageButton);
+ useButton.graphic.color = UseButtonManager.EnabledColor;
+ useButton.text.enabled = false;
+ } else {
+ useButton.graphic.sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(ImageNames.UseButton);
+ useButton.text.enabled = false;
+ }
+ }
+
+ // Mafia sabotage button render patch
+ bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer);
+ bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead);
+ if (__instance.currentTarget == null && (blockSabotageJanitor || blockSabotageMafioso)) {
+ var useButton = __instance.currentButtonShown;
+ useButton.graphic.sprite = DestroyableSingleton<TranslationController>.Instance.GetImage(ImageNames.UseButton);
+ useButton.graphic.color = UseButtonManager.DisabledColor;
+ useButton.text.enabled = false;
+ }
+
+ }
+ }
+
+ [HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.DoClick))]
+ class UseButtonDoClickPatch {
+ static bool Prefix(UseButtonManager __instance) {
+ if (__instance.currentTarget != null) return true;
+ // Jester sabotage
+ if (Jester.canSabotage && Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead) {
+ Action<MapBehaviour> action = m => m.ShowInfectedMap() ;
+ DestroyableSingleton<HudManager>.Instance.ShowMap(action);
+ return false;
+ }
+ // Mafia sabotage button click patch
+ bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer);
+ bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.Data.IsDead);
+ if (blockSabotageJanitor || blockSabotageMafioso) return false;
+
+ return true;
+ }
+ }
+
+ [HarmonyPatch(typeof(EmergencyMinigame), nameof(EmergencyMinigame.Update))]
+ class EmergencyMinigameUpdatePatch {
+ static void Postfix(EmergencyMinigame __instance) {
+ var roleCanCallEmergency = true;
+ var statusText = "";
+
+ // Deactivate emergency button for Swapper
+ if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer && !Swapper.canCallEmergency) {
+ roleCanCallEmergency = false;
+ statusText = "The Swapper can't start an emergency meeting";
+ }
+ // Potentially deactivate emergency button for Jester
+ if (Jester.jester != null && Jester.jester == PlayerControl.LocalPlayer && !Jester.canCallEmergency) {
+ roleCanCallEmergency = false;
+ statusText = "The Jester can't start an emergency meeting";
+ }
+
+ if (!roleCanCallEmergency) {
+ __instance.StatusText.text = statusText;
+ __instance.NumberText.text = string.Empty;
+ __instance.ClosedLid.gameObject.SetActive(true);
+ __instance.OpenLid.gameObject.SetActive(false);
+ __instance.ButtonActive = false;
+ return;
+ }
+
+ // Handle max number of meetings
+ if (__instance.state == 1) {
+ int localRemaining = PlayerControl.LocalPlayer.RemainingEmergencies;
+ int teamRemaining = Mathf.Max(0, maxNumberOfMeetings - meetingsCount);
+ int remaining = Mathf.Min(localRemaining, (Mayor.mayor != null && Mayor.mayor == PlayerControl.LocalPlayer) ? 1 : teamRemaining);
+ __instance.NumberText.text = $"{localRemaining.ToString()} and the ship has {teamRemaining.ToString()}";
+ __instance.ButtonActive = remaining > 0;
+ __instance.ClosedLid.gameObject.SetActive(!__instance.ButtonActive);
+ __instance.OpenLid.gameObject.SetActive(__instance.ButtonActive);
+ return;
+ }
+ }
+ }
+
+
+ [HarmonyPatch(typeof(Console), nameof(Console.CanUse))]
+ public static class ConsoleCanUsePatch {
+ public static bool Prefix(ref float __result, Console __instance, [HarmonyArgument(0)] GameData.PlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse) {
+ canUse = couldUse = false;
+ if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer)
+ return !__instance.TaskTypes.Any(x => x == TaskTypes.FixLights || x == TaskTypes.FixComms);
+ if (__instance.AllowImpostor) return true;
+ if (!Helpers.hasFakeTasks(pc.Object)) return true;
+ __result = float.MaxValue;
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(TuneRadioMinigame), nameof(TuneRadioMinigame.Begin))]
+ class CommsMinigameBeginPatch {
+ static void Postfix(TuneRadioMinigame __instance) {
+ // Block Swapper from fixing comms. Still looking for a better way to do this, but deleting the task doesn't seem like a viable option since then the camera, admin table, ... work while comms are out
+ if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer) {
+ __instance.Close();
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(SwitchMinigame), nameof(SwitchMinigame.Begin))]
+ class LightsMinigameBeginPatch {
+ static void Postfix(SwitchMinigame __instance) {
+ // Block Swapper from fixing lights. One could also just delete the PlayerTask, but I wanted to do it the same way as with coms for now.
+ if (Swapper.swapper != null && Swapper.swapper == PlayerControl.LocalPlayer) {
+ __instance.Close();
+ }
+ }
+ }
+
+ [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 {
+
+ 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 {
+ static Dictionary<SystemTypes, List<Color>> players = new Dictionary<SystemTypes, List<Color>>();
+
+ [HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))]
+ class MapCountOverlayUpdatePatch {
+ static bool Prefix(MapCountOverlay __instance) {
+ // Save colors for the Hacker
+ __instance.timer += Time.deltaTime;
+ if (__instance.timer < 0.1f)
+ {
+ return false;
+ }
+ __instance.timer = 0f;
+ players = new Dictionary<SystemTypes, List<Color>>();
+ bool commsActive = false;
+ foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
+ if (task.TaskType == TaskTypes.FixComms) commsActive = true;
+
+
+ if (!__instance.isSab && commsActive)
+ {
+ __instance.isSab = true;
+ __instance.BackgroundColor.SetColor(Palette.DisabledGrey);
+ __instance.SabotageText.gameObject.SetActive(true);
+ return false;
+ }
+ if (__instance.isSab && !commsActive)
+ {
+ __instance.isSab = false;
+ __instance.BackgroundColor.SetColor(Color.green);
+ __instance.SabotageText.gameObject.SetActive(false);
+ }
+
+ for (int i = 0; i < __instance.CountAreas.Length; i++)
+ {
+ CounterArea counterArea = __instance.CountAreas[i];
+ List<Color> roomColors = new List<Color>();
+ players.Add(counterArea.RoomType, roomColors);
+
+ if (!commsActive)
+ {
+ PlainShipRoom plainShipRoom = ShipStatus.Instance.FastRooms[counterArea.RoomType];
+
+ if (plainShipRoom != null && plainShipRoom.roomArea)
+ {
+ int num = plainShipRoom.roomArea.OverlapCollider(__instance.filter, __instance.buffer);
+ int num2 = num;
+ for (int j = 0; j < num; j++)
+ {
+ Collider2D collider2D = __instance.buffer[j];
+ if (!(collider2D.tag == "DeadBody"))
+ {
+ PlayerControl component = collider2D.GetComponent<PlayerControl>();
+ if (!component || component.Data == null || component.Data.Disconnected || component.Data.IsDead)
+ {
+ num2--;
+ } else if (component?.myRend?.material != null) {
+ Color color = component.myRend.material.GetColor("_BodyColor");
+ if (Hacker.onlyColorType) {
+ var id = Mathf.Max(0, Palette.PlayerColors.IndexOf(color));
+ color = Helpers.isLighterColor((byte)id) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
+ }
+ roomColors.Add(color);
+ }
+ } else {
+ DeadBody component = collider2D.GetComponent<DeadBody>();
+ if (component) {
+ GameData.PlayerInfo playerInfo = GameData.Instance.GetPlayerById(component.ParentId);
+ if (playerInfo != null) {
+ var color = Palette.PlayerColors[playerInfo.ColorId];
+ if (Hacker.onlyColorType)
+ color = Helpers.isLighterColor(playerInfo.ColorId) ? Palette.PlayerColors[7] : Palette.PlayerColors[6];
+ roomColors.Add(color);
+ }
+ }
+ }
+ }
+ counterArea.UpdateCount(num2);
+ }
+ else
+ {
+ Debug.LogWarning("Couldn't find counter for:" + counterArea.RoomType);
+ }
+ }
+ else
+ {
+ counterArea.UpdateCount(0);
+ }
+ }
+ return false;
+ }
+ }
+
+ [HarmonyPatch(typeof(CounterArea), nameof(CounterArea.UpdateCount))]
+ class CounterAreaUpdateCountPatch {
+ private static Material defaultMat;
+ private static Material newMat;
+ static void Postfix(CounterArea __instance) {
+ // Hacker display saved colors on the admin panel
+ bool showHackerInfo = Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && Hacker.hackerTimer > 0;
+ if (players.ContainsKey(__instance.RoomType)) {
+ List<Color> colors = players[__instance.RoomType];
+
+ for (int i = 0; i < __instance.myIcons.Count; i++) {
+ PoolableBehavior icon = __instance.myIcons[i];
+ SpriteRenderer renderer = icon.GetComponent<SpriteRenderer>();
+
+ if (renderer != null) {
+ if (defaultMat == null) defaultMat = renderer.material;
+ if (newMat == null) newMat = UnityEngine.Object.Instantiate<Material>(defaultMat);
+ if (showHackerInfo && colors.Count > i) {
+ renderer.material = newMat;
+ var color = colors[i];
+ renderer.material.SetColor("_BodyColor", color);
+ var id = Palette.PlayerColors.IndexOf(color);
+ if (id < 0) {
+ renderer.material.SetColor("_BackColor", color);
+ } else {
+ renderer.material.SetColor("_BackColor", Palette.ShadowColors[id]);
+ }
+ renderer.material.SetColor("_VisorColor", Palette.VisorColor);
+ } else {
+ renderer.material = defaultMat;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch]
+ class SurveillanceMinigamePatch {
+ private static int page = 0;
+ private static float timer = 0f;
+ [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Begin))]
+ class SurveillanceMinigameBeginPatch {
+ public static void Postfix(SurveillanceMinigame __instance) {
+ // Add securityGuard cameras
+ page = 0;
+ timer = 0;
+ if (ShipStatus.Instance.AllCameras.Length > 4 && __instance.FilteredRooms.Length > 0) {
+ __instance.textures = __instance.textures.ToList().Concat(new RenderTexture[ShipStatus.Instance.AllCameras.Length - 4]).ToArray();
+ for (int i = 4; i < ShipStatus.Instance.AllCameras.Length; i++) {
+ SurvCamera surv = ShipStatus.Instance.AllCameras[i];
+ Camera camera = UnityEngine.Object.Instantiate<Camera>(__instance.CameraPrefab);
+ camera.transform.SetParent(__instance.transform);
+ camera.transform.position = new Vector3(surv.transform.position.x, surv.transform.position.y, 8f);
+ camera.orthographicSize = 2.35f;
+ RenderTexture temporary = RenderTexture.GetTemporary(256, 256, 16, (RenderTextureFormat)0);
+ __instance.textures[i] = temporary;
+ camera.targetTexture = temporary;
+ }
+ }
+ }
+ }
+
+ [HarmonyPatch(typeof(SurveillanceMinigame), nameof(SurveillanceMinigame.Update))]
+ class SurveillanceMinigameUpdatePatch {
+
+ public static bool Prefix(SurveillanceMinigame __instance) {
+ // Update normal and securityGuard cameras
+ timer += Time.deltaTime;
+ int numberOfPages = Mathf.CeilToInt(ShipStatus.Instance.AllCameras.Length / 4f);
+
+ bool update = false;
+
+ if (timer > 3f || Input.GetKeyDown(KeyCode.RightArrow)) {
+ update = true;
+ timer = 0f;
+ page = (page + 1) % numberOfPages;
+ } else if (Input.GetKeyDown(KeyCode.LeftArrow)) {
+ page = (page + numberOfPages - 1) % numberOfPages;
+ update = true;
+ timer = 0f;
+ }
+
+ if ((__instance.isStatic || update) && !PlayerTask.PlayerHasTaskOfType<IHudOverrideTask>(PlayerControl.LocalPlayer)) {
+ __instance.isStatic = false;
+ for (int i = 0; i < __instance.ViewPorts.Length; i++) {
+ __instance.ViewPorts[i].sharedMaterial = __instance.DefaultMaterial;
+ __instance.SabText[i].gameObject.SetActive(false);
+ if (page * 4 + i < __instance.textures.Length)
+ __instance.ViewPorts[i].material.SetTexture("_MainTex", __instance.textures[page * 4 + i]);
+ else
+ __instance.ViewPorts[i].sharedMaterial = __instance.StaticMaterial;
+ }
+ } else if (!__instance.isStatic && PlayerTask.PlayerHasTaskOfType<HudOverrideTask>(PlayerControl.LocalPlayer)) {
+ __instance.isStatic = true;
+ for (int j = 0; j < __instance.ViewPorts.Length; j++) {
+ __instance.ViewPorts[j].sharedMaterial = __instance.StaticMaterial;
+ __instance.SabText[j].gameObject.SetActive(true);
+ }
+ }
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+using HarmonyLib;
+using Hazel;
+using static TheOtherRoles.TheOtherRoles;
+using static TheOtherRoles.HudManagerStartPatch;
+using static TheOtherRoles.GameHistory;
+using static TheOtherRoles.MapOptions;
+using TheOtherRoles.Objects;
+using TheOtherRoles.Patches;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using System;
+
+namespace TheOtherRoles
+{
+ enum RoleId {
+ Jester,
+ Mayor,
+ Engineer,
+ Sheriff,
+ Lighter,
+ Godfather,
+ Mafioso,
+ Janitor,
+ Detective,
+ TimeMaster,
+ Medic,
+ Shifter,
+ Swapper,
+ Lover,
+ Seer,
+ Morphling,
+ Camouflager,
+ Hacker,
+ Mini,
+ Tracker,
+ Vampire,
+ Snitch,
+ Jackal,
+ Sidekick,
+ Eraser,
+ Spy,
+ Trickster,
+ Cleaner,
+ Warlock,
+ SecurityGuard,
+ Arsonist,
+ Guesser,
+ BountyHunter,
+ Crewmate,
+ Impostor
+ }
+
+ enum CustomRPC
+ {
+ // Main Controls
+
+ ResetVaribles = 50,
+ ShareOptionSelection,
+ ForceEnd,
+ SetRole,
+ VersionHandshake,
+ UseUncheckedVent,
+ UncheckedMurderPlayer,
+ // Role functionality
+
+ EngineerFixLights = 81,
+ EngineerUsedRepair,
+ CleanBody,
+ SheriffKill,
+ MedicSetShielded,
+ ShieldedMurderAttempt,
+ TimeMasterShield,
+ TimeMasterRewindTime,
+ ShifterShift,
+ SwapperSwap,
+ MorphlingMorph,
+ CamouflagerCamouflage,
+ TrackerUsedTracker,
+ VampireSetBitten,
+ VampireTryKill,
+ PlaceGarlic,
+ JackalKill,
+ SidekickKill,
+ JackalCreatesSidekick,
+ SidekickPromotes,
+ ErasePlayerRoles,
+ SetFutureErased,
+ SetFutureShifted,
+ PlaceJackInTheBox,
+ LightsOut,
+ WarlockCurseKill,
+ PlaceCamera,
+ SealVent,
+ ArsonistWin,
+ GuesserShoot
+ }
+
+ public static class RPCProcedure {
+
+ // Main Controls
+
+ public static void resetVariables() {
+ Garlic.clearGarlics();
+ JackInTheBox.clearJackInTheBoxes();
+ clearAndReloadMapOptions();
+ clearAndReloadRoles();
+ clearGameHistory();
+ setCustomButtonCooldowns();
+ }
+
+ public static void shareOptionSelection(uint id, uint selection) {
+ CustomOption option = CustomOption.options.FirstOrDefault(option => option.id == (int)id);
+ option.updateSelection((int)selection);
+ }
+
+ public static void forceEnd() {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ {
+ if (!player.Data.IsImpostor)
+ {
+ player.RemoveInfected();
+ player.MurderPlayer(player);
+ player.Data.IsDead = true;
+ }
+ }
+ }
+
+ public static void setRole(byte roleId, byte playerId, byte flag) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ if (player.PlayerId == playerId) {
+ switch((RoleId)roleId) {
+ case RoleId.Jester:
+ Jester.jester = player;
+ break;
+ case RoleId.Mayor:
+ Mayor.mayor = player;
+ break;
+ case RoleId.Engineer:
+ Engineer.engineer = player;
+ break;
+ case RoleId.Sheriff:
+ Sheriff.sheriff = player;
+ break;
+ case RoleId.Lighter:
+ Lighter.lighter = player;
+ break;
+ case RoleId.Godfather:
+ Godfather.godfather = player;
+ break;
+ case RoleId.Mafioso:
+ Mafioso.mafioso = player;
+ break;
+ case RoleId.Janitor:
+ Janitor.janitor = player;
+ break;
+ case RoleId.Detective:
+ Detective.detective = player;
+ break;
+ case RoleId.TimeMaster:
+ TimeMaster.timeMaster = player;
+ break;
+ case RoleId.Medic:
+ Medic.medic = player;
+ break;
+ case RoleId.Shifter:
+ Shifter.shifter = player;
+ break;
+ case RoleId.Swapper:
+ Swapper.swapper = player;
+ break;
+ case RoleId.Lover:
+ if (flag == 0) Lovers.lover1 = player;
+ else Lovers.lover2 = player;
+ break;
+ case RoleId.Seer:
+ Seer.seer = player;
+ break;
+ case RoleId.Morphling:
+ Morphling.morphling = player;
+ break;
+ case RoleId.Camouflager:
+ Camouflager.camouflager = player;
+ break;
+ case RoleId.Hacker:
+ Hacker.hacker = player;
+ break;
+ case RoleId.Mini:
+ Mini.mini = player;
+ break;
+ case RoleId.Tracker:
+ Tracker.tracker = player;
+ break;
+ case RoleId.Vampire:
+ Vampire.vampire = player;
+ break;
+ case RoleId.Snitch:
+ Snitch.snitch = player;
+ break;
+ case RoleId.Jackal:
+ Jackal.jackal = player;
+ break;
+ case RoleId.Sidekick:
+ Sidekick.sidekick = player;
+ break;
+ case RoleId.Eraser:
+ Eraser.eraser = player;
+ break;
+ case RoleId.Spy:
+ Spy.spy = player;
+ break;
+ case RoleId.Trickster:
+ Trickster.trickster = player;
+ break;
+ case RoleId.Cleaner:
+ Cleaner.cleaner = player;
+ break;
+ case RoleId.Warlock:
+ Warlock.warlock = player;
+ break;
+ case RoleId.SecurityGuard:
+ SecurityGuard.securityGuard = player;
+ break;
+ case RoleId.Arsonist:
+ Arsonist.arsonist = player;
+ break;
+ case RoleId.Guesser:
+ Guesser.guesser = player;
+ break;
+ case RoleId.BountyHunter:
+ BountyHunter.bountyHunter = player;
+ break;
+ }
+ }
+ }
+
+ public static void versionHandshake(int major, int minor, int build, int revision, Guid guid, int clientId) {
+ System.Version ver;
+ if (revision < 0)
+ ver = new System.Version(major, minor, build);
+ else
+ ver = new System.Version(major, minor, build, revision);
+
+ GameStartManagerPatch.playerVersions[clientId] = new GameStartManagerPatch.PlayerVersion(ver, guid);
+ }
+
+ public static void useUncheckedVent(int ventId, byte playerId, byte isEnter) {
+ PlayerControl player = Helpers.playerById(playerId);
+ if (player == null) return;
+ // Fill dummy MessageReader and call MyPhysics.HandleRpc as the corountines cannot be accessed
+ MessageReader reader = new MessageReader();
+ byte[] bytes = BitConverter.GetBytes(ventId);
+ if (!BitConverter.IsLittleEndian)
+ Array.Reverse(bytes);
+ reader.Buffer = bytes;
+ reader.Length = bytes.Length;
+
+ JackInTheBox.startAnimation(ventId);
+ player.MyPhysics.HandleRpc(isEnter != 0 ? (byte)19 : (byte)20, reader);
+ }
+
+ public static void uncheckedMurderPlayer(byte sourceId, byte targetId) {
+ PlayerControl source = Helpers.playerById(sourceId);
+ PlayerControl target = Helpers.playerById(targetId);
+ if (source != null && target != null) source.MurderPlayer(target);
+ }
+
+ // Role functionality
+
+ public static void engineerFixLights() {
+ SwitchSystem switchSystem = ShipStatus.Instance.Systems[SystemTypes.Electrical].Cast<SwitchSystem>();
+ switchSystem.ActualSwitches = switchSystem.ExpectedSwitches;
+ }
+
+ public static void engineerUsedRepair() {
+ Engineer.usedRepair = true;
+ }
+
+ public static void cleanBody(byte playerId) {
+ DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
+ for (int i = 0; i < array.Length; i++) {
+ if (GameData.Instance.GetPlayerById(array[i].ParentId).PlayerId == playerId)
+ UnityEngine.Object.Destroy(array[i].gameObject);
+ }
+ }
+
+ public static void sheriffKill(byte targetId) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ {
+ if (player.PlayerId == targetId)
+ {
+ Sheriff.sheriff.MurderPlayer(player);
+ return;
+ }
+ }
+ }
+
+ public static void timeMasterRewindTime() {
+ TimeMaster.shieldActive = false; // Shield is no longer active when rewinding
+ if(TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer) {
+ resetTimeMasterButton();
+ }
+ HudManager.Instance.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f);
+ HudManager.Instance.FullScreen.enabled = true;
+ HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.rewindTime / 2, new Action<float>((p) => {
+ if (p == 1f) HudManager.Instance.FullScreen.enabled = false;
+ })));
+
+ if (TimeMaster.timeMaster == null || PlayerControl.LocalPlayer == TimeMaster.timeMaster) return; // Time Master himself does not rewind
+
+ TimeMaster.isRewinding = true;
+
+ if (MapBehaviour.Instance)
+ MapBehaviour.Instance.Close();
+ if (Minigame.Instance)
+ Minigame.Instance.ForceClose();
+ PlayerControl.LocalPlayer.moveable = false;
+ }
+
+ public static void timeMasterShield() {
+ TimeMaster.shieldActive = true;
+ HudManager.Instance.StartCoroutine(Effects.Lerp(TimeMaster.shieldDuration, new Action<float>((p) => {
+ if (p == 1f) TimeMaster.shieldActive = false;
+ })));
+ }
+
+ public static void medicSetShielded(byte shieldedId) {
+ Medic.usedShield = true;
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ if (player.PlayerId == shieldedId)
+ Medic.shielded = player;
+ }
+
+ public static void shieldedMurderAttempt() {
+ if (Medic.shielded != null && Medic.shielded == PlayerControl.LocalPlayer && Medic.showAttemptToShielded && HudManager.Instance?.FullScreen != null) {
+ HudManager.Instance.FullScreen.enabled = true;
+ HudManager.Instance.StartCoroutine(Effects.Lerp(0.5f, new Action<float>((p) => {
+ var renderer = HudManager.Instance.FullScreen;
+ Color c = Palette.ImpostorRed;
+ if (p < 0.5) {
+ if (renderer != null)
+ renderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01(p * 2 * 0.75f));
+ } else {
+ if (renderer != null)
+ renderer.color = new Color(c.r, c.g, c.b, Mathf.Clamp01((1-p) * 2 * 0.75f));
+ }
+ if (p == 1f && renderer != null) renderer.enabled = false;
+ })));
+ }
+ }
+
+ public static void shifterShift(byte targetId) {
+ PlayerControl oldShifter = Shifter.shifter;
+ PlayerControl player = Helpers.playerById(targetId);
+ if (player == null || oldShifter == null) return;
+
+ Shifter.futureShift = null;
+ Shifter.clearAndReload();
+
+ // Suicide (exile) when impostor or impostor variants
+ if (player.Data.IsImpostor || player == Jackal.jackal || player == Sidekick.sidekick || Jackal.formerJackals.Contains(player) || player == Jester.jester || player == Arsonist.arsonist) {
+ oldShifter.Exiled();
+ return;
+ }
+
+ 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;
+ }
+
+ // Shift role
+ if (Mayor.mayor != null && Mayor.mayor == player)
+ Mayor.mayor = oldShifter;
+ if (Engineer.engineer != null && Engineer.engineer == player)
+ Engineer.engineer = oldShifter;
+ if (Sheriff.sheriff != null && Sheriff.sheriff == player)
+ Sheriff.sheriff = oldShifter;
+ if (Lighter.lighter != null && Lighter.lighter == player)
+ Lighter.lighter = oldShifter;
+ if (Detective.detective != null && Detective.detective == player)
+ Detective.detective = oldShifter;
+ if (TimeMaster.timeMaster != null && TimeMaster.timeMaster == player)
+ TimeMaster.timeMaster = oldShifter;
+ if (Medic.medic != null && Medic.medic == player)
+ Medic.medic = oldShifter;
+ if (Swapper.swapper != null && Swapper.swapper == player)
+ Swapper.swapper = oldShifter;
+ if (Seer.seer != null && Seer.seer == player)
+ Seer.seer = oldShifter;
+ if (Hacker.hacker != null && Hacker.hacker == player)
+ Hacker.hacker = oldShifter;
+ if (Mini.mini != null && Mini.mini == player)
+ Mini.mini = oldShifter;
+ if (Tracker.tracker != null && Tracker.tracker == player)
+ Tracker.tracker = oldShifter;
+ if (Snitch.snitch != null && Snitch.snitch == player)
+ Snitch.snitch = oldShifter;
+ if (Spy.spy != null && Spy.spy == player)
+ Spy.spy = oldShifter;
+ if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player)
+ SecurityGuard.securityGuard = oldShifter;
+ if (Guesser.guesser != null && Guesser.guesser == player)
+ Guesser.guesser = oldShifter;
+
+ // Set cooldowns to max for both players
+ if (PlayerControl.LocalPlayer == oldShifter || PlayerControl.LocalPlayer == player)
+ CustomButton.ResetAllCooldowns();
+ }
+
+ public static void swapperSwap(byte playerId1, byte playerId2) {
+ if (MeetingHud.Instance) {
+ Swapper.playerId1 = playerId1;
+ Swapper.playerId2 = playerId2;
+ }
+ }
+
+ public static void morphlingMorph(byte playerId) {
+ PlayerControl target = Helpers.playerById(playerId);
+ if (Morphling.morphling == null || target == null) return;
+
+ Morphling.morphTimer = Morphling.duration;
+ Morphling.morphTarget = target;
+ }
+
+ public static void camouflagerCamouflage() {
+ if (Camouflager.camouflager == null) return;
+
+ Camouflager.camouflageTimer = Camouflager.duration;
+ }
+
+ public static void vampireSetBitten(byte targetId, byte reset) {
+ if (reset != 0) {
+ Vampire.bitten = null;
+ return;
+ }
+
+ if (Vampire.vampire == null) return;
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
+ if (player.PlayerId == targetId && !player.Data.IsDead) {
+ Vampire.bitten = player;
+ }
+ }
+ }
+
+ public static void vampireTryKill() {
+ if (Vampire.bitten != null && !Vampire.bitten.Data.IsDead) {
+ Vampire.vampire.MurderPlayer(Vampire.bitten);
+ }
+ Vampire.bitten = null;
+ }
+
+ public static void placeGarlic(byte[] buff) {
+ Vector3 position = Vector3.zero;
+ position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
+ position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
+ new Garlic(position);
+ }
+
+ public static void trackerUsedTracker(byte targetId) {
+ Tracker.usedTracker = true;
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ if (player.PlayerId == targetId)
+ Tracker.tracked = player;
+ }
+
+ public static void jackalKill(byte targetId) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ {
+ if (player.PlayerId == targetId)
+ {
+ Jackal.jackal.MurderPlayer(player);
+ return;
+ }
+ }
+ }
+
+ public static void sidekickKill(byte targetId) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ {
+ if (player.PlayerId == targetId)
+ {
+ Sidekick.sidekick.MurderPlayer(player);
+ return;
+ }
+ }
+ }
+
+ public static void jackalCreatesSidekick(byte targetId) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ {
+ if (player.PlayerId == targetId)
+ {
+ if (!Jackal.canCreateSidekickFromImpostor && player.Data.IsImpostor) {
+ Jackal.fakeSidekick = player;
+ } else {
+ player.RemoveInfected();
+ erasePlayerRoles(player.PlayerId, true);
+ Sidekick.sidekick = player;
+ }
+ Jackal.canCreateSidekick = false;
+ return;
+ }
+ }
+ }
+
+ public static void sidekickPromotes() {
+ Jackal.removeCurrentJackal();
+ Jackal.jackal = Sidekick.sidekick;
+ Jackal.canCreateSidekick = Jackal.jackalPromotedFromSidekickCanCreateSidekick;
+ Sidekick.clearAndReload();
+ return;
+ }
+
+ public static void erasePlayerRoles(byte playerId, bool ignoreLovers = false) {
+ PlayerControl player = Helpers.playerById(playerId);
+ if (player == null) return;
+
+ // Crewmate roles
+ if (player == Mayor.mayor) Mayor.clearAndReload();
+ if (player == Engineer.engineer) Engineer.clearAndReload();
+ if (player == Sheriff.sheriff) Sheriff.clearAndReload();
+ if (player == Lighter.lighter) Lighter.clearAndReload();
+ if (player == Detective.detective) Detective.clearAndReload();
+ if (player == TimeMaster.timeMaster) TimeMaster.clearAndReload();
+ if (player == Medic.medic) Medic.clearAndReload();
+ if (player == Shifter.shifter) Shifter.clearAndReload();
+ if (player == Seer.seer) Seer.clearAndReload();
+ if (player == Hacker.hacker) Hacker.clearAndReload();
+ if (player == Mini.mini) Mini.clearAndReload();
+ if (player == Tracker.tracker) Tracker.clearAndReload();
+ if (player == Snitch.snitch) Snitch.clearAndReload();
+ if (player == Swapper.swapper) Swapper.clearAndReload();
+ if (player == Spy.spy) Spy.clearAndReload();
+ if (player == SecurityGuard.securityGuard) SecurityGuard.clearAndReload();
+
+ // Impostor roles
+ if (player == Morphling.morphling) Morphling.clearAndReload();
+ if (player == Camouflager.camouflager) Camouflager.clearAndReload();
+ if (player == Godfather.godfather) Godfather.clearAndReload();
+ if (player == Mafioso.mafioso) Mafioso.clearAndReload();
+ if (player == Janitor.janitor) Janitor.clearAndReload();
+ if (player == Vampire.vampire) Vampire.clearAndReload();
+ if (player == Eraser.eraser) Eraser.clearAndReload();
+ if (player == Trickster.trickster) Trickster.clearAndReload();
+ if (player == Cleaner.cleaner) Cleaner.clearAndReload();
+ if (player == Warlock.warlock) Warlock.clearAndReload();
+
+ // Other roles
+ if (player == Jester.jester) Jester.clearAndReload();
+ if (player == Arsonist.arsonist) Arsonist.clearAndReload();
+ if (player == Guesser.guesser) Guesser.clearAndReload();
+ if (!ignoreLovers && (player == Lovers.lover1 || player == Lovers.lover2)) { // The whole Lover couple is being erased
+ Lovers.clearAndReload();
+ }
+ if (player == Jackal.jackal) { // Promote Sidekick and hence override the the Jackal or erase Jackal
+ if (Sidekick.promotesToJackal && Sidekick.sidekick != null && !Sidekick.sidekick.Data.IsDead) {
+ RPCProcedure.sidekickPromotes();
+ } else {
+ Jackal.clearAndReload();
+ }
+ }
+ 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);
+ }
+ }
+
+ public static void setFutureShifted(byte playerId) {
+ Shifter.futureShift = Helpers.playerById(playerId);
+ }
+
+ public static void placeJackInTheBox(byte[] buff) {
+ Vector3 position = Vector3.zero;
+ position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
+ position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
+ new JackInTheBox(position);
+ }
+
+ public static void lightsOut() {
+ Trickster.lightsOutTimer = Trickster.lightsOutDuration;
+ // If the local player is impostor indicate lights out
+ if(PlayerControl.LocalPlayer.Data.IsImpostor) {
+ new CustomMessage("Lights are out", Trickster.lightsOutDuration);
+ }
+ }
+
+ public static void warlockCurseKill(byte targetId) {
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
+ if (player.PlayerId == targetId) {
+ Warlock.curseKillTarget = player;
+ Warlock.warlock.MurderPlayer(player);
+ return;
+ }
+ }
+ }
+
+ public static void placeCamera(byte[] buff) {
+ var referenceCamera = UnityEngine.Object.FindObjectOfType<SurvCamera>();
+ if (referenceCamera == null) return; // Mira HQ
+
+ SecurityGuard.remainingScrews -= SecurityGuard.camPrice;
+ SecurityGuard.placedCameras++;
+
+ Vector3 position = Vector3.zero;
+ position.x = BitConverter.ToSingle(buff, 0*sizeof(float));
+ position.y = BitConverter.ToSingle(buff, 1*sizeof(float));
+
+ var camera = UnityEngine.Object.Instantiate<SurvCamera>(referenceCamera);
+ camera.transform.position = new Vector3(position.x, position.y, referenceCamera.transform.position.z - 1f);
+ camera.CamName = $"Security Camera {SecurityGuard.placedCameras}";
+ camera.Offset = new Vector3(0f, 0f, camera.Offset.z);
+ if (PlayerControl.GameOptions.MapId == 2 || PlayerControl.GameOptions.MapId == 4) camera.transform.localRotation = new Quaternion(0, 0, 1, 1); // Polus and Airship
+
+ if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) {
+ camera.gameObject.SetActive(true);
+ camera.gameObject.GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 0.5f);
+ } else {
+ camera.gameObject.SetActive(false);
+ }
+ MapOptions.camerasToAdd.Add(camera);
+ }
+
+ public static void sealVent(int ventId) {
+ Vent vent = ShipStatus.Instance.AllVents.FirstOrDefault((x) => x != null && x.Id == ventId);
+ if (vent == null) return;
+
+ SecurityGuard.remainingScrews -= SecurityGuard.ventPrice;
+ if (PlayerControl.LocalPlayer == SecurityGuard.securityGuard) {
+ PowerTools.SpriteAnim animator = vent.GetComponent<PowerTools.SpriteAnim>();
+ animator?.Stop();
+ vent.EnterVentAnim = vent.ExitVentAnim = null;
+ vent.myRend.sprite = animator == null ? SecurityGuard.getStaticVentSealedSprite() : SecurityGuard.getAnimatedVentSealedSprite();
+ vent.myRend.color = new Color(1f, 1f, 1f, 0.5f);
+ vent.name = "FutureSealedVent_" + vent.name;
+ }
+
+ MapOptions.ventsToSeal.Add(vent);
+ }
+
+ public static void arsonistWin() {
+ Arsonist.triggerArsonistWin = true;
+ }
+
+ public static void guesserShoot(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.TargetPlayerId == partnerId) {
+ pva.SetDead(pva.DidReport, true);
+ pva.Overlay.gameObject.SetActive(true);
+ }
+ }
+ if (AmongUsClient.Instance.AmHost)
+ MeetingHud.Instance.CheckForEndVoting();
+ }
+ 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
+ {
+ static void Postfix([HarmonyArgument(0)]byte callId, [HarmonyArgument(1)]MessageReader reader)
+ {
+ byte packetId = callId;
+ switch (packetId) {
+
+ // Main Controls
+
+ case (byte)CustomRPC.ResetVaribles:
+ RPCProcedure.resetVariables();
+ break;
+ case (byte)CustomRPC.ShareOptionSelection:
+ uint id = reader.ReadPackedUInt32();
+ uint selection = reader.ReadPackedUInt32();
+ RPCProcedure.shareOptionSelection(id, selection);
+ break;
+ case (byte)CustomRPC.ForceEnd:
+ RPCProcedure.forceEnd();
+ break;
+ case (byte)CustomRPC.SetRole:
+ byte roleId = reader.ReadByte();
+ byte playerId = reader.ReadByte();
+ byte flag = reader.ReadByte();
+ RPCProcedure.setRole(roleId, playerId, flag);
+ break;
+ case (byte)CustomRPC.VersionHandshake:
+ byte major = reader.ReadByte();
+ byte minor = reader.ReadByte();
+ byte patch = reader.ReadByte();
+ int versionOwnerId = reader.ReadPackedInt32();
+ byte revision = 0xFF;
+ Guid guid;
+ if (reader.Length - reader.Position >= 17) { // enough bytes left to read
+ revision = reader.ReadByte();
+ // GUID
+ byte[] gbytes = reader.ReadBytes(16);
+ guid = new Guid(gbytes);
+ } else {
+ guid = new Guid(new byte[16]);
+ }
+ RPCProcedure.versionHandshake(major, minor, patch, revision == 0xFF ? -1 : revision, guid, versionOwnerId);
+ break;
+ case (byte)CustomRPC.UseUncheckedVent:
+ int ventId = reader.ReadPackedInt32();
+ byte ventingPlayer = reader.ReadByte();
+ byte isEnter = reader.ReadByte();
+ RPCProcedure.useUncheckedVent(ventId, ventingPlayer, isEnter);
+ break;
+ case (byte)CustomRPC.UncheckedMurderPlayer:
+ byte source = reader.ReadByte();
+ byte target = reader.ReadByte();
+ RPCProcedure.uncheckedMurderPlayer(source, target);
+ break;
+
+ // Role functionality
+
+ case (byte)CustomRPC.EngineerFixLights:
+ RPCProcedure.engineerFixLights();
+ break;
+ case (byte)CustomRPC.EngineerUsedRepair:
+ RPCProcedure.engineerUsedRepair();
+ break;
+ case (byte)CustomRPC.CleanBody:
+ RPCProcedure.cleanBody(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.SheriffKill:
+ RPCProcedure.sheriffKill(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.TimeMasterRewindTime:
+ RPCProcedure.timeMasterRewindTime();
+ break;
+ case (byte)CustomRPC.TimeMasterShield:
+ RPCProcedure.timeMasterShield();
+ break;
+ case (byte)CustomRPC.MedicSetShielded:
+ RPCProcedure.medicSetShielded(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.ShieldedMurderAttempt:
+ RPCProcedure.shieldedMurderAttempt();
+ break;
+ case (byte)CustomRPC.ShifterShift:
+ RPCProcedure.shifterShift(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.SwapperSwap:
+ byte playerId1 = reader.ReadByte();
+ byte playerId2 = reader.ReadByte();
+ RPCProcedure.swapperSwap(playerId1, playerId2);
+ break;
+ case (byte)CustomRPC.MorphlingMorph:
+ RPCProcedure.morphlingMorph(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.CamouflagerCamouflage:
+ RPCProcedure.camouflagerCamouflage();
+ break;
+ case (byte)CustomRPC.VampireSetBitten:
+ byte bittenId = reader.ReadByte();
+ byte reset = reader.ReadByte();
+ RPCProcedure.vampireSetBitten(bittenId, reset);
+ break;
+ case (byte)CustomRPC.VampireTryKill:
+ RPCProcedure.vampireTryKill();
+ break;
+ case (byte)CustomRPC.PlaceGarlic:
+ RPCProcedure.placeGarlic(reader.ReadBytesAndSize());
+ break;
+ case (byte)CustomRPC.TrackerUsedTracker:
+ RPCProcedure.trackerUsedTracker(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.JackalKill:
+ RPCProcedure.jackalKill(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.SidekickKill:
+ RPCProcedure.sidekickKill(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.JackalCreatesSidekick:
+ RPCProcedure.jackalCreatesSidekick(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.SidekickPromotes:
+ RPCProcedure.sidekickPromotes();
+ break;
+ case (byte)CustomRPC.ErasePlayerRoles:
+ RPCProcedure.erasePlayerRoles(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.SetFutureErased:
+ RPCProcedure.setFutureErased(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.SetFutureShifted:
+ RPCProcedure.setFutureShifted(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.PlaceJackInTheBox:
+ RPCProcedure.placeJackInTheBox(reader.ReadBytesAndSize());
+ break;
+ case (byte)CustomRPC.LightsOut:
+ RPCProcedure.lightsOut();
+ break;
+ case (byte)CustomRPC.WarlockCurseKill:
+ RPCProcedure.warlockCurseKill(reader.ReadByte());
+ break;
+ case (byte)CustomRPC.PlaceCamera:
+ RPCProcedure.placeCamera(reader.ReadBytesAndSize());
+ break;
+ case (byte)CustomRPC.SealVent:
+ RPCProcedure.sealVent(reader.ReadPackedInt32());
+ break;
+ case (byte)CustomRPC.ArsonistWin:
+ RPCProcedure.arsonistWin();
+ break;
+ case (byte)CustomRPC.GuesserShoot:
+ RPCProcedure.guesserShoot(reader.ReadByte());
+ break;
+ }
+ }
+ }
+}
--- /dev/null
+using HarmonyLib;
+using System.Linq;
+using System;
+using System.Collections.Generic;
+using static TheOtherRoles.TheOtherRoles;
+using UnityEngine;
+
+namespace TheOtherRoles
+{
+ class RoleInfo {
+ public Color color;
+ public string name;
+ public string introDescription;
+ public string shortDescription;
+ public RoleId roleId;
+
+ RoleInfo(string name, Color color, string introDescription, string shortDescription, RoleId roleId) {
+ this.color = color;
+ this.name = name;
+ this.introDescription = introDescription;
+ this.shortDescription = shortDescription;
+ this.roleId = roleId;
+ }
+
+ public static RoleInfo jester = new RoleInfo("Jester", Jester.color, "Get voted out", "Get voted out", RoleId.Jester);
+ public static RoleInfo mayor = new RoleInfo("Mayor", Mayor.color, "Your vote counts twice", "Your vote counts twice", RoleId.Mayor);
+ public static RoleInfo engineer = new RoleInfo("Engineer", Engineer.color, "Maintain important systems on the ship", "Repair the ship", RoleId.Engineer);
+ public static RoleInfo sheriff = new RoleInfo("Sheriff", Sheriff.color, "Shoot the <color=#FF1919FF>Impostors</color>", "Shoot the Impostors", RoleId.Sheriff);
+ public static RoleInfo lighter = new RoleInfo("Lighter", Lighter.color, "Your light never goes out", "Your light never goes out", RoleId.Lighter);
+ public static RoleInfo godfather = new RoleInfo("Godfather", Godfather.color, "Kill all Crewmates", "Kill all Crewmates", RoleId.Godfather);
+ public static RoleInfo mafioso = new RoleInfo("Mafioso", Mafioso.color, "Work with the <color=#FF1919FF>Mafia</color> to kill the Crewmates", "Kill all Crewmates", RoleId.Mafioso);
+ public static RoleInfo janitor = new RoleInfo("Janitor", Janitor.color, "Work with the <color=#FF1919FF>Mafia</color> by hiding dead bodies", "Hide dead bodies", RoleId.Janitor);
+ public static RoleInfo morphling = new RoleInfo("Morphling", Morphling.color, "Change your look to not get caught", "Change your look", RoleId.Morphling);
+ public static RoleInfo camouflager = new RoleInfo("Camouflager", Camouflager.color, "Camouflage and kill the Crewmates", "Hide among others", RoleId.Camouflager);
+ public static RoleInfo vampire = new RoleInfo("Vampire", Vampire.color, "Kill the Crewmates with your bites", "Bite your enemies", RoleId.Vampire);
+ public static RoleInfo eraser = new RoleInfo("Eraser", Eraser.color, "Kill the Crewmates and erase their roles", "Erase the roles of your enemies", RoleId.Eraser);
+ 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);
+ public static RoleInfo shifter = new RoleInfo("Shifter", Shifter.color, "Shift your role", "Shift your role", RoleId.Shifter);
+ public static RoleInfo swapper = new RoleInfo("Swapper", Swapper.color, "Swap votes to exile the <color=#FF1919FF>Impostors</color>", "Swap votes", RoleId.Swapper);
+ public static RoleInfo seer = new RoleInfo("Seer", Seer.color, "You will see players die", "You will see players die", RoleId.Seer);
+ public static RoleInfo hacker = new RoleInfo("Hacker", Hacker.color, "Hack systems to find the <color=#FF1919FF>Impostors</color>", "Hack to find the Impostors", RoleId.Hacker);
+ public static RoleInfo niceMini = new RoleInfo("Nice Mini", Mini.color, "No one will harm you until you grow up", "No one will harm you", RoleId.Mini);
+ public static RoleInfo evilMini = new RoleInfo("Evil Mini", Palette.ImpostorRed, "No one will harm you until you grow up", "No one will harm you", RoleId.Mini);
+ public static RoleInfo tracker = new RoleInfo("Tracker", Tracker.color, "Track the <color=#FF1919FF>Impostors</color> down", "Track the Impostors down", RoleId.Tracker);
+ public static RoleInfo snitch = new RoleInfo("Snitch", Snitch.color, "Finish your tasks to find the <color=#FF1919FF>Impostors</color>", "Finish your tasks", RoleId.Snitch);
+ public static RoleInfo jackal = new RoleInfo("Jackal", Jackal.color, "Kill all Crewmates and <color=#FF1919FF>Impostors</color> to win", "Kill everyone", RoleId.Jackal);
+ public static RoleInfo sidekick = new RoleInfo("Sidekick", Sidekick.color, "Help your Jackal to kill everyone", "Help your Jackal to kill everyone", RoleId.Sidekick);
+ public static RoleInfo spy = new RoleInfo("Spy", Spy.color, "Confuse the <color=#FF1919FF>Impostors</color>", "Confuse the Impostors", RoleId.Spy);
+ public static RoleInfo securityGuard = new RoleInfo("Security Guard", SecurityGuard.color, "Seal vents and place cameras", "Seal vents and place cameras", RoleId.SecurityGuard);
+ public static RoleInfo arsonist = new RoleInfo("Arsonist", Arsonist.color, "Let them burn", "Let them burn", RoleId.Arsonist);
+ public static RoleInfo goodGuesser = new RoleInfo("Nice Guesser", Guesser.color, "Guess and shoot", "Guess and shoot", RoleId.Guesser);
+ public static RoleInfo badGuesser = new RoleInfo("Evil Guesser", Palette.ImpostorRed, "Guess and shoot", "Guess and shoot", RoleId.Guesser);
+ public static RoleInfo impostor = new RoleInfo("Impostor", Palette.ImpostorRed, Helpers.cs(Palette.ImpostorRed, "Sabotage and kill everyone"), "Sabotage and kill everyone", RoleId.Impostor);
+ public static RoleInfo crewmate = new RoleInfo("Crewmate", Color.white, "Find the Impostors", "Find the Impostors", RoleId.Crewmate);
+ public static RoleInfo lover = new RoleInfo("Lover", Lovers.color, $"You are in love", $"You are in love", RoleId.Lover);
+
+ public static List<RoleInfo> allRoleInfos = new List<RoleInfo>() {
+ impostor,
+ godfather,
+ mafioso,
+ janitor,
+ morphling,
+ camouflager,
+ vampire,
+ eraser,
+ trickster,
+ cleaner,
+ warlock,
+ bountyHunter,
+ niceMini,
+ evilMini,
+ goodGuesser,
+ badGuesser,
+ lover,
+ jester,
+ arsonist,
+ jackal,
+ sidekick,
+ crewmate,
+ shifter,
+ mayor,
+ engineer,
+ sheriff,
+ lighter,
+ detective,
+ timeMaster,
+ medic,
+ swapper,
+ seer,
+ hacker,
+ tracker,
+ snitch,
+ spy,
+ securityGuard,
+ bountyHunter
+ };
+
+ public static List<RoleInfo> getRoleInfoForPlayer(PlayerControl p) {
+ List<RoleInfo> infos = new List<RoleInfo>();
+ if (p == null) return infos;
+
+ // Special roles
+ if (p == Jester.jester) infos.Add(jester);
+ if (p == Mayor.mayor) infos.Add(mayor);
+ if (p == Engineer.engineer) infos.Add(engineer);
+ if (p == Sheriff.sheriff) infos.Add(sheriff);
+ if (p == Lighter.lighter) infos.Add(lighter);
+ if (p == Godfather.godfather) infos.Add(godfather);
+ if (p == Mafioso.mafioso) infos.Add(mafioso);
+ if (p == Janitor.janitor) infos.Add(janitor);
+ if (p == Morphling.morphling) infos.Add(morphling);
+ if (p == Camouflager.camouflager) infos.Add(camouflager);
+ if (p == Vampire.vampire) infos.Add(vampire);
+ if (p == Eraser.eraser) infos.Add(eraser);
+ if (p == Trickster.trickster) infos.Add(trickster);
+ if (p == Cleaner.cleaner) infos.Add(cleaner);
+ if (p == Warlock.warlock) infos.Add(warlock);
+ if (p == Detective.detective) infos.Add(detective);
+ if (p == TimeMaster.timeMaster) infos.Add(timeMaster);
+ if (p == Medic.medic) infos.Add(medic);
+ if (p == Shifter.shifter) infos.Add(shifter);
+ if (p == Swapper.swapper) infos.Add(swapper);
+ if (p == Seer.seer) infos.Add(seer);
+ if (p == Hacker.hacker) infos.Add(hacker);
+ if (p == Mini.mini) infos.Add(p.Data.IsImpostor ? evilMini : niceMini);
+ if (p == Tracker.tracker) infos.Add(tracker);
+ if (p == Snitch.snitch) infos.Add(snitch);
+ if (p == Jackal.jackal || (Jackal.formerJackals != null && Jackal.formerJackals.Any(x => x.PlayerId == p.PlayerId))) infos.Add(jackal);
+ if (p == Sidekick.sidekick) infos.Add(sidekick);
+ if (p == Spy.spy) infos.Add(spy);
+ 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
+ if (infos.Count == 0 && !p.Data.IsImpostor) infos.Add(crewmate); // Just Crewmate
+
+ // Modifier
+ if (p == Lovers.lover1|| p == Lovers.lover2) infos.Add(lover);
+
+ return infos;
+ }
+ }
+}
--- /dev/null
+using HarmonyLib;
+using static TheOtherRoles.TheOtherRoles;
+using System.Collections;
+using System.Collections.Generic;
+using System;
+
+namespace TheOtherRoles {
+ [HarmonyPatch]
+ public static class TasksHandler {
+
+ public static Tuple<int, int> taskInfo(GameData.PlayerInfo playerInfo) {
+ int TotalTasks = 0;
+ int CompletedTasks = 0;
+ if (!playerInfo.Disconnected && playerInfo.Tasks != null &&
+ playerInfo.Object &&
+ (PlayerControl.GameOptions.GhostsDoTasks || !playerInfo.IsDead) &&
+ !playerInfo.IsImpostor &&
+ !playerInfo.Object.hasFakeTasks()
+ ) {
+
+ for (int j = 0; j < playerInfo.Tasks.Count; j++) {
+ TotalTasks++;
+ if (playerInfo.Tasks[j].Complete) {
+ CompletedTasks++;
+ }
+ }
+ }
+ return Tuple.Create(CompletedTasks, TotalTasks);
+ }
+
+ [HarmonyPatch(typeof(GameData), nameof(GameData.RecomputeTaskCounts))]
+ private static class GameDataRecomputeTaskCountsPatch {
+ private static bool Prefix(GameData __instance) {
+ __instance.TotalTasks = 0;
+ __instance.CompletedTasks = 0;
+ for (int i = 0; i < __instance.AllPlayers.Count; i++) {
+ GameData.PlayerInfo playerInfo = __instance.AllPlayers[i];
+ if (playerInfo.Object && playerInfo.Object.hasAliveKillingLover())
+ continue;
+ var (playerCompleted, playerTotal) = taskInfo(playerInfo);
+ __instance.TotalTasks += playerTotal;
+ __instance.CompletedTasks += playerCompleted;
+ }
+ return false;
+ }
+ }
+
+ }
+}
--- /dev/null
+using System.Net;
+using System.Linq;
+using BepInEx;
+using BepInEx.Configuration;
+using BepInEx.IL2CPP;
+using HarmonyLib;
+using Hazel;
+using System;
+using System.Collections.Generic;
+using System.Collections;
+using System.IO;
+using UnityEngine;
+using TheOtherRoles.Objects;
+
+namespace TheOtherRoles
+{
+ [HarmonyPatch]
+ public static class TheOtherRoles
+ {
+ public static System.Random rnd = new System.Random((int)DateTime.Now.Ticks);
+
+ public static void clearAndReloadRoles() {
+ Jester.clearAndReload();
+ Mayor.clearAndReload();
+ Engineer.clearAndReload();
+ Sheriff.clearAndReload();
+ Lighter.clearAndReload();
+ Godfather.clearAndReload();
+ Mafioso.clearAndReload();
+ Janitor.clearAndReload();
+ Detective.clearAndReload();
+ TimeMaster.clearAndReload();
+ Medic.clearAndReload();
+ Shifter.clearAndReload();
+ Swapper.clearAndReload();
+ Lovers.clearAndReload();
+ Seer.clearAndReload();
+ Morphling.clearAndReload();
+ Camouflager.clearAndReload();
+ Hacker.clearAndReload();
+ Mini.clearAndReload();
+ Tracker.clearAndReload();
+ Vampire.clearAndReload();
+ Snitch.clearAndReload();
+ Jackal.clearAndReload();
+ Sidekick.clearAndReload();
+ Eraser.clearAndReload();
+ Spy.clearAndReload();
+ Trickster.clearAndReload();
+ Cleaner.clearAndReload();
+ Warlock.clearAndReload();
+ SecurityGuard.clearAndReload();
+ Arsonist.clearAndReload();
+ Guesser.clearAndReload();
+ BountyHunter.clearAndReload();
+ }
+
+ public static class Jester {
+ public static PlayerControl jester;
+ public static Color color = new Color32(236, 98, 165, byte.MaxValue);
+
+ public static bool triggerJesterWin = false;
+ public static bool canCallEmergency = true;
+ public static bool canSabotage = true;
+
+ public static void clearAndReload() {
+ jester = null;
+ triggerJesterWin = false;
+ canCallEmergency = CustomOptionHolder.jesterCanCallEmergency.getBool();
+ canSabotage = CustomOptionHolder.jesterCanSabotage.getBool();
+ }
+ }
+
+ public static class Mayor {
+ public static PlayerControl mayor;
+ public static Color color = new Color32(32, 77, 66, byte.MaxValue);
+
+ public static void clearAndReload() {
+ mayor = null;
+ }
+ }
+
+ public static class Engineer {
+ public static PlayerControl engineer;
+ public static Color color = new Color32(0, 40, 245, byte.MaxValue);
+ public static bool usedRepair;
+ private static Sprite buttonSprite;
+
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.RepairButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ engineer = null;
+ usedRepair = false;
+ }
+ }
+
+ public static class Godfather {
+ public static PlayerControl godfather;
+ public static Color color = Palette.ImpostorRed;
+
+ public static void clearAndReload() {
+ godfather = null;
+ }
+ }
+
+ public static class Mafioso {
+ public static PlayerControl mafioso;
+ public static Color color = Palette.ImpostorRed;
+
+ public static void clearAndReload() {
+ mafioso = null;
+ }
+ }
+
+
+ public static class Janitor {
+ public static PlayerControl janitor;
+ public static Color color = Palette.ImpostorRed;
+
+ public static float cooldown = 30f;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CleanButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ janitor = null;
+ cooldown = CustomOptionHolder.janitorCooldown.getFloat();
+ }
+ }
+
+ public static class Sheriff {
+ public static PlayerControl sheriff;
+ public static Color color = new Color32(248, 205, 70, byte.MaxValue);
+
+ public static float cooldown = 30f;
+ public static bool canKillNeutrals = false;
+ public static bool spyCanDieToSheriff = false;
+
+ public static PlayerControl currentTarget;
+
+ public static void clearAndReload() {
+ sheriff = null;
+ currentTarget = null;
+ cooldown = CustomOptionHolder.sheriffCooldown.getFloat();
+ canKillNeutrals = CustomOptionHolder.sheriffCanKillNeutrals.getBool();
+ spyCanDieToSheriff = CustomOptionHolder.spyCanDieToSheriff.getBool();
+ }
+ }
+
+ public static class Lighter {
+ public static PlayerControl lighter;
+ public static Color color = new Color32(238, 229, 190, byte.MaxValue);
+
+ public static float lighterModeLightsOnVision = 2f;
+ public static float lighterModeLightsOffVision = 0.75f;
+
+ public static float cooldown = 30f;
+ public static float duration = 5f;
+
+ public static float lighterTimer = 0f;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LighterButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ lighter = null;
+ lighterTimer = 0f;
+ cooldown = CustomOptionHolder.lighterCooldown.getFloat();
+ duration = CustomOptionHolder.lighterDuration.getFloat();
+ lighterModeLightsOnVision = CustomOptionHolder.lighterModeLightsOnVision.getFloat();
+ lighterModeLightsOffVision = CustomOptionHolder.lighterModeLightsOffVision.getFloat();
+ }
+ }
+
+ public static class Detective {
+ public static PlayerControl detective;
+ public static Color color = new Color32(45, 106, 165, byte.MaxValue);
+
+ public static float footprintIntervall = 1f;
+ public static float footprintDuration = 1f;
+ public static bool anonymousFootprints = false;
+ public static float reportNameDuration = 0f;
+ public static float reportColorDuration = 20f;
+ public static float timer = 6.2f;
+
+ public static void clearAndReload() {
+ detective = null;
+ anonymousFootprints = CustomOptionHolder.detectiveAnonymousFootprints.getBool();
+ footprintIntervall = CustomOptionHolder.detectiveFootprintIntervall.getFloat();
+ footprintDuration = CustomOptionHolder.detectiveFootprintDuration.getFloat();
+ reportNameDuration = CustomOptionHolder.detectiveReportNameDuration.getFloat();
+ reportColorDuration = CustomOptionHolder.detectiveReportColorDuration.getFloat();
+ timer = 6.2f;
+ }
+ }
+ }
+
+ public static class TimeMaster {
+ public static PlayerControl timeMaster;
+ public static Color color = new Color32(112, 142, 239, byte.MaxValue);
+
+ public static bool reviveDuringRewind = false;
+ public static float rewindTime = 3f;
+ public static float shieldDuration = 3f;
+ public static float cooldown = 30f;
+
+ public static bool shieldActive = false;
+ public static bool isRewinding = false;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TimeShieldButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ timeMaster = null;
+ isRewinding = false;
+ shieldActive = false;
+ rewindTime = CustomOptionHolder.timeMasterRewindTime.getFloat();
+ shieldDuration = CustomOptionHolder.timeMasterShieldDuration.getFloat();
+ cooldown = CustomOptionHolder.timeMasterCooldown.getFloat();
+ }
+ }
+
+ public static class Medic {
+ public static PlayerControl medic;
+ public static PlayerControl shielded;
+ public static Color color = new Color32(126, 251, 194, byte.MaxValue);
+ public static bool usedShield;
+
+ public static int showShielded = 0;
+ public static bool showAttemptToShielded = false;
+
+ public static Color shieldedColor = new Color32(0, 221, 255, byte.MaxValue);
+ public static PlayerControl currentTarget;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ShieldButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ medic = null;
+ shielded = null;
+ currentTarget = null;
+ usedShield = false;
+ showShielded = CustomOptionHolder.medicShowShielded.getSelection();
+ showAttemptToShielded = CustomOptionHolder.medicShowAttemptToShielded.getBool();
+ }
+ }
+
+ public static class Shifter {
+ public static PlayerControl shifter;
+ public static Color color = new Color32(102, 102, 102, byte.MaxValue);
+
+ public static PlayerControl futureShift;
+ public static PlayerControl currentTarget;
+ public static bool shiftModifiers = false;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.ShiftButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ shifter = null;
+ currentTarget = null;
+ futureShift = null;
+ shiftModifiers = CustomOptionHolder.shifterShiftsModifiers.getBool();
+ }
+ }
+
+ public static class Swapper {
+ public static PlayerControl swapper;
+ public static Color color = new Color32(134, 55, 86, byte.MaxValue);
+ private static Sprite spriteCheck;
+ public static bool canCallEmergency = false;
+ public static bool canOnlySwapOthers = false;
+
+ public static byte playerId1 = Byte.MaxValue;
+ public static byte playerId2 = Byte.MaxValue;
+
+ public static Sprite getCheckSprite() {
+ if (spriteCheck) return spriteCheck;
+ spriteCheck = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SwapperCheck.png", 150f);
+ return spriteCheck;
+ }
+
+ public static void clearAndReload() {
+ swapper = null;
+ playerId1 = Byte.MaxValue;
+ playerId2 = Byte.MaxValue;
+ canCallEmergency = CustomOptionHolder.swapperCanCallEmergency.getBool();
+ canOnlySwapOthers = CustomOptionHolder.swapperCanOnlySwapOthers.getBool();
+ }
+ }
+
+ public static class Lovers {
+ public static PlayerControl lover1;
+ public static PlayerControl lover2;
+ public static Color color = new Color32(232, 57, 185, byte.MaxValue);
+
+ public static bool bothDie = true;
+ // Lovers save if next to be exiled is a lover, because RPC of ending game comes before RPC of exiled
+ public static bool notAckedExiledIsLover = false;
+
+ public static bool existing() {
+ return lover1 != null && lover2 != null && !lover1.Data.Disconnected && !lover2.Data.Disconnected;
+ }
+
+ public static bool existingAndAlive() {
+ return existing() && !lover1.Data.IsDead && !lover2.Data.IsDead && !notAckedExiledIsLover; // ADD NOT ACKED IS LOVER
+ }
+
+ public static bool existingWithKiller() {
+ return existing() && (lover1 == Jackal.jackal || lover2 == Jackal.jackal
+ || lover1 == Sidekick.sidekick || lover2 == Sidekick.sidekick
+ || lover1.Data.IsImpostor || lover2.Data.IsImpostor);
+ }
+
+ public static bool hasAliveKillingLover(this PlayerControl player) {
+ if (!Lovers.existingAndAlive() || !existingWithKiller())
+ return false;
+ return (player != null && (player == lover1 || player == lover2));
+ }
+
+ public static void clearAndReload() {
+ lover1 = null;
+ lover2 = null;
+ 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 seer;
+ public static Color color = new Color32(97, 178, 108, byte.MaxValue);
+ public static List<Vector3> deadBodyPositions = new List<Vector3>();
+
+ public static float soulDuration = 15f;
+ public static bool limitSoulDuration = false;
+ public static int mode = 0;
+
+ private static Sprite soulSprite;
+ public static Sprite getSoulSprite() {
+ if (soulSprite) return soulSprite;
+ soulSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Soul.png", 500f);
+ return soulSprite;
+ }
+
+ public static void clearAndReload() {
+ seer = null;
+ deadBodyPositions = new List<Vector3>();
+ limitSoulDuration = CustomOptionHolder.seerLimitSoulDuration.getBool();
+ soulDuration = CustomOptionHolder.seerSoulDuration.getFloat();
+ mode = CustomOptionHolder.seerMode.getSelection();
+ }
+ }
+
+ public static class Morphling {
+ public static PlayerControl morphling;
+ public static Color color = Palette.ImpostorRed;
+ private static Sprite sampleSprite;
+ private static Sprite morphSprite;
+
+ public static float cooldown = 30f;
+ public static float duration = 10f;
+
+ public static PlayerControl currentTarget;
+ public static PlayerControl sampledTarget;
+ public static PlayerControl morphTarget;
+ public static float morphTimer = 0f;
+
+ public static void resetMorph() {
+ morphTarget = null;
+ morphTimer = 0f;
+ if (morphling == null) return;
+ morphling.SetName(morphling.Data.PlayerName);
+ morphling.SetHat(morphling.Data.HatId, (int)morphling.Data.ColorId);
+ Helpers.setSkinWithAnim(morphling.MyPhysics, morphling.Data.SkinId);
+ morphling.SetPet(morphling.Data.PetId);
+ morphling.CurrentPet.Visible = morphling.Visible;
+ morphling.SetColor(morphling.Data.ColorId);
+ }
+
+ public static void clearAndReload() {
+ resetMorph();
+ morphling = null;
+ currentTarget = null;
+ sampledTarget = null;
+ morphTarget = null;
+ morphTimer = 0f;
+ cooldown = CustomOptionHolder.morphlingCooldown.getFloat();
+ duration = CustomOptionHolder.morphlingDuration.getFloat();
+ }
+
+ public static Sprite getSampleSprite() {
+ if (sampleSprite) return sampleSprite;
+ sampleSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SampleButton.png", 115f);
+ return sampleSprite;
+ }
+
+ public static Sprite getMorphSprite() {
+ if (morphSprite) return morphSprite;
+ morphSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.MorphButton.png", 115f);
+ return morphSprite;
+ }
+ }
+
+ public static class Camouflager {
+ public static PlayerControl camouflager;
+ public static Color color = Palette.ImpostorRed;
+
+ public static float cooldown = 30f;
+ public static float duration = 10f;
+ public static float camouflageTimer = 0f;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CamoButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void resetCamouflage() {
+ camouflageTimer = 0f;
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
+ if (p == null) continue;
+ if (Morphling.morphling == null || Morphling.morphling != p) {
+ p.SetName(p.Data.PlayerName);
+ p.SetHat(p.Data.HatId, (int)p.Data.ColorId);
+ Helpers.setSkinWithAnim(p.MyPhysics, p.Data.SkinId);
+ p.SetPet(p.Data.PetId);
+ p.CurrentPet.Visible = p.Visible;
+ p.SetColor(p.Data.ColorId);
+ }
+ }
+ }
+
+ public static void clearAndReload() {
+ resetCamouflage();
+ camouflager = null;
+ camouflageTimer = 0f;
+ cooldown = CustomOptionHolder.camouflagerCooldown.getFloat();
+ duration = CustomOptionHolder.camouflagerDuration.getFloat();
+ }
+ }
+
+ public static class Hacker {
+ public static PlayerControl hacker;
+ public static Color color = new Color32(117, 250, 76, byte.MaxValue);
+
+ public static float cooldown = 30f;
+ public static float duration = 10f;
+ public static bool onlyColorType = false;
+ public static float hackerTimer = 0f;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.HackerButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ hacker = null;
+ hackerTimer = 0f;
+ cooldown = CustomOptionHolder.hackerCooldown.getFloat();
+ duration = CustomOptionHolder.hackerHackeringDuration.getFloat();
+ onlyColorType = CustomOptionHolder.hackerOnlyColorType.getBool();
+ }
+ }
+
+ public static class Mini {
+ public static PlayerControl mini;
+ public static Color color = Color.white;
+ public const float defaultColliderRadius = 0.2233912f;
+ public const float defaultColliderOffset = 0.3636057f;
+
+ public static float growingUpDuration = 400f;
+ public static DateTime timeOfGrowthStart = DateTime.UtcNow;
+ public static bool triggerMiniLose = false;
+
+ public static void clearAndReload() {
+ mini = null;
+ triggerMiniLose = false;
+ growingUpDuration = CustomOptionHolder.miniGrowingUpDuration.getFloat();
+ timeOfGrowthStart = DateTime.UtcNow;
+ }
+
+ public static float growingProgress() {
+ if (timeOfGrowthStart == null) return 0f;
+
+ float timeSinceStart = (float)(DateTime.UtcNow - timeOfGrowthStart).TotalMilliseconds;
+ return Mathf.Clamp(timeSinceStart/(growingUpDuration*1000), 0f, 1f);
+ }
+
+ public static bool isGrownUp() {
+ return growingProgress() == 1f;
+ }
+ }
+
+ public static class Tracker {
+ public static PlayerControl tracker;
+ public static Color color = new Color32(100, 58, 220, byte.MaxValue);
+
+ public static float updateIntervall = 5f;
+
+ public static PlayerControl currentTarget;
+ public static PlayerControl tracked;
+ public static bool usedTracker = false;
+ public static float timeUntilUpdate = 0f;
+ public static Arrow arrow = new Arrow(Color.blue);
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TrackerButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ tracker = null;
+ currentTarget = null;
+ tracked = null;
+ usedTracker = false;
+ timeUntilUpdate = 0f;
+ updateIntervall = CustomOptionHolder.trackerUpdateIntervall.getFloat();
+ if (arrow?.arrow != null) UnityEngine.Object.Destroy(arrow.arrow);
+ arrow = new Arrow(Color.blue);
+ if (arrow.arrow != null) arrow.arrow.SetActive(false);
+ }
+ }
+
+ public static class Vampire {
+ public static PlayerControl vampire;
+ public static Color color = Palette.ImpostorRed;
+
+ public static float delay = 10f;
+ public static float cooldown = 30f;
+ public static bool canKillNearGarlics = true;
+ public static bool localPlacedGarlic = false;
+ public static bool garlicsActive = true;
+
+ public static PlayerControl currentTarget;
+ public static PlayerControl bitten;
+ public static bool targetNearGarlic = false;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.VampireButton.png", 115f);
+ return buttonSprite;
+ }
+
+ private static Sprite garlicButtonSprite;
+ public static Sprite getGarlicButtonSprite() {
+ if (garlicButtonSprite) return garlicButtonSprite;
+ garlicButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.GarlicButton.png", 115f);
+ return garlicButtonSprite;
+ }
+
+ public static void clearAndReload() {
+ vampire = null;
+ bitten = null;
+ targetNearGarlic = false;
+ localPlacedGarlic = false;
+ currentTarget = null;
+ garlicsActive = CustomOptionHolder.vampireSpawnRate.getSelection() > 0;
+ delay = CustomOptionHolder.vampireKillDelay.getFloat();
+ cooldown = CustomOptionHolder.vampireCooldown.getFloat();
+ canKillNearGarlics = CustomOptionHolder.vampireCanKillNearGarlics.getBool();
+ }
+ }
+
+ public static class Snitch {
+ public static PlayerControl snitch;
+ public static Color color = new Color32(184, 251, 79, byte.MaxValue);
+
+ public static List<Arrow> localArrows = new List<Arrow>();
+ public static int taskCountForImpostors = 1;
+
+ public static void clearAndReload() {
+ if (localArrows != null) {
+ foreach (Arrow arrow in localArrows)
+ if (arrow?.arrow != null)
+ UnityEngine.Object.Destroy(arrow.arrow);
+ }
+ localArrows = new List<Arrow>();
+ taskCountForImpostors = Mathf.RoundToInt(CustomOptionHolder.snitchLeftTasksForImpostors.getFloat());
+ snitch = null;
+ }
+ }
+
+ public static class Jackal {
+ 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 float cooldown = 30f;
+ public static float createSidekickCooldown = 30f;
+ public static bool canUseVents = true;
+ public static bool canCreateSidekick = true;
+ public static Sprite buttonSprite;
+ public static bool jackalPromotedFromSidekickCanCreateSidekick = true;
+ public static bool canCreateSidekickFromImpostor = true;
+ public static bool hasImpostorVision = false;
+
+ public static Sprite getSidekickButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.SidekickButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void removeCurrentJackal() {
+ if (!formerJackals.Any(x => x.PlayerId == jackal.PlayerId)) formerJackals.Add(jackal);
+ jackal = null;
+ currentTarget = null;
+ fakeSidekick = null;
+ cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
+ createSidekickCooldown = CustomOptionHolder.jackalCreateSidekickCooldown.getFloat();
+ }
+
+ public static void clearAndReload() {
+ jackal = null;
+ currentTarget = null;
+ fakeSidekick = null;
+ cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
+ createSidekickCooldown = CustomOptionHolder.jackalCreateSidekickCooldown.getFloat();
+ canUseVents = CustomOptionHolder.jackalCanUseVents.getBool();
+ canCreateSidekick = CustomOptionHolder.jackalCanCreateSidekick.getBool();
+ jackalPromotedFromSidekickCanCreateSidekick = CustomOptionHolder.jackalPromotedFromSidekickCanCreateSidekick.getBool();
+ canCreateSidekickFromImpostor = CustomOptionHolder.jackalCanCreateSidekickFromImpostor.getBool();
+ formerJackals.Clear();
+ hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool();
+ }
+
+ }
+
+ public static class Sidekick {
+ public static PlayerControl sidekick;
+ public static Color color = new Color32(0, 180, 235, byte.MaxValue);
+
+ public static PlayerControl currentTarget;
+
+ public static float cooldown = 30f;
+ public static bool canUseVents = true;
+ public static bool canKill = true;
+ public static bool promotesToJackal = true;
+ public static bool hasImpostorVision = false;
+
+ public static void clearAndReload() {
+ sidekick = null;
+ currentTarget = null;
+ cooldown = CustomOptionHolder.jackalKillCooldown.getFloat();
+ canUseVents = CustomOptionHolder.sidekickCanUseVents.getBool();
+ canKill = CustomOptionHolder.sidekickCanKill.getBool();
+ promotesToJackal = CustomOptionHolder.sidekickPromotesToJackal.getBool();
+ hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool();
+ }
+ }
+
+ public static class Eraser {
+ public static PlayerControl eraser;
+ public static Color color = Palette.ImpostorRed;
+
+ public static List<PlayerControl> futureErased = new List<PlayerControl>();
+ public static PlayerControl currentTarget;
+ public static float cooldown = 30f;
+ public static bool canEraseAnyone = false;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.EraserButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ eraser = null;
+ futureErased = new List<PlayerControl>();
+ currentTarget = null;
+ cooldown = CustomOptionHolder.eraserCooldown.getFloat();
+ canEraseAnyone = CustomOptionHolder.eraserCanEraseAnyone.getBool();
+ }
+ }
+
+ public static class Spy {
+ public static PlayerControl spy;
+ public static Color color = Palette.ImpostorRed;
+
+ public static bool impostorsCanKillAnyone = true;
+ public static bool canEnterVents = false;
+ public static bool hasImpostorVision = false;
+
+ public static void clearAndReload() {
+ spy = null;
+ impostorsCanKillAnyone = CustomOptionHolder.spyImpostorsCanKillAnyone.getBool();
+ canEnterVents = CustomOptionHolder.spyCanEnterVents.getBool();
+ hasImpostorVision = CustomOptionHolder.spyHasImpostorVision.getBool();
+ }
+ }
+
+ public static class Trickster {
+ public static PlayerControl trickster;
+ public static Color color = Palette.ImpostorRed;
+ public static float placeBoxCooldown = 30f;
+ public static float lightsOutCooldown = 30f;
+ public static float lightsOutDuration = 10f;
+ public static float lightsOutTimer = 0f;
+
+ private static Sprite placeBoxButtonSprite;
+ private static Sprite lightOutButtonSprite;
+ private static Sprite tricksterVentButtonSprite;
+
+ public static Sprite getPlaceBoxButtonSprite() {
+ if (placeBoxButtonSprite) return placeBoxButtonSprite;
+ placeBoxButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlaceJackInTheBoxButton.png", 115f);
+ return placeBoxButtonSprite;
+ }
+
+ public static Sprite getLightsOutButtonSprite() {
+ if (lightOutButtonSprite) return lightOutButtonSprite;
+ lightOutButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LightsOutButton.png", 115f);
+ return lightOutButtonSprite;
+ }
+
+ public static Sprite getTricksterVentButtonSprite() {
+ if (tricksterVentButtonSprite) return tricksterVentButtonSprite;
+ tricksterVentButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TricksterVentButton.png", 115f);
+ return tricksterVentButtonSprite;
+ }
+
+ public static void clearAndReload() {
+ trickster = null;
+ lightsOutTimer = 0f;
+ placeBoxCooldown = CustomOptionHolder.tricksterPlaceBoxCooldown.getFloat();
+ lightsOutCooldown = CustomOptionHolder.tricksterLightsOutCooldown.getFloat();
+ lightsOutDuration = CustomOptionHolder.tricksterLightsOutDuration.getFloat();
+ JackInTheBox.UpdateStates(); // if the role is erased, we might have to update the state of the created objects
+ }
+
+ }
+
+ public static class Cleaner {
+ public static PlayerControl cleaner;
+ public static Color color = Palette.ImpostorRed;
+
+ public static float cooldown = 30f;
+
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CleanButton.png", 115f);
+ return buttonSprite;
+ }
+
+ public static void clearAndReload() {
+ cleaner = null;
+ cooldown = CustomOptionHolder.cleanerCooldown.getFloat();
+ }
+ }
+
+ public static class Warlock {
+
+ public static PlayerControl warlock;
+ public static Color color = Palette.ImpostorRed;
+
+ public static PlayerControl currentTarget;
+ public static PlayerControl curseVictim;
+ public static PlayerControl curseVictimTarget;
+ public static PlayerControl curseKillTarget;
+
+ public static float cooldown = 30f;
+ public static float rootTime = 5f;
+
+ private static Sprite curseButtonSprite;
+ private static Sprite curseKillButtonSprite;
+
+ public static Sprite getCurseButtonSprite() {
+ if (curseButtonSprite) return curseButtonSprite;
+ curseButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CurseButton.png", 115f);
+ return curseButtonSprite;
+ }
+
+ public static Sprite getCurseKillButtonSprite() {
+ if (curseKillButtonSprite) return curseKillButtonSprite;
+ curseKillButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CurseKillButton.png", 115f);
+ return curseKillButtonSprite;
+ }
+
+ public static void clearAndReload() {
+ warlock = null;
+ currentTarget = null;
+ curseVictim = null;
+ curseVictimTarget = null;
+ curseKillTarget = null;
+ cooldown = CustomOptionHolder.warlockCooldown.getFloat();
+ rootTime = CustomOptionHolder.warlockRootTime.getFloat();
+ }
+
+ public static void resetCurse() {
+ HudManagerStartPatch.warlockCurseButton.Timer = HudManagerStartPatch.warlockCurseButton.MaxTimer;
+ HudManagerStartPatch.warlockCurseButton.Sprite = Warlock.getCurseButtonSprite();
+ HudManagerStartPatch.warlockCurseButton.killButtonManager.TimerText.color = Palette.EnabledColor;
+ currentTarget = null;
+ curseVictim = null;
+ curseVictimTarget = null;
+ curseKillTarget = null;
+ }
+ }
+
+ public static class SecurityGuard {
+ public static PlayerControl securityGuard;
+ public static Color color = new Color32(195, 178, 95, byte.MaxValue);
+
+ public static float cooldown = 30f;
+ public static int remainingScrews = 7;
+ public static int totalScrews = 7;
+ public static int ventPrice = 1;
+ public static int camPrice = 2;
+ public static int placedCameras = 0;
+ public static Vent ventTarget = null;
+
+ private static Sprite closeVentButtonSprite;
+ public static Sprite getCloseVentButtonSprite() {
+ if (closeVentButtonSprite) return closeVentButtonSprite;
+ closeVentButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.CloseVentButton.png", 115f);
+ return closeVentButtonSprite;
+ }
+
+ private static Sprite placeCameraButtonSprite;
+ public static Sprite getPlaceCameraButtonSprite() {
+ if (placeCameraButtonSprite) return placeCameraButtonSprite;
+ placeCameraButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlaceCameraButton.png", 115f);
+ return placeCameraButtonSprite;
+ }
+
+ private static Sprite animatedVentSealedSprite;
+ public static Sprite getAnimatedVentSealedSprite() {
+ if (animatedVentSealedSprite) return animatedVentSealedSprite;
+ animatedVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.AnimatedVentSealed.png", 160f);
+ return animatedVentSealedSprite;
+ }
+
+ private static Sprite staticVentSealedSprite;
+ public static Sprite getStaticVentSealedSprite() {
+ if (staticVentSealedSprite) return staticVentSealedSprite;
+ staticVentSealedSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.StaticVentSealed.png", 160f);
+ return staticVentSealedSprite;
+ }
+
+ public static void clearAndReload() {
+ securityGuard = null;
+ ventTarget = null;
+ placedCameras = 0;
+ cooldown = CustomOptionHolder.securityGuardCooldown.getFloat();
+ totalScrews = remainingScrews = Mathf.RoundToInt(CustomOptionHolder.securityGuardTotalScrews.getFloat());
+ camPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardCamPrice.getFloat());
+ ventPrice = Mathf.RoundToInt(CustomOptionHolder.securityGuardVentPrice.getFloat());
+ }
+ }
+
+ public static class Arsonist {
+ public static PlayerControl arsonist;
+ public static Color color = new Color32(238, 112, 46, byte.MaxValue);
+
+ public static float cooldown = 30f;
+ public static float duration = 3f;
+ public static bool triggerArsonistWin = false;
+
+ public static PlayerControl currentTarget;
+ public static PlayerControl douseTarget;
+ public static List<PlayerControl> dousedPlayers = new List<PlayerControl>();
+
+ private static Sprite douseSprite;
+ public static Sprite getDouseSprite() {
+ if (douseSprite) return douseSprite;
+ douseSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.DouseButton.png", 115f);
+ return douseSprite;
+ }
+
+ private static Sprite igniteSprite;
+ public static Sprite getIgniteSprite() {
+ if (igniteSprite) return igniteSprite;
+ igniteSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.IgniteButton.png", 115f);
+ return igniteSprite;
+ }
+
+ public static bool dousedEveryoneAlive() {
+ return PlayerControl.AllPlayerControls.ToArray().All(x => { return x == Arsonist.arsonist || x.Data.IsDead || x.Data.Disconnected || Arsonist.dousedPlayers.Any(y => y.PlayerId == x.PlayerId); });
+ }
+
+ public static void clearAndReload() {
+ arsonist = null;
+ currentTarget = null;
+ douseTarget = null;
+ triggerArsonistWin = false;
+ dousedPlayers = new List<PlayerControl>();
+ foreach (PoolablePlayer p in MapOptions.playerIcons.Values) {
+ if (p != null && p.gameObject != null) p.gameObject.SetActive(false);
+ }
+ cooldown = CustomOptionHolder.arsonistCooldown.getFloat();
+ duration = CustomOptionHolder.arsonistDuration.getFloat();
+ }
+ }
+
+ public static class Guesser {
+ public static PlayerControl guesser;
+ public static Color color = new Color32(255, 255, 0, byte.MaxValue);
+ private static Sprite targetSprite;
+
+ public static int remainingShots = 2;
+
+ public static Sprite getTargetSprite() {
+ if (targetSprite) return targetSprite;
+ targetSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TargetIcon.png", 150f);
+ return targetSprite;
+ }
+
+ public static void clearAndReload() {
+ guesser = null;
+
+ 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
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.1</TargetFramework>
+ <Version>2.7.3</Version>
+ <Description>TheOtherRoles</Description>
+ <Authors>Eisbison</Authors>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <GameVersion>2021.6.15</GameVersion>
+ <DefineConstants>$(DefineConstants);STEAM</DefineConstants>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <EmbeddedResource Include="Resources\CustomHats\*.png" />
+ <EmbeddedResource Include="Resources\*.png" />
+ <EmbeddedResource Include="Resources\TricksterAnimation\*.png" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Reference Include="$(AmongUsLatest)/BepInEx/core/*.dll"/>
+ <Reference Include="$(AmongUsLatest)/BepInEx/unhollowed/*.dll"/>
+ </ItemGroup>
+
+ <Target Name="CopyCustomContent" AfterTargets="AfterBuild">
+ <Message Text="Second occurrence" />
+ <Copy SourceFiles="$(ProjectDir)\bin\$(Configuration)\netstandard2.1\TheOtherRoles.dll" DestinationFolder="$(AmongUsLatest)/BepInEx/plugins/" />
+ </Target>
+</Project>
\ No newline at end of file