using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Shared.Roles.Jobs; /// /// Handles the job data on mind entities. /// public abstract class SharedJobSystem : EntitySystem { [Dependency] private readonly SharedPlayerSystem _playerSystem = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; private readonly Dictionary _inverseTrackerLookup = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnProtoReload); SetupTrackerLookup(); } private void OnProtoReload(PrototypesReloadedEventArgs obj) { if (obj.WasModified()) SetupTrackerLookup(); } private void SetupTrackerLookup() { _inverseTrackerLookup.Clear(); // This breaks if you have N trackers to 1 JobId but future concern. foreach (var job in _prototypes.EnumeratePrototypes()) { _inverseTrackerLookup.Add(job.PlayTimeTracker, job.ID); } } /// /// Gets the corresponding Job Prototype to a /// /// /// public string GetJobPrototype(string trackerProto) { DebugTools.Assert(_prototypes.HasIndex(trackerProto)); return _inverseTrackerLookup[trackerProto]; } /// /// Tries to get the first corresponding department for this job prototype. /// public bool TryGetDepartment(string jobProto, [NotNullWhen(true)] out DepartmentPrototype? departmentPrototype) { // Not that many departments so we can just eat the cost instead of storing the inverse lookup. var departmentProtos = _prototypes.EnumeratePrototypes().ToList(); departmentProtos.Sort((x, y) => string.Compare(x.ID, y.ID, StringComparison.Ordinal)); foreach (var department in departmentProtos) { if (department.Roles.Contains(jobProto)) { departmentPrototype = department; return true; } } departmentPrototype = null; return false; } /// /// Like but ignores any non-primary departments. /// For example, with CE it will return Engineering but with captain it will /// not return anything, since Command is not a primary department. /// public bool TryGetPrimaryDepartment(string jobProto, [NotNullWhen(true)] out DepartmentPrototype? departmentPrototype) { // not sorting it since there should only be 1 primary department for a job. // this is enforced by the job tests. var departmentProtos = _prototypes.EnumeratePrototypes(); foreach (var department in departmentProtos) { if (department.Primary && department.Roles.Contains(jobProto)) { departmentPrototype = department; return true; } } departmentPrototype = null; return false; } public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) { MindRoleComponent? comp = null; if (mindId is null) return false; _roles.MindHasRole(mindId.Value, out var role); if (role is null) return false; comp = role.Value.Comp; return (comp.JobPrototype == prototypeId); } public bool MindTryGetJob( [NotNullWhen(true)] EntityUid? mindId, [NotNullWhen(true)] out JobPrototype? prototype) { prototype = null; MindTryGetJobId(mindId, out var protoId); return (_prototypes.TryIndex(protoId, out prototype) || prototype is not null); } public bool MindTryGetJobId( [NotNullWhen(true)] EntityUid? mindId, out ProtoId? job) { job = null; if (mindId is null) return false; if (_roles.MindHasRole(mindId.Value, out var role)) job = role.Value.Comp.JobPrototype; return (job is not null); } /// /// Tries to get the job name for this mind. /// Returns unknown if not found. /// public bool MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId, out string name) { if (MindTryGetJob(mindId, out var prototype)) { name = prototype.LocalizedName; return true; } name = Loc.GetString("generic-unknown-title"); return false; } /// /// Tries to get the job name for this mind. /// Returns unknown if not found. /// public string MindTryGetJobName([NotNullWhen(true)] EntityUid? mindId) { MindTryGetJobName(mindId, out var name); return name; } public bool CanBeAntag(ICommonSession player) { // If the player does not have any mind associated with them (e.g., has not spawned in or is in the lobby), then // they are eligible to be given an antag role/entity. if (_playerSystem.ContentData(player) is not { Mind: { } mindId }) return true; if (!MindTryGetJob(mindId, out var prototype)) return true; return prototype.CanBeAntag; } }