Experimental: More Death Reasons + Bloodmoon fix attempt + Disabling "DisableFungleCamera" (#624)

* Update GameState.cs

* Update Glitch.cs

* Update Arrogance.cs

* Update Sheriff.cs

* Update Veteran.cs

* Update Goddess.cs

* Update Cleaner.cs

* hope this works

* wait nvm

* Update PlagueBearer.cs

* for later bloodmoon fix (prob?)

* Update Bloodmoon.cs

* Update Bloodmoon.cs

* Update en_US.jsonc

* Update PlagueBearer.cs

* Update Veteran.cs

* wow fk me

* Update PlagueBearer.cs

* wow fk me again

* Update PlagueBearer.cs

* Update CustomRolesHelper.cs

* Update Survivor.cs

Added a late task to set the player's custom role to Scanner if they are in Killing state. Updated button text method parameter name and modified vent entry logic.

* Update Survivor.cs

* Hmmm. :)))

* done

* done

* done?

Update death reason handling for Medusa effect.

* done

* bruh

* wow ctrl z without replacing, fk me

* disable fungle camera DO NOT WORKS FOR VANILLA CLIENTS

* part 2

* part 3
This commit is contained in:
Newchan authored and GitHub committed 2026-06-02 10:01:10 +02:00
1 parent 47df8e7dbd
commit 3e7c0fbb0f
15 files changed
+89 -33

No files matched your search

+1
View File
@@ -1867,6 +1867,7 @@ internal static class CustomRolesHelper
{
CustomRoles.Blind when pc.Is(CustomRoles.Sensor) => false,
CustomRoles.Composter when float.IsNaN(pc.GetAbilityUseLimit()) => false,
CustomRoles.Absorber when float.IsNaN(pc.GetAbilityUseLimit()) => false,
CustomRoles.TaskMaster when !pc.IsCrewmate() || !Utils.HasTasks(pc.Data, forRecompute: false) => false,
CustomRoles.Commited when pc.GetRoleTypes() is not (RoleTypes.Impostor or RoleTypes.Phantom or RoleTypes.Shapeshifter or RoleTypes.Viper) => false,
CustomRoles.Underdog when pc.Is(CustomRoles.Mare) => false,
+3 -3
View File
@@ -118,7 +118,7 @@ internal static class DisableDevice
if (Options.DisableAirshipVital.GetBool() || rogueForce) doComms |= FastVector2.DistanceWithinRange(PlayerPos, DevicePos["AirshipVital"], UsableDistance);
break;
case 5:
if (Options.DisableFungleCamera.GetBool() || rogueForce) doComms |= FastVector2.DistanceWithinRange(PlayerPos, DevicePos["FungleCamera"], UsableDistance);
/*if (Options.DisableFungleCamera.GetBool() || rogueForce) doComms |= FastVector2.DistanceWithinRange(PlayerPos, DevicePos["FungleCamera"], UsableDistance);*/
if (Options.DisableFungleVital.GetBool() || rogueForce) doComms |= FastVector2.DistanceWithinRange(PlayerPos, DevicePos["FungleVital"], UsableDistance);
break;
}
@@ -218,9 +218,9 @@ public class RemoveDisableDevicesPatch
if (Options.DisableAirshipVital.GetBool() || rogueForce) consoles.DoIf(x => x.name == "panel_vitals", x => x.gameObject.GetComponent<CircleCollider2D>().enabled = ignore);
break;
case 5:
if (Options.DisableFungleCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "BinocularsSecurityConsole", x => x.gameObject.GetComponent<PolygonCollider2D>().enabled = ignore);
/*if (Options.DisableFungleCamera.GetBool() || rogueForce) consoles.DoIf(x => x.name == "BinocularsSecurityConsole", x => x.gameObject.GetComponent<PolygonCollider2D>().enabled = ignore);*/
if (Options.DisableFungleVital.GetBool() || rogueForce) consoles.DoIf(x => x.name == "VitalsConsole", x => x.gameObject.GetComponent<BoxCollider2D>().enabled = ignore);
break;
}
}
}
}
+5
View File
@@ -74,6 +74,11 @@ public class PlayerState(byte playerId)
LossOfBlood,
Frightened,
Bankrupt,
Bugged,
Destroyed,
Shot,
Diseased,
Stoned,
// Natural Disasters
Meteor,
+4 -4
View File
@@ -578,7 +578,7 @@ public static class Options
public static OptionItem DisableAirshipCamera;
public static OptionItem DisableAirshipVital;
private static OptionItem DisableFungleDevices;
public static OptionItem DisableFungleCamera;
//public static OptionItem DisableFungleCamera;
public static OptionItem DisableFungleVital;
private static OptionItem DisableDevicesIgnoreConditions;
public static OptionItem DisableDevicesIgnoreImpostors;
@@ -2420,11 +2420,11 @@ public static class Options
DisableFungleDevices = new BooleanOptionItem(22925, "DisableFungleDevices", false, TabGroup.GameSettings)
.SetParent(DisableDevices)
.SetColor(new Color32(255, 153, 153, byte.MaxValue));
/*
DisableFungleCamera = new BooleanOptionItem(22926, "DisableFungleCamera", false, TabGroup.GameSettings)
.SetParent(DisableFungleDevices)
.SetColor(new Color32(255, 153, 153, byte.MaxValue));
*/
DisableFungleVital = new BooleanOptionItem(22927, "DisableFungleVital", false, TabGroup.GameSettings)
.SetParent(DisableFungleDevices)
.SetColor(new Color32(255, 153, 153, byte.MaxValue));
@@ -3591,4 +3591,4 @@ public static class Options
public static OptionItem PacifistAbilityChargesWhenFinishedTasks;
// ReSharper restore NotAccessedField.Global
}
}
+8 -3
View File
@@ -2742,7 +2742,7 @@
"HookshotTpToTarget": "TP to Target",
"Infection": "Infection",
"InfectionInfo": "Spread the infection!",
"InfectionInfoLong": "(Neutrals) (Plague Doctor from TOH):\\nThe Plague Doctor's goal is to infect every living player. They start by choosing one player to infect, after which anyone who spends a set amount of time in range of the infected player becomes infected themselves. Infection progress is cumulative, and does not reset with distance or after meetings.",
"InfectionInfoLong": "(Neutrals) (Plague Doctor from TOH):\\nThe Infection's goal is to infect every living player. They start by choosing one player to infect, after which anyone who spends a set amount of time in range of the infected player becomes infected themselves. Infection progress is cumulative, and does not reset with distance or after meetings.",
"InfectionCanInfectSelf": "Can Infect Self",
"InfectionCanInfectVent": "Can Infect While In Vent",
"InfectionInfectDistance": "Infect Distance",
@@ -3733,7 +3733,7 @@
"EntrancedKillCoven": "As an Entranced player, you cannot kill Coven members",
"EvilSpirit": "Evil Spirit",
"EvilSpiritInfo": "You are an evil Spirit",
"EvilSpiritInfoLong": "(Add-ons):\\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.",
"EvilSpiritInfoLong": "(Neutral Ghost):\\nAs Evil Spirit, it's your job to help the Spiritcaller to victory. You can use your Haunt button to freeze players and reduce their vision. Alternatively, you can use your Haunt button to give the Spiritcaller a shield against a kill attempt temporarily.",
"Finder": "Finder",
"FinderInfo": "Grants Access to Tracking Players",
"FinderInfoLong": "(Add-ons):\\nGrants access to tracking players for crewmate roles.",
@@ -3870,7 +3870,7 @@
"ReachInfoLong": "(Add-on)\\nOnly roles with a kill button can get this add-on. You have the longest kill range possible in the game, unlike everyone else.",
"Reroll": "Reroll",
"RerollInfo": "You can reroll your role via /cmd reroll",
"RerollInfoLong": "(Add-ons):\\nIf you use /cmd reroll during a meeting, you reroll your role after the meeting ends. If you survive the meeting, you become a new role from the configured pool. If you are exiled, no reroll happens.",
"RerollInfoLong": "(Add-ons):\\nAs the Reroll, use /cmd reroll during a meeting to reroll your role after the meeting ends. If you survive, you will become a new role from the configured pool; otherwise, no reroll happens.",
"Reroll.SameFactionOnly": "Can only become a role from the same faction",
"Reroll.AnnounceReroll": "Notify everyone when a reroll happens",
"Reroll.OnlyHostEnabledRoles": "Only reroll into host-enabled roles",
@@ -9989,15 +9989,18 @@
"DeathReason.BadLuck": "Bad Luck",
"DeathReason.Bankrupt": "Bankrupted",
"DeathReason.Bite": "Bitten",
"DeathReason.Bugged": "Bugged",
"DeathReason.Bombed": "Exploded",
"DeathReason.Collapsed": "Collapsed",
"DeathReason.Consumed": "Consumed",
"DeathReason.Curse": "Cursed",
"DeathReason.Cursed": "Cursed",
"DeathReason.Deafened": "Deafened",
"DeathReason.Destroyed": "Destroyed",
"DeathReason.Demolished": "Demolished",
"DeathReason.DidntVote": "Didn't Vote",
"DeathReason.Disconnected": "Disconnected",
"DeathReason.Diseased": "Diseased",
"DeathReason.Dismembered": "Dismembered",
"DeathReason.Dragged": "Dragged",
"DeathReason.Drowned": "Drowned",
@@ -10034,9 +10037,11 @@
"DeathReason.Revenge": "Revenge",
"DeathReason.Sacrifice": "Sacrifice",
"DeathReason.Scavenged": "Scavenged",
"DeathReason.Shot": "Shot",
"DeathReason.SkippedVote": "Skipped Voting",
"DeathReason.Sniped": "Sniped",
"DeathReason.Spell": "Spelled",
"DeathReason.Stoned": "Stoned",
"DeathReason.Stung": "Stung",
"DeathReason.Suicide": "Suicide",
"DeathReason.Sunken": "Sunken",
+7 -1
View File
@@ -76,10 +76,16 @@ public class Goddess : CovenBase
if (Timer == null || killer.Is(CustomRoles.Pestilence) || !killer.IsAlive()) return true;
killer.SetRealKiller(target);
Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Stoned;
target.Kill(killer);
return false;
}
public override void OnMurder(PlayerControl killer, PlayerControl target)
{
Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Stoned;
}
public void ReceiveRPC(MessageReader reader)
{
Timer = new CountdownTimer(AbilityDuration.GetInt(), () => Timer = null, onCanceled: () => Timer = null);
@@ -90,4 +96,4 @@ public class Goddess : CovenBase
if (seer.PlayerId != GoddessId || seer.PlayerId != target.PlayerId || (seer.IsModdedClient() && !hud) || meeting || Timer == null) return string.Empty;
return string.Format(Translator.GetString("Goddess.Suffix"), (int)Math.Ceiling(Timer.Remaining.TotalSeconds), Main.CovenColor);
}
}
}
+2 -1
View File
@@ -139,6 +139,7 @@ public class Sheriff : RoleBase
{
killer.RpcRemoveAbilityUse();
Logger.Info($"{killer.GetNameWithRole().RemoveHtmlTags()} : Number of kills left: {killer.GetAbilityUseLimit()}", "Sheriff");
Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Shot;
if (killer.AmOwner && Utils.TimeStamp - IntroCutsceneDestroyPatch.IntroDestroyTS < 25)
Achievements.Type.ItsGamblingTime.Complete();
@@ -197,4 +198,4 @@ public class Sheriff : RoleBase
keepGameGoing = true;
countsAs = 1;
}
}
}
+2 -1
View File
@@ -124,6 +124,7 @@ internal class Veteran : RoleBase
if (!killer.Is(CustomRoles.Pestilence))
{
killer.SetRealKiller(target);
Main.PlayerStates[killer.PlayerId].deathReason = PlayerState.DeathReason.Shot;
target.Kill(killer);
Logger.Info($"{target.GetRealName()} reverse killed: {killer.GetRealName()}", "Veteran Kill");
return false;
@@ -158,4 +159,4 @@ internal class Veteran : RoleBase
keepGameGoing = true;
countsAs = 1;
}
}
}
+9 -1
View File
@@ -73,6 +73,14 @@ public class Survivor : RoleBase
ShieldTimer = null;
Killing = false;
SurvivorId = playerId;
LateTask.New(() =>
{
PlayerControl player = Utils.GetPlayerById(playerId);
Killing = player.Is(CustomRoles.Bloodlust);
if (Killing)
player.RpcSetCustomRole(CustomRoles.Scanner);
}, 1f, log: false);
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
@@ -121,7 +129,7 @@ public class Survivor : RoleBase
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
if (!Killing)
if (!Killing && !pc.Is(CustomRoles.Bloodlust))
ShieldSelf(pc);
}
+17 -14
View File
@@ -13,14 +13,14 @@ internal class Bloodmoon : IGhostRole
private static OptionItem DieOnMeetingCall;
private static OptionItem CDIncreasePerUse;
private static readonly Dictionary<byte, long> ScheduledDeaths = [];
private static readonly Dictionary<byte, (long TimeStamp, byte KillerId)> ScheduledDeaths = [];
private long LastUpdate;
private byte BloodmoonID;
public Team Team => Team.Impostor | Team.Neutral;
public RoleTypes RoleTypes => RoleTypes.GuardianAngel;
public int Cooldown => CD.GetInt() + Main.PlayerStates.Values.Count(x => x.GetRealKiller() == BloodmoonID && x.deathReason == PlayerState.DeathReason.LossOfBlood) * CDIncreasePerUse.GetInt();
public int Cooldown => CD.GetInt() + (Main.PlayerStates.Values.Count(x => x.GetRealKiller() == BloodmoonID && x.deathReason == PlayerState.DeathReason.LossOfBlood) + ScheduledDeaths.Values.Count(v => v.KillerId == BloodmoonID)) * CDIncreasePerUse.GetInt();
public void OnAssign(PlayerControl pc)
{
@@ -32,8 +32,10 @@ internal class Bloodmoon : IGhostRole
public void OnProtect(PlayerControl pc, PlayerControl target)
{
if (target.Is(CustomRoles.Pestilence) || !pc.RpcCheckAndMurder(target, true)) return;
ScheduledDeaths.TryAdd(target.PlayerId, Utils.TimeStamp);
if (target.Is(CustomRoles.Pestilence) || (target.Is(Team.Impostor) && pc.Is(Team.Impostor)) || !pc.RpcCheckAndMurder(target, true)) return;
ScheduledDeaths.TryAdd(target.PlayerId, (Utils.TimeStamp, pc.PlayerId));
Main.AllPlayerSpeed[pc.PlayerId] = Speed.GetFloat();
pc.MarkDirtySettings(); // failsafe check
}
public void SetupCustomOption()
@@ -68,12 +70,12 @@ internal class Bloodmoon : IGhostRole
if (now == instance.LastUpdate) return;
instance.LastUpdate = now;
foreach (KeyValuePair<byte, long> death in ScheduledDeaths)
foreach (KeyValuePair<byte, (long Value, byte KillerId)> death in ScheduledDeaths)
{
PlayerControl player = Utils.GetPlayerById(death.Key);
if (!player || !player.IsAlive()) continue;
if (now - death.Value < Duration.GetInt())
if (now - death.Value.Value < Duration.GetInt())
{
Utils.NotifyRoles(SpecifySeer: player, SpecifyTarget: player);
continue;
@@ -86,7 +88,7 @@ internal class Bloodmoon : IGhostRole
for (int index = 0; index < alivePlayers.Count; index++)
{
PlayerControl target = alivePlayers[index];
if (target.Is(Team.Impostor)) continue;
if (target.Is(Team.Impostor) && pc.Is(Team.Impostor)) continue;
if (!FastVector2.DistanceWithinRange(target.Pos(), pc.Pos(), 4f)) continue;
target.Notify(string.Format(Translator.GetString("BloodmoonNearYou"), CustomRoles.Bloodmoon.ToColoredString()), sendOption: SendOption.None);
@@ -97,23 +99,24 @@ internal class Bloodmoon : IGhostRole
{
if (DieOnMeetingCall.GetBool())
{
foreach (byte id in ScheduledDeaths.Keys)
foreach (var kvp in ScheduledDeaths)
{
PlayerControl pc = Utils.GetPlayerById(id);
PlayerControl pc = Utils.GetPlayerById(kvp.Key);
if (!pc || !pc.IsAlive()) continue;
pc.Suicide(PlayerState.DeathReason.LossOfBlood);
pc.Suicide(PlayerState.DeathReason.LossOfBlood, Utils.GetPlayerById(kvp.Value.KillerId));
}
}
ScheduledDeaths.Clear();
}
}
public static string GetSuffix(PlayerControl seer)
{
if (!ScheduledDeaths.TryGetValue(seer.PlayerId, out long ts)) return string.Empty;
if (!ScheduledDeaths.TryGetValue(seer.PlayerId, out var deathInfo)) return string.Empty;
long timeLeft = Duration.GetInt() - (Utils.TimeStamp - deathInfo.TimeStamp) + 1;
long timeLeft = Duration.GetInt() - (Utils.TimeStamp - ts) + 1;
(string TextColor, string TimeColor) colors = GetColors();
return string.Format(Translator.GetString("Bloodmoon.Suffix"), timeLeft, colors.TextColor, colors.TimeColor);
@@ -124,4 +127,4 @@ internal class Bloodmoon : IGhostRole
_ => ("#ff0000", "#ffff00")
};
}
}
}
@@ -135,6 +135,9 @@ public class Arrogance : RoleBase
Utils.SendRPC(CustomRPC.SyncRoleData, ArroganceID, NowCooldown);
killer?.ResetKillCooldown();
killer?.SyncSettings();
if (UsedRole == CustomRoles.Juggernaut && target)
Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Destroyed;
if (killer?.AmOwner == true && Mathf.Approximately(NowCooldown, MinKCD))
Achievements.Type.Bloodthirsty.Complete();
@@ -263,7 +263,7 @@ internal class AntiAdminer : RoleBase
}
case 5:
{
if (!Options.DisableFungleCamera.GetBool() && FastVector2.DistanceWithinRange(playerPos, DisableDevice.DevicePos["FungleCamera"], usableDistance))
if (/*!Options.DisableFungleCamera.GetBool() && */FastVector2.DistanceWithinRange(playerPos, DisableDevice.DevicePos["FungleCamera"], usableDistance))
{
camera = true;
AddDeviceUse(playerId, Device.Camera);
@@ -416,4 +416,4 @@ internal class AntiAdminer : RoleBase
else
hud.AbilityButton?.OverrideText(Translator.GetString("VeteranVentButtonText"));
}
}
}
+10 -1
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using AmongUs.GameOptions;
using EHR.Modules;
using static EHR.Options;
namespace EHR.Roles;
@@ -106,6 +107,14 @@ internal class Cleaner : RoleBase
cleaner.Notify(Translator.GetString("CleanerCleanBody"));
cleaner.SetKillCooldown(KCDAfterClean);
if (IsMedusa)
{
PlayerControl tpc = target.Object;
PlayerState tps = Main.PlayerStates[tpc.PlayerId];
tps.deathReason = PlayerState.DeathReason.Stoned;
RPC.SendDeathReason(tpc.PlayerId, tps.deathReason, tps.IsDead);
}
Logger.Info($"{cleaner.GetRealName()} cleans up the corpse of {target.Object.GetRealName()}", "Cleaner/Medusa");
return false;
@@ -129,4 +138,4 @@ internal class Cleaner : RoleBase
if (!seer.Is(CustomRoles.Cleaner) || seer.PlayerId != target.PlayerId || hud || meeting || !WaitForKCDUp || Main.KillTimers[seer.PlayerId] <= 0f) return string.Empty;
return seer.IsHost() ? string.Format(Translator.GetString("CleanerKCDNotUpTimer"), (int)Math.Ceiling(Main.KillTimers[seer.PlayerId])) : Translator.GetString("CleanerKCDNotUp");
}
}
}
+2 -1
View File
@@ -166,6 +166,7 @@ public class Glitch : RoleBase
LastKill = Utils.TimeStamp;
KCDTimer = KillCooldown.GetInt();
SendRPCSyncTimers();
Main.PlayerStates[target.PlayerId].deathReason = PlayerState.DeathReason.Bugged;
return true;
}
@@ -235,4 +236,4 @@ public class Glitch : RoleBase
HackCDTimer = 10;
SendRPCSyncTimers();
}
}
}
+14 -1
View File
@@ -223,4 +223,17 @@ public class Pestilence : RoleBase
LateTask.New(() => Utils.SendMessage(GetString("TransformationAnnouncementMessage"), title: $"{CustomRoles.PlagueBearer.ToColoredString()} => {CustomRoles.Pestilence.ToColoredString()}", importance: MessageImportance.High), 12f, "Pb to Pesti transform message");
Announced = true;
}
}
public override void OnFixedUpdate(PlayerControl pc)
{
foreach (PlayerControl player in Main.CachedAllPlayerControls())
{
PlayerState state = Main.PlayerStates[player.PlayerId];
if (!player.IsAlive() && player.GetRealKiller() == pc && state.deathReason != PlayerState.DeathReason.Diseased)
{
state.deathReason = PlayerState.DeathReason.Diseased;
RPC.SendDeathReason(player.PlayerId, state.deathReason, state.IsDead);
}
}
}
}