JobRequiremet refactor (#579)

* JobRequirement refactor (#30347)

* refactor JobRequirements

* add profile support

* fix

* Update quartermaster.yml

* sloth fixes

* inport 30208

* Update DepartmentPrototype.cs

* species restriction

* left tweak stick

* stringbuilder is cool!

* Add JobRequirementOverride prototypes (#28607)

* Add JobRequirementOverride prototypes

* a

* invert if

* Add override that takes in prototypes directly

* - fix: Errors.

* - add: Add stuff.

* - fix: Formatted message fix.

* - add: Another requirement.

---------

Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com>
Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
This commit is contained in:
Aviu00
2024-08-07 17:30:43 +00:00
committed by GitHub
parent 44447d573f
commit 9a9c9598e0
53 changed files with 805 additions and 297 deletions

View File

@@ -42,7 +42,7 @@ public sealed partial class RoleBanWindow : DefaultWindow
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
CreateRoleGroup(proto.ID, proto.Roles.Select(x => x.Id), proto.Color);
}
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID),

View File

@@ -4,7 +4,9 @@ using Content.Client.CrewManifest;
using Content.Client.GameTicking.Managers;
using Content.Client.UserInterface.Controls;
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Preferences;
using Content.Shared.CCVar;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Robust.Client.Console;
@@ -26,6 +28,7 @@ namespace Content.Client.LateJoin
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
public event Action<(NetEntity, string)> SelectedId;
@@ -254,7 +257,7 @@ namespace Content.Client.LateJoin
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
if (!_jobRequirements.IsAllowed(prototype, out var reason))
if (!_jobRequirements.IsAllowed(prototype, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
{
jobButton.Disabled = true;

View File

@@ -1,8 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using Content.Client.Administration.Managers;
using Content.Client.Lobby;
using Content.Shared.CCVar;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Client;
using Robust.Client.Player;
@@ -22,7 +24,6 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IClientAdminManager _adminManager = default!; // WD
private readonly Dictionary<string, TimeSpan> _roles = new();
private readonly List<string> _roleBans = new();
@@ -40,7 +41,6 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
_net.RegisterNetMessage<MsgPlayTime>(RxPlayTime);
_client.RunLevelChanged += ClientOnRunLevelChanged;
_adminManager.AdminStatusUpdated += () => Updated?.Invoke(); // WD
}
private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
@@ -82,7 +82,8 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
Updated?.Invoke();
}
public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
public bool IsAllowed(JobPrototype job, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
@@ -96,11 +97,16 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
if (player == null)
return true;
return _adminManager.IsActive() || // WD
CheckRoleTime(job.Requirements, out reason);
return CheckRoleRequirements(job, profile, out reason);
}
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
public bool CheckRoleRequirements(JobPrototype job, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
{
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
return CheckRoleRequirements(reqs, profile, out reason);
}
public bool CheckRoleRequirements(HashSet<JobRequirement>? requirements, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
@@ -110,7 +116,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
var reasons = new List<string>();
foreach (var requirement in requirements)
{
if (JobRequirements.TryRequirementMet(requirement, _roles, out var jobReason, _entManager, _prototypes))
if (requirement.Check(_entManager, _prototypes, profile, _roles, out var jobReason))
continue;
reasons.Add(jobReason.ToMarkup());

View File

@@ -1,4 +1,5 @@
using Content.Client.Players.PlayTimeTracking;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Client.UserInterface.Controls;
@@ -15,7 +16,7 @@ public sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototyp
public event Action<bool>? PreferenceChanged;
public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup)
public AntagPreferenceSelector(AntagPrototype proto, ButtonGroup btnGroup, HumanoidCharacterProfile profile)
: base(proto, btnGroup)
{
Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
@@ -28,12 +29,12 @@ public sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototyp
var title = Loc.GetString(proto.Name);
var description = Loc.GetString(proto.Objective);
// Not supported yet get fucked.
Setup(null, items, title, 250, description);
Setup(null, profile, items, title, 250, description);
// immediately lock requirements if they arent met.
// another function checks Disabled after creating the selector so this has to be done now
var requirements = IoCManager.Resolve<JobRequirementsManager>();
if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
if (!requirements.CheckRoleRequirements(proto.Requirements, profile, out var reason))
{
LockRequirements(reason);
}

View File

@@ -557,12 +557,16 @@ namespace Content.Client.Preferences.UI
_antagPreferences.Clear();
var btnGroup = new ButtonGroup();
var character = (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter;
if (character == null)
return;
foreach (var antag in _prototypeManager.EnumeratePrototypes<AntagPrototype>().OrderBy(a => Loc.GetString(a.Name)))
{
if (!antag.SetPreference)
continue;
var selector = new AntagPreferenceSelector(antag, btnGroup)
var selector = new AntagPreferenceSelector(antag, btnGroup, character)
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
@@ -590,6 +594,10 @@ namespace Content.Client.Preferences.UI
_jobCategories.Clear();
var firstCategory = true;
var character = (HumanoidCharacterProfile?) _preferencesManager.Preferences?.SelectedCharacter;
if (character == null)
return;
var departments = _prototypeManager.EnumeratePrototypes<DepartmentPrototype>().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
@@ -650,12 +658,12 @@ namespace Content.Client.Preferences.UI
// Clone so we don't modify the underlying loadout.
Profile?.Loadouts.TryGetValue(LoadoutSystem.GetJobPrototype(job.ID), out loadout);
loadout = loadout?.Clone();
var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager)
var selector = new JobPrioritySelector(loadout, job, jobLoadoutGroup, _prototypeManager, character)
{
Margin = new Thickness(3f, 3f, 3f, 0f),
};
if (!_requirements.IsAllowed(job, out var reason))
if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
{
selector.LockRequirements(reason);
}

View File

@@ -20,7 +20,7 @@ public sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
public event Action<JobPriority>? PriorityChanged;
public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan)
public JobPrioritySelector(RoleLoadout? loadout, JobPrototype proto, ButtonGroup btnGroup, IPrototypeManager protoMan, HumanoidCharacterProfile profile)
: base(proto, btnGroup)
{
Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
@@ -41,6 +41,6 @@ public sealed class JobPrioritySelector : RequirementsSelector<JobPrototype>
var jobIcon = protoMan.Index<StatusIconPrototype>(proto.Icon);
icon.Texture = jobIcon.Icon.Frame0();
Setup(loadout, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
Setup(loadout, profile, items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
}
}

View File

@@ -1,5 +1,6 @@
using System.Linq;
using Content.Shared.Clothing;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
@@ -17,18 +18,18 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
public event Action<ProtoId<ItemLoadoutPrototype>>? OnLoadoutPressed;
public event Action<ProtoId<ItemLoadoutPrototype>>? OnLoadoutUnpressed;
public LoadoutGroupContainer(RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
public LoadoutGroupContainer(HumanoidCharacterProfile profile, RoleLoadout loadout, LoadoutGroupPrototype groupProto, ICommonSession session, IDependencyCollection collection)
{
RobustXamlLoader.Load(this);
_groupProto = groupProto;
RefreshLoadouts(loadout, session, collection);
RefreshLoadouts(profile, loadout, session, collection);
}
/// <summary>
/// Updates button availabilities and buttons.
/// </summary>
public void RefreshLoadouts(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
public void RefreshLoadouts(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection)
{
var protoMan = collection.Resolve<IPrototypeManager>();
var loadoutSystem = collection.Resolve<IEntityManager>().System<LoadoutSystem>();
@@ -74,7 +75,7 @@ public sealed partial class LoadoutGroupContainer : BoxContainer
var matchingLoadout = selected.FirstOrDefault(e => e.Prototype == loadoutProto);
var pressed = matchingLoadout != null;
var enabled = loadout.IsValid(session, loadoutProto, collection, out var reason);
var enabled = loadout.IsValid(profile, session, loadoutProto, collection, out var reason);
var loadoutContainer = new LoadoutContainer(loadoutProto, !enabled, reason);
loadoutContainer.Select.Pressed = pressed;
loadoutContainer.Text = loadoutSystem.GetName(loadProto);

View File

@@ -1,5 +1,6 @@
using Content.Client.Lobby;
using Content.Client.UserInterface.Controls;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Robust.Client.AutoGenerated;
@@ -17,9 +18,12 @@ public sealed partial class LoadoutWindow : FancyWindow
private List<LoadoutGroupContainer> _groups = new();
public LoadoutWindow(RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
public HumanoidCharacterProfile Profile;
public LoadoutWindow(HumanoidCharacterProfile profile, RoleLoadout loadout, RoleLoadoutPrototype proto, ICommonSession session, IDependencyCollection collection)
{
RobustXamlLoader.Load(this);
Profile = profile;
var protoManager = collection.Resolve<IPrototypeManager>();
foreach (var group in proto.Groups)
@@ -27,7 +31,7 @@ public sealed partial class LoadoutWindow : FancyWindow
if (!protoManager.TryIndex(group, out var groupProto))
continue;
var container = new LoadoutGroupContainer(loadout, protoManager.Index(group), session, collection);
var container = new LoadoutGroupContainer(profile, loadout, protoManager.Index(group), session, collection);
LoadoutGroupsContainer.AddTab(container, Loc.GetString(groupProto.Name));
_groups.Add(container);
@@ -54,7 +58,7 @@ public sealed partial class LoadoutWindow : FancyWindow
{
foreach (var group in _groups)
{
group.RefreshLoadouts(loadout, session, collection);
group.RefreshLoadouts(Profile, loadout, session, collection);
}
}
}

View File

@@ -3,6 +3,7 @@ using Content.Client.Lobby;
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Clothing;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
using Content.Shared.Preferences.Loadouts.Effects;
using Content.Shared.Roles;
@@ -75,7 +76,7 @@ public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototyp
/// <summary>
/// Actually adds the controls, must be called in the inheriting class' constructor.
/// </summary>
protected void Setup(RoleLoadout? loadout, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
protected void Setup(RoleLoadout? loadout, HumanoidCharacterProfile profile, (string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
{
_loadout = loadout;
@@ -131,7 +132,7 @@ public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototyp
_loadout ??= new RoleLoadout(LoadoutSystem.GetJobPrototype(Proto.ID));
_loadout.SetDefault(protoManager);
_loadoutWindow = new LoadoutWindow(_loadout, protoManager.Index(_loadout.Role), session, collection)
_loadoutWindow = new LoadoutWindow(profile, _loadout, protoManager.Index(_loadout.Role), session, collection)
{
Title = Loc.GetString(Proto.ID + "-loadout"),
};
@@ -150,7 +151,7 @@ public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototyp
if (!_loadout.RemoveLoadout(selectedGroup, selectedLoadout, protoManager))
return;
_loadout.EnsureValid(session, collection);
_loadout.EnsureValid(profile, session, collection);
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.ReloadProfile();
@@ -162,7 +163,7 @@ public abstract class RequirementsSelector<T> : BoxContainer where T : IPrototyp
if (!_loadout.AddLoadout(selectedGroup, selectedLoadout, protoManager))
return;
_loadout.EnsureValid(session, collection);
_loadout.EnsureValid(profile, session, collection);
_loadoutWindow.RefreshLoadouts(_loadout, session, collection);
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
controller.ReloadProfile();

View File

@@ -93,7 +93,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
bool hasAccess = true;
FormattedMessage? reason;
if (!requirementsManager.CheckRoleTime(group.Key.Requirements, out reason))
if (!requirementsManager.CheckRoleRequirements(group.Key.Requirements, null, out reason))
{
hasAccess = false;
}

View File

@@ -386,4 +386,4 @@ public sealed class AntagSelectionSystem : GameRuleSystem<GameRuleComponent>
}
#endregion
}
}

View File

@@ -14,6 +14,10 @@ namespace Content.Server.Ghost.Roles.Components
[DataField("rules")] private string _roleRules = "ghost-role-component-default-rules";
// TODO ROLE TIMERS
// Actually make use of / enforce this requirement?
// Why is this even here.
// Move to ghost role prototype & respect CCvars.GameRoleTimerOverride
[DataField("requirements")]
public HashSet<JobRequirement>? Requirements;

View File

@@ -4,12 +4,15 @@ using Content.Server.Afk;
using Content.Server.Afk.Events;
using Content.Server.GameTicking;
using Content.Server.Mind;
using Content.Server.Preferences.Managers;
using Content.Server.Station.Events;
using Content.Shared.CCVar;
using Content.Shared.GameTicking;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Components;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.GameObjects;
using Robust.Server.Player;
@@ -33,6 +36,8 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly IPlayTimeTrackingManager _tracking = default!;
[Dependency] private readonly IAdminManager _adminManager = default!;
[Dependency] private readonly IServerPreferencesManager _preferencesManager = default!;
[Dependency] private readonly SharedRoleSystem _role = default!;
public override void Initialize()
{
@@ -170,7 +175,6 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
return true;
if (!_prototypes.TryIndex<JobPrototype>(role, out var job) ||
job.Requirements == null ||
!_cfg.GetCVar(CCVars.GameRoleTimers))
return true;
@@ -180,7 +184,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
playTimes = new Dictionary<string, TimeSpan>();
}
return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes);
return JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter);
}
public HashSet<string> GetDisallowedJobs(ICommonSession player)
@@ -201,25 +205,14 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
{
if (job.Requirements != null)
{
foreach (var requirement in job.Requirements)
{
if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes))
continue;
goto NoRole;
}
}
roles.Add(job.ID);
NoRole:;
if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter))
roles.Add(job.ID);
}
return roles;
}
public void RemoveDisallowedJobs(NetUserId userId, ref List<string> jobs)
public void RemoveDisallowedJobs(NetUserId userId, List<ProtoId<JobPrototype>> jobs)
{
if (!_cfg.GetCVar(CCVars.GameRoleTimers))
return;
@@ -238,22 +231,14 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
for (var i = 0; i < jobs.Count; i++)
{
var job = jobs[i];
if (!_prototypes.TryIndex<JobPrototype>(job, out var jobber) ||
jobber.Requirements == null ||
jobber.Requirements.Count == 0)
continue;
foreach (var requirement in jobber.Requirements)
if (_prototypes.TryIndex(jobs[i], out var job)
&& JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter))
{
if (JobRequirements.TryRequirementMet(requirement, playTimes, out _, EntityManager, _prototypes))
continue;
jobs.RemoveSwap(i);
i--;
break;
continue;
}
jobs.RemoveSwap(i);
i--;
}
}

View File

@@ -345,8 +345,8 @@ public sealed partial class StationJobsSystem
foreach (var (player, profile) in profiles)
{
var roleBans = _banManager.GetJobBans(player);
var profileJobs = profile.JobPriorities.Keys.ToList();
_playTime.RemoveDisallowedJobs(player, ref profileJobs);
var profileJobs = profile.JobPriorities.Keys.Select(k => new ProtoId<JobPrototype>(k)).ToList();
_playTime.RemoveDisallowedJobs(player, profileJobs);
List<string>? availableJobs = null;

View File

@@ -43,7 +43,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
{
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.Roles.Contains(job.Prototype) && Blacklist.Contains(department.ID))
if (department.Roles.Contains(job.Prototype.Value) && Blacklist.Contains(department.ID))
return false;
}
}
@@ -56,7 +56,7 @@ public sealed partial class BuyerDepartmentCondition : ListingCondition
{
foreach (var department in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
if (department.Roles.Contains(job.Prototype) && Whitelist.Contains(department.ID))
if (department.Roles.Contains(job.Prototype.Value) && Whitelist.Contains(department.ID))
{
found = true;
break;

View File

@@ -1,3 +1,5 @@
using Content.Shared.Maps;
using Content.Shared.Roles;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -235,6 +237,12 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool>
GameRoleTimers = CVarDef.Create("game.role_timers", true, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Override default role requirements using a <see cref="JobRequirementOverridePrototype"/>
/// </summary>
public static readonly CVarDef<string>
GameRoleTimerOverride = CVarDef.Create("game.role_timer_override", "", CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Whether or not disconnecting inside of a cryopod should remove the character or just store them until they reconnect.
/// </summary>

View File

@@ -0,0 +1,38 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Ghost.Roles;
/// <summary>
/// For selectable ghostrole prototypes in ghostrole spawners.
/// </summary>
[Prototype]
public sealed partial class GhostRolePrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
/// <summary>
/// The name of the ghostrole.
/// </summary>
[DataField(required: true)]
public string Name { get; set; } = default!;
/// <summary>
/// The description of the ghostrole.
/// </summary>
[DataField(required: true)]
public string Description { get; set; } = default!;
/// <summary>
/// The entity prototype of the ghostrole
/// </summary>
[DataField(required: true)]
public EntProtoId EntityPrototype;
/// <summary>
/// Rules of the ghostrole
/// </summary>
[DataField(required: true)]
public string Rules = default!;
}

View File

@@ -11,6 +11,11 @@ namespace Content.Shared.Ghost.Roles
public string Name { get; set; }
public string Description { get; set; }
public string Rules { get; set; }
// TODO ROLE TIMERS
// Actually make use of / enforce this requirement?
// Why is this even here.
// Move to ghost role prototype & respect CCvars.GameRoleTimerOverride
public HashSet<JobRequirement>? Requirements { get; set; }
/// <inheritdoc cref="GhostRoleKind"/>

View File

@@ -707,7 +707,7 @@ namespace Content.Shared.Preferences
continue;
}
loadouts.EnsureValid(session, collection);
loadouts.EnsureValid(this, session, collection);
}
foreach (var value in toRemove)

View File

@@ -13,13 +13,13 @@ public sealed partial class GroupLoadoutEffect : LoadoutEffect
[DataField(required: true)]
public ProtoId<LoadoutEffectGroupPrototype> Proto;
public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
{
var effectsProto = collection.Resolve<IPrototypeManager>().Index(Proto);
foreach (var effect in effectsProto.Effects)
{
if (!effect.Validate(loadout, session, collection, out reason))
if (!effect.Validate(profile, loadout, session, collection, out reason))
return false;
}

View File

@@ -15,12 +15,14 @@ public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect
[DataField(required: true)]
public JobRequirement Requirement = default!;
public override bool Validate(RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
public override bool Validate(HumanoidCharacterProfile profile, RoleLoadout loadout, ICommonSession session, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
{
var manager = collection.Resolve<ISharedPlaytimeManager>();
var playtimes = manager.GetPlayTimes(session);
return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason,
collection.Resolve<IEntityManager>(),
collection.Resolve<IPrototypeManager>());
return Requirement.Check(collection.Resolve<IEntityManager>(),
collection.Resolve<IPrototypeManager>(),
profile,
playtimes,
out reason);
}
}

View File

@@ -11,6 +11,7 @@ public abstract partial class LoadoutEffect
/// Tries to validate the effect.
/// </summary>
public abstract bool Validate(
HumanoidCharacterProfile profile,
RoleLoadout loadout,
ICommonSession session,
IDependencyCollection collection,

View File

@@ -11,6 +11,7 @@ public sealed partial class PointsCostLoadoutEffect : LoadoutEffect
public int Cost = 1;
public override bool Validate(
HumanoidCharacterProfile profile,
RoleLoadout loadout,
ICommonSession session,
IDependencyCollection collection,

View File

@@ -44,7 +44,7 @@ public sealed class RoleLoadout
/// <summary>
/// Ensures all prototypes exist and effects can be applied.
/// </summary>
public void EnsureValid(ICommonSession session, IDependencyCollection collection)
public void EnsureValid(HumanoidCharacterProfile profile, ICommonSession session, IDependencyCollection collection)
{
var groupRemove = new ValueList<string>();
var protoManager = collection.Resolve<IPrototypeManager>();
@@ -81,7 +81,7 @@ public sealed class RoleLoadout
}
// Validate the loadout can be applied (e.g. points).
if (!IsValid(session, loadout.Prototype, collection, out _))
if (!IsValid(profile, session, loadout.Prototype, collection, out _))
{
loadouts.RemoveAt(i);
continue;
@@ -167,7 +167,7 @@ public sealed class RoleLoadout
/// <summary>
/// Returns whether a loadout is valid or not.
/// </summary>
public bool IsValid(ICommonSession session, ProtoId<ItemLoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
public bool IsValid(HumanoidCharacterProfile profile, ICommonSession session, ProtoId<ItemLoadoutPrototype> loadout, IDependencyCollection collection, [NotNullWhen(false)] out FormattedMessage? reason)
{
reason = null;
@@ -190,7 +190,7 @@ public sealed class RoleLoadout
foreach (var effect in loadoutProto.Effects)
{
valid = valid && effect.Validate(this, session, collection, out reason);
valid = valid && effect.Validate(profile, this, session, collection, out reason);
}
return valid;

View File

@@ -41,6 +41,8 @@ public sealed partial class AntagPrototype : IPrototype
/// <summary>
/// Requirements that must be met to opt in to this antag role.
/// </summary>
[DataField("requirements")]
// TODO ROLE TIMERS
// Actually check if the requirements are met. Because apparently this is actually unused.
[DataField]
public HashSet<JobRequirement>? Requirements;
}

View File

@@ -9,16 +9,16 @@ public sealed partial class DepartmentPrototype : IPrototype
[IdDataField] public string ID { get; } = default!;
/// <summary>
/// A name string.
/// The name LocId of the department that will be displayed in the various menus.
/// </summary>
[DataField(required: true)]
public string Name = default!;
public LocId Name = string.Empty;
/// <summary>
/// A description string to display in the character menu as an explanation of the department's function.
/// A description LocId to display in the character menu as an explanation of the department's function.
/// </summary>
[DataField (required: true)]
public string Description = default!;
[DataField(required: true)]
public LocId Description = string.Empty;
/// <summary>
/// A color representing this department to use for text.
@@ -26,8 +26,8 @@ public sealed partial class DepartmentPrototype : IPrototype
[DataField(required: true)]
public Color Color = default!;
[ViewVariables(VVAccess.ReadWrite), DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<JobPrototype>))]
public List<string> Roles = new();
[DataField, ViewVariables(VVAccess.ReadWrite)]
public List<ProtoId<JobPrototype>> Roles = new();
/// <summary>
/// Whether this is a primary department or not.

View File

@@ -0,0 +1,50 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Preferences;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
/// <summary>
/// Requires the character to be older or younger than a certain age (inclusive)
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class AgeRequirement : JobRequirement
{
[DataField(required: true)]
public int RequiredAge;
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
if (profile is null) //the profile could be null if the player is a ghost. In this case we don't need to block the role selection for ghostrole
return true;
if (!Inverted)
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-young",
("age", RequiredAge)));
if (profile.Age <= RequiredAge)
return false;
}
else
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-age-to-old",
("age", RequiredAge)));
if (profile.Age >= RequiredAge)
return false;
}
return true;
}
}

