.vs
.vscode/tasks.json
-TODO
-.DS_Store
+TODO
\ No newline at end of file
# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2021.12.14s| v3.3.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.3.0/TheOtherRoles.zip)
| 2021.11.9.5s| v3.2.4| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.4/TheOtherRoles.zip)
| 2021.11.9.5s| v3.2.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.3/TheOtherRoles.zip)
| 2021.11.9.5s| v3.2.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.2/TheOtherRoles.zip)
<details>
<summary>Click to show the Changelog</summary>
+**Version 3.3.0**
+- Update to Among Us version 2021.12.14s
+- Fixed a bug where the Pursuer won if the Pursuer was the last killed or voted player
+- Fixed a bug where the option "Enable Mod Roles And Block Vanilla Roles" was not set correctly
+- New option for the Guesser "Evil Guesser can guess spy"
+- New option for the Guesser "Other Guesser Spawn Rate"
+- New ability for the Hacker "Mobile Gadgets" (including vitals & admin table)
+- New option for the Hacker "Max Mobile Gadget Charges"
+- New option for the Hacker "Number Of Tasks Needed For Recharging"
+- Fixed some UI bugs during the meeting
+
**Version 3.2.4**
- Fixed a bug where the Vampire teleported when the bitten player died
- The settings UI has been improved by [Amsyar Rasyiq](https://github.com/amsyarasyiq)
| Guesser Can Shoot Multiple Times Per Meeting| -
| Guesses Visible In Ghost Chat| -
| Guesses Ignore The Medic Shield| -
+| Evil Guesser Can Guess The Spy| -
+| Other Guesser Spawn Rate| -
-----------------------
Otherwise they see the same information as everyone else.
**Admin table:** The Hacker can see the colors (or color types) of the players on the table.\
**Vitals**: The Hacker can see how long dead players have been dead for.\
+The Hacker can access his mobile gadgets (vitals & admin table), with a maximum of charges (uses) and a configurable amount of tasks needed to recharge.\
\
**NOTE:**
- If the Morphling morphs or the Camouflager camouflages, the colors on the admin table change accordingly
| Hacker Cooldown| -
| Hacker Duration | Sets how long the "Hacker mode" remains active
| Hacker Only Sees Color Type | Sets if the Hacker sees the player colors on the admin table or only white/gray (for Lighter and darker colors)
+| Max Mobile Gadget Charges| -
+| Number Of Tasks Needed For Recharging | Number of tasks to get a charge
-----------------------
using UnityEngine;
using static TheOtherRoles.TheOtherRoles;
using TheOtherRoles.Objects;
+using System.Linq;
namespace TheOtherRoles
{
private static CustomButton morphlingButton;
private static CustomButton camouflagerButton;
private static CustomButton hackerButton;
+ private static CustomButton hackerVitalsButton;
+ private static CustomButton hackerAdminTableButton;
private static CustomButton trackerTrackPlayerButton;
private static CustomButton trackerTrackCorpsesButton;
private static CustomButton vampireKillButton;
public static TMPro.TMP_Text securityGuardButtonScrewsText;
public static TMPro.TMP_Text pursuerButtonBlanksText;
+ public static TMPro.TMP_Text hackerAdminTableChargesText;
+ public static TMPro.TMP_Text hackerVitalsChargesText;
public static void setCustomButtonCooldowns() {
engineerRepairButton.MaxTimer = 0f;
morphlingButton.MaxTimer = Morphling.cooldown;
camouflagerButton.MaxTimer = Camouflager.cooldown;
hackerButton.MaxTimer = Hacker.cooldown;
+ hackerVitalsButton.MaxTimer = Hacker.cooldown;
+ hackerAdminTableButton.MaxTimer = Hacker.cooldown;
vampireKillButton.MaxTimer = Vampire.cooldown;
trackerTrackPlayerButton.MaxTimer = 0f;
garlicButton.MaxTimer = 0f;
timeMasterShieldButton.EffectDuration = TimeMaster.shieldDuration;
hackerButton.EffectDuration = Hacker.duration;
+ hackerVitalsButton.EffectDuration = Hacker.duration;
+ hackerAdminTableButton.EffectDuration = Hacker.duration;
vampireKillButton.EffectDuration = Vampire.delay;
lighterButton.EffectDuration = Lighter.duration;
camouflagerButton.EffectDuration = Camouflager.duration;
hackerButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
},
Hacker.getButtonSprite(),
- new Vector3(-1.8f, -0.06f, 0),
+ new Vector3(0f, 1f, 0),
__instance,
KeyCode.F,
true,
0f,
- () => {
- hackerButton.Timer = hackerButton.MaxTimer;
- }
+ () => { hackerButton.Timer = hackerButton.MaxTimer;}
);
+ hackerAdminTableButton = new CustomButton(
+ () => {
+ if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled)
+ DestroyableSingleton<HudManager>.Instance.ShowMap((System.Action<MapBehaviour>)(m => m.ShowCountOverlay()));
+
+ Hacker.chargesAdminTable--;
+ },
+ () => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead;},
+ () => {
+ if (hackerAdminTableChargesText != null) hackerAdminTableChargesText.text = $"{Hacker.chargesAdminTable} / {Hacker.toolsNumber}";
+ return PlayerControl.LocalPlayer.CanMove && Hacker.chargesAdminTable > 0;
+ },
+ () => {
+ hackerAdminTableButton.Timer = hackerAdminTableButton.MaxTimer;
+ hackerAdminTableButton.isEffectActive = false;
+ hackerAdminTableButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ },
+ Hacker.getAdminSprite(),
+ new Vector3(-1.8f, -0.06f, 0),
+ __instance,
+ KeyCode.Q,
+ true,
+ 0f,
+ () => {
+ hackerAdminTableButton.Timer = hackerAdminTableButton.MaxTimer;
+ if (MapBehaviour.Instance && MapBehaviour.Instance.isActiveAndEnabled) MapBehaviour.Instance.Close();
+ },
+ PlayerControl.GameOptions.MapId == 3,
+ "ADMIN"
+ );
+
+ // Hacker Admin Table Charges
+ hackerAdminTableChargesText = GameObject.Instantiate(hackerAdminTableButton.actionButton.cooldownTimerText, hackerAdminTableButton.actionButton.cooldownTimerText.transform.parent);
+ hackerAdminTableChargesText.text = "";
+ hackerAdminTableChargesText.enableWordWrapping = false;
+ hackerAdminTableChargesText.transform.localScale = Vector3.one * 0.5f;
+ hackerAdminTableChargesText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
+ hackerVitalsButton = new CustomButton(
+ () => {
+ if (!Minigame.Instance) {
+ if (Hacker.vitals == null) {
+ var e = UnityEngine.Object.FindObjectsOfType<SystemConsole>().FirstOrDefault(x => x.gameObject.name.Contains("panel_vitals"));
+ if (e == null || Camera.main == null) return;
+ Hacker.vitals = UnityEngine.Object.Instantiate(e.MinigamePrefab, Camera.main.transform, false);
+ }
+ Hacker.vitals.transform.SetParent(Camera.main.transform, false);
+ Hacker.vitals.transform.localPosition = new Vector3(0.0f, 0.0f, -50f);
+ Hacker.vitals.Begin(null);
+ }
+ Hacker.chargesVitals--;
+ },
+ () => { return Hacker.hacker != null && Hacker.hacker == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead;},
+ () => {
+ if (hackerVitalsChargesText != null) hackerVitalsChargesText.text = $"{Hacker.chargesVitals} / {Hacker.toolsNumber}";
+ return PlayerControl.LocalPlayer.CanMove && Hacker.chargesVitals > 0;
+ },
+ () => {
+ hackerVitalsButton.Timer = hackerVitalsButton.MaxTimer;
+ hackerVitalsButton.isEffectActive = false;
+ hackerVitalsButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ },
+ Hacker.getVitalsSprite(),
+ new Vector3(-2.7f, -0.06f, 0),
+ __instance,
+ KeyCode.Q,
+ true,
+ 0f,
+ () => {
+ hackerVitalsButton.Timer = hackerVitalsButton.MaxTimer;
+ if (Minigame.Instance) Hacker.vitals.ForceClose();
+ },
+ false,
+ "VITALS"
+ );
+
+ // Hacker Vitals Charges
+ hackerVitalsChargesText = GameObject.Instantiate(hackerVitalsButton.actionButton.cooldownTimerText, hackerVitalsButton.actionButton.cooldownTimerText.transform.parent);
+ hackerVitalsChargesText.text = "";
+ hackerVitalsChargesText.enableWordWrapping = false;
+ hackerVitalsChargesText.transform.localScale = Vector3.one * 0.5f;
+ hackerVitalsChargesText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
// Tracker button
trackerTrackPlayerButton = new CustomButton(
() => {
public static CustomOption guesserHasMultipleShotsPerMeeting;
public static CustomOption guesserShowInfoInGhostChat;
public static CustomOption guesserKillsThroughShield;
+ public static CustomOption guesserEvilCanKillSpy;
+ public static CustomOption guesserSpawnBothRate;
public static CustomOption jesterSpawnRate;
public static CustomOption jesterCanCallEmergency;
public static CustomOption hackerCooldown;
public static CustomOption hackerHackeringDuration;
public static CustomOption hackerOnlyColorType;
+ public static CustomOption hackerToolsNumber;
+ public static CustomOption hackerRechargeTasksNumber;
public static CustomOption trackerSpawnRate;
public static CustomOption trackerUpdateIntervall;
// Role Options
presetSelection = CustomOption.Create(0, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true);
- activateRoles = CustomOption.Create(7, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Enable Mod Roles And Block Vanilla Roles"), true, null, true);
+ activateRoles = CustomOption.Create(1, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Enable Mod Roles And Block Vanilla Roles"), true, 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);
guesserHasMultipleShotsPerMeeting = CustomOption.Create(313, "Guesser Can Shoot Multiple Times Per Meeting", false, guesserSpawnRate);
guesserShowInfoInGhostChat = CustomOption.Create(314, "Guesses Visible In Ghost Chat", true, guesserSpawnRate);
guesserKillsThroughShield = CustomOption.Create(315, "Guesses Ignore The Medic Shield", true, guesserSpawnRate);
+ guesserEvilCanKillSpy = CustomOption.Create(316, "Evil Guesser Can Guess The Spy", true, guesserSpawnRate);
+ guesserSpawnBothRate = CustomOption.Create(317, "Other Guesser Spawn Rate", rates, guesserSpawnRate);
jesterSpawnRate = CustomOption.Create(60, cs(Jester.color, "Jester"), rates, null, true);
jesterCanCallEmergency = CustomOption.Create(61, "Jester can call emergency meeting", true, jesterSpawnRate);
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);
+ hackerToolsNumber = CustomOption.Create(174, "Max Mobile Gadget Charges", 5f, 1f, 30f, 1f, hackerSpawnRate);
+ hackerRechargeTasksNumber = CustomOption.Create(175, "Number Of Tasks Needed For Recharging", 2f, 1f, 5f, 1f, 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);
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "3.2.4";
+ public const string VersionString = "3.3.0";
public static System.Version Version = System.Version.Parse(VersionString);
internal static BepInEx.Logging.ManualLogSource Logger;
YOffset = createHatPackage(value, key, YOffset, __instance);
}
- __instance.scroller.YBounds.max = -(YOffset + 4.1f);
+ __instance.scroller.ContentYBounds.max = -(YOffset + 4.1f);
}
}
public static void Postfix(GameOptionsMenu __instance) {
if (__instance.Children.Length < 100) return; // TODO: Introduce a cleaner way to seperate the TOR settings from the game settings
- __instance.GetComponentInParent<Scroller>().YBounds.max = -0.5F + __instance.Children.Length * 0.55F;
+ __instance.GetComponentInParent<Scroller>().ContentYBounds.max = -0.5F + __instance.Children.Length * 0.55F;
timer += Time.deltaTime;
if (timer < 0.1f) return;
timer = 0f;
private HudManager hudManager;
private bool mirror;
private KeyCode? hotkey;
+ private string buttonText;
- 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)
+ 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, string buttonText = "")
{
this.hudManager = hudManager;
this.OnClick = OnClick;
this.Sprite = Sprite;
this.mirror = mirror;
this.hotkey = hotkey;
+ this.buttonText = buttonText;
Timer = 16.2f;
buttons.Add(this);
actionButton = UnityEngine.Object.Instantiate(hudManager.KillButton, hudManager.KillButton.transform.parent);
PassiveButton button = actionButton.GetComponent<PassiveButton>();
- this.showButtonText = actionButton.graphic.sprite == Sprite;
+ this.showButtonText = (actionButton.graphic.sprite == Sprite || buttonText != "");
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) { }
+ public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, KeyCode? hotkey, bool mirror = false, string buttonText = "")
+ : this(OnClick, HasButton, CouldUse, OnMeetingEnds, Sprite, PositionOffset, hudManager, hotkey, false, 0f, () => {}, mirror, buttonText) { }
- void onClickEvent()
+ public void onClickEvent()
{
if (this.Timer < 0f && HasButton() && CouldUse())
{
setActive(hudManager.UseButton.isActiveAndEnabled);
actionButton.graphic.sprite = Sprite;
+ if (showButtonText && buttonText != ""){
+ actionButton.OverrideText(buttonText);
+ }
actionButton.buttonLabelText.enabled = showButtonText; // Only show the text if it's a kill button
if (hudManager.UseButton != null) {
Vector3 pos = hudManager.UseButton.transform.localPosition;
$@"Modded by <color=#FCCE03FF>Eisbison</color>, <color=#FCCE03FF>Thunderstorm584</color>, <color=#FCCE03FF>EndOfFile</color> & <color=#FCCE03FF>Mallöris</color>
Design by <color=#FCCE03FF>Bavari</color>";
+
public static string contributorsCredentials = "<size=80%>GitHub Contributors: Alex2911, amsyarasyiq, gendelo3</size>";
[HarmonyPatch(typeof(VersionShower), nameof(VersionShower.Start))]
}
// Possible Additional winner: Pursuer
- if (Pursuer.pursuer != null && !Pursuer.pursuer.Data.IsDead) {
+ if (Pursuer.pursuer != null && !Pursuer.pursuer.Data.IsDead && !Pursuer.notAckedExiled) {
if (!TempData.winners.ToArray().Any(x => x.PlayerName == Pursuer.pursuer.Data.PlayerName))
TempData.winners.Add(new WinningPlayerData(Pursuer.pursuer.Data));
AdditionalTempData.additionalWinConditions.Add(WinCondition.AdditionalAlivePursuerWin);
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 & Pursuer save next to be exiled, because RPC of ending game comes before RPC of exiled
Lovers.notAckedExiledIsLover = false;
- if (exiled != null)
+ Pursuer.notAckedExiled = false;
+ if (exiled != null) {
Lovers.notAckedExiledIsLover = ((Lovers.lover1 != null && Lovers.lover1.PlayerId == exiled.PlayerId) || (Lovers.lover2 != null && Lovers.lover2.PlayerId == exiled.PlayerId));
+ Pursuer.notAckedExiled = (Pursuer.pursuer != null && Pursuer.pursuer.PlayerId == exiled.PlayerId);
+ }
+
+
}
}
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
+ RoleId guesserRole = (Guesser.niceGuesser != null && PlayerControl.LocalPlayer.PlayerId == Guesser.niceGuesser.PlayerId) ? RoleId.NiceGuesser : RoleId.EvilGuesser;
+ if (roleInfo.roleId == RoleId.Lover || roleInfo.roleId == guesserRole || roleInfo == RoleInfo.niceMini || (!Guesser.evilGuesserCanGuessSpy && guesserRole == RoleId.EvilGuesser && roleInfo.roleId == RoleId.Spy)) continue; // Not guessable roles
Transform buttonParent = (new GameObject()).transform;
buttonParent.SetParent(container);
Transform button = UnityEngine.Object.Instantiate(buttonTemplate, buttonParent);
buttons.ForEach(x => x.GetComponent<SpriteRenderer>().color = x == selectedButton ? Color.red : Color.white);
} else {
PlayerControl focusedTarget = Helpers.playerById((byte)__instance.playerStates[buttonTarget].TargetPlayerId);
- if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || focusedTarget == null || Guesser.remainingShots <= 0 ) return;
+ if (!(__instance.state == MeetingHud.VoteStates.Voted || __instance.state == MeetingHud.VoteStates.NotVoted) || focusedTarget == null || Guesser.remainingShots(PlayerControl.LocalPlayer.PlayerId) <= 0 ) return;
if (!Guesser.killsThroughShield && focusedTarget == Medic.shielded) { // Depending on the options, shooting the shielded player will not allow the guess, notifiy everyone about the kill attempt and close the window
- __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
+ __instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
UnityEngine.Object.Destroy(container.gameObject);
MessageWriter murderAttemptWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.Reliable, -1);
// Reset the GUI
__instance.playerStates.ToList().ForEach(x => x.gameObject.SetActive(true));
UnityEngine.Object.Destroy(container.gameObject);
- if (Guesser.hasMultipleShotsPerMeeting && Guesser.remainingShots > 1 && dyingTarget != PlayerControl.LocalPlayer)
+ if (Guesser.hasMultipleShotsPerMeeting && Guesser.remainingShots(PlayerControl.LocalPlayer.PlayerId) > 1 && dyingTarget != PlayerControl.LocalPlayer)
__instance.playerStates.ToList().ForEach(x => { if (x.TargetPlayerId == dyingTarget.PlayerId && x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); });
else
__instance.playerStates.ToList().ForEach(x => { if (x.transform.FindChild("ShootButton") != null) UnityEngine.Object.Destroy(x.transform.FindChild("ShootButton").gameObject); });
// Shoot player and send chat info if activated
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.GuesserShoot, Hazel.SendOption.Reliable, -1);
+ writer.Write(PlayerControl.LocalPlayer.PlayerId);
writer.Write(dyingTarget.PlayerId);
writer.Write(focusedTarget.PlayerId);
writer.Write((byte)roleInfo.roleId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.guesserShoot(dyingTarget.PlayerId, focusedTarget.PlayerId, (byte)roleInfo.roleId);
+ RPCProcedure.guesserShoot(PlayerControl.LocalPlayer.PlayerId, dyingTarget.PlayerId, focusedTarget.PlayerId, (byte)roleInfo.roleId);
}
}));
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.Select))]
class PlayerVoteAreaSelectPatch {
static bool Prefix(MeetingHud __instance) {
- return !(PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer == Guesser.guesser && guesserUI != null);
+ return !(PlayerControl.LocalPlayer != null && Guesser.isGuesser(PlayerControl.LocalPlayer.PlayerId) && guesserUI != null);
}
}
}
}
+ //Fix visor in Meetings
+ foreach (PlayerVoteArea pva in __instance.playerStates) {
+ if(pva.PlayerIcon != null && pva.PlayerIcon.VisorSlot != null){
+ pva.PlayerIcon.VisorSlot.transform.position += new Vector3(0, 0, -1f);
+ }
+ }
+
// Add overlay for spelled players
if (Witch.witch != null && Witch.futureSpelled != null) {
foreach (PlayerVoteArea pva in __instance.playerStates) {
}
// Add Guesser Buttons
- if (Guesser.guesser != null && PlayerControl.LocalPlayer == Guesser.guesser && !Guesser.guesser.Data.IsDead && Guesser.remainingShots > 0) {
+ if (Guesser.isGuesser(PlayerControl.LocalPlayer.PlayerId) && !PlayerControl.LocalPlayer.Data.IsDead && Guesser.remainingShots(PlayerControl.LocalPlayer.PlayerId) > 0) {
for (int i = 0; i < __instance.playerStates.Length; i++) {
PlayerVoteArea playerVoteArea = __instance.playerStates[i];
- if (playerVoteArea.AmDead || playerVoteArea.TargetPlayerId == Guesser.guesser.PlayerId) continue;
+ if (playerVoteArea.AmDead || playerVoteArea.TargetPlayerId == PlayerControl.LocalPlayer.PlayerId) continue;
GameObject template = playerVoteArea.Buttons.transform.Find("CancelButton").gameObject;
GameObject targetBox = UnityEngine.Object.Instantiate(template, playerVoteArea.transform);
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.transform.localPosition += Vector3.down * 0.10f;
+ meetingInfo.fontSize *= 0.60f;
meetingInfo.gameObject.name = "Info";
}
+ // Set player name higher to align in middle
+ if (meetingInfo != null && playerVoteArea != null) {
+ var playerName = playerVoteArea.NameText;
+ playerName.transform.localPosition = new Vector3(0.3384f, (0.0311f + 0.0683f), -0.1f);
+ }
+
var (tasksCompleted, tasksTotal) = TasksHandler.taskInfo(p.Data);
string roleNames = RoleInfo.GetRolesString(p, true);
string taskInfo = tasksTotal > 0 ? $"<color=#FAD934FF>({tasksCompleted}/{tasksTotal})</color>" : "";
}
}
+ public static void hackerUpdate() {
+ if (Hacker.hacker == null || PlayerControl.LocalPlayer != Hacker.hacker || Hacker.hacker.Data.IsDead) return;
+ var (playerCompleted, _) = TasksHandler.taskInfo(Hacker.hacker.Data);
+ if (playerCompleted == Hacker.rechargedTasks) {
+ Hacker.rechargedTasks += Hacker.rechargeTasksNumber;
+ if (Hacker.toolsNumber > Hacker.chargesVitals) Hacker.chargesVitals++;
+ if (Hacker.toolsNumber > Hacker.chargesAdminTable) Hacker.chargesAdminTable++;
+ }
+ }
+
static void pursuerSetTarget() {
if (Pursuer.pursuer == null || Pursuer.pursuer != PlayerControl.LocalPlayer) return;
Pursuer.target = setTarget();
pursuerSetTarget();
// Witch
witchSetTarget();
+ hackerUpdate();
}
}
}
// 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());
+ data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnRate.getSelection());
+ if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection()); // Second guesser
} else if (data.crewmates.Count > 0 && data.maxCrewmateRoles > 0) {
- data.crewSettings.Add((byte)RoleId.Guesser, CustomOptionHolder.guesserSpawnRate.getSelection());
+ data.crewSettings.Add((byte)RoleId.NiceGuesser, CustomOptionHolder.guesserSpawnRate.getSelection());
+ if (data.impostors.Count > 0 && data.maxImpostorRoles > 0) data.impSettings.Add((byte)RoleId.EvilGuesser, CustomOptionHolder.guesserSpawnBothRate.getSelection()); // Second guesser
}
}
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.Role.IsImpostor ? Palette.ImpostorRed : Guesser.color);
+ } else if (Guesser.niceGuesser != null && Guesser.niceGuesser == PlayerControl.LocalPlayer) {
+ setPlayerNameColor(Guesser.niceGuesser, Guesser.color);
+ } else if (Guesser.evilGuesser != null && Guesser.evilGuesser == PlayerControl.LocalPlayer) {
+ setPlayerNameColor(Guesser.evilGuesser, Palette.ImpostorRed);
} else if (Bait.bait != null && Bait.bait == PlayerControl.LocalPlayer) {
setPlayerNameColor(Bait.bait, Bait.color);
} else if (Vulture.vulture != null && Vulture.vulture == PlayerControl.LocalPlayer) {
}
}
+
+ //Fix Visor in Vitals
+ foreach (VitalsPanel panel in __instance.vitals) {
+ if (panel.PlayerIcon != null && panel.PlayerIcon.Skin != null) {
+ panel.PlayerIcon.Skin.transform.position = new Vector3(0, 0, 0f);
+ }
+ }
}
}
Warlock,
SecurityGuard,
Arsonist,
- Guesser,
+ EvilGuesser,
+ NiceGuesser,
BountyHunter,
Bait,
Vulture,
case RoleId.Arsonist:
Arsonist.arsonist = player;
break;
- case RoleId.Guesser:
- Guesser.guesser = player;
+ case RoleId.EvilGuesser:
+ Guesser.evilGuesser = player;
+ break;
+ case RoleId.NiceGuesser:
+ Guesser.niceGuesser = player;
break;
case RoleId.BountyHunter:
BountyHunter.bountyHunter = player;
Spy.spy = oldShifter;
if (SecurityGuard.securityGuard != null && SecurityGuard.securityGuard == player)
SecurityGuard.securityGuard = oldShifter;
- if (Guesser.guesser != null && Guesser.guesser == player)
- Guesser.guesser = oldShifter;
+ if (Guesser.niceGuesser != null && Guesser.niceGuesser == player)
+ Guesser.niceGuesser = oldShifter;
if (Bait.bait != null && Bait.bait == player) {
Bait.bait = oldShifter;
if (Bait.bait.Data.IsDead) Bait.reported = true;
// Other roles
if (player == Jester.jester) Jester.clearAndReload();
if (player == Arsonist.arsonist) Arsonist.clearAndReload();
- if (player == Guesser.guesser) Guesser.clearAndReload();
+ if (Guesser.isGuesser(player.PlayerId)) Guesser.clear(player.PlayerId);
if (!ignoreLovers && (player == Lovers.lover1 || player == Lovers.lover2)) { // The whole Lover couple is being erased
Lovers.clearAndReload();
}
}
}
- public static void guesserShoot(byte dyingTargetId, byte guessedTargetId, byte guessedRoleId) {
+ public static void guesserShoot(byte killerId, byte dyingTargetId, byte guessedTargetId, byte guessedRoleId) {
PlayerControl dyingTarget = Helpers.playerById(dyingTargetId);
if (dyingTarget == null ) return;
dyingTarget.Exiled();
PlayerControl dyingLoverPartner = Lovers.bothDie ? dyingTarget.getPartner() : null; // Lover check
byte partnerId = dyingLoverPartner != null ? dyingLoverPartner.PlayerId : dyingTargetId;
- Guesser.remainingShots = Mathf.Max(0, Guesser.remainingShots - 1);
+ Guesser.remainingShots(killerId, true);
if (Constants.ShouldPlaySfx()) SoundManager.Instance.PlaySound(dyingTarget.KillSfx, false, 0.8f);
if (MeetingHud.Instance) {
foreach (PlayerVoteArea pva in MeetingHud.Instance.playerStates) {
if (AmongUsClient.Instance.AmHost)
MeetingHud.Instance.CheckForEndVoting();
}
- if (HudManager.Instance != null && Guesser.guesser != null)
+ PlayerControl guesser = Helpers.playerById(killerId);
+ if (HudManager.Instance != null && guesser != null)
if (PlayerControl.LocalPlayer == dyingTarget)
- HudManager.Instance.KillOverlay.ShowKillAnimation(Guesser.guesser.Data, dyingTarget.Data);
+ HudManager.Instance.KillOverlay.ShowKillAnimation(guesser.Data, dyingTarget.Data);
else if (dyingLoverPartner != null && PlayerControl.LocalPlayer == dyingLoverPartner)
HudManager.Instance.KillOverlay.ShowKillAnimation(dyingLoverPartner.Data, dyingLoverPartner.Data);
RoleInfo roleInfo = RoleInfo.allRoleInfos.FirstOrDefault(x => (byte)x.roleId == guessedRoleId);
string msg = $"Guesser guessed the role {roleInfo?.name ?? ""} for {guessedTarget.Data.PlayerName}!";
if (AmongUsClient.Instance.AmClient && DestroyableSingleton<HudManager>.Instance)
- DestroyableSingleton<HudManager>.Instance.Chat.AddChat(Guesser.guesser, msg);
+ DestroyableSingleton<HudManager>.Instance.Chat.AddChat(guesser, msg);
if (msg.IndexOf("who", StringComparison.OrdinalIgnoreCase) >= 0)
DestroyableSingleton<Assets.CoreScripts.Telemetry>.Instance.SendWho();
}
RPCProcedure.arsonistWin();
break;
case (byte)CustomRPC.GuesserShoot:
+ byte killerId = reader.ReadByte();
byte dyingTarget = reader.ReadByte();
byte guessedTarget = reader.ReadByte();
byte guessedRoleId = reader.ReadByte();
- RPCProcedure.guesserShoot(dyingTarget, guessedTarget, guessedRoleId);
+ RPCProcedure.guesserShoot(killerId, dyingTarget, guessedTarget, guessedRoleId);
break;
case (byte)CustomRPC.VultureWin:
RPCProcedure.vultureWin();
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, true);
- 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 goodGuesser = new RoleInfo("Nice Guesser", Guesser.color, "Guess and shoot", "Guess and shoot", RoleId.NiceGuesser);
+ public static RoleInfo badGuesser = new RoleInfo("Evil Guesser", Palette.ImpostorRed, "Guess and shoot", "Guess and shoot", RoleId.EvilGuesser);
public static RoleInfo bait = new RoleInfo("Bait", Bait.color, "Bait your enemies", "Bait your enemies", RoleId.Bait);
public static RoleInfo vulture = new RoleInfo("Vulture", Vulture.color, "Eat Corpses to win", "Eat dead bodies", RoleId.Vulture, true);
public static RoleInfo medium = new RoleInfo("Medium", Medium.color, "Question the souls of the dead to gain informations", "Question the souls", RoleId.Medium);
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.Role.IsImpostor ? badGuesser : goodGuesser);
+ if (p == Guesser.niceGuesser) infos.Add(goodGuesser);
+ if (p == Guesser.evilGuesser) infos.Add(badGuesser);
if (p == BountyHunter.bountyHunter) infos.Add(bountyHunter);
if (p == Bait.bait) infos.Add(bait);
if (p == Vulture.vulture) infos.Add(vulture);
public static class Hacker {
public static PlayerControl hacker;
+ public static Minigame vitals = null;
public static Color color = new Color32(117, 250, 76, byte.MaxValue);
public static float cooldown = 30f;
public static float duration = 10f;
+ public static float toolsNumber = 5f;
public static bool onlyColorType = false;
public static float hackerTimer = 0f;
+ public static int rechargeTasksNumber = 2;
+ public static int rechargedTasks = 2;
+ public static int chargesVitals = 1;
+ public static int chargesAdminTable = 1;
private static Sprite buttonSprite;
+ private static Sprite vitalsSprite;
+ private static Sprite adminSprite;
+
public static Sprite getButtonSprite() {
if (buttonSprite) return buttonSprite;
buttonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.HackerButton.png", 115f);
return buttonSprite;
}
+ public static Sprite getVitalsSprite() {
+ if (vitalsSprite) return vitalsSprite;
+ vitalsSprite = HudManager.Instance.UseButton.fastUseSettings[ImageNames.VitalsButton].Image;
+ return vitalsSprite;
+ }
+
+ public static Sprite getAdminSprite() {
+ byte mapId = PlayerControl.GameOptions.MapId;
+ UseButtonSettings button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.PolusAdminButton]; // Polus
+ if (mapId == 0 || mapId == 3) button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.AdminMapButton]; // Skeld || Dleks
+ else if (mapId == 1) button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.MIRAAdminButton]; // Mira HQ
+ else if (mapId == 4) button = HudManager.Instance.UseButton.fastUseSettings[ImageNames.AirshipAdminButton]; // Airship
+ adminSprite = button.Image;
+ return adminSprite;
+ }
+
public static void clearAndReload() {
hacker = null;
+ vitals = null;
hackerTimer = 0f;
+ adminSprite = null;
cooldown = CustomOptionHolder.hackerCooldown.getFloat();
duration = CustomOptionHolder.hackerHackeringDuration.getFloat();
onlyColorType = CustomOptionHolder.hackerOnlyColorType.getBool();
+ toolsNumber = CustomOptionHolder.hackerToolsNumber.getFloat();
+ rechargeTasksNumber = Mathf.RoundToInt(CustomOptionHolder.hackerRechargeTasksNumber.getFloat());
+ rechargedTasks = Mathf.RoundToInt(CustomOptionHolder.hackerRechargeTasksNumber.getFloat());
+ chargesVitals = Mathf.RoundToInt(CustomOptionHolder.hackerToolsNumber.getFloat()) / 2;
+ chargesAdminTable = Mathf.RoundToInt(CustomOptionHolder.hackerToolsNumber.getFloat()) / 2;
}
}
}
public static class Guesser {
- public static PlayerControl guesser;
+ public static PlayerControl niceGuesser;
+ public static PlayerControl evilGuesser;
public static Color color = new Color32(255, 255, 0, byte.MaxValue);
private static Sprite targetSprite;
- public static int remainingShots = 2;
+ public static int remainingShotsEvilGuesser = 2;
+ public static int remainingShotsNiceGuesser = 2;
public static bool hasMultipleShotsPerMeeting = false;
public static bool showInfoInGhostChat = true;
public static bool killsThroughShield = true;
+ public static bool evilGuesserCanGuessSpy = true;
public static Sprite getTargetSprite() {
if (targetSprite) return targetSprite;
return targetSprite;
}
+ public static bool isGuesser (byte playerId) {
+ if ((niceGuesser != null && niceGuesser.PlayerId == playerId) || (evilGuesser != null && evilGuesser.PlayerId == playerId)) return true;
+ return false;
+ }
+
+ public static void clear (byte playerId) {
+ if (niceGuesser != null && niceGuesser.PlayerId == playerId) niceGuesser = null;
+ else if (evilGuesser != null && evilGuesser.PlayerId == playerId) evilGuesser = null;
+ }
+
+ public static int remainingShots(byte playerId, bool shoot = false) {
+ int remainingShots = remainingShotsEvilGuesser;
+ if (niceGuesser.PlayerId == playerId) {
+ remainingShots = remainingShotsNiceGuesser;
+ if (shoot) remainingShotsNiceGuesser = Mathf.Max(0, remainingShotsNiceGuesser - 1);
+ } else if (shoot) {
+ remainingShotsEvilGuesser = Mathf.Max(0, remainingShotsEvilGuesser - 1);
+ }
+ return remainingShots;
+ }
+
public static void clearAndReload() {
- guesser = null;
-
- remainingShots = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat());
+ niceGuesser = null;
+ evilGuesser = null;
+
+ remainingShotsEvilGuesser = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat());
+ remainingShotsNiceGuesser = Mathf.RoundToInt(CustomOptionHolder.guesserNumberOfShots.getFloat());
hasMultipleShotsPerMeeting = CustomOptionHolder.guesserHasMultipleShotsPerMeeting.getBool();
showInfoInGhostChat = CustomOptionHolder.guesserShowInfoInGhostChat.getBool();
killsThroughShield = CustomOptionHolder.guesserKillsThroughShield.getBool();
+ evilGuesserCanGuessSpy = CustomOptionHolder.guesserEvilCanKillSpy.getBool();
}
}
public static List<PlayerControl> blankedList = new List<PlayerControl>();
public static int blanks = 0;
public static Sprite blank;
+ public static bool notAckedExiled = false;
public static float cooldown = 30f;
public static int blanksNumber = 5;
target = null;
blankedList = new List<PlayerControl>();
blanks = 0;
+ notAckedExiled = false;
cooldown = CustomOptionHolder.pursuerCooldown.getFloat();
blanksNumber = Mathf.RoundToInt(CustomOptionHolder.pursuerBlanksNumber.getFloat());
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>3.2.4</Version>
+ <Version>3.3.0</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>