| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- using Content.Server.Antag;
- using Content.Server.Chat.Systems;
- using Content.Server.GameTicking.Rules.Components;
- using Content.Server.Popups;
- using Content.Server.Roles;
- using Content.Server.RoundEnd;
- using Content.Server.Station.Components;
- using Content.Server.Station.Systems;
- using Content.Server.Zombies;
- using Content.Shared.GameTicking.Components;
- using Content.Shared.Humanoid;
- using Content.Shared.Mind;
- using Content.Shared.Mobs;
- using Content.Shared.Mobs.Components;
- using Content.Shared.Mobs.Systems;
- using Content.Shared.Roles;
- using Content.Shared.Zombies;
- using Robust.Shared.Player;
- using Robust.Shared.Timing;
- using System.Globalization;
- namespace Content.Server.GameTicking.Rules;
- public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
- {
- [Dependency] private readonly AntagSelectionSystem _antag = default!;
- [Dependency] private readonly ChatSystem _chat = default!;
- [Dependency] private readonly SharedMindSystem _mindSystem = default!;
- [Dependency] private readonly MobStateSystem _mobState = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly SharedRoleSystem _roles = default!;
- [Dependency] private readonly RoundEndSystem _roundEnd = default!;
- [Dependency] private readonly StationSystem _station = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly ZombieSystem _zombie = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<InitialInfectedRoleComponent, GetBriefingEvent>(OnGetBriefing);
- SubscribeLocalEvent<ZombieRoleComponent, GetBriefingEvent>(OnGetBriefing);
- SubscribeLocalEvent<IncurableZombieComponent, ZombifySelfActionEvent>(OnZombifySelf);
- }
- private void OnGetBriefing(Entity<InitialInfectedRoleComponent> role, ref GetBriefingEvent args)
- {
- if (!_roles.MindHasRole<ZombieRoleComponent>(args.Mind.Owner))
- args.Append(Loc.GetString("zombie-patientzero-role-greeting"));
- }
- private void OnGetBriefing(Entity<ZombieRoleComponent> role, ref GetBriefingEvent args)
- {
- args.Append(Loc.GetString("zombie-infection-greeting"));
- }
- protected override void AppendRoundEndText(EntityUid uid,
- ZombieRuleComponent component,
- GameRuleComponent gameRule,
- ref RoundEndTextAppendEvent args)
- {
- base.AppendRoundEndText(uid, component, gameRule, ref args);
- // This is just the general condition thing used for determining the win/lose text
- var fraction = GetInfectedFraction(true, true);
- if (fraction <= 0)
- args.AddLine(Loc.GetString("zombie-round-end-amount-none"));
- else if (fraction <= 0.25)
- args.AddLine(Loc.GetString("zombie-round-end-amount-low"));
- else if (fraction <= 0.5)
- args.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture))));
- else if (fraction < 1)
- args.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture))));
- else
- args.AddLine(Loc.GetString("zombie-round-end-amount-all"));
- var antags = _antag.GetAntagIdentifiers(uid);
- args.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", antags.Count)));
- foreach (var (_, data, entName) in antags)
- {
- args.AddLine(Loc.GetString("zombie-round-end-user-was-initial",
- ("name", entName),
- ("username", data.UserName)));
- }
- var healthy = GetHealthyHumans();
- // Gets a bunch of the living players and displays them if they're under a threshold.
- // InitialInfected is used for the threshold because it scales with the player count well.
- if (healthy.Count <= 0 || healthy.Count > 2 * antags.Count)
- return;
- args.AddLine("");
- args.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", healthy.Count)));
- foreach (var survivor in healthy)
- {
- var meta = MetaData(survivor);
- var username = string.Empty;
- if (_mindSystem.TryGetMind(survivor, out _, out var mind) && mind.Session != null)
- {
- username = mind.Session.Name;
- }
- args.AddLine(Loc.GetString("zombie-round-end-user-was-survivor",
- ("name", meta.EntityName),
- ("username", username)));
- }
- }
- /// <summary>
- /// The big kahoona function for checking if the round is gonna end
- /// </summary>
- private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
- {
- var healthy = GetHealthyHumans();
- if (healthy.Count == 1) // Only one human left. spooky
- _popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
- if (GetInfectedFraction(false) > zombieRuleComponent.ZombieShuttleCallPercentage && !_roundEnd.IsRoundEndRequested())
- {
- foreach (var station in _station.GetStations())
- {
- _chat.DispatchStationAnnouncement(station, Loc.GetString("zombie-shuttle-call"), colorOverride: Color.Crimson);
- }
- _roundEnd.RequestRoundEnd(null, false);
- }
- // we include dead for this count because we don't want to end the round
- // when everyone gets on the shuttle.
- if (GetInfectedFraction() >= 1) // Oops, all zombies
- _roundEnd.EndRound();
- }
- protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
- {
- base.Started(uid, component, gameRule, args);
- component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay;
- }
- protected override void ActiveTick(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, float frameTime)
- {
- base.ActiveTick(uid, component, gameRule, frameTime);
- if (!component.NextRoundEndCheck.HasValue || component.NextRoundEndCheck > _timing.CurTime)
- return;
- CheckRoundEnd(component);
- component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay;
- }
- private void OnZombifySelf(EntityUid uid, IncurableZombieComponent component, ZombifySelfActionEvent args)
- {
- _zombie.ZombifyEntity(uid);
- if (component.Action != null)
- Del(component.Action.Value);
- }
- /// <summary>
- /// Get the fraction of players that are infected, between 0 and 1
- /// </summary>
- /// <param name="includeOffStation">Include healthy players that are not on the station grid</param>
- /// <param name="includeDead">Should dead zombies be included in the count</param>
- /// <returns></returns>
- private float GetInfectedFraction(bool includeOffStation = true, bool includeDead = false)
- {
- var players = GetHealthyHumans(includeOffStation);
- var zombieCount = 0;
- var query = EntityQueryEnumerator<HumanoidAppearanceComponent, ZombieComponent, MobStateComponent>();
- while (query.MoveNext(out _, out _, out _, out var mob))
- {
- if (!includeDead && mob.CurrentState == MobState.Dead)
- continue;
- zombieCount++;
- }
- return zombieCount / (float) (players.Count + zombieCount);
- }
- /// <summary>
- /// Gets the list of humans who are alive, not zombies, and are on a station.
- /// Flying off via a shuttle disqualifies you.
- /// </summary>
- /// <returns></returns>
- private List<EntityUid> GetHealthyHumans(bool includeOffStation = true)
- {
- var healthy = new List<EntityUid>();
- var stationGrids = new HashSet<EntityUid>();
- if (!includeOffStation)
- {
- foreach (var station in _station.GetStationsSet())
- {
- if (TryComp<StationDataComponent>(station, out var data) && _station.GetLargestGrid(data) is { } grid)
- stationGrids.Add(grid);
- }
- }
- var players = AllEntityQuery<HumanoidAppearanceComponent, ActorComponent, MobStateComponent, TransformComponent>();
- var zombers = GetEntityQuery<ZombieComponent>();
- while (players.MoveNext(out var uid, out _, out _, out var mob, out var xform))
- {
- if (!_mobState.IsAlive(uid, mob))
- continue;
- if (zombers.HasComponent(uid))
- continue;
- if (!includeOffStation && !stationGrids.Contains(xform.GridUid ?? EntityUid.Invalid))
- continue;
- healthy.Add(uid);
- }
- return healthy;
- }
- }
|