View File

@@ -0,0 +1,83 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Preferences;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class DepartmentTimeRequirement : JobRequirement
{
/// <summary>
/// Which department needs the required amount of time.
/// </summary>
[DataField(required: true)]
public ProtoId<DepartmentPrototype> Department = default!;
/// <summary>
/// How long (in seconds) this requirement is.
/// </summary>
[DataField(required: true)]
public TimeSpan Time;
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
var playtime = TimeSpan.Zero;
// Check all jobs' departments
var department = protoManager.Index(Department);
var jobs = department.Roles;
string proto;
// Check all jobs' playtime
foreach (var other in jobs)
{
// The schema is stored on the Job role but we want to explode if the timer isn't found anyway.
proto = protoManager.Index(other).PlayTimeTracker;
playTimes.TryGetValue(proto, out var otherTime);
playtime += otherTime;
}
var deptDiff = Time.TotalMinutes - playtime.TotalMinutes;
var nameDepartment = "role-timer-department-unknown";
if (protoManager.TryIndex(Department, out var departmentIndexed))
{
nameDepartment = departmentIndexed.Name;
}
if (!Inverted)
{
if (deptDiff <= 0)
return true;
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-department-insufficient",
("time", Math.Ceiling(deptDiff)),
("department", Loc.GetString(nameDepartment)),
("departmentColor", department.Color.ToHex())));
return false;
}
if (deptDiff <= 0)
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-department-too-high",
("time", -deptDiff),
("department", Loc.GetString(nameDepartment)),
("departmentColor", department.Color.ToHex())));
return false;
}
return true;
}
}

