Add Quarry

This commit is contained in:
Gurge44 committed 2026-01-22 11:10:06 +01:00
1 parent 7208792921
commit 78ec88b68d
9 files changed
+279

No files matched your search

+1
View File
@@ -359,6 +359,7 @@ public enum CustomRoles
Pulse, Pulse,
Pursuer, Pursuer,
Pyromaniac, Pyromaniac,
Quarry,
QuizMaster, QuizMaster,
Reckless, Reckless,
Revolutionist, Revolutionist,
+2
View File
@@ -561,6 +561,7 @@ public class Main : BasePlugin
{ CustomRoles.Thanos, "#F9D401" }, { CustomRoles.Thanos, "#F9D401" },
{ CustomRoles.Berserker, "#50538F" }, { CustomRoles.Berserker, "#50538F" },
{ CustomRoles.SerialKiller, "#233fcc" }, { CustomRoles.SerialKiller, "#233fcc" },
{ CustomRoles.Quarry, "#c1fb2b" },
{ CustomRoles.Accumulator, "#2bfbae" }, { CustomRoles.Accumulator, "#2bfbae" },
{ CustomRoles.Spider, "#C9E44C" }, { CustomRoles.Spider, "#C9E44C" },
{ CustomRoles.SoulCollector, "#6021A0" }, { CustomRoles.SoulCollector, "#6021A0" },
@@ -1067,6 +1068,7 @@ public enum CustomWinner
Necromancer = CustomRoles.Necromancer, Necromancer = CustomRoles.Necromancer,
Wraith = CustomRoles.Wraith, Wraith = CustomRoles.Wraith,
SerialKiller = CustomRoles.SerialKiller, SerialKiller = CustomRoles.SerialKiller,
Quarry = CustomRoles.Quarry,
Accumulator = CustomRoles.Accumulator, Accumulator = CustomRoles.Accumulator,
Spider = CustomRoles.Spider, Spider = CustomRoles.Spider,
SoulCollector = CustomRoles.SoulCollector, SoulCollector = CustomRoles.SoulCollector,
+3
View File
@@ -517,6 +517,7 @@ internal static class CustomRolesHelper
CustomRoles.Duality => RoleTypes.Impostor, CustomRoles.Duality => RoleTypes.Impostor,
CustomRoles.Berserker => RoleTypes.Impostor, CustomRoles.Berserker => RoleTypes.Impostor,
CustomRoles.SerialKiller => RoleTypes.Impostor, CustomRoles.SerialKiller => RoleTypes.Impostor,
CustomRoles.Quarry => RoleTypes.Shapeshifter,
CustomRoles.SoulCollector => RoleTypes.Impostor, CustomRoles.SoulCollector => RoleTypes.Impostor,
CustomRoles.Spider => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter, CustomRoles.Spider => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter,
CustomRoles.Explosivist => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter, CustomRoles.Explosivist => UsePets ? RoleTypes.Impostor : RoleTypes.Shapeshifter,
@@ -655,6 +656,7 @@ internal static class CustomRolesHelper
CustomRoles.Parasite or CustomRoles.Parasite or
CustomRoles.Berserker or CustomRoles.Berserker or
CustomRoles.SerialKiller or CustomRoles.SerialKiller or
CustomRoles.Quarry or
CustomRoles.Accumulator or CustomRoles.Accumulator or
CustomRoles.Spider or CustomRoles.Spider or
CustomRoles.SoulCollector or CustomRoles.SoulCollector or
@@ -1966,6 +1968,7 @@ public enum CountTypes
HexMaster, HexMaster,
Wraith, Wraith,
SerialKiller, SerialKiller,
Quarry,
Accumulator, Accumulator,
Spider, Spider,
SoulCollector, SoulCollector,
+1
View File
@@ -209,6 +209,7 @@ public static class NameColorManager
CustomRoles.Crewpostor when seer.Is(CustomRoleTypes.Impostor) && Options.AlliesKnowCrewpostor.GetBool() => Main.RoleColors[CustomRoles.Madmate], CustomRoles.Crewpostor when seer.Is(CustomRoleTypes.Impostor) && Options.AlliesKnowCrewpostor.GetBool() => Main.RoleColors[CustomRoles.Madmate],
CustomRoles.Hypocrite when seer.Is(CustomRoleTypes.Impostor) && Hypocrite.AlliesKnowHypocrite.GetBool() => Main.RoleColors[CustomRoles.Madmate], CustomRoles.Hypocrite when seer.Is(CustomRoleTypes.Impostor) && Hypocrite.AlliesKnowHypocrite.GetBool() => Main.RoleColors[CustomRoles.Madmate],
CustomRoles.President when ((President)targetRoleClass).IsRevealed => Main.RoleColors[CustomRoles.President], CustomRoles.President when ((President)targetRoleClass).IsRevealed => Main.RoleColors[CustomRoles.President],
CustomRoles.Quarry when ((Quarry)targetRoleClass).TargetId == seer.PlayerId => "000000",
_ => color _ => color
}; };
+1
View File
@@ -825,6 +825,7 @@ public static class Utils
case CustomRoles.Eclipse: case CustomRoles.Eclipse:
case CustomRoles.Pyromaniac: case CustomRoles.Pyromaniac:
case CustomRoles.SerialKiller: case CustomRoles.SerialKiller:
case CustomRoles.Quarry:
case CustomRoles.Spider: case CustomRoles.Spider:
case CustomRoles.SoulCollector: case CustomRoles.SoulCollector:
case CustomRoles.Berserker: case CustomRoles.Berserker:
+7
View File
@@ -264,6 +264,7 @@ internal static class CheckMurderPatch
if (ToiletMaster.OnAnyoneCheckMurderStart(killer, target)) return false; if (ToiletMaster.OnAnyoneCheckMurderStart(killer, target)) return false;
if (Dad.OnAnyoneCheckMurderStart(target)) return false; if (Dad.OnAnyoneCheckMurderStart(target)) return false;
if (Quarry.OnAnyoneCheckMurderStart(killer, target)) return false;
Simon.RemoveTarget(killer, Simon.Instruction.Kill); Simon.RemoveTarget(killer, Simon.Instruction.Kill);
@@ -1069,6 +1070,12 @@ internal static class ReportDeadBodyPatch
return false; return false;
} }
if (!Quarry.OnAnyoneCheckReportDeadBody(__instance))
{
Notify("QuarryTarget");
return false;
}
if (!Librarian.OnAnyoneReport(__instance)) return false; // Player dies, no notify needed if (!Librarian.OnAnyoneReport(__instance)) return false; // Player dies, no notify needed
if (!Trapster.OnAnyoneCheckReportDeadBody(__instance, target)) return false; // Player dies, no notify needed if (!Trapster.OnAnyoneCheckReportDeadBody(__instance, target)) return false; // Player dies, no notify needed
+9
View File
@@ -9250,6 +9250,15 @@
"Telekinetic.Mode.Shield": "Shield", "Telekinetic.Mode.Shield": "Shield",
"Telekinetic.Mode.Speed": "Speed", "Telekinetic.Mode.Speed": "Speed",
"Telekinetic.Mode.Doors": "Doors", "Telekinetic.Mode.Doors": "Doors",
"Quarry": "Quarry",
"QuarryInfo": "Hide From Players",
"QuarryInfoLong": "(Neutrals):\\nAs the Quarry, you may shapeshift to select your target and swap places with them, they then have to kill you in a specified time or they die. If they find and kill you, they live. You can also kill normally using the kill button.\\nKill everyone to win!",
"Quarry.TargetHasArrowToQuarry": "Target has an arrow to the <#c1fb2b>Quarry</color>",
"Quarry.CanVentDuringSeekTime": "Can Vent During Seek Time",
"Quarry.TargetDiesOnMeetingCall": "Target Dies on Meeting Call",
"Quarry.SeekTime": "Seek Time",
"Quarry.TargetSeekBeginNotify": "<#ff0000>You were swapped with the {0}! They have a black name!",
"Quarry.TimeLeftSuffix": "<#ffff00>You have <#ffffff>{0}s</color> to kill the {1} <#ffa500>or you die!",
"Nonplus": "Nonplus", "Nonplus": "Nonplus",
"NonplusInfo": "Blind Others", "NonplusInfo": "Blind Others",
"NonplusInfoLong": "(Neutrals):\\nAs the Nonplus, you can blind others by venting or petting. Blinded players can't see anything for a set amount of time.\\nKill everyone to win.", "NonplusInfoLong": "(Neutrals):\\nAs the Nonplus, you can blind others by venting or petting. Blinded players can't see anything for a set amount of time.\\nKill everyone to win.",
@@ -96,6 +96,14 @@ public class Accumulator : RoleBase
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc); Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
} }
public override void AfterMeetingTasks()
{
if (!Killing) return;
PlayerControl pc = AccumulatorId.GetPlayer();
if (pc == null || !pc.IsAlive()) return;
pc.RpcSetRoleDesync(RoleTypes.Impostor, pc.OwnerId, setRoleMap: true);
}
public void ReceiveRPC(MessageReader reader) public void ReceiveRPC(MessageReader reader)
{ {
KCD = reader.ReadSingle(); KCD = reader.ReadSingle();
+247
View File
@@ -0,0 +1,247 @@
using System.Collections.Generic;
using System.Diagnostics;
using AmongUs.GameOptions;
using EHR.Modules;
using EHR.Modules.Extensions;
using Hazel;
namespace EHR.Roles;
public class Quarry : RoleBase
{
public static bool On;
private static List<Quarry> Instances = [];
private static OptionItem TargetHasArrowToQuarry;
private static OptionItem CanVentDuringSeekTime;
private static OptionItem TargetDiesOnMeetingCall;
private static OptionItem SeekTime;
private static OptionItem ShapeshiftCooldown;
private static OptionItem KillCooldown;
private static OptionItem CanVent;
private static OptionItem ImpostorVision;
private static OptionItem AbilityUseLimit;
private static OptionItem AbilityUseGainWithEachKill;
private static OptionItem CooldownsResetEachOther;
public override bool IsEnable => On;
private byte QuarryId;
public byte TargetId;
private Stopwatch SeekTimer;
private long LastUpdate;
public override void SetupCustomOption()
{
StartSetup(657800)
.AutoSetupOption(ref TargetHasArrowToQuarry, true)
.AutoSetupOption(ref CanVentDuringSeekTime, false)
.AutoSetupOption(ref TargetDiesOnMeetingCall, false)
.AutoSetupOption(ref SeekTime, 15, new IntegerValueRule(0, 120, 1), OptionFormat.Seconds)
.AutoSetupOption(ref ShapeshiftCooldown, 15f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref KillCooldown, 22.5f, new FloatValueRule(0f, 180f, 0.5f), OptionFormat.Seconds)
.AutoSetupOption(ref CanVent, true)
.AutoSetupOption(ref ImpostorVision, true)
.AutoSetupOption(ref AbilityUseLimit, 2f, new FloatValueRule(0, 20, 0.05f), OptionFormat.Times)
.AutoSetupOption(ref AbilityUseGainWithEachKill, 1f, new FloatValueRule(0f, 5f, 0.1f), OptionFormat.Times)
.AutoSetupOption(ref CooldownsResetEachOther, true);
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
QuarryId = playerId;
TargetId = byte.MaxValue;
SeekTimer = new();
LastUpdate = 0;
Instances.Add(this);
playerId.SetAbilityUseLimit(AbilityUseLimit.GetFloat());
}
public override void Remove(byte playerId)
{
Instances.Remove(this);
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
}
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
opt.SetVision(ImpostorVision.GetBool());
AURoleOptions.ShapeshifterCooldown = ShapeshiftCooldown.GetFloat();
AURoleOptions.ShapeshifterDuration = 1f;
}
public override bool CanUseImpostorVentButton(PlayerControl pc)
{
return CanVent.GetBool() && (TargetId == byte.MaxValue || CanVentDuringSeekTime.GetBool());
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
return target.PlayerId != TargetId;
}
public override void OnMurder(PlayerControl killer, PlayerControl target)
{
if (CooldownsResetEachOther.GetBool())
killer.RpcResetAbilityCooldown();
}
public override bool OnShapeshift(PlayerControl shapeshifter, PlayerControl target, bool shapeshifting)
{
if (!shapeshifting) return true;
if (shapeshifter.GetAbilityUseLimit() < 1) return false;
shapeshifter.RpcRemoveAbilityUse();
Vector2 pos = shapeshifter.Pos();
shapeshifter.TP(target);
target.TP(pos);
TargetId = target.PlayerId;
SeekTimer = Stopwatch.StartNew();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 1, TargetId);
Main.Instance.StartCoroutine(ContinuouslyResetAbilityCooldown());
if (TargetHasArrowToQuarry.GetBool()) TargetArrow.Add(target.PlayerId, shapeshifter.PlayerId);
var sender = CustomRpcSender.Create("QuarryTarget", SendOption.Reliable);
var hasValue = false;
hasValue |= sender.Notify(target, string.Format(Translator.GetString("Quarry.TargetSeekBeginNotify"), CustomRoles.Quarry.ToColoredString()));
hasValue |= sender.RpcSetRole(target, RoleTypes.Impostor, target.OwnerId);
hasValue |= sender.RpcSetRole(shapeshifter, RoleTypes.Crewmate, target.OwnerId);
hasValue |= sender.NotifyRolesSpecific(target, shapeshifter, out sender, out bool cleared);
if (cleared) hasValue = false;
sender.SendMessage(dispose: !hasValue);
if (CooldownsResetEachOther.GetBool())
shapeshifter.SetKillCooldown();
return false;
System.Collections.IEnumerator ContinuouslyResetAbilityCooldown()
{
while (TargetId != byte.MaxValue)
{
int interval = ShapeshiftCooldown.GetInt() - 2;
Stopwatch stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed.Seconds < interval) yield return null;
if (TargetId == byte.MaxValue) yield break;
shapeshifter.RpcResetAbilityCooldown();
}
}
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (!Main.IntroDestroyed || !GameStates.IsInTask || ExileController.Instance || AntiBlackout.SkipTasks || !pc.IsAlive() || TargetId == byte.MaxValue) return;
long now = Utils.TimeStamp;
if (now == LastUpdate) return;
LastUpdate = now;
PlayerControl target = TargetId.GetPlayer();
if (target == null || !target.IsAlive())
{
TargetId = byte.MaxValue;
SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
return;
}
if (SeekTimer.GetRemainingTime(SeekTime.GetInt()) <= 0)
{
target.Suicide();
TargetId = byte.MaxValue;
SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
}
else
{
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.NotifyRoles(SpecifySeer: target, SpecifyTarget: target);
}
}
public static bool OnAnyoneCheckMurderStart(PlayerControl killer, PlayerControl target)
{
foreach (Quarry instance in Instances)
{
if (instance.TargetId == killer.PlayerId && instance.QuarryId == target.PlayerId)
{
killer.Kill(target);
killer.RpcSetRoleDesync(killer.GetRoleTypes(), killer.OwnerId);
instance.TargetId = byte.MaxValue;
instance.SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, instance.QuarryId, 2);
return true;
}
}
return false;
}
public static bool OnAnyoneCheckReportDeadBody(PlayerControl reporter)
{
return !Instances.Exists(x => x.TargetId == reporter.PlayerId);
}
public override bool CanUseVent(PlayerControl pc, int ventId)
{
return pc.PlayerId != TargetId;
}
public override void OnReportDeadBody()
{
if (TargetId == byte.MaxValue) return;
TargetId = byte.MaxValue;
SeekTimer.Reset();
Utils.SendRPC(CustomRPC.SyncRoleData, QuarryId, 2);
PlayerControl target = TargetId.GetPlayer();
if (target == null || !target.IsAlive()) return;
if (TargetDiesOnMeetingCall.GetBool())
target.Suicide();
else
{
PlayerControl pc = QuarryId.GetPlayer();
if (pc == null || !pc.IsAlive()) return;
pc.RpcIncreaseAbilityUseLimitBy(1);
}
}
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
TargetId = reader.ReadByte();
SeekTimer = Stopwatch.StartNew();
break;
case 2:
TargetId = byte.MaxValue;
SeekTimer.Reset();
break;
}
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if ((seer.PlayerId != QuarryId && seer.PlayerId != TargetId) || seer.PlayerId != target.PlayerId || meeting || hud || TargetId == byte.MaxValue) return string.Empty;
string timer = string.Format(Translator.GetString("Quarry.TimeLeftSuffix"), SeekTimer.GetRemainingTime(SeekTime.GetInt()) - 1, CustomRoles.Quarry.ToColoredString());
return seer.PlayerId == TargetId ? $"{TargetArrow.GetAllArrows(TargetId)}\n{timer}" : timer;
}
}