| [Witch](#witch) | [Tracker](#tracker) | | |
| [Ninja](#ninja) | [Snitch](#snitch) | | |
| [Bomber](#bomber) | [Spy](#spy) | | |
-| [Evil Guesser](#guesser) | [Portalmaker](#portalmaker) | | |
-| | [Security Guard](#security-guard) | | |
+| [Yo-Yo](#yoyo) | [Portalmaker](#portalmaker) | | |
+| [Evil Guesser](#guesser) | [Security Guard](#security-guard) | | |
| | [Medium](#medium) | | |
| | [Trapper](#trapper) | | |
| | [Nice Guesser](#guesser) | | |
# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2024.3.5s| v4.5.3| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.3/TheOtherRoles.zip)
| 2024.3.5s| v4.5.2| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.2/TheOtherRoles.zip)
| 2023.11.28s| v4.5.1| [Download](https://github.com/TheOtherRolesAU/TheOtherRoles/releases/download/v4.5.1/TheOtherRoles.zip)
<details>
<summary>Click to show the Changelog</summary>
+**Version 4.5.3**
+- Added a new role: Yo-Yo (Impostor) - Mark and blink to a position, then blink back to the start of the blink!
+- Added a new option to Jackal And Sidekick: Jackal / Sidekick can sabotage lights (cooldown syncs with Imp Sabotages)
+- Added a new option: Block Dead Impostor From Sabotaging
+- Added a new option to the Tracker: Tracking Type: Arrow, Proximity Meter, Both
+- Added a new feature: The host is always displayed to all players, in the meeting ui and ping tracker text
+- Added a new feature: Ghosts see the icons of all players on their map
+- Fixed the chat command to ban a player from the lobby `/ban`
+- Fixed an issue where killing in PropHunt crashed the lobby
+- Fixed an issue with the very short kill distance in Vanilla HideNSeek
+- Changed trapper trap: is now visible with transparency to the trapper while inactive
+
+
**Version 4.5.2**
- Updated to Among Us version 2024.3.5s (various small / "long" features and bugfixes)
- Updated BepInEx dependency to 688
| Bomb Is Active After |
-----------------------
+## YoYo
+### **Team: Impostors**
+The Yo-Yo is an Impostor who has the ability mark a position and later blink (teleport) to this position.\
+After the initial blink, the Yo-Yo has a fixed amount of time (option) to do whatever they want, before automatically blinking back to the starting point of the first blink.\
+Each blink leaves behind a silhouette with configurable transparency. The silhouette is very hard to see.\
+The Yo-Yo may also have access to a mobile admin table, depending on the settings.
+
+**NOTE:**
+
+- The marked position for the first jump's target is displayed to the Yo-Yo and ghosts only.
+- The silhouette of the first blink stays (almost) until the Yo-Yo returns
+- The silhouette of the second blink (return) stays for 5 seconds
+
+### Game Options
+| Name | Description |
+|----------|:-------------:|
+| Yo-Yo Spawn Chance | -
+| Blink Duration | -
+| Mark Location Cooldown | -
+| Marked Location Stays After Meeting | -
+| Has Admin Table | -
+| Admin Table Cooldown | -
+| Silhouette Visibility | -
+-----------------------
+
## Guesser
### **Team: Crewmates or Impostors**
The Guesser can be a Crewmate or an Impostor (depending on the settings).\
| Jackal/Sidekick Kill Cooldown | Kill cooldown |
| Jackal Create Sidekick Cooldown | Cooldown before a Sidekick can be created |
| Jackal can use vents | Yes/No |
+| Jackal can sabotage Lights | Yes/No |
| Jackal can create a Sidekick | Yes/No |
+| Sidekick gets promoted to Jackal on Jackal Death | Yes/No |
+| Sidekick can kill | Yes/No |
+| Sidekick can Vent | Yes/No |
+| Jackal can sabotage Lights | Yes/No |
| Jackals promoted from Sidekick can create a Sidekick | Yes/No (to prevent the Jackal team from growing) |
| Jackals can make an Impostor to their Sidekick | Yes/No (to prevent a Jackal from turning an Impostor into a Sidekick, if they use the ability on an Impostor they see the Impostor as Sidekick, but the Impostor isn't converted to Sidekick. If this option is set to "No" Jackal and Sidekick can kill each other.) |
| Jackal and Sidekick have Impostor vision | - |
The Tracker can select one player to track. Depending on the options the Tracker can track a different person after each meeting or the Tracker tracks the same person for the whole game.\
An arrow points to the last tracked position of the player.\
The arrow updates its position every few seconds (configurable).\
+By an option, the arrow can be replaced or combined with the Proximity Tracker from Hide N Seek.\
Depending on the options, the Tracker has another ability: They can track all corpses on the map for a set amount of time. They will keep tracking corpses, even if they were cleaned or eaten by the Vulture.
**NOTE**
| Tracker Can Track Corpses | -
| Corpses Tracking Cooldown | -
| Corpses Tracking Duration | -
+| How Tracker Gets Target Location| -
-----------------------
## Snitch
public static CustomButton jackalKillButton;
public static CustomButton sidekickKillButton;
private static CustomButton jackalSidekickButton;
+ public static CustomButton jackalAndSidekickSabotageLightsButton;
private static CustomButton eraserButton;
private static CustomButton placeJackInTheBoxButton;
private static CustomButton lightsOutButton;
public static CustomButton thiefKillButton;
public static CustomButton trapperButton;
public static CustomButton bomberButton;
+ public static CustomButton yoyoButton;
+ public static CustomButton yoyoAdminTableButton;
public static CustomButton defuseButton;
public static CustomButton zoomOutButton;
private static CustomButton hunterLighterButton;
mayorMeetingButton.MaxTimer = GameManager.Instance.LogicOptions.GetEmergencyCooldown();
trapperButton.MaxTimer = Trapper.cooldown;
bomberButton.MaxTimer = Bomber.bombCooldown;
+ yoyoButton.MaxTimer = Yoyo.markCooldown;
+ yoyoAdminTableButton.MaxTimer = Yoyo.adminCooldown;
+ yoyoAdminTableButton.EffectDuration = 10f;
hunterLighterButton.MaxTimer = Hunter.lightCooldown;
hunterAdminTableButton.MaxTimer = Hunter.AdminCooldown;
hunterArrowButton.MaxTimer = Hunter.ArrowCooldown;
KeyCode.Q
);
+ jackalAndSidekickSabotageLightsButton = new CustomButton(
+ () => {
+ ShipStatus.Instance.RpcUpdateSystem(SystemTypes.Sabotage, (byte)SystemTypes.Electrical);
+ },
+ () => {
+ return (Jackal.jackal != null && Jackal.jackal == CachedPlayer.LocalPlayer.PlayerControl && Jackal.canSabotageLights ||
+ Sidekick.sidekick != null && Sidekick.sidekick == CachedPlayer.LocalPlayer.PlayerControl && Sidekick.canSabotageLights) && !CachedPlayer.LocalPlayer.Data.IsDead;
+ },
+ () => {
+ if (Helpers.sabotageTimer() > jackalAndSidekickSabotageLightsButton.Timer || Helpers.sabotageActive())
+ jackalAndSidekickSabotageLightsButton.Timer = Helpers.sabotageTimer() + 5f; // this will give imps time to do another sabotage.
+ return Helpers.canUseSabotage();},
+ () => {
+ jackalAndSidekickSabotageLightsButton.Timer = Helpers.sabotageTimer() + 5f;
+ },
+ Trickster.getLightsOutButtonSprite(),
+ CustomButton.ButtonPositions.upperRowCenter,
+ __instance,
+ KeyCode.G
+ );
+
// Eraser erase button
eraserButton = new CustomButton(
() => {
trapperChargesText.transform.localScale = Vector3.one * 0.5f;
trapperChargesText.transform.localPosition += new Vector3(-0.05f, 0.7f, 0);
+
+ // Yoyo button
+ yoyoButton = new CustomButton(
+ () => {
+ var pos = CachedPlayer.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));
+
+ if (Yoyo.markedLocation == null) {
+ TheOtherRolesPlugin.Logger.LogMessage($"marked location is null in button press");
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.YoyoMarkLocation, Hazel.SendOption.Reliable);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.yoyoMarkLocation(buff);
+ SoundEffectsManager.play("tricksterPlaceBox");
+ yoyoButton.Sprite = Yoyo.getBlinkButtonSprite();
+ yoyoButton.Timer = 10f;
+ yoyoButton.HasEffect = false;
+ yoyoButton.buttonText = "Blink";
+ } else {
+ TheOtherRolesPlugin.Logger.LogMessage("in else for some reason");
+ // Jump to location
+ TheOtherRolesPlugin.Logger.LogMessage($"trying to blink!");
+ var exit = (Vector3)Yoyo.markedLocation;
+ if (SubmergedCompatibility.IsSubmerged) {
+ SubmergedCompatibility.ChangeFloor(exit.y > -7);
+ }
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.YoyoBlink, Hazel.SendOption.Reliable);
+ writer.Write(Byte.MaxValue);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.yoyoBlink(true, buff);
+ yoyoButton.EffectDuration = Yoyo.blinkDuration;
+ yoyoButton.Timer = 10f;
+ yoyoButton.HasEffect = true;
+ yoyoButton.buttonText = "Returning...";
+ SoundEffectsManager.play("morphlingMorph");
+ }
+ },
+ () => { return Yoyo.yoyo != null && Yoyo.yoyo == CachedPlayer.LocalPlayer.PlayerControl && !CachedPlayer.LocalPlayer.Data.IsDead; },
+ () => { return CachedPlayer.LocalPlayer.PlayerControl.CanMove; },
+ () => {
+ if (Yoyo.markStaysOverMeeting) {
+ yoyoButton.Timer = 10f;
+ } else {
+ Yoyo.markedLocation = null;
+ yoyoButton.Timer = yoyoButton.MaxTimer;
+ yoyoButton.Sprite = Yoyo.getMarkButtonSprite();
+ yoyoButton.buttonText = "Mark Location";
+ }
+ },
+ Yoyo.getMarkButtonSprite(),
+ CustomButton.ButtonPositions.upperRowLeft,
+ __instance,
+ KeyCode.F,
+ false,
+ Yoyo.blinkDuration,
+ () => {
+ if (TransportationToolPatches.isUsingTransportation(Yoyo.yoyo)) {
+ yoyoButton.Timer = 0.5f;
+ yoyoButton.DeputyTimer = 0.5f;
+ yoyoButton.isEffectActive = true;
+ yoyoButton.actionButton.cooldownTimerText.color = new Color(0F, 0.8F, 0F);
+ return;
+ } else if (Yoyo.yoyo.inVent) {
+ __instance.ImpostorVentButton.DoClick();
+ }
+
+ // jump back!
+ var pos = CachedPlayer.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));
+ var exit = (Vector3)Yoyo.markedLocation;
+ if (SubmergedCompatibility.IsSubmerged) {
+ SubmergedCompatibility.ChangeFloor(exit.y > -7);
+ }
+ MessageWriter writer = AmongUsClient.Instance.StartRpc(CachedPlayer.LocalPlayer.PlayerControl.NetId, (byte)CustomRPC.YoyoBlink, Hazel.SendOption.Reliable);
+ writer.Write((byte)0);
+ writer.WriteBytesAndSize(buff);
+ writer.EndMessage();
+ RPCProcedure.yoyoBlink(false, buff);
+
+ yoyoButton.Timer = yoyoButton.MaxTimer;
+ yoyoButton.isEffectActive = false;
+ yoyoButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ yoyoButton.HasEffect = false;
+ yoyoButton.Sprite = Yoyo.getMarkButtonSprite();
+ yoyoButton.buttonText = "Mark Location";
+ SoundEffectsManager.play("morphlingMorph");
+ if (Minigame.Instance) {
+ Minigame.Instance.Close();
+ }
+ },
+ buttonText: "Mark Location"
+ );
+
+ yoyoAdminTableButton = new CustomButton(
+ () => {
+ if (!MapBehaviour.Instance || !MapBehaviour.Instance.isActiveAndEnabled) {
+ HudManager __instance = FastDestroyableSingleton<HudManager>.Instance;
+ __instance.InitMap();
+ MapBehaviour.Instance.ShowCountOverlay(allowedToMove: true, showLivePlayerPosition: true, includeDeadBodies: true);
+ }
+ },
+ () => { return Yoyo.yoyo != null && Yoyo.yoyo == CachedPlayer.LocalPlayer.PlayerControl && Yoyo.hasAdminTable && !CachedPlayer.LocalPlayer.Data.IsDead; },
+ () => {
+ return true;
+ },
+ () => {
+ yoyoAdminTableButton.Timer = yoyoAdminTableButton.MaxTimer;
+ yoyoAdminTableButton.isEffectActive = false;
+ yoyoAdminTableButton.actionButton.cooldownTimerText.color = Palette.EnabledColor;
+ },
+ Hacker.getAdminSprite(),
+ CustomButton.ButtonPositions.lowerRowCenter,
+ __instance,
+ KeyCode.G,
+ true,
+ 0f,
+ () => {
+ yoyoAdminTableButton.Timer = yoyoAdminTableButton.MaxTimer;
+ if (MapBehaviour.Instance && MapBehaviour.Instance.isActiveAndEnabled) MapBehaviour.Instance.Close();
+ },
+ GameOptionsManager.Instance.currentNormalGameOptions.MapId == 3,
+ "ADMIN"
+ );
+
+
zoomOutButton = new CustomButton(
() => { Helpers.toggleZoom();
},
- () => { if (CachedPlayer.LocalPlayer.PlayerControl == null || !CachedPlayer.LocalPlayer.Data.IsDead || CachedPlayer.LocalPlayer.Data.Role.IsImpostor) return false;
+ () => { if (CachedPlayer.LocalPlayer.PlayerControl == null || !CachedPlayer.LocalPlayer.Data.IsDead || (CachedPlayer.LocalPlayer.Data.Role.IsImpostor && !CustomOptionHolder.deadImpsBlockSabotage.getBool())) return false;
var (playerCompleted, playerTotal) = TasksHandler.taskInfo(CachedPlayer.LocalPlayer.Data);
int numberOfLeftTasks = playerTotal - playerCompleted;
return numberOfLeftTasks <= 0 || !CustomOptionHolder.finishTasksBeforeHauntingOrZoomingOut.getBool();
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.MurderPlayer))]
[HarmonyPostfix]
public static void MurderPlayerPostfix(PlayerControl __instance, [HarmonyArgument(0)] PlayerControl target) {
- if (!PropHunt.isPropHuntGM) return;
+ if (!PropHunt.isPropHuntGM || target != CachedPlayer.LocalPlayer.PlayerControl) return;
try {
target.NetTransform.RpcSnapTo(__instance.transform.position);
} catch { }
public static CustomOption anyPlayerCanStopStart;
public static CustomOption enableEventMode;
+ public static CustomOption deadImpsBlockSabotage;
public static CustomOption mafiaSpawnRate;
public static CustomOption janitorCooldown;
public static CustomOption jackalSpawnRate;
public static CustomOption jackalKillCooldown;
public static CustomOption jackalCreateSidekickCooldown;
+ public static CustomOption jackalCanSabotageLights;
public static CustomOption jackalCanUseVents;
public static CustomOption jackalCanCreateSidekick;
public static CustomOption sidekickPromotesToJackal;
public static CustomOption sidekickCanKill;
public static CustomOption sidekickCanUseVents;
+ public static CustomOption sidekickCanSabotageLights;
public static CustomOption jackalPromotedFromSidekickCanCreateSidekick;
public static CustomOption jackalCanCreateSidekickFromImpostor;
public static CustomOption jackalAndSidekickHaveImpostorVision;
public static CustomOption trackerCanTrackCorpses;
public static CustomOption trackerCorpsesTrackingCooldown;
public static CustomOption trackerCorpsesTrackingDuration;
+ public static CustomOption trackerTrackingMethod;
public static CustomOption snitchSpawnRate;
public static CustomOption snitchLeftTasksForReveal;
public static CustomOption bomberBombCooldown;
public static CustomOption bomberBombActiveAfter;
+ public static CustomOption yoyoSpawnRate;
+ public static CustomOption yoyoBlinkDuration;
+ public static CustomOption yoyoMarkCooldown;
+ public static CustomOption yoyoMarkStaysOverMeeting;
+ public static CustomOption yoyoHasAdminTable;
+ public static CustomOption yoyoAdminTableCooldown;
+ public static CustomOption yoyoSilhouetteVisibility;
+
+
+
public static CustomOption modifiersAreHidden;
public static CustomOption modifierBait;
presetSelection = CustomOption.Create(0, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Preset"), presets, null, true);
activateRoles = CustomOption.Create(1, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Enable Mod Roles And Block Vanilla Roles"), true, null, true);
anyPlayerCanStopStart = CustomOption.Create(2, Types.General, cs(new Color(204f / 255f, 204f / 255f, 0, 1f), "Any Player Can Stop The Start"), false, null, false);
-
+
if (Utilities.EventUtility.canBeEnabled) enableEventMode = CustomOption.Create(10423, Types.General, cs(Color.green, "Enable Special Mode"), true, null, true);
// Using new id's for the options to not break compatibilty with older versions
bomberBombCooldown = CustomOption.Create(465, Types.Impostor, "Bomb Cooldown", 15f, 2.5f, 30f, 2.5f, bomberSpawnRate);
bomberBombActiveAfter = CustomOption.Create(466, Types.Impostor, "Bomb Is Active After", 3f, 0.5f, 15f, 0.5f, bomberSpawnRate);
+
+ yoyoSpawnRate = CustomOption.Create(470, Types.Impostor, cs(Yoyo.color, "Yo-Yo"), rates, null, true);
+ yoyoBlinkDuration = CustomOption.Create(471, Types.Impostor, "Blink Duration", 20f, 2.5f, 120f, 2.5f, yoyoSpawnRate);
+ yoyoMarkCooldown = CustomOption.Create(472, Types.Impostor, "Mark Location Cooldown", 20f, 2.5f, 120f, 2.5f, yoyoSpawnRate);
+ yoyoMarkStaysOverMeeting = CustomOption.Create(473, Types.Impostor, "Marked Location Stays After Meeting", true, yoyoSpawnRate);
+ yoyoHasAdminTable = CustomOption.Create(474, Types.Impostor, "Has Admin Table", true, yoyoSpawnRate);
+ yoyoAdminTableCooldown = CustomOption.Create(475, Types.Impostor, "Admin Table Cooldown", 20f, 2.5f, 120f, 2.5f, yoyoHasAdminTable);
+ yoyoSilhouetteVisibility = CustomOption.Create(476, Types.Impostor, "Silhouette Visibility", new string[] { "0%", "10%", "20%", "30%", "40%", "50%" }, yoyoSpawnRate);
+
+
guesserSpawnRate = CustomOption.Create(310, Types.Neutral, cs(Guesser.color, "Guesser"), rates, null, true);
guesserIsImpGuesserRate = CustomOption.Create(311, Types.Neutral, "Chance That The Guesser Is An Impostor", rates, guesserSpawnRate);
guesserNumberOfShots = CustomOption.Create(312, Types.Neutral, "Guesser Number Of Shots", 2f, 1f, 15f, 1f, guesserSpawnRate);
jackalKillCooldown = CustomOption.Create(221, Types.Neutral, "Jackal/Sidekick Kill Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate);
jackalCreateSidekickCooldown = CustomOption.Create(222, Types.Neutral, "Jackal Create Sidekick Cooldown", 30f, 10f, 60f, 2.5f, jackalSpawnRate);
jackalCanUseVents = CustomOption.Create(223, Types.Neutral, "Jackal Can Use Vents", true, jackalSpawnRate);
+ jackalCanSabotageLights = CustomOption.Create(431, Types.Neutral, "Jackal Can Sabotage Lights", true, jackalSpawnRate);
jackalCanCreateSidekick = CustomOption.Create(224, Types.Neutral, "Jackal Can Create A Sidekick", false, jackalSpawnRate);
sidekickPromotesToJackal = CustomOption.Create(225, Types.Neutral, "Sidekick Gets Promoted To Jackal On Jackal Death", false, jackalCanCreateSidekick);
sidekickCanKill = CustomOption.Create(226, Types.Neutral, "Sidekick Can Kill", false, jackalCanCreateSidekick);
sidekickCanUseVents = CustomOption.Create(227, Types.Neutral, "Sidekick Can Use Vents", true, jackalCanCreateSidekick);
+ sidekickCanSabotageLights = CustomOption.Create(432, Types.Neutral, "Sidekick Can Sabotage Lights", true, jackalCanCreateSidekick);
jackalPromotedFromSidekickCanCreateSidekick = CustomOption.Create(228, Types.Neutral, "Jackals Promoted From Sidekick Can Create A Sidekick", true, sidekickPromotesToJackal);
jackalCanCreateSidekickFromImpostor = CustomOption.Create(229, Types.Neutral, "Jackals Can Make An Impostor To His Sidekick", true, jackalCanCreateSidekick);
jackalAndSidekickHaveImpostorVision = CustomOption.Create(430, Types.Neutral, "Jackal And Sidekick Have Impostor Vision", false, jackalSpawnRate);
trackerCanTrackCorpses = CustomOption.Create(203, Types.Crewmate, "Tracker Can Track Corpses", true, trackerSpawnRate);
trackerCorpsesTrackingCooldown = CustomOption.Create(204, Types.Crewmate, "Corpses Tracking Cooldown", 30f, 5f, 120f, 5f, trackerCanTrackCorpses);
trackerCorpsesTrackingDuration = CustomOption.Create(205, Types.Crewmate, "Corpses Tracking Duration", 5f, 2.5f, 30f, 2.5f, trackerCanTrackCorpses);
+ trackerTrackingMethod = CustomOption.Create(206, Types.Crewmate, "How Tracker Gets Target Location", new string[] { "Arrow Only", "Proximity Dectector Only", "Arrow + Proximity" }, trackerSpawnRate);
snitchSpawnRate = CustomOption.Create(210, Types.Crewmate, cs(Snitch.color, "Snitch"), rates, null, true);
snitchLeftTasksForReveal = CustomOption.Create(219, Types.Crewmate, "Task Count Where The Snitch Will Be Revealed", 5f, 0f, 25f, 1f, snitchSpawnRate);
finishTasksBeforeHauntingOrZoomingOut = CustomOption.Create(9, Types.General, "Finish Tasks Before Haunting Or Zooming Out", true);
camsNightVision = CustomOption.Create(11, Types.General, "Cams Switch To Night Vision If Lights Are Off", false, null, true);
camsNoNightVisionIfImpVision = CustomOption.Create(12, Types.General, "Impostor Vision Ignores Night Vision Cams", false, camsNightVision, false);
+ deadImpsBlockSabotage = CustomOption.Create(13, Types.General, cs(Palette.ImpostorRed, "Block Dead Impostor From Sabotaging"), false, null, false);
dynamicMap = CustomOption.Create(500, Types.General, "Play On A Random Map", false, null, true);
return CachedPlayer.LocalPlayer.PlayerControl.myTasks.ToArray().Any((x) => x.TaskType == TaskTypes.MushroomMixupSabotage);
}
+
+ public static bool sabotageActive() {
+ var sabSystem = ShipStatus.Instance.Systems[SystemTypes.Sabotage].CastFast<SabotageSystemType>();
+ return sabSystem.AnyActive;
+ }
+
+ public static float sabotageTimer() {
+ var sabSystem = ShipStatus.Instance.Systems[SystemTypes.Sabotage].CastFast<SabotageSystemType>();
+ return sabSystem.Timer;
+ }
+ public static bool canUseSabotage() {
+ var sabSystem = ShipStatus.Instance.Systems[SystemTypes.Sabotage].CastFast<SabotageSystemType>();
+ ISystemType systemType;
+ IActivatable doors = null;
+ if (ShipStatus.Instance.Systems.TryGetValue(SystemTypes.Doors, out systemType)) {
+ doors = systemType.CastFast<IActivatable>();
+ }
+ return GameManager.Instance.SabotagesEnabled() && sabSystem.Timer <= 0f && !sabSystem.AnyActive && !(doors != null && doors.IsActive);
+ }
+
public static void setSemiTransparent(this PoolablePlayer player, bool value, float alpha=0.25f) {
alpha = value ? alpha : 1f;
foreach (SpriteRenderer r in player.gameObject.GetComponentsInChildren<SpriteRenderer>())
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "4.5.2";
+ public const string VersionString = "4.5.3";
public static uint betaDays = 0; // amount of days for the build to be usable (0 for infinite!)
public static Version Version = Version.Parse(VersionString);
}
}
} else if (text.ToLower().StartsWith("/ban ")) {
- string playerName = text.Substring(6);
+ string playerName = text.Substring(5);
PlayerControl target = CachedPlayer.AllPlayers.FirstOrDefault(x => x.Data.PlayerName.Equals(playerName));
if (target != null && AmongUsClient.Instance != null && AmongUsClient.Instance.CanBan()) {
var client = AmongUsClient.Instance.GetClient(target.OwnerId);
public static void Prefix(ref StringNames stringName, ref string value)
{
if (stringName == StringNames.GameKillDistance) {
- var index = GameOptionsManager.Instance.currentNormalGameOptions.KillDistance;
+ int index;
+ if (GameOptionsManager.Instance.currentGameMode == GameModes.Normal) {
+ index = GameOptionsManager.Instance.currentNormalGameOptions.KillDistance;
+ }
+ else {
+ index = GameOptionsManager.Instance.currentHideNSeekGameOptions.KillDistance;
+ }
value = GameOptionsData.KillDistanceStrings[index];
}
}
arrowBehaviour.target = target;
arrowBehaviour.Update();
}
+
+ public static void UpdateProximity(Vector3 position) {
+ if (!GameManager.Instance.GameHasStarted) return;
+
+ if (Tracker.DangerMeterParent == null) {
+ Tracker.DangerMeterParent = GameObject.Instantiate(GameObject.Find("ImpostorDetector"), HudManager.Instance.transform);
+ Tracker.Meter = Tracker.DangerMeterParent.transform.GetChild(0).GetComponent<DangerMeter>();
+ Tracker.DangerMeterParent.transform.localPosition = new(3.7f, -1.6f, 0);
+ var backgroundrend = Tracker.DangerMeterParent.transform.GetChild(0).GetChild(0).GetComponent<SpriteRenderer>();
+ backgroundrend.color = backgroundrend.color.SetAlpha(0.5f);
+ }
+ Tracker.DangerMeterParent.SetActive(MeetingHud.Instance == null && LobbyBehaviour.Instance == null && !Tracker.tracker.Data.IsDead && Tracker.tracked != null);
+ Tracker.Meter.gameObject.SetActive(MeetingHud.Instance == null && LobbyBehaviour.Instance == null && !Tracker.tracker.Data.IsDead && Tracker.tracked != null);
+ if (Tracker.tracker.Data.IsDead) return;
+ if (Tracker.tracked == null) {
+ Tracker.Meter.SetDangerValue(0, 0);
+ return;
+ }
+ if (Tracker.DangerMeterParent.transform.localPosition.x != 3.7f) Tracker.DangerMeterParent.transform.localPosition = new(3.7f, -1.6f, 0);
+ float num = float.MaxValue;
+ float dangerLevel1;
+ float dangerLevel2;
+
+ float sqrMagnitude = (position - Tracker.tracker.transform.position).sqrMagnitude;
+ if (sqrMagnitude < (55 * GameOptionsManager.Instance.currentNormalGameOptions.PlayerSpeedMod) && num > sqrMagnitude)
+ {
+ num = sqrMagnitude;
+ }
+
+ dangerLevel1 = Mathf.Clamp01((55 - num) / (55 - 15 * GameOptionsManager.Instance.currentNormalGameOptions.PlayerSpeedMod));
+ dangerLevel2 = Mathf.Clamp01((15 - num) / (15 * GameOptionsManager.Instance.currentNormalGameOptions.PlayerSpeedMod));
+
+ Tracker.Meter.SetDangerValue(dangerLevel1, dangerLevel2);
+ }
}
}
\ No newline at end of file
public HudManager hudManager;
public bool mirror;
public KeyCode? hotkey;
- private string buttonText;
+ public string buttonText;
public bool isHandcuffed = false;
private static readonly int Desat = Shader.PropertyToID("_Desat");
--- /dev/null
+using Innersloth.DebugTool;
+using LibCpp2IL.Elf;
+using Reactor.Utilities.Extensions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using TheOtherRoles.Utilities;
+using UnityEngine;
+
+namespace TheOtherRoles.Objects {
+ public class Silhouette {
+ public GameObject gameObject;
+ public float timeRemaining;
+ public bool permanent = false;
+ private bool visibleForEveryOne = false;
+ private SpriteRenderer renderer;
+
+ public static List<Silhouette> silhouettes = new List<Silhouette>();
+
+
+ private static Sprite SilhouetteSprite;
+ public static Sprite getSilhouetteSprite() {
+ if (SilhouetteSprite) return SilhouetteSprite;
+ SilhouetteSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.Silhouette.png", 225f);
+ return SilhouetteSprite;
+ }
+
+ public Silhouette(Vector3 p, float duration = 1f, bool visibleForEveryOne = true) {
+ if (duration <= 0f) {
+ TheOtherRolesPlugin.Logger.LogMessage("silhouette: permanent!");
+ permanent = true;
+ }
+ this.visibleForEveryOne = visibleForEveryOne;
+ gameObject = new GameObject("Silhouette");
+ gameObject.AddSubmergedComponent(SubmergedCompatibility.Classes.ElevatorMover);
+ //Vector3 position = new Vector3(p.x, p.y, CachedPlayer.LocalPlayer.transform.localPosition.z + 0.001f); // just behind player
+ Vector3 position = new Vector3(p.x, p.y, p.y / 1000f + 0.01f);
+ gameObject.transform.position = position;
+ gameObject.transform.localPosition = position;
+
+ renderer = gameObject.AddComponent<SpriteRenderer>();
+ renderer.sprite = getSilhouetteSprite();
+
+ timeRemaining = duration;
+
+ renderer.color = renderer.color.SetAlpha(Yoyo.SilhouetteVisibility);
+
+ bool visible = visibleForEveryOne || PlayerControl.LocalPlayer == Yoyo.yoyo || PlayerControl.LocalPlayer.Data.IsDead;
+
+ gameObject.SetActive(visible);
+ silhouettes.Add(this);
+ }
+
+ public static void clearSilhouettes() {
+ foreach (var sil in silhouettes)
+ sil.gameObject.Destroy();
+ silhouettes = new();
+ }
+
+ public static void UpdateAll() {
+ foreach (Silhouette current in new List<Silhouette>(silhouettes)) {
+ current.timeRemaining -= Time.fixedDeltaTime;
+ bool visible = current.visibleForEveryOne || PlayerControl.LocalPlayer == Yoyo.yoyo || PlayerControl.LocalPlayer.Data.IsDead;
+ current.gameObject.SetActive(visible);
+
+ if (visible && current.timeRemaining > 0 && current.timeRemaining < 0.5) {
+ var alphaRatio = current.timeRemaining / 0.5f;
+ current.renderer.color = current.renderer.color.SetAlpha(Yoyo.SilhouetteVisibility * alphaRatio);
+ }
+
+ if (current.timeRemaining < 0 && !current.permanent) {
+ TheOtherRolesPlugin.Logger.LogMessage($"update: permanent: {current.permanent}, time: {current.timeRemaining}");
+ current.gameObject.SetActive(false);
+ UnityEngine.Object.Destroy(current.gameObject);
+ silhouettes.Remove(current);
+ }
+ }
+ }
+ }
+}
trapRenderer.sprite = getTrapSprite();
trap.SetActive(false);
if (CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) trap.SetActive(true);
+ trapRenderer.color = Color.white * new Vector4(1, 1, 1, 0.5f);
this.instanceId = ++instanceCounter;
traps.Add(this);
arrow.Update(position);
FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(5, new Action<float>((x) => {
if (x == 1f) {
this.triggerable = true;
+ trapRenderer.color = Color.white;
}
})));
}
[HarmonyPatch(typeof(PingTracker), nameof(PingTracker.Update))]
internal static class PingTrackerPatch
{
- public 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 *= SubmergedCompatibility.Loaded ? 0 : 0.6f;
- }
- float offset = (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) ? 0.75f : 0f;
- modStamp.transform.position = FastDestroyableSingleton<HudManager>.Instance.MapButton.transform.position + Vector3.down * offset;
- }*/
static void Postfix(PingTracker __instance){
- __instance.text.alignment = TMPro.TextAlignmentOptions.TopRight;
+ __instance.text.alignment = TextAlignmentOptions.TopRight;
+ var position = __instance.GetComponent<AspectPosition>();
if (AmongUsClient.Instance.GameState == InnerNet.InnerNetClient.GameStates.Started) {
string gameModeText = $"";
if (HideNSeek.isHideNSeekGM) gameModeText = $"Hide 'N Seek";
else if (PropHunt.isPropHuntGM) gameModeText = "Prop Hunt";
if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n";
__instance.text.text = $"<size=130%><color=#ff351f>TheOtherRoles</color></size> v{TheOtherRolesPlugin.Version.ToString() + (TheOtherRolesPlugin.betaDays > 0 ? "-BETA" : "")}\n{gameModeText}" + __instance.text.text;
- if (CachedPlayer.LocalPlayer.Data.IsDead || (!(CachedPlayer.LocalPlayer.PlayerControl == null) && (CachedPlayer.LocalPlayer.PlayerControl == Lovers.lover1 || CachedPlayer.LocalPlayer.PlayerControl == Lovers.lover2))) {
- __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);
- }
+ position.DistanceFromEdge = new Vector3(2.25f, 0.11f, 0);
} else {
string gameModeText = $"";
if (TORMapOptions.gameMode == CustomGamemodes.HideNSeek) gameModeText = $"Hide 'N Seek";
else if (TORMapOptions.gameMode == CustomGamemodes.PropHunt) gameModeText = $"Prop Hunt";
if (gameModeText != "") gameModeText = Helpers.cs(Color.yellow, gameModeText) + "\n";
- __instance.text.text = $"{fullCredentialsVersion}\n {gameModeText + fullCredentials}\n {__instance.text.text}";
- __instance.transform.localPosition = new Vector3(3.5f, __instance.transform.localPosition.y, __instance.transform.localPosition.z);
+ var host = $"Host: {GameData.Instance?.GetHost()?.PlayerName}";
+ __instance.text.text = $"{fullCredentialsVersion}\n {gameModeText + fullCredentials}\n {host}\n {__instance.text.text}";
+ position.DistanceFromEdge = new Vector3(3.5f, 0.1f, 0);
}
+ position.AdjustPosition();
}
}
--- /dev/null
+using HarmonyLib;
+using TheOtherRoles.Players;
+using UnityEngine;
+
+namespace TheOtherRoles.Patches {
+
+ [HarmonyPatch]
+
+ public class DangerMeterPatch {
+
+ [HarmonyPatch(typeof(DangerMeter), nameof(DangerMeter.SetFirstNBarColors))]
+ [HarmonyPrefix]
+
+ public static void Prefix(DangerMeter __instance, ref Color color) {
+ if (CachedPlayer.LocalPlayer.PlayerControl != Tracker.tracker) return;
+ if (__instance == HudManager.Instance.DangerMeter) return;
+
+ color = color.SetAlpha(0.5f);
+ }
+ }
+}
\ No newline at end of file
FastDestroyableSingleton<HudManager>.Instance.StartCoroutine(Effects.Lerp(GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown / 2 + 2, new Action<float>((p) => {
if (p == 1f) foreach (Trap trap in Trap.traps) trap.triggerable = true;
})));
+
+ if (!Yoyo.markStaysOverMeeting)
+ Silhouette.clearSilhouettes();
}
}
[HarmonyPatch(typeof(MapBehaviour), nameof(MapBehaviour.FixedUpdate))]
static void Postfix(MapBehaviour __instance) {
+ if (__instance.infectedOverlay.gameObject.active && PlayerControl.LocalPlayer.Data.IsDead && CustomOptionHolder.deadImpsBlockSabotage.getBool()) {
+ __instance.Close();
+ }
+
__instance.HerePoint.transform.SetLocalZ(-2.1f);
if (Trapper.trapper != null && CachedPlayer.LocalPlayer.PlayerId == Trapper.trapper.PlayerId) {
foreach (PlayerControl player in Trapper.playersOnMap) {
}
}
+ // Show location of all players on the map for ghosts!
+ if (PlayerControl.LocalPlayer.Data.IsDead && (!PlayerControl.LocalPlayer.Data.Role.IsImpostor || CustomOptionHolder.deadImpsBlockSabotage.getBool())) {
+ foreach (PlayerControl player in CachedPlayer.AllPlayers) {
+ if (player == PlayerControl.LocalPlayer)
+ continue;
+ var alpha = player.Data.IsDead ? 0.25f : 1f;
+ Vector3 v = player.transform.position;
+ v /= MapUtilities.CachedShipStatus.MapScale;
+ v.x *= Mathf.Sign(MapUtilities.CachedShipStatus.transform.localScale.x);
+ v.z = -2.1f;
+ if (herePoints.ContainsKey(player)) {
+ herePoints[player].transform.localPosition = v;
+ herePoints[player].color = herePoints[player].color.SetAlpha(alpha);
+ continue;
+ }
+ var herePoint = UnityEngine.Object.Instantiate(__instance.HerePoint, __instance.HerePoint.transform.parent, true);
+ herePoint.transform.localPosition = v;
+ herePoint.enabled = true;
+
+ int colorId = player.CurrentOutfit.ColorId;
+ player.SetPlayerMaterialColors(herePoint);
+ herePoints.Add(player, herePoint);
+ }
+ }
foreach (var vent in MapUtilities.CachedShipStatus.AllVents) {
if ((vent.name.StartsWith("JackInThe") && !(PlayerControl.LocalPlayer == Trickster.trickster || PlayerControl.LocalPlayer.Data.IsDead))) continue; //for trickster vents
using TheOtherRoles.Utilities;
using UnityEngine;
using Innersloth.Assets;
+using TMPro;
namespace TheOtherRoles.Patches {
[HarmonyPatch]
EventUtility.meetingStartsUpdate();
}
+ [HarmonyPatch]
+ public class ShowHost {
+ private static TextMeshPro Text = null;
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))]
+ [HarmonyPostfix]
+
+ public static void Setup(MeetingHud __instance)
+ {
+ if (AmongUsClient.Instance.NetworkMode != NetworkModes.OnlineGame) return;
+
+ __instance.ProceedButton.gameObject.transform.localPosition = new(-2.5f, 2.2f, 0);
+ __instance.ProceedButton.gameObject.GetComponent<SpriteRenderer>().enabled = false;
+ __instance.ProceedButton.GetComponent<PassiveButton>().enabled = false;
+ __instance.HostIcon.gameObject.SetActive(true);
+ __instance.ProceedButton.gameObject.SetActive(true);
+ }
+
+ [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Update))]
+ [HarmonyPostfix]
+
+ public static void Postfix(MeetingHud __instance) {
+ var host = GameData.Instance.GetHost();
+
+ if (host != null)
+ {
+ PlayerMaterial.SetColors(host.DefaultOutfit.ColorId, __instance.HostIcon);
+ if (Text == null) Text = __instance.ProceedButton.gameObject.GetComponentInChildren<TextMeshPro>();
+ Text.text = $"host: {host.PlayerName}";
+ }
+ }
+ }
}
}
if (Tracker.arrow?.arrow != null) {
if (Tracker.tracker == null || CachedPlayer.LocalPlayer.PlayerControl != Tracker.tracker) {
Tracker.arrow.arrow.SetActive(false);
+ if (Tracker.DangerMeterParent) Tracker.DangerMeterParent.SetActive(false);
return;
}
- if (Tracker.tracker != null && Tracker.tracked != null && CachedPlayer.LocalPlayer.PlayerControl == Tracker.tracker && !Tracker.tracker.Data.IsDead) {
+ if (Tracker.tracked != null && !Tracker.tracker.Data.IsDead) {
Tracker.timeUntilUpdate -= Time.fixedDeltaTime;
if (Tracker.timeUntilUpdate <= 0f) {
}
}
- Tracker.arrow.Update(position);
- Tracker.arrow.arrow.SetActive(trackedOnMap);
+ if (Tracker.trackingMode == 1 || Tracker.trackingMode == 2) Arrow.UpdateProximity(position);
+ if (Tracker.trackingMode == 0 || Tracker.trackingMode == 2) {
+ Tracker.arrow.Update(position);
+ Tracker.arrow.arrow.SetActive(trackedOnMap);
+ }
Tracker.timeUntilUpdate = Tracker.updateIntervall;
} else {
- Tracker.arrow.Update();
+ if (Tracker.trackingMode == 0 || Tracker.trackingMode == 2) Tracker.arrow.Update();
}
+ }
+ else if (Tracker.tracker.Data.IsDead) {
+ Tracker.DangerMeterParent?.SetActive(false);
+ Tracker.Meter?.gameObject.SetActive(false);
}
}
ninjaUpdate();
// Thief
thiefSetTarget();
+ // yoyo
+ Silhouette.UpdateAll();
hackerUpdate();
swapperUpdate();
impSettings.Add((byte)RoleId.Witch, CustomOptionHolder.witchSpawnRate.getSelection());
impSettings.Add((byte)RoleId.Ninja, CustomOptionHolder.ninjaSpawnRate.getSelection());
impSettings.Add((byte)RoleId.Bomber, CustomOptionHolder.bomberSpawnRate.getSelection());
+ impSettings.Add((byte)RoleId.Yoyo, CustomOptionHolder.yoyoSpawnRate.getSelection());
neutralSettings.Add((byte)RoleId.Jester, CustomOptionHolder.jesterSpawnRate.getSelection());
neutralSettings.Add((byte)RoleId.Arsonist, CustomOptionHolder.arsonistSpawnRate.getSelection());
static void updateSabotageButton(HudManager __instance) {
if (MeetingHud.Instance || TORMapOptions.gameMode == CustomGamemodes.HideNSeek || TORMapOptions.gameMode == CustomGamemodes.PropHunt) __instance.SabotageButton.Hide();
+ if (PlayerControl.LocalPlayer.Data.IsDead && CustomOptionHolder.deadImpsBlockSabotage.getBool()) __instance.SabotageButton.Hide();
}
static void updateMapButton(HudManager __instance) {
if (HideNSeek.isHideNSeekGM)
return HideNSeek.canSabotage;
if (PropHunt.isPropHuntGM) return false;
+ if (PlayerControl.LocalPlayer.Data.IsDead && CustomOptionHolder.deadImpsBlockSabotage.getBool()) {
+ __instance.ShowNormalMap();
+ return false;
+ }
return true;
}
}
Ninja,
Thief,
Bomber,
+ Yoyo,
Crewmate,
Impostor,
// Modifier ---
PlaceBomb,
DefuseBomb,
ShareRoom,
+ YoyoMarkLocation,
+ YoyoBlink,
// Gamemode
SetGuesserGm,
Garlic.clearGarlics();
JackInTheBox.clearJackInTheBoxes();
NinjaTrace.clearTraces();
+ Silhouette.clearSilhouettes();
Portal.clearPortals();
Bloodytrail.resetSprites();
Trap.clearTraps();
case RoleId.Bomber:
Bomber.bomber = player;
break;
+ case RoleId.Yoyo:
+ Yoyo.yoyo = player;
+ break;
}
if (AmongUsClient.Instance.AmHost && Helpers.roleCanUseVents(player) && !player.Data.Role.IsImpostor) {
player.RpcSetRole(RoleTypes.Engineer);
if (player == Witch.witch) Witch.clearAndReload();
if (player == Ninja.ninja) Ninja.clearAndReload();
if (player == Bomber.bomber) Bomber.clearAndReload();
+ if (player == Yoyo.yoyo) Yoyo.clearAndReload();
// Other roles
if (player == Jester.jester) Jester.clearAndReload();
}
if (target == Ninja.ninja) Ninja.ninja = thief;
if (target == Bomber.bomber) Bomber.bomber = thief;
+ if (target == Yoyo.yoyo) {
+ Yoyo.yoyo = thief;
+ Yoyo.markedLocation = null;
+ }
if (target.Data.Role.IsImpostor) {
RoleManager.Instance.SetRole(Thief.thief, RoleTypes.Impostor);
FastDestroyableSingleton<HudManager>.Instance.KillButton.SetCoolDown(Thief.thief.killTimer, GameOptionsManager.Instance.currentNormalGameOptions.KillCooldown);
if (Snitch.playerRoomMap.ContainsKey(playerId)) Snitch.playerRoomMap[playerId] = roomId;
else Snitch.playerRoomMap.Add(playerId, roomId);
}
+
+ public static void yoyoMarkLocation(byte[] buff) {
+ if (Yoyo.yoyo == null) return;
+ Vector3 position = Vector3.zero;
+ position.x = BitConverter.ToSingle(buff, 0 * sizeof(float));
+ position.y = BitConverter.ToSingle(buff, 1 * sizeof(float));
+ Yoyo.markLocation(position);
+ new Silhouette(position, -1, false);
+ }
+
+ public static void yoyoBlink(bool isFirstJump, byte[] buff) {
+ TheOtherRolesPlugin.Logger.LogMessage($"blink fistjumpo: {isFirstJump}");
+ if (Yoyo.yoyo == null || Yoyo.markedLocation == null) return;
+ var markedPos = (Vector3)Yoyo.markedLocation;
+ Yoyo.yoyo.NetTransform.SnapTo(markedPos);
+
+ var markedSilhouette = Silhouette.silhouettes.FirstOrDefault(s => s.gameObject.transform.position.x == markedPos.x && s.gameObject.transform.position.y == markedPos.y);
+ if (markedSilhouette != null)
+ markedSilhouette.permanent = false;
+
+ Vector3 position = Vector3.zero;
+ position.x = BitConverter.ToSingle(buff, 0 * sizeof(float));
+ position.y = BitConverter.ToSingle(buff, 1 * sizeof(float));
+ // Create Silhoutte At Start Position:
+ if (isFirstJump) {
+ Yoyo.markLocation(position);
+ new Silhouette(position, Yoyo.blinkDuration, true);
+ } else {
+ new Silhouette(position, 5, true);
+ Yoyo.markedLocation = null;
+ }
+ if (Chameleon.chameleon.Any(x => x.PlayerId == Yoyo.yoyo.PlayerId)) // Make the Yoyo visible if chameleon!
+ Chameleon.lastMoved[Yoyo.yoyo.PlayerId] = Time.time;
+ }
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.HandleRpc))]
case (byte)CustomRPC.StopStart:
RPCProcedure.stopStart(reader.ReadByte());
break;
+ case (byte)CustomRPC.YoyoMarkLocation:
+ RPCProcedure.yoyoMarkLocation(reader.ReadBytesAndSize());
+ break;
+ case (byte)CustomRPC.YoyoBlink:
+ RPCProcedure.yoyoBlink(reader.ReadByte() == byte.MaxValue, reader.ReadBytesAndSize());
+ break;
// Game mode
case (byte)CustomRPC.SetGuesserGm:
public static RoleInfo ninja = new RoleInfo("Ninja", Ninja.color, "Surprise and assassinate your foes", "Surprise and assassinate your foes", RoleId.Ninja);
public static RoleInfo thief = new RoleInfo("Thief", Thief.color, "Steal a killers role by killing them", "Steal a killers role", RoleId.Thief, true);
public static RoleInfo bomber = new RoleInfo("Bomber", Bomber.color, "Bomb all Crewmates", "Bomb all Crewmates", RoleId.Bomber);
+ public static RoleInfo yoyo = new RoleInfo("Yo-Yo", Yoyo.color, "Blink to a marked location and Back", "Blink to a location", RoleId.Yoyo);
public static RoleInfo hunter = new RoleInfo("Hunter", Palette.ImpostorRed, Helpers.cs(Palette.ImpostorRed, "Seek and kill everyone"), "Seek and kill everyone", RoleId.Impostor);
public static RoleInfo hunted = new RoleInfo("Hunted", Color.white, "Hide", "Hide", RoleId.Crewmate);
witch,
ninja,
bomber,
+ yoyo,
goodGuesser,
badGuesser,
lover,
if (p == Witch.witch) infos.Add(witch);
if (p == Ninja.ninja) infos.Add(ninja);
if (p == Bomber.bomber) infos.Add(bomber);
+ if (p == Yoyo.yoyo) infos.Add(yoyo);
if (p == Detective.detective) infos.Add(detective);
if (p == TimeMaster.timeMaster) infos.Add(timeMaster);
if (p == Medic.medic) infos.Add(medic);
Version = plugin.Metadata.Version.BaseVersion();
Assembly = Plugin!.GetType().Assembly;
}
-
- CredentialsPatch.PingTrackerPatch.modStamp = new GameObject();
- Object.DontDestroyOnLoad(CredentialsPatch.PingTrackerPatch.modStamp);
Types = AccessTools.GetTypesFromAssembly(Assembly);
using static TheOtherRoles.TheOtherRoles;
using AmongUs.Data;
using Hazel;
+using Reactor.Utilities.Extensions;
namespace TheOtherRoles
{
Thief.clearAndReload();
Trapper.clearAndReload();
Bomber.clearAndReload();
+ Yoyo.clearAndReload();
// Modifier
Bait.clearAndReload();
public static class Tracker {
public static PlayerControl tracker;
public static Color color = new Color32(100, 58, 220, byte.MaxValue);
- public static List<Arrow> localArrows = new List<Arrow>();
+ public static List<Arrow> localArrows = new();
public static float updateIntervall = 5f;
public static bool resetTargetAfterMeeting = false;
public static float corpsesTrackingCooldown = 30f;
public static float corpsesTrackingDuration = 5f;
public static float corpsesTrackingTimer = 0f;
- public static List<Vector3> deadBodyPositions = new List<Vector3>();
+ public static int trackingMode = 0;
+ public static List<Vector3> deadBodyPositions = new();
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);
+ public static Arrow arrow = new(Color.blue);
+
+ public static GameObject DangerMeterParent;
+ public static DangerMeter Meter;
private static Sprite trackCorpsesButtonSprite;
public static Sprite getTrackCorpsesButtonSprite()
corpsesTrackingCooldown = CustomOptionHolder.trackerCorpsesTrackingCooldown.getFloat();
corpsesTrackingDuration = CustomOptionHolder.trackerCorpsesTrackingDuration.getFloat();
canTrackCorpses = CustomOptionHolder.trackerCanTrackCorpses.getBool();
+ trackingMode = CustomOptionHolder.trackerTrackingMethod.getSelection();
+ if (DangerMeterParent) {
+ Meter.gameObject.Destroy();
+ DangerMeterParent.Destroy();
+ }
}
}
public static bool wasTeamRed;
public static bool wasImpostor;
public static bool wasSpy;
+ public static bool canSabotageLights;
public static Sprite getSidekickButtonSprite() {
if (buttonSprite) return buttonSprite;
formerJackals.Clear();
hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool();
wasTeamRed = wasImpostor = wasSpy = false;
+ canSabotageLights = CustomOptionHolder.jackalCanSabotageLights.getBool();
}
}
public static bool canKill = true;
public static bool promotesToJackal = true;
public static bool hasImpostorVision = false;
+ public static bool canSabotageLights;
public static void clearAndReload() {
sidekick = null;
promotesToJackal = CustomOptionHolder.sidekickPromotesToJackal.getBool();
hasImpostorVision = CustomOptionHolder.jackalAndSidekickHaveImpostorVision.getBool();
wasTeamRed = wasImpostor = wasSpy = false;
+ canSabotageLights = CustomOptionHolder.sidekickCanSabotageLights.getBool();
}
}
}
}
+ public static class Yoyo {
+ public static PlayerControl yoyo = null;
+ public static Color color = Palette.ImpostorRed;
+
+ public static float blinkDuration = 0;
+ public static float markCooldown = 0;
+ public static bool markStaysOverMeeting = false;
+ public static bool hasAdminTable = false;
+ public static float adminCooldown = 0;
+ public static float SilhouetteVisibility => (silhouetteVisibility == 0 && (PlayerControl.LocalPlayer == yoyo || PlayerControl.LocalPlayer.Data.IsDead)) ? 0.1f : silhouetteVisibility;
+ public static float silhouetteVisibility = 0;
+
+ public static Vector3? markedLocation = null;
+
+ private static Sprite markButtonSprite;
+
+ public static Sprite getMarkButtonSprite() {
+ if (markButtonSprite) return markButtonSprite;
+ markButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.YoyoMarkButtonSprite.png", 115f);
+ return markButtonSprite;
+ }
+ private static Sprite blinkButtonSprite;
+
+ public static Sprite getBlinkButtonSprite() {
+ if (blinkButtonSprite) return blinkButtonSprite;
+ blinkButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.YoyoBlinkButtonSprite.png", 115f);
+ return blinkButtonSprite;
+ }
+
+ public static void markLocation(Vector3 position) {
+ markedLocation = position;
+ }
+
+ public static void clearAndReload() {
+ blinkDuration = CustomOptionHolder.yoyoBlinkDuration.getFloat();
+ markCooldown = CustomOptionHolder.yoyoMarkCooldown.getFloat();
+ markStaysOverMeeting = CustomOptionHolder.yoyoMarkStaysOverMeeting.getBool();
+ hasAdminTable = CustomOptionHolder.yoyoHasAdminTable.getBool();
+ adminCooldown = CustomOptionHolder.yoyoAdminTableCooldown.getFloat();
+ silhouetteVisibility = CustomOptionHolder.yoyoSilhouetteVisibility.getSelection() / 10f;
+
+ markedLocation = null;
+
+ }
+ }
+
// Modifier
public static class Bait {
public static List<PlayerControl> bait = new List<PlayerControl>();
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
- <Version>4.5.1</Version>
+ <Version>4.5.3</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
<LangVersion>latest</LangVersion>
<ItemGroup>
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.3.5" />
- <PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.671" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" />
+ <PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.688" />
<PackageReference Include="Reactor" Version="2.2.0" />
</ItemGroup>
},
"BepInEx.Unity.IL2CPP": {
"type": "Direct",
- "requested": "[6.0.0-be.671, )",
- "resolved": "6.0.0-be.671",
- "contentHash": "XpmrBitEO7pe5YG9VHqn0nuelEF5Dp0Rpkxa2F7PFZF1a00KSsdCL809++HYnGzLumNOCIt7mO7yyDNP5k25mw==",
+ "requested": "[6.0.0-be.688, )",
+ "resolved": "6.0.0-be.688",
+ "contentHash": "a68J6PA9AdgYlj8HeuEK1hM4Vv+j1x/6TflK5/PaYAd05/TPdjNI09LsSTIOaS9hdWZwCcpPEw/HxLhXaQKodw==",
"dependencies": {
- "BepInEx.Core": "6.0.0-be.671",
- "BepInEx.Unity.Common": "6.0.0-be.671",
+ "BepInEx.Core": "6.0.0-be.688",
+ "BepInEx.Unity.Common": "6.0.0-be.688",
"HarmonyX": "2.10.1",
"Iced": "1.18.0",
- "Il2CppInterop.Generator": "1.4.6-ci.367",
- "Il2CppInterop.HarmonySupport": "1.4.6-ci.367",
- "Il2CppInterop.Runtime": "1.4.6-ci.367",
+ "Il2CppInterop.Generator": "1.4.6-ci.389",
+ "Il2CppInterop.HarmonySupport": "1.4.6-ci.389",
+ "Il2CppInterop.Runtime": "1.4.6-ci.389",
"MonoMod.RuntimeDetour": "22.5.1.1",
"Samboy063.Cpp2IL.Core": "2022.1.0-development.866"
}
"AsmResolver": "5.1.0"
}
},
+ "AssetRipper.Primitives": {
+ "type": "Transitive",
+ "resolved": "2.1.0",
+ "contentHash": "nrgwUMa69ztOfMKbmytNI3mU/fuN3eWptq2Mu8aLg2ly394w51KJbUx5z477HbRvabD8TD8rM4wRe6EBqB2xdA=="
+ },
"AssetRipper.VersionUtilities": {
"type": "Transitive",
- "resolved": "1.3.1",
- "contentHash": "/1D18NOpqm7rS7+qVUbAReVMNWwgDuYpsnf0RkX1Aah/I0a0irqldoFJ+Kku7YdbSm0zqD+Z6iYUGKoP3GwTNQ=="
+ "resolved": "1.2.1",
+ "contentHash": "knQ/W1YwTZS2AbdaZh+nhyyBEZQPQDcahhSOnh88i+cdg4/FEoFJiCwaavgFvCMNuUeHTbcZc4qttF8WQ8xLIA=="
},
"BepInEx.Core": {
"type": "Transitive",
- "resolved": "6.0.0-be.671",
- "contentHash": "Hp9QsgwtlGuNULFq5tF8YHrVD5R5WA5nMgFTJbcScWsGJDq7KPEhiLNGvKHAxcmMM8GA0CKM+jn8cJPtlMKvNQ==",
+ "resolved": "6.0.0-be.688",
+ "contentHash": "34VErIttx6PckRukoaRXIb6ZF5oby+9Oktt9E3ZafOGzA0lBWOmY/BKHfg766XZBplE06KijHp+vrryCjfLP7Q==",
"dependencies": {
"HarmonyX": "2.10.1",
"MonoMod.Utils": "22.5.1.1",
},
"BepInEx.Unity.Common": {
"type": "Transitive",
- "resolved": "6.0.0-be.671",
- "contentHash": "05W7ERsHizZl0MacTAhiGkcihZl2b0DYBP0e6gA1Vzoay+teQ/mHy3zorGu1r8lLWlesH4LXl89HwFjrMA1ZKw==",
+ "resolved": "6.0.0-be.688",
+ "contentHash": "RRPG9sEgIdGgut0LOSQopW+6mMpE0LDggKZWfLBBeaJj1p0WFrVBCqEAruFLO0FrwT/+dEC7EtV62n13RV1JfA==",
"dependencies": {
"AssetRipper.VersionUtilities": "1.2.1",
"MonoMod.Utils": "22.5.1.1"
},
"Il2CppInterop.Common": {
"type": "Transitive",
- "resolved": "1.4.6-ci.367",
- "contentHash": "zviSAk0COGdxx1KLTJlSi6lyvfRjbPO4qhj9RvLAU5zP74hwAkjhim0WsSo9clkkyGS+KXDrjnUEHqfXuMMm9Q==",
+ "resolved": "1.4.6-ci.389",
+ "contentHash": "KTyf2f66Q2mS9a6xhvoPWwD0VzRIoskPMVQehaVMo1dx4Di1FPFsKqukQ+ob6wgP5Rbnx53kiH0PY+IRSb35XA==",
"dependencies": {
"Iced": "1.17.0",
"Microsoft.Extensions.Logging.Abstractions": "6.0.1",
},
"Il2CppInterop.Generator": {
"type": "Transitive",
- "resolved": "1.4.6-ci.367",
- "contentHash": "VP/1LX3wEy0/5BCrgggzIm9niyRGp/zjXaxFJip1D2bvM7oIXwK0LR0Rp8BE/uxKW8aQjVYxxwVwsdXPS/1KZw==",
+ "resolved": "1.4.6-ci.389",
+ "contentHash": "XJX4sn/RzAEBNIZZXuyDcLZrFx5tCoahujDu5aKWjApiMj/nTRQAxZn17daIIvd9aHqLaAfiWWth5QDLIt2bKw==",
"dependencies": {
- "Il2CppInterop.Common": "1.4.6-ci.367",
+ "Il2CppInterop.Common": "1.4.6-ci.389",
"Mono.Cecil": "0.11.3"
}
},
"Il2CppInterop.HarmonySupport": {
"type": "Transitive",
- "resolved": "1.4.6-ci.367",
- "contentHash": "rKb4Bt4cZQNWtI1L1/n/m5jJelP8pwAcQHwN/gaUAcobnapGZPJvRp8gyLQRvthGm3sxeleIQelEsv6L+21A2g==",
+ "resolved": "1.4.6-ci.389",
+ "contentHash": "Xd2zK6sLqwA0ReyUy83SWKcALwY99fq6vVL5gFOMN5Ct8OOVhtNIws0ysCWsfx1GL0RJiRYfR4pza/MmWRz4PQ==",
"dependencies": {
"HarmonyX": "2.10.0",
- "Il2CppInterop.Runtime": "1.4.6-ci.367"
+ "Il2CppInterop.Runtime": "1.4.6-ci.389"
}
},
"Il2CppInterop.Runtime": {
"type": "Transitive",
- "resolved": "1.4.6-ci.367",
- "contentHash": "Wb4rj/ESlfiYljfVXskndbkYNSX1WxPcsylrYex39QnVzujk6tRQUbHLA7PZn509z6P5wYbqJvhodzJ+w9Kk4A==",
+ "resolved": "1.4.6-ci.389",
+ "contentHash": "nzYjuVzDG/O8+AQ8ax3WVHf+ZVsQKrVtp/yvuW8XoEPLdkbgryIrMo75dvN+tPqxOU3o2MPt9MZ9j25P5aVJMw==",
"dependencies": {
"Iced": "1.17.0",
- "Il2CppInterop.Common": "1.4.6-ci.367"
+ "Il2CppInterop.Common": "1.4.6-ci.389"
}
},
"js6pak.Gee.External.Capstone": {
},
"Samboy063.LibCpp2IL": {
"type": "Transitive",
- "resolved": "2022.1.0-development.866",
- "contentHash": "EcJSWTfQko8SQozozoUT+aQJVWC8wj8C0YUiPBF4rNRId19yTKp2IXcXnI747v+8/aal222QR6WHfoBK2WlVLg==",
+ "resolved": "2022.1.0-pre-release.13",
+ "contentHash": "fwkVLAXqD+7XfIXJEDh2OkJqe6YMkNmOP05oudEnuq3ITEuVUSb0K8ffa22vx+n/NDIyNP+s1lLRFcmHVP57oQ==",
"dependencies": {
- "AssetRipper.VersionUtilities": "1.3.1",
- "Samboy063.WasmDisassembler": "2022.1.0-development.866"
+ "AssetRipper.Primitives": "2.1.0",
+ "Samboy063.WasmDisassembler": "2022.1.0-pre-release.13"
}
},
"Samboy063.WasmDisassembler": {
"type": "Transitive",
- "resolved": "2022.1.0-development.866",
- "contentHash": "2QK8F3eISkLcoW/Nt7wtxyQh31EXAqEMBVXXjEnuxyvXpBXxOIGpjmE3pWhGwxQwDCbe4zBsLF1zUiqq3/pXRA=="
+ "resolved": "2022.1.0-pre-release.13",
+ "contentHash": "dI4PDG+AFctYzaD0pqojK7SGiiPi6xgIRXp07hHnXumW+DYbS2nVQ8nTFSHiP6xE7UhW6I+PpbC5jh/2nksW+Q=="
},
"SemanticVersioning": {
"type": "Transitive",
},
"StableNameDotNet": {
"type": "Transitive",
- "resolved": "0.1.0-development.866",
- "contentHash": "DJb8x4dV3ucDgM12sU28HxCdeqY/JudJbeJG6mgXWDJzpFZw2Jd7JF1pKFRNLzh/0gpo8bCGLQS9gOWZmBjdtg=="
+ "resolved": "0.1.0-pre-release.13",
+ "contentHash": "8ovX1WcVUO6R1pHzaW1v1SC9coSWSYh5JjjZ/vvr7jsb7+hWzMVpsV+uyWLMie9olE2FW1k4RLaXf5GmvlVwMg=="
},
"System.Collections": {
"type": "Transitive",
<packageSources>
<add key="BepInEx" value="https://nuget.bepinex.dev/v3/index.json" />
<add key="Reactor" value="https://nuget.reactor.gg/v3/index.json" />
- <add key="Samboy063" value="https://nuget.samboy.dev/v3/index.json" />
</packageSources>
</configuration>