View File

@@ -0,0 +1,50 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Preferences;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class OverallPlaytimeRequirement : JobRequirement
{
/// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
[DataField(required: true)]
public TimeSpan Time;
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
var overallDiff = Time.TotalMinutes - overallTime.TotalMinutes;
if (!Inverted)
{
if (overallDiff <= 0 || overallTime >= Time)
return true;
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-overall-insufficient",
("time", Math.Ceiling(overallDiff))));
return false;
}
if (overallDiff <= 0 || overallTime >= Time)
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-overall-too-high",
("time", -overallDiff)));
return false;
}
return true;
}
}

View File

@@ -0,0 +1,73 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Preferences;
using Content.Shared.Roles.Jobs;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class RoleTimeRequirement : JobRequirement
{
/// <summary>
/// What particular role they need the time requirement with.
/// </summary>
[DataField(required: true)]
public ProtoId<PlayTimeTrackerPrototype> Role = default!;
/// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
[DataField(required: true)]
public TimeSpan Time;
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
string proto = Role;
playTimes.TryGetValue(proto, out var roleTime);
var roleDiff = Time.TotalMinutes - roleTime.TotalMinutes;
var departmentColor = Color.Yellow;
if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
{
var jobProto = jobSystem.GetJobPrototype(proto);
if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
departmentColor = departmentProto.Color;
}
if (!Inverted)
{
if (roleDiff <= 0)
return true;
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-role-insufficient",
("time", Math.Ceiling(roleDiff)),
("job", Loc.GetString(proto)),
("departmentColor", departmentColor.ToHex())));
return false;
}
if (roleDiff <= 0)
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString(
"role-timer-role-too-high",
("time", -roleDiff),
("job", Loc.GetString(proto)),
("departmentColor", departmentColor.ToHex())));
return false;
}
return true;
}
}

