]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
JobRequirement refactor (#30347)
authorEd <96445749+TheShuEd@users.noreply.github.com>
Mon, 5 Aug 2024 04:25:49 +0000 (07:25 +0300)
committerGitHub <noreply@github.com>
Mon, 5 Aug 2024 04:25:49 +0000 (14:25 +1000)
* refactor JobRequirements

* add profile support

* fix

* Update quartermaster.yml

* sloth fixes

* inport 30208

* Update DepartmentPrototype.cs

* species restriction

* left tweak stick

* stringbuilder is cool!

22 files changed:
Content.Client/LateJoin/LateJoinGui.cs
Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs
Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs
Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs
Content.Shared/Roles/DepartmentPrototype.cs
Content.Shared/Roles/JobRequirement/AgeRequirement.cs [new file with mode: 0644]
Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs [new file with mode: 0644]
Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs [new file with mode: 0644]
Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs [new file with mode: 0644]
Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs [new file with mode: 0644]
Content.Shared/Roles/JobRequirements.cs
Resources/Locale/en-US/job/role-requirements.ftl [moved from Resources/Locale/en-US/job/role-timers.ftl with 71% similarity]
Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml
Resources/Prototypes/Roles/Jobs/Command/captain.yml
Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml
Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml
Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml
Resources/Prototypes/Roles/Jobs/Science/research_director.yml
Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml
Resources/Prototypes/Roles/Jobs/departments.yml

index 62a06629f29443f71434634aa342dd7911c33b3f..13cf281513d2abca4b0753ea872e309ca729fe70 100644 (file)
@@ -2,9 +2,11 @@ using System.Linq;
 using System.Numerics;
 using Content.Client.CrewManifest;
 using Content.Client.GameTicking.Managers;
+using Content.Client.Lobby;
 using Content.Client.UserInterface.Controls;
 using Content.Client.Players.PlayTimeTracking;
 using Content.Shared.CCVar;
+using Content.Shared.Preferences;
 using Content.Shared.Roles;
 using Content.Shared.StatusIcon;
 using Robust.Client.Console;
@@ -26,6 +28,7 @@ namespace Content.Client.LateJoin
         [Dependency] private readonly IConfigurationManager _configManager = default!;
         [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
         [Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
+        [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
 
         public event Action<(NetEntity, string)> SelectedId;
 
@@ -254,7 +257,7 @@ namespace Content.Client.LateJoin
 
                         jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
 
-                        if (!_jobRequirements.IsAllowed(prototype, out var reason))
+                        if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
                         {
                             jobButton.Disabled = true;
 
index 1509a2fed13ba00bb9837c8cae04b86c3da5af1d..c5f2f311d9b7db4896573ac9091e5cc3a689ef06 100644 (file)
@@ -649,7 +649,7 @@ namespace Content.Client.Lobby.UI
                 selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
 
                 var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
-                if (!_requirements.CheckRoleTime(requirements, out var reason))
+                if (!_requirements.CheckRoleRequirements(requirements, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
                 {
                     selector.LockRequirements(reason);
                     Profile = Profile?.WithAntagPreference(antag.ID, false);
@@ -903,7 +903,7 @@ namespace Content.Client.Lobby.UI
                     icon.Texture = jobIcon.Icon.Frame0();
                     selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
 
-                    if (!_requirements.IsAllowed(job, out var reason))
+                    if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
                     {
                         selector.LockRequirements(reason);
                     }
index bd4ac877dbbf6f3ae9f7af78ad8a608efda116b5..8ce22489c7181a27b68cf2895f8fac88aaf319ed 100644 (file)
@@ -1,8 +1,10 @@
 using System.Diagnostics.CodeAnalysis;
+using Content.Client.Lobby;
 using Content.Shared.CCVar;
 using Content.Shared.Players;
 using Content.Shared.Players.JobWhitelist;
 using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Preferences;
 using Content.Shared.Roles;
 using Robust.Client;
 using Robust.Client.Player;
@@ -89,7 +91,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
         Updated?.Invoke();
     }
 
-    public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
+    public bool IsAllowed(JobPrototype job, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
     {
         reason = null;
 
@@ -106,16 +108,16 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
         if (player == null)
             return true;
 
-        return CheckRoleTime(job, out reason);
+        return CheckRoleRequirements(job, profile, out reason);
     }
 
-    public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
+    public bool CheckRoleRequirements(JobPrototype job, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
     {
         var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
-        return CheckRoleTime(reqs, out reason);
+        return CheckRoleRequirements(reqs, profile, out reason);
     }
 
-    public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
+    public bool CheckRoleRequirements(HashSet<JobRequirement>? requirements, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
     {
         reason = null;
 
@@ -125,7 +127,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
         var reasons = new List<string>();
         foreach (var requirement in requirements)
         {
-            if (JobRequirements.TryRequirementMet(requirement, _roles, out var jobReason, _entManager, _prototypes))
+            if (requirement.Check(_entManager, _prototypes, profile, _roles, out var jobReason))
                 continue;
 
             reasons.Add(jobReason.ToMarkup());
index 33358a68a4d4326da6cbfc630668a07b1de824fa..6b183362e56e26d8e5f74aff539547dc89c70bc3 100644 (file)
@@ -93,7 +93,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
                 bool hasAccess = true;
                 FormattedMessage? reason;
 
-                if (!requirementsManager.CheckRoleTime(group.Key.Requirements, out reason))
+                if (!requirementsManager.CheckRoleRequirements(group.Key.Requirements, null, out reason))
                 {
                     hasAccess = false;
                 }
index ea6f0ad3f45546ab21ebc2ea417167f2a8dd6789..4139499e9fbec978277ab85ecd2245fb359eef1c 100644 (file)
@@ -6,6 +6,7 @@ using Content.Server.Afk.Events;
 using Content.Server.GameTicking;
 using Content.Server.GameTicking.Events;
 using Content.Server.Mind;
+using Content.Server.Preferences.Managers;
 using Content.Server.Station.Events;
 using Content.Shared.CCVar;
 using Content.Shared.GameTicking;
@@ -13,6 +14,7 @@ using Content.Shared.Mobs;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Players;
 using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Preferences;
 using Content.Shared.Roles;
 using Robust.Server.Player;
 using Robust.Shared.Configuration;
@@ -35,6 +37,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 IServerPreferencesManager _preferencesManager = default!;
 
     public override void Initialize()
     {
@@ -206,7 +209,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
             playTimes = new Dictionary<string, TimeSpan>();
         }
 
-        return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes);
+        return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter);
     }
 
     public HashSet<ProtoId<JobPrototype>> GetDisallowedJobs(ICommonSession player)
@@ -223,7 +226,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
 
         foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
         {
-            if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes))
+            if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter))
                 roles.Add(job.ID);
         }
 
@@ -246,7 +249,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
         for (var i = 0; i < jobs.Count; i++)
         {
             if (_prototypes.TryIndex(jobs[i], out var job)
-                && JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes))
+                && JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter))
             {
                 continue;
             }
index 4a40e2c65e64aff4462a0eca147b98483e43408a..2f7e7b7c4877c0a64966a8b8b043ffed7be3d599 100644 (file)
@@ -25,8 +25,10 @@ public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect
 
         var manager = collection.Resolve<ISharedPlaytimeManager>();
         var playtimes = manager.GetPlayTimes(session);
-        return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason,
-            collection.Resolve<IEntityManager>(),
-            collection.Resolve<IPrototypeManager>());
+        return Requirement.Check(collection.Resolve<IEntityManager>(),
+            collection.Resolve<IPrototypeManager>(),
+            profile,
+            playtimes,
+            out reason);
     }
 }
