using System.Diagnostics.CodeAnalysis; using System.Linq; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Roles.Components; 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; } /// /// Tries to get all the departments for a given job. Will return an empty list if none are found. /// public bool TryGetAllDepartments(string jobProto, out List departmentPrototypes) { // 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(); departmentPrototypes = new List(); var found = false; foreach (var department in departmentProtos) { if (department.Roles.Contains(jobProto)) { departmentPrototypes.Add(department); found = true; } } return found; } /// /// Try to get the lowest weighted department for the given job. If the job has no departments will return null. /// public bool TryGetLowestWeightDepartment(string jobProto, [NotNullWhen(true)] out DepartmentPrototype? departmentPrototype) { departmentPrototype = null; if (!TryGetAllDepartments(jobProto, out var departmentPrototypes) || departmentPrototypes.Count == 0) return false; departmentPrototypes.Sort((x, y) => y.Weight.CompareTo(x.Weight)); departmentPrototype = departmentPrototypes[0]; return true; } public bool MindHasJobWithId(EntityUid? mindId, string prototypeId) { if (mindId is null) return false; _roles.MindHasRole(mindId.Value, out var role); if (role is null) return false; return role.Value.Comp1.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.Comp1.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; } }