View File

@@ -0,0 +1,59 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Roles;
/// <summary>
/// Requires the character to be or not be on the list of specified species
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class SpeciesRequirement : JobRequirement
{
[DataField(required: true)]
public HashSet<ProtoId<SpeciesPrototype>> Species = new();
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
if (profile is null) //the profile could be null if the player is a ghost. In this case we don't need to block the role selection for ghostrole
return true;
var sb = new StringBuilder();
sb.Append("[color=yellow]");
foreach (var s in Species)
{
sb.Append(Loc.GetString(protoManager.Index(s).Name) + " ");
}
sb.Append("[/color]");
if (!Inverted)
{
reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-whitelisted-species")}\n{sb}");
if (!Species.Contains(profile.Species))
return false;
}
else
{
reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-blacklisted-species")}\n{sb}");
if (Species.Contains(profile.Species))
return false;
}
return true;
}
}

View File

@@ -0,0 +1,20 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Roles;
/// <summary>
/// Collection of job, antag, and ghost-role job requirements for per-server requirement overrides.
/// </summary>
[Prototype]
public sealed partial class JobRequirementOverridePrototype : IPrototype
{
[ViewVariables]
[IdDataField]
public string ID { get; private set; } = default!;
[DataField]
public Dictionary<ProtoId<JobPrototype>, HashSet<JobRequirement>> Jobs = new ();
[DataField]
public Dictionary<ProtoId<AntagPrototype>, HashSet<JobRequirement>> Antags = new ();
}

View File

@@ -1,226 +1,51 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles.Jobs;
using JetBrains.Annotations;
using Content.Shared.Preferences;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
namespace Content.Shared.Roles
namespace Content.Shared.Roles;
public static class JobRequirements
{
/// <summary>
/// Abstract class for playtime and other requirements for role gates.
/// </summary>
[ImplicitDataDefinitionForInheritors]
[Serializable, NetSerializable]
public abstract partial class JobRequirement{}
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class DepartmentTimeRequirement : JobRequirement
public static bool TryRequirementsMet(
JobPrototype job,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason,
IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile)
{
/// <summary>
/// Which department needs the required amount of time.
/// </summary>
[DataField("department", customTypeSerializer: typeof(PrototypeIdSerializer<DepartmentPrototype>))]
public string Department = default!;
/// <summary>
/// How long (in seconds) this requirement is.
/// </summary>
[DataField("time")] public TimeSpan Time;
/// <summary>
/// If true, requirement will return false if playtime above the specified time.
/// </summary>
/// <value>
/// <c>False</c> by default.<br />
/// <c>True</c> for invert general requirement
/// </value>
[DataField("inverted")] public bool Inverted;
}
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class RoleTimeRequirement : JobRequirement
{
/// <summary>
/// What particular role they need the time requirement with.
/// </summary>
[DataField("role", customTypeSerializer: typeof(PrototypeIdSerializer<PlayTimeTrackerPrototype>))]
public string Role = default!;
/// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
[DataField("time")] public TimeSpan Time;
/// <inheritdoc cref="DepartmentTimeRequirement.Inverted"/>
[DataField("inverted")] public bool Inverted;
}
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class OverallPlaytimeRequirement : JobRequirement
{
/// <inheritdoc cref="DepartmentTimeRequirement.Time"/>
[DataField("time")] public TimeSpan Time;
/// <inheritdoc cref="DepartmentTimeRequirement.Inverted"/>
[DataField("inverted")] public bool Inverted;
}
public static class JobRequirements
{
public static bool TryRequirementsMet(
JobPrototype job,
Dictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason,
IEntityManager entManager,
IPrototypeManager prototypes)
{
reason = null;
if (job.Requirements == null)
return true;
foreach (var requirement in job.Requirements)
{
if (!TryRequirementMet(requirement, playTimes, out reason, entManager, prototypes))
return false;
}
var sys = entManager.System<SharedRoleSystem>();
var requirements = sys.GetJobRequirement(job);
reason = null;
if (requirements == null)
return true;
}
/// <summary>
/// Returns a string with the reason why a particular requirement may not be met.
/// </summary>
public static bool TryRequirementMet(
JobRequirement requirement,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason,
IEntityManager entManager,
IPrototypeManager prototypes)
foreach (var requirement in requirements)
{
reason = null;
switch (requirement)
{
case DepartmentTimeRequirement deptRequirement:
var playtime = TimeSpan.Zero;
// Check all jobs' departments
var department = prototypes.Index<DepartmentPrototype>(deptRequirement.Department);
var jobs = department.Roles;
string proto;
// Check all jobs' playtime
foreach (var other in jobs)
{
// The schema is stored on the Job role but we want to explode if the timer isn't found anyway.
proto = prototypes.Index<JobPrototype>(other).PlayTimeTracker;
playTimes.TryGetValue(proto, out var otherTime);
playtime += otherTime;
}
var deptDiff = deptRequirement.Time.TotalMinutes - playtime.TotalMinutes;
if (!deptRequirement.Inverted)
{
if (deptDiff <= 0)
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-department-insufficient",
("time", Math.Ceiling(deptDiff)),
("department", Loc.GetString(deptRequirement.Department)),
("departmentColor", department.Color.ToHex())));
return false;
}
else
{
if (deptDiff <= 0)
{
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-department-too-high",
("time", -deptDiff),
("department", Loc.GetString(deptRequirement.Department)),
("departmentColor", department.Color.ToHex())));
return false;
}
return true;
}
case OverallPlaytimeRequirement overallRequirement:
var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
var overallDiff = overallRequirement.Time.TotalMinutes - overallTime.TotalMinutes;
if (!overallRequirement.Inverted)
{
if (overallDiff <= 0 || overallTime >= overallRequirement.Time)
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-overall-insufficient",
("time", Math.Ceiling(overallDiff))));
return false;
}
else
{
if (overallDiff <= 0 || overallTime >= overallRequirement.Time)
{
reason = FormattedMessage.FromMarkup(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff)));
return false;
}
return true;
}
case RoleTimeRequirement roleRequirement:
proto = roleRequirement.Role;
playTimes.TryGetValue(proto, out var roleTime);
var roleDiff = roleRequirement.Time.TotalMinutes - roleTime.TotalMinutes;
var departmentColor = Color.Yellow;
if (entManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
{
var jobProto = jobSystem.GetJobPrototype(proto);
if (jobSystem.TryGetDepartment(jobProto, out var departmentProto))
departmentColor = departmentProto.Color;
}
if (!roleRequirement.Inverted)
{
if (roleDiff <= 0)
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-role-insufficient",
("time", Math.Ceiling(roleDiff)),
("job", Loc.GetString(proto)),
("departmentColor", departmentColor.ToHex())));
return false;
}
else
{
if (roleDiff <= 0)
{
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-role-too-high",
("time", -roleDiff),
("job", Loc.GetString(proto)),
("departmentColor", departmentColor.ToHex())));
return false;
}
return true;
}
default:
throw new NotImplementedException();
}
if (!requirement.Check(entManager, protoManager, profile, playTimes, out reason))
return false;
}
return true;
}
}
/// <summary>
/// Abstract class for playtime and other requirements for role gates.
/// </summary>
[ImplicitDataDefinitionForInheritors]
[Serializable, NetSerializable]
public abstract partial class JobRequirement
{
[DataField]
public bool Inverted;
public abstract bool Check(
IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason);
}

View File

@@ -1,9 +1,12 @@
using Content.Shared.Administration.Logs;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Ghost.Roles;
using Content.Shared.Mind;
using Content.Shared.Roles.Jobs;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -15,14 +18,30 @@ public abstract class SharedRoleSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedMindSystem _minds = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
// TODO please lord make role entities
private readonly HashSet<Type> _antagTypes = new();
private JobRequirementOverridePrototype? _requirementOverride;
public override void Initialize()
{
// TODO make roles entities
SubscribeLocalEvent<JobComponent, MindGetAllRolesEvent>(OnJobGetAllRoles);
Subs.CVar(_cfg, CCVars.GameRoleTimerOverride, SetRequirementOverride, true);
}
private void SetRequirementOverride(string value)
{
if (string.IsNullOrEmpty(value))
{
_requirementOverride = null;
return;
}
if (!_prototypes.TryIndex(value, out _requirementOverride ))
Log.Error($"Unknown JobRequirementOverridePrototype: {value}");
}
private void OnJobGetAllRoles(EntityUid uid, JobComponent component, ref MindGetAllRolesEvent args)
@@ -186,4 +205,36 @@ public abstract class SharedRoleSystem : EntitySystem
if (Resolve(mindId, ref mind) && mind.Session != null)
_audio.PlayGlobal(sound, mind.Session);
}
public HashSet<JobRequirement>? GetJobRequirement(JobPrototype job)
{
if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job.ID, out var req))
return req;
return job.Requirements;
}
public HashSet<JobRequirement>? GetJobRequirement(ProtoId<JobPrototype> job)
{
if (_requirementOverride != null && _requirementOverride.Jobs.TryGetValue(job, out var req))
return req;
return _prototypes.Index(job).Requirements;
}
public HashSet<JobRequirement>? GetAntagRequirement(ProtoId<AntagPrototype> antag)
{
if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag, out var req))
return req;
return _prototypes.Index(antag).Requirements;
}
public HashSet<JobRequirement>? GetAntagRequirement(AntagPrototype antag)
{
if (_requirementOverride != null && _requirementOverride.Antags.TryGetValue(antag.ID, out var req))
return req;
return antag.Requirements;
}
}

View File

@@ -0,0 +1,68 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Shared.Enums;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared._White.JobRequirements;
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class GenderRequirement : JobRequirement
{
[DataField(required: true)]
public HashSet<Gender> RequiredGenders;
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
if (profile is null)
return true;
var sb = new StringBuilder();
sb.Append("[color=yellow]");
foreach (var g in RequiredGenders)
{
sb.Append(GenderToString(g) + " ");
}
sb.Append("[/color]");
if (!Inverted)
{
reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-gender-whitelisted")}\n{sb}");
if (!RequiredGenders.Contains(profile.Gender))
return false;
}
else
{
reason = FormattedMessage.FromMarkupPermissive($"{Loc.GetString("role-timer-gender-blacklisted")}\n{sb}");
if (RequiredGenders.Contains(profile.Gender))
return false;
}
return true;
}
private string GenderToString(Gender gender)
{
return gender switch
{
Gender.Male => Loc.GetString("humanoid-profile-editor-pronouns-male-text"),
Gender.Female => Loc.GetString("humanoid-profile-editor-pronouns-female-text"),
Gender.Epicene => Loc.GetString("humanoid-profile-editor-pronouns-epicene-text"),
_ => Loc.GetString("humanoid-profile-editor-pronouns-neuter-text")
};
}
}

View File

@@ -0,0 +1,59 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Humanoid;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared._White.JobRequirements;
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class SexRequirement : JobRequirement
{
[DataField(required: true)]
public Sex RequiredSex;
public override bool Check(IEntityManager entManager,
IPrototypeManager protoManager,
HumanoidCharacterProfile? profile,
IReadOnlyDictionary<string, TimeSpan> playTimes,
[NotNullWhen(false)] out FormattedMessage? reason)
{
reason = new FormattedMessage();
if (profile is null)
return true;
if (!Inverted)
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-sex-whitelisted",
("sex", SexToString())));
if (profile.Sex != RequiredSex)
return false;
}
else
{
reason = FormattedMessage.FromMarkupPermissive(Loc.GetString("role-timer-sex-blacklisted",
("sex", SexToString())));
if (profile.Sex == RequiredSex)
return false;
}
return true;
}
private string SexToString()
{
return RequiredSex switch
{
Sex.Male => Loc.GetString("role-timer-sex-male"),
Sex.Female => Loc.GetString("role-timer-sex-female"),
_ => Loc.GetString("role-timer-sex-unsexed")
};
}
}

View File