index d6288bec90984ae8913b3b19233fbc394fb67545..5c94c90f8b3400382cfe0e5d5d7e90db1f3fd4e2 100644 (file)
@@ -9,10 +9,16 @@ public sealed partial class DepartmentPrototype : IPrototype
     public string ID { get; } = string.Empty;
 
     /// <summary>
-    /// A description string to display in the character menu as an explanation of the department's function.
+    /// The name LocId of the department that will be displayed in the various menus.
     /// </summary>
     [DataField(required: true)]
-    public string Description = string.Empty;
+    public LocId Name = string.Empty;
+
+    /// <summary>
+    /// A description LocId to display in the character menu as an explanation of the department's function.
+    /// </summary>
+    [DataField(required: true)]
+    public LocId Description = string.Empty;
 
     /// <summary>
     /// A color representing this department to use for text.
diff --git a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs
new file mode 100644 (file)
index 0000000..2cdc8e0
--- /dev/null
@@ -0,0 +1,50 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Preferences;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Roles;
+
+/// <summary>
+/// Requires the character to be older or younger than a certain age (inclusive)
+/// </summary>
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class AgeRequirement : JobRequirement
+{
+    [DataField(required: true)]
+    public int RequiredAge;
+
+    public override bool Check(IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason)
+    {
+        reason = new FormattedMessage();
+
+        if (profile is null) //the profile could be null if the player is a ghost. In this case we don't need to block the role selection for ghostrole
+            return true;
+
+        if (!Inverted)
+        {
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-young",
+                ("age", RequiredAge)));
+
+            if (profile.Age <= RequiredAge)
+                return false;
+        }
+        else
+        {
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-old",
+                ("age", RequiredAge)));
+
+            if (profile.Age >= RequiredAge)
+                return false;
+        }
+
+        return true;
+    }
+}
diff --git a/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs
new file mode 100644 (file)
index 0000000..56b7d8b
--- /dev/null
@@ -0,0 +1,83 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Preferences;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Roles;
+
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class DepartmentTimeRequirement : JobRequirement
+{
+    /// <summary>
+    /// Which department needs the required amount of time.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<DepartmentPrototype> Department = default!;
+
+    /// <summary>
+    /// How long (in seconds) this requirement is.
+    /// </summary>
+    [DataField(required: true)]
+    public TimeSpan Time;
+
+    public override bool Check(IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason)
+    {
+        reason = new FormattedMessage();
+        var playtime = TimeSpan.Zero;
+
+        // Check all jobs' departments
+        var department = protoManager.Index(Department);
+        var jobs = department.Roles;
+        string proto;
+
+        // Check all jobs' playtime
+        foreach (var other in jobs)
+        {
+            // The schema is stored on the Job role but we want to explode if the timer isn't found anyway.
+            proto = protoManager.Index(other).PlayTimeTracker;
+
+            playTimes.TryGetValue(proto, out var otherTime);
+            playtime += otherTime;
+        }
+
+        var deptDiff = Time.TotalMinutes - playtime.TotalMinutes;
+        var nameDepartment = "role-timer-department-unknown";
+
+        if (protoManager.TryIndex(Department, out var departmentIndexed))
+        {
+            nameDepartment = departmentIndexed.Name;
+        }
+
+        if (!Inverted)
+        {
+            if (deptDiff <= 0)
+                return true;
+
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
+                "role-timer-department-insufficient",
+                ("time", Math.Ceiling(deptDiff)),
+                ("department", Loc.GetString(nameDepartment)),
+                ("departmentColor", department.Color.ToHex())));
+            return false;
+        }
+
+        if (deptDiff <= 0)
+        {
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
+                "role-timer-department-too-high",
+                ("time", -deptDiff),
+                ("department", Loc.GetString(nameDepartment)),
+                ("departmentColor", department.Color.ToHex())));
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs
new file mode 100644 (file)
index 0000000..acbb8f2
--- /dev/null
@@ -0,0 +1,50 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Preferences;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Roles;
+
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class OverallPlaytimeRequirement : JobRequirement
+{
+    /// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
+    [DataField(required: true)]
+    public TimeSpan Time;
+
+    public override bool Check(IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason)
+    {
+        reason = new FormattedMessage();
+
+        var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
+        var overallDiff = Time.TotalMinutes - overallTime.TotalMinutes;
+
+        if (!Inverted)
+        {
+            if (overallDiff <= 0 || overallTime >= Time)
+                return true;
+
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
+                "role-timer-overall-insufficient",
+                ("time", Math.Ceiling(overallDiff))));
+            return false;
+        }
+
+        if (overallDiff <= 0 || overallTime >= Time)
+        {
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high",
+                ("time", -overallDiff)));
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs
new file mode 100644 (file)
index 0000000..658db95
--- /dev/null
@@ -0,0 +1,73 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Preferences;
+using Content.Shared.Roles.Jobs;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Roles;
+
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class RoleTimeRequirement : JobRequirement
+{
+    /// <summary>
+    /// What particular role they need the time requirement with.
+    /// </summary>
+    [DataField(required: true)]
+    public ProtoId<PlayTimeTrackerPrototype> Role = default!;
+
+    /// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
+    [DataField(required: true)]
+    public TimeSpan Time;
+
+    public override bool Check(IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason)
+    {
+        reason = new FormattedMessage();
+
+        string proto = Role;
+
+        playTimes.TryGetValue(proto, out var roleTime);
+        var roleDiff = Time.TotalMinutes - roleTime.TotalMinutes;
+        var departmentColor = Color.Yellow;
+
+        if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
+        {
+            var jobProto = jobSystem.GetJobPrototype(proto);
+
+            if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
+                departmentColor = departmentProto.Color;
+        }
+
+        if (!Inverted)
+        {
+            if (roleDiff <= 0)
+                return true;
+
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
+                "role-timer-role-insufficient",
+                ("time", Math.Ceiling(roleDiff)),
+                ("job", Loc.GetString(proto)),
+                ("departmentColor", departmentColor.ToHex())));
+            return false;
+        }
+
+        if (roleDiff <= 0)
+        {
+            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
+                "role-timer-role-too-high",
+                ("time", -roleDiff),
+                ("job", Loc.GetString(proto)),
+                ("departmentColor", departmentColor.ToHex())));
+            return false;
+        }
+
+        return true;
+    }
+}
diff --git a/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs b/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs
new file mode 100644 (file)
index 0000000..68c0699
--- /dev/null
@@ -0,0 +1,59 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using JetBrains.Annotations;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Roles;
+
+/// <summary>
+/// Requires the character to be or not be on the list of specified species
+/// </summary>
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class SpeciesRequirement : JobRequirement
+{
+    [DataField(required: true)]
+    public HashSet<ProtoId<SpeciesPrototype>> Species = new();
+
+    public override bool Check(IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason)
+    {
+        reason = new FormattedMessage();
+
+        if (profile is null) //the profile could be null if the player is a ghost. In this case we don't need to block the role selection for ghostrole
+            return true;
+
+        var sb = new StringBuilder();
+        sb.Append("[color=yellow]");
+        foreach (var s in Species)
+        {
+            sb.Append(Loc.GetString(protoManager.Index(s).Name) + " ");
+        }
+
+        sb.Append("[/color]");
+
+        if (!Inverted)
+        {
+            reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-whitelisted-species")}\n{sb}");
+
+            if (!Species.Contains(profile.Species))
+                return false;
+        }
+        else
+        {
+            reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-blacklisted-species")}\n{sb}");
+
+            if (Species.Contains(profile.Species))
+                return false;
+        }
+
+        return true;
+    }
+}
index c9d66fcf9186a89b640917f4b66bbb4f19e7de54..17f5f7bd6a29af7ecf060a3aa3c2a247b5d1ff9a 100644 (file)
 using System.Diagnostics.CodeAnalysis;
-using Content.Shared.Players.PlayTimeTracking;
-using Content.Shared.Roles.Jobs;
-using JetBrains.Annotations;
+using Content.Shared.Preferences;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 using Robust.Shared.Utility;
 
-namespace Content.Shared.Roles
-{
-    /// <summary>
-    /// Abstract class for playtime and other requirements for role gates.
-    /// </summary>
-    [ImplicitDataDefinitionForInheritors]
-    [Serializable, NetSerializable]
-    public abstract partial class JobRequirement{}
-
-    [UsedImplicitly]
-    [Serializable, NetSerializable]
-    public sealed partial class DepartmentTimeRequirement : JobRequirement
-    {
-        /// <summary>
-        /// Which department needs the required amount of time.
-        /// </summary>
-        [DataField("department", customTypeSerializer: typeof(PrototypeIdSerializer<DepartmentPrototype>))]
-        public string Department = default!;
-
-        /// <summary>
-        /// How long (in seconds) this requirement is.
-        /// </summary>
-        [DataField("time")] public TimeSpan Time;
-
-        /// <summary>
-        /// If true, requirement will return false if playtime above the specified time.
-        /// </summary>
-        /// <value>
-        /// <c>False</c> by default.<br />
-        /// <c>True</c> for invert general requirement
-        /// </value>
-        [DataField("inverted")] public bool Inverted;
-    }
-
-    [UsedImplicitly]
-    [Serializable, NetSerializable]
-    public sealed partial class RoleTimeRequirement : JobRequirement
-    {
-        /// <summary>
-        /// What particular role they need the time requirement with.
-        /// </summary>
-        [DataField("role", customTypeSerializer: typeof(PrototypeIdSerializer<PlayTimeTrackerPrototype>))]
-        public string Role = default!;
-
-        /// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
-        [DataField("time")] public TimeSpan Time;
-
-        /// <inheritdoc cref="DepartmentTimeRequirement.Inverted"/>
-        [DataField("inverted")] public bool Inverted;
-    }
+namespace Content.Shared.Roles;
 
-    [UsedImplicitly]
-    [Serializable, NetSerializable]
-    public sealed partial class OverallPlaytimeRequirement : JobRequirement
-    {
-        /// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
-        [DataField("time")] public TimeSpan Time;
-
-        /// <inheritdoc cref="DepartmentTimeRequirement.Inverted"/>
-        [DataField("inverted")] public bool Inverted;
-    }
-
-    public static class JobRequirements
+public static class JobRequirements
+{
+    public static bool TryRequirementsMet(
+        JobPrototype job,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason,
+        IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile)
     {
-        public static bool TryRequirementsMet(
-            JobPrototype job,
-            IReadOnlyDictionary<string, TimeSpan> playTimes,
-            [NotNullWhen(false)] out FormattedMessage? reason,
-            IEntityManager entManager,
-            IPrototypeManager prototypes)
-        {
-            var sys = entManager.System<SharedRoleSystem>();
-            var requirements = sys.GetJobRequirement(job);
-            reason = null;
-            if (requirements == null)
-                return true;
-
-            foreach (var requirement in requirements)
-            {
-                if (!TryRequirementMet(requirement, playTimes, out reason, entManager, prototypes))
-                    return false;
-            }
-
+        var sys = entManager.System<SharedRoleSystem>();
+        var requirements = sys.GetJobRequirement(job);
+        reason = null;
+        if (requirements == null)
             return true;
-        }
 
-        /// <summary>
-        /// Returns a string with the reason why a particular requirement may not be met.
-        /// </summary>
-        public static bool TryRequirementMet(
-            JobRequirement requirement,
-            IReadOnlyDictionary<string, TimeSpan> playTimes,
-            [NotNullWhen(false)] out FormattedMessage? reason,
-            IEntityManager entManager,
-            IPrototypeManager prototypes)
+        foreach (var requirement in requirements)
         {
-            reason = null;
-
-            switch (requirement)
-            {
-                case DepartmentTimeRequirement deptRequirement:
-                    var playtime = TimeSpan.Zero;
-
-                    // Check all jobs' departments
-                    var department = prototypes.Index<DepartmentPrototype>(deptRequirement.Department);
-                    var jobs = department.Roles;
-                    string proto;
-
-                    // Check all jobs' playtime
-                    foreach (var other in jobs)
-                    {
-                        // The schema is stored on the Job role but we want to explode if the timer isn't found anyway.
-                        proto = prototypes.Index<JobPrototype>(other).PlayTimeTracker;
-
-                        playTimes.TryGetValue(proto, out var otherTime);
-                        playtime += otherTime;
-                    }
-
-                    var deptDiff = deptRequirement.Time.TotalMinutes - playtime.TotalMinutes;
-
-                    if (!deptRequirement.Inverted)
-                    {
-                        if (deptDiff <= 0)
-                            return true;
-
-                        reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
-                            "role-timer-department-insufficient",
-                            ("time", Math.Ceiling(deptDiff)),
-                            ("department", Loc.GetString(deptRequirement.Department)),
-                            ("departmentColor", department.Color.ToHex())));
-                        return false;
-                    }
-                    else
-                    {
-                        if (deptDiff <= 0)
-                        {
-                            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
-                                "role-timer-department-too-high",
-                                ("time", -deptDiff),
-                                ("department", Loc.GetString(deptRequirement.Department)),
-                                ("departmentColor", department.Color.ToHex())));
-                            return false;
-                        }
-
-                        return true;
-                    }
-
-                case OverallPlaytimeRequirement overallRequirement:
-                    var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
-                    var overallDiff = overallRequirement.Time.TotalMinutes - overallTime.TotalMinutes;
-
-                    if (!overallRequirement.Inverted)
-                    {
-                        if (overallDiff <= 0 || overallTime >= overallRequirement.Time)
-                            return true;
-
-                        reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
-                              "role-timer-overall-insufficient",
-                              ("time", Math.Ceiling(overallDiff))));
-                        return false;
-                    }
-                    else
-                    {
-                        if (overallDiff <= 0 || overallTime >= overallRequirement.Time)
-                        {
-                            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff)));
-                            return false;
-                        }
-
-                        return true;
-                    }
-
-                case RoleTimeRequirement roleRequirement:
-                    proto = roleRequirement.Role;
-
-                    playTimes.TryGetValue(proto, out var roleTime);
-                    var roleDiff = roleRequirement.Time.TotalMinutes - roleTime.TotalMinutes;
-                    var departmentColor = Color.Yellow;
-
-                    if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
-                    {
-                        var jobProto = jobSystem.GetJobPrototype(proto);
-
-                        if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
-                            departmentColor = departmentProto.Color;
-                    }
-
-                    if (!roleRequirement.Inverted)
-                    {
-                        if (roleDiff <= 0)
-                            return true;
-
-                        reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
-                            "role-timer-role-insufficient",
-                            ("time", Math.Ceiling(roleDiff)),
-                            ("job", Loc.GetString(proto)),
-                            ("departmentColor", departmentColor.ToHex())));
-                        return false;
-                    }
-                    else
-                    {
-                        if (roleDiff <= 0)
-                        {
-                            reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
-                                "role-timer-role-too-high",
-                                ("time", -roleDiff),
-                                ("job", Loc.GetString(proto)),
-                                ("departmentColor", departmentColor.ToHex())));
-                            return false;
-                        }
-
-                        return true;
-                    }
-                default:
-                    throw new NotImplementedException();
-            }
+            if (!requirement.Check(entManager, protoManager, profile, playTimes, out reason))
+                return false;
         }
