Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3df2237
add IPlayerRequirement and requirement context
portfiend Mar 16, 2026
d4c5754
add player requirement manager
portfiend Mar 17, 2026
d2a9329
add department playtime requirements
portfiend Mar 17, 2026
3b5240a
redundancy
portfiend Mar 17, 2026
d6253d5
add abstract player requirement class
portfiend Mar 24, 2026
651a20f
add inverted string to playtime reasons
portfiend Mar 24, 2026
58876e3
Add count requirements
portfiend Mar 24, 2026
2ff98ab
Awagga
portfiend Mar 24, 2026
b7afe20
add trait requirements
portfiend Mar 24, 2026
207c653
add player requirements to IOC
portfiend Mar 24, 2026
fdd11f2
comments
portfiend Mar 24, 2026
6942b61
add auto-pass to playtime requirements
portfiend Mar 24, 2026
5cd22c6
add IPlayerRequirementManager interface
portfiend Mar 24, 2026
d4ac753
add "any" count requirement
portfiend Mar 25, 2026
fc85ec9
misnamed it oops
portfiend Mar 25, 2026
0696111
add GetPlayerContext()
portfiend Mar 25, 2026
ffbdbd9
get tooltips and trait requirements working
portfiend Mar 25, 2026
0405af2
clean up tooltip code
portfiend Mar 25, 2026
b45bab7
add species requirements
portfiend Mar 25, 2026
9cf0f61
add a missing comment
portfiend Mar 25, 2026
58122c5
comment this method
portfiend Mar 25, 2026
3522c32
simplify this
portfiend Mar 25, 2026
3650bc9
add hiding traits from UI
portfiend Mar 25, 2026
b6f862e
remove AllowedSpecies from EntityTraitPrototype
portfiend Mar 25, 2026
21349cc
add species requirements to morphology traits
portfiend Mar 25, 2026
fb11510
obsolete this
portfiend Mar 25, 2026
9ee17ee
add job playtime requirements
portfiend Mar 25, 2026
dd0d311
Merge branch 'master' into fear/character-requirements
portfiend Mar 26, 2026
e06f5d5
Turn min/max ranges into IPlayerRangeRequirement
portfiend Mar 26, 2026
e2b0805
add age requirement
portfiend Mar 26, 2026
5a9e35d
add overall playtime requirement
portfiend Mar 26, 2026
23d9543
obsolete more job requirements
portfiend Mar 26, 2026
df0bb0a
move this out of IPlayerRequirementManager
portfiend Mar 26, 2026
ac4282f
use static CheckRequirements
portfiend Mar 26, 2026
1dab83d
add PlayerRequirementLoadoutEffect
portfiend Mar 26, 2026
63f43f8
static ShouldHide
portfiend Mar 26, 2026
8a640cc
oops i should disable timers here
portfiend Mar 26, 2026
f5822a8
add playtime requirement types
portfiend Mar 26, 2026
33c61bf
use player requirements in loadouts
portfiend Mar 26, 2026
921dea6
obsoleted and refactored a lot of bullshsit
portfiend Mar 26, 2026
b0a3c6e
add debug traits for requirements
portfiend Mar 26, 2026
dc7b929
add comments to these fields
portfiend Mar 27, 2026
0fcd30e
remove "shouldHide" traits from characters
portfiend Mar 27, 2026
de4b86e
include context in player requirement reasons
portfiend Mar 27, 2026
e2775da
add difference ranges to player range requirements
portfiend Mar 27, 2026
30891b0
give jobs/antags timer requirement difference reasons
portfiend Mar 27, 2026
b7c9297
exclude playtime difference if the requirement is inverted
portfiend Mar 27, 2026
3b34ca0
exclude difference reason on age requirements too
portfiend Mar 27, 2026
d7985e3
format number requirements
portfiend Mar 27, 2026
1174b7c
refactor player restrictions for antags
portfiend Mar 27, 2026
ce40ea3
migrate jobs to using playerrequirements
portfiend Mar 27, 2026
57d9318
whoops i forgot to put this under a Captain key
portfiend Mar 27, 2026
2427556
fix borg job IDs oops
portfiend Mar 27, 2026
c41e950
add errors for invalid ids
portfiend Mar 27, 2026
976dc5a
fixes
portfiend Mar 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Content.Client/IoC/ClientContentIoC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -66,6 +68,7 @@ public static void Register(IDependencyCollection collection)
collection.Register<ClientsidePlaytimeTrackingManager>();
collection.Register<ClientFeedbackManager>();
collection.Register<ISharedFeedbackManager, ClientFeedbackManager>();
collection.Register<IPlayerRequirementManager, PlayerRequirementManager>(); // DEN
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -15,14 +16,15 @@

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!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[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<string, TimeSpan> _roles = new();
private readonly List<ProtoId<JobPrototype>> _jobBans = new();
Expand Down Expand Up @@ -148,10 +150,21 @@ public bool IsAllowed(
return false;

// Check other role requirements
// TODO DEN: This is deprecated
var reqs = _entManager.System<SharedRoleSystem>().GetRoleRequirements(job);
if (!CheckRoleRequirements(reqs, profile, out reason))
return false;

// Begin DEN: Use player requirements
var roleSystem = _entManager.System<SharedRoleSystem>();
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;
}

Expand All @@ -175,14 +188,26 @@ public bool IsAllowed(
return false;

// Check other role requirements
// TODO DEN: This is deprecated
var reqs = _entManager.System<SharedRoleSystem>().GetRoleRequirements(antag);
if (!CheckRoleRequirements(reqs, profile, out reason))
return false;

// Begin DEN: Use player requirements
var roleSystem = _entManager.System<SharedRoleSystem>();
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<JobRequirement>? requirements, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
Expand Down
94 changes: 93 additions & 1 deletion Content.Client/_DEN/Lobby/UI/Traits/EntityTraitSelector.xaml.cs
Original file line number Diff line number Diff line change
@@ -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<bool>? PreferenceChanged;

public int Cost { private set; get; } = 0;
Expand All @@ -19,19 +28,23 @@ public bool Preference
}

public ProtoId<EntityTraitPrototype>? 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 = "";

Expand All @@ -40,9 +53,88 @@ private void SetTrait(EntityTraitPrototype trait)

text += Loc.GetString(trait.Name);
SelectorCheckbox.Text = text;
UpdateVisibility(trait);
}