@@ -164,7 +164,7 @@ public sealed class WhiteCVars
*/
public static readonly CVarDef<bool> FanaticXenophobiaEnabled =
CVarDef.Create("white.fanatic_xenophobia", true, CVar.SERVERONLY | CVar.ARCHIVE);
CVarDef.Create("white.fanatic_xenophobia", false, CVar.SERVERONLY | CVar.ARCHIVE);
/*
* MeatyOre

View File

@@ -4,7 +4,13 @@ role-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time, "0"
role-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime to play this role. (Are you trying to play a trainee role?)
role-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color] to play this role.
role-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color] to play this role. (Are you trying to play a trainee role?)
role-timer-age-to-old = Your character must be under the age of [color=yellow]{$age}[/color] to play this role.
role-timer-age-to-young = Your character must be over the age of [color=yellow]{$age}[/color] to play this role.
role-timer-whitelisted-species = Your character must be one of the following species to play this role:
role-timer-blacklisted-species = Your character must not be one of the following species to play this role:
role-timer-locked = Locked (hover for details)
role-timer-department-unknown = Unknown Department
role-ban = You have been banned from this role.

View File

@@ -0,0 +1,16 @@
role-timer-age-to-old = Для игры на этой роли вашему персонажу должно быть меньше [color=yellow]{$age}[/color] лет.
role-timer-age-to-young = Для игры на этой роли вашему персонажу должно быть больше [color=yellow]{$age}[/color] лет.
role-timer-whitelisted-species = Для игры на этой роли ваш персонаж должен быть одной из следующих рас:
role-timer-blacklisted-species = Для игры на этой роли ваш персонаж не должен быть одной из следующих рас:
role-timer-sex-whitelisted = Для игры на этой роли ваш персонаж должен быть [color=yellow]{$sex}[/color].
role-timer-sex-blacklisted = Для игры на этой роли ваш персонаж не должен быть [color=yellow]{$sex}[/color].
role-timer-sex-male = мужского пола
role-timer-sex-female = женского пола
role-timer-sex-unsexed = бесполым
role-timer-gender-whitelisted = Для игры на этой роли ваш персонаж должен иметь одно из следующих местоимений:
role-timer-gender-blacklisted = Для игры на этой роли ваш персонаж не должен иметь следующие местоимения:
role-timer-department-unknown = Unknown Department

View File

@@ -12,6 +12,13 @@
time: 18000
- !type:OverallPlaytimeRequirement
time: 108000
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
- Dwarf
- Felinid
weight: 10
icon: "JobIconQuarterMaster"
arrivalNotificationPrototype: QuartermasterArrivalNotification

View File

@@ -16,6 +16,12 @@
- !type:DepartmentTimeRequirement
department: Command
time: 18000 # 15 3
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
- Felinid
weight: 20
icon: "JobIconCaptain"
requireAdminNotify: true

View File

@@ -16,6 +16,12 @@
- !type:DepartmentTimeRequirement
department: Command
time: 3600
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
- Felinid
weight: 20
icon: "JobIconHeadOfPersonnel"
requireAdminNotify: true

View File

@@ -12,6 +12,13 @@
time: 18000
- !type:OverallPlaytimeRequirement
time: 108000
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
- Dwarf
- Felinid
weight: 10
icon: "JobIconChiefEngineer"
requireAdminNotify: true

View File

@@ -12,6 +12,8 @@
- !type:DepartmentTimeRequirement
department: Security
time: 36000 #10 hrs
- !type:AgeRequirement
requiredAge: 20
icon: "JobIconInspector"
arrivalNotificationPrototype: InspectorArrivalNotification
supervisors: job-supervisors-captain

View File

@@ -11,6 +11,12 @@
time: 18000
- !type:OverallPlaytimeRequirement
time: 108000
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
- Felinid
weight: 10
icon: "JobIconChiefMedicalOfficer"
arrivalNotificationPrototype: ChiefMedicalOfficerArrivalNotification

View File

@@ -9,6 +9,12 @@
time: 18000
- !type:OverallPlaytimeRequirement
time: 108000
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
- Felinid
weight: 10
icon: "JobIconResearchDirector"
requireAdminNotify: true

View File

@@ -7,6 +7,11 @@
- !type:DepartmentTimeRequirement
department: Security
time: 54000 # 15 hours
- !type:SpeciesRequirement
species:
- Human
- Dwarf
- Felinid
icon: "JobIconDetective"
supervisors: job-supervisors-hos
whitelistedSpecies:

View File

@@ -12,6 +12,11 @@
time: 18000
- !type:OverallPlaytimeRequirement
time: 108000
- !type:AgeRequirement
requiredAge: 20
- !type:SpeciesRequirement
species:
- Human
weight: 10
icon: "JobIconHeadOfSecurity"
requireAdminNotify: true

View File

@@ -10,6 +10,9 @@
department: Security
time: 54000 #15 hrs
inverted: true # stop playing intern if you're good at security!
- !type:SpeciesRequirement
species:
- Human
icon: "JobIconSecurityCadet"
supervisors: job-supervisors-security
whitelistedSpecies:

View File

@@ -7,6 +7,9 @@
- !type:DepartmentTimeRequirement
department: Security
time: 36000 #10 hrs
- !type:SpeciesRequirement
species:
- Human
icon: "JobIconSecurityOfficer"
supervisors: job-supervisors-hos
whitelistedSpecies:

View File

@@ -16,6 +16,9 @@
- !type:DepartmentTimeRequirement
department: Security
time: 216000 # 60 hrs
- !type:SpeciesRequirement
species:
- Human
icon: "JobIconSeniorOfficer"
supervisors: job-supervisors-hos
whitelistedSpecies:

View File

@@ -7,6 +7,9 @@
- !type:RoleTimeRequirement
role: JobSecurityOfficer
time: 36000 #10 hrs
- !type:SpeciesRequirement
species:
- Human
icon: "JobIconWarden"
supervisors: job-supervisors-hos
whitelistedSpecies:

View File

@@ -0,0 +1,16 @@
- type: jobRequirementOverride
id: Reduced
jobs:
Captain:
- !type:DepartmentTimeRequirement
department: Engineering
time: 3600 # 1 hours
- !type:DepartmentTimeRequirement
department: Medical
time: 3600 # 1 hours
- !type:DepartmentTimeRequirement
department: Security
time: 3600 # 1 hours
- !type:DepartmentTimeRequirement
department: Command
time: 3600 # 1 hour