Compare commits

...
Author SHA1 Message Date
pre-commit-ci[bot] 69b6463898 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-06-26 23:25:57 +00:00
Vanessa 2dc3f61cd8 type fix 2026-06-26 18:12:16 -05:00
Vanessa 164b9be1f9 Added CosCult state checking. 2026-06-26 18:11:52 -05:00
Vanessa eb95b25eaa End Rounds Better 2026-06-26 17:00:12 -05:00
9 changed files with 142 additions and 18 deletions
@@ -1,3 +1,4 @@
using Content.Server.RoundEnd; // DeltaV
using Content.Shared.Roles;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
@@ -25,4 +26,29 @@ public sealed partial class ZombieRuleComponent : Component
/// </summary>
[DataField]
public float ZombieShuttleCallPercentage = 0.7f;
/// <summary>
/// DeltaV - The behavior of the round if all zombies are defeated.
/// </summary>
public RoundEndBehavior ZombieRoundEndBehavior = RoundEndBehavior.ShuttleCall;
/// <summary>
/// DeltaV - The amount of time before the evac shuttle will arrive if the ZombieRoundEndBehavior is set to ShuttleCall.
/// </summary>
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
/// <summary>
/// DeltaV - The announcement sender when calling the shuttle.
/// </summary>
public LocId RoundEndTextSender = "comms-console-announcement-title-centcom";
/// <summary>
/// DeltaV - The announcement text when calling a shuttle.
/// </summary>
public LocId RoundEndTextShuttleCall = "zombie-no-more-threat-announcement-shuttle-call";
/// <summary>
/// DeltaV - The announcement text when a shuttle is already called.
/// </summary>
public LocId RoundEndTextAnnouncement = "zombie-no-more-threat-announcement";
}
@@ -30,6 +30,7 @@ using System.Linq;
using Content.Shared.Station.Components;
using Content.Shared.Store.Components;
using Robust.Shared.Prototypes;
using Content.Shared.Cuffs.Components; // DeltaV
namespace Content.Server.GameTicking.Rules;
@@ -500,12 +501,23 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
// Check if there are nuke operatives still alive on the same map as the shuttle,
// or on the same map as the station.
// If there are, the round can continue.
var operatives = EntityQuery<NukeOperativeComponent, MobStateComponent, TransformComponent>(true);
// BEGIN DeltaV - End Rounds Better
// We need to use a EntityQueryEnumerator instead of a EntityQuery, because we need the uid to check for cuffs
var operatives = new List<Entity<NukeOperativeComponent, MobStateComponent, TransformComponent>>();
var operativesEnumerator = EntityQueryEnumerator<NukeOperativeComponent, MobStateComponent, TransformComponent>();
while (operativesEnumerator.MoveNext(out var uid, out var nukeOp, out var mobState, out var transform))
{
operatives.Add((uid, nukeOp, mobState, transform));
}
var operativesAlive = operatives
.Where(op =>
op.Item3.MapID == shuttleMapId
|| op.Item3.MapID == targetStationMap)
.Any(op => op.Item2.CurrentState == MobState.Alive && op.Item1.Running);
op.Comp3.MapID == shuttleMapId
|| op.Comp3.MapID == targetStationMap)
.Any(op => op.Comp2.CurrentState == MobState.Alive && op.Comp1.Running &&
!TryComp<CuffableComponent>(op, out _)); // in the case the crew keeps one alive
// END DeltaV
if (operativesAlive)
return; // There are living operatives than can access the shuttle, or are still on the station's map.
@@ -3,7 +3,7 @@ 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.RoundEnd; // DeltaV
using Content.Server.Station.Systems;
using Content.Server.Zombies;
using Content.Shared.GameTicking.Components;
@@ -114,11 +114,31 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
/// </summary>
private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
{
// BEGIN DeltaV - End Rounds Better
// Zombies have failed, time to go home
if (zombieRuleComponent.ZombieRoundEndBehavior == RoundEndBehavior.Nothing)
return;
// END DeltaV
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())
// BEGIN DeltaV - End Rounds Better
var infectedPercent = GetInfectedFraction(false);
if (Math.Round(infectedPercent, 0) == 0) // All zombies defeated
{
_roundEnd.DoRoundEndBehavior(zombieRuleComponent.ZombieRoundEndBehavior,
zombieRuleComponent.EvacShuttleTime,
zombieRuleComponent.RoundEndTextSender,
zombieRuleComponent.RoundEndTextShuttleCall,
zombieRuleComponent.RoundEndTextAnnouncement);
zombieRuleComponent.ZombieRoundEndBehavior = RoundEndBehavior.Nothing; // stop this check in the future
}
// END DeltaV
if (infectedPercent > zombieRuleComponent.ZombieShuttleCallPercentage && !_roundEnd.IsRoundEndRequested()) // DeltaV - Move GetInfectedFraction to var
{
foreach (var station in _station.GetStations())
{
+1 -1
View File
@@ -351,7 +351,7 @@ namespace Content.Server.RoundEnd
else
{
RequestRoundEnd(time, null, false, textCall,
Loc.GetString(sender));
Loc.GetString(sender), cantRecall: true);
}
break;
}
@@ -61,6 +61,39 @@ public sealed partial class CosmicCultRuleComponent : Component
[DataField]
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
#region Progress Checking
/// <summary>
/// The next time the monument should check the progress of the cult.
/// </summary>
public TimeSpan NextProgressCheck = TimeSpan.Zero;
/// <summary>
/// The amount of time between progress checks.
/// </summary>
public TimeSpan TimeBetweenProgressChecks = TimeSpan.FromMinutes(15);
/// <summary>
/// The progress value of the monument last time it was checked.
/// </summary>
[DataField]
public int LastProgress = 0;
/// <summary>
/// The number of times the the progress check as failed. Resets after some progress.
/// </summary>
public int ConsecutiveProgressFails = 0;
/// <summary>
/// The number of times the progress check has to fail for the round to start to end.
/// </summary>
public int ProgressFailureTolerance = 3;
public LocId ProgressFailTextShuttleCall = "cosmiccult-progress-fail-shuttle-call";
public LocId ProgressFailTextAnnouncement = "cosmiccult-progress-fail-announcement";
#endregion
[DataField]
public HashSet<EntityUid> Cultists = [];
@@ -62,13 +62,15 @@ using Robust.Shared.Random;
using Robust.Shared.Timing;
using System.Collections.Immutable;
using System.Linq;
using Content.Shared.StatusIcon;
using Content.Shared.SSDIndicator;
namespace Content.Server._DV.CosmicCult;
/// <summary>
/// Where all the main stuff for Cosmic Cultists happens.
/// </summary>
public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponent>
public sealed partial class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponent>
{
[Dependency] private readonly ActionsSystem _actions = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
@@ -172,6 +174,28 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
component.StewardVoteTimer = null;
StewardVote();
}
// Progress check. Is the cult trying to progress at all?
if (component.MonumentPlaced && _timing.CurTime > component.NextProgressCheck)
{
// We're going to assume one monument.
var monument = EntityQuery<MonumentComponent>().First();
if (component.LastProgress == monument.CurrentProgress)
component.ConsecutiveProgressFails++;
else
component.ConsecutiveProgressFails = 0;
// They haven't progressed enough in X checks, end the round.
if (component.ConsecutiveProgressFails >= component.ProgressFailureTolerance) {
SetWinType((uid, component), WinType.CrewMinor);
_roundEnd.DoRoundEndBehavior(component.RoundEndBehavior, component.EvacShuttleTime, component.RoundEndTextSender, component.ProgressFailTextShuttleCall, component.ProgressFailTextAnnouncement);
component.RoundEndBehavior = RoundEndBehavior.Nothing;
}
component.LastProgress = monument.CurrentProgress;
component.NextProgressCheck = _timing.CurTime + component.TimeBetweenProgressChecks;
}
if (component.ExtraRiftTimer is { } riftTimer && _timing.CurTime >= riftTimer && !component.RiftStop)
{
component.ExtraRiftTimer = _timing.CurTime + _rand.Next(TimeSpan.FromSeconds(230), TimeSpan.FromSeconds(360)); //3min50 to 6min between new rifts. Seconds instead of minutes for granularity.
@@ -442,17 +466,19 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
}
}
private bool CultistsAlive()
private int GetCultistsAlive()
{
var query = EntityQueryEnumerator<CosmicCultComponent, MobStateComponent>();
int cultistsAlive = 0;
while (query.MoveNext(out var ent, out _, out var mobComp))
{
if (TryComp<CuffableComponent>(ent, out var cuffed) && cuffed.CuffedHandCount > 0) continue;
if (TryComp<SSDIndicatorComponent>(ent, out var ssd) && ssd.IsSSD) continue; // TODO: Maybe check how long.
if (mobComp.Running && mobComp.CurrentState != MobState.Dead)
return true;
cultistsAlive++;
}
return false;
return cultistsAlive;
}
private void OnMobStateChanged(Entity<CosmicCultComponent> ent, ref MobStateChangedEvent args)
@@ -467,14 +493,19 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
private void CheckForActiveCultists()
{
if (CultistsAlive())
// A lone cultist can't do anything, so check for more than one.
if (GetCultistsAlive() > 1)
return;
var query = QueryActiveRules();
List<Entity<CosmicCultRuleComponent>> cosCultRules = new();
// ConfirmWinState needs to be outside the query, because it might add an active rule
while (query.MoveNext(out var ruleUid, out _, out var ruleComp, out _))
{
ConfirmWinState((ruleUid, ruleComp));
cosCultRules.Add((ruleUid, ruleComp));
}
cosCultRules.ForEach(ConfirmWinState);
}
private void ConfirmWinState(Entity<CosmicCultRuleComponent> ent)
@@ -504,8 +535,6 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
else
SetWinType(ent, WinType.Neutral); //The monument wasn't completed, no cultists escaped to midpoint. Some cultists still remain on the station, though.
if (CultistsAlive()) return; //If there are no cultists alive, ignore all previous checks, crew alreay won.
if (tier <= 1) //Prevent the cult getting cooked by accident before anyone even knows there's a cult.
{
SetWinType(ent, WinType.CrewMinor); //If we somehow get here at the end of the round, we'll still count this as a crew minor.
@@ -62,9 +62,11 @@ cosmiccult-summary-crewminor = The Monument did not reach full power.
cosmiccult-summary-crewmajor = All cosmic cultists were eliminated.
cosmiccult-summary-crewcomplete = All cosmic cultists were deconverted!
cosmiccult-elimination-shuttle-call = Based on scans from our long-range sensors, the Λ-CDM anomaly has subsided. We thank you for your prudence. An emergency shuttle has been automatically called to the station for decontamination and debriefing procedures. ETA: {$time} {$units}. Please note, if the psychological impact of the anomaly is negligible, you may recall the shuttle to extend the shift.
cosmiccult-elimination-announcement = Based on scans from our long-range sensors, the Λ-CDM anomaly has subsided. We thank you for your prudence. An emergency shuttle is already inbound. Return to CentComm safely for decontamination and debriefing procedures.
cosmiccult-elimination-shuttle-call = Based on scans from our long-range sensors, the Λ-CDM anomaly has subsided. We thank you for your prudence. An emergency shuttle has been automatically called to the station for decontamination and debriefing procedures. ETA: {$time} {$units}.
cosmiccult-elimination-announcement = Based on scans from our long-range sensors, the Λ-CDM anomaly has subsided. We thank you for your prudence. An emergency shuttle is already inbound. Return to Midpoint safely for decontamination and debriefing procedures.
cosmiccult-progress-fail-shuttle-call = Due to sustained Λ-CDM activity on you station, an emergency shuttle has been automatically called so that the station may be decontaminated as soon as possible. We thank you for your cooperation.
cosmiccult-progress-fail-announcement = Due to sustained Λ-CDM activity on you station, an emergency shuttle is already inbound. Please ensure as much of the station is ready and accessible for the decontamination team. We thank you for your cooperation.
## BRIEFINGS
@@ -0,0 +1,2 @@
zombie-no-more-threat-announcement-shuttle-call = Long-range sensors indicate that you station has dealt with a biohazardous threat involving Romerol. Station decontamination required. The emergency shuttle has been called. ETA: {$time} {$units}.
zombie-no-more-threat-announcement = Long-range sensors indicate that you station has dealt with a biohazardous threat involving Romerol. Station decontamination required. The emergency shuttle is already en route.
+1 -1
View File
@@ -1,2 +1,2 @@
nuke-ops-no-more-threat-announcement-shuttle-call = Long-range sensors indicate the nuclear threat has been eliminated. The emergency shuttle has been called. ETA: {$time} {$units}. You may recall the shuttle to extend the shift.
nuke-ops-no-more-threat-announcement-shuttle-call = Long-range sensors indicate the nuclear threat has been eliminated. The emergency shuttle has been called. ETA: {$time} {$units}.
nuke-ops-no-more-threat-announcement = Long-range sensors indicate the nuclear threat has been eliminated. The emergency shuttle is already en route.