* Fix usages of TryIndex()
Most usages of TryIndex() were using it incorrectly. Checking whether prototype IDs specified in prototypes actually existed before using them. This is not appropriate as it's just hiding bugs that should be getting caught by the YAML linter and other tools. (#39115)
This then resulted in TryIndex() getting modified to log errors (94f98073b0), which is incorrect as it causes false-positive errors in proper uses of the API: external data validation. (#39098)
This commit goes through and checks every call site of TryIndex() to see whether they were correct. Most call sites were replaced with the new Resolve(), which is suitable for these "defensive programming" use cases.
Fixes #39115
Breaking change: while doing this I noticed IdCardComponent and related systems were erroneously using ProtoId<AccessLevelPrototype> for job prototypes. This has been corrected.
* fix tests
---------
Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
221 lines
7.1 KiB
C#
221 lines
7.1 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Handles the job data on mind entities.
|
|
/// </summary>
|
|
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<string, string> _inverseTrackerLookup = new();
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnProtoReload);
|
|
SetupTrackerLookup();
|
|
}
|
|
|
|
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
|
{
|
|
if (obj.WasModified<JobPrototype>())
|
|
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<JobPrototype>())
|
|
{
|
|
_inverseTrackerLookup.Add(job.PlayTimeTracker, job.ID);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the corresponding Job Prototype to a <see cref="PlayTimeTrackerPrototype"/>
|
|
/// </summary>
|
|
/// <param name="trackerProto"></param>
|
|
/// <returns></returns>
|
|
public string GetJobPrototype(string trackerProto)
|
|
{
|
|
DebugTools.Assert(_prototypes.HasIndex<PlayTimeTrackerPrototype>(trackerProto));
|
|
return _inverseTrackerLookup[trackerProto];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get the first corresponding department for this job prototype.
|
|
/// </summary>
|
|
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<DepartmentPrototype>().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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Like <see cref="TryGetDepartment"/> 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.
|
|
/// </summary>
|
|
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<DepartmentPrototype>();
|
|
|
|
foreach (var department in departmentProtos)
|
|
{
|
|
if (department.Primary && department.Roles.Contains(jobProto))
|
|
{
|
|
departmentPrototype = department;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
departmentPrototype = null;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get all the departments for a given job. Will return an empty list if none are found.
|
|
/// </summary>
|
|
public bool TryGetAllDepartments(string jobProto, out List<DepartmentPrototype> 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<DepartmentPrototype>();
|
|
departmentPrototypes = new List<DepartmentPrototype>();
|
|
var found = false;
|
|
|
|
foreach (var department in departmentProtos)
|
|
{
|
|
if (department.Roles.Contains(jobProto))
|
|
{
|
|
departmentPrototypes.Add(department);
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to get the lowest weighted department for the given job. If the job has no departments will return null.
|
|
/// </summary>
|
|
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<JobRoleComponent>(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.Resolve(protoId, out prototype) || prototype is not null;
|
|
}
|
|
|
|
public bool MindTryGetJobId(
|
|
[NotNullWhen(true)] EntityUid? mindId,
|
|
out ProtoId<JobPrototype>? job)
|
|
{
|
|
job = null;
|
|
|
|
if (mindId is null)
|
|
return false;
|
|
|
|
if (_roles.MindHasRole<JobRoleComponent>(mindId.Value, out var role))
|
|
job = role.Value.Comp1.JobPrototype;
|
|
|
|
return job is not null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get the job name for this mind.
|
|
/// Returns unknown if not found.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get the job name for this mind.
|
|
/// Returns unknown if not found.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|