/// <summary>
/// Retrieve this session's context.
/// </summary>
/// <returns>The local player's session context.</returns>
private PlayerRequirementContext GetContext()
{
var session = _player.LocalSession;
if (session == null)
return new();

var context = _requirements.GetPlayerContext(session);
return context;
}

/// <summary>
/// Toggles the visibility of this selector, depending on the requirements of a given trait.
/// </summary>
/// <param name="trait">The trait to check the requirements of.</param>
private void UpdateVisibility(EntityTraitPrototype trait)
{
var context = GetContext();
Visible = !SharedPlayerRequirementManager.ShouldHide(context, trait.Requirements);
}

/// <summary>
/// Provide a tooltip to this control if it has a valid trait set.
/// This includes the trait's description and its requirement text.
/// </summary>
/// <returns>A tooltip with the trait's description, if this control has a trait.</returns>
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;
}

/// <summary>
/// Construct a full tooltip description for a given trait.
/// </summary>
/// <param name="trait">The trait to construct a description out of.</param>
/// <returns>The tooltip text associated with this trait.</returns>
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)
Expand Down
3 changes: 1 addition & 2 deletions Content.Client/_DEN/Lobby/UI/Traits/TraitCategoryBox.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ public void SetTraits(List<EntityTraitPrototype> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Check if we pass a given list of requirements.
/// A profile may be optionally supplied to replace the one in the context.
/// </summary>
/// <param name="profile">A profile associated with the character we're loading in.</param>
/// <param name="requirements">The requirements to check.</param>
/// <param name="The context we generated to check requirements.</param>
/// <returns>Whether we pass the requirements given.</returns>
private bool PassesRequirements(HumanoidCharacterProfile? profile,
List<IPlayerRequirement> requirements,
out PlayerRequirementContext context)
{
var session = _playerManager.LocalSession;
context = session != null
? _requirements.GetPlayerContext(session)
: new();

if (profile != null)
context.Profile = profile;

return SharedPlayerRequirementManager.CheckRequirements(context, requirements);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Content.Client.Lobby;
using Content.Shared._DEN.Requirements.Managers;
using Content.Shared.Players.PlayTimeTracking;
using Robust.Client.Player;
using Robust.Shared.Player;

namespace Content.Client._DEN.Requirements.Managers;

/// <inheritdoc />
public sealed partial class PlayerRequirementManager : SharedPlayerRequirementManager
{
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IClientPreferencesManager _preferences = default!;
[Dependency] private readonly ISharedPlaytimeManager _playtimeManager = default!;

/// <inheritdoc />
public override PlayerRequirementContext GetPlayerContext(ICommonSession session)
{
if (_player.LocalSession != session)
return new();

var playtimes = _playtimeManager.GetPlayTimes(session);
var profile = _preferences.Preferences?.SelectedCharacter;

return new()
{
Playtimes = playtimes,
Profile = profile,
};
}
}
3 changes: 3 additions & 0 deletions Content.Server/IoC/ServerContentIoC.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Content.Server._DEN.Requirements.Managers;
using Content.Server.Administration;
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
Expand All @@ -24,6 +25,7 @@
using Content.Server.ServerUpdates;
using Content.Server.Voting.Managers;
using Content.Server.Worldgen.Tools;
using Content.Shared._DEN.Requirements.Managers;
using Content.Shared.Administration.Logs;
using Content.Shared.Administration.Managers;
using Content.Shared.Chat;
Expand Down Expand Up @@ -84,5 +86,6 @@ public static void Register(IDependencyCollection deps)
deps.Register<DiscordChatLink>();
deps.Register<ServerFeedbackManager>();
deps.Register<ISharedFeedbackManager, ServerFeedbackManager>();
deps.Register<IPlayerRequirementManager, PlayerRequirementManager>(); // DEN
}
}
Loading
Loading