using Content.Server._NF.CryoSleep; using Content.Server.Afk; using Content.Server.GameTicking; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Shared._NF.Roles.Components; using Content.Shared._NF.Roles.Systems; using Content.Shared.Mind.Components; using Content.Shared.Roles; using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.Prototypes; namespace Content.Server._NF.Roles.Systems; /// /// This handles job tracking for station jobs that should be reopened on cryo. /// public sealed class JobTrackingSystem : SharedJobTrackingSystem { [Dependency] private readonly IAfkManager _afk = default!; [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly StationJobsSystem _stationJobs = default!; /// public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnJobBeforeCryoEntered); SubscribeLocalEvent(OnJobMindAdded); SubscribeLocalEvent(OnJobMindRemoved); } // If, through admin jiggery pokery, the player returns (or the mob is controlled), we should close the slot if it's opened. private void OnJobMindAdded(Entity ent, ref MindAddedMessage ev) { if (ent.Comp.Job is not { } job || ent.Comp.Active) return; ent.Comp.Active = true; if (!JobShouldBeReopened(ent.Comp.Job.Value)) return; try { if (!TryComp(ent.Comp.SpawnStation, out var stationJobs) || !_stationJobs.TryGetJobSlot(ent.Comp.SpawnStation, job, out var slots) || slots == null) return; // The character is back, readjust their job slot if you can. _stationJobs.TryAdjustJobSlot(ent.Comp.SpawnStation, job, -1); } catch (ArgumentException) { } catch (KeyNotFoundException) { } } private void OnJobMindRemoved(Entity ent, ref MindRemovedMessage ev) { if (ent.Comp.Job == null || !ent.Comp.Active || !JobShouldBeReopened(ent.Comp.Job.Value)) return; OpenJob(ent); } private void OnJobBeforeCryoEntered(Entity ent, ref CryosleepBeforeMindRemovedEvent ev) { if (ent.Comp.Job == null || !ent.Comp.Active || !JobShouldBeReopened(ent.Comp.Job.Value)) return; OpenJob(ent); ev.DeleteEntity = true; } public void OpenJob(Entity ent) { if (ent.Comp.Job is not { } job) return; if (!TryComp(ent.Comp.SpawnStation, out var stationJobs)) return; ent.Comp.Active = false; try { if (!_stationJobs.TryGetJobSlot(ent.Comp.SpawnStation, job, out var slots) || slots == null) return; // Get number of open job slots that are present (not on the cryo map [or on expedition]). var occupiedJobs = GetNumberOfActiveRoles(job, includeAfk: true, exclude: ent); if (slots + occupiedJobs >= stationJobs.SetupAvailableJobs[job][1]) return; _stationJobs.TryAdjustJobSlot(ent.Comp.SpawnStation, job, 1); } catch (ArgumentException) { } catch (KeyNotFoundException) { } } /// /// Returns the number of active players who match the requested Job Prototype Id. /// /// PrototypeID for a job to check. /// If true, includes AFK players in the check. /// The number of active players with this job. public int GetNumberOfActiveRoles(ProtoId jobProtoId, bool includeAfk = true, EntityUid? exclude = null) { var activeJobCount = 0; var jobQuery = AllEntityQuery(); while (jobQuery.MoveNext(out var uid, out var job, out var mindContainer, out var xform)) { if (exclude == uid) continue; if (!job.Active || job.Job != jobProtoId || xform.MapID != _gameTicker.DefaultMap // Skip if they're in cryo or on expedition || !_player.TryGetSessionByEntity(uid, out var session) || session.State.Status != SessionStatus.InGame) continue; if (!includeAfk && _afk.IsAfk(session)) continue; activeJobCount++; } return activeJobCount; } }