Files
EndlessHostRoles/Roles/Standard/Neutral/Killing/Jackal.cs
T
2026-09-29 20:25:12 +02:00

286 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
using EHR.Modules;
using Hazel;
using static EHR.Options;
using static EHR.Translator;
namespace EHR.Roles;
public class Jackal : RoleBase
{
private const int Id = 12100;
public static List<Jackal> Instances = [];
private static OptionItem KillCooldown;
private static OptionItem CanVent;
public static OptionItem CanSabotage;
public static OptionItem HasImpostorVision;
public static OptionItem ResetKillCooldownWhenSbGetKilled;
private static OptionItem ResetKillCooldownOn;
private static OptionItem CanRecruitImpostors;
private static OptionItem CanRecruitMadmates;
public static OptionItem SidekickCountMode;
public static OptionItem SKCanKill;
public static OptionItem KillCooldownSK;
public static OptionItem CanVentSK;
public static OptionItem CanSabotageSK;
private static OptionItem SKPromotesToJackal;
private static OptionItem PromotedSKCanRecruit;
public static bool On;
public byte SidekickId;
private byte JackalId;
public override bool IsEnable => Instances.Count > 0;
public override void SetupCustomOption()
{
SetupRoleOptions(Id, TabGroup.NeutralRoles, CustomRoles.Jackal);
KillCooldown = new FloatOptionItem(Id + 2, "KillCooldown", new(0f, 180f, 0.5f), 22.5f, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jackal])
.SetValueFormat(OptionFormat.Seconds);
CanVent = new BooleanOptionItem(Id + 3, "CanVent", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]);
CanSabotage = new BooleanOptionItem(Id + 4, "CanSabotage", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]);
HasImpostorVision = new BooleanOptionItem(Id + 6, "ImpostorVision", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]);
ResetKillCooldownWhenSbGetKilled = new BooleanOptionItem(Id + 7, "ResetKillCooldownWhenPlayerGetKilled", false, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]);
ResetKillCooldownOn = new FloatOptionItem(Id + 8, "ResetKillCooldownOn", new(0f, 180f, 0.5f), 15f, TabGroup.NeutralRoles)
.SetParent(ResetKillCooldownWhenSbGetKilled)
.SetValueFormat(OptionFormat.Seconds);
OptionItem SKOpts = new BooleanOptionItem(Id + 9, "SidekickSettings", true, TabGroup.NeutralRoles)
.SetParent(CustomRoleSpawnChances[CustomRoles.Jackal]);
CanRecruitImpostors = new BooleanOptionItem(Id + 10, "JackalCanRecruitImpostors", true, TabGroup.NeutralRoles)
.SetParent(SKOpts);
CanRecruitMadmates = new BooleanOptionItem(Id + 11, "JackalCanRecruitMadmates", true, TabGroup.NeutralRoles)
.SetParent(CanRecruitImpostors);
JackalCanKillSidekick = new BooleanOptionItem(Id + 12, "JackalCanKillSidekick", false, TabGroup.NeutralRoles)
.SetParent(SKOpts);
SKCanKill = new BooleanOptionItem(Id + 13, "SKCanKill", true, TabGroup.NeutralRoles)
.SetParent(SKOpts);
KillCooldownSK = new FloatOptionItem(Id + 14, "KillCooldown", new(0f, 180f, 0.5f), 20f, TabGroup.NeutralRoles)
.SetParent(SKCanKill)
.SetValueFormat(OptionFormat.Seconds);
SidekickCanKillJackal = new BooleanOptionItem(Id + 15, "SidekickCanKillJackal", false, TabGroup.NeutralRoles)
.SetParent(SKCanKill);
SidekickCanKillSidekick = new BooleanOptionItem(Id + 16, "SidekickCanKillSidekick", false, TabGroup.NeutralRoles)
.SetParent(SKCanKill);
CanVentSK = new BooleanOptionItem(Id + 17, "CanVent", true, TabGroup.NeutralRoles)
.SetParent(SKOpts);
CanSabotageSK = new BooleanOptionItem(Id + 18, "CanSabotage", true, TabGroup.NeutralRoles)
.SetParent(SKOpts);
SKPromotesToJackal = new BooleanOptionItem(Id + 19, "SKPromotesToJackal", true, TabGroup.NeutralRoles)
.SetParent(SKOpts);
PromotedSKCanRecruit = new BooleanOptionItem(Id + 20, "PromotedSKCanRecruit", true, TabGroup.NeutralRoles)
.SetParent(SKPromotesToJackal);
SidekickCountMode = new StringOptionItem(Id + 21, "SidekickCountMode", Options.SidekickCountMode, 0, TabGroup.NeutralRoles)
.SetParent(SKOpts);
}
public override void Init()
{
Instances = [];
On = false;
}
public override void Add(byte playerId)
{
On = true;
Instances.Add(this);
playerId.SetAbilityUseLimit(1);
SidekickId = byte.MaxValue;
JackalId = playerId;
}
public override void Remove(byte playerId)
{
Instances.Remove(this);
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = KillCooldown.GetFloat();
}
public override bool CanUseImpostorVentButton(PlayerControl pc)
{
return CanVent.GetBool();
}
public override bool CanUseSabotage(PlayerControl pc)
{
return base.CanUseSabotage(pc) || CanSabotage.GetBool() && pc.IsAlive();
}
public override void SetButtonTexts(HudManager hud, byte id)
{
hud.KillButton.OverrideText(id.GetAbilityUseLimit() > 0 ? $"{GetString("GangsterButtonText")}" : $"{GetString("KillButtonText")}");
hud.SabotageButton.ToggleVisible(CanSabotage.GetBool());
}
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
opt.SetVision(HasImpostorVision.GetBool());
}
public static void AfterPlayerDiedTask(PlayerControl target)
{
if (target.Is(CustomRoles.Jackal)) return;
Main.EnumerateAlivePlayerControls()
.Where(x => x.Is(CustomRoles.Jackal))
.Do(x => x.SetKillCooldown(ResetKillCooldownOn.GetFloat()));
}
public override bool OnCheckMurder(PlayerControl killer, PlayerControl target)
{
if (killer.GetAbilityUseLimit() < 1 || !CanBeSidekick(target)) return true;
var sender = CustomRpcSender.Create("Jackal.OnCheckMurder", SendOption.Reliable);
var hasValue = false;
killer.RpcRemoveAbilityUse(notify: false);
target.RpcSetCustomRole(CustomRoles.Sidekick);
target.RpcChangeRoleBasis(CustomRoles.Sidekick);
SidekickId = target.PlayerId;
Main.ResetCamPlayerList.Add(target.PlayerId);
hasValue |= CustomRpcSenderExtensions.Notify(ref sender, killer, CustomRoles.Jackal.ColoredTextByRole(GetString("GangsterSuccessfullyRecruited")), setName: false);
hasValue |= sender.SetKillCooldown(killer, 3f);
hasValue |= sender.NotifyRolesSpecific(killer, target, out sender, out bool cleared);
if (cleared) hasValue = false;
hasValue |= CustomRpcSenderExtensions.Notify(ref sender, target, CustomRoles.Jackal.ColoredTextByRole(GetString("BeRecruitedByJackal")), setName: false);
hasValue |= sender.RpcGuardAndKill(target, killer);
hasValue |= sender.RpcGuardAndKill(target, target);
hasValue |= sender.NotifyRolesSpecific(target, killer, out sender, out cleared);
if (cleared) hasValue = false;
sender.SendMessage(!hasValue);
Logger.Info($" {target.Data?.PlayerName} = {target.GetCustomRole()} + {CustomRoles.Sidekick}", $"Assign {CustomRoles.Sidekick}");
if (killer.AmOwner)
Achievements.Type.YoureMyFriendNow.Complete();
return false;
}
private static bool CanBeSidekick(PlayerControl pc)
{
if (!CanRecruitImpostors.GetBool() && pc.Is(CustomRoleTypes.Impostor)) return false;
if (!CanRecruitMadmates.GetBool() && pc.IsMadmate()) return false;
return pc && !pc.Is(CustomRoles.Sidekick) && !pc.Is(CustomRoles.Curser) && !pc.Is(CustomRoles.Loyal) && !pc.Is(CustomRoles.Bloodlust) && !pc.IsConverted() && pc.GetCustomRole().IsAbleToBeSidekicked();
}
public void PromoteSidekick()
{
try
{
if (!SKPromotesToJackal.GetBool()) return;
PlayerControl sk = SidekickId.GetPlayer();
if (!sk || !sk.Is(CustomRoles.Sidekick)) return;
GameEndChecker.ShouldNotCheck = true;
try
{
sk.RpcSetCustomRole(CustomRoles.Jackal);
if (!PromotedSKCanRecruit.GetBool()) sk.SetAbilityUseLimit(0);
}
finally { GameEndChecker.ShouldNotCheck = false; }
}
catch (Exception e) { Utils.ThrowException(e); }
}
public static void OnAnyoneDead()
{
for (var index = Instances.Count - 1; index >= 0; index--)
{
Jackal instance = Instances[index];
var pc = instance.JackalId.GetPlayer();
if (pc && pc.IsAlive()) return;
instance.PromoteSidekick();
}
}
}
public class Sidekick : RoleBase
{
private static List<byte> PlayerIdList = [];
public override bool IsEnable => PlayerIdList.Count > 0;
public override void SetupCustomOption() { }
public override void Init()
{
PlayerIdList = [];
}
public override void Add(byte playerId)
{
PlayerIdList.Add(playerId);
new[] { CustomRoles.Damocles, CustomRoles.Stressed }.Do(x => Main.PlayerStates[playerId].RemoveSubRole(x));
}
public override bool CanUseKillButton(PlayerControl pc)
{
return Jackal.SKCanKill.GetBool();
}
public override bool CanUseImpostorVentButton(PlayerControl pc)
{
return Jackal.CanVentSK.GetBool();
}
public override void SetKillCooldown(byte id)
{
Main.AllPlayerKillCooldown[id] = Jackal.KillCooldownSK.GetFloat();
}
public override void ApplyGameOptions(IGameOptions opt, byte id)
{
opt.SetVision(Jackal.HasImpostorVision.GetBool());
}
public override bool CanUseSabotage(PlayerControl pc)
{
return base.CanUseSabotage(pc) || Jackal.CanSabotageSK.GetBool() && pc.IsAlive();
}
public override void SetButtonTexts(HudManager __instance, byte id)
{
__instance.SabotageButton.ToggleVisible(Jackal.CanSabotageSK.GetBool());
__instance.KillButton?.OverrideText(GetString("KillButtonText"));
__instance.ImpostorVentButton?.OverrideText(GetString("ReportButtonText"));
__instance.SabotageButton?.OverrideText(GetString("SabotageButtonText"));
}
}