From 9a9c9598e069dc81e250864279134ce881dac6ce Mon Sep 17 00:00:00 2001 From: Aviu00 <93730715+Aviu00@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:30:43 +0000 Subject: [PATCH] JobRequiremet refactor (#579) * JobRequirement refactor (#30347) * refactor JobRequirements * add profile support * fix * Update quartermaster.yml * sloth fixes * inport 30208 * Update DepartmentPrototype.cs * species restriction * left tweak stick * stringbuilder is cool! * Add JobRequirementOverride prototypes (#28607) * Add JobRequirementOverride prototypes * a * invert if * Add override that takes in prototypes directly * - fix: Errors. * - add: Add stuff. * - fix: Formatted message fix. * - add: Another requirement. --------- Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> --- .../UI/Tabs/AdminTab/RoleBanWindow.xaml.cs | 2 +- Content.Client/LateJoin/LateJoinGui.cs | 5 +- .../JobRequirementsManager.cs | 20 +- .../Preferences/UI/AntagPreferenceSelector.cs | 7 +- .../UI/HumanoidProfileEditor.xaml.cs | 14 +- .../Preferences/UI/JobPrioritySelector.cs | 4 +- .../UI/LoadoutGroupContainer.xaml.cs | 9 +- .../Preferences/UI/LoadoutWindow.xaml.cs | 10 +- .../Preferences/UI/RequirementsSelector.cs | 9 +- .../Ghost/Controls/Roles/GhostRolesEui.cs | 2 +- Content.Server/Antag/AntagSelectionSystem.cs | 2 +- .../Roles/Components/GhostRoleComponent.cs | 4 + .../PlayTimeTrackingSystem.cs | 45 ++-- .../Systems/StationJobsSystem.Roundstart.cs | 4 +- .../Conditions/BuyerDepartmentCondition.cs | 4 +- Content.Shared/CCVar/CCVars.cs | 8 + .../Ghost/Roles/GhostRolePrototype.cs | 38 +++ .../Ghost/Roles/GhostRolesEuiMessages.cs | 5 + .../Preferences/HumanoidCharacterProfile.cs | 2 +- .../Loadouts/Effects/GroupLoadoutEffect.cs | 4 +- .../Effects/JobRequirementLoadoutEffect.cs | 10 +- .../Loadouts/Effects/LoadoutEffect.cs | 1 + .../Effects/PointsCostLoadoutEffect.cs | 1 + .../Preferences/Loadouts/RoleLoadout.cs | 8 +- Content.Shared/Roles/AntagPrototype.cs | 4 +- Content.Shared/Roles/DepartmentPrototype.cs | 14 +- .../Roles/JobRequirement/AgeRequirement.cs | 50 ++++ .../DepartmentTimeRequirement.cs | 83 ++++++ .../OverallPlaytimeRequirement.cs | 50 ++++ .../JobRequirement/RoleTimeRequirement.cs | 73 +++++ .../JobRequirement/SpeciesRequirement.cs | 59 ++++ .../Roles/JobRequirementOverridePrototype.cs | 20 ++ Content.Shared/Roles/JobRequirements.cs | 251 +++--------------- Content.Shared/Roles/SharedRoleSystem.cs | 51 ++++ .../JobRequirements/GenderRequirement.cs | 68 +++++ .../_White/JobRequirements/SexRequirement.cs | 59 ++++ Content.Shared/_White/WhiteCVars.cs | 2 +- ...{role-timers.ftl => role-requirements.ftl} | 6 + .../Locale/ru-RU/job/role-requirements.ftl | 16 ++ .../Roles/Jobs/Cargo/quartermaster.yml | 7 + .../Prototypes/Roles/Jobs/Command/captain.yml | 6 + .../Roles/Jobs/Command/head_of_personnel.yml | 6 + .../Roles/Jobs/Engineering/chief_engineer.yml | 7 + .../Roles/Jobs/Justice/inspector.yml | 2 + .../Jobs/Medical/chief_medical_officer.yml | 6 + .../Roles/Jobs/Science/research_director.yml | 6 + .../Roles/Jobs/Security/detective.yml | 5 + .../Roles/Jobs/Security/head_of_security.yml | 5 + .../Roles/Jobs/Security/security_cadet.yml | 3 + .../Roles/Jobs/Security/security_officer.yml | 3 + .../Roles/Jobs/Security/senior_officer.yml | 3 + .../Prototypes/Roles/Jobs/Security/warden.yml | 3 + .../Roles/requirement_overrides.yml | 16 ++ 53 files changed, 805 insertions(+), 297 deletions(-) create mode 100644 Content.Shared/Ghost/Roles/GhostRolePrototype.cs create mode 100644 Content.Shared/Roles/JobRequirement/AgeRequirement.cs create mode 100644 Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs create mode 100644 Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs create mode 100644 Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs create mode 100644 Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs create mode 100644 Content.Shared/Roles/JobRequirementOverridePrototype.cs create mode 100644 Content.Shared/_White/JobRequirements/GenderRequirement.cs create mode 100644 Content.Shared/_White/JobRequirements/SexRequirement.cs rename Resources/Locale/en-US/job/{role-timers.ftl => role-requirements.ftl} (71%) create mode 100644 Resources/Locale/ru-RU/job/role-requirements.ftl create mode 100644 Resources/Prototypes/Roles/requirement_overrides.yml diff --git a/Content.Client/Administration/UI/Tabs/AdminTab/RoleBanWindow.xaml.cs b/Content.Client/Administration/UI/Tabs/AdminTab/RoleBanWindow.xaml.cs index 1a27579084..888dc11c66 100644 --- a/Content.Client/Administration/UI/Tabs/AdminTab/RoleBanWindow.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/AdminTab/RoleBanWindow.xaml.cs @@ -42,7 +42,7 @@ public sealed partial class RoleBanWindow : DefaultWindow var prototypeManager = IoCManager.Resolve(); foreach (var proto in prototypeManager.EnumeratePrototypes()) { - CreateRoleGroup(proto.ID, proto.Roles, proto.Color); + CreateRoleGroup(proto.ID, proto.Roles.Select(x => x.Id), proto.Color); } CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes().Select(p => p.ID), diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs index 7a1f463bfb..225ac3eac2 100644 --- a/Content.Client/LateJoin/LateJoinGui.cs +++ b/Content.Client/LateJoin/LateJoinGui.cs @@ -4,7 +4,9 @@ using Content.Client.CrewManifest; using Content.Client.GameTicking.Managers; using Content.Client.UserInterface.Controls; using Content.Client.Players.PlayTimeTracking; +using Content.Client.Preferences; 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; diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 2da87d13ca..953e7cb10a 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -1,8 +1,10 @@ using System.Diagnostics.CodeAnalysis; using Content.Client.Administration.Managers; +using Content.Client.Lobby; using Content.Shared.CCVar; using Content.Shared.Players; using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Client; using Robust.Client.Player; @@ -22,7 +24,6 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager [Dependency] private readonly IEntityManager _entManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; - [Dependency] private readonly IClientAdminManager _adminManager = default!; // WD private readonly Dictionary _roles = new(); private readonly List _roleBans = new(); @@ -40,7 +41,6 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager _net.RegisterNetMessage(RxPlayTime); _client.RunLevelChanged += ClientOnRunLevelChanged; - _adminManager.AdminStatusUpdated += () => Updated?.Invoke(); // WD } private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e) @@ -82,7 +82,8 @@ 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; @@ -96,11 +97,16 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager if (player == null) return true; - return _adminManager.IsActive() || // WD - CheckRoleTime(job.Requirements, out reason); + return CheckRoleRequirements(job, profile, out reason); } - public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(false)] out FormattedMessage? reason) + public bool CheckRoleRequirements(JobPrototype job, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason) + { + var reqs = _entManager.System().GetJobRequirement(job); + return CheckRoleRequirements(reqs, profile, out reason); + } + + public bool CheckRoleRequirements(HashSet? requirements, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason) { reason = null; @@ -110,7 +116,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager var reasons = new List(); 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()); diff --git a/Content.Client/Preferences/UI/AntagPreferenceSelector.cs b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs index 654c393b26..4829cec1aa 100644 --- a/Content.Client/Preferences/UI/AntagPreferenceSelector.cs +++ b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs @@ -1,4 +1,5 @@ using Content.Client.Players.PlayTimeTracking; +using Content.Shared.Preferences; using Content.Shared.Roles; using Robust.Client.UserInterface.Controls; @@ -15,7 +16,7 @@ public sealed class AntagPreferenceSelector : RequirementsSelector? PreferenceChanged; - public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup) + public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup, HumanoidCharacterProfile profile) : base(proto, btnGroup) { Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference); @@ -28,12 +29,12 @@ public sealed class AntagPreferenceSelector : RequirementsSelector(); - if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason)) + if (!requirements.CheckRoleRequirements(proto.Requirements, profile, out var reason)) { LockRequirements(reason); } diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs index a2b118818a..75254d8d3e 100644 --- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs +++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs @@ -557,12 +557,16 @@ namespace Content.Client.Preferences.UI _antagPreferences.Clear(); var btnGroup = new ButtonGroup(); + var character = (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter; + if (character == null) + return; + foreach (var antag in _prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name))) { if (!antag.SetPreference) continue; - var selector = new AntagPreferenceSelector(antag, btnGroup) + var selector = new AntagPreferenceSelector(antag, btnGroup, character) { Margin = new Thickness(3f, 3f, 3f, 0f), }; @@ -590,6 +594,10 @@ namespace Content.Client.Preferences.UI _jobCategories.Clear(); var firstCategory = true; + var character = (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter; + if (character == null) + return; + var departments = _prototypeManager.EnumeratePrototypes().ToArray(); Array.Sort(departments, DepartmentUIComparer.Instance); @@ -650,12 +658,12 @@ namespace Content.Client.Preferences.UI // Clone so we don't modify the underlying loadout. Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout); loadout = loadout?.Clone(); - var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager) + var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager, character) { Margin = new Thickness(3f, 3f, 3f, 0f), }; - if (!_requirements.IsAllowed(job, out var reason)) + if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason)) { selector.LockRequirements(reason); } diff --git a/Content.Client/Preferences/UI/JobPrioritySelector.cs b/Content.Client/Preferences/UI/JobPrioritySelector.cs index 243c78f07e..e15b934e8d 100644 --- a/Content.Client/Preferences/UI/JobPrioritySelector.cs +++ b/Content.Client/Preferences/UI/JobPrioritySelector.cs @@ -20,7 +20,7 @@ public sealed class JobPrioritySelector : RequirementsSelector public event Action? PriorityChanged; - public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan) + public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan, HumanoidCharacterProfile profile) : base(proto, btnGroup) { Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority); @@ -41,6 +41,6 @@ public sealed class JobPrioritySelector : RequirementsSelector var jobIcon = protoMan.Index(proto.Icon); icon.Texture = jobIcon.Icon.Frame0(); - Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon); + Setup(loadout, profile, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon); } } diff --git a/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs b/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs index 8511fbc304..9e903788a6 100644 --- a/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs +++ b/Content.Client/Preferences/UI/LoadoutGroupContainer.xaml.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Shared.Clothing; +using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface.Controls; @@ -17,18 +18,18 @@ public sealed partial class LoadoutGroupContainer : BoxContainer public event Action>? OnLoadoutPressed; public event Action>? OnLoadoutUnpressed; - public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection) + public LoadoutGroupContainer(HumanoidCharacterProfile profile, RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection) { RobustXamlLoader.Load(this); _groupProto = groupProto; - RefreshLoadouts(loadout, session, collection); + RefreshLoadouts(profile, loadout, session, collection); } /// /// Updates button availabilities and buttons. /// - public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection) + public void RefreshLoadouts(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection) { var protoMan = collection.Resolve(); var loadoutSystem = collection.Resolve().System(); @@ -74,7 +75,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto); var pressed = matchingLoadout != null; - var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason); + var enabled = loadout.IsValid(profile, session, loadoutProto, collection, out var reason); var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason); loadoutContainer.Select.Pressed = pressed; loadoutContainer.Text = loadoutSystem.GetName(loadProto); diff --git a/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs b/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs index 79f1e4a833..f732ae2adb 100644 --- a/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs +++ b/Content.Client/Preferences/UI/LoadoutWindow.xaml.cs @@ -1,5 +1,6 @@ using Content.Client.Lobby; using Content.Client.UserInterface.Controls; +using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts.Effects; using Robust.Client.AutoGenerated; @@ -17,9 +18,12 @@ public sealed partial class LoadoutWindow : FancyWindow private List _groups = new(); - public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection) + public HumanoidCharacterProfile Profile; + + public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection) { RobustXamlLoader.Load(this); + Profile = profile; var protoManager = collection.Resolve(); foreach (var group in proto.Groups) @@ -27,7 +31,7 @@ public sealed partial class LoadoutWindow : FancyWindow if (!protoManager.TryIndex(group, out var groupProto)) continue; - var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection); + var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection); LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name)); _groups.Add(container); @@ -54,7 +58,7 @@ public sealed partial class LoadoutWindow : FancyWindow { foreach (var group in _groups) { - group.RefreshLoadouts(loadout, session, collection); + group.RefreshLoadouts(Profile, loadout, session, collection); } } } diff --git a/Content.Client/Preferences/UI/RequirementsSelector.cs b/Content.Client/Preferences/UI/RequirementsSelector.cs index e016661ee6..2b0ae7eb1d 100644 --- a/Content.Client/Preferences/UI/RequirementsSelector.cs +++ b/Content.Client/Preferences/UI/RequirementsSelector.cs @@ -3,6 +3,7 @@ using Content.Client.Lobby; using Content.Client.Stylesheets; using Content.Client.UserInterface.Controls; using Content.Shared.Clothing; +using Content.Shared.Preferences; using Content.Shared.Preferences.Loadouts; using Content.Shared.Preferences.Loadouts.Effects; using Content.Shared.Roles; @@ -75,7 +76,7 @@ public abstract class RequirementsSelector : BoxContainer where T : IPrototyp /// /// Actually adds the controls, must be called in the inheriting class' constructor. /// - protected void Setup(RoleLoadout? loadout, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null) + protected void Setup(RoleLoadout? loadout, HumanoidCharacterProfile profile, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null) { _loadout = loadout; @@ -131,7 +132,7 @@ public abstract class RequirementsSelector : BoxContainer where T : IPrototyp _loadout ??= new RoleLoadout(LoadoutSystem.GetJobPrototype(Proto.ID)); _loadout.SetDefault(protoManager); - _loadoutWindow = new LoadoutWindow(_loadout, protoManager.Index(_loadout.Role), session, collection) + _loadoutWindow = new LoadoutWindow(profile, _loadout, protoManager.Index(_loadout.Role), session, collection) { Title = Loc.GetString(Proto.ID + "-loadout"), }; @@ -150,7 +151,7 @@ public abstract class RequirementsSelector : BoxContainer where T : IPrototyp if (!_loadout.RemoveLoadout(selectedGroup, selectedLoadout, protoManager)) return; - _loadout.EnsureValid(session, collection); + _loadout.EnsureValid(profile, session, collection); _loadoutWindow.RefreshLoadouts(_loadout, session, collection); var controller = UserInterfaceManager.GetUIController(); controller.ReloadProfile(); @@ -162,7 +163,7 @@ public abstract class RequirementsSelector : BoxContainer where T : IPrototyp if (!_loadout.AddLoadout(selectedGroup, selectedLoadout, protoManager)) return; - _loadout.EnsureValid(session, collection); + _loadout.EnsureValid(profile, session, collection); _loadoutWindow.RefreshLoadouts(_loadout, session, collection); var controller = UserInterfaceManager.GetUIController(); controller.ReloadProfile(); diff --git a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs index 33358a68a4..6b183362e5 100644 --- a/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs +++ b/Content.Client/UserInterface/Systems/Ghost/Controls/Roles/GhostRolesEui.cs @@ -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; } diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index f40cfbe6ac..10d57e5044 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -386,4 +386,4 @@ public sealed class AntagSelectionSystem : GameRuleSystem } #endregion -} \ No newline at end of file +} diff --git a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs index ccd460a9cf..51c004fee6 100644 --- a/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs +++ b/Content.Server/Ghost/Roles/Components/GhostRoleComponent.cs @@ -14,6 +14,10 @@ namespace Content.Server.Ghost.Roles.Components [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 00e35623b7..d4e19c6545 100644 --- a/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs +++ b/Content.Server/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -4,12 +4,15 @@ using Content.Server.Afk; using Content.Server.Afk.Events; using Content.Server.GameTicking; using Content.Server.Mind; +using Content.Server.Preferences.Managers; +using Content.Server.Station.Events; using Content.Shared.CCVar; using Content.Shared.GameTicking; 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.GameObjects; using Robust.Server.Player; @@ -33,6 +36,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem [Dependency] private readonly MindSystem _minds = default!; [Dependency] private readonly IPlayTimeTrackingManager _tracking = default!; [Dependency] private readonly IAdminManager _adminManager = default!; + [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; public override void Initialize() { @@ -170,7 +175,6 @@ public sealed class PlayTimeTrackingSystem : EntitySystem return true; if (!_prototypes.TryIndex(role, out var job) || - job.Requirements == null || !_cfg.GetCVar(CCVars.GameRoleTimers)) return true; @@ -180,7 +184,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem playTimes = new Dictionary(); } - return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes); + return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter); } public HashSet GetDisallowedJobs(ICommonSession player) @@ -201,25 +205,14 @@ public sealed class PlayTimeTrackingSystem : EntitySystem 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, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter)) + roles.Add(job.ID); } return roles; } - public void RemoveDisallowedJobs(NetUserId userId, ref List jobs) + public void RemoveDisallowedJobs(NetUserId userId, List> jobs) { if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return; @@ -238,22 +231,14 @@ public sealed class PlayTimeTrackingSystem : EntitySystem 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, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter)) { - if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes)) - continue; - - jobs.RemoveSwap(i); - i--; - break; + continue; } + + jobs.RemoveSwap(i); + i--; } } diff --git a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs index 3966b67674..c9a15cc36f 100644 --- a/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs +++ b/Content.Server/Station/Systems/StationJobsSystem.Roundstart.cs @@ -345,8 +345,8 @@ public sealed partial class StationJobsSystem foreach (var (player, profile) in profiles) { var roleBans = _banManager.GetJobBans(player); - var profileJobs = profile.JobPriorities.Keys.ToList(); - _playTime.RemoveDisallowedJobs(player, ref profileJobs); + var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId(k)).ToList(); + _playTime.RemoveDisallowedJobs(player, profileJobs); List? availableJobs = null; diff --git a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs index 4e5e504aec..ea8de4a9cc 100644 --- a/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs +++ b/Content.Server/Store/Conditions/BuyerDepartmentCondition.cs @@ -43,7 +43,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype) && Blacklist.Contains(department.ID)) + if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID)) return false; } } @@ -56,7 +56,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition { foreach (var department in prototypeManager.EnumeratePrototypes()) { - if (department.Roles.Contains(job.Prototype) && Whitelist.Contains(department.ID)) + if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID)) { found = true; break; diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index e80c083e60..f202820f15 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1,3 +1,5 @@ +using Content.Shared.Maps; +using Content.Shared.Roles; using Robust.Shared; using Robust.Shared.Configuration; @@ -235,6 +237,12 @@ namespace Content.Shared.CCVar 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); + /// /// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect. /// diff --git a/Content.Shared/Ghost/Roles/GhostRolePrototype.cs b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs new file mode 100644 index 0000000000..bc36774ea8 --- /dev/null +++ b/Content.Shared/Ghost/Roles/GhostRolePrototype.cs @@ -0,0 +1,38 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Ghost.Roles; + +/// +/// For selectable ghostrole prototypes in ghostrole spawners. +/// +[Prototype] +public sealed partial class GhostRolePrototype : IPrototype +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + /// + /// The name of the ghostrole. + /// + [DataField(required: true)] + public string Name { get; set; } = default!; + + /// + /// The description of the ghostrole. + /// + [DataField(required: true)] + public string Description { get; set; } = default!; + + /// + /// The entity prototype of the ghostrole + /// + [DataField(required: true)] + public EntProtoId EntityPrototype; + + /// + /// Rules of the ghostrole + /// + [DataField(required: true)] + public string Rules = default!; +} diff --git a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs index b7457538eb..b5d8fedbd9 100644 --- a/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs +++ b/Content.Shared/Ghost/Roles/GhostRolesEuiMessages.cs @@ -11,6 +11,11 @@ namespace Content.Shared.Ghost.Roles 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/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index e04778a20c..5ceee0838f 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -707,7 +707,7 @@ namespace Content.Shared.Preferences continue; } - loadouts.EnsureValid(session, collection); + loadouts.EnsureValid(this, session, collection); } foreach (var value in toRemove) diff --git a/Content.Shared/Preferences/Loadouts/Effects/GroupLoadoutEffect.cs b/Content.Shared/Preferences/Loadouts/Effects/GroupLoadoutEffect.cs index 5a2cd87cc1..1be75f7dbc 100644 --- a/Content.Shared/Preferences/Loadouts/Effects/GroupLoadoutEffect.cs +++ b/Content.Shared/Preferences/Loadouts/Effects/GroupLoadoutEffect.cs @@ -13,13 +13,13 @@ public sealed partial class GroupLoadoutEffect : LoadoutEffect [DataField(required: true)] public ProtoId Proto; - public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason) + public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason) { var effectsProto = collection.Resolve().Index(Proto); foreach (var effect in effectsProto.Effects) { - if (!effect.Validate(loadout, session, collection, out reason)) + if (!effect.Validate(profile, loadout, session, collection, out reason)) return false; } diff --git a/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs b/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs index 4a750a5578..5e8d18fdf9 100644 --- a/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs +++ b/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs @@ -15,12 +15,14 @@ public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect [DataField(required: true)] public JobRequirement Requirement = default!; - public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason) + public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason) { var manager = collection.Resolve(); var playtimes = manager.GetPlayTimes(session); - return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason, - collection.Resolve(), - collection.Resolve()); + return Requirement.Check(collection.Resolve(), + collection.Resolve(), + profile, + playtimes, + out reason); } } diff --git a/Content.Shared/Preferences/Loadouts/Effects/LoadoutEffect.cs b/Content.Shared/Preferences/Loadouts/Effects/LoadoutEffect.cs index 65694d52a1..f35b14e2e0 100644 --- a/Content.Shared/Preferences/Loadouts/Effects/LoadoutEffect.cs +++ b/Content.Shared/Preferences/Loadouts/Effects/LoadoutEffect.cs @@ -11,6 +11,7 @@ public abstract partial class LoadoutEffect /// Tries to validate the effect. /// public abstract bool Validate( + HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, diff --git a/Content.Shared/Preferences/Loadouts/Effects/PointsCostLoadoutEffect.cs b/Content.Shared/Preferences/Loadouts/Effects/PointsCostLoadoutEffect.cs index 3146ff6163..842b4cfc03 100644 --- a/Content.Shared/Preferences/Loadouts/Effects/PointsCostLoadoutEffect.cs +++ b/Content.Shared/Preferences/Loadouts/Effects/PointsCostLoadoutEffect.cs @@ -11,6 +11,7 @@ public sealed partial class PointsCostLoadoutEffect : LoadoutEffect public int Cost = 1; public override bool Validate( + HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, diff --git a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs index 4603f7006d..2f8877e3cc 100644 --- a/Content.Shared/Preferences/Loadouts/RoleLoadout.cs +++ b/Content.Shared/Preferences/Loadouts/RoleLoadout.cs @@ -44,7 +44,7 @@ public sealed class RoleLoadout /// /// Ensures all prototypes exist and effects can be applied. /// - public void EnsureValid(ICommonSession session, IDependencyCollection collection) + public void EnsureValid(HumanoidCharacterProfile profile, ICommonSession session, IDependencyCollection collection) { var groupRemove = new ValueList(); var protoManager = collection.Resolve(); @@ -81,7 +81,7 @@ public sealed class RoleLoadout } // Validate the loadout can be applied (e.g. points). - if (!IsValid(session, loadout.Prototype, collection, out _)) + if (!IsValid(profile, session, loadout.Prototype, collection, out _)) { loadouts.RemoveAt(i); continue; @@ -167,7 +167,7 @@ public sealed class RoleLoadout /// /// Returns whether a loadout is valid or not. /// - public bool IsValid(ICommonSession session, ProtoId loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason) + public bool IsValid(HumanoidCharacterProfile profile, ICommonSession session, ProtoId loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason) { reason = null; @@ -190,7 +190,7 @@ public sealed class RoleLoadout foreach (var effect in loadoutProto.Effects) { - valid = valid && effect.Validate(this, session, collection, out reason); + valid = valid && effect.Validate(profile, this, session, collection, out reason); } return valid; diff --git a/Content.Shared/Roles/AntagPrototype.cs b/Content.Shared/Roles/AntagPrototype.cs index c6acb9b757..3f38af294d 100644 --- a/Content.Shared/Roles/AntagPrototype.cs +++ b/Content.Shared/Roles/AntagPrototype.cs @@ -41,6 +41,8 @@ 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] public HashSet? Requirements; } diff --git a/Content.Shared/Roles/DepartmentPrototype.cs b/Content.Shared/Roles/DepartmentPrototype.cs index c9c9ad270b..136e8be613 100644 --- a/Content.Shared/Roles/DepartmentPrototype.cs +++ b/Content.Shared/Roles/DepartmentPrototype.cs @@ -9,16 +9,16 @@ public sealed partial class DepartmentPrototype : IPrototype [IdDataField] public string ID { get; } = default!; /// - /// A name string. + /// The name LocId of the department that will be displayed in the various menus. /// [DataField(required: true)] - public string Name = default!; + public LocId Name = string.Empty; /// - /// A description string to display in the character menu as an explanation of the department's function. + /// A description LocId to display in the character menu as an explanation of the department's function. /// - [DataField (required: true)] - public string Description = default!; + [DataField(required: true)] + public LocId Description = string.Empty; /// /// A color representing this department to use for text. @@ -26,8 +26,8 @@ public sealed partial class DepartmentPrototype : IPrototype [DataField(required: true)] public Color Color = default!; - [ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List Roles = new(); + [DataField, ViewVariables(VVAccess.ReadWrite)] + public List> Roles = new(); /// /// Whether this is a primary department or not. diff --git a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs new file mode 100644 index 0000000000..2cdc8e001c --- /dev/null +++ b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs @@ -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; + +/// +/// Requires the character to be older or younger than a certain age (inclusive) +/// +[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 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 index 0000000000..56b7d8ba81 --- /dev/null +++ b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs @@ -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 +{ + /// + /// Which department needs the required amount of time. + /// + [DataField(required: true)] + public ProtoId Department = default!; + + /// + /// How long (in seconds) this requirement is. + /// + [DataField(required: true)] + public TimeSpan Time; + + public override bool Check(IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary 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 index 0000000000..acbb8f2b4d --- /dev/null +++ b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs @@ -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 +{ + /// + [DataField(required: true)] + public TimeSpan Time; + + public override bool Check(IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary 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 index 0000000000..658db95ab5 --- /dev/null +++ b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs @@ -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 +{ + /// + /// What particular role they need the time requirement with. + /// + [DataField(required: true)] + public ProtoId Role = default!; + + /// + [DataField(required: true)] + public TimeSpan Time; + + public override bool Check(IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary 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 index 0000000000..68c069931f --- /dev/null +++ b/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs @@ -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; + +/// +/// Requires the character to be or not be on the list of specified species +/// +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class SpeciesRequirement : JobRequirement +{ + [DataField(required: true)] + public HashSet> Species = new(); + + public override bool Check(IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary 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; + } +} diff --git a/Content.Shared/Roles/JobRequirementOverridePrototype.cs b/Content.Shared/Roles/JobRequirementOverridePrototype.cs new file mode 100644 index 0000000000..d0ce649f36 --- /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 ba559fadd5..17f5f7bd6a 100644 --- a/Content.Shared/Roles/JobRequirements.cs +++ b/Content.Shared/Roles/JobRequirements.cs @@ -1,226 +1,51 @@ 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 +namespace Content.Shared.Roles; + +public static class JobRequirements { - /// - /// Abstract class for playtime and other requirements for role gates. - /// - [ImplicitDataDefinitionForInheritors] - [Serializable, NetSerializable] - public abstract partial class JobRequirement{} - - [UsedImplicitly] - [Serializable, NetSerializable] - public sealed partial class DepartmentTimeRequirement : JobRequirement + public static bool TryRequirementsMet( + JobPrototype job, + IReadOnlyDictionary playTimes, + [NotNullWhen(false)] out FormattedMessage? reason, + IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile) { - /// - /// Which department needs the required amount of time. - /// - [DataField("department", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Department = default!; - - /// - /// How long (in seconds) this requirement is. - /// - [DataField("time")] public TimeSpan Time; - - /// - /// If true, requirement will return false if playtime above the specified time. - /// - /// - /// False by default.
- /// True for invert general requirement - ///
- [DataField("inverted")] public bool Inverted; - } - - [UsedImplicitly] - [Serializable, NetSerializable] - public sealed partial class RoleTimeRequirement : JobRequirement - { - /// - /// What particular role they need the time requirement with. - /// - [DataField("role", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Role = default!; - - /// - [DataField("time")] public TimeSpan Time; - - /// - [DataField("inverted")] public bool Inverted; - } - - [UsedImplicitly] - [Serializable, NetSerializable] - public sealed partial class OverallPlaytimeRequirement : JobRequirement - { - /// - [DataField("time")] public TimeSpan Time; - - /// - [DataField("inverted")] public bool Inverted; - } - - public static class JobRequirements - { - public static bool TryRequirementsMet( - JobPrototype job, - Dictionary playTimes, - [NotNullWhen(false)] out FormattedMessage? reason, - IEntityManager entManager, - IPrototypeManager prototypes) - { - reason = null; - if (job.Requirements == null) - return true; - - foreach (var requirement in job.Requirements) - { - if (!TryRequirementMet(requirement, playTimes, out reason, entManager, prototypes)) - return false; - } - + var sys = entManager.System(); + var requirements = sys.GetJobRequirement(job); + reason = null; + if (requirements == null) return true; - } - /// - /// Returns a string with the reason why a particular requirement may not be met. - /// - public static bool TryRequirementMet( - JobRequirement requirement, - IReadOnlyDictionary 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(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(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.FromMarkup(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.FromMarkup(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.FromMarkup(Loc.GetString( - "role-timer-overall-insufficient", - ("time", Math.Ceiling(overallDiff)))); - return false; - } - else - { - if (overallDiff <= 0 || overallTime >= overallRequirement.Time) - { - reason = FormattedMessage.FromMarkup(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.FromMarkup(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.FromMarkup(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; } } + +/// +/// Abstract class for playtime and other requirements for role gates. +/// +[ImplicitDataDefinitionForInheritors] +[Serializable, NetSerializable] +public abstract partial class JobRequirement +{ + [DataField] + public bool Inverted; + + public abstract bool Check( + IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary playTimes, + [NotNullWhen(false)] out FormattedMessage? reason); +} diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index e8053e4c67..d155fed5a1 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,9 +1,12 @@ 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; @@ -15,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) @@ -186,4 +205,36 @@ public abstract class SharedRoleSystem : EntitySystem 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/Content.Shared/_White/JobRequirements/GenderRequirement.cs b/Content.Shared/_White/JobRequirements/GenderRequirement.cs new file mode 100644 index 0000000000..9674f6546b --- /dev/null +++ b/Content.Shared/_White/JobRequirements/GenderRequirement.cs @@ -0,0 +1,68 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using JetBrains.Annotations; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared._White.JobRequirements; + +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class GenderRequirement : JobRequirement +{ + [DataField(required: true)] + public HashSet RequiredGenders; + + public override bool Check(IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary playTimes, + [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = new FormattedMessage(); + + if (profile is null) + return true; + + var sb = new StringBuilder(); + sb.Append("[color=yellow]"); + foreach (var g in RequiredGenders) + { + sb.Append(GenderToString(g) + " "); + } + + sb.Append("[/color]"); + + if (!Inverted) + { + reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-gender-whitelisted")}\n{sb}"); + + if (!RequiredGenders.Contains(profile.Gender)) + return false; + } + else + { + reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-gender-blacklisted")}\n{sb}"); + + if (RequiredGenders.Contains(profile.Gender)) + return false; + } + + return true; + } + + private string GenderToString(Gender gender) + { + return gender switch + { + Gender.Male => Loc.GetString("humanoid-profile-editor-pronouns-male-text"), + Gender.Female => Loc.GetString("humanoid-profile-editor-pronouns-female-text"), + Gender.Epicene => Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"), + _ => Loc.GetString("humanoid-profile-editor-pronouns-neuter-text") + }; + } +} diff --git a/Content.Shared/_White/JobRequirements/SexRequirement.cs b/Content.Shared/_White/JobRequirements/SexRequirement.cs new file mode 100644 index 0000000000..fe5e7fcdcc --- /dev/null +++ b/Content.Shared/_White/JobRequirements/SexRequirement.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared.Humanoid; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.Utility; + +namespace Content.Shared._White.JobRequirements; + +[UsedImplicitly] +[Serializable, NetSerializable] +public sealed partial class SexRequirement : JobRequirement +{ + [DataField(required: true)] + public Sex RequiredSex; + + public override bool Check(IEntityManager entManager, + IPrototypeManager protoManager, + HumanoidCharacterProfile? profile, + IReadOnlyDictionary playTimes, + [NotNullWhen(false)] out FormattedMessage? reason) + { + reason = new FormattedMessage(); + + if (profile is null) + return true; + + if (!Inverted) + { + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-sex-whitelisted", + ("sex", SexToString()))); + + if (profile.Sex != RequiredSex) + return false; + } + else + { + reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-sex-blacklisted", + ("sex", SexToString()))); + + if (profile.Sex == RequiredSex) + return false; + } + + return true; + } + + private string SexToString() + { + return RequiredSex switch + { + Sex.Male => Loc.GetString("role-timer-sex-male"), + Sex.Female => Loc.GetString("role-timer-sex-female"), + _ => Loc.GetString("role-timer-sex-unsexed") + }; + } +} diff --git a/Content.Shared/_White/WhiteCVars.cs b/Content.Shared/_White/WhiteCVars.cs index 2a290fcf88..3abd771896 100644 --- a/Content.Shared/_White/WhiteCVars.cs +++ b/Content.Shared/_White/WhiteCVars.cs @@ -164,7 +164,7 @@ public sealed class WhiteCVars */ public static readonly CVarDef FanaticXenophobiaEnabled = - CVarDef.Create("white.fanatic_xenophobia", true, CVar.SERVERONLY | CVar.ARCHIVE); + CVarDef.Create("white.fanatic_xenophobia", false, CVar.SERVERONLY | CVar.ARCHIVE); /* * MeatyOre diff --git a/Resources/Locale/en-US/job/role-timers.ftl b/Resources/Locale/en-US/job/role-requirements.ftl similarity index 71% rename from Resources/Locale/en-US/job/role-timers.ftl rename to Resources/Locale/en-US/job/role-requirements.ftl index 1981f5e795..906a0470af 100644 --- a/Resources/Locale/en-US/job/role-timers.ftl +++ b/Resources/Locale/en-US/job/role-requirements.ftl @@ -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. diff --git a/Resources/Locale/ru-RU/job/role-requirements.ftl b/Resources/Locale/ru-RU/job/role-requirements.ftl new file mode 100644 index 0000000000..b6e647c538 --- /dev/null +++ b/Resources/Locale/ru-RU/job/role-requirements.ftl @@ -0,0 +1,16 @@ +role-timer-age-to-old = Для игры на этой роли вашему персонажу должно быть меньше [color=yellow]{$age}[/color] лет. +role-timer-age-to-young = Для игры на этой роли вашему персонажу должно быть больше [color=yellow]{$age}[/color] лет. +role-timer-whitelisted-species = Для игры на этой роли ваш персонаж должен быть одной из следующих рас: +role-timer-blacklisted-species = Для игры на этой роли ваш персонаж не должен быть одной из следующих рас: + +role-timer-sex-whitelisted = Для игры на этой роли ваш персонаж должен быть [color=yellow]{$sex}[/color]. +role-timer-sex-blacklisted = Для игры на этой роли ваш персонаж не должен быть [color=yellow]{$sex}[/color]. + +role-timer-sex-male = мужского пола +role-timer-sex-female = женского пола +role-timer-sex-unsexed = бесполым + +role-timer-gender-whitelisted = Для игры на этой роли ваш персонаж должен иметь одно из следующих местоимений: +role-timer-gender-blacklisted = Для игры на этой роли ваш персонаж не должен иметь следующие местоимения: + +role-timer-department-unknown = Unknown Department diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml index a53cb1c3dc..18bd1eec0f 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml @@ -12,6 +12,13 @@ time: 18000 - !type:OverallPlaytimeRequirement time: 108000 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human + - Dwarf + - Felinid weight: 10 icon: "JobIconQuarterMaster" arrivalNotificationPrototype: QuartermasterArrivalNotification diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index e7897558e3..38785dbfc4 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -16,6 +16,12 @@ - !type:DepartmentTimeRequirement department: Command time: 18000 # 15 3 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human + - Felinid weight: 20 icon: "JobIconCaptain" requireAdminNotify: true diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 29f3a99afe..2ace32982b 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -16,6 +16,12 @@ - !type:DepartmentTimeRequirement department: Command time: 3600 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human + - Felinid weight: 20 icon: "JobIconHeadOfPersonnel" requireAdminNotify: true diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml index bcd5a14890..f8580a93cd 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml @@ -12,6 +12,13 @@ time: 18000 - !type:OverallPlaytimeRequirement time: 108000 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human + - Dwarf + - Felinid weight: 10 icon: "JobIconChiefEngineer" requireAdminNotify: true diff --git a/Resources/Prototypes/Roles/Jobs/Justice/inspector.yml b/Resources/Prototypes/Roles/Jobs/Justice/inspector.yml index 9f28aa1a09..beb47c28e7 100644 --- a/Resources/Prototypes/Roles/Jobs/Justice/inspector.yml +++ b/Resources/Prototypes/Roles/Jobs/Justice/inspector.yml @@ -12,6 +12,8 @@ - !type:DepartmentTimeRequirement department: Security time: 36000 #10 hrs + - !type:AgeRequirement + requiredAge: 20 icon: "JobIconInspector" arrivalNotificationPrototype: InspectorArrivalNotification supervisors: job-supervisors-captain diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml index 74f023b60d..5388fefdc4 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml @@ -11,6 +11,12 @@ time: 18000 - !type:OverallPlaytimeRequirement time: 108000 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human + - Felinid weight: 10 icon: "JobIconChiefMedicalOfficer" arrivalNotificationPrototype: ChiefMedicalOfficerArrivalNotification diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index fddecee013..9a6f8b21c5 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -9,6 +9,12 @@ time: 18000 - !type:OverallPlaytimeRequirement time: 108000 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human + - Felinid weight: 10 icon: "JobIconResearchDirector" requireAdminNotify: true diff --git a/Resources/Prototypes/Roles/Jobs/Security/detective.yml b/Resources/Prototypes/Roles/Jobs/Security/detective.yml index d7d9776350..c532b15895 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/detective.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/detective.yml @@ -7,6 +7,11 @@ - !type:DepartmentTimeRequirement department: Security time: 54000 # 15 hours + - !type:SpeciesRequirement + species: + - Human + - Dwarf + - Felinid icon: "JobIconDetective" supervisors: job-supervisors-hos whitelistedSpecies: diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml index 5eebae74c7..6a048705e9 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml @@ -12,6 +12,11 @@ time: 18000 - !type:OverallPlaytimeRequirement time: 108000 + - !type:AgeRequirement + requiredAge: 20 + - !type:SpeciesRequirement + species: + - Human weight: 10 icon: "JobIconHeadOfSecurity" requireAdminNotify: true diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml index e4bcb4c3c0..046ab9a595 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml @@ -10,6 +10,9 @@ department: Security time: 54000 #15 hrs inverted: true # stop playing intern if you're good at security! + - !type:SpeciesRequirement + species: + - Human icon: "JobIconSecurityCadet" supervisors: job-supervisors-security whitelistedSpecies: diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml index 9e9e4d6569..9e8406f595 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml @@ -7,6 +7,9 @@ - !type:DepartmentTimeRequirement department: Security time: 36000 #10 hrs + - !type:SpeciesRequirement + species: + - Human icon: "JobIconSecurityOfficer" supervisors: job-supervisors-hos whitelistedSpecies: diff --git a/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml index 7bac01133e..1751206c53 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/senior_officer.yml @@ -16,6 +16,9 @@ - !type:DepartmentTimeRequirement department: Security time: 216000 # 60 hrs + - !type:SpeciesRequirement + species: + - Human icon: "JobIconSeniorOfficer" supervisors: job-supervisors-hos whitelistedSpecies: diff --git a/Resources/Prototypes/Roles/Jobs/Security/warden.yml b/Resources/Prototypes/Roles/Jobs/Security/warden.yml index 88acbf9e13..4ea3faebcf 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/warden.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/warden.yml @@ -7,6 +7,9 @@ - !type:RoleTimeRequirement role: JobSecurityOfficer time: 36000 #10 hrs + - !type:SpeciesRequirement + species: + - Human icon: "JobIconWarden" supervisors: job-supervisors-hos whitelistedSpecies: diff --git a/Resources/Prototypes/Roles/requirement_overrides.yml b/Resources/Prototypes/Roles/requirement_overrides.yml new file mode 100644 index 0000000000..62041f42d7 --- /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