diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs index efaf88b0522..2ebde9222cd 100644 --- a/Content.Client/IoC/ClientContentIoC.cs +++ b/Content.Client/IoC/ClientContentIoC.cs @@ -28,6 +28,8 @@ using Content.Shared.IoC; using Content.Shared.Players.PlayTimeTracking; using Content.Shared.Players.RateLimiting; +using Content.Shared._DEN.Requirements.Managers; +using Content.Client._DEN.Requirements.Managers; namespace Content.Client.IoC { @@ -66,6 +68,7 @@ public static void Register(IDependencyCollection collection) collection.Register(); collection.Register(); collection.Register(); + collection.Register(); // DEN } } } diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs index 9325507c536..04ac5ddce75 100644 --- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs +++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Shared._DEN.Requirements.Managers; using Content.Shared.CCVar; using Content.Shared.Players; using Content.Shared.Players.JobWhitelist; @@ -15,7 +16,7 @@ namespace Content.Client.Players.PlayTimeTracking; -public sealed class JobRequirementsManager : ISharedPlaytimeManager +public sealed partial class JobRequirementsManager : ISharedPlaytimeManager // DEN: Make partial { [Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IClientNetManager _net = default!; @@ -23,6 +24,7 @@ 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 IPlayerRequirementManager _requirements = default!; // DEN private readonly Dictionary _roles = new(); private readonly List> _jobBans = new(); @@ -148,10 +150,21 @@ public bool IsAllowed( return false; // Check other role requirements + // TODO DEN: This is deprecated var reqs = _entManager.System().GetRoleRequirements(job); if (!CheckRoleRequirements(reqs, profile, out reason)) return false; + // Begin DEN: Use player requirements + var roleSystem = _entManager.System(); + var requirements = roleSystem.GetRolePlayerRequirements(job); + if (requirements != null && !PassesRequirements(profile, requirements, out var context)) + { + reason = SharedPlayerRequirementManager.GetCombinedReason(requirements, context); + return false; + } + // End DEN + return true; } @@ -175,14 +188,26 @@ public bool IsAllowed( return false; // Check other role requirements + // TODO DEN: This is deprecated var reqs = _entManager.System().GetRoleRequirements(antag); if (!CheckRoleRequirements(reqs, profile, out reason)) return false; + // Begin DEN: Use player requirements + var roleSystem = _entManager.System(); + var requirements = roleSystem.GetRolePlayerRequirements(antag); + if (requirements != null && !PassesRequirements(profile, requirements, out var context)) + { + reason = SharedPlayerRequirementManager.GetCombinedReason(requirements, context); + return false; + } + // End DEN + return true; } // This must be private so code paths can't accidentally skip requirement overrides. Call this through IsAllowed() + [Obsolete("Use SharedPlayerRequirementManager.CheckRequirements() instead")] // DEN private bool CheckRoleRequirements(HashSet? requirements, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason) { reason = null; diff --git a/Content.Client/_DEN/Lobby/UI/Traits/EntityTraitSelector.xaml.cs b/Content.Client/_DEN/Lobby/UI/Traits/EntityTraitSelector.xaml.cs index 12cc197c9e4..ea6e832c623 100644 --- a/Content.Client/_DEN/Lobby/UI/Traits/EntityTraitSelector.xaml.cs +++ b/Content.Client/_DEN/Lobby/UI/Traits/EntityTraitSelector.xaml.cs @@ -1,14 +1,23 @@ +using System.Text; +using Content.Shared._DEN.Requirements.Managers; using Content.Shared._DEN.Traits.Prototypes; using Robust.Client.AutoGenerated; +using Robust.Client.Player; +using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; +using Robust.Shared.Utility; namespace Content.Client._DEN.Lobby.UI.Traits; [GenerateTypedNameReferences] public sealed partial class EntityTraitSelector : BoxContainer { + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IPlayerRequirementManager _requirements = default!; + public event Action? PreferenceChanged; public int Cost { private set; get; } = 0; @@ -19,19 +28,23 @@ public bool Preference } public ProtoId? PrototypeId { private set; get; } = null; + private EntityTraitPrototype? _trait = null; public EntityTraitSelector(EntityTraitPrototype trait) { RobustXamlLoader.Load(this); + IoCManager.InjectDependencies(this); SetTrait(trait); SelectorCheckbox.OnToggled += args => { PreferenceChanged?.Invoke(args.Pressed); }; + SelectorCheckbox.TooltipSupplier = SupplyTooltip; } private void SetTrait(EntityTraitPrototype trait) { Cost = trait.Cost; PrototypeId = trait.ID; + _trait = trait; var text = ""; @@ -40,9 +53,88 @@ private void SetTrait(EntityTraitPrototype trait) text += Loc.GetString(trait.Name); SelectorCheckbox.Text = text; + UpdateVisibility(trait); + } + + /// + /// Retrieve this session's context. + /// + /// The local player's session context. + private PlayerRequirementContext GetContext() + { + var session = _player.LocalSession; + if (session == null) + return new(); + + var context = _requirements.GetPlayerContext(session); + return context; + } + + /// + /// Toggles the visibility of this selector, depending on the requirements of a given trait. + /// + /// The trait to check the requirements of. + private void UpdateVisibility(EntityTraitPrototype trait) + { + var context = GetContext(); + Visible = !SharedPlayerRequirementManager.ShouldHide(context, trait.Requirements); + } + + /// + /// Provide a tooltip to this control if it has a valid trait set. + /// This includes the trait's description and its requirement text. + /// + /// A tooltip with the trait's description, if this control has a trait. + private Tooltip? SupplyTooltip(Control sender) + { + if (_trait is null) + return null; + var context = _trait.Requirements.Count > 0 + ? GetContext() + : null; + + var tooltipString = ConstructTooltipDescription(_trait, context); + if (tooltipString.Length == 0) + return null; + + var tooltip = new Tooltip(); + if (FormattedMessage.TryFromMarkup(tooltipString, out var msg)) + tooltip.SetMessage(msg); + + return tooltip; + } + + /// + /// Construct a full tooltip description for a given trait. + /// + /// The trait to construct a description out of. + /// The tooltip text associated with this trait. + private static string ConstructTooltipDescription(EntityTraitPrototype trait, + PlayerRequirementContext? context = null) + { + var tooltipBuilder = new StringBuilder(); + + // Add description if (trait.Description is not null) - SelectorCheckbox.ToolTip = Loc.GetString(trait.Description.Value); + tooltipBuilder.AppendLine(Loc.GetString(trait.Description.Value)); + + // Add requirement reason texts + if (trait.Requirements.Count > 0) + { + tooltipBuilder.AppendLine(); // Empty line + foreach (var requirement in trait.Requirements) + { + var reason = requirement.GetReason(context); + if (reason is null) + continue; + + tooltipBuilder.AppendLine(reason); + } + } + + var tooltipString = tooltipBuilder.ToString().Trim(); + return tooltipString; } public void SetInvalid(bool invalid) diff --git a/Content.Client/_DEN/Lobby/UI/Traits/TraitCategoryBox.xaml.cs b/Content.Client/_DEN/Lobby/UI/Traits/TraitCategoryBox.xaml.cs index 5a440bae602..32926df9113 100644 --- a/Content.Client/_DEN/Lobby/UI/Traits/TraitCategoryBox.xaml.cs +++ b/Content.Client/_DEN/Lobby/UI/Traits/TraitCategoryBox.xaml.cs @@ -46,8 +46,7 @@ public void SetTraits(List traits) foreach (var trait in traits) { - if (!trait.Selectable || trait.AllowedSpecies is not null - && (_profile?.Species is null || !trait.AllowedSpecies.Contains(_profile.Species))) + if (!trait.Selectable) continue; var selector = new EntityTraitSelector(trait); diff --git a/Content.Client/_DEN/Players/PlayTimeTracking/JobRequirementManager.cs b/Content.Client/_DEN/Players/PlayTimeTracking/JobRequirementManager.cs new file mode 100644 index 00000000000..143ac5d3237 --- /dev/null +++ b/Content.Client/_DEN/Players/PlayTimeTracking/JobRequirementManager.cs @@ -0,0 +1,32 @@ +using Content.Shared._DEN.Requirements.Managers; +using Content.Shared._DEN.Requirements.PlayerRequirements; +using Content.Shared.Preferences; +using Content.Shared.Roles; + +namespace Content.Client.Players.PlayTimeTracking; + +public sealed partial class JobRequirementsManager +{ + /// + /// Check if we pass a given list of requirements. + /// A profile may be optionally supplied to replace the one in the context. + /// + /// A profile associated with the character we're loading in. + /// The requirements to check. + /// to the simulation state. Reports trackers and such. /// -public sealed class PlayTimeTrackingSystem : EntitySystem +public sealed partial class PlayTimeTrackingSystem : EntitySystem // DEN: Make partial { [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IAfkManager _afk = default!; @@ -35,6 +36,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!; [Dependency] private readonly IPrototypeManager _prototypes = default!; + [Dependency] private readonly IPlayerRequirementManager _requirements = default!; // DEN [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly PlayTimeTrackingManager _tracking = default!; @@ -249,15 +251,25 @@ public bool IsAllowed(ICommonSession player, ProtoId job) playTimes = new Dictionary(); } + // Begin DEN: This is a guard clause now. var requirements = _roles.GetRoleRequirements(job); - return JobRequirements.TryRequirementsMet( + if (!JobRequirements.TryRequirementsMet( requirements, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) - _preferencesManager.GetPreferences(player.UserId).SelectedCharacter); + _preferencesManager.GetPreferences(player.UserId).SelectedCharacter)) + return false; + // End DEN + // Begin DEN: Use PlayerRequirements + var playerReqs = _roles.GetRolePlayerRequirements(job); + if (playerReqs != null && !PassesRequirements(player, playerReqs)) + return false; + + return true; + // End DEN } /// @@ -277,15 +289,25 @@ public bool IsAllowed(ICommonSession player, ProtoId antag) playTimes = new Dictionary(); } + // Begin DEN: This is a guard clause now. var requirements = _roles.GetRoleRequirements(antag); - return JobRequirements.TryRequirementsMet( + if (!JobRequirements.TryRequirementsMet( requirements, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) - _preferencesManager.GetPreferences(player.UserId).SelectedCharacter); + _preferencesManager.GetPreferences(player.UserId).SelectedCharacter)) + return false; + // End DEN + // Begin DEN: Use PlayerRequirements + var playerReqs = _roles.GetRolePlayerRequirements(antag); + if (playerReqs != null && !PassesRequirements(player, playerReqs)) + return false; + + return true; + // End DEN } public HashSet> GetDisallowedJobs(ICommonSession player) @@ -294,18 +316,21 @@ public HashSet> GetDisallowedJobs(ICommonSession player) if (!_cfg.GetCVar(CCVars.GameRoleTimers)) return roles; - if (!_tracking.TryGetTrackerTimes(player, out var playTimes)) - { - Log.Error($"Unable to check playtimes {Environment.StackTrace}"); - playTimes = new Dictionary(); - } + // DEN: Commented out because we're using contexts now + // if (!_tracking.TryGetTrackerTimes(player, out var playTimes)) + // { + // Log.Error($"Unable to check playtimes {Environment.StackTrace}"); + // playTimes = new Dictionary(); + // } + // Begin DEN: Use PlayerRequirements + var context = _requirements.GetPlayerContext(player); foreach (var job in _prototypes.EnumeratePrototypes()) { - if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter)) + if (IsJobAllowed(player, job, context)) roles.Add(job.ID); } - + // End DEN return roles; } @@ -315,20 +340,21 @@ public void RemoveDisallowedJobs(NetUserId userId, List> j return; var player = _playerManager.GetSessionById(userId); - if (!_tracking.TryGetTrackerTimes(player, out var playTimes)) - { - // Sorry mate but your playtimes haven't loaded. - Log.Error($"Playtimes weren't ready yet for {player} on roundstart!"); - playTimes ??= new Dictionary(); - } - + // DEN: Commented out because we're using contexts now + // if (!_tracking.TryGetTrackerTimes(player, out var playTimes)) + // { + // // Sorry mate but your playtimes haven't loaded. + // Log.Error($"Playtimes weren't ready yet for {player} on roundstart!"); + // playTimes ??= new Dictionary(); + // } + + // Begin DEN: Use PlayerRequirements + var context = _requirements.GetPlayerContext(player); for (var i = 0; i < jobs.Count; i++) { - if (_prototypes.Resolve(jobs[i], out var job) - && JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter)) - { + if (_prototypes.Resolve(jobs[i], out var job) && IsJobAllowed(player, job, context)) continue; - } + // End DEN jobs.RemoveSwap(i); i--; diff --git a/Content.Server/_DEN/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs b/Content.Server/_DEN/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs new file mode 100644 index 00000000000..8cdc7c57e43 --- /dev/null +++ b/Content.Server/_DEN/Players/PlayTimeTracking/PlayTimeTrackingSystem.cs @@ -0,0 +1,40 @@ +using Content.Shared._DEN.Requirements.Managers; +using Content.Shared._DEN.Requirements.PlayerRequirements; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using Robust.Shared.Player; + +namespace Content.Server.Players.PlayTimeTracking; + +public sealed partial class PlayTimeTrackingSystem +{ + private bool PassesRequirements(ICommonSession player, + List requirements, + PlayerRequirementContext? context = null) + { + context ??= _requirements.GetPlayerContext(player); + return SharedPlayerRequirementManager.CheckRequirements(context, requirements); + } + + private bool IsJobAllowed(ICommonSession player, + JobPrototype job, + PlayerRequirementContext? context = null) + { + context ??= _requirements.GetPlayerContext(player); + var playTimes = context.Playtimes ?? new Dictionary(); + var profile = context.Profile + ?? (HumanoidCharacterProfile?)_preferencesManager.GetPreferences(player.UserId).SelectedCharacter; + + // TODO DEN: This is deprecated. + var oldRequirementsMet = JobRequirements.TryRequirementsMet(job, + playTimes, + out _, + EntityManager, + _prototypes, + profile); + + var playerReqs = _roles.GetRolePlayerRequirements(job); + var requirementsMet = playerReqs == null || PassesRequirements(player, playerReqs, context); + return oldRequirementsMet && requirementsMet; + } +} diff --git a/Content.Server/_DEN/Requirements/Managers/PlayerRequirementManager.cs b/Content.Server/_DEN/Requirements/Managers/PlayerRequirementManager.cs new file mode 100644 index 00000000000..ea93973b6ba --- /dev/null +++ b/Content.Server/_DEN/Requirements/Managers/PlayerRequirementManager.cs @@ -0,0 +1,40 @@ +using Content.Server.Preferences.Managers; +using Content.Shared._DEN.Requirements.Managers; +using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Preferences; +using Robust.Shared.Player; + +namespace Content.Server._DEN.Requirements.Managers; + +/// +public sealed partial class PlayerRequirementManager : SharedPlayerRequirementManager +{ + [Dependency] private readonly ISharedPlaytimeManager _playtimeManager = default!; + [Dependency] private readonly IServerPreferencesManager _prefsManager = default!; + + /// + public override PlayerRequirementContext GetPlayerContext(ICommonSession session) + { + var playtimes = _playtimeManager.GetPlayTimes(session); + var profile = GetProfile(session); + + return new() + { + Playtimes = playtimes, + Profile = profile, + }; + } + + /// + /// Retrieves a session's selected lobby character. + /// + /// The session to retrieve a profile for. + /// The session's currently-selected profile, if any. + private HumanoidCharacterProfile? GetProfile(ICommonSession session) + { + if (!_prefsManager.TryGetCachedPreferences(session.UserId, out var prefs)) + return null; + + return prefs.SelectedCharacter; + } +} diff --git a/Content.Server/_DEN/Traits/EntitySystems/TraitSystem.cs b/Content.Server/_DEN/Traits/EntitySystems/TraitSystem.cs index 997672410db..b1281a40d1d 100644 --- a/Content.Server/_DEN/Traits/EntitySystems/TraitSystem.cs +++ b/Content.Server/_DEN/Traits/EntitySystems/TraitSystem.cs @@ -1,10 +1,7 @@ +using Content.Shared._DEN.Requirements.Managers; using Content.Shared._DEN.Traits.EntitySystems; -using Content.Shared._DEN.Traits.Prototypes; using Content.Shared.GameTicking; -using Content.Shared.Hands.EntitySystems; -using Content.Shared.Humanoid; using Content.Shared.Roles; -using Content.Shared.Whitelist; using Robust.Shared.Prototypes; #pragma warning disable IDE1006 // Naming Styles @@ -14,6 +11,7 @@ namespace Content.Server._DEN.Traits.EntitySystems; public sealed partial class TraitSystem : SharedTraitSystem { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerRequirementManager _requirements = default!; public override void Initialize() { @@ -45,14 +43,12 @@ private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args) continue; } - if (trait.AllowedSpecies != null) + var context = _requirements.GetPlayerContext(args.Player); + context.Profile = args.Profile; + if (!SharedPlayerRequirementManager.CheckRequirements(context, trait.Requirements)) { - if (!TryComp(mob, out var profile) - || !trait.AllowedSpecies.Contains(profile.Species)) - { - Log.Error($"Tried to spawn trait {traitId} on {ToPrettyString(mob)} with invalid species: {profile?.Species ?? "null"}!"); - continue; - } + Log.Error($"Tried to spawn trait {traitId} on {ToPrettyString(mob)}, but we failed the requirements to do so!"); + continue; } TryAddTrait(mob, traitId, out _); diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs index e117771313d..6017de228d3 100644 --- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs +++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs @@ -21,6 +21,7 @@ using Robust.Shared; using YamlDotNet.RepresentationModel; using Content.Shared._DEN.Traits.Prototypes; +using Content.Shared._DEN.Requirements.Managers; namespace Content.Shared.Preferences { @@ -489,6 +490,7 @@ public void EnsureValid(ICommonSession session, IDependencyCollection collection { var configManager = collection.Resolve(); var prototypeManager = collection.Resolve(); + var requirements = collection.Resolve(); // DEN if (!prototypeManager.TryIndex(Species, out var speciesPrototype) || speciesPrototype.RoundStart == false) { @@ -605,15 +607,20 @@ public void EnsureValid(ICommonSession session, IDependencyCollection collection .Where(id => prototypeManager.TryIndex(id, out var antag) && antag.SetPreference) .ToList(); + // Begin DEN: Trait validation // var traits = TraitPreferences // .Where(prototypeManager.HasIndex) - // .ToList(); // DEN + // .ToList(); + + var context = requirements.GetPlayerContext(session); + context.Profile = this; var traits = EntityTraitPreferences .Where(t => prototypeManager.TryIndex(t, out var trait) && trait.Selectable - && (trait.AllowedSpecies is null || trait.AllowedSpecies.Contains(Species))) - .ToList(); // DEN + && !SharedPlayerRequirementManager.ShouldHide(context, trait.Requirements)) + .ToList(); + // End DEN Name = name; FlavorText = flavortext; diff --git a/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs b/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs index b9bf4b38bcd..e793cd7e859 100644 --- a/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs +++ b/Content.Shared/Preferences/Loadouts/Effects/JobRequirementLoadoutEffect.cs @@ -12,6 +12,7 @@ namespace Content.Shared.Preferences.Loadouts.Effects; /// /// Checks for a job requirement to be met such as playtime. /// +[Obsolete("Use PlayerRequirementLoadoutEffect")] // DEN public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect { [DataField(required: true)] diff --git a/Content.Shared/Roles/AntagPrototype.cs b/Content.Shared/Roles/AntagPrototype.cs index 367b05c3dd1..3d12856e8a1 100644 --- a/Content.Shared/Roles/AntagPrototype.cs +++ b/Content.Shared/Roles/AntagPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared._DEN.Requirements.PlayerRequirements; using Content.Shared.Guidebook; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; @@ -48,8 +49,17 @@ public sealed partial class AntagPrototype : IPrototype /// Requirements that must be met to opt in to this antag role. /// [DataField, Access(typeof(SharedRoleSystem), Other = AccessPermissions.None)] + [Obsolete("Use PlayerRequirements instead")] // DEN public HashSet? Requirements; + // Begin DEN: Use PlayerRequirements + /// + /// A list of PlayerRequirements for this role. + /// + [DataField("playerRequirements")] + public List? PlayerRequirements; + // End DEN + /// /// Optional list of guides associated with this antag. If the guides are opened, the first entry in this list /// will be used to select the currently selected guidebook. diff --git a/Content.Shared/Roles/JobPrototype.cs b/Content.Shared/Roles/JobPrototype.cs index 6b58c424287..2883b9b3957 100644 --- a/Content.Shared/Roles/JobPrototype.cs +++ b/Content.Shared/Roles/JobPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared._DEN.Requirements.PlayerRequirements; using Content.Shared.Access; using Content.Shared.Guidebook; using Content.Shared.Players.PlayTimeTracking; @@ -48,8 +49,17 @@ public sealed partial class JobPrototype : IPrototype /// Requirements for the job. /// [DataField, Access(typeof(SharedRoleSystem), Other = AccessPermissions.None)] + [Obsolete("Use PlayerRequirements instead")] // DEN public HashSet? Requirements; + // Begin DEN: Use PlayerRequirements + /// + /// A list of PlayerRequirements for this job. + /// + [DataField("playerRequirements")] + public List? PlayerRequirements; + // End DEN + /// /// When true - the station will have anouncement about arrival of this player. /// diff --git a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs index 30f607adf7f..a7106484df0 100644 --- a/Content.Shared/Roles/JobRequirement/AgeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/AgeRequirement.cs @@ -12,6 +12,7 @@ namespace Content.Shared.Roles; /// [UsedImplicitly] [Serializable, NetSerializable] +[Obsolete("Use PlayerAgeRequirement instead")] // DEN public sealed partial class AgeRequirement : JobRequirement { [DataField(required: true)] diff --git a/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs index 4034b8b419d..a05cb952f9f 100644 --- a/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/DepartmentTimeRequirement.cs @@ -10,6 +10,7 @@ namespace Content.Shared.Roles; [UsedImplicitly] [Serializable, NetSerializable] +[Obsolete("Use PlayerDepartmentPlaytimeRequirement instead")] // DEN public sealed partial class DepartmentTimeRequirement : JobRequirement { /// diff --git a/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs index 67b3938e1a7..543f16b3b92 100644 --- a/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/OverallPlaytimeRequirement.cs @@ -11,6 +11,7 @@ namespace Content.Shared.Roles; [UsedImplicitly] [Serializable, NetSerializable] +[Obsolete("Use PlayerOverallPlaytimeRequirement instead")] // DEN public sealed partial class OverallPlaytimeRequirement : JobRequirement { /// diff --git a/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs index f096cfcb420..c5f6fce5ec4 100644 --- a/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs @@ -12,6 +12,7 @@ namespace Content.Shared.Roles; [UsedImplicitly] [Serializable, NetSerializable] +[Obsolete("Use PlayerJobPlaytimeRequirement instead")] // DEN public sealed partial class RoleTimeRequirement : JobRequirement { /// diff --git a/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs b/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs index 68c069931f3..736247c5fa4 100644 --- a/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs @@ -14,6 +14,7 @@ namespace Content.Shared.Roles; /// [UsedImplicitly] [Serializable, NetSerializable] +[Obsolete("Use PlayerSpeciesRequirement instead")] // DEN public sealed partial class SpeciesRequirement : JobRequirement { [DataField(required: true)] diff --git a/Content.Shared/Roles/JobRequirement/TraitsRequirement.cs b/Content.Shared/Roles/JobRequirement/TraitsRequirement.cs index 3ebba44cf28..a1016c107ee 100644 --- a/Content.Shared/Roles/JobRequirement/TraitsRequirement.cs +++ b/Content.Shared/Roles/JobRequirement/TraitsRequirement.cs @@ -15,7 +15,7 @@ namespace Content.Shared.Roles; /// [UsedImplicitly] [Serializable, NetSerializable] -[Obsolete("Use EntityTraitsRequirement instead")] // DEN +[Obsolete("Use PlayerTraitRequirement instead")] // DEN public sealed partial class TraitsRequirement : JobRequirement { [DataField(required: true)] diff --git a/Content.Shared/Roles/JobRequirementOverridePrototype.cs b/Content.Shared/Roles/JobRequirementOverridePrototype.cs index d0ce649f360..9c43a63d64a 100644 --- a/Content.Shared/Roles/JobRequirementOverridePrototype.cs +++ b/Content.Shared/Roles/JobRequirementOverridePrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared._DEN.Requirements.PlayerRequirements; using Robust.Shared.Prototypes; namespace Content.Shared.Roles; @@ -13,8 +14,26 @@ public sealed partial class JobRequirementOverridePrototype : IPrototype public string ID { get; private set; } = default!; [DataField] + [Obsolete("Use JobRequirements instead")] // DEN public Dictionary, HashSet> Jobs = new (); [DataField] + [Obsolete("Use AntagRequirements instead")] // DEN public Dictionary, HashSet> Antags = new (); + + // Begin DEN: Use PlayerRequirements + + /// + /// A dictionary of job roles mapped to a list of overriding requirements. + /// + [DataField] + public Dictionary, List> JobRequirements = new(); + + /// + /// A dictionary of antagonist roles mapped to a list of overriding requirements. + /// + [DataField] + public Dictionary, List> AntagRequirements = new(); + + // End DEN } diff --git a/Content.Shared/Roles/JobRequirements.cs b/Content.Shared/Roles/JobRequirements.cs index 62d50f84890..488d7a2cc45 100644 --- a/Content.Shared/Roles/JobRequirements.cs +++ b/Content.Shared/Roles/JobRequirements.cs @@ -6,6 +6,7 @@ namespace Content.Shared.Roles; +[Obsolete("Use PlayerRequirements instead")] // DEN public static class JobRequirements { /// @@ -62,6 +63,7 @@ public static bool TryRequirementsMet( /// [ImplicitDataDefinitionForInheritors] [Serializable, NetSerializable] +[Obsolete("Use PlayerRequirements instead")] // DEN public abstract partial class JobRequirement { [DataField] diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 47db09ebdb3..29046f1e421 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -17,7 +17,7 @@ namespace Content.Shared.Roles; -public abstract class SharedRoleSystem : EntitySystem +public abstract partial class SharedRoleSystem : EntitySystem // DEN: Make partial { [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; @@ -672,6 +672,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent /// /// Returns the list of requirements for a role, or null. May be altered by requirement overrides. /// + [Obsolete("Use GetRolePlayerRequirements instead")] // DEN public HashSet? GetRoleRequirements(JobPrototype job) { if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req)) @@ -682,6 +683,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent // TODO ROLES Change to readonly? /// + [Obsolete("Use GetRolePlayerRequirements instead")] // DEN public HashSet? GetRoleRequirements(AntagPrototype antag) { if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req)) @@ -692,6 +694,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent // TODO ROLES Change to readonly? /// + [Obsolete("Use GetRolePlayerRequirements instead")] // DEN public HashSet? GetRoleRequirements(ProtoId jobId) { return _prototypes.TryIndex(jobId, out var job) ? GetRoleRequirements(job) : null; @@ -699,6 +702,7 @@ public void MindPlaySound(EntityUid mindId, SoundSpecifier? sound, MindComponent // TODO ROLES Change to readonly? /// + [Obsolete("Use GetRolePlayerRequirements instead")] // DEN public HashSet? GetRoleRequirements(ProtoId antagId) { return _prototypes.TryIndex(antagId, out var antag) ? GetRoleRequirements(antag) : null; diff --git a/Content.Shared/_DEN/Preferences/Loadouts/Effects/PlayerRequirementLoadoutEffect.cs b/Content.Shared/_DEN/Preferences/Loadouts/Effects/PlayerRequirementLoadoutEffect.cs new file mode 100644 index 00000000000..ef782f3bdbc --- /dev/null +++ b/Content.Shared/_DEN/Preferences/Loadouts/Effects/PlayerRequirementLoadoutEffect.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared._DEN.Requirements.Managers; +using Content.Shared._DEN.Requirements.PlayerRequirements; +using Content.Shared.Preferences; +using Content.Shared.Preferences.Loadouts; +using Content.Shared.Preferences.Loadouts.Effects; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Shared._DEN.Preferences.Loadouts.Effects; + +/// +/// Checks for a player requirement to be met. +/// +public sealed partial class PlayerRequirementLoadoutEffect : LoadoutEffect +{ + [DataField(required: true)] + public IPlayerRequirement Requirement = default!; + + public override bool Validate(HumanoidCharacterProfile profile, + RoleLoadout loadout, + ICommonSession? session, + IDependencyCollection collection, + [NotNullWhen(false)] out FormattedMessage? reason) + { + if (session == null + // Auto-pass playtime requirements if they're disabled + || Requirement is PlayerPlaytimeRequirement playtimeReq && playtimeReq.ShouldAutoPass()) + { + reason = FormattedMessage.Empty; + return true; + } + + var requirements = collection.Resolve(); + var context = requirements.GetPlayerContext(session); + context.Profile = profile; + var success = SharedPlayerRequirementManager.CheckRequirement(context, Requirement); + + if (!success) + { + var reasonText = Requirement.GetReason(context) ?? string.Empty; + reason = FormattedMessage.FromMarkupPermissive(reasonText); + return false; + } + + reason = null; + return true; + } +} diff --git a/Content.Shared/_DEN/Requirements/Managers/IPlayerRequirementManager.cs b/Content.Shared/_DEN/Requirements/Managers/IPlayerRequirementManager.cs new file mode 100644 index 00000000000..e0ac562b06d --- /dev/null +++ b/Content.Shared/_DEN/Requirements/Managers/IPlayerRequirementManager.cs @@ -0,0 +1,18 @@ +using Content.Shared._DEN.Requirements.PlayerRequirements; +using Robust.Shared.Player; + +namespace Content.Shared._DEN.Requirements.Managers; + +/// +/// A manager used to check player stats against a list of requirements, getting the pass/fail status of these requirements. +/// This can be used to apply restrictions to character actions, like jobs or traits. +/// +public interface IPlayerRequirementManager +{ + /// + /// Creates a new PlayerRequirementContext with context fields pre-filled. + /// + /// The session associated with this player. + /// A pre-filled requirement context for this player. + PlayerRequirementContext GetPlayerContext(ICommonSession session); +} diff --git a/Content.Shared/_DEN/Requirements/Managers/PlayerRequirementContext.cs b/Content.Shared/_DEN/Requirements/Managers/PlayerRequirementContext.cs new file mode 100644 index 00000000000..d2da99b62a1 --- /dev/null +++ b/Content.Shared/_DEN/Requirements/Managers/PlayerRequirementContext.cs @@ -0,0 +1,22 @@ +using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Preferences; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DEN.Requirements.Managers; + +/// +/// A record that represents a list of factors that will be checked against PlayerRequirements. +/// All fields are nullable. A null field represents an optional parameter. +/// +public partial record PlayerRequirementContext +{ + /// + /// The currently-selected character profile of the player we are going to check. + /// + public HumanoidCharacterProfile? Profile = null; + + /// + /// A dictionary of registered playtimes for the player. + /// + public IReadOnlyDictionary? Playtimes = null; +} diff --git a/Content.Shared/_DEN/Requirements/Managers/SharedPlayerRequirementManager.cs b/Content.Shared/_DEN/Requirements/Managers/SharedPlayerRequirementManager.cs new file mode 100644 index 00000000000..26175b955d0 --- /dev/null +++ b/Content.Shared/_DEN/Requirements/Managers/SharedPlayerRequirementManager.cs @@ -0,0 +1,111 @@ +using System.Text; +using Content.Shared._DEN.Requirements.PlayerRequirements; +using JetBrains.Annotations; +using Robust.Shared.Player; +using Robust.Shared.Utility; + +namespace Content.Shared._DEN.Requirements.Managers; + +/// +/// A manager used to check player stats against a list of requirements, getting the pass/fail status of these requirements. +/// This can be used to apply restrictions to character actions, like jobs or traits. +/// +public abstract partial class SharedPlayerRequirementManager : IPlayerRequirementManager +{ + /// + /// Check a context against enumerable requirements and gets the final pass/fail status of these requirements. + /// + /// The context containing fields to check against the requirements. + /// An enumerable collection of requirements. + /// Whether or not this context passes *all* requirements. If even one fails, then this is false. + [PublicAPI] + public static bool CheckRequirements(PlayerRequirementContext context, IEnumerable requirements) + { + foreach (var requirement in requirements) + if (!CheckRequirement(context, requirement)) + return false; + + return true; + } + + /// + /// Whether or not the item associated with a set of requirements should be hidden + /// from the player, such as in UI. + /// + /// The context containing fields to check against the requirements. + /// An enumerable collection of requirements. + /// Whether or not the item should be hidden from the player. + + [PublicAPI] + public static bool ShouldHide(PlayerRequirementContext context, IEnumerable requirements) + { + foreach (var requirement in requirements) + if (!CheckRequirement(context, requirement) && requirement.HideIfFailed) + return true; + + return false; + } + + /// + /// Check a single requirement for whether it passes/fails against a context. + /// + /// The context containing fields to check against the requirement. + /// The requirement to check. + /// Whether this context passes the requirement. + [PublicAPI] + public static bool CheckRequirement(PlayerRequirementContext context, IPlayerRequirement requirement) + { + // Pre-check the requirement. This ensures our context has all the fields needed for the requirement. + // If the pre-check fails, whether or not this requirement passes depends on requirement.MustPassPreCheck. + // If you don't need to pass the pre-check, then it's an auto-success. + if (!requirement.PreCheck(context)) + return !requirement.MustPassPreCheck; + + // Check the actual requirement, now. + if (!requirement.CheckRequirement(context)) + return false; + + return true; + } + + /// + /// Get a combined reason message for an enumerable collection of requirements. + /// + /// A collection of requirements. + /// A context to check against the requirements. + /// A formatted message containing all the requirement reasons. + [PublicAPI] + public static FormattedMessage GetCombinedReason(IEnumerable requirements, PlayerRequirementContext? context = null) + { + var messageBuilder = new StringBuilder(); + foreach (var req in requirements) + messageBuilder.AppendLine(req.GetReason(context)); + + var messageString = messageBuilder.ToString().Trim(); + return FormattedMessage.FromMarkupPermissive(messageString); + } + + /// + /// Get a combined reason message for an enumerable collection of requirements. + /// This function will only include requirements that fail, given a context. + /// + /// A context to check against the requirements. + /// A collection of requirements. + /// A formatted message containing all the requirement reasons. + [PublicAPI] + public static FormattedMessage GetFailedCombinedReason(PlayerRequirementContext context, IEnumerable requirements) + { + var messageBuilder = new StringBuilder(); + foreach (var req in requirements) + { + if (!CheckRequirement(context, req)) + messageBuilder.AppendLine(req.GetReason(context)); + } + + var messageString = messageBuilder.ToString().Trim(); + return FormattedMessage.FromMarkupPermissive(messageString); + } + + /// + public abstract PlayerRequirementContext GetPlayerContext(ICommonSession session); +} diff --git a/Content.Shared/_DEN/Requirements/PlayerRequirements/CountRequirement.cs b/Content.Shared/_DEN/Requirements/PlayerRequirements/CountRequirement.cs new file mode 100644 index 00000000000..7461d21f1b4 --- /dev/null +++ b/Content.Shared/_DEN/Requirements/PlayerRequirements/CountRequirement.cs @@ -0,0 +1,186 @@ +using System.Linq; +using Content.Shared._DEN.Requirements.Managers; +using JetBrains.Annotations; + +namespace Content.Shared._DEN.Requirements.PlayerRequirements; + +/// +/// An abstract class for requirements to determine how many items in a set should be selected. +/// +/// +/// For example: A player selecting multiple traits that overlap with a certain required group of traits. +/// +[ImplicitDataDefinitionForInheritors] +[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)] +public abstract partial class CountRequirement +{ + /// + /// Gets a string reason representation for this range. + /// + /// + /// This would slot into the sentence "Must have [reason] of the following items: [items]". + /// + /// "At least 1", "between 2 and 5", "all" + /// An optional context used to add additional information to the reason. + /// A string representation of this range's requirement bounds. + public abstract string GetReason(); + + /// + /// Check if our currently-selected items in a collection meets this + /// requirement, against a collection of required items. + /// + /// The type of the collection. + /// The items we have selected. + /// The items required for this condition. + /// Whether or not we fulfill this requirement. + public abstract bool CheckRequirement(IEnumerable have, IEnumerable required); + + /// + /// Get how many of the required items in a collection we currently have. + /// + /// The type of the collection. + /// The items we have selected. + /// The items required for this condition. + /// How many of the required items we have. + protected static int GetFulfilledCount(IEnumerable have, IEnumerable required) + { + return have.Intersect(required).Count(); + } +} + +/// +/// To fulfill this requirement, you must have exactly some number of items. +/// +public sealed partial class ConstantCountRequirement : CountRequirement +{ + /// + /// How many items in the collection you need to pass the requirement. + /// + [DataField] + public int Count; + + /// + public override string GetReason() + { + // "Must have exactly 1 of the following items." + return Loc.GetString("count-requirement-constant-reason", + ("count", Count)); + } + + /// + public override bool CheckRequirement(IEnumerable have, IEnumerable required) + { + var count = GetFulfilledCount(have, required); + return count == Count; + } +} + +/// +/// To fulfill this requirement, you must have a number of items between two values, or either a minimum / maximum. +/// +public sealed partial class RangeCountRequirement : CountRequirement, IPlayerRangeRequirement +{ + /// + /// Minimum amount of required items you need. Null means no minimum constraint. + /// + [DataField] + public int? Min { get; set; } = null; + + /// + /// Maximum amount of required items you can have. Null means no maximum constraint. + /// + [DataField] + public int? Max { get; set; } = null; + + /// + public override string GetReason() + { + if (this is IPlayerRangeRequirement range) + return range.GetRangeConstraintReason(); + + return string.Empty; + } + + /// + public override bool CheckRequirement(IEnumerable have, IEnumerable required) + { + var count = GetFulfilledCount(have, required); + + if (this is IPlayerRangeRequirement range) + return range.IsInRange(count); + + return false; + } + + /// + public string FormatValue(int value) + { + return Loc.GetString("player-requirement-format-number", + ("number", value.ToString())); + } + + /// + public int? GetDifference(int value) + { + // Unimplemented - unused + throw new NotImplementedException(); + } + + /// + public int Sign(int difference) + { + // Unimplemented - unused + throw new NotImplementedException(); + } + + /// + public string FormatDifferenceText(int difference) + { + // Unimplemented - unused + throw new NotImplementedException(); + } +} + +/// +/// To fulfill the requirement, you must have at least one item in the required collection. +/// +/// +/// This is similar to , with a Min of 1, but it says "any" in the reason instead. +/// This sounds smoother for inverted requirements; i.e. "Must not have any of the following items." +/// +public sealed partial class AnyCountRequirement : CountRequirement +{ + /// + public override string GetReason() + { + // "Must have any of the following items." + return Loc.GetString("count-requirement-any-reason"); + } + + /// + public override bool CheckRequirement(IEnumerable have, IEnumerable required) + { + var count = GetFulfilledCount(have, required); + return count >= 1; + } +} + +/// +/// To fulfill the requirement, you must have all items in the required collection. +/// +public sealed partial class AllCountRequirement : CountRequirement +{ + /// + public override string GetReason() + { + // "Must have all of the following items." + return Loc.GetString("count-requirement-all-reason"); + } + + /// + public override bool CheckRequirement(IEnumerable have, IEnumerable required) + { + var count = GetFulfilledCount(have, required); + return count == required.Count(); + } +} diff --git a/Content.Shared/_DEN/Requirements/PlayerRequirements/IPlayerRequirement.cs b/Content.Shared/_DEN/Requirements/PlayerRequirements/IPlayerRequirement.cs new file mode 100644 index 00000000000..01b83c44c28 --- /dev/null +++ b/Content.Shared/_DEN/Requirements/PlayerRequirements/IPlayerRequirement.cs @@ -0,0 +1,251 @@ +using Content.Shared._DEN.Requirements.Managers; + +namespace Content.Shared._DEN.Requirements.PlayerRequirements; + +/// +/// This interface is used to check some parameters associated with a player (a +/// ) to determine whether or not the player is allowed to +/// use a certain feature. +/// +/// +/// This is a replacement of the old JobRequirement system. +/// +[ImplicitDataDefinitionForInheritors] +public partial interface IPlayerRequirement +{ + /// + /// Whether or not this requirement is inverted. + /// In all cases where this requirement should fail, it will succeed instead, and vice versa. + /// + [DataField] + bool Inverted { get; set; } + + /// + /// Requirements undergo a "PreCheck" to check if the context value has all relevant parameters. + /// If this is false, then the requirement auto-passes the requirement if the pre-check fails. + /// + /// + /// Say you have a requirement that checks your playtimes. In the pre-check, we check if the + /// context's playtimes are non-null. If your playtimes are null, we fail the pre-check. + /// + /// If MustPassPreCheck is false, then this will treat it as auto-passing the requirement; + /// we ignore the requirement entirely. If MustPassPreCheck is true, we auto-fail the + /// requirement instead. + /// + [DataField] + bool MustPassPreCheck { get; set; } + + /// + /// Whether or not the item should be hidden from the UI if this requirement fails. + /// + [DataField] + bool HideIfFailed { get; set; } + + /// + /// Check if the given context has all parameters required in order to perform an actual check. + /// + /// + /// Because context parameters are meant to be optional, we can use this information to ignore + /// (auto-succeed) checks where we lack all the needded parameters, depending on the value of + /// . + /// + /// A definition of parameters to check against the requirement. + /// Whether or not the context has all required parameters. + bool PreCheck(PlayerRequirementContext context); + + /// + /// Check a context's fields against this requirement to determine if it passes or fails. + /// + /// A definition of parameters to check against the requirement. + /// Whether or not the given context passes this requirement. + bool CheckRequirement(PlayerRequirementContext context); + + /// + /// Get a localized string representing how this requirement displays to players. + /// + /// + /// This should display differently depending on the value of . + /// + /// An optional context used to add additional information to the reason. + /// An optional string representing the requirement text to display to a player. + string? GetReason(PlayerRequirementContext? context = null); +} + +/// +/// Abstract class inherited by other player requirements. +/// +public abstract partial class PlayerRequirement : IPlayerRequirement +{ + /// + [DataField] public bool Inverted { get; set; } = false; + + /// + [DataField] public bool MustPassPreCheck { get; set; } = false; + + /// + [DataField] public bool HideIfFailed { get; set; } = false; + + /// + public abstract bool CheckRequirement(PlayerRequirementContext context); + + /// + public abstract string? GetReason(PlayerRequirementContext? context = null); + + /// + public abstract bool PreCheck(PlayerRequirementContext context); +} + +/// +/// An interface representing a requirement where the value is between a given minimum +/// and/or maximum range. +/// +/// The value of the range to use. +public interface IPlayerRangeRequirement where T : struct, IComparable +{ + /// + /// The optional minimum value of this requirement to pass. + /// + T? Min { get; set; } + + /// + /// The optional maximum value of this requirement to pass. + /// + T? Max { get; set; } + + /// + /// Check if a value is within range. + /// + /// The value to check. + /// Whether this value is in range or not. + bool IsInRange(T value) + { + // Value is less than minimum + if (Min != null && value.CompareTo(Min) < 0) + return false; + + // Value is greater than maximum + if (Max != null && value.CompareTo(Max) > 0) + return false; + + return true; + } + + /// + /// Get a reason message to display to the player for this requirement's allowed value range. + /// + string GetRangeConstraintReason(PlayerRequirementContext? context = null) + { + var minText = GetMinText(); + var maxText = GetMaxText(); + + return (minText, maxText) switch + { + // "Must have between 1 and 5 of the following items." + (not null, not null) => Loc.GetString("player-requirement-range-minmax-reason", + ("minimum", minText), + ("maximum", maxText)), + + // "Must have at most 5 of the following items." + (null, not null) => Loc.GetString("player-requirement-range-maximum-reason", + ("maximum", maxText)), + + // "Must have at least 1 of the following items." + (not null, null) => Loc.GetString("player-requirement-range-minimum-reason", + ("minimum", minText)), + + _ => string.Empty + }; + } + + /// + /// Get the difference between this value and the maximum or minimum value of this range. + /// Should return null if the value is in range. + /// + /// The value to get a difference of. + /// The difference between the value and the nearest bound of this range. + T? GetDifference(T value); + + /// + /// Get the "sign" associated with this difference. + /// + /// + /// "1" means the difference is lower than the minimum. + /// "-1" means the difference is higher than the maximum. + /// "0" means the difference is within range. + /// + /// The difference value. + /// The sign of this difference. + int Sign(T difference); + + /// + /// Format this difference value as a string to display to the player. + /// + /// The difference value. + /// A string representation of this difference. + string FormatDifferenceText(T difference); + + /// + /// Format a "difference" value depending on if it is higher than the maximum or lower than the minimum. + /// + /// The difference value to format. + /// A string representation of this difference. + string FormatDifference(T difference) + { + var diffText = FormatDifferenceText(difference); + return Sign(difference) switch + { + 1 => Loc.GetString("player-requirement-range-difference-lower", ("count", diffText)), + -1 => Loc.GetString("player-requirement-range-difference-higher", ("count", diffText)), + _ => "0", + }; + } + + /// + /// Get a string representation between this value and its distance from the nearest bound. + /// Returns null if the value is in range. + /// + /// A value being tested against the requirement. + /// A string representation of the difference between the value and the requirement's bounds. + string? GetDifferenceReason(T value) + { + var difference = GetDifference(value); + if (difference == null) + return null; + + var diffValue = FormatDifference(difference.Value) ?? difference.Value.ToString(); + if (diffValue == null) + return null; + + return Loc.GetString("player-requirement-range-difference", + ("difference", diffValue)); + } + + /// + /// Format a value of the range type into a string. + /// + /// The value to format. + /// A string representation of this value. + string FormatValue(T value); + + /// + /// Get a string representation of this range's minimum value. + /// + string? GetMinText() + { + if (Min == null) + return null; + + return FormatValue(Min.Value); + } + + /// + /// Get a string representation of this range's maximum value. + /// + string? GetMaxText() + { + if (Max == null) + return null; + + return FormatValue(Max.Value); + } +} diff --git a/Content.Shared/_DEN/Requirements/PlayerRequirements/PlayerRequirements.Playtime.cs b/Content.Shared/_DEN/Requirements/PlayerRequirements/PlayerRequirements.Playtime.cs new file mode 100644 index 00000000000..0dc1326ae54 --- /dev/null +++ b/Content.Shared/_DEN/Requirements/PlayerRequirements/PlayerRequirements.Playtime.cs @@ -0,0 +1,433 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Shared._DEN.Requirements.Managers; +using Content.Shared.CCVar; +using Content.Shared.Localizations; +using Content.Shared.Players.PlayTimeTracking; +using Content.Shared.Roles; +using Content.Shared.Roles.Jobs; +using Robust.Shared.Configuration; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DEN.Requirements.PlayerRequirements; + +/// +/// An abstract class for playtime requirements that expect a playtime to be within +/// optional minimum and maximum parameters. +/// +public abstract partial class PlayerPlaytimeRequirement : PlayerRequirement, IPlayerRangeRequirement +{ + /// + /// The minimum time you can have in this tracker. + /// + [DataField("minTime")] + public TimeSpan? Min { get; set; } = null; + + /// + /// The maximum time you can have in this tracker. + /// + [DataField("maxTime")] + public TimeSpan? Max { get; set; } = null; + + /// + /// The "type" of playtime requirement this is. + /// This affects what CVAR is used to turn the timer off. + /// + [DataField] + public PlaytimeRequirementType RequirementType = PlaytimeRequirementType.Role; + + /// + public override bool PreCheck(PlayerRequirementContext context) + { + // We are always returning "true" if ShouldAutoPass() is true, because otherwise, if the + // pre-check failed, then it would be possible to fail this requirement as per + // PlayerRequirement.MustPassPreCheck even when role timers should be ignored anyway. + return ShouldAutoPass() || context.Playtimes != null; + } + + /// + public override bool CheckRequirement(PlayerRequirementContext context) + { + // Auto-pass if role timers are disabled. + if (ShouldAutoPass()) + return true; + + var playtime = GetPlaytime(context); + if (playtime is null) + return false; + + return IsInRange(playtime.Value); + } + + /// + public override string? GetReason(PlayerRequirementContext? context = null) + { + // Do not give a reason if role timers are disabled. + if (ShouldAutoPass()) + return null; + + // Get the playtime constraint string. + if (!TryGetRangeConstraintReason(out var constraintReason)) + return null; + + // If there's no "difference reason", then just the constraint is fine. + // "Must have 2h of playtime overall." + if (!TryGetRangeDifferenceReason(out var differenceReason, context)) + return constraintReason; + + // "Must have 2h of playtime overall. (Need 1h more)" + return Loc.GetString("player-requirement-range-with-difference", + ("range", constraintReason), + ("difference", differenceReason)); + } + + /// + /// Whether or not this requirement should auto-pass. This applies if role timers + /// are disabled, because playtimes shouldn't matter anyway in this case - we shouldn't + /// fail playtime requirements ever when role timers are disabled. + /// + /// + /// Whether or not this requirement should auto-pass. + /// + public bool ShouldAutoPass() + { + var config = IoCManager.Resolve(); + var timerEnabled = RequirementType switch + { + PlaytimeRequirementType.Role => config.GetCVar(CCVars.GameRoleTimers), + PlaytimeRequirementType.Loadout => config.GetCVar(CCVars.GameRoleLoadoutTimers), + _ => throw new ArgumentOutOfRangeException(nameof(RequirementType)), + }; + + return !timerEnabled; + } + + /// + /// Format a playtime TimeSpan into text to display to the player. + /// + /// The playtime to format. + /// The formatted playtime, if playtime is not null. + private static string FormatPlaytime(TimeSpan playtime) + { + var playtimeString = ContentLocalizationManager.FormatPlaytime(playtime); + return Loc.GetString("player-requirement-format-time", + ("playtime", playtimeString)); + } + + /// + /// Check if the given playtime is in range. + /// + /// The playtime to check. + /// Whether or not the playtime is in range. + protected bool IsInRange(TimeSpan playtime) + { + if (this is IPlayerRangeRequirement range) + return range.IsInRange(playtime); + + return false; + } + + /// + public string FormatValue(TimeSpan value) + { + return FormatPlaytime(value); + } + + /// + public TimeSpan? GetDifference(TimeSpan value) + { + if (this is IPlayerRangeRequirement range && range.IsInRange(value)) + return null; + + // Negative value = greater than maximum + if (Max != null && value > Max) + return Max - value; + + // Positive value = less than minimum + if (Min != null && value < Min) + return Min - value; + + return null; + } + + /// + public int Sign(TimeSpan difference) + { + return Math.Sign(difference.TotalSeconds); + } + + /// + public string FormatDifferenceText(TimeSpan difference) + { + var diffValue = difference.Duration(); + return FormatValue(diffValue); + } + + /// + /// Get the text to display to the player that represents the range of valid playtimes. + /// + /// The playtime range description. + /// Whether or not this operation was successful. + protected virtual bool TryGetRangeConstraintReason([NotNullWhen(true)] out string? playtimeString) + { + playtimeString = null; + + if (this is IPlayerRangeRequirement range) + { + var constraintReason = range.GetRangeConstraintReason(); + playtimeString = Loc.GetString("player-requirement-playtime-constraint-reason", + ("inverted", Inverted), + ("constraint", constraintReason)); + } + + return playtimeString != null; + } + + /// + /// Get the text to display to the player that represents the difference between their + /// playtime and the upper/lower bound of acceptable playtimes. + /// + /// A string representing the player's playtime in relation to the requirement's bounds. + /// A context that may contain this character's playtimes. + /// Whether or not this operation was successful. + protected virtual bool TryGetRangeDifferenceReason([NotNullWhen(true)] out string? reason, + PlayerRequirementContext? context = null) + { + reason = null; + + // TODO DEN: I'm just excluding this if it's inverted, + // because otherwise it can get really confusing. You probably + // should not be using inverted playtime requirements anyway. + if (context?.Profile == null || Inverted) + return false; + + if (this is IPlayerRangeRequirement range) + { + var playtime = GetPlaytime(context); + if (playtime != null) + reason = range.GetDifferenceReason(playtime.Value); + } + + return reason != null; + } + + /// + /// Get the playtime associated with this requirement. + /// + /// The context that may or may not contain playtimes. + /// The playtime associated with this requirement. + protected abstract TimeSpan? GetPlaytime(PlayerRequirementContext context); +} + +[Serializable] +public enum PlaytimeRequirementType +{ + Role, + Loadout +} + +/// +/// Checks if a player's total playtime in a given department fits within a given playtime range. +/// +public sealed partial class PlayerDepartmentPlaytimeRequirement : PlayerPlaytimeRequirement +{ + /// + /// The department we should check against the requirement. + /// + [DataField(required: true)] + public ProtoId Department = default!; + + /// + protected override TimeSpan? GetPlaytime(PlayerRequirementContext context) + { + return GetDepartmentPlaytime(context); + } + + /// + protected override bool TryGetRangeConstraintReason([NotNullWhen(true)] out string? playtimeString) + { + if (!base.TryGetRangeConstraintReason(out playtimeString)) + return false; + + var protoMan = IoCManager.Resolve(); + var deptName = FormatDepartment(protoMan); + + // E.g. "You must have 2h30m of playtime in the Science department." + playtimeString = Loc.GetString("player-requirement-department-playtime-reason", + ("constraint", playtimeString), + ("department", deptName)); + + return true; + } + + /// + /// Format this requirement's department name, with a color. + /// + /// The prototype manager. + /// The department name of this prototype, formatted. + private string FormatDepartment(IPrototypeManager protoMan) + { + if (!protoMan.Resolve(Department, out var department)) + return Department; + + var deptName = Loc.GetString(department.Name); + var deptColor = department.Color.ToHex(); + var formattedDept = Loc.GetString("player-requirement-format-department", + ("color", deptColor), + ("department", deptName)); + + return formattedDept; + } + + /// + /// Get the total playtime for this department. + /// + /// A definition of parameters to check against the requirement. + /// The total playtime of this department. Null if either context playtimes or department is invalid. + private TimeSpan? GetDepartmentPlaytime(PlayerRequirementContext context) + { + var protoMan = IoCManager.Resolve(); + var playtime = TimeSpan.Zero; + + if (context.Playtimes == null + || !protoMan.Resolve(Department, out var department)) + return null; + + // Sum the playtimes of all roles in this department. + foreach (var roleId in department.Roles) + { + if (!protoMan.TryIndex(roleId, out var role)) + continue; + + if (!context.Playtimes.TryGetValue(role.PlayTimeTracker, out var roleTime)) + continue; + + playtime += roleTime; + } + + return playtime; + } +} + +/// +/// Checks if a player's total playtime in a given job fits within a given playtime range. +/// +public sealed partial class PlayerJobPlaytimeRequirement : PlayerPlaytimeRequirement +{ + /// + /// The job we should check against the requirement. + /// + [DataField(required: true)] + public ProtoId Job = default!; + + /// + protected override TimeSpan? GetPlaytime(PlayerRequirementContext context) + { + return GetJobPlaytime(context); + } + + /// + protected override bool TryGetRangeConstraintReason([NotNullWhen(true)] out string? playtimeString) + { + if (!base.TryGetRangeConstraintReason(out playtimeString)) + return false; + + var protoMan = IoCManager.Resolve(); + var jobName = FormatJob(protoMan); + + // E.g. "You must have 2h30m of playtime as a Mime." + playtimeString = Loc.GetString("player-requirement-job-playtime-reason", + ("constraint", playtimeString), + ("job", jobName)); + + return true; + } + + /// + /// Format this requirement's job name, with a color. + /// + /// The prototype manager. + /// The department name of this prototype, formatted. + private string FormatJob(IPrototypeManager protoMan) + { + if (!protoMan.Resolve(Job, out var job)) + return Job; + + var jobName = Loc.GetString(job.Name); + + // Gotta use the department to recolor this role's name. + var entMan = IoCManager.Resolve(); + var jobSystem = entMan.System(); + var deptColor = Color.LightGray.ToHex(); + if (jobSystem.TryGetPrimaryDepartment(Job, out var dept) || jobSystem.TryGetDepartment(Job, out dept)) + deptColor = dept.Color.ToHex(); + + var formattedJob = Loc.GetString("player-requirement-format-job", + ("color", deptColor), + ("job", jobName)); + + return formattedJob; + } + + /// + /// Get the total playtime for this job. + /// + /// A definition of parameters to check against the requirement. + /// The total playtime of this job. Null if either context playtimes or job is invalid. + private TimeSpan? GetJobPlaytime(PlayerRequirementContext context) + { + var protoMan = IoCManager.Resolve(); + var playtime = TimeSpan.Zero; + + if (context.Playtimes == null || !protoMan.Resolve(Job, out var job)) + return null; + + if (context.Playtimes.TryGetValue(job.PlayTimeTracker, out var tracker)) + playtime = tracker; + + return playtime; + } +} + +/// +/// Checks if a player's total overall playtime fits within a given playtime range. +/// +public sealed partial class PlayerOverallPlaytimeRequirement : PlayerPlaytimeRequirement +{ + /// + protected override TimeSpan? GetPlaytime(PlayerRequirementContext context) + { + return GetOverallPlaytime(context); + } + + /// + protected override bool TryGetRangeConstraintReason([NotNullWhen(true)] out string? playtimeString) + { + if (!base.TryGetRangeConstraintReason(out playtimeString)) + return false; + + // E.g. "You must have 300h of playtime overall." + playtimeString = Loc.GetString("player-requirement-overall-playtime-reason", + ("constraint", playtimeString)); + + return true; + } + + /// + /// Get the overall playtime for this context. + /// + /// The context being used for checking this requirement. + /// The player's overall playtime. + private static TimeSpan? GetOverallPlaytime(PlayerRequirementContext context) + { + var overallTracker = PlayTimeTrackingShared.TrackerOverall; + var playtime = TimeSpan.Zero; + + if (context.Playtimes == null) + return null; + + if (context.Playtimes.TryGetValue(overallTracker, out var tracker)) + playtime = tracker; + + return playtime; + } +} diff --git a/Content.Shared/_DEN/Requirements/PlayerRequirements/PlayerRequirements.Profile.cs b/Content.Shared/_DEN/Requirements/PlayerRequirements/PlayerRequirements.Profile.cs new file mode 100644 index 00000000000..d388826c077 --- /dev/null +++ b/Content.Shared/_DEN/Requirements/PlayerRequirements/PlayerRequirements.Profile.cs @@ -0,0 +1,262 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Shared._DEN.Requirements.Managers; +using Content.Shared._DEN.Traits.Prototypes; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Prototypes; + +namespace Content.Shared._DEN.Requirements.PlayerRequirements; + +/// +/// Checks if a player's character is within the given age range. +/// +public sealed partial class PlayerAgeRequirement : PlayerRequirement, IPlayerRangeRequirement +{ + /// + /// Minimum age of the character to pass the requirement. + /// + [DataField] + public int? Min { get; set; } = null; + + /// + /// Maximum age of the character to pass the requirement. + /// + [DataField] + public int? Max { get; set; } = null; + + /// + public override bool PreCheck(PlayerRequirementContext context) + { + return context.Profile != null; + } + + /// + public override bool CheckRequirement(PlayerRequirementContext context) + { + if (context.Profile == null) + return false; + + if (this is IPlayerRangeRequirement range) + return range.IsInRange(context.Profile.Age); + + return false; + } + + /// + public override string? GetReason(PlayerRequirementContext? context = null) + { + if (!TryGetRangeConstraintReason(out var constraintReason)) + return null; + + // "Must be between 20 and 40 years old." + var rangeReason = Loc.GetString("player-requirement-age-reason", + ("inverted", Inverted), + ("constraint", constraintReason)); + + if (!TryGetRangeDifferenceReason(out var differenceReason, context)) + return rangeReason; + + return Loc.GetString("player-requirement-range-with-difference", + ("range", rangeReason), + ("difference", differenceReason)); + } + + /// + /// Get the text to display to the player that represents the range of valid playtimes. + /// + /// The age range description. + /// Whether or not this operation was successful. + private bool TryGetRangeConstraintReason([NotNullWhen(true)] out string? reason) + { + reason = null; + + if (this is IPlayerRangeRequirement range) + reason = range.GetRangeConstraintReason(); + + return reason != null; + } + + /// + /// Get the text to display to the player that represents the difference between their + /// character's age and the upper/lower bound of acceptable ages. + /// + /// A string representing this character's age in relation to the requirement's bounds. + /// A context that may contain this character's profile. + /// Whether or not this operation was successful. + private bool TryGetRangeDifferenceReason([NotNullWhen(true)] out string? reason, + PlayerRequirementContext? context = null) + { + reason = null; + + // TODO DEN: I'm just excluding this if it's inverted, + // because otherwise it can get really confusing. You probably + // should not be using inverted age requirements anyway. + if (context?.Profile == null || Inverted) + return false; + + var age = context.Profile.Age; + if (this is IPlayerRangeRequirement range) + reason = range.GetDifferenceReason(age); + + return reason != null; + } + + /// + public string FormatValue(int value) + { + return Loc.GetString("player-requirement-format-number", + ("number", value.ToString())); + } + + /// + public int? GetDifference(int value) + { + if (this is IPlayerRangeRequirement range && range.IsInRange(value)) + return null; + + // Negative value = greater than maximum + if (Max != null && value > Max) + return Max - value; + + // Positive value = less than minimum + if (Min != null && value < Min) + return Min - value; + + return null; + } + + /// + public int Sign(int difference) + { + return Math.Sign(difference); + } + + /// + public string FormatDifferenceText(int difference) + { + var diffValue = Math.Abs(difference); + return FormatValue(diffValue); + } +} + +/// +/// Checks if a player's character is one of the following species. +/// +public sealed partial class PlayerSpeciesRequirement : PlayerRequirement +{ + /// + /// Character's profile must be one of these species to pass the requirement. + /// + [DataField] + public HashSet> Species = new(); + + /// + public override bool PreCheck(PlayerRequirementContext context) + { + return context.Profile != null; + } + + /// + public override bool CheckRequirement(PlayerRequirementContext context) + { + if (context.Profile == null) + return false; + + return Species.Contains(context.Profile.Species); + } + + /// + public override string? GetReason(PlayerRequirementContext? context = null) + { + var protoMan = IoCManager.Resolve(); + + // Add all species names to a list + var speciesList = new List(); + foreach (var speciesId in Species) + speciesList.Add(LocalizeSpecies(speciesId, protoMan)); + + var joinedSpecies = string.Join(", ", speciesList); + + // "Must be one of these species: Human, Dwarf, Slimeperson" + return Loc.GetString("player-requirement-species-reason", + ("inverted", Inverted), + ("species", joinedSpecies)); + } + + /// + /// Localizes a species ID into a formatted species name. + /// + /// The ID of the species. + /// The prototype manager. + /// A formatted species name string. + private static string LocalizeSpecies(ProtoId speciesId, IPrototypeManager protoMan) + { + var speciesName = speciesId; + if (!protoMan.Resolve(speciesId, out var species)) + return speciesName; + + speciesName = Loc.GetString(species.Name); + return Loc.GetString("player-requirement-format-species", ("species", speciesName)); + } +} + +/// +/// Checks if a player's character has a required number of the given traits. +/// +public sealed partial class PlayerTraitRequirement : PlayerRequirement +{ + /// + /// Traits that the character needs to have to the pass the requirement. + /// + [DataField] + public HashSet> Traits = new(); + + [DataField] + public CountRequirement Count; + + /// + public override bool PreCheck(PlayerRequirementContext context) + { + return context.Profile != null; + } + + /// + public override bool CheckRequirement(PlayerRequirementContext context) + { + if (context.Profile == null) + return false; + + var profileTraits = context.Profile.EntityTraitPreferences; + return Count.CheckRequirement(profileTraits, Traits); + } + + /// + public override string? GetReason(PlayerRequirementContext? context = null) + { + var protoMan = IoCManager.Resolve(); + var traitNames = Traits.Select(t => LocalizeTrait(t, protoMan)); + var traitList = string.Join(", ", traitNames); + var constraintReason = Count.GetReason(); + + return Loc.GetString("player-requirement-trait-reason", + ("inverted", Inverted), + ("constraint", constraintReason), + ("traits", traitList)); + } + + /// + /// Localizes a trait ID into a formatted trait name. + /// + /// The ID of the trait. + /// The prototype manager. + /// A formatted trait name string. + private static string LocalizeTrait(ProtoId traitId, IPrototypeManager protoMan) + { + var traitName = traitId; + + if (protoMan.Resolve(traitId, out var trait)) + traitName = Loc.GetString(trait.Name); + + return Loc.GetString("player-requirement-format-trait", ("trait", traitName)); + } +} diff --git a/Content.Shared/_DEN/Roles/JobRequirement/EntityTraitsRequirement.cs b/Content.Shared/_DEN/Roles/JobRequirement/EntityTraitsRequirement.cs index ad813ecd44b..965db853790 100644 --- a/Content.Shared/_DEN/Roles/JobRequirement/EntityTraitsRequirement.cs +++ b/Content.Shared/_DEN/Roles/JobRequirement/EntityTraitsRequirement.cs @@ -15,6 +15,7 @@ namespace Content.Shared._DEN.Roles; /// [UsedImplicitly] [Serializable, NetSerializable] +[Obsolete("Use PlayerTraitRequirement instead")] public sealed partial class EntityTraitsRequirement : JobRequirement { [DataField(required: true)] diff --git a/Content.Shared/_DEN/Roles/SharedRoleSystem.cs b/Content.Shared/_DEN/Roles/SharedRoleSystem.cs new file mode 100644 index 00000000000..2f1cee4f74c --- /dev/null +++ b/Content.Shared/_DEN/Roles/SharedRoleSystem.cs @@ -0,0 +1,80 @@ +using Content.Shared._DEN.Requirements.PlayerRequirements; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Roles; + +public abstract partial class SharedRoleSystem +{ + /// + /// Get a list of requirements associated with a given job. + /// + /// + /// If a role requirement override is set for this job, the requirements associated + /// with the job in the override will be provided instead. + /// + /// The job to get player requirements for. + /// The job's actual requirements, accounting for the requirement CVar. + [PublicAPI] + public List? GetRolePlayerRequirements(JobPrototype job) + { + if (_requirementOverride != null + && _requirementOverride.JobRequirements.TryGetValue(job.ID, out var overrides)) + return overrides; + + return job.PlayerRequirements; + } + + /// + /// Get a list of requirements associated with a given antagonist role. + /// + /// + /// If a role requirement override is set for this role, the requirements associated + /// with the role in the override will be provided instead. + /// + /// The antagonist role to get player requirements for. + /// The role's actual requirements, accounting for the requirement CVar. + [PublicAPI] + public List? GetRolePlayerRequirements(AntagPrototype antag) + { + if (_requirementOverride != null + && _requirementOverride.AntagRequirements.TryGetValue(antag.ID, out var overrides)) + return overrides; + + return antag.PlayerRequirements; + } + + /// + /// Get a list of requirements associated with a given job. + /// + /// + /// If a role requirement override is set for this job, the requirements associated + /// with the job in the override will be provided instead. + /// + /// The ID of a job to get player requirements for. + /// The job's actual requirements, accounting for the requirement CVar. + [PublicAPI] + public List? GetRolePlayerRequirements(ProtoId jobId) + { + return _prototypes.TryIndex(jobId, out var job) + ? GetRolePlayerRequirements(job) + : null; + } + + /// + /// Get a list of requirements associated with a given antagonist role. + /// + /// + /// If a role requirement override is set for this role, the requirements associated + /// with the role in the override will be provided instead. + /// + /// The Id of an antagonist role to get player requirements for. + /// The role's actual requirements, accounting for the requirement CVar. + [PublicAPI] + public List? GetRolePlayerRequirements(ProtoId antagId) + { + return _prototypes.TryIndex(antagId, out var antag) + ? GetRolePlayerRequirements(antag) + : null; + } +} diff --git a/Content.Shared/_DEN/Traits/Prototypes/EntityTraitPrototype.cs b/Content.Shared/_DEN/Traits/Prototypes/EntityTraitPrototype.cs index 70e6fc7dde3..9553ca6a4eb 100644 --- a/Content.Shared/_DEN/Traits/Prototypes/EntityTraitPrototype.cs +++ b/Content.Shared/_DEN/Traits/Prototypes/EntityTraitPrototype.cs @@ -1,3 +1,4 @@ +using Content.Shared._DEN.Requirements.PlayerRequirements; using Content.Shared._DEN.Traits.TraitFunctions; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Traits; @@ -51,13 +52,6 @@ public sealed partial class EntityTraitPrototype : IPrototype [DataField] public ProtoId? Category; - /// - /// A list of species that allowed to take this trait. If null, then all species may take it. - /// TODO: Replace with a more robust "requirement" system. - /// - [DataField] - public HashSet>? AllowedSpecies = null; - /// /// A list of functions associated with this trait. /// @@ -69,4 +63,10 @@ public sealed partial class EntityTraitPrototype : IPrototype /// [DataField("characterEditorSelectable")] public bool Selectable = true; + + /// + /// A list of requirements to use this trait. + /// + [DataField] + public List Requirements = new(); } diff --git a/Resources/Locale/en-US/_DEN/requirements/formatted-fields.ftl b/Resources/Locale/en-US/_DEN/requirements/formatted-fields.ftl new file mode 100644 index 00000000000..388bfd1cf31 --- /dev/null +++ b/Resources/Locale/en-US/_DEN/requirements/formatted-fields.ftl @@ -0,0 +1,6 @@ +player-requirement-format-department = [color={$color}]{$department}[/color] +player-requirement-format-job = [color={$color}]{$job}[/color] +player-requirement-format-number = [color=White]{$number}[/color] +player-requirement-format-species = [color=Green]{$species}[/color] +player-requirement-format-time = [color=Yellow]{$playtime}[/color] +player-requirement-format-trait = [color=LightBlue]{$trait}[/color] diff --git a/Resources/Locale/en-US/_DEN/requirements/playtime-requirements.ftl b/Resources/Locale/en-US/_DEN/requirements/playtime-requirements.ftl new file mode 100644 index 00000000000..fa56fcf911c --- /dev/null +++ b/Resources/Locale/en-US/_DEN/requirements/playtime-requirements.ftl @@ -0,0 +1,11 @@ +player-requirement-playtime-minimum-time = at least {$playtime} +player-requirement-playtime-maximum-time = less than {$playtime} +player-requirement-playtime-minmax-time = between {$minimum} and {$maximum} +player-requirement-playtime-constraint-reason = Must{$inverted -> + [true] {" "}not + *[false] {""} +} have {$constraint} of playtime + +player-requirement-department-playtime-reason = {$constraint} in the {$department} department. +player-requirement-job-playtime-reason = {$constraint} as a {$job}. +player-requirement-overall-playtime-reason = {$constraint} overall. diff --git a/Resources/Locale/en-US/_DEN/requirements/profile-requirements.ftl b/Resources/Locale/en-US/_DEN/requirements/profile-requirements.ftl new file mode 100644 index 00000000000..f2799cb22ca --- /dev/null +++ b/Resources/Locale/en-US/_DEN/requirements/profile-requirements.ftl @@ -0,0 +1,14 @@ +player-requirement-trait-reason = Must{$inverted -> + [true] {" "}not + *[false] {""} +} have {$constraint} of these traits: {$traits} + +player-requirement-species-reason = Must{$inverted -> + [true] {" "}not + *[false] {""} +} be one of these species: {$species} + +player-requirement-age-reason = Must{$inverted -> + [true] {" "}not + *[false] {""} +} be {$constraint} years old. diff --git a/Resources/Locale/en-US/_DEN/requirements/requirement-range.ftl b/Resources/Locale/en-US/_DEN/requirements/requirement-range.ftl new file mode 100644 index 00000000000..eee27649831 --- /dev/null +++ b/Resources/Locale/en-US/_DEN/requirements/requirement-range.ftl @@ -0,0 +1,12 @@ +player-requirement-range-minimum-reason = at least {$minimum} +player-requirement-range-maximum-reason = at most {$maximum} +player-requirement-range-minmax-reason = between {$minimum} and {$maximum} + +player-requirement-range-with-difference = {$range} {$difference} +player-requirement-range-difference = (Need {$difference}) +player-requirement-range-difference-lower = {$count} more +player-requirement-range-difference-higher = {$count} less + +count-requirement-constant-reason = exactly {$count} +count-requirement-any-reason = any +count-requirement-all-reason = all diff --git a/Resources/Locale/en-US/_DEN/traits/debug.ftl b/Resources/Locale/en-US/_DEN/traits/debug.ftl index 00267de3eee..b9cb00f3695 100644 --- a/Resources/Locale/en-US/_DEN/traits/debug.ftl +++ b/Resources/Locale/en-US/_DEN/traits/debug.ftl @@ -1,3 +1,5 @@ trait-debug-jittery-name = Jittery trait-debug-stunned-name = Stunned trait-debug-spawn-crowbars-name = Spawn Crowbars +trait-debug-requirements-playtime-name = Playtime Requirements +trait-debug-requirements-profile-name = Profile Requirements diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml index d23027130fc..069dceded82 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/cargo_technician.yml @@ -2,21 +2,38 @@ - type: loadoutEffectGroup id: SeniorCargo effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobCargoTechnician - time: 21600 #6 hrs - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobSalvageSpecialist - time: 21600 #6 hrs - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: CargoTechnician + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: SalvageSpecialist + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Cargo - time: 216000 # 60 hrs + minTime: 60h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobCargoTechnician + # time: 21600 #6 hrs + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobSalvageSpecialist + # time: 21600 #6 hrs + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Cargo + # time: 216000 # 60 hrs + # End DEN # Head - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml index 602b2d36fd6..ecd48c11537 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Cargo/quartermaster.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: MasterQM effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobQuartermaster - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Quartermaster + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobQuartermaster + # time: 20h + # End DEN # Jumpsuit - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml index b8a8744915d..b928dd64e1d 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/bartender.yml @@ -1,11 +1,18 @@ - type: loadoutEffectGroup id: SeniorBar effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobBartender - time: 52h # 1 hour per week for 1 year + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Bartender + minTime: 52h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobBartender + # time: 52h # 1 hour per week for 1 year + # End DEN # Head - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml index 0bfc99d41fe..1ab03e734a4 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/chaplain.yml @@ -2,31 +2,52 @@ - type: loadoutEffectGroup id: NanoTrasenBibleRequirement effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobCaptain - time: 7200 #2 hrs + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Captain + minTime: 2h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobCaptain + # time: 7200 #2 hrs + # End DEN # Playtime requirement for Druid Bible, Druidic Tablet - type: loadoutEffectGroup id: DruidBibleRequirement effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobBotanist - time: 18000 #5 hrs + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Botanist + minTime: 5h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobBotanist + # time: 18000 #5 hrs + # End DEN # Playtime requirement for Clown Bible, Mirth of the Honkmother - type: loadoutEffectGroup id: ClownBibleRequirement effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobClown - time: 18000 #5 hrs + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Clown + minTime: 5h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobClown + # time: 18000 #5 hrs + # End DEN # Head - type: loadout @@ -132,7 +153,7 @@ storage: back: - BibleNanoTrasen - + - type: loadout id: BibleNarsie storage: diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/janitor.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/janitor.yml index 431b83a99d2..02df8d61ec7 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/janitor.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/janitor.yml @@ -1,11 +1,18 @@ - type: loadoutEffectGroup id: SeniorJanitorial effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobJanitor - time: 52h # 1 hour per week for 1 year + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Janitor + minTime: 52h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobJanitor + # time: 52h # 1 hour per week for 1 year + # End DEN # Head - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml b/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml index 1ed5e8aca03..c9ac6729ee8 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Civilian/passenger.yml @@ -2,21 +2,35 @@ - type: loadoutEffectGroup id: GreyTider effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobPassenger - time: 10h # silly reward for people who play passenger a lot + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Passenger + minTime: 10h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobPassenger + # time: 10h # silly reward for people who play passenger a lot + # End DEN # Head of Greytide (for grey mantle) - type: loadoutEffectGroup id: MasterGrey effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobPassenger - time: 20h # fun mantle for the most experienced greytiders + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Passenger + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobPassenger + # time: 20h # fun mantle for the most experienced greytiders + # End DEN # Face - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml b/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml index 29516789689..62b4920f18d 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Command/captain.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: MasterCap effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobCaptain - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Captain + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobCaptain + # time: 20h + # End DEN # Jumpsuit - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml index 17a84b73865..3bc5952afac 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Command/head_of_personnel.yml @@ -2,21 +2,35 @@ - type: loadoutEffectGroup id: MasterHoP effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobHeadOfPersonnel - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: HeadOfPersonnel + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobHeadOfPersonnel + # time: 20h + # End DEN # Professional HoP Time - type: loadoutEffectGroup id: ProfessionalHoP effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobHeadOfPersonnel - time: 15h # special reward for HoP mains + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: HeadOfPersonnel + minTime: 15h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobHeadOfPersonnel + # time: 15h # special reward for HoP mains + # End DEN # Jumpsuit - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml index 55f184a1681..d2d313d627e 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Engineering/chief_engineer.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: MasterCE effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobChiefEngineer - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: ChiefEngineer + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobChiefEngineer + # time: 20h + # End DEN # Jumpsuit - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml b/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml index 64bfe79a4d1..32083b4e8e7 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Engineering/station_engineer.yml @@ -2,21 +2,38 @@ - type: loadoutEffectGroup id: SeniorEngineering effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobAtmosphericTechnician - time: 6h - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobStationEngineer - time: 6h - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: AtmosphericTechnician + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: StationEngineer + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Engineering - time: 60h + minTime: 60h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobAtmosphericTechnician + # time: 6h + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobStationEngineer + # time: 6h + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Engineering + # time: 60h + # End DEN # Head - type: startingGear diff --git a/Resources/Prototypes/Loadouts/Jobs/Medical/role_timers.yml b/Resources/Prototypes/Loadouts/Jobs/Medical/role_timers.yml index f023cfc227b..cc5eadd03f5 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Medical/role_timers.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Medical/role_timers.yml @@ -2,38 +2,69 @@ - type: loadoutEffectGroup id: SeniorPhysician effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobChemist - time: 6h - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobMedicalDoctor - time: 6h - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Chemist + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: MedicalDoctor + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Medical - time: 60h + minTime: 60h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobChemist + # time: 6h + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobMedicalDoctor + # time: 6h + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Medical + # time: 60h + # End DEN # Mid-level timer for players who have a solid amount of experience - type: loadoutEffectGroup id: MedicalJourneymanTimer effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobMedicalDoctor - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: MedicalDoctor + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobMedicalDoctor + # time: 20h + # End DEN # Mid-level timer for CMO - type: loadoutEffectGroup id: MasterCMO effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobChiefMedicalOfficer - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: ChiefMedicalOfficer + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobChiefMedicalOfficer + # time: 20h + # End DEN diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml b/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml index 3717c1c67cb..a325320db0d 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Science/research_director.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: MasterRD effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobResearchDirector - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: ResearchDirector + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobResearchDirector + # time: 20h + # End DEN # Head - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml b/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml index 080015f0da7..9b1591f9d9d 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Science/scientist.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: SeniorResearcher effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Science - time: 60h + minTime: 60h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Science + # time: 60h + # End DEN # Head - type: startingGear diff --git a/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml index e85e1c8ccb5..89c8694875c 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Security/head_of_security.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: MasterHoS effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobHeadOfSecurity - time: 20h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: HeadOfSecurity + minTime: 20h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobHeadOfSecurity + # time: 20h + # End DEN # Jumpsuit - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml b/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml index e0b0ce1a3f1..6e2c90e9ab1 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Security/security_officer.yml @@ -2,16 +2,28 @@ - type: loadoutEffectGroup id: SeniorOfficer effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobWarden - time: 6h - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Warden + minTime: 6h + requirementType: Loadout + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Security - time: 60h + minTime: 60h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobWarden + # time: 6h + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Security + # time: 60h + # End DEN # Head - type: loadout diff --git a/Resources/Prototypes/Loadouts/Jobs/Wildcards/reporter.yml b/Resources/Prototypes/Loadouts/Jobs/Wildcards/reporter.yml index f5f98255262..85f08d0a61d 100644 --- a/Resources/Prototypes/Loadouts/Jobs/Wildcards/reporter.yml +++ b/Resources/Prototypes/Loadouts/Jobs/Wildcards/reporter.yml @@ -13,11 +13,18 @@ - type: loadout id: ReporterPressFedora effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobReporter - time: 10h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Reporter + minTime: 10h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobReporter + # time: 10h + # End DEN equipment: head: ClothingHeadHatFedoraPress @@ -25,10 +32,17 @@ - type: loadout id: ReporterPressVest effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobReporter - time: 35h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Reporter + minTime: 35h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobReporter + # time: 35h + # End DEN equipment: outerClothing: ClothingOuterVestPress diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml b/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml index 8a474d359aa..ceaaf4906b0 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/glasses.yml @@ -2,20 +2,34 @@ - type: loadoutEffectGroup id: JamjarTimer effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobLibrarian - time: 1h # for being the biggest nerd on the station + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Librarian + minTime: 1h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobLibrarian + # time: 1h # for being the biggest nerd on the station + # End DEN - type: loadoutEffectGroup id: JensenTimer effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Cargo - time: 10h # 10 hours of being a space trucker + minTime: 10h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Cargo + # time: 10h # 10 hours of being a space trucker + # End DEN # Basic options # Glasses diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/jobtrinkets.yml b/Resources/Prototypes/Loadouts/Miscellaneous/jobtrinkets.yml index 27d266c5bbb..173cb7d087e 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/jobtrinkets.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/jobtrinkets.yml @@ -12,11 +12,18 @@ - type: loadout id: FlowerWaterClown effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobClown - time: 4h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Clown + minTime: 4h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobClown + # time: 4h + # End DEN storage: back: - SprayFlowerPin @@ -24,11 +31,18 @@ - type: loadout id: SecStar effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement - department: Security - time: 100h + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement + department: Security + minTime: 100h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Security + # time: 100h + # End DEN storage: back: - Dinkystar diff --git a/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml b/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml index 50331ff89dc..aba71486bcb 100644 --- a/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml +++ b/Resources/Prototypes/Loadouts/Miscellaneous/trinkets.yml @@ -2,11 +2,18 @@ - type: loadoutEffectGroup id: Command effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Command - time: 1h + minTime: 1h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Command + # time: 1h + # End DEN # Flowers - type: loadout @@ -277,11 +284,18 @@ - type: loadout id: OffsetCaneClown effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobClown - time: 3600 # 1hr + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Clown + minTime: 1h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobClown + # time: 3600 # 1hr + # End DEN storage: back: - OffsetCaneClown @@ -290,11 +304,18 @@ - type: loadout id: OffsetCaneMime effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobMime - time: 3600 # 1hr + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Mime + minTime: 1h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobMime + # time: 3600 # 1hr + # End DEN storage: back: - OffsetCaneMime @@ -303,11 +324,18 @@ - type: loadout id: OffsetCaneNT effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerDepartmentPlaytimeRequirement department: Command - time: 18000 # 5hr + minTime: 5h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:DepartmentTimeRequirement + # department: Command + # time: 18000 # 5hr + # End DEN storage: back: - OffsetCaneNT @@ -316,11 +344,18 @@ - type: loadout id: Cane effects: - - !type:JobRequirementLoadoutEffect - requirement: - !type:RoleTimeRequirement - role: JobLibrarian - time: 3600 # 1hr same as Jamjar. + # Begin DEN: Use PlayerRequirements + - !type:PlayerRequirementLoadoutEffect + requirement: !type:PlayerJobPlaytimeRequirement + job: Librarian + minTime: 1h + requirementType: Loadout + # - !type:JobRequirementLoadoutEffect + # requirement: + # !type:RoleTimeRequirement + # role: JobLibrarian + # time: 3600 # 1hr same as Jamjar. + # End DEN storage: back: - Cane diff --git a/Resources/Prototypes/Roles/Antags/nukeops.yml b/Resources/Prototypes/Roles/Antags/nukeops.yml index de86b705609..bf444176852 100644 --- a/Resources/Prototypes/Roles/Antags/nukeops.yml +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -4,9 +4,14 @@ antagonist: true setPreference: true objective: roles-antag-nuclear-operative-objective - requirements: - - !type:OverallPlaytimeRequirement - time: 5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 5h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 5h + # End DEN guides: [ NuclearOperatives ] - type: antag @@ -16,11 +21,19 @@ setPreference: true objective: roles-antag-nuclear-operative-agent-objective requirements: - - !type:OverallPlaytimeRequirement - time: 5h - - !type:RoleTimeRequirement - role: JobChemist - time: 3h + # Begin DEN: Use PlayerRequirements + # - !type:OverallPlaytimeRequirement + # time: 5h + # - !type:RoleTimeRequirement + # role: JobChemist + # time: 3h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 5h + - !type:PlayerJobPlaytimeRequirement + job: Chemist + minTime: 3h + # End DEN guides: [ NuclearOperatives ] - type: antag @@ -29,13 +42,21 @@ antagonist: true setPreference: true objective: roles-antag-nuclear-operative-commander-objective - requirements: - - !type:OverallPlaytimeRequirement - time: 5h - - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 5h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 5h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 5h + - !type:PlayerDepartmentPlaytimeRequirement department: Security - time: 5h + minTime: 5h # should be changed to nukie playtime when thats tracked (wyci) + # End DEN guides: [ NuclearOperatives ] - type: startingGear diff --git a/Resources/Prototypes/Roles/Antags/revolutionary.yml b/Resources/Prototypes/Roles/Antags/revolutionary.yml index 172876040a6..c2f473b8b3e 100644 --- a/Resources/Prototypes/Roles/Antags/revolutionary.yml +++ b/Resources/Prototypes/Roles/Antags/revolutionary.yml @@ -5,9 +5,14 @@ setPreference: true objective: roles-antag-rev-head-objective guides: [ Revolutionaries ] - requirements: - - !type:OverallPlaytimeRequirement - time: 1h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 1h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 1h + # End DEN - type: antag id: Rev diff --git a/Resources/Prototypes/Roles/Antags/thief.yml b/Resources/Prototypes/Roles/Antags/thief.yml index 740a7e217f6..f3488df5ec5 100644 --- a/Resources/Prototypes/Roles/Antags/thief.yml +++ b/Resources/Prototypes/Roles/Antags/thief.yml @@ -5,9 +5,14 @@ setPreference: true objective: roles-antag-thief-objective guides: [ Thieves ] - requirements: - - !type:OverallPlaytimeRequirement - time: 1h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 1h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 1h + # End DEN - type: startingGear id: ThiefGear diff --git a/Resources/Prototypes/Roles/Antags/traitor.yml b/Resources/Prototypes/Roles/Antags/traitor.yml index edc130ef8b3..c9da593486c 100644 --- a/Resources/Prototypes/Roles/Antags/traitor.yml +++ b/Resources/Prototypes/Roles/Antags/traitor.yml @@ -5,9 +5,14 @@ setPreference: true objective: roles-antag-syndicate-agent-objective guides: [ Traitors ] - requirements: - - !type:OverallPlaytimeRequirement - time: 1h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 1h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 1h + # End DEN - type: antag id: TraitorSleeper @@ -16,9 +21,14 @@ setPreference: true objective: roles-antag-syndicate-agent-sleeper-objective guides: [ Traitors ] - requirements: - - !type:OverallPlaytimeRequirement - time: 1h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 1h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 1h + # End DEN # Syndicate Operative Outfit - Monkey - type: startingGear diff --git a/Resources/Prototypes/Roles/Antags/wizard.yml b/Resources/Prototypes/Roles/Antags/wizard.yml index 9d528f9b797..75145aefdd1 100644 --- a/Resources/Prototypes/Roles/Antags/wizard.yml +++ b/Resources/Prototypes/Roles/Antags/wizard.yml @@ -11,9 +11,14 @@ antagonist: true # setPreference: true # Disabled as roundstart gamemode until reworked objective: roles-antag-wizard-objective # TODO: maybe give random objs and stationary ones from AntagObjectives and AntagRandomObjectives - requirements: # I hate time locked roles but this should be enough time for someone to be acclimated - - !type:OverallPlaytimeRequirement - time: 5h + # Begin DEN: Use PlayerRequirements + # requirements: # I hate time locked roles but this should be enough time for someone to be acclimated + # - !type:OverallPlaytimeRequirement + # time: 5h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 5h + # End DEN guides: [ Wizard ] # See wizard_startinggear for wiz start gear options diff --git a/Resources/Prototypes/Roles/Antags/xenoborgs.yml b/Resources/Prototypes/Roles/Antags/xenoborgs.yml index 4e1989be8df..c3c53f4deea 100644 --- a/Resources/Prototypes/Roles/Antags/xenoborgs.yml +++ b/Resources/Prototypes/Roles/Antags/xenoborgs.yml @@ -4,10 +4,16 @@ antagonist: true setPreference: true objective: roles-antag-mothership-core-objective - requirements: - - !type:RoleTimeRequirement - role: JobBorg - time: 18000 # 5 hrs + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobBorg + # time: 18000 # 5 hrs + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: Borg + minTime: 5h + # End DEN guides: [ Xenoborgs ] - type: antag @@ -16,8 +22,14 @@ antagonist: true setPreference: true objective: roles-antag-xenoborg-objective - requirements: - - !type:RoleTimeRequirement - role: JobBorg - time: 18000 # 5 hrs + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobBorg + # time: 18000 # 5 hrs + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: Borg + minTime: 5h + # End DEN guides: [ Xenoborgs ] diff --git a/Resources/Prototypes/Roles/Antags/zombie.yml b/Resources/Prototypes/Roles/Antags/zombie.yml index fa6561aa5e6..2c5fa08b3ee 100644 --- a/Resources/Prototypes/Roles/Antags/zombie.yml +++ b/Resources/Prototypes/Roles/Antags/zombie.yml @@ -5,9 +5,14 @@ setPreference: true objective: roles-antag-initial-infected-objective guides: [ Zombies ] - requirements: - - !type:OverallPlaytimeRequirement - time: 1h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 1h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 1h + # End DEN - type: antag id: Zombie diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml index 92b9b93671e..e8bc709ed39 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/quartermaster.yml @@ -3,16 +3,28 @@ name: job-name-qm description: job-description-qm playTimeTracker: JobQuartermaster - requirements: - - !type:RoleTimeRequirement - role: JobCargoTechnician - time: 5h - - !type:RoleTimeRequirement - role: JobSalvageSpecialist - time: 2.5h - - !type:DepartmentTimeRequirement - department: Cargo - time: 10h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobCargoTechnician + # time: 5h + # - !type:RoleTimeRequirement + # role: JobSalvageSpecialist + # time: 2.5h + # - !type:DepartmentTimeRequirement + # department: Cargo + # time: 10h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: CargoTechnician + minTime: 5h + - !type:PlayerJobPlaytimeRequirement + job: SalvageSpecialist + minTime: 2.5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Cargo + minTime: 10h + # End DEN weight: 10 startingGear: QuartermasterGear icon: "JobIconQuarterMaster" diff --git a/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml b/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml index 7756f8dde90..57197e2f32d 100644 --- a/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml +++ b/Resources/Prototypes/Roles/Jobs/Cargo/salvage_specialist.yml @@ -3,10 +3,16 @@ name: job-name-salvagespec description: job-description-salvagespec playTimeTracker: JobSalvageSpecialist - requirements: - - !type:DepartmentTimeRequirement - department: Cargo - time: 2.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Cargo + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Cargo + minTime: 2.5h + # End DEN icon: "JobIconShaftMiner" startingGear: SalvageSpecialistGear supervisors: job-supervisors-qm diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml b/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml index 2d32b87f68b..24a00314f71 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/bartender.yml @@ -3,10 +3,16 @@ name: job-name-bartender description: job-description-bartender playTimeTracker: JobBartender - requirements: - - !type:DepartmentTimeRequirement - department: Civilian - time: 0.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Civilian + # time: 0.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Civilian + minTime: 0.5h + # End DEN startingGear: BartenderGear icon: "JobIconBartender" supervisors: job-supervisors-hop diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml index b3a5deab15a..ff2325e06e9 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chef.yml @@ -3,10 +3,16 @@ name: job-name-chef description: job-description-chef playTimeTracker: JobChef - requirements: - - !type:DepartmentTimeRequirement - department: Civilian - time: 0.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Civilian + # time: 0.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Civilian + minTime: 0.5h + # End DEN startingGear: ChefGear icon: "JobIconChef" supervisors: job-supervisors-hop diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml index 11f07252a31..26dee4de454 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/lawyer.yml @@ -3,9 +3,14 @@ name: job-name-lawyer description: job-description-lawyer playTimeTracker: JobLawyer - requirements: - - !type:OverallPlaytimeRequirement - time: 2.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 2.5h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 2.5h + # End DEN startingGear: LawyerGear icon: "JobIconLawyer" supervisors: job-supervisors-hop diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 8ff045173da..88ec64d7b60 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -3,9 +3,14 @@ name: job-name-mime description: job-description-mime playTimeTracker: JobMime - requirements: - - !type:OverallPlaytimeRequirement - time: 4h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 4h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 4h + # End DEN startingGear: MimeGear icon: "JobIconMime" supervisors: job-supervisors-hop diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml b/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml index 0c839718272..a04528f220f 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/service_worker.yml @@ -3,10 +3,16 @@ name: job-name-serviceworker description: job-description-serviceworker playTimeTracker: JobServiceWorker - requirements: - - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Civilian + # time: 0.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement department: Civilian - time: 0.5h + minTime: 0.5h + # End DEN startingGear: ServiceWorkerGear icon: "JobIconServiceWorker" supervisors: job-supervisors-service diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index 54251b263b1..63fb5803d11 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -3,22 +3,40 @@ name: job-name-captain description: job-description-captain playTimeTracker: JobCaptain - requirements: - - !type:DepartmentTimeRequirement - department: Engineering - time: 4h - - !type:DepartmentTimeRequirement - department: Medical - time: 4h - - !type:DepartmentTimeRequirement - department: Science - time: 4h - - !type:DepartmentTimeRequirement - department: Security - time: 4h - - !type:DepartmentTimeRequirement - department: Command - time: 4h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 4h + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 4h + # - !type:DepartmentTimeRequirement + # department: Science + # time: 4h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 4h + # - !type:DepartmentTimeRequirement + # department: Command + # time: 4h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Engineering + minTime: 4h + - !type:PlayerDepartmentPlaytimeRequirement + department: Medical + minTime: 4h + - !type:PlayerDepartmentPlaytimeRequirement + department: Science + minTime: 4h + - !type:PlayerDepartmentPlaytimeRequirement + department: Security + minTime: 4h + - !type:PlayerDepartmentPlaytimeRequirement + department: Command + minTime: 4h + # End DEN weight: 20 startingGear: CaptainGear icon: "JobIconCaptain" diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 9150ca39f3c..56276d27981 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -3,22 +3,40 @@ name: job-name-hop description: job-description-hop playTimeTracker: JobHeadOfPersonnel - requirements: - - !type:DepartmentTimeRequirement - department: Engineering - time: 2.5h - - !type:DepartmentTimeRequirement - department: Medical - time: 2.5h - - !type:DepartmentTimeRequirement - department: Science - time: 2.5h - - !type:DepartmentTimeRequirement - department: Security - time: 2.5h - - !type:DepartmentTimeRequirement - department: Command - time: 2.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 2.5h + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 2.5h + # - !type:DepartmentTimeRequirement + # department: Science + # time: 2.5h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 2.5h + # - !type:DepartmentTimeRequirement + # department: Command + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Engineering + minTime: 2.5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Medical + minTime: 2.5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Science + minTime: 2.5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Security + minTime: 2.5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Command + minTime: 2.5h + # End DEN weight: 20 startingGear: HoPGear icon: "JobIconHeadOfPersonnel" diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml b/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml index 20a6dc7aced..ba35f9f5e73 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml @@ -3,10 +3,16 @@ name: job-name-atmostech description: job-description-atmostech playTimeTracker: JobAtmosphericTechnician - requirements: - - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement department: Engineering - time: 2.5h + minTime: 2.5h + # End DEN startingGear: AtmosphericTechnicianGear icon: "JobIconAtmosphericTechnician" supervisors: job-supervisors-ce diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml index 561d0260204..fe55c004119 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml @@ -3,16 +3,28 @@ name: job-name-ce description: job-description-ce playTimeTracker: JobChiefEngineer - requirements: - - !type:RoleTimeRequirement - role: JobAtmosphericTechnician - time: 2.5h - - !type:RoleTimeRequirement - role: JobStationEngineer - time: 5h - - !type:DepartmentTimeRequirement - department: Engineering - time: 10h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobAtmosphericTechnician + # time: 2.5h + # - !type:RoleTimeRequirement + # role: JobStationEngineer + # time: 5h + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 10h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: AtmosphericTechnician + minTime: 2.5h + - !type:PlayerJobPlaytimeRequirement + job: StationEngineer + minTime: 5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Engineering + minTime: 10h + # End DEN weight: 10 startingGear: ChiefEngineerGear icon: "JobIconChiefEngineer" diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml index c99140b0956..ca10c9b1f6c 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/station_engineer.yml @@ -3,10 +3,16 @@ name: job-name-engineer description: job-description-engineer playTimeTracker: JobStationEngineer - requirements: - - !type:DepartmentTimeRequirement - department: Engineering - time: 2.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Engineering + minTime: 2.5h + # End DEN startingGear: StationEngineerGear icon: "JobIconStationEngineer" supervisors: job-supervisors-ce diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml b/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml index ee4431bded1..257199a3d27 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/technical_assistant.yml @@ -3,13 +3,21 @@ name: job-name-technical-assistant description: job-description-technical-assistant playTimeTracker: JobTechnicalAssistant - requirements: - - !type:OverallPlaytimeRequirement - time: 1h - - !type:DepartmentTimeRequirement - department: Engineering - time: 10h - inverted: true # stop playing intern if you're good at engineering! + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 1h + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 10h + # inverted: true # stop playing intern if you're good at engineering! + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 1h + - !type:PlayerDepartmentPlaytimeRequirement + department: Engineering + maxTime: 10h + # End DEN startingGear: TechnicalAssistantGear icon: "JobIconTechnicalAssistant" supervisors: job-supervisors-engineering diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml b/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml index 6f861790152..b916c116f35 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chemist.yml @@ -3,10 +3,16 @@ name: job-name-chemist description: job-description-chemist playTimeTracker: JobChemist - requirements: - - !type:DepartmentTimeRequirement - department: Medical - time: 5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Medical + minTime: 5h + # End DEN startingGear: ChemistGear icon: "JobIconChemist" supervisors: job-supervisors-cmo diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml index 5abd370503f..48ff1e6814c 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml @@ -5,16 +5,28 @@ name: job-name-cmo description: job-description-cmo playTimeTracker: JobChiefMedicalOfficer - requirements: - - !type:RoleTimeRequirement - role: JobChemist - time: 2.5h - - !type:RoleTimeRequirement - role: JobMedicalDoctor - time: 5h - - !type:DepartmentTimeRequirement - department: Medical - time: 10h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobChemist + # time: 2.5h + # - !type:RoleTimeRequirement + # role: JobMedicalDoctor + # time: 5h + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 10h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: Chemist + minTime: 2.5h + - !type:PlayerJobPlaytimeRequirement + job: MedicalDoctor + minTime: 5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Medical + minTime: 10h + # End DEN weight: 10 startingGear: CMOGear icon: "JobIconChiefMedicalOfficer" diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml index f012f251972..98957102668 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_doctor.yml @@ -3,10 +3,16 @@ name: job-name-doctor description: job-description-doctor playTimeTracker: JobMedicalDoctor - requirements: - - !type:DepartmentTimeRequirement - department: Medical - time: 2.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Medical + minTime: 2.5h + # End DEN startingGear: DoctorGear icon: "JobIconMedicalDoctor" supervisors: job-supervisors-cmo diff --git a/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml b/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml index cc96b6bee22..c29397c3d8a 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/medical_intern.yml @@ -3,11 +3,17 @@ name: job-name-intern description: job-description-intern playTimeTracker: JobMedicalIntern - requirements: - - !type:DepartmentTimeRequirement - department: Medical - time: 10h - inverted: true # stop playing intern if you're good at med! + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 10h + # inverted: true # stop playing intern if you're good at med! + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Medical + maxTime: 10h + # End DEN startingGear: MedicalInternGear icon: "JobIconMedicalIntern" supervisors: job-supervisors-medicine diff --git a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml index 4ef5f08e4d0..10f33cf27e0 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/paramedic.yml @@ -3,10 +3,16 @@ name: job-name-paramedic description: job-description-paramedic playTimeTracker: JobParamedic - requirements: - - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement department: Medical - time: 2.5h + minTime: 2.5h + # End DEN startingGear: ParamedicGear icon: "JobIconParamedic" supervisors: job-supervisors-cmo diff --git a/Resources/Prototypes/Roles/Jobs/Science/borg.yml b/Resources/Prototypes/Roles/Jobs/Science/borg.yml index 7834043799e..3aa5cf483ae 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/borg.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/borg.yml @@ -5,10 +5,16 @@ name: job-name-station-ai description: job-description-station-ai playTimeTracker: JobStationAi - requirements: - - !type:RoleTimeRequirement - role: JobBorg - time: 5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobBorg + # time: 5h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: Borg + minTime: 5h + # End DEN weight: 10 canBeAntag: false icon: JobIconStationAi @@ -22,9 +28,14 @@ name: job-name-borg description: job-description-borg playTimeTracker: JobBorg - requirements: - - !type:OverallPlaytimeRequirement - time: 10h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 10h + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 10h + # End DEN canBeAntag: false icon: JobIconBorg supervisors: job-supervisors-rd diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml b/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml index 48059858229..70c8e41b520 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_assistant.yml @@ -3,11 +3,17 @@ name: job-name-research-assistant description: job-description-research-assistant playTimeTracker: JobResearchAssistant - requirements: - - !type:DepartmentTimeRequirement - department: Science - time: 10h - inverted: true # stop playing intern if you're good at science! + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Science + # time: 10h + # inverted: true # stop playing intern if you're good at science! + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Science + maxTime: 10h + # End DEN startingGear: ResearchAssistantGear icon: "JobIconResearchAssistant" supervisors: job-supervisors-science diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index a7b057dc1bb..6e1d67ec378 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -3,13 +3,22 @@ name: job-name-rd description: job-description-rd playTimeTracker: JobResearchDirector - requirements: - - !type:RoleTimeRequirement - role: JobScientist - time: 5h - - !type:DepartmentTimeRequirement + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobScientist + # time: 5h + # - !type:DepartmentTimeRequirement + # department: Science + # time: 10h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: Scientist + minTime: 5h + - !type:PlayerDepartmentPlaytimeRequirement department: Science - time: 10h + minTime: 10h + # End DEN weight: 10 startingGear: ResearchDirectorGear icon: "JobIconResearchDirector" diff --git a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml index 5e8e1b6e143..c1a7b6d9276 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/scientist.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/scientist.yml @@ -3,10 +3,16 @@ name: job-name-scientist description: job-description-scientist playTimeTracker: JobScientist - requirements: - - !type:DepartmentTimeRequirement - department: Science - time: 2.5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Science + # time: 2.5h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Science + minTime: 2.5h + # End DEN startingGear: ScientistGear icon: "JobIconScientist" supervisors: job-supervisors-rd diff --git a/Resources/Prototypes/Roles/Jobs/Security/detective.yml b/Resources/Prototypes/Roles/Jobs/Security/detective.yml index b38f2c8b1c9..bff111939ba 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/detective.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/detective.yml @@ -3,10 +3,16 @@ name: job-name-detective description: job-description-detective playTimeTracker: JobDetective - requirements: - - !type:RoleTimeRequirement - role: JobSecurityOfficer - time: 5h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobSecurityOfficer + # time: 5h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: SecurityOfficer + minTime: 5h + # End DEN startingGear: DetectiveGear icon: "JobIconDetective" supervisors: job-supervisors-hos diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml index e8fdc6f7383..daf6b0dff07 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml @@ -3,19 +3,34 @@ name: job-name-hos description: job-description-hos playTimeTracker: JobHeadOfSecurity - requirements: - - !type:RoleTimeRequirement - role: JobWarden - time: 3h - - !type:RoleTimeRequirement - role: JobDetective - time: 1h # knowing how to use the tools is important - - !type:RoleTimeRequirement - role: JobSecurityOfficer - time: 5h - - !type:DepartmentTimeRequirement - department: Security - time: 10h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobWarden + # time: 3h + # - !type:RoleTimeRequirement + # role: JobDetective + # time: 1h # knowing how to use the tools is important + # - !type:RoleTimeRequirement + # role: JobSecurityOfficer + # time: 5h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 10h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: Detective + minTime: 1h + - !type:PlayerJobPlaytimeRequirement + job: SecurityOfficer + minTime: 5h + - !type:PlayerJobPlaytimeRequirement + job: Warden + minTime: 3h + - !type:PlayerDepartmentPlaytimeRequirement + department: Security + minTime: 10h + # End DEN weight: 10 startingGear: HoSGear icon: "JobIconHeadOfSecurity" diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml index 47b7472541e..f6e8b0ce158 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_cadet.yml @@ -3,13 +3,21 @@ name: job-name-cadet description: job-description-cadet playTimeTracker: JobSecurityCadet - requirements: - - !type:OverallPlaytimeRequirement - time: 10h - - !type:DepartmentTimeRequirement - department: Security - time: 10h - inverted: true # stop playing intern if you're good at security! + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:OverallPlaytimeRequirement + # time: 10h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 10h + # inverted: true # stop playing intern if you're good at security! + playerRequirements: + - !type:PlayerOverallPlaytimeRequirement + minTime: 10h + - !type:PlayerDepartmentPlaytimeRequirement + department: Security + maxTime: 10h + # End DEN startingGear: SecurityCadetGear icon: "JobIconSecurityCadet" supervisors: job-supervisors-security diff --git a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml index af8a274d77b..627588e21d3 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/security_officer.yml @@ -3,10 +3,16 @@ name: job-name-security description: job-description-security playTimeTracker: JobSecurityOfficer - requirements: - - !type:DepartmentTimeRequirement - department: Security - time: 4h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:DepartmentTimeRequirement + # department: Security + # time: 4h + playerRequirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Security + minTime: 4h + # End DEN startingGear: SecurityOfficerGear icon: "JobIconSecurityOfficer" supervisors: job-supervisors-hos diff --git a/Resources/Prototypes/Roles/Jobs/Security/warden.yml b/Resources/Prototypes/Roles/Jobs/Security/warden.yml index 2d8813a69e7..2fcb39047ae 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/warden.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/warden.yml @@ -3,13 +3,22 @@ name: job-name-warden description: job-description-warden playTimeTracker: JobWarden - requirements: - - !type:RoleTimeRequirement - role: JobSecurityOfficer - time: 5h - - !type:DepartmentTimeRequirement - department: Security - time: 10h + # Begin DEN: Use PlayerRequirements + # requirements: + # - !type:RoleTimeRequirement + # role: JobSecurityOfficer + # time: 5h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 10h + playerRequirements: + - !type:PlayerJobPlaytimeRequirement + job: SecurityOfficer + minTime: 5h + - !type:PlayerDepartmentPlaytimeRequirement + department: Security + minTime: 10h + # End DEN weight: 5 startingGear: WardenGear icon: "JobIconWarden" diff --git a/Resources/Prototypes/Roles/requirement_overrides.yml b/Resources/Prototypes/Roles/requirement_overrides.yml index 752249e90bc..e7904921bc2 100644 --- a/Resources/Prototypes/Roles/requirement_overrides.yml +++ b/Resources/Prototypes/Roles/requirement_overrides.yml @@ -1,16 +1,32 @@ - type: jobRequirementOverride id: Reduced - jobs: + # Begin DEN: Use PlayerRequirements + # jobs: + # Captain: + # - !type:DepartmentTimeRequirement + # department: Engineering + # time: 1h + # - !type:DepartmentTimeRequirement + # department: Medical + # time: 1h + # - !type:DepartmentTimeRequirement + # department: Security + # time: 1h + # - !type:DepartmentTimeRequirement + # department: Command + # time: 1h + jobRequirements: Captain: - - !type:DepartmentTimeRequirement + - !type:PlayerDepartmentPlaytimeRequirement department: Engineering - time: 1h - - !type:DepartmentTimeRequirement + minTime: 1h + - !type:PlayerDepartmentPlaytimeRequirement department: Medical - time: 1h - - !type:DepartmentTimeRequirement + minTime: 1h + - !type:PlayerDepartmentPlaytimeRequirement department: Security - time: 1h - - !type:DepartmentTimeRequirement + minTime: 1h + - !type:PlayerDepartmentPlaytimeRequirement department: Command - time: 1h + minTime: 1h + # End DEN diff --git a/Resources/Prototypes/_DEN/EntityTraits/debug.yml b/Resources/Prototypes/_DEN/EntityTraits/debug.yml index b2435e90352..1cb2697f61e 100644 --- a/Resources/Prototypes/_DEN/EntityTraits/debug.yml +++ b/Resources/Prototypes/_DEN/EntityTraits/debug.yml @@ -32,3 +32,41 @@ - Crowbar - Crowbar - Crowbar + +- type: entityTrait + id: DebugRequirementsPlaytime + name: trait-debug-requirements-playtime-name + characterEditorSelectable: false + functions: [] + requirements: + - !type:PlayerDepartmentPlaytimeRequirement + department: Command + minTime: 86400 + - !type:PlayerJobPlaytimeRequirement + job: Clown + maxTime: 8340 + - !type:PlayerOverallPlaytimeRequirement + minTime: 3600 + maxTime: 14400 + +- type: entityTrait + id: DebugRequirementsProfile + name: trait-debug-requirements-profile-name + characterEditorSelectable: false + functions: [] + requirements: + - !type:PlayerAgeRequirement + max: 60 + - !type:PlayerSpeciesRequirement + species: + - Human + - Vulpkanin + - SlimePerson + - !type:PlayerTraitRequirement + inverted: true + traits: + - PoorVision + - Blindness + count: !type:RangeCountRequirement + min: 2 + max: 8 diff --git a/Resources/Prototypes/_DEN/EntityTraits/species.yml b/Resources/Prototypes/_DEN/EntityTraits/species.yml index 71eae2c81b2..d6a16102280 100644 --- a/Resources/Prototypes/_DEN/EntityTraits/species.yml +++ b/Resources/Prototypes/_DEN/EntityTraits/species.yml @@ -8,7 +8,10 @@ name: trait-morph-human-canid-name description: trait-morph-human-canid-desc category: SpeciesMorphs - allowedSpecies: [Human] + requirements: + - !type:PlayerSpeciesRequirement + hideIfFailed: true + species: [Human] cost: 1 whitelist: components: @@ -22,7 +25,10 @@ name: trait-morph-human-felinid-name description: trait-morph-human-felinid-desc category: SpeciesMorphs - allowedSpecies: [Human] + requirements: + - !type:PlayerSpeciesRequirement + hideIfFailed: true + species: [Human] cost: 1 whitelist: components: @@ -36,7 +42,10 @@ name: trait-morph-human-kitsune-name description: trait-morph-human-kitsune-desc category: SpeciesMorphs - allowedSpecies: [Human] + requirements: + - !type:PlayerSpeciesRequirement + hideIfFailed: true + species: [Human] cost: 1 whitelist: components: @@ -50,7 +59,10 @@ name: trait-morph-human-oni-name description: trait-morph-human-oni-desc category: SpeciesMorphs - allowedSpecies: [Human] + requirements: + - !type:PlayerSpeciesRequirement + hideIfFailed: true + species: [Human] cost: 1 whitelist: components: