# Releases :\r
| Among Us - Version| Mod Version | Link |\r
|----------|-------------|-----------------|\r
-| **2021.3.5s** | v1.4 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip)\r
-| **2021.3.5s** | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip)\r
+| 2021.3.5s | v1.5 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.5/TheOtherRoles.zip)\r
+| 2021.3.5s | v1.4 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip)\r
+| 2021.3.5s | v1.3 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.3/TheOtherRoles.zip)\r
| 2020.12.19s | v1.1 | [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.1/TheOtherRoles.zip)\r
| 2020.12.19s | v1.0 | [Download](https://github.com/Eisbison/TheOtherRoles/files/6097191/TheOtherRoles.zip) |\r
\r
\\r
+**Changes in v1.5:**\r
+- Time Master - Buff: He is not affected by his rewind anymore, which gives him more utility. Players will now be rewinded out of vents.\r
+- Child - Nerf: The child now grows up (see [child](#child)) and becomes a normal crewmate at some point. A growing child is not killable anymore. Some tasks are still not doable for the small child, we are working on that. But eventually when growing up it can do all the tasks as it's size increases.\r
+- Seer - Nerf: Added an option that sets how often the Seer mistakes the player for another.\r
+- Spy - Nerf: The Spy now only sees the additional information when he activates his "Spy mode". That should stops the spy from camping the admin table/vitals.\r
+- Other: Camouflager/Morphling cooldowns were fixed. Custom regions code was removed to enable 3rd party tools. Some minor bugfixes.\r
+\r
**Changes in v1.4:**\r
- Fixing a Camoflager/Morphling animation bug\r
- Fixing a bug where the Swapper could swap votes even if they was dead\r
- A bug was removed where the game of the Lovers crashed if they were the last players killed by the host of the lobby.\r
\r
# Installation \r
-1. Download and unzip the newest [release](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.4/TheOtherRoles.zip)\r
+1. Download and unzip the newest [release](https://github.com/Eisbison/TheOtherRoles/releases/download/v1.5/TheOtherRoles.zip)\r
2. Find the folder of your game, for steams players you can right click in steam, on the game, a menu will appear proposing you to go to the folders.3. Make a copy of your game, it's not obligatory but advise, put it where you want.\r
4. Drag or extract the files from the zip into your game, at the .exe level.\r
6. Run the game (the first launch might take a while)\r
\r
\r
\r
+# Custom Servers\r
+We highly recommend you to play on custom servers rather than on the official ones. A guide on how to set up a server will be added soon. In order to make your client able to connect to a custom server we recommend using [Unify](https://github.com/moltenmods/unify). Just add\r
+the *.dll* file into your Among Us/BepInEx/plugins folder and you're good to go.\r
+\r
# Credits & Resources\r
[Reactor](https://github.com/NuclearPowered/Reactor) - The framework used\\r
[BepInEx](https://github.com/BepInEx) - Used to hook game functions\\r
Depending on the selected option they either directly see the role of the player (appended to their name) or if the player is \r
a good (name text changes to yellow) or a bad (name text changes to a darker color) player.\\r
Depending on the selected option the player will get a blue flash on their screen if they are being revealed by the Seer.\r
+The Seer can make mistakes (how many depends on the selected option). If a mistake happens, the information of another player (not the target, not the Seer) gets revealed.\r
+\r
\\r
**NOTE**\r
- **Good players:** Crewmates with all their special roles\\r
|----------|:-------------:|:-------------:|\r
| Seer Spawn Chance | - | -\r
| Seer Cooldown | Cooldown for his special ability. This cooldown will **not** be reset after a meeting | -\r
+| Seer Chance Of Seeing Right | Sets the chance of how likely the Seer makes a mistake | -\r
| Info That Seer Reveals | Description above | "Role", "Good/Bad"\r
| Players That See When They Are Being Revealed | Selects the group of players that get notified when they are being revealed | "Everyone", "The Good", "The Bad", "Nobody"\r
-----------------------\r
### **Team: Crewmates**\r
The Engineer (if alive) can fix one sabotage per game from anywhere on the map.\\r
The Engineer can use vents.\r
+Because of the vents the Engineer might not be able to start some tasks using the "Use" button,\r
+you can double click on the tasks instead.\r
\\r
**NOTE**\r
- The kill button of Impostors activates if they stand next to a vent where the Engineer is. They can also kill them there. No other action (e.g. Morphling sample, Shifter shift, ...) can affect players inside vents.\r
\r
## Child\r
### **Team: Crewmates**\r
-The Child won't be harmed by anyone, since if it dies everyone besides the child will lose the game.\\r
+The Child won't be harmed by anyone until it turns 18 years old, then it becomes a regular crewmate.\\r
The Child's character is smaller and hence visible to everyone in the game.\\r
-The child works with the crewmate and aims to play out the strength of its "invincibility".\\r
-It does **not** want to win the game as a dead child and the impostors **don't want** to deny\r
-a crewmate win by killing a child (changes will come to prevent this behaviour).\r
+The child works with the crewmates and aims to play out the strength of its "invincibility" in the early game.\\r
+If the child is voted out before it turns 18, everyone in the lobby loses.\r
+\\r
+**NOTE**\r
+- Impostors can't kill the child (the button does not work) until it turns 18\r
+- The Sheriff always suicides if he tries to kill the child\r
\r
### Game Options\r
| Name | Description |\r
|----------|:-------------:|\r
| Child Spawn Chance | -\r
+| Child | Child Growing Up Duration\r
-----------------------\r
\r
## Medic\r
\r
## Spy\r
### **Team: Crewmates**\r
-The Spy gets more information than others from the admin table and vitals. \\r
+If the Spy activates the "Spy mode", the Spy gets more information than others from the admin table and vitals for a set duration.\\r
+Otherwise he sees the same information as everyone else.\r
**Admin table:** The spy can see the colors of the players on the table.\\r
**Vitals**: The spy can see how long dead players have been dead for.\\r
\\r
| Name | Description |\r
|----------|:-------------:|\r
| Spy Spawn Chance | -\r
+| Spy Cooldown| -\r
+| Spy Duration | Sets how long the "Spy mode" remains active\r
-----------------------\r
\r
\r
\r
## Time Master\r
### **Team: Crewmates**\r
-The Time Master can rewind the time.\\r
+The Time Master can rewind the time. The Time Master won't be affected by the rewind.\\r
If set in the options, players will be revived during the time rewind.\\r
Only movement and kills are affected (not cooldowns, meetings, tasks, ...)\r
\r
--- /dev/null
+bin
+obj
\ No newline at end of file
[HarmonyPatch]
public static class BonusRoles
{
- public static System.Random rnd = new System.Random();
+ public static System.Random rnd = new System.Random((int)DateTime.Now.Ticks);
public static void clearAndReloadRoles() {
Jester.clearAndReload();
public static class Seer {
public static PlayerControl seer;
public static Color color = new Color(60f / 255f, 181f / 255f, 100f / 255f, 1);
- public static List<PlayerControl> revealedPlayers = new List<PlayerControl>();
+ public static Dictionary<PlayerControl, PlayerControl> revealedPlayers = new Dictionary<PlayerControl, PlayerControl>();
public static float cooldown = float.MaxValue;
public static int kindOfInfo = 0;
public static int playersWithNotification = 0;
+ public static float chanceOfSeeingRight = 100;
public static PlayerControl currentTarget;
public static void clearAndReload() {
seer = null;
- revealedPlayers = new List<PlayerControl>();
+ revealedPlayers = new Dictionary<PlayerControl, PlayerControl>();
cooldown = BonusRolesPlugin.seerCooldown.GetValue();
kindOfInfo = BonusRolesPlugin.seerKindOfInfo.GetValue();
playersWithNotification = BonusRolesPlugin.seerPlayersWithNotification.GetValue();
+ chanceOfSeeingRight = BonusRolesPlugin.seerChanceOfSeeingRight.GetValue();
}
}
private static Sprite adminTableIcon;
public static Color color = new Color(252f / 255f, 90f / 255f, 30f / 255f, 1);
+ public static float cooldown = float.MaxValue;
+ public static float duration = 10f;
+
+ public static float spyTimer = 0f;
+
public static Sprite getAdminTableIconSprite() {
if (adminTableIcon) return adminTableIcon;
adminTableIcon = Helpers.loadSpriteFromResources("BonusRoles.Resources.AdminTableIcon.png", 350f);
return adminTableIcon;
}
+ private static Sprite buttonSprite;
+ public static Sprite getButtonSprite() {
+ if (buttonSprite) return buttonSprite;
+ buttonSprite = Helpers.loadSpriteFromResources("BonusRoles.Resources.SpyButton.png", 100f);
+ return buttonSprite;
+ }
+
public static void clearAndReload() {
spy = null;
+ spyTimer = 0f;
+ cooldown = BonusRolesPlugin.spyCooldown.GetValue();
+ duration = BonusRolesPlugin.spySpyingDuration.GetValue();
}
}
public static class Child {
public static PlayerControl child;
public static Color color = Color.white;
+
+ public static float growingUpDuration = float.MaxValue;
+ public static DateTime timeOfGrowthStart = DateTime.UtcNow;
public static void clearAndReload() {
child = null;
+ growingUpDuration = BonusRolesPlugin.childGrowingUpDuration.GetValue();
+ 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;
}
}
}
\ No newline at end of file
<Version>1.0.0</Version>
<Mappings>NuclearPowered/Mappings:0.2.0</Mappings>
- <Description>BonusRoles</Description>
+ <Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>
<EmbeddedResource Include="Resources\MorphButton.png"/>
<EmbeddedResource Include="Resources\SampleButton.png"/>
<EmbeddedResource Include="Resources\CamoButton.png"/>
+ <EmbeddedResource Include="Resources\SpyButton.png"/>
<EmbeddedResource Include="Resources\Footprint.png"/>
<EmbeddedResource Include="Resources\AdminTableIcon.png"/>
<EmbeddedResource Include="Resources\SwapperCheck.png"/>
using static BonusRoles.BonusRoles;
using Reactor.Extensions;
using System.Collections.Generic;
+using System.Linq;
+using UnhollowerBaseLib;
namespace BonusRoles
{
private static CustomButton seerRevealButton;
private static CustomButton morphlingButton;
private static CustomButton camouflagerButton;
+ private static CustomButton spyButton;
public static void setCustomButtonCooldowns() {
engineerRepairButton.MaxTimer = 0f;
seerRevealButton.MaxTimer = Seer.cooldown;
morphlingButton.MaxTimer = Morphling.cooldown;
camouflagerButton.MaxTimer = Camouflager.cooldown;
+ spyButton.MaxTimer = Spy.cooldown;
+
+ spyButton.EffectDuration = Spy.duration;
}
public static void Postfix(HudManager __instance)
}
}
},
- () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Engineer.engineer != null && Engineer.engineer == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => {
bool sabotageActive = false;
foreach (PlayerTask task in PlayerControl.LocalPlayer.myTasks)
sabotageActive = true;
return sabotageActive && !Engineer.usedRepair && PlayerControl.LocalPlayer.CanMove;
},
- false,
+ () => {},
Engineer.getButtonSprite(),
Vector3.zero,
__instance
}
}
},
- () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return __instance.ReportButton.renderer.color == Palette.EnabledColor && PlayerControl.LocalPlayer.CanMove; },
- true,
+ () => { janitorCleanButton.Timer = janitorCleanButton.MaxTimer;},
Janitor.getButtonSprite(),
Vector3.zero,
__instance
sheriffKillButton.Timer = sheriffKillButton.MaxTimer;
Sheriff.currentTarget = null;
},
- () => { return Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Sheriff.sheriff != null && Sheriff.sheriff == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return Sheriff.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- true,
+ () => { sheriffKillButton.Timer = sheriffKillButton.MaxTimer;},
__instance.KillButton.renderer.sprite,
Vector3.zero,
__instance
RPCProcedure.timeMasterRewindTime();
timeMasterRewindTimeButton.Timer = timeMasterRewindTimeButton.MaxTimer;
},
- () => { return TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return TimeMaster.timeMaster != null && TimeMaster.timeMaster == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return PlayerControl.LocalPlayer.CanMove; },
- true,
+ () => { timeMasterRewindTimeButton.Timer = timeMasterRewindTimeButton.MaxTimer;},
TimeMaster.getButtonSprite(),
Vector3.zero,
__instance
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.medicSetShielded(Medic.currentTarget.PlayerId);
},
- () => { return Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Medic.medic != null && Medic.medic == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return !Medic.usedShield && Medic.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- false,
+ () => {},
Medic.getButtonSprite(),
Vector3.zero,
__instance
RPCProcedure.shifterShift(Shifter.currentTarget.PlayerId);
},
- () => { return Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Shifter.shifter != null && Shifter.shifter == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return Shifter.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- true,
+ () => { shifterShiftButton.Timer = shifterShiftButton.MaxTimer; },
Shifter.getButtonSprite(),
Vector3.zero,
__instance
() => {
seerRevealButton.Timer = seerRevealButton.MaxTimer;
+ PlayerControl targetOrMistake = Seer.currentTarget;
+ if (rnd.Next(1, 101) > Seer.chanceOfSeeingRight) {
+ var players = PlayerControl.AllPlayerControls.ToArray().ToList();
+ players.RemoveAll(p => p != null && (p.PlayerId == Seer.seer.PlayerId || p.PlayerId == Seer.currentTarget.PlayerId));
+ int index = rnd.Next(0, players.Count);
+
+ if (players.Count != 0 && players[index] != null)
+ targetOrMistake = players[index];
+ }
+
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.SeerReveal, Hazel.SendOption.None, -1);
writer.Write(Seer.currentTarget.PlayerId);
+ writer.Write(targetOrMistake.PlayerId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.seerReveal(Seer.currentTarget.PlayerId);
+ RPCProcedure.seerReveal(Seer.currentTarget.PlayerId, targetOrMistake.PlayerId);
},
- () => { return Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Seer.seer != null && Seer.seer == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return Seer.currentTarget && PlayerControl.LocalPlayer.CanMove; },
- false,
+ () => {},
Seer.getButtonSprite(),
Vector3.zero,
__instance
morphlingButton.HasEffect = false; // Block effect on this click
}
},
- () => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Morphling.morphling != null && Morphling.morphling == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return (Morphling.currentTarget || Morphling.sampledTarget) && PlayerControl.LocalPlayer.CanMove; },
- true,
+ () => {
+ morphlingButton.Timer = morphlingButton.MaxTimer;
+ morphlingButton.Sprite = Morphling.getSampleSprite();
+ morphlingButton.isEffectActive = false;
+ morphlingButton.killButtonManager.TimerText.Color = Palette.EnabledColor;
+ Morphling.sampledTarget = null;
+ },
Morphling.getSampleSprite(),
new Vector3(0f, 1.3f, 0f),
__instance,
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.camouflagerCamouflage();
},
- () => { return Camouflager.camouflager != null && Camouflager.camouflager == PlayerControl.LocalPlayer && __instance.UseButton.isActiveAndEnabled && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return Camouflager.camouflager != null && Camouflager.camouflager == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
() => { return PlayerControl.LocalPlayer.CanMove; },
- true,
+ () => {
+ camouflagerButton.Timer = camouflagerButton.MaxTimer;
+ camouflagerButton.isEffectActive = false;
+ camouflagerButton.killButtonManager.TimerText.Color = Palette.EnabledColor;
+ },
Camouflager.getButtonSprite(),
new Vector3(0f, 1.3f, 0f),
__instance,
10f,
() => { camouflagerButton.Timer = camouflagerButton.MaxTimer; }
);
+
+ // Spy button
+ spyButton = new CustomButton(
+ () => {
+ Spy.spyTimer = Spy.duration;
+ },
+ () => { return Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer && !PlayerControl.LocalPlayer.Data.IsDead; },
+ () => { return PlayerControl.LocalPlayer.CanMove; },
+ () => {
+ spyButton.Timer = spyButton.MaxTimer;
+ spyButton.isEffectActive = false;
+ spyButton.killButtonManager.TimerText.Color = Palette.EnabledColor;
+ },
+ Spy.getButtonSprite(),
+ Vector3.zero,
+ __instance,
+ true,
+ 0f,
+ () => {
+ spyButton.Timer = spyButton.MaxTimer;
+ }
+ );
}
}
}
\ No newline at end of file
static void Postfix(VersionShower __instance) {
string spacer = new String('\n', 15);
if (__instance.text.Text.Contains(spacer))
- __instance.text.Text += "\n[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.4\n by [FCCE03FF]Eisbison";
+ __instance.text.Text += "\n[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.5\n by [FCCE03FF]Eisbison";
else
- __instance.text.Text += spacer + "[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.4\n by [FCCE03FF]Eisbison";
+ __instance.text.Text += spacer + "[FFFFFFFF]- Loaded [FCCE03FF]TheOtherRoles[FFFFFFFF] v1.5\n by [FCCE03FF]Eisbison";
}
}
public Vector3 PositionOffset;
public float MaxTimer = float.MaxValue;
public float Timer = 0f;
- public bool ResetTimeAfterMeeting;
private Action OnClick;
+ private Action OnMeetingEnds;
private Func<bool> HasButton;
private Func<bool> CouldUse;
private Action OnEffectEnds;
public bool HasEffect;
- private bool isEffectActive = false;
- private float EffectDuration;
+ public bool isEffectActive = false;
+ public float EffectDuration;
public Sprite Sprite;
private HudManager hudManager;
- public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, bool ResetTimeAfterMeeting, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, bool HasEffect, float EffectDuration, Action OnEffectEnds)
+ public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager, bool HasEffect, float EffectDuration, Action OnEffectEnds)
{
this.hudManager = hudManager;
this.OnClick = OnClick;
this.HasButton = HasButton;
this.CouldUse = CouldUse;
this.PositionOffset = PositionOffset;
- this.ResetTimeAfterMeeting = ResetTimeAfterMeeting;
+ this.OnMeetingEnds = OnMeetingEnds;
this.HasEffect = HasEffect;
this.EffectDuration = EffectDuration;
this.OnEffectEnds = OnEffectEnds;
setActive(false);
}
- public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, bool ResetTimeAfterMeeting, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager)
- : this(OnClick, HasButton, CouldUse, ResetTimeAfterMeeting, Sprite, PositionOffset, hudManager, false, 0f, () => {}) { }
-
+ public CustomButton(Action OnClick, Func<bool> HasButton, Func<bool> CouldUse, Action OnMeetingEnds, Sprite Sprite, Vector3 PositionOffset, HudManager hudManager)
+ : this(OnClick, HasButton, CouldUse, OnMeetingEnds, Sprite, PositionOffset, hudManager, false, 0f, () => {}) { }
public static void HudUpdate()
{
buttons.RemoveAll(item => item.killButtonManager == null);
+
if (MeetingHud.Instance || ExileController.Instance) return;
for (int i = 0; i < buttons.Count; i++)
{
try
{
- if (buttons[i].ResetTimeAfterMeeting) {
- buttons[i].Timer = buttons[i].MaxTimer;
- buttons[i].Update();
- }
+ buttons[i].OnMeetingEnds();
+ buttons[i].Update();
}
catch (NullReferenceException)
{
setActive(false);
return;
}
- setActive(true);
+ setActive(hudManager.UseButton.isActiveAndEnabled);
killButtonManager.renderer.sprite = Sprite;
LoversTeamWin,
LoversSoloWin,
JesterWin,
- ChildWin
+ ChildDied
}
static class AdditionalTempData {
// Child win condition (should be implemented using a proper GameOverReason in the future)
if (Child.child != null && Child.child.Data.IsImpostor) {
- AdditionalTempData.winCondition = WinCondition.ChildWin;
+ AdditionalTempData.winCondition = WinCondition.ChildDied;
TempData.winners = new Il2CppSystem.Collections.Generic.List<WinningPlayerData>();
WinningPlayerData wpd = new WinningPlayerData(Child.child.Data);
- wpd.IsImpostor = false;
+ wpd.IsImpostor = false;
+ wpd.IsYou = false;
TempData.winners.Add(wpd);
}
TextRenderer textRenderer = bonusText.GetComponent<TextRenderer>();
textRenderer.Text = "";
- if (AdditionalTempData.winCondition == WinCondition.ChildWin) {
- textRenderer.Text = "Child Wins";
+ if (AdditionalTempData.winCondition == WinCondition.ChildDied) {
+ textRenderer.Text = "Child Died";
textRenderer.Color = Child.color;
}
else if (AdditionalTempData.winCondition == WinCondition.JesterWin) {
static class GameHistory {
public static List<Tuple<Vector3, DateTime>> localPlayerPositions = new List<Tuple<Vector3, DateTime>>();
public static List<DeadPlayer> deadPlayers = new List<DeadPlayer>();
+ public static DateTime localVentEnterTimePoint = DateTime.MinValue;
public static void clearGameHistory() {
localPlayerPositions = new List<Tuple<Vector3, DateTime>>();
deadPlayers = new List<DeadPlayer>();
+ localVentEnterTimePoint = DateTime.MinValue;
}
}
}
\ No newline at end of file
{
__instance.__this.Title.Text = "Child";
__instance.__this.Title.Color = Child.color;
- __instance.__this.ImpostorText.Text = "No one will harm you";
+ __instance.__this.ImpostorText.Text = "No one will harm you until you grow up";
__instance.__this.BackgroundBar.material.color = Child.color;
}
}
// Role spawn chances
public static CustomNumberOption mafiaSpawnChance = CustomOption.AddNumber("Mafia Spawn Chance", 100, 0, 100, 10);
+ public static CustomNumberOption loversSpawnChance = CustomOption.AddNumber("Lovers Spawn Chance", 100, 0, 100, 10);
public static CustomNumberOption morphlingSpawnChance = CustomOption.AddNumber("Morphling Spawn Chance", 100, 0, 100, 10);
public static CustomNumberOption camouflagerSpawnChance = CustomOption.AddNumber("Camouflager Spawn Chance", 100, 0, 100, 10);
- public static CustomNumberOption loversSpawnChance = CustomOption.AddNumber("Lovers Spawn Chance", 100, 0, 100, 10);
public static CustomNumberOption jesterSpawnChance = CustomOption.AddNumber("Jester Spawn Chance", 100, 0, 100, 10);
public static CustomNumberOption mayorSpawnChance = CustomOption.AddNumber("Mayor Spawn Chance", 100, 0, 100, 10);
public static CustomNumberOption engineerSpawnChance = CustomOption.AddNumber("Engineer Spawn Chance", 100, 0, 100, 10);
public static CustomToggleOption medicShowAttemptToShielded = CustomOption.AddToggle("Shielded Player Sees Murder Attempt", false);
public static CustomNumberOption shifterCooldown = CustomOption.AddNumber("Shifter Cooldown", 30f, 10f, 60f, 2.5f);
public static CustomNumberOption seerCooldown = CustomOption.AddNumber("Seer Cooldown (No Reset After Meeting)", 15f, 30f, 180f, 15f);
+ public static CustomNumberOption seerChanceOfSeeingRight = CustomOption.AddNumber("Seer Chance Of Seeing Right", 100, 0, 100, 5);
public static CustomStringOption seerKindOfInfo = CustomOption.AddString("Info That Seer Reveals", new string[] {"Role", "Good/Bad"});
- public static CustomStringOption seerPlayersWithNotification = CustomOption.AddString("Players That See When They Are Being Revealed", new string[] {"Everyone", "The Good", "The Bad", "Nobody"});
+ public static CustomStringOption seerPlayersWithNotification = CustomOption.AddString("Players That See When They Are Being Revealed", new string[] {"Everyone", "The Good", "The Bad", "Nobody"});
+ public static CustomNumberOption spyCooldown = CustomOption.AddNumber("Spy Cooldown", 30f, 10f, 120f, 5f);
+ public static CustomNumberOption spySpyingDuration = CustomOption.AddNumber("Spy Duration", 10f, 2.5f, 60f, 2.5f);
+ public static CustomNumberOption childGrowingUpDuration = CustomOption.AddNumber("Child Growing Up Duration", 400f, 100f, 1500f, 100f);
+
+ public static ConfigEntry<bool> DebugMode { get; private set; }
+
- public static ConfigEntry<string> Ip { get; set; }
- public static ConfigEntry<ushort> Port { get; set; }
- public static ConfigEntry<string> Name { get; private set; }
public override void Load()
{
- Ip = Config.Bind("Custom", "Ipv4 or Hostname", "127.0.0.1");
- Port = Config.Bind("Custom", "Port", (ushort)22023);
- Name = Config.Bind("Custom", "Custom", "Custom");
-
- var defaultRegions = ServerManager.DefaultRegions.ToList();
- var ip = Ip.Value;
- if (Uri.CheckHostName(Ip.Value).ToString() == "Dns")
- {
- try
- {
- foreach (IPAddress address in Dns.GetHostAddresses(Ip.Value))
- {
- if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
- {
- ip = address.ToString(); break;
- }
- }
- }
- catch { }
- }
-
- var port = Port.Value;
- defaultRegions.Insert(0, new StaticRegionInfo(
- Name.Value, (StringNames)3000, ip, new[]
- {
- new ServerInfo($"CustomServer", ip, Port.Value)
- }).Duplicate()
- ) ;
- ServerManager.DefaultRegions = defaultRegions.ToArray();
+ DebugMode = Config.Bind("Custom", "Enable Debug Mode", false);
CustomOption.ShamelessPlug = false;
Harmony.PatchAll();
}
}
- // Patch server name
- [HarmonyPatch(typeof(LanguageUnit),"HOGGPANBLGP")]
- public class ServerNamePatch {
- public static bool Prefix(StringNames LENBLADGELB, string NNLLKLDPBAI, Il2CppReferenceArray<Il2CppSystem.Object> DKBJCINDDCD, ref string __result)
- {
- if (LENBLADGELB == (StringNames)3000)
- {
- __result = BonusRolesPlugin.Name.Value;
- return false;
- }
- return true;
- }
- }
-
// 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
__result = 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 (!BonusRolesPlugin.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, 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.None, -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());
+ }
+ }
+
}
}
// Child win condition
- if (Child.child != null && ExileController.Instance.exiled != null && ExileController.Instance.exiled.PlayerId == Child.child.PlayerId) {
+ if (Child.child != null && ExileController.Instance.exiled != null && ExileController.Instance.exiled.PlayerId == Child.child.PlayerId && !Child.isGrownUp()) {
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ChildDied, Hazel.SendOption.None, -1);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.childDied();
if (localPlayerPositions.Count > 0) {
// Set position
var next = localPlayerPositions[0];
- PlayerControl.LocalPlayer.transform.position = next.Item1;
+ if (!PlayerControl.LocalPlayer.inVent)
+ PlayerControl.LocalPlayer.transform.position = next.Item1;
localPlayerPositions.RemoveAt(0);
if (localPlayerPositions.Count > 0) localPlayerPositions.RemoveAt(0); // Skip every second position to rewinde in half the time
static void seerSetTarget() {
if (Seer.seer == null || Seer.seer != PlayerControl.LocalPlayer) return;
Seer.currentTarget = setAnyRoleTarget();
- if (Seer.currentTarget != null && Seer.revealedPlayers.Any(p => p.Data.PlayerId == Seer.currentTarget.Data.PlayerId)) Seer.currentTarget = null; // Remove target if already revealed
+ if (Seer.currentTarget != null && Seer.revealedPlayers.Keys.Any(p => p.Data.PlayerId == Seer.currentTarget.Data.PlayerId)) Seer.currentTarget = null; // Remove target if already revealed
}
class PerformKillPatch
{
public static bool Prefix(KillButtonManager __instance) {
- // Block imposter shielded kill
- if (__instance.isActiveAndEnabled && __instance.CurrentTarget && !__instance.isCoolingDown && !PlayerControl.LocalPlayer.Data.IsDead && PlayerControl.LocalPlayer.CanMove) {
- if (Medic.shielded != null && Medic.shielded == __instance.CurrentTarget) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.None, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.shieldedMurderAttempt();
- return false;
- }
+ // Block impostor shielded kill
+ if (Medic.shielded != null && Medic.shielded == __instance.CurrentTarget) {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ShieldedMurderAttempt, Hazel.SendOption.None, -1);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.shieldedMurderAttempt();
+ return false;
+ }
+ // Block impostor not fully grown child kill
+ else if (Child.child != null && __instance.CurrentTarget == Child.child && !Child.isGrownUp()) {
+ return false;
}
return true;
}
RPCProcedure.loverSuicide(otherLover.PlayerId);
}
}
-
- // Child died
- if (Child.child != null && PAIBDFDMIGK == Child.child) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.ChildDied, Hazel.SendOption.None, -1);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.childDied();
- }
}
}
using System.Collections;
using System.Linq;
using UnityEngine;
+using System;
namespace BonusRoles
{
}
public static void janitorClean(byte playerId) {
- DeadBody[] array = Object.FindObjectsOfType<DeadBody>();
+ DeadBody[] array = UnityEngine.Object.FindObjectsOfType<DeadBody>();
for (int i = 0; i < array.Length; i++) {
if (GameData.Instance.GetPlayerById(array[i].ParentId).PlayerId == playerId)
- Object.Destroy(array[i].gameObject);
+ UnityEngine.Object.Destroy(array[i].gameObject);
}
}
}
public static void timeMasterRewindTime() {
+ if (TimeMaster.timeMaster == null) return;
+
+ PlayerControl lp = PlayerControl.LocalPlayer;
+ if (lp?.Data != null && !lp.Data.IsDead && lp.inVent) {
+ if ((float)(DateTime.UtcNow -localVentEnterTimePoint).TotalMilliseconds < 1000 * TimeMaster.rewindTime) {
+ 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);
+ }
+ }
+ }
+ }
+
+ if (PlayerControl.LocalPlayer == TimeMaster.timeMaster) return; // Time Master himself does not rewind
+
TimeMaster.isRewinding = true;
+
if (MapBehaviour.Instance)
MapBehaviour.Instance.Close();
if (Minigame.Instance)
}
}
- public static void seerReveal(byte playerId) {
+ public static void seerReveal(byte targetId, byte targetOrMistakeId) {
if (Seer.seer == null) return;
+
+ PlayerControl target = Helpers.playerById(targetId);
+ PlayerControl targetOrMistake = Helpers.playerById(targetOrMistakeId);
- PlayerControl player = Helpers.playerById(playerId);
- if (player != null && !Seer.revealedPlayers.Any(p => p.Data.PlayerId == playerId)) {
- Seer.revealedPlayers.Add(player);
+ if (target != null && targetOrMistake != null && !Seer.revealedPlayers.Keys.Any(p => p.Data.PlayerId == targetId)) {
+ Seer.revealedPlayers.Add(target, targetOrMistake);
- if (PlayerControl.LocalPlayer == player && HudManager.Instance?.FullScreen != null) {
- SeerInfo si = SeerInfo.getSeerInfoForPlayer(player);
+ if (PlayerControl.LocalPlayer == target && HudManager.Instance?.FullScreen != null) {
+ SeerInfo si = SeerInfo.getSeerInfoForPlayer(target); // Use SeerInfo for target here, because we need the isGood of the targets role
bool showNotification = false;
if (Seer.playersWithNotification == 0 ) showNotification = true;
else if (Seer.playersWithNotification == 1 && si.isGood) showNotification = true;
RPCProcedure.swapperSwap(playerId1, playerId2);
break;
case (byte)CustomRPC.SeerReveal:
- RPCProcedure.seerReveal(HFPCBBHJIPJ.ReadByte());
+ byte targetId = HFPCBBHJIPJ.ReadByte();
+ byte targetOrMistakeId = HFPCBBHJIPJ.ReadByte();
+ RPCProcedure.seerReveal(targetId, targetOrMistakeId);
break;
case (byte)CustomRPC.MorphlingMorph:
RPCProcedure.morphlingMorph(HFPCBBHJIPJ.ReadByte());
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.resetVariables();
- List<PlayerControl> crewmates = PlayerControl.AllPlayerControls.ToArray().ToList();
+ 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();
+ List<PlayerControl> impostors = PlayerControl.AllPlayerControls.ToArray().ToList().OrderBy(x => Guid.NewGuid()).ToList();
impostors.RemoveAll(x => !x.Data.IsImpostor);
// Special roles impostors can be converted to
setRoleToRandomPlayer((byte)RoleId.Mafioso, impostors);
}
- if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.morphlingSpawnChance.GetValue()))
- setRoleToRandomPlayer((byte)RoleId.Morphling, impostors);
-
- if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.camouflagerSpawnChance.GetValue()))
- setRoleToRandomPlayer((byte)RoleId.Camouflager, impostors);
-
// Special roles that involve crewmates and impostors
if (rnd.Next(1, 101) <= BonusRolesPlugin.loversSpawnChance.GetValue())
{
}
}
+ // Special roles impostors can be converted to
+ if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.morphlingSpawnChance.GetValue()))
+ setRoleToRandomPlayer((byte)RoleId.Morphling, impostors);
+
+ if (impostors.Count >= 1 && (rnd.Next(1, 101) <= BonusRolesPlugin.camouflagerSpawnChance.GetValue()))
+ setRoleToRandomPlayer((byte)RoleId.Camouflager, impostors);
+
// Special roles crewmates can be converted to
if (crewmates.Count > 0 && (rnd.Next(1, 101) <= BonusRolesPlugin.jesterSpawnChance.GetValue()))
setRoleToRandomPlayer((byte)RoleId.Jester, crewmates);
if (Seer.seer == null || Seer.seer != PlayerControl.LocalPlayer) return;
// Update revealed players
- foreach (PlayerControl p in Seer.revealedPlayers) {
+ foreach (KeyValuePair<PlayerControl, PlayerControl> entry in Seer.revealedPlayers) {
+ PlayerControl target = entry.Key;
+ PlayerControl targetOrMistake = entry.Value;
+
+ if (target == null || targetOrMistake == null) continue;
+
// Update color and name regarding settings and given info
- string result = p.Data.PlayerName;
- SeerInfo si = SeerInfo.getSeerInfoForPlayer(p);
+ string result = target.Data.PlayerName;
+ SeerInfo si = SeerInfo.getSeerInfoForPlayer(targetOrMistake);
if (Seer.kindOfInfo == 0)
- result = p.Data.PlayerName + " (" + si.roleName + ")";
+ result = target.Data.PlayerName + " (" + si.roleName + ")";
else if (Seer.kindOfInfo == 1) {
si.color = si.isGood ? new Color(250f / 255f, 217f / 255f, 52f / 255f, 1) : new Color (51f / 255f, 61f / 255f, 54f / 255f, 1);
}
// Set color and name
- p.nameText.Color = si.color;
- p.nameText.Text = result;
+ target.nameText.Color = si.color;
+ target.nameText.Text = result;
if (MeetingHud.Instance != null) {
foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates) {
- if (p.PlayerId == player.TargetPlayerId) {
+ if (target.PlayerId == player.TargetPlayerId) {
player.NameText.Text = result;
player.NameText.Color = si.color;
break;
}
}
+ static void spyUpdate() {
+ Spy.spyTimer -= Time.deltaTime;
+ }
+
static void camouflageAndMorphActions() {
float oldCamouflageTimer = Camouflager.camouflageTimer;
float oldMorphTimer = Morphling.morphTimer;
}
}
- public static void updatePlayerSizes() {
+ public static void childUpdate() {
foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
if (p == null) continue;
- if (Child.child != null && Child.child == p)
- p.transform.localScale = new Vector3(0.35f, 0.35f, 1f);
- else if (Morphling.morphling != null && Morphling.morphling == p && Morphling.morphTarget != null && Morphling.morphTarget == Child.child && Morphling.morphTimer > 0f)
- p.transform.localScale = new Vector3(0.35f, 0.35f, 1f);
+ if (Child.child == null) {
+ p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
+ return;
+ }
+
+ float growingProgress = Child.growingProgress();
+ float scale = growingProgress * 0.35f + 0.35f;
+ string suffix = "";
+ if (growingProgress != 1f)
+ suffix = " [FAD934FF](" + Mathf.FloorToInt(growingProgress * 18) + ")";
+
+ if (Child.child == p) {
+ p.transform.localScale = new Vector3(scale, scale, 1f);
+ p.nameText.Text += suffix;
+ if (MeetingHud.Instance != null)
+ foreach (PlayerVoteArea player in MeetingHud.Instance.playerStates)
+ if (player.NameText != null && p.PlayerId == player.TargetPlayerId)
+ player.NameText.Text += suffix;
+ }
+ else if (Morphling.morphling != null && Morphling.morphling == p && Morphling.morphTarget != null && Morphling.morphTarget == Child.child && Morphling.morphTimer > 0f) {
+ p.transform.localScale = new Vector3(scale, scale, 1f);
+ p.nameText.Text += suffix;
+ }
else
p.transform.localScale = new Vector3(0.7f, 0.7f, 1f);
}
shifterClearTasks();
// Seer update
seerUpdate();
+ // Spy update();
+ spyUpdate();
camouflageAndMorphActions();
- updatePlayerSizes();
+ childUpdate();
}
}
}
\ No newline at end of file
{
[HarmonyPatch(typeof(Vent), "CanUse")]
- public static class VentPatch
+ 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)
{
}
}
+ [HarmonyPatch(typeof(Vent), "Use")]
+ public static class VentUsePatch {
+ public static void Prefix(Vent __instance) {
+ bool flag;
+ bool flag2;
+ __instance.CanUse(PlayerControl.LocalPlayer.Data, out flag, out flag2);
+ if (flag && !PlayerControl.LocalPlayer.inVent)
+ localVentEnterTimePoint = DateTime.UtcNow;
+ }
+ }
+
[HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.SetTarget))]
class UseButtonSetTargetPatch {
static void Postfix(UseButtonManager __instance) {
class VitalsMinigamePatch {
static void Postfix(VitalsMinigame __instance) {
// Spy show time since death
- if (Spy.spy == null || Spy.spy != PlayerControl.LocalPlayer) return;
+ bool showSpyInfo = Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer && Spy.spyTimer > 0;
for (int k = 0; k < __instance.vitals.Length; k++)
{
VitalsPanel vitalsPanel = __instance.vitals[k];
DeadPlayer deadPlayer = deadPlayers?.Where(x => x.player?.PlayerId == playerInfo?.PlayerId)?.FirstOrDefault();
if (deadPlayer != null && deadPlayer.timeOfDeath != null) {
float timeSinceDeath = ((float)(DateTime.UtcNow - deadPlayer.timeOfDeath).TotalMilliseconds);
- vitalsPanel.Text.Text = Math.Round(timeSinceDeath / 1000) + "s";
+
+ if (showSpyInfo)
+ vitalsPanel.Text.Text = Math.Round(timeSinceDeath / 1000) + "s";
+ else
+ vitalsPanel.Text.Text = DestroyableSingleton<TranslationController>.Instance.GetString(Palette.ShortColorNames[(int)playerInfo.ColorId], new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(0));
}
}
}
[HarmonyPatch(typeof(MapCountOverlay), nameof(MapCountOverlay.Update))]
class MapCountOverlayUpdatePatch {
static bool Prefix(MapCountOverlay __instance) {
- // Spy save colors for each room on the admin panel
- if (Spy.spy == null || Spy.spy != PlayerControl.LocalPlayer) return true;
-
+ // Save colors for the Spy
__instance.timer += Time.deltaTime;
if (__instance.timer < 0.1f)
{
[HarmonyPatch(typeof(CounterArea), nameof(CounterArea.UpdateCount))]
class CounterAreaUpdateCountPatch {
+ private static Sprite defaultIcon;
+
static void Postfix(CounterArea __instance) {
// Spy display saved colors on the admin panel
- if (Spy.spy == null || Spy.spy != PlayerControl.LocalPlayer) return;
-
+ bool showSpyInfo = Spy.spy != null && Spy.spy == PlayerControl.LocalPlayer && Spy.spyTimer > 0;
if (players.ContainsKey(__instance.RoomType)) {
List<Color> colors = players[__instance.RoomType];
PoolableBehavior icon = __instance.myIcons[i];
SpriteRenderer renderer = icon.GetComponent<SpriteRenderer>();
- if (renderer != null && colors.Count > i && Spy.getAdminTableIconSprite() != null) {
- renderer.sprite = Spy.getAdminTableIconSprite();
- renderer.color = colors[i];
+ if (renderer != null) {
+ if (defaultIcon == null) defaultIcon = renderer.sprite;
+ if (showSpyInfo && colors.Count > i && Spy.getAdminTableIconSprite() != null) {
+ renderer.sprite = Spy.getAdminTableIconSprite();
+ renderer.color = colors[i];
+ } else {
+ renderer.sprite = defaultIcon;
+ renderer.color = Color.white;
+ }
}
}
}