using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Server.AlertLevel; using Content.Server.Antag; using Content.Server._Goobstation.Blob; using Content.Server._Goobstation.Blob.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Chat.Managers; using Content.Server.Chat.Systems; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; using Content.Server.Mind; using Content.Server.Nuke; using Content.Server.Objectives; using Content.Server.RoundEnd; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared._Goobstation.Blob.Components; using Content.Shared.GameTicking.Components; using Content.Shared.Objectives.Components; using Robust.Shared.Audio; using Robust.Shared.Player; using Content.Server.Announcements.Systems; namespace Content.Server.GameTicking.Rules; public sealed class BlobRuleSystem : GameRuleSystem { [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; [Dependency] private readonly NukeCodePaperSystem _nukeCode = default!; [Dependency] private readonly StationSystem _stationSystem = default!; [Dependency] private readonly ObjectivesSystem _objectivesSystem = default!; [Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!; [Dependency] private readonly IChatManager _chatManager = default!; [Dependency] private readonly AnnouncerSystem _announcer = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(AfterAntagSelected); } protected override void Started(EntityUid uid, BlobRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { var activeRules = QueryActiveRules(); while (activeRules.MoveNext(out var entityUid, out _, out _, out _)) { if (uid == entityUid) continue; GameTicker.EndGameRule(uid, gameRule); Log.Warning("blob is active!!! remove!"); break; } } protected override void ActiveTick(EntityUid uid, BlobRuleComponent component, GameRuleComponent gameRule, float frameTime) { component.Accumulator += frameTime; if (component.Accumulator < 10) return; component.Accumulator = 0; var check = new Dictionary(); var blobCoreQuery = EntityQueryEnumerator(); while (blobCoreQuery.MoveNext(out var ent, out var comp, out var md, out var xform)) { if (TerminatingOrDeleted(ent, md) || !CheckBlobInStation(ent, xform, out var stationUid)) { continue; } check.TryAdd(stationUid.Value, 0); check[stationUid.Value] += comp.BlobTiles.Count; } foreach (var (station, length) in check.AsParallel()) { CheckChangeStage(station, component, length); } } private bool CheckBlobInStation(EntityUid blobCore, TransformComponent? xform, [NotNullWhen(true)] out EntityUid? stationUid) { var station = _stationSystem.GetOwningStation(blobCore, xform); if (station == null || !HasComp(station.Value)) { _chatManager.SendAdminAlert(blobCore, Loc.GetString("blob-alert-out-off-station")); QueueDel(blobCore); stationUid = null; return false; } stationUid = station.Value; return true; } private const string StationAlertCritical = "delta"; private const string StationAlertDetected = "red"; private void CheckChangeStage( Entity stationUid, BlobRuleComponent blobRuleComp, long blobTilesCount) { Resolve(stationUid, ref stationUid.Comp, false); if (blobTilesCount >= (stationUid.Comp?.StageBegin ?? StationBlobConfigComponent.DefaultStageBegin) && _roundEndSystem.ExpectedCountdownEnd != null) { _roundEndSystem.CancelRoundEndCountdown(checkCooldown: false); _announcer.SendAnnouncement( "blob-recall-shuttle", Loc.GetString("blob-alert-recall-shuttle"), colorOverride: Color.Red, station: stationUid ); } switch (blobRuleComp.Stage) { case BlobStage.Default when blobTilesCount >= (stationUid.Comp?.StageBegin ?? StationBlobConfigComponent.DefaultStageBegin): blobRuleComp.Stage = BlobStage.Begin; _announcer.SendAnnouncement( "blob-detect", Loc.GetString("blob-alert-detect"), colorOverride: Color.Red, station: stationUid ); // blobRuleComp.DetectedAudio, _alertLevelSystem.SetLevel(stationUid, StationAlertDetected, true, true, true, true); RaiseLocalEvent(stationUid, new BlobChangeLevelEvent { Station = stationUid, Level = blobRuleComp.Stage }, broadcast: true); return; case BlobStage.Begin when blobTilesCount >= (stationUid.Comp?.StageCritical ?? StationBlobConfigComponent.DefaultStageCritical): { if (_nukeCode.SendNukeCodes(stationUid))//send the nuke code? { blobRuleComp.Stage = BlobStage.Critical; _announcer.SendAnnouncement( "blob-critical", Loc.GetString("blob-alert-critical"), colorOverride: Color.Red, station: stationUid ); // blobRuleComp.CriticalAudio } else { blobRuleComp.Stage = BlobStage.Critical; _announcer.SendAnnouncement( "blob-critical-no-nuke", Loc.GetString("blob-alert-critical-NoNukeCode"), colorOverride: Color.Red ); // blobRuleComp.CriticalAudio } _alertLevelSystem.SetLevel(stationUid, StationAlertCritical, true, true, true, true); RaiseLocalEvent(stationUid, new BlobChangeLevelEvent { Station = stationUid, Level = blobRuleComp.Stage }, broadcast: true); return; } case BlobStage.Critical when blobTilesCount >= (stationUid.Comp?.StageTheEnd ?? StationBlobConfigComponent.DefaultStageEnd): { blobRuleComp.Stage = BlobStage.TheEnd; _roundEndSystem.EndRound(); RaiseLocalEvent(stationUid, new BlobChangeLevelEvent { Station = stationUid, Level = blobRuleComp.Stage }, broadcast: true); return; } } } private const string BlobIssuer = "objective-issuer-blob"; protected override void AppendRoundEndText( EntityUid uid, BlobRuleComponent blob, GameRuleComponent gameRule, ref RoundEndTextAppendEvent ev) { if (blob.Blobs.Count < 1) return; // no blob no fun var result = Loc.GetString("blob-round-end-result", ("blobCount", blob.Blobs.Count)); var totalPercentage = 0f; // Get the total amount of blob tiles foreach (var (mindId, mind) in blob.Blobs) { var objectives = mind.Objectives.ToArray(); foreach (var objective in objectives) { var comp = Comp(objective); if (comp.LocIssuer != Loc.GetString(BlobIssuer)) continue; var info = _objectivesSystem.GetInfo(objective, mindId, mind); totalPercentage += info?.Progress ?? 0; } } if (totalPercentage >= 0.99f) { result += "\n" + Loc.GetString("blob-end-victory"); } else { result += "\n" + Loc.GetString("blob-end-fail"); result += "\n" + Loc.GetString("blob-end-fail-progress", ("progress", (int) (totalPercentage * 100))); } result += "\n"; // yeah this is duplicated from traitor rules lol, there needs to be a generic rewrite where it just goes through all minds with objectives foreach (var (mindId, mind) in blob.Blobs) { var name = mind.CharacterName; _mindSystem.TryGetSession(mindId, out var session); var username = session?.Name; var objectives = mind.Objectives.ToArray(); if (objectives.Length == 0) { if (username != null) { if (name == null) result += "\n" + Loc.GetString("blob-user-was-a-blob", ("user", username)); else { result += "\n" + Loc.GetString("blob-user-was-a-blob-named", ("user", username), ("name", name)); } } else if (name != null) result += "\n" + Loc.GetString("blob-was-a-blob-named", ("name", name)); continue; } if (username != null) { if (name == null) { result += "\n" + Loc.GetString("blob-user-was-a-blob-with-objectives", ("user", username)); } else { result += "\n" + Loc.GetString("blob-user-was-a-blob-with-objectives-named", ("user", username), ("name", name)); } } else if (name != null) result += "\n" + Loc.GetString("blob-was-a-blob-with-objectives-named", ("name", name)); foreach (var objectiveGroup in objectives.GroupBy(o => Comp(o).LocIssuer == Loc.GetString(BlobIssuer))) { if (!objectiveGroup.Key) continue; foreach (var objective in objectiveGroup) { var info = _objectivesSystem.GetInfo(objective, mindId, mind); if (info == null) continue; var progress = info.Value.Progress; result += "\n- " + Loc.GetString( "blob-objective-percentage", ("progress", (int) (progress * 100)) ); } } } ev.AddLine(result); } public void MakeBlob(EntityUid player) { var comp = EnsureComp(player); comp.HasMind = HasComp(player); comp.TransformationDelay = 10 * 60; // 10min } private void AfterAntagSelected(EntityUid uid, BlobRuleComponent component, AfterAntagEntitySelectedEvent args) { MakeBlob(args.EntityUid); } }