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:
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -386,4 +386,4 @@ public sealed class AntagSelectionSystem : GameRuleSystem<GameRuleComponent>
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
38
Content.Shared/Ghost/Roles/GhostRolePrototype.cs
Normal file
38
Content.Shared/Ghost/Roles/GhostRolePrototype.cs
Normal 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!;
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -707,7 +707,7 @@ namespace Content.Shared.Preferences
|
||||
continue;
|
||||
}
|
||||
|
||||
loadouts.EnsureValid(session, collection);
|
||||
loadouts.EnsureValid(this, session, collection);
|
||||
}
|
||||
|
||||
foreach (var value in toRemove)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
50
Content.Shared/Roles/JobRequirement/AgeRequirement.cs
Normal file
50
Content.Shared/Roles/JobRequirement/AgeRequirement.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
73
Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs
Normal file
73
Content.Shared/Roles/JobRequirement/RoleTimeRequirement.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
59
Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs
Normal file
59
Content.Shared/Roles/JobRequirement/SpeciesRequirement.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
20
Content.Shared/Roles/JobRequirementOverridePrototype.cs
Normal file
20
Content.Shared/Roles/JobRequirementOverridePrototype.cs
Normal 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 ();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
68
Content.Shared/_White/JobRequirements/GenderRequirement.cs
Normal file
68
Content.Shared/_White/JobRequirements/GenderRequirement.cs
Normal 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")
|
||||
};
|
||||
}
|
||||
}
|
||||
59
Content.Shared/_White/JobRequirements/SexRequirement.cs
Normal file
59
Content.Shared/_White/JobRequirements/SexRequirement.cs
Normal 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")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
16
Resources/Locale/ru-RU/job/role-requirements.ftl
Normal file
16
Resources/Locale/ru-RU/job/role-requirements.ftl
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
- !type:DepartmentTimeRequirement
|
||||
department: Security
|
||||
time: 36000 #10 hrs
|
||||
- !type:AgeRequirement
|
||||
requiredAge: 20
|
||||
icon: "JobIconInspector"
|
||||
arrivalNotificationPrototype: InspectorArrivalNotification
|
||||
supervisors: job-supervisors-captain
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,6 +12,11 @@
|
||||
time: 18000
|
||||
- !type:OverallPlaytimeRequirement
|
||||
time: 108000
|
||||
- !type:AgeRequirement
|
||||
requiredAge: 20
|
||||
- !type:SpeciesRequirement
|
||||
species:
|
||||
- Human
|
||||
weight: 10
|
||||
icon: "JobIconHeadOfSecurity"
|
||||
requireAdminNotify: true
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
- !type:DepartmentTimeRequirement
|
||||
department: Security
|
||||
time: 36000 #10 hrs
|
||||
- !type:SpeciesRequirement
|
||||
species:
|
||||
- Human
|
||||
icon: "JobIconSecurityOfficer"
|
||||
supervisors: job-supervisors-hos
|
||||
whitelistedSpecies:
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
- !type:DepartmentTimeRequirement
|
||||
department: Security
|
||||
time: 216000 # 60 hrs
|
||||
- !type:SpeciesRequirement
|
||||
species:
|
||||
- Human
|
||||
icon: "JobIconSeniorOfficer"
|
||||
supervisors: job-supervisors-hos
|
||||
whitelistedSpecies:
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
- !type:RoleTimeRequirement
|
||||
role: JobSecurityOfficer
|
||||
time: 36000 #10 hrs
|
||||
- !type:SpeciesRequirement
|
||||
species:
|
||||
- Human
|
||||
icon: "JobIconWarden"
|
||||
supervisors: job-supervisors-hos
|
||||
whitelistedSpecies:
|
||||
|
||||
16
Resources/Prototypes/Roles/requirement_overrides.yml
Normal file
16
Resources/Prototypes/Roles/requirement_overrides.yml
Normal 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
|
||||
Reference in New Issue
Block a user