# Releases\r
| Among Us - Version| Mod Version | Link |\r
|----------|-------------|-----------------|\r
+| 2021.4.12s| v2.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.1/TheOtherRoles.zip)\r
| 2021.4.12s| v2.2.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.2.0/TheOtherRoles.zip)\r
| 2021.3.31.3s| v2.1.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.1.0/TheOtherRoles.zip)\r
| 2021.3.31.3s| v2.0.1 beta| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.0.1/TheOtherRoles.zip)\r
<details>\r
<summary>Click to show the Changelog</summary>\r
\r
+**Version 2.2.1**\r
+- Trickster: The vent button now has a custom texture. Fixed a bug where the Trickster could clip out of bounds when his box was close to a wall.\r
+- Fixed a bug where the Bad Child's kill button went on cooldown when someone else performed a kill\r
+- Fixed a few bugs with footprints, Seer souls and the Vampire delayed kill\r
+- Fixed a bug where the Child was banned for hacking (because of its reduced kill cooldown)\r
+- Improved the version handshake\r
+\r
**Version 2.2.0**\r
- **Works with the latest Among Us version (2021.4.12s)**\r
- **Added support for 10+ player lobbies on custom servers:** Check the [Custom Servers and 10+ Players](#Custom-Servers-and-10+-Players) section. During meetings use the up/down keys, on vitals use the left/right keys.\r
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0);
- PlayerControl.LocalPlayer.StartCoroutine(Effects.DCHLMIDMBHG(Vampire.delay, new Action<float>((p) => { // Delayed action
+ HudManager.CHNDKKBEIDG.StartCoroutine(Effects.DCHLMIDMBHG(Vampire.delay, new Action<float>((p) => { // Delayed action
if (p == 1f) {
if (Vampire.bitten != null && !Vampire.bitten.PPMOEEPBHJO.IAGJEKLJCCI && Helpers.handleMurderAttempt(Vampire.bitten)) {
// Perform kill
{
static void Postfix(VersionShower __instance) {
string spacer = new String('\n', 21);
- string fullCredentials = $"<color=#FCCE03FF>TheOtherRoles</color> v{TheOtherRolesPlugin.Major}.{TheOtherRolesPlugin.Minor}.{TheOtherRolesPlugin.Patch}:\n- Modded by <color=#FCCE03FF>TheOtherRoles</color> and <color=#FFEB91FF>Thunderstorm584</color>\n- Balanced with <color=#FFEB91FF>Dhalucard</color>\n- Button design by <color=#FFEB91FF>Bavari</color>";
+ string fullCredentials = $"<color=#FCCE03FF>TheOtherRoles</color> v{TheOtherRolesPlugin.Major}.{TheOtherRolesPlugin.Minor}.{TheOtherRolesPlugin.Patch}:\n- Modded by <color=#FCCE03FF>Eisbison</color> and <color=#FFEB91FF>Thunderstorm584</color>\n- Balanced with <color=#FFEB91FF>Dhalucard</color>\n- Button design by <color=#FFEB91FF>Bavari</color>";
if (__instance.text.text.Contains(spacer))
__instance.text.text += "\n" + fullCredentials;
else
footprint.SetActive(true);
footprints.Add(this);
- PlayerControl.LocalPlayer.StartCoroutine(Effects.DCHLMIDMBHG(footprintDuration, new Action<float>((p) => {
+ HudManager.CHNDKKBEIDG.StartCoroutine(Effects.DCHLMIDMBHG(footprintDuration, new Action<float>((p) => {
Color c = color;
if (!anonymousFootprints && owner != null) {
if (owner == Morphling.morphling && Morphling.morphTimer > 0 && Morphling.morphTarget?.PPMOEEPBHJO != null)
namespace TheOtherRoles {
public class GameStartManagerPatch {
- public static Dictionary<byte, Tuple<byte, byte, byte>> playerVersions = new Dictionary<byte, Tuple<byte, byte, byte>>();
+ public static Dictionary<int, Tuple<byte, byte, byte>> playerVersions = new Dictionary<int, Tuple<byte, byte, byte>>();
private static float timer = 600f;
private static bool versionSent = false;
[HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.Start))]
public class GameStartManagerStartPatch {
public static void Postfix() {
- // Refresh version infos
- playerVersions = new Dictionary<byte, Tuple<byte, byte, byte>>();
+ // Trigger version refresh
versionSent = false;
-
// Reset lobby countdown timer
timer = 600f;
}
writer.Write(TheOtherRolesPlugin.Major);
writer.Write(TheOtherRolesPlugin.Minor);
writer.Write(TheOtherRolesPlugin.Patch);
- writer.Write(PlayerControl.LocalPlayer.PlayerId);
+ writer.WritePacked(AmongUsClient.Instance.ClientId);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.versionHandshake(TheOtherRolesPlugin.Major, TheOtherRolesPlugin.Minor, TheOtherRolesPlugin.Patch, PlayerControl.LocalPlayer.PlayerId);
+ RPCProcedure.versionHandshake(TheOtherRolesPlugin.Major, TheOtherRolesPlugin.Minor, TheOtherRolesPlugin.Patch, AmongUsClient.Instance.ClientId);
}
// Host update with version handshake infos
if (AmongUsClient.Instance.HHBLOCGKFAB) {
bool blockStart = false;
string message = "";
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- var dummyComponent = player.GetComponent<DummyBehaviour>();
+ foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) {
+ if (client.Character == null) continue;
+ var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
if (dummyComponent != null && dummyComponent.enabled)
continue;
- else if (!playerVersions.ContainsKey(player.PlayerId)) {
+ else if (!playerVersions.ContainsKey(client.Id)) {
blockStart = true;
- message += $"<color=#FF0000FF>{player.PPMOEEPBHJO.PCLLABJCIPC} has an outdated or no version of The Other Roles\n</color>";
- } else if (playerVersions[player.PlayerId].Item1 != TheOtherRolesPlugin.Major || playerVersions[player.PlayerId].Item2 != TheOtherRolesPlugin.Minor || playerVersions[player.PlayerId].Item3 != TheOtherRolesPlugin.Patch) {
+ message += $"<color=#FF0000FF>{client.Character.PPMOEEPBHJO.PCLLABJCIPC} has an outdated or no version of The Other Roles\n</color>";
+ } else if (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch) {
blockStart = true;
- message += $"<color=#FF0000FF>{player.PPMOEEPBHJO.PCLLABJCIPC} has an outdated version (v{playerVersions[player.PlayerId].Item1}.{playerVersions[player.PlayerId].Item2}.{playerVersions[player.PlayerId].Item3}) of The Other Roles\n</color>";
+ message += $"<color=#FF0000FF>{client.Character.PPMOEEPBHJO.PCLLABJCIPC} has an outdated version (v{playerVersions[client.Id].Item1}.{playerVersions[client.Id].Item2}.{playerVersions[client.Id].Item3}) of The Other Roles\n</color>";
}
}
if (blockStart) {
// Allow the start for this version to test the feature, blocking it with the next version
// if (AmongUsClient.Instance.HHBLOCGKFAB) {
- // uint hostVersion = Convert.ToUInt32(TheOtherRolesPlugin.Version.Replace(".", string.Empty));
- // foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- // var dummyComponent = player.GetComponent<DummyBehaviour>();
+ // foreach (InnerNet.ClientData client in AmongUsClient.Instance.allClients) {
+ // if (client.Character == null) continue;
+ // var dummyComponent = client.Character.GetComponent<DummyBehaviour>();
// if (dummyComponent != null && dummyComponent.enabled) continue;
- // if (!playerVersions.ContainsKey(player.PlayerId) || (playerVersions[player.PlayerId].Item1 != TheOtherRolesPlugin.Major || playerVersions[player.PlayerId].Item2 != TheOtherRolesPlugin.Minor || playerVersions[player.PlayerId].Item3 != TheOtherRolesPlugin.Patch))
+ // if (!playerVersions.ContainsKey(client.Id) || (playerVersions[client.Id].Item1 != TheOtherRolesPlugin.Major || playerVersions[client.Id].Item2 != TheOtherRolesPlugin.Minor || playerVersions[client.Id].Item3 != TheOtherRolesPlugin.Patch))
// continueStart = false;
// }
// }
}
public static bool isCustomServer() {
- return DestroyableSingleton<ServerManager>.CHNDKKBEIDG?.KEABFOHAECE == TheOtherRolesPlugin.Ip.Value;
+ if (DestroyableSingleton<ServerManager>.CHNDKKBEIDG == null) return false;
+ StringNames n = DestroyableSingleton<ServerManager>.CHNDKKBEIDG.HMIJGFFKBNN.TranslateName;
+ return n != StringNames.ServerNA && n != StringNames.ServerEU && n != StringNames.ServerAS;
}
public static string cs(Color c, string s) {
public JackInTheBox(Vector2 p) {
gameObject = new GameObject("JackInTheBox");
Vector3 position = new Vector3(p.x, p.y, PlayerControl.LocalPlayer.transform.position.z + 1f);
-
+ position += (Vector3)PlayerControl.LocalPlayer.Collider.offset; // add Player Collider offset as this gets subtracted again on Vent.DoMove
// Create the marker
gameObject.transform.position = position;
var boxRenderer = gameObject.AddComponent<SpriteRenderer>();
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string Version = "2.2.0";
+ public const string Version = "2.2.1";
public const byte Major = 2;
public const byte Minor = 2;
- public const byte Patch = 0;
+ public const byte Patch = 1;
public Harmony Harmony { get; } = new Harmony(Id);
public static TheOtherRolesPlugin Instance;
rend.sprite = Seer.getSoulSprite();
if(Seer.limitSoulDuration) {
- PlayerControl.LocalPlayer.StartCoroutine(Effects.DCHLMIDMBHG(Seer.soulDuration, new Action<float>((p) => {
+ HudManager.CHNDKKBEIDG.StartCoroutine(Effects.DCHLMIDMBHG(Seer.soulDuration, new Action<float>((p) => {
if (rend != null) {
var tmp = rend.color;
tmp.a = Mathf.Clamp01(1 - p);
}
}
[HarmonyPatch(typeof(KillButtonManager), nameof(KillButtonManager.PerformKill))]
- class PerformKillPatch
- {
+ class PerformKillPatch {
public static bool Prefix(KillButtonManager __instance) {
- return Helpers.handleMurderAttempt(__instance.CurrentTarget);
+ if (__instance.isActiveAndEnabled && __instance.CurrentTarget && !__instance.isCoolingDown && !PlayerControl.LocalPlayer.PPMOEEPBHJO.IAGJEKLJCCI && PlayerControl.LocalPlayer.POECPOEKKNO) { // Among Us default checks
+ if (Helpers.handleMurderAttempt(__instance.CurrentTarget)) { // Custom checks
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
+ writer.Write(PlayerControl.LocalPlayer.PlayerId);
+ writer.Write(__instance.CurrentTarget.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.uncheckedMurderPlayer(PlayerControl.LocalPlayer.PlayerId, __instance.CurrentTarget.PlayerId);
+ __instance.SetTarget(null);
+ }
+ }
+ return false;
}
}
if (Seer.deadBodyPositions != null) Seer.deadBodyPositions.Add(DGDGDKCCKHJ.transform.position);
// Child set adapted kill cooldown
- if (Child.child != null && PlayerControl.LocalPlayer == Child.child && Child.child.PPMOEEPBHJO.FDNMBJOAPFL) {
+ if (Child.child != null && PlayerControl.LocalPlayer == Child.child && Child.child.PPMOEEPBHJO.FDNMBJOAPFL && Child.child == __instance) {
var multiplier = Child.isGrownUp() ? 0.66f : 2f;
Child.child.SetKillTimer(PlayerControl.GameOptions.DGOPNLEEAAJ * multiplier);
}
SetUncheckedColor,
VersionHandshake,
UseUncheckedVent,
+ UncheckedMurderPlayer,
// Role functionality
if (player != null) player.SetColor(colorId);
}
- public static void versionHandshake(byte major, byte minor, byte patch, byte playerId) {
- if (AmongUsClient.Instance.HHBLOCGKFAB) { // If lobby host
- GameStartManagerPatch.playerVersions[playerId] = new Tuple<byte, byte, byte>(major, minor, patch);
- }
+ public static void versionHandshake(byte major, byte minor, byte patch, int clientId) {
+ GameStartManagerPatch.playerVersions[clientId] = new Tuple<byte, byte, byte>(major, minor, patch);
}
public static void useUncheckedVent(int ventId, byte playerId, byte isEnter) {
player.MyPhysics.HandleRpc(isEnter != 0 ? (byte)19 : (byte)20, reader);
}
+ public static void uncheckedMurderPlayer(byte sourceId, byte targetId) {
+ PlayerControl source = Helpers.playerById(sourceId);
+ PlayerControl target = Helpers.playerById(targetId);
+ if (source != null && target != null) source.MurderPlayer(target);
+ }
+
// Role functionality
public static void engineerFixLights() {
}
HudManager.CHNDKKBEIDG.FullScreen.color = new Color(0f, 0.5f, 0.8f, 0.3f);
HudManager.CHNDKKBEIDG.FullScreen.enabled = true;
- PlayerControl.LocalPlayer.StartCoroutine(Effects.DCHLMIDMBHG(TimeMaster.rewindTime / 2, new Action<float>((p) => {
+ HudManager.CHNDKKBEIDG.StartCoroutine(Effects.DCHLMIDMBHG(TimeMaster.rewindTime / 2, new Action<float>((p) => {
if (p == 1f) HudManager.CHNDKKBEIDG.FullScreen.enabled = false;
})));
public static void timeMasterShield() {
TimeMaster.shieldActive = true;
- PlayerControl.LocalPlayer.StartCoroutine(Effects.DCHLMIDMBHG(TimeMaster.shieldDuration, new Action<float>((p) => {
+ HudManager.CHNDKKBEIDG.StartCoroutine(Effects.DCHLMIDMBHG(TimeMaster.shieldDuration, new Action<float>((p) => {
if (p == 1f) TimeMaster.shieldActive = false;
})));
}
byte major = JIGFBHFFNFI.ReadByte();
byte minor = JIGFBHFFNFI.ReadByte();
byte patch = JIGFBHFFNFI.ReadByte();
- byte versionOwnerId = JIGFBHFFNFI.ReadByte();
+ int versionOwnerId = JIGFBHFFNFI.ReadPackedInt32();
RPCProcedure.versionHandshake(major, minor, patch, versionOwnerId);
break;
case (byte)CustomRPC.UseUncheckedVent:
byte isEnter = JIGFBHFFNFI.ReadByte();
RPCProcedure.useUncheckedVent(ventId, ventingPlayer, isEnter);
break;
+ case (byte)CustomRPC.UncheckedMurderPlayer:
+ byte source = JIGFBHFFNFI.ReadByte();
+ byte target = JIGFBHFFNFI.ReadByte();
+ RPCProcedure.uncheckedMurderPlayer(source, target);
+ break;
// Role functionality
private static Sprite placeBoxButtonSprite;
private static Sprite lightOutButtonSprite;
+ private static Sprite tricksterVentButtonSprite;
+
public static Sprite getPlaceBoxButtonSprite() {
if (placeBoxButtonSprite) return placeBoxButtonSprite;
placeBoxButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.PlaceJackInTheBoxButton.png", 115f);
return placeBoxButtonSprite;
}
+
public static Sprite getLightsOutButtonSprite() {
if (lightOutButtonSprite) return lightOutButtonSprite;
lightOutButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.LightsOutButton.png", 115f);
return lightOutButtonSprite;
}
+ public static Sprite getTricksterVentButtonSprite() {
+ if (tricksterVentButtonSprite) return tricksterVentButtonSprite;
+ tricksterVentButtonSprite = Helpers.loadSpriteFromResources("TheOtherRoles.Resources.TricksterVentButton.png", 115f);
+ return tricksterVentButtonSprite;
+ }
+
public static void clearAndReload() {
trickster = null;
lightsOutTimer = 0f;
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>2.2.0</Version>
+ <Version>2.2.1</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
<EmbeddedResource Include="Resources\GarlicButton.png" />
<EmbeddedResource Include="Resources\LighterButton.png" />
<EmbeddedResource Include="Resources\EraserButton.png" />
+ <EmbeddedResource Include="Resources\TricksterVentButton.png" />
<EmbeddedResource Include="Resources\Arrow.png" />
<EmbeddedResource Include="Resources\Garlic.png" />
<EmbeddedResource Include="Resources\GarlicBackground.png" />
[HarmonyPatch(typeof(UseButtonManager), nameof(UseButtonManager.SetTarget))]
class UseButtonSetTargetPatch {
static void Postfix(UseButtonManager __instance) {
- if (__instance.LHAKKAAOLLM != null) return;
+ // Trickster render special vent button
+ if (__instance.LHAKKAAOLLM != null && Trickster.trickster != null && Trickster.trickster == PlayerControl.LocalPlayer) {
+ Vent possibleVent = __instance.LHAKKAAOLLM.TryCast<Vent>();
+ if (possibleVent?.gameObject != null && possibleVent.gameObject.name.StartsWith("JackInTheBoxVent_")) {
+ __instance.UseButton.sprite = Trickster.getTricksterVentButtonSprite();
+ }
+ }
// Mafia sabotage button render patch
bool blockSabotageJanitor = (Janitor.janitor != null && Janitor.janitor == PlayerControl.LocalPlayer);
bool blockSabotageMafioso = (Mafioso.mafioso != null && Mafioso.mafioso == PlayerControl.LocalPlayer && Godfather.godfather != null && !Godfather.godfather.PPMOEEPBHJO.IAGJEKLJCCI);
- if (blockSabotageJanitor || blockSabotageMafioso) {
+ if (__instance.LHAKKAAOLLM == null && (blockSabotageJanitor || blockSabotageMafioso)) {
__instance.UseButton.sprite = DestroyableSingleton<TranslationController>.CHNDKKBEIDG.GetImage(ImageNames.UseButton);
__instance.UseButton.color = new Color(1f, 1f, 1f, 0.3f);
}