Files
EndlessHostRoles/Roles/Standard/Crewmate/Chaos/Dad.cs
T
2026-05-19 10:25:43 +02:00

491 lines
17 KiB
C#

using AmongUs.GameOptions;
using EHR.Gamemodes;
using EHR.Modules;
using Hazel;
using System;
using System.Collections.Generic;
using System.Linq;
using static EHR.Roles.Adventurer;
namespace EHR.Roles;
public class Dad : RoleBase
{
public enum Ability
{
GoForMilk,
SuperVision,
Sniffing,
Sleep,
Rage,
GiveDrink,
BecomeGodOfAlcohol
}
public static bool On;
private static List<Dad> Instances = [];
private static List<Vector2> AllDeadBodies = [];
private static OptionItem AlcoholDecreaseOnKilled;
private static OptionItem AlcoholDecreaseOnVotedOut;
private static OptionItem NormalAlcoholDecreaseFrequency;
private static OptionItem NormalAlcoholDecreaseValue;
private static OptionItem AlcoholIncreaseOnBeerPurchase;
private static OptionItem ShowWarningWhenAlcoholIsBelow;
private static OptionItem StartingAlcohol;
private static OptionItem StartingMoney;
private static OptionItem MoneyGainOnTaskComplete;
private static OptionItem AlcoholCost;
private static OptionItem SuperVisionDuration;
private static OptionItem GivingDrinkRange;
private static OptionItem DrunkRoleIncorrectChance;
private static readonly Dictionary<Ability, OptionItem> AbilityAlcoholDecreaseOptions = [];
private static readonly Dictionary<Ability, OptionItem> AbilityAlcoholRequirement = [];
private int Alcohol;
private string Arrows;
private int Count;
private byte DadId;
public bool DoneTasks;
public List<byte> DrunkPlayers;
private long LastUpdate;
private long MilkTimer;
private Ability SelectedAbility;
private Vent Shop;
private float StartingSpeed;
private long SuperVisionTS;
public HashSet<Ability> UsingAbilities;
private readonly StringBuilder Suffix = new();
private static readonly Ability[] AllAbility = Enum.GetValues<Ability>();
public override bool IsEnable => On;
public override void SetupCustomOption()
{
var id = 648450;
const TabGroup tab = TabGroup.CrewmateRoles;
const CustomRoles role = CustomRoles.Dad;
Options.SetupRoleOptions(id++, tab, role);
StringOptionItem parent = Options.CustomRoleSpawnChances[role];
AlcoholDecreaseOnKilled = new IntegerOptionItem(++id, "Dad.AlcoholDecreaseOnKilled", new(0, 100, 1), 10, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
AlcoholDecreaseOnVotedOut = new IntegerOptionItem(++id, "Dad.AlcoholDecreaseOnVotedOut", new(0, 100, 1), 5, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
NormalAlcoholDecreaseFrequency = new IntegerOptionItem(++id, "Dad.NormalAlcoholDecreaseFrequency", new(1, 60, 1), 10, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Seconds);
NormalAlcoholDecreaseValue = new IntegerOptionItem(++id, "Dad.NormalAlcoholDecreaseValue", new(0, 100, 1), 1, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
AlcoholIncreaseOnBeerPurchase = new IntegerOptionItem(++id, "Dad.AlcoholIncreaseOnBeerPurchase", new(0, 100, 1), 10, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
ShowWarningWhenAlcoholIsBelow = new IntegerOptionItem(++id, "Dad.ShowWarningWhenAlcoholIsBelow", new(0, 100, 1), 20, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
StartingAlcohol = new IntegerOptionItem(++id, "Dad.StartingAlcohol", new(0, 100, 1), 25, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
StartingMoney = new IntegerOptionItem(++id, "Dad.StartingMoney", new(0, 100, 1), 0, tab)
.SetParent(parent);
MoneyGainOnTaskComplete = new IntegerOptionItem(++id, "Dad.MoneyGainOnTaskComplete", new(0, 100, 1), 15, tab)
.SetParent(parent);
AlcoholCost = new IntegerOptionItem(++id, "Dad.AlcoholCost", new(0, 100, 1), 10, tab)
.SetParent(parent);
SuperVisionDuration = new IntegerOptionItem(++id, "Dad.SuperVisionDuration", new(1, 90, 1), 40, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Seconds);
GivingDrinkRange = new FloatOptionItem(++id, "Dad.GivingDrinkRange", new(0.5f, 10f, 0.5f), 6f, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Multiplier);
DrunkRoleIncorrectChance = new IntegerOptionItem(++id, "Dad.DrunkRoleIncorrectChance", new(0, 100, 1), 10, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
foreach (Ability ability in AllAbility)
{
AbilityAlcoholDecreaseOptions[ability] = new IntegerOptionItem(++id, $"Dad.{ability}.AlcoholDecrease", new(0, 100, 1), 10, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
}
foreach (Ability ability in AllAbility)
{
AbilityAlcoholRequirement[ability] = new IntegerOptionItem(++id, $"Dad.{ability}.AlcoholRequirement", new(0, 100, 1), ability == Ability.BecomeGodOfAlcohol ? 40 : (int)ability * 5, tab)
.SetParent(parent)
.SetValueFormat(OptionFormat.Percent);
}
}
public override void Init()
{
On = false;
Instances = [];
}
public override void Add(byte playerId)
{
On = true;
Instances.Add(this);
Main.AllPlayerSpeed[playerId] *= -1;
DadId = playerId;
Alcohol = StartingAlcohol.GetInt();
LastUpdate = Utils.TimeStamp;
Count = 0;
Shop = ShipStatus.Instance.AllVents.RandomElement();
DoneTasks = false;
SelectedAbility = default(Ability);
MilkTimer = 315569520;
UsingAbilities = [];
SuperVisionTS = 0;
Arrows = string.Empty;
StartingSpeed = Main.AllPlayerSpeed[playerId];
DrunkPlayers = [];
playerId.SetAbilityUseLimit(StartingMoney.GetFloat());
Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 1, Shop.Id);
}
public override void Remove(byte playerId)
{
Instances.Remove(this);
if (!AmongUsClient.Instance.AmHost) return;
Main.AllPlayerSpeed[playerId] = Main.RealOptionsData.GetFloat(FloatOptionNames.PlayerSpeedMod);
PlayerGameOptionsSender.SetDirty(playerId);
}
public override void ApplyGameOptions(IGameOptions opt, byte playerId)
{
if (UsingAbilities.Contains(Ability.Sleep))
{
opt.SetVision(false);
opt.BlackOut(true);
return;
}
if (UsingAbilities.Contains(Ability.SuperVision))
{
opt.SetVision(true);
opt.SetFloat(FloatOptionNames.CrewLightMod, 1.5f);
opt.SetFloat(FloatOptionNames.ImpostorLightMod, 1.5f);
}
AURoleOptions.EngineerCooldown = 0.1f;
AURoleOptions.EngineerInVentMaxTime = 1f;
}
public override void OnReportDeadBody()
{
AllDeadBodies = [];
Arrows = string.Empty;
Main.AllPlayerSpeed[DadId] = StartingSpeed;
if (DrunkPlayers.Count > 0)
{
int chance = DrunkRoleIncorrectChance.GetInt();
List<CustomRoles> allRoles = Main.CustomRoleValues.Where(x => x.IsEnable() && !x.IsAdditionRole() && !CustomHnS.AllHnSRoles.Contains(x) && !x.IsForOtherGameMode()).ToList();
foreach (byte id in DrunkPlayers)
{
PlayerControl pc = Utils.GetPlayerById(id);
if (pc == null) continue;
CustomRoles selfRole = Main.PlayerStates[id].MainRole;
CustomRoles rndRole = allRoles.RandomElement();
CustomRoles role = IRandom.Instance.Next(100) < chance ? rndRole : selfRole;
string msg = string.Format(Translator.GetString("Dad.DrunkRoleNotify"), Translator.GetString(role.ToString()).ToLower());
int delay = 10 + IRandom.Instance.Next(12);
LateTask.New(() => pc.RpcSendChat(msg), delay, $"Dad - DrunkRoleNotify - {pc.GetNameWithRole()}");
}
DrunkPlayers = [];
}
Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 2, Arrows);
}
public override void AfterMeetingTasks()
{
if (UsingAbilities.Contains(Ability.GoForMilk)) LateTask.New(() => Utils.GetPlayerById(DadId)?.TP(Pelican.GetBlackRoomPS()), 1f, log: false);
}
public override void OnPet(PlayerControl pc)
{
SelectedAbility = (Ability)(((int)SelectedAbility + 1) % AllAbility.Length);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
}
public override void OnEnterVent(PlayerControl pc, Vent vent)
{
if (vent.Id == Shop.Id || UsingAbilities.Contains(Ability.Sleep)) return;
if (AbilityAlcoholRequirement[SelectedAbility].GetInt() > Alcohol)
{
pc.Notify(Translator.GetString("Dad.AbilityNotAvailable"));
return;
}
switch (SelectedAbility)
{
case Ability.GoForMilk:
LateTask.New(() => pc.TP(Pelican.GetBlackRoomPS()), 2f, log: false);
Main.EnumerateAlivePlayerControls().NotifyPlayers(Translator.GetString("Dad.GoForMilkNotify"), 10f);
UsingAbilities.Add(SelectedAbility);
break;
case Ability.SuperVision:
SuperVisionTS = Utils.TimeStamp;
UsingAbilities.Add(SelectedAbility);
pc.MarkDirtySettings();
break;
case Ability.Sniffing:
AllDeadBodies.Do(x => LocateArrow.Add(DadId, x));
Arrows = LocateArrow.GetArrows(pc);
LocateArrow.RemoveAllTarget(DadId);
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 2, Arrows);
break;
case Ability.Sleep:
Main.AllPlayerSpeed[DadId] = Main.MinSpeed;
UsingAbilities.Add(SelectedAbility);
pc.MarkDirtySettings();
break;
case Ability.Rage:
UsingAbilities.Add(SelectedAbility);
break;
case Ability.GiveDrink:
Vector2 pos = pc.Pos();
DrunkPlayers = Main.EnumerateAlivePlayerControls().Without(pc).Where(x => FastVector2.DistanceWithinRange(x.Pos(), pos, GivingDrinkRange.GetFloat())).Select(x => x.PlayerId).ToList();
Utils.NotifyRoles(SpecifySeer: pc);
break;
case Ability.BecomeGodOfAlcohol:
Alcohol = 1;
UsingAbilities.Add(SelectedAbility);
break;
}
Alcohol -= AbilityAlcoholDecreaseOptions[SelectedAbility].GetInt();
NotifyIfNecessary(pc);
}
public override void OnExitVent(PlayerControl pc, Vent vent)
{
if (vent.Id != Shop.Id) return;
int cost = AlcoholCost.GetInt();
int get = AlcoholIncreaseOnBeerPurchase.GetInt();
for (float money = pc.GetAbilityUseLimit(); money >= cost; money -= cost)
{
pc.SetAbilityUseLimit(money - cost);
Alcohol += get;
}
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 3, Alcohol);
}
public override void OnTaskComplete(PlayerControl pc, int completedTaskCount, int totalTaskCount)
{
pc.RpcIncreaseAbilityUseLimitBy(MoneyGainOnTaskComplete.GetInt());
if (completedTaskCount + 1 >= totalTaskCount)
{
pc.RpcResetTasks();
pc.SyncSettings();
DoneTasks = true;
GameData.Instance.RecomputeTaskCounts();
}
}
public override void OnFixedUpdate(PlayerControl pc)
{
if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return;
if (Count++ < 15) return;
Count = 0;
if (UsingAbilities.Contains(Ability.Rage) && FastVector2.TryGetClosestPlayerInRangeTo(pc, 1.3f, out PlayerControl target) && pc.RpcCheckAndMurder(target))
UsingAbilities.Remove(Ability.Rage);
bool notify = FastVector2.DistanceWithinRange(pc.Pos(), Shop.transform.position, 2f);
long now = Utils.TimeStamp;
long elapsed = now - LastUpdate;
if (elapsed < NormalAlcoholDecreaseFrequency.GetInt())
{
if (notify) Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
return;
}
if (UsingAbilities.Contains(Ability.GoForMilk))
{
MilkTimer -= elapsed;
if (MilkTimer <= 0)
{
pc.TPToRandomVent();
CustomWinnerHolder.ShiftWinnerAndSetWinner(CustomWinner.Crewmate);
CustomWinnerHolder.WinnerIds.UnionWith(Main.EnumeratePlayerControls().Where(x => x.Is(Team.Crewmate)).Select(x => x.PlayerId));
}
}
LastUpdate = now;
if (UsingAbilities.Contains(Ability.SuperVision) && SuperVisionTS + SuperVisionDuration.GetInt() <= now)
{
UsingAbilities.Remove(Ability.SuperVision);
SuperVisionTS = 0;
pc.MarkDirtySettings();
}
if (UsingAbilities.Contains(Ability.Sleep) || UsingAbilities.Contains(Ability.GoForMilk)) return;
if (UsingAbilities.Contains(Ability.BecomeGodOfAlcohol))
Alcohol += NormalAlcoholDecreaseValue.GetInt();
else
{
Alcohol -= NormalAlcoholDecreaseValue.GetInt();
if (Alcohol <= 0)
{
pc.Suicide();
return;
}
}
NotifyIfNecessary(pc, notify);
}
public override bool OnCheckMurderAsTarget(PlayerControl killer, PlayerControl target)
{
LateTask.New(() =>
{
Alcohol -= AlcoholDecreaseOnKilled.GetInt();
NotifyIfNecessary(target);
}, 5f, log: false);
return false;
}
public static bool OnVotedOut(byte id)
{
Dad dad = Instances.FirstOrDefault(d => d.DadId == id);
if (dad == null) return false;
dad.Alcohol -= AlcoholDecreaseOnVotedOut.GetInt();
dad.NotifyIfNecessary(Utils.GetPlayerById(dad.DadId));
Logger.Info("Ejection prohibited", "Dad");
return true;
}
public static void OnAnyoneDeath(PlayerControl target)
{
AllDeadBodies.Add(target.Pos());
}
public static bool OnAnyoneCheckMurderStart(PlayerControl target)
{
Dad dad = Instances.FirstOrDefault(d => d.DadId == target.PlayerId);
return dad != null && dad.UsingAbilities.Contains(Ability.Sleep);
}
private void NotifyIfNecessary(PlayerControl pc, bool force = false)
{
if (!pc.IsAlive() || !GameStates.IsInTask || ExileController.Instance) return;
if (force || Alcohol <= ShowWarningWhenAlcoholIsBelow.GetInt())
Utils.NotifyRoles(SpecifySeer: pc, SpecifyTarget: pc);
Utils.SendRPC(CustomRPC.SyncRoleData, DadId, 3, Alcohol);
}
public void ReceiveRPC(MessageReader reader)
{
switch (reader.ReadPackedInt32())
{
case 1:
int id = reader.ReadPackedInt32();
Shop = ShipStatus.Instance.AllVents.FirstOrDefault(x => x.Id == id);
break;
case 2:
Arrows = reader.ReadString();
break;
case 3:
Alcohol = reader.ReadPackedInt32();
break;
}
}
public override string GetSuffix(PlayerControl seer, PlayerControl target, bool hud = false, bool meeting = false)
{
if (seer.PlayerId != target.PlayerId || seer.PlayerId != DadId || meeting || (seer.IsModdedClient() && !hud)) return string.Empty;
Suffix.Clear();
if (FastVector2.DistanceWithinRange(seer.Pos(), Shop.transform.position, 2f))
{
float canBuyAmount = seer.GetAbilityUseLimit() / AlcoholCost.GetInt();
Suffix.AppendFormat(Translator.GetString("Dad.ShopSuffix"), canBuyAmount);
}
if (Alcohol <= ShowWarningWhenAlcoholIsBelow.GetInt())
{
if (Suffix.Length > 0) Suffix.Append('\n');
Suffix.AppendFormat(Translator.GetString("Dad.LowAlcoholSuffix"), Alcohol);
}
if (Arrows.Length > 0)
{
if (Suffix.Length > 0) Suffix.Append('\n');
Suffix.Append(Arrows);
}
if (Suffix.Length > 0) Suffix.Append("\n\n<size=70%>");
Suffix.AppendFormat(Translator.GetString("Dad.ShopLocation"), Shop.name)
.Append('\n')
.AppendFormat(Translator.GetString("Dad.SelectedAbilitySuffix"), Translator.GetString($"Dad.Ability.{SelectedAbility}"))
.Append('\n')
.Append(Translator.GetString($"Dad.{SelectedAbility}.Description"))
.Append("</size>");
return Suffix.ToString().Trim();
}
public override bool CanUseVent(PlayerControl pc, int ventId)
{
return !IsThisRole(pc) || pc.Is(CustomRoles.Nimble) || pc.GetClosestVent()?.Id == ventId;
}
public override void ManipulateGameEndCheckCrew(PlayerState playerState, out bool keepGameGoing, out int countsAs)
{
if (playerState.IsDead)
{
base.ManipulateGameEndCheckCrew(playerState, out keepGameGoing, out countsAs);
return;
}
bool wentForMilk = UsingAbilities.Contains(Ability.GoForMilk);
keepGameGoing = !wentForMilk;
countsAs = wentForMilk ? 0 : 1;
}
}