# Releases
| Among Us - Version| Mod Version | Link |
|----------|-------------|-----------------|
+| 2021.6.30s| v2.9.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.9.1/TheOtherRoles.zip)
| 2021.6.30s| v2.9.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.9.0/TheOtherRoles.zip)
| 2021.6.30s| v2.8.1| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.8.1/TheOtherRoles.zip)
| 2021.6.30s| v2.8.0| [Download](https://github.com/Eisbison/TheOtherRoles/releases/download/v2.8.0/TheOtherRoles.zip)
<details>
<summary>Click to show the Changelog</summary>
+**Version 2.9.1**
+- Fixed a bug where [Camouflager](#camouflager) & [Morphling](#morphling) caused performance issues
+- Fixed a bug where [Medium](#medium) did not exlude the Evil [Mini](#mini)
+- [Vulture](#vulture) "Number Of Corpses Needed To Be Eaten" max value extended to 10
+- Added Vulture Option: "Show Arrows Pointing Towards The Corpes"
+- Removed Medium Question: "What is your name?" (name of the soul is added after each question)
+
+
**Version 2.9.0**
- **New Role:** [Medium](#medium)
- **New Role:** [Vulture](#vulture)
[TownOfUs](https://github.com/slushiegoose/Town-Of-Us) - Idea for the Swapper, Shifter, Arsonist and a similar Mayor role come from **Slushiegoose**\
[Ottomated](https://twitter.com/ottomated_) - Idea for the Morphling, Snitch and Camouflager role come from **Ottomated**\
[Crowded-Mod](https://github.com/CrowdedMods/CrowdedMod) - Our implementation for 10+ player lobbies is inspired by the one from the **Crowded Mod Team**\
+[Goose-Goose-Duck](https://store.steampowered.com/app/1568590/Goose_Goose_Duck) - Idea for the Vulture role come from **Slushygoose**
# Settings
The mod adds a few new settings to Among Us (in addition to the role settings):
\
The medium is a crewmate who can ask the souls of dead players for information. Like the Seer, it sees the places where the players have died (after the next meeting) and can question them. It then gets random information about the soul or the killer in the chat. The souls only stay for one round, i.e. until the next meeting. Depending on the options, the souls can only be questioned once and then disappear.
-Questions: What is your name?
+Questions:
What is your Role?
What is your killer's color type?
When did you die?
### Game Options
| Name | Description
|----------|:-------------:|
-| Bait Spawn Chance | -
+| Medium Spawn Chance | -
| Medium Cooldown | -
| Medium Duration | The time it takes to question a soul
| Medium Each Soul Can Only Be Questioned Once | If set to true, souls can only be questioned once and then disappear
\
The Vulture does not have any tasks, he has to win the game as a solo.\
The Vulture is a neutral role that must eat a specified number of corpses (depending on the options) in order to win.\
-When a player dies, the Vulture gets an arrow pointing to the corpse.
+Depending on the options, when a player dies, the Vulture gets an arrow pointing to the corpse.
### Game Options
| Name | Description |
| Vulture Countdown | -
| Number Of Corpses Needed To Be Eaten | Corpes needed to be eaten to win the game
| Vulture Can Use Vents | -
+| Show Arrows Pointing Towards The Corpes | -
-----------------------
# Source code
if (Medium.target == null || Medium.target.player == null) return;
string msg = "";
- int randomNumber = Medium.target.player.PlayerId == Mini.mini?.PlayerId ? TheOtherRoles.rnd.Next(4) : TheOtherRoles.rnd.Next(5);
+ int randomNumber = Medium.target.killerIfExisting?.PlayerId == Mini.mini?.PlayerId ? TheOtherRoles.rnd.Next(3) : TheOtherRoles.rnd.Next(4);
string typeOfColor = Helpers.isLighterColor(Medium.target.killerIfExisting.Data.ColorId) ? "lighter" : "darker";
float timeSinceDeath = ((float)(Medium.meetingStartTime - Medium.target.timeOfDeath).TotalMilliseconds);
-
- if (randomNumber == 0) msg = "What is your Name? My name is " + Medium.target.player.Data.PlayerName;
- else if (randomNumber == 1) msg = "What is your role? My role is " + RoleInfo.GetRole(Medium.target.player);
- else if (randomNumber == 2) msg = "What is your killer`s color type? My killer is a " + typeOfColor + " color";
- else if (randomNumber == 3) msg = "When did you die? I have died " + Math.Round(timeSinceDeath / 1000) + "s before meeting started";
- else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRole(Medium.target.killerIfExisting); //exlude mini
+ string name = " (" + Medium.target.player.Data.PlayerName + ")";
+
+
+ if (randomNumber == 0) msg = "What is your role? My role is " + RoleInfo.GetRole(Medium.target.player) + name;
+ else if (randomNumber == 1) msg = "What is your killer`s color type? My killer is a " + typeOfColor + " color" + name;
+ else if (randomNumber == 2) msg = "When did you die? I have died " + Math.Round(timeSinceDeath / 1000) + "s before meeting started" + name;
+ else msg = "What is your killer`s role? My killer is " + RoleInfo.GetRole(Medium.target.killerIfExisting) + name; //exlude mini
DestroyableSingleton<HudManager>.Instance.Chat.AddChat(PlayerControl.LocalPlayer, $"{msg}");
public static CustomOption vultureCooldown;
public static CustomOption vultureNumberToWin;
public static CustomOption vultureCanUseVents;
+ public static CustomOption vultureShowArrows;
public static CustomOption mediumSpawnRate;
public static CustomOption mediumCooldown;
vultureSpawnRate = CustomOption.Create(340, cs(Vulture.color, "Vulture"), rates, null, true);
vultureCooldown = CustomOption.Create(341, "Vulture Cooldown", 15f, 10f, 60f, 2.5f, vultureSpawnRate);
- vultureNumberToWin = CustomOption.Create(342, "Number Of Corpses Needed To Be Eaten", 4f, 0f, 5f, 1f, vultureSpawnRate);
+ vultureNumberToWin = CustomOption.Create(342, "Number Of Corpses Needed To Be Eaten", 4f, 0f, 10f, 1f, vultureSpawnRate);
vultureCanUseVents = CustomOption.Create(343, "Vulture Can Use Vents", true, vultureSpawnRate);
+ vultureShowArrows = CustomOption.Create(344, "Show Arrows Pointing Towards The Corpes", true, vultureSpawnRate);
shifterSpawnRate = CustomOption.Create(70, cs(Shifter.color, "Shifter"), rates, null, true);
shifterShiftsModifiers = CustomOption.Create(71, "Shifter Shifts Modifiers", false, shifterSpawnRate);
return res;
}
- public static void setSkinWithAnim(PlayerPhysics playerPhysics, uint SkinId) {
- SkinData nextSkin = DestroyableSingleton<HatManager>.Instance.AllSkins[(int)SkinId];
- AnimationClip clip = null;
- var spriteAnim = playerPhysics.Skin.animator;
- var anim = spriteAnim.m_animator;
- var skinLayer = playerPhysics.Skin;
-
- var currentPhysicsAnim = playerPhysics.Animator.GetCurrentAnimation();
- if (currentPhysicsAnim == playerPhysics.RunAnim) clip = nextSkin.RunAnim;
- else if (currentPhysicsAnim == playerPhysics.SpawnAnim) clip = nextSkin.SpawnAnim;
- else if (currentPhysicsAnim == playerPhysics.EnterVentAnim) clip = nextSkin.EnterVentAnim;
- else if (currentPhysicsAnim == playerPhysics.ExitVentAnim) clip = nextSkin.ExitVentAnim;
- else if (currentPhysicsAnim == playerPhysics.IdleAnim) clip = nextSkin.IdleAnim;
- else clip = nextSkin.IdleAnim;
-
- float progress = playerPhysics.Animator.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
- skinLayer.skin = nextSkin;
-
- spriteAnim.Play(clip, 1f);
- anim.Play("a", 0, progress % 1);
- anim.Update(0f);
- }
-
public static bool handleMurderAttempt(PlayerControl target, bool isMeetingStart = false) {
// Block impostor shielded kill
if (Medic.shielded != null && Medic.shielded == target) {
}
return result;
}
+
+ public static bool hidePlayerName(PlayerControl source, PlayerControl target) {
+ if (!MapOptions.hidePlayerNames) return false; // All names are visible
+ else if (source == null || target == null) return true;
+ else if (source == target) return false; // Player sees his own name
+ else if (source.Data.IsImpostor && (target.Data.IsImpostor || target == Spy.spy)) return false; // Members of team Impostors see the names of Impostors/Spies
+ else if ((source == Lovers.lover1 || source == Lovers.lover2) && (target == Lovers.lover1 || target == Lovers.lover2)) return false; // Members of team Lovers see the names of each other
+ else if ((source == Jackal.jackal || source == Sidekick.sidekick) && (target == Jackal.jackal || target == Sidekick.sidekick || target == Jackal.fakeSidekick)) return false; // Members of team Jackal see the names of each other
+ return true;
+ }
+
+ public static void setDefaultLook(this PlayerControl target) {
+ target.setLook(target.Data.PlayerName, target.Data.ColorId, target.Data.HatId, target.Data.SkinId, target.Data.PetId);
+ }
+
+ public static void setLook(this PlayerControl target, String playerName, int colorId, uint hatId, uint skinId, uint petId) {
+ target.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, target) ? "" : playerName;
+ target.myRend.material.SetColor("_BackColor", Palette.ShadowColors[colorId]);
+ target.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[colorId]);
+ target.HatRenderer.SetHat(hatId, colorId);
+ target.nameText.transform.localPosition = new Vector3(0f, ((hatId == 0U) ? 0.7f : 1.05f) * 2f, -0.5f);
+
+ SkinData nextSkin = DestroyableSingleton<HatManager>.Instance.AllSkins[(int)skinId];
+ PlayerPhysics playerPhysics = target.MyPhysics;
+ AnimationClip clip = null;
+ var spriteAnim = playerPhysics.Skin.animator;
+ var currentPhysicsAnim = playerPhysics.Animator.GetCurrentAnimation();
+ if (currentPhysicsAnim == playerPhysics.RunAnim) clip = nextSkin.RunAnim;
+ else if (currentPhysicsAnim == playerPhysics.SpawnAnim) clip = nextSkin.SpawnAnim;
+ else if (currentPhysicsAnim == playerPhysics.EnterVentAnim) clip = nextSkin.EnterVentAnim;
+ else if (currentPhysicsAnim == playerPhysics.ExitVentAnim) clip = nextSkin.ExitVentAnim;
+ else if (currentPhysicsAnim == playerPhysics.IdleAnim) clip = nextSkin.IdleAnim;
+ else clip = nextSkin.IdleAnim;
+ float progress = playerPhysics.Animator.m_animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
+ playerPhysics.Skin.skin = nextSkin;
+ spriteAnim.Play(clip, 1f);
+ spriteAnim.m_animator.Play("a", 0, progress % 1);
+ spriteAnim.m_animator.Update(0f);
+
+ if (target.CurrentPet) UnityEngine.Object.Destroy(target.CurrentPet.gameObject);
+ target.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[(int)petId]);
+ target.CurrentPet.transform.position = target.transform.position;
+ target.CurrentPet.Source = target;
+ target.CurrentPet.Visible = target.Visible;
+ PlayerControl.SetPlayerMaterialColors(colorId, target.CurrentPet.rend);
+ }
}
}
public class TheOtherRolesPlugin : BasePlugin
{
public const string Id = "me.eisbison.theotherroles";
- public const string VersionString = "2.9.0";
+ public const string VersionString = "2.9.1";
public static System.Version Version = System.Version.Parse(VersionString);
public Harmony Harmony { get; } = new Harmony(Id);
}
}
static void vultureUpdate() {
- if (Vulture.vulture == null || PlayerControl.LocalPlayer != Vulture.vulture || Vulture.localArrows == null) return;
+ if (Vulture.vulture == null || PlayerControl.LocalPlayer != Vulture.vulture || Vulture.localArrows == null || !Vulture.showArrows) return;
if (Vulture.vulture.Data.IsDead) {
foreach (Arrow arrow in Vulture.localArrows) UnityEngine.Object.Destroy(arrow.arrow);
Vulture.localArrows = new List<Arrow>();
Medium.target = target;
}
+ static void morphlingAndCamouflagerUpdate() {
+ float oldCamouflageTimer = Camouflager.camouflageTimer;
+ float oldMorphTimer = Morphling.morphTimer;
+ Camouflager.camouflageTimer = Mathf.Max(0f, Camouflager.camouflageTimer - Time.fixedDeltaTime);
+ Morphling.morphTimer = Mathf.Max(0f, Morphling.morphTimer - Time.fixedDeltaTime);
+
+
+ // Camouflage reset and set Morphling look if necessary
+ if (oldCamouflageTimer > 0f && Camouflager.camouflageTimer <= 0f) {
+ Camouflager.resetCamouflage();
+ if (Morphling.morphTimer > 0f && Morphling.morphling != null && Morphling.morphTarget != null) {
+ PlayerControl target = Morphling.morphTarget;
+ Morphling.morphling.setLook(target.Data.PlayerName, target.Data.ColorId, target.Data.HatId, target.Data.SkinId, target.Data.PetId);
+ }
+ }
+
+ // Morphling reset (only if camouflage is inactive)
+ if (Camouflager.camouflageTimer <= 0f && oldMorphTimer > 0f && Morphling.morphTimer <= 0f && Morphling.morphling != null)
+ Morphling.resetMorph();
+ }
+
public static void Postfix(PlayerControl __instance) {
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
vultureUpdate();
// Medium
mediumSetTarget();
+ // Morphling and Camouflager
+ morphlingAndCamouflagerUpdate();
}
}
}
[HarmonyPatch(typeof(HudManager), nameof(HudManager.Update))]
class HudManagerUpdatePatch
{
- public static bool hidePlayerName(PlayerControl source, PlayerControl target) {
- if (!MapOptions.hidePlayerNames) return false; // All names are visible
- else if (source == null || target == null) return true;
- else if (source == target) return false; // Player sees his own name
- else if (source.Data.IsImpostor && (target.Data.IsImpostor || target == Spy.spy)) return false; // Members of team Impostors see the names of Impostors/Spies
- else if ((source == Lovers.lover1 || source == Lovers.lover2) && (target == Lovers.lover1 || target == Lovers.lover2)) return false; // Members of team Lovers see the names of each other
- else if ((source == Jackal.jackal || source == Sidekick.sidekick) && (target == Jackal.jackal || target == Sidekick.sidekick || target == Jackal.fakeSidekick)) return false; // Members of team Jackal see the names of each other
- return true;
- }
-
static void resetNameTagsAndColors() {
Dictionary<byte, PlayerControl> playersById = Helpers.allPlayersById();
foreach (PlayerControl player in PlayerControl.AllPlayerControls) {
- player.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, player) ? "" : player.Data.PlayerName;
+ player.nameText.text = Helpers.hidePlayerName(PlayerControl.LocalPlayer, player) ? "" : player.Data.PlayerName;
if (PlayerControl.LocalPlayer.Data.IsImpostor && player.Data.IsImpostor) {
player.nameText.color = Palette.ImpostorRed;
} else {
Trickster.lightsOutTimer -= Time.deltaTime;
}
- static void camouflageAndMorphActions() {
- float oldCamouflageTimer = Camouflager.camouflageTimer;
- float oldMorphTimer = Morphling.morphTimer;
-
- Camouflager.camouflageTimer -= Time.deltaTime;
- Morphling.morphTimer -= Time.deltaTime;
-
- // Morphling player size not done here
-
- // Set morphling morphed look
- if (Morphling.morphTimer > 0f && Camouflager.camouflageTimer <= 0f) {
- if (Morphling.morphling != null && Morphling.morphTarget != null) {
- Morphling.morphling.nameText.text = hidePlayerName(PlayerControl.LocalPlayer, Morphling.morphling) ? "" : Morphling.morphTarget.Data.PlayerName;
- Morphling.morphling.myRend.material.SetColor("_BackColor", Palette.ShadowColors[Morphling.morphTarget.Data.ColorId]);
- Morphling.morphling.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[Morphling.morphTarget.Data.ColorId]);
- Morphling.morphling.HatRenderer.SetHat(Morphling.morphTarget.Data.HatId, Morphling.morphTarget.Data.ColorId);
- Morphling.morphling.nameText.transform.localPosition = new Vector3(0f, ((Morphling.morphTarget.Data.HatId == 0U) ? 0.7f : 1.05f) * 2f, -0.5f);
-
- if (Morphling.morphling.MyPhysics.Skin.skin.ProdId != DestroyableSingleton<HatManager>.Instance.AllSkins[(int)Morphling.morphTarget.Data.SkinId].ProdId) {
- Helpers.setSkinWithAnim(Morphling.morphling.MyPhysics, Morphling.morphTarget.Data.SkinId);
- }
- if (Morphling.morphling.CurrentPet == null || Morphling.morphling.CurrentPet.ProdId != DestroyableSingleton<HatManager>.Instance.AllPets[(int)Morphling.morphTarget.Data.PetId].ProdId) {
- if (Morphling.morphling.CurrentPet) UnityEngine.Object.Destroy(Morphling.morphling.CurrentPet.gameObject);
- Morphling.morphling.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[(int)Morphling.morphTarget.Data.PetId]);
- Morphling.morphling.CurrentPet.transform.position = Morphling.morphling.transform.position;
- Morphling.morphling.CurrentPet.Source = Morphling.morphling;
- Morphling.morphling.CurrentPet.Visible = Morphling.morphling.Visible;
- PlayerControl.SetPlayerMaterialColors(Morphling.morphTarget.Data.ColorId, Morphling.morphling.CurrentPet.rend);
- } else if (Morphling.morphling.CurrentPet) {
- PlayerControl.SetPlayerMaterialColors(Morphling.morphTarget.Data.ColorId, Morphling.morphling.CurrentPet.rend);
- }
- }
- }
-
- // Set camouflaged look (overrides morphling morphed look if existent)
- if (Camouflager.camouflageTimer > 0f) {
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- p.nameText.text = "";
- p.myRend.material.SetColor("_BackColor", Palette.PlayerColors[6]);
- p.myRend.material.SetColor("_BodyColor", Palette.PlayerColors[6]);
- p.HatRenderer.SetHat(0, 0);
- Helpers.setSkinWithAnim(p.MyPhysics, 0);
- bool spawnPet = false;
- if (p.CurrentPet == null) spawnPet = true;
- else if (p.CurrentPet.ProdId != DestroyableSingleton<HatManager>.Instance.AllPets[0].ProdId) {
- UnityEngine.Object.Destroy(p.CurrentPet.gameObject);
- spawnPet = true;
- }
-
- if (spawnPet) {
- p.CurrentPet = UnityEngine.Object.Instantiate<PetBehaviour>(DestroyableSingleton<HatManager>.Instance.AllPets[0]);
- p.CurrentPet.transform.position = p.transform.position;
- p.CurrentPet.Source = p;
- }
- }
- }
-
- // Everyone but morphling reset
- if (oldCamouflageTimer > 0f && Camouflager.camouflageTimer <= 0f) {
- Camouflager.resetCamouflage();
- }
-
- // Morphling reset
- if ((oldMorphTimer > 0f || oldCamouflageTimer > 0f) && Camouflager.camouflageTimer <= 0f && Morphling.morphTimer <= 0f && Morphling.morphling != null) {
- Morphling.resetMorph();
- }
- }
-
public static void miniUpdate() {
if (Mini.mini == null || Camouflager.camouflageTimer > 0f) return;
updateImpostorKillButton(__instance);
// Timer updates
timerUpdate();
- // Camouflager and Morphling
- camouflageAndMorphActions();
// Mini
miniUpdate();
}
Morphling.morphTimer = Morphling.duration;
Morphling.morphTarget = target;
+ if (Camouflager.camouflageTimer <= 0f)
+ Morphling.morphling.setLook(target.Data.PlayerName, target.Data.ColorId, target.Data.HatId, target.Data.SkinId, target.Data.PetId);
}
public static void camouflagerCamouflage() {
if (Camouflager.camouflager == null) return;
Camouflager.camouflageTimer = Camouflager.duration;
+ foreach (PlayerControl player in PlayerControl.AllPlayerControls)
+ player.setLook("", 6, 0, 0, 0);
}
public static void vampireSetBitten(byte targetId, byte reset) {
morphTarget = null;
morphTimer = 0f;
if (morphling == null) return;
- morphling.SetName(morphling.Data.PlayerName);
- morphling.SetHat(morphling.Data.HatId, (int)morphling.Data.ColorId);
- Helpers.setSkinWithAnim(morphling.MyPhysics, morphling.Data.SkinId);
- morphling.SetPet(morphling.Data.PetId);
- morphling.CurrentPet.Visible = morphling.Visible;
- morphling.SetColor(morphling.Data.ColorId);
+ morphling.setDefaultLook();
}
public static void clearAndReload() {
public static void resetCamouflage() {
camouflageTimer = 0f;
- foreach (PlayerControl p in PlayerControl.AllPlayerControls) {
- if (p == null) continue;
- if (Morphling.morphling == null || Morphling.morphling != p) {
- p.SetName(p.Data.PlayerName);
- p.SetHat(p.Data.HatId, (int)p.Data.ColorId);
- Helpers.setSkinWithAnim(p.MyPhysics, p.Data.SkinId);
- p.SetPet(p.Data.PetId);
- p.CurrentPet.Visible = p.Visible;
- p.SetColor(p.Data.ColorId);
- }
- }
+ foreach (PlayerControl p in PlayerControl.AllPlayerControls)
+ p.setDefaultLook();
}
public static void clearAndReload() {
public static int vultureNumberToWin = 4;
public static int eatenBodies = 0;
public static bool triggerVultureWin = false;
- public static bool canUseVents = false;
+ public static bool canUseVents = true;
+ public static bool showArrows = true;
private static Sprite buttonSprite;
public static Sprite getButtonSprite() {
if (buttonSprite) return buttonSprite;
cooldown = CustomOptionHolder.vultureCooldown.getFloat();
triggerVultureWin = false;
canUseVents = CustomOptionHolder.vultureCanUseVents.getBool();
+ showArrows = CustomOptionHolder.vultureShowArrows.getBool();
if (localArrows != null) {
foreach (Arrow arrow in localArrows)
if (arrow?.arrow != null)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
- <Version>2.9.0</Version>
+ <Version>2.9.1</Version>
<Description>TheOtherRoles</Description>
<Authors>Eisbison</Authors>
</PropertyGroup>