+
+        return true;
     }
 }
+
+/// <summary>
+/// Abstract class for playtime and other requirements for role gates.
+/// </summary>
+[ImplicitDataDefinitionForInheritors]
+[Serializable, NetSerializable]
+public abstract partial class JobRequirement
+{
+    [DataField]
+    public bool Inverted;
+
+    public abstract bool Check(
+        IEntityManager entManager,
+        IPrototypeManager protoManager,
+        HumanoidCharacterProfile? profile,
+        IReadOnlyDictionary<string, TimeSpan> playTimes,
+        [NotNullWhen(false)] out FormattedMessage? reason);
+}
similarity index 71%
rename from Resources/Locale/en-US/job/role-timers.ftl
rename to Resources/Locale/en-US/job/role-requirements.ftl
index 1981f5e795776143a258489500f7dda69c33e0cc..906a0470af0dd7b171928caaa23dbf7f660cbf8f 100644 (file)
@@ -4,7 +4,13 @@ role-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time, "0"
 role-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime to play this role. (Are you trying to play a trainee role?)
 role-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color] to play this role.
 role-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color] to play this role. (Are you trying to play a trainee role?)
+role-timer-age-to-old = Your character must be under the age of [color=yellow]{$age}[/color] to play this role.
+role-timer-age-to-young = Your character must be over the age of [color=yellow]{$age}[/color] to play this role.
+role-timer-whitelisted-species = Your character must be one of the following species to play this role:
+role-timer-blacklisted-species = Your character must not be one of the following species to play this role:
 
 role-timer-locked = Locked (hover for details)
 
+role-timer-department-unknown = Unknown Department
+
 role-ban = You have been banned from this role.
index 515e2f8425a7ce5e4cd848dbde66be8184b58407..0197cd0daf5a8b51d2297df8f560d9fc94a09cb1 100644 (file)
@@ -15,6 +15,8 @@
       time: 36000 #10 hours
     - !type:OverallPlaytimeRequirement
       time: 144000 #40 hrs
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 10
   startingGear: QuartermasterGear
   icon: "JobIconQuarterMaster"
index 79634aa5d9f741fc16aaf9e68996485d901689d2..e2687dd6538130e530b3b24d5aac9fa4df976fbb 100644 (file)
@@ -16,6 +16,8 @@
     - !type:DepartmentTimeRequirement
       department: Command
       time: 54000 # 15 hours
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 20
   startingGear: CaptainGear
   icon: "JobIconCaptain"
index d5521f767f189db3be77c78a792cb79ec0acdb8f..c1b285303d2cef247055e87d91635408ea10e477 100644 (file)
@@ -16,6 +16,8 @@
     - !type:DepartmentTimeRequirement
       department: Command
       time: 36000 # 10 hours
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 20
   startingGear: HoPGear
   icon: "JobIconHeadOfPersonnel"
index 0ee0b6736ca75923e66f970ea19d22fbb121f421..c0d9569c5da7fc2fa61acaedccbebdf3dc89c4b5 100644 (file)
@@ -15,6 +15,8 @@
       time: 36000 #10 hrs
     - !type:OverallPlaytimeRequirement
       time: 144000 #40 hrs
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 10
   startingGear: ChiefEngineerGear
   icon: "JobIconChiefEngineer"
index 25871134155efa67f1caee999b5eeae3562e70c8..94248450cdad1d4855be745f7b5c7e41f6fa5136 100644 (file)
@@ -17,6 +17,8 @@
       time: 36000 #10 hrs
     - !type:OverallPlaytimeRequirement
       time: 144000 #40 hrs
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 10
   startingGear: CMOGear
   icon: "JobIconChiefMedicalOfficer"
index b54ba54b1a476e05a31eacdf37fd1f03df390678..b66e89f42077d326e254a84cc48d933bd16dbae5 100644 (file)
@@ -9,6 +9,8 @@
       time: 36000 #10 hrs
     - !type:OverallPlaytimeRequirement
       time: 144000 #40 hrs
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 10
   startingGear: ResearchDirectorGear
   icon: "JobIconResearchDirector"
index 044df7f69e20afc7cf2c12c79702542fcbc0a6d5..c02f1cee760596a413cd11a9d7b60dfe30fbc1a8 100644 (file)
@@ -15,6 +15,8 @@
       time: 108000 # 30 hrs
     - !type:OverallPlaytimeRequirement
       time: 144000 #40 hrs
+    - !type:AgeRequirement
+      requiredAge: 20
   weight: 10
   startingGear: HoSGear
   icon: "JobIconHeadOfSecurity"
index 9be98be950c6bf675771cf5ec4f3f210da9fab55..6d25d8fd881ce7d05fa50cd6baa367c2197b32f5 100644 (file)
@@ -1,5 +1,6 @@
 - type: department
   id: Cargo
+  name: department-Cargo
   description: department-Cargo-description
   color: "#A46106"
   roles:
@@ -9,6 +10,7 @@
 
 - type: department
   id: Civilian
+  name: department-Civilian
   description: department-Civilian-description
   color: "#9FED58"
   weight: -10
@@ -34,6 +36,7 @@
 
 - type: department
   id: Command
+  name: department-Command
   description: department-Command-description
   color: "#334E6D"
   roles:
@@ -50,6 +53,7 @@
 
 - type: department
   id: Engineering
+  name: department-Engineering
   description: department-Engineering-description
   color: "#EFB341"
   roles:
@@ -60,6 +64,7 @@
 
 - type: department
   id: Medical
+  name: department-Medical
   description: department-Medical-description
   color: "#52B4E9"
   roles:
@@ -72,6 +77,7 @@
 
 - type: department
   id: Security
+  name: department-Security
   description: department-Security-description
   color: "#DE3A3A"
   weight: 20
@@ -84,6 +90,7 @@
 
 - type: department
   id: Science
+  name: department-Science
   description: department-Science-description
   color: "#D381C9"
   roles:
 
 - type: department
   id: Specific
+  name: department-Specific
   description: department-Specific-description
   color: "#9FED58"
   weight: 10