# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2021.11.9.5s| v3.2.3| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.3/TheOtherRoles.zip)
| 2021.11.9.5s| v3.2.2| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.2/TheOtherRoles.zip)
| 2021.11.9.5s| v3.2.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.1/TheOtherRoles.zip)
| 2021.11.9.5s| v3.2.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v3.2.0/TheOtherRoles.zip)
<details>
<summary>Click to show the Changelog</summary>
+**Version 3.2.3**
+- Fixed a bug where the role of a dead client was visible to the Pursuer
+- Fixed a bug where the Morphling changed their color when killing players
+- Fixed a bug where voting the Lover partner of a Lover Witch did not safe the spellbound players
+- Fixed a bug where the Vampire would teleport to the bitten player in some cases
+- When the Lawyer dies, the client doesn't have the client mark (§) anymore, making the client aware of the fact that the Lawyer can't steal the win anymore (only relevant if the "Client Knows" option is on)
+
**Version 3.2.2**
- Add new option "Play On A Random Map" created by [Alex2911](https://github.com/Alex2911)
- Add Witch option "Voting The Witch Saves All The Targets"
- Add Lawyer option "Lawyer Knows Target Role"
- We changed the win conditions of the [Lawyer](#lawyer), to make it more viable
-- Bug fix: The Medium now shows the roles of players in the rigth format
+- Bug fix: The Medium now shows the roles of players in the right format
- The name and the role of all winners is now being displayed on the end screen
- We changed the way settings are being shared among the players (which caused some people to be unable to join the lobby). This might resolve the problem or make it even worse... we'll see.
MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
killWriter.Write(Sheriff.sheriff.Data.PlayerId);
killWriter.Write(targetId);
+ killWriter.Write(byte.MaxValue);
AmongUsClient.Instance.FinishRpcImmediately(killWriter);
- RPCProcedure.uncheckedMurderPlayer(Sheriff.sheriff.Data.PlayerId, targetId);
+ RPCProcedure.uncheckedMurderPlayer(Sheriff.sheriff.Data.PlayerId, targetId, Byte.MaxValue);
}
sheriffKillButton.Timer = sheriffKillButton.MaxTimer;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
writer.Write(Vampire.vampire.PlayerId);
writer.Write(Vampire.currentTarget.PlayerId);
+ writer.Write(Byte.MaxValue);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.uncheckedMurderPlayer(Vampire.vampire.PlayerId, Vampire.currentTarget.PlayerId);
+ RPCProcedure.uncheckedMurderPlayer(Vampire.vampire.PlayerId, Vampire.currentTarget.PlayerId, Byte.MaxValue);
vampireKillButton.HasEffect = false; // Block effect on this click
vampireKillButton.Timer = vampireKillButton.MaxTimer;
// Notify players about bitten
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
writer.Write(Vampire.bitten.PlayerId);
- writer.Write(0);
+ writer.Write((byte)0);
AmongUsClient.Instance.FinishRpcImmediately(writer);
RPCProcedure.vampireSetBitten(Vampire.bitten.PlayerId, 0);
if (murder == MurderAttemptResult.SuppressKill) return;
// Curse Kill
- // Not using Helpers.checkMuderAttemptAndKill here directly, since we need to share the Warlock.curseKillTarget
-
if (murder == MurderAttemptResult.PerformKill) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.WarlockCurseKill, Hazel.SendOption.Reliable, -1);
- writer.Write(Warlock.curseVictimTarget.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.warlockCurseKill(Warlock.curseVictimTarget.PlayerId);
-
+ MessageWriter killWriter = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
+ killWriter.Write(Warlock.warlock.Data.PlayerId);
+ killWriter.Write(Warlock.curseVictimTarget.PlayerId);
+ killWriter.Write((byte)0);
+ AmongUsClient.Instance.FinishRpcImmediately(killWriter);
+ RPCProcedure.uncheckedMurderPlayer(Warlock.warlock.Data.PlayerId, Warlock.curseVictimTarget.PlayerId, (byte)0);
}
if(Warlock.rootTime > 0) {
public static void handleVampireBiteOnBodyReport() {
// Murder the bitten player and reset bitten (regardless whether the kill was successful or not)
- Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten, true);
+ Helpers.checkMuderAttemptAndKill(Vampire.vampire, Vampire.bitten, true, false);
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.VampireSetBitten, Hazel.SendOption.Reliable, -1);
writer.Write(byte.MaxValue);
writer.Write(byte.MaxValue);
return MurderAttemptResult.PerformKill;
}
- public static MurderAttemptResult checkMuderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false) {
+ public static MurderAttemptResult checkMuderAttemptAndKill(PlayerControl killer, PlayerControl target, bool isMeetingStart = false, bool showAnimation = true) {
// The local player checks for the validity of the kill and performs it afterwards (different to vanilla, where the host performs all the checks)
// The kill attempt will be shared using a custom RPC, hence combining modded and unmodded versions is impossible
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedMurderPlayer, Hazel.SendOption.Reliable, -1);
writer.Write(killer.PlayerId);
writer.Write(target.PlayerId);
+ writer.Write(showAnimation ? Byte.MaxValue : 0);
AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.uncheckedMurderPlayer(killer.PlayerId, target.PlayerId);
+ RPCProcedure.uncheckedMurderPlayer(killer.PlayerId, target.PlayerId, showAnimation ? Byte.MaxValue : (byte)0);
}
return murder;
}
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "3.2.2";
+ public const string VersionString = "3.2.3";
public static System.Version Version = System.Version.Parse(VersionString);
internal static BepInEx.Logging.ManualLogSource Logger;
// Witch execute casted spells
if (Witch.witch != null && Witch.futureSpelled != null && AmongUsClient.Instance.AmHost) {
- if (!Witch.witchVoteSavesTargets || exiled == null || exiled.PlayerId != Witch.witch.PlayerId) {
- foreach (PlayerControl target in Witch.futureSpelled) {
- if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill) {
- MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedExilePlayer, Hazel.SendOption.Reliable, -1);
- writer.Write(target.PlayerId);
- AmongUsClient.Instance.FinishRpcImmediately(writer);
- RPCProcedure.uncheckedExilePlayer(target.PlayerId);
- }
+ bool exiledIsWitch = exiled != null && exiled.PlayerId == Witch.witch.PlayerId;
+ bool witchDiesWithExiledLover = exiled != null && Lovers.existing() && Lovers.bothDie && (Lovers.lover1.PlayerId == Witch.witch.PlayerId || Lovers.lover2.PlayerId == Witch.witch.PlayerId) && (exiled.PlayerId == Lovers.lover1.PlayerId || exiled.PlayerId == Lovers.lover2.PlayerId);
+
+ if ((witchDiesWithExiledLover || exiledIsWitch) && Witch.witchVoteSavesTargets) Witch.futureSpelled = new List<PlayerControl>();
+ foreach (PlayerControl target in Witch.futureSpelled) {
+ if (target != null && !target.Data.IsDead && Helpers.checkMuderAttempt(Witch.witch, target, true) == MurderAttemptResult.PerformKill)
+ {
+ MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(PlayerControl.LocalPlayer.NetId, (byte)CustomRPC.UncheckedExilePlayer, Hazel.SendOption.Reliable, -1);
+ writer.Write(target.PlayerId);
+ AmongUsClient.Instance.FinishRpcImmediately(writer);
+ RPCProcedure.uncheckedExilePlayer(target.PlayerId);
}
}
}
Transform button = UnityEngine.Object.Instantiate(buttonTemplate, buttonParent);
Transform buttonMask = UnityEngine.Object.Instantiate(maskTemplate, buttonParent);
TMPro.TextMeshPro label = UnityEngine.Object.Instantiate(textTemplate, button);
+ button.GetComponent<SpriteRenderer>().sprite = DestroyableSingleton<HatManager>.Instance.AllNamePlates[0].Image;
buttons.Add(button);
int row = i/5, col = i%5;
buttonParent.localPosition = new Vector3(-3.47f + 1.75f * col, 1.5f - 0.45f * row, -5);
[HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
class KillAnimationCoPerformKillPatch {
+ public static bool hideNextAnimation = true;
public static void Prefix(KillAnimation __instance, [HarmonyArgument(0)]ref PlayerControl source, [HarmonyArgument(1)]ref PlayerControl target) {
- if (Vampire.vampire != null && Vampire.vampire == source && Vampire.bitten != null && Vampire.bitten == target)
+ if (hideNextAnimation)
source = target;
-
- if (Warlock.warlock != null && Warlock.warlock == source && Warlock.curseKillTarget != null && Warlock.curseKillTarget == target) {
- source = target;
- Warlock.curseKillTarget = null; // Reset here
+ hideNextAnimation = false;
+ }
+ }
+
+ [HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.SetMovement))]
+ class KillAnimationSetMovementPatch {
+ private static int? colorId = null;
+ public static void Prefix(PlayerControl source, bool canMove) {
+ Color color = source.myRend.material.GetColor("_BodyColor");
+ if (color != null && Morphling.morphling != null && source.Data.PlayerId == Morphling.morphling.PlayerId) {
+ var index = Palette.PlayerColors.IndexOf(color);
+ if (index != -1) colorId = index;
}
}
+
+ public static void Postfix(PlayerControl source, bool canMove) {
+ if (colorId.HasValue) source.RawSetColor(colorId.Value);
+ colorId = null;
+ }
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.Exiled))]
// Lawyer
bool localIsLawyer = Lawyer.lawyer != null && Lawyer.target != null && Lawyer.lawyer == PlayerControl.LocalPlayer;
bool localIsKnowingTarget = Lawyer.lawyer != null && Lawyer.target != null && Lawyer.targetKnows && Lawyer.target == PlayerControl.LocalPlayer;
- if (localIsLawyer || localIsKnowingTarget) {
+ if (localIsLawyer || (localIsKnowingTarget && !Lawyer.lawyer.Data.IsDead)) {
string suffix = Helpers.cs(Lawyer.color, " §");
Lawyer.target.nameText.text += suffix;
SetFutureSpelled,
PlaceJackInTheBox,
LightsOut,
- WarlockCurseKill,
PlaceCamera,
SealVent,
ArsonistWin,
player.MyPhysics.HandleRpc(isEnter != 0 ? (byte)19 : (byte)20, reader);
}
- public static void uncheckedMurderPlayer(byte sourceId, byte targetId) {
+ public static void uncheckedMurderPlayer(byte sourceId, byte targetId, byte showAnimation) {
PlayerControl source = Helpers.playerById(sourceId);
PlayerControl target = Helpers.playerById(targetId);
- if (source != null && target != null) source.MurderPlayer(target);
+ if (source != null && target != null) {
+ if (showAnimation == 0) KillAnimationCoPerformKillPatch.hideNextAnimation = true;
+ source.MurderPlayer(target);
+ }
}
public static void uncheckedCmdReportDeadBody(byte sourceId, byte targetId) {
player.setLook("", 6, "", "", "", "");
}
- public static void vampireSetBitten(byte targetId, byte reset) {
- if (reset != 0) {
+ public static void vampireSetBitten(byte targetId, byte performReset) {
+ if (performReset != 0) {
Vampire.bitten = null;
return;
}
}
}
- public static void warlockCurseKill(byte targetId) {
- foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- if (player.PlayerId == targetId) {
- Warlock.curseKillTarget = player;
- Warlock.warlock.MurderPlayer(player);
- return;
- }
- }
- }
-
public static void placeCamera(byte[] buff) {
var referenceCamera = UnityEngine.Object.FindObjectOfType<SurvCamera>();
if (referenceCamera == null) return; // Mira HQ
public static void lawyerPromotesToPursuer() {
PlayerControl player = Lawyer.lawyer;
+ PlayerControl client = Lawyer.target;
Lawyer.clearAndReload();
Pursuer.pursuer = player;
- return;
+
+ if (player.PlayerId == PlayerControl.LocalPlayer.PlayerId && client != null) {
+ Transform playerInfoTransform = client.nameText.transform.parent.FindChild("Info");
+ TMPro.TextMeshPro playerInfo = playerInfoTransform != null ? playerInfoTransform.GetComponent<TMPro.TextMeshPro>() : null;
+ if (playerInfo != null) playerInfo.text = "";
+ }
}
public static void guesserShoot(byte dyingTargetId, byte guessedTargetId, byte guessedRoleId) {
case (byte)CustomRPC.UncheckedMurderPlayer:
byte source = reader.ReadByte();
byte target = reader.ReadByte();
- RPCProcedure.uncheckedMurderPlayer(source, target);
+ byte showAnimation = reader.ReadByte();
+ RPCProcedure.uncheckedMurderPlayer(source, target, showAnimation);
break;
case (byte)CustomRPC.UncheckedExilePlayer:
byte exileTarget = reader.ReadByte();
case (byte)CustomRPC.LightsOut:
RPCProcedure.lightsOut();
break;
- case (byte)CustomRPC.WarlockCurseKill:
- RPCProcedure.warlockCurseKill(reader.ReadByte());
- break;
case (byte)CustomRPC.PlaceCamera:
RPCProcedure.placeCamera(reader.ReadBytesAndSize());
break;
public static PlayerControl currentTarget;
public static PlayerControl curseVictim;
public static PlayerControl curseVictimTarget;
- public static PlayerControl curseKillTarget;
public static float cooldown = 30f;
public static float rootTime = 5f;
currentTarget = null;
curseVictim = null;
curseVictimTarget = null;
- curseKillTarget = null;
cooldown = CustomOptionHolder.warlockCooldown.getFloat();
rootTime = CustomOptionHolder.warlockRootTime.getFloat();
}
currentTarget = null;
curseVictim = null;
curseVictimTarget = null;
- curseKillTarget = null;
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>3.2.2</Version>
+ <Version>3.2.3</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>