diff --git a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs index 1681cd93bd2..8ee25d6880b 100644 --- a/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs @@ -623,7 +623,8 @@ public void RefreshAntags() selector.Setup(items, title, 250, description, guides: antag.Guides); selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1); - if (!_requirements.CheckRoleTime(antag.Requirements, out var reason)) + var requirements = _entManager.System().GetAntagRequirement(antag); + if (!_requirements.CheckRoleTime(requirements, out var reason)) { selector.LockRequirements(reason); Profile = Profile?.WithAntagPreference(antag.ID, false); diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index cb155cadbf4..bd4ac877dbb 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -106,7 +106,13 @@ public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessag if (player == null) return true; - return CheckRoleTime(job.Requirements, out reason); + return CheckRoleTime(job, out reason); + } + + public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason) + { + var reqs = _entManager.System().GetJobRequirement(job); + return CheckRoleTime(reqs, out reason); } public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(false)] out FormattedMessage? reason) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 52ccb1bd708..55c66e89334 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -374,6 +374,9 @@ public AntagSelectionPlayerPool GetPlayerPool(Entity en /// public bool IsSessionValid(Entity ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null) { + // TODO ROLE TIMERS + // Check if antag role requirements are met + if (session == null) return true; diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index 86026b230ba..14007edcbf9 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -14,6 +14,10 @@ public sealed partial class GhostRoleComponent : Component [DataField("rules")] private string _roleRules = "ghost-role-component-default-rules"; + // TODO ROLE TIMERS + // Actually make use of / enforce this requirement? + // Why is this even here. + // Move to ghost role prototype & respect CCvars.GameRoleTimerOverride [DataField("requirements")] public HashSet? Requirements; diff --git a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs index c3142a709a7..09956e313f3 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -35,6 +35,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem [Dependency] private readonly MindSystem _minds = default!; [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; public override void Initialize() { @@ -197,7 +198,6 @@ private void OnGetDisallowedJobs(ref GetDisallowedJobsEvent ev) public bool IsAllowed(ICommonSession player, string role) { if (!_prototypes.TryIndex(role, out var job) || - job.Requirements == null || !_cfg.GetCVar(CCVars.GameRoleTimers)) return true; @@ -224,19 +224,8 @@ public HashSet> GetDisallowedJobs(ICommonSession player) foreach (var job in _prototypes.EnumeratePrototypes()) { - if (job.Requirements != null) - { - foreach (var requirement in job.Requirements) - { - if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes)) - continue; - - goto NoRole; - } - } - - roles.Add(job.ID); - NoRole:; + if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes)) + roles.Add(job.ID); } return roles; @@ -257,22 +246,14 @@ public void RemoveDisallowedJobs(NetUserId userId, List> j for (var i = 0; i < jobs.Count; i++) { - var job = jobs[i]; - - if (!_prototypes.TryIndex(job, out var jobber) || - jobber.Requirements == null || - jobber.Requirements.Count == 0) - continue; - - foreach (var requirement in jobber.Requirements) + if (_prototypes.TryIndex(jobs[i], out var job) + && JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes)) { - if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes)) - continue; - - jobs.RemoveSwap(i); - i--; - break; + continue; } + + jobs.RemoveSwap(i); + i--; } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index c74a3c23636..0f3e62d33f5 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1,4 +1,5 @@ using Content.Shared.Maps; +using Content.Shared.Roles; using Robust.Shared; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; @@ -219,6 +220,12 @@ public static readonly CVarDef public static readonly CVarDef GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED); + /// + /// Override default role requirements using a + /// + public static readonly CVarDef + GameRoleTimerOverride = CVarDef.Create("game.role_timer_override", "", CVar.SERVER | CVar.REPLICATED); + /// /// If roles should be restricted based on whether or not they are whitelisted. /// diff --git a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs index 43d64322504..bc36774ea8b 100644 --- a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs +++ b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs @@ -15,24 +15,24 @@ public sealed partial class GhostRolePrototype : IPrototype /// /// The name of the ghostrole. /// - [DataField] + [DataField(required: true)] public string Name { get; set; } = default!; /// /// The description of the ghostrole. /// - [DataField] + [DataField(required: true)] public string Description { get; set; } = default!; /// /// The entity prototype of the ghostrole /// - [DataField] - public string EntityPrototype = default!; + [DataField(required: true)] + public EntProtoId EntityPrototype; /// /// Rules of the ghostrole /// - [DataField] + [DataField(required: true)] public string Rules = default!; -} \ No newline at end of file +} diff --git a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs index b7457538ebe..b5d8fedbd92 100644 --- a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs +++ b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs @@ -11,6 +11,11 @@ public struct GhostRoleInfo public string Name { get; set; } public string Description { get; set; } public string Rules { get; set; } + + // TODO ROLE TIMERS + // Actually make use of / enforce this requirement? + // Why is this even here. + // Move to ghost role prototype & respect CCvars.GameRoleTimerOverride public HashSet? Requirements { get; set; } /// diff --git a/Content.Shared/Roles/AntagPrototype.cs b/Content.Shared/Roles/AntagPrototype.cs index 05c0c535049..3cb81d4f9c5 100644 --- a/Content.Shared/Roles/AntagPrototype.cs +++ b/Content.Shared/Roles/AntagPrototype.cs @@ -42,7 +42,9 @@ public sealed partial class AntagPrototype : IPrototype /// /// Requirements that must be met to opt in to this antag role. /// - [DataField("requirements")] + // TODO ROLE TIMERS + // Actually check if the requirements are met. Because apparently this is actually unused. + [DataField, Access(typeof(SharedRoleSystem), Other = AccessPermissions.None)] public HashSet? Requirements; /// diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 2a8e9d4b033..c2549e3925f 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -41,7 +41,7 @@ public sealed partial class JobPrototype : IPrototype [ViewVariables(VVAccess.ReadOnly)] public string? LocalizedDescription => Description is null ? null : Loc.GetString(Description); - [DataField("requirements")] + [DataField, Access(typeof(SharedRoleSystem), Other = AccessPermissions.None)] public HashSet? Requirements; [DataField("joinNotifyCrew")] diff --git a/Content.Shared/Roles/JobRequirementOverridePrototype.cs b/Content.Shared/Roles/JobRequirementOverridePrototype.cs new file mode 100644 index 00000000000..d0ce649f360 --- /dev/null +++ b/Content.Shared/Roles/JobRequirementOverridePrototype.cs @@ -0,0 +1,20 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Roles; + +/// +/// Collection of job, antag, and ghost-role job requirements for per-server requirement overrides. +/// +[Prototype] +public sealed partial class JobRequirementOverridePrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField] + public Dictionary, HashSet> Jobs = new (); + + [DataField] + public Dictionary, HashSet> Antags = new (); +} diff --git a/Content.Shared/Roles/JobRequirements.cs b/Content.Shared/Roles/JobRequirements.cs index ba559fadd5c..c9d66fcf918 100644 --- a/Content.Shared/Roles/JobRequirements.cs +++ b/Content.Shared/Roles/JobRequirements.cs @@ -73,16 +73,18 @@ public static class JobRequirements { public static bool TryRequirementsMet( JobPrototype job, - Dictionary playTimes, + IReadOnlyDictionary playTimes, [NotNullWhen(false)] out FormattedMessage? reason, IEntityManager entManager, IPrototypeManager prototypes) { + var sys = entManager.System(); + var requirements = sys.GetJobRequirement(job); reason = null; - if (job.Requirements == null) + if (requirements == null) return true; - foreach (var requirement in job.Requirements) + foreach (var requirement in requirements) { if (!TryRequirementMet(requirement, playTimes, out reason, entManager, prototypes)) return false; @@ -130,7 +132,7 @@ public static bool TryRequirementMet( if (deptDiff <= 0) return true; - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-department-insufficient", ("time", Math.Ceiling(deptDiff)), ("department", Loc.GetString(deptRequirement.Department)), @@ -141,7 +143,7 @@ public static bool TryRequirementMet( { if (deptDiff <= 0) { - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-department-too-high", ("time", -deptDiff), ("department", Loc.GetString(deptRequirement.Department)), @@ -161,7 +163,7 @@ public static bool TryRequirementMet( if (overallDiff <= 0 || overallTime >= overallRequirement.Time) return true; - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-overall-insufficient", ("time", Math.Ceiling(overallDiff)))); return false; @@ -170,7 +172,7 @@ public static bool TryRequirementMet( { if (overallDiff <= 0 || overallTime >= overallRequirement.Time) { - reason = FormattedMessage.FromMarkup(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff))); + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff))); return false; } @@ -197,7 +199,7 @@ public static bool TryRequirementMet( if (roleDiff <= 0) return true; - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-role-insufficient", ("time", Math.Ceiling(roleDiff)), ("job", Loc.GetString(proto)), @@ -208,7 +210,7 @@ public static bool TryRequirementMet( { if (roleDiff <= 0) { - reason = FormattedMessage.FromMarkup(Loc.GetString( + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString( "role-timer-role-too-high", ("time", -roleDiff), ("job", Loc.GetString(proto)), diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index d5ac2e5923a..81a360ebb7f 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,10 +1,12 @@ -using System.Linq; using Content.Shared.Administration.Logs; +using Content.Shared.CCVar; using Content.Shared.Database; +using Content.Shared.Ghost.Roles; using Content.Shared.Mind; using Content.Shared.Roles.Jobs; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; +using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -16,14 +18,30 @@ public abstract class SharedRoleSystem : EntitySystem [Dependency] private readonly IPrototypeManager _prototypes = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedMindSystem _minds = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; // TODO please lord make role entities private readonly HashSet _antagTypes = new(); + private JobRequirementOverridePrototype? _requirementOverride; + public override void Initialize() { // TODO make roles entities SubscribeLocalEvent(OnJobGetAllRoles); + Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true); + } + + private void SetRequirementOverride(string value) + { + if (string.IsNullOrEmpty(value)) + { + _requirementOverride = null; + return; + } + + if (!_prototypes.TryIndex(value, out _requirementOverride )) + Log.Error($"Unknown JobRequirementOverridePrototype: {value}"); } private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args) @@ -253,4 +271,36 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent if (Resolve(mindId, ref mind) && mind.Session != null) _audio.PlayGlobal(sound, mind.Session); } + + public HashSet? GetJobRequirement(JobPrototype job) + { + if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req)) + return req; + + return job.Requirements; + } + + public HashSet? GetJobRequirement(ProtoId job) + { + if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req)) + return req; + + return _prototypes.Index(job).Requirements; + } + + public HashSet? GetAntagRequirement(ProtoId antag) + { + if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req)) + return req; + + return _prototypes.Index(antag).Requirements; + } + + public HashSet? GetAntagRequirement(AntagPrototype antag) + { + if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req)) + return req; + + return antag.Requirements; + } } diff --git a/Resources/Prototypes/Roles/requirement_overrides.yml b/Resources/Prototypes/Roles/requirement_overrides.yml new file mode 100644 index 00000000000..62041f42d7e --- /dev/null +++ b/Resources/Prototypes/Roles/requirement_overrides.yml @@ -0,0 +1,16 @@ +- type: jobRequirementOverride + id: Reduced + jobs: + Captain: + - !type:DepartmentTimeRequirement + department: Engineering + time: 3600 # 1 hours + - !type:DepartmentTimeRequirement + department: Medical + time: 3600 # 1 hours + - !type:DepartmentTimeRequirement + department: Security + time: 3600 # 1 hours + - !type:DepartmentTimeRequirement + department: Command + time: 3600 # 1 hour