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!
This commit is contained in:
@@ -2,9 +2,11 @@ using System.Linq;
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Client.CrewManifest;
|
using Content.Client.CrewManifest;
|
||||||
using Content.Client.GameTicking.Managers;
|
using Content.Client.GameTicking.Managers;
|
||||||
|
using Content.Client.Lobby;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Client.Players.PlayTimeTracking;
|
using Content.Client.Players.PlayTimeTracking;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Client.Console;
|
using Robust.Client.Console;
|
||||||
@@ -26,6 +28,7 @@ namespace Content.Client.LateJoin
|
|||||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||||
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
|
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
|
||||||
|
[Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
|
||||||
|
|
||||||
public event Action<(NetEntity, string)> SelectedId;
|
public event Action<(NetEntity, string)> SelectedId;
|
||||||
|
|
||||||
@@ -254,7 +257,7 @@ namespace Content.Client.LateJoin
|
|||||||
|
|
||||||
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
|
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;
|
jobButton.Disabled = true;
|
||||||
|
|
||||||
|
|||||||
@@ -649,7 +649,7 @@ namespace Content.Client.Lobby.UI
|
|||||||
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
selector.Select(Profile?.AntagPreferences.Contains(antag.ID) == true ? 0 : 1);
|
||||||
|
|
||||||
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
|
var requirements = _entManager.System<SharedRoleSystem>().GetAntagRequirement(antag);
|
||||||
if (!_requirements.CheckRoleTime(requirements, out var reason))
|
if (!_requirements.CheckRoleRequirements(requirements, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
|
||||||
{
|
{
|
||||||
selector.LockRequirements(reason);
|
selector.LockRequirements(reason);
|
||||||
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
Profile = Profile?.WithAntagPreference(antag.ID, false);
|
||||||
@@ -903,7 +903,7 @@ namespace Content.Client.Lobby.UI
|
|||||||
icon.Texture = jobIcon.Icon.Frame0();
|
icon.Texture = jobIcon.Icon.Frame0();
|
||||||
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
|
selector.Setup(items, job.LocalizedName, 200, job.LocalizedDescription, icon, job.Guides);
|
||||||
|
|
||||||
if (!_requirements.IsAllowed(job, out var reason))
|
if (!_requirements.IsAllowed(job, (HumanoidCharacterProfile?)_preferencesManager.Preferences?.SelectedCharacter, out var reason))
|
||||||
{
|
{
|
||||||
selector.LockRequirements(reason);
|
selector.LockRequirements(reason);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Content.Client.Lobby;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
using Content.Shared.Players.JobWhitelist;
|
using Content.Shared.Players.JobWhitelist;
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
@@ -89,7 +91,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
Updated?.Invoke();
|
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;
|
reason = null;
|
||||||
|
|
||||||
@@ -106,16 +108,16 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
if (player == null)
|
if (player == null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return CheckRoleTime(job, out reason);
|
return CheckRoleRequirements(job, profile, out reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckRoleTime(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
|
public bool CheckRoleRequirements(JobPrototype job, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||||
{
|
{
|
||||||
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
|
var reqs = _entManager.System<SharedRoleSystem>().GetJobRequirement(job);
|
||||||
return CheckRoleTime(reqs, out reason);
|
return CheckRoleRequirements(reqs, profile, out reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
|
public bool CheckRoleRequirements(HashSet<JobRequirement>? requirements, HumanoidCharacterProfile? profile, [NotNullWhen(false)] out FormattedMessage? reason)
|
||||||
{
|
{
|
||||||
reason = null;
|
reason = null;
|
||||||
|
|
||||||
@@ -125,7 +127,7 @@ public sealed class JobRequirementsManager : ISharedPlaytimeManager
|
|||||||
var reasons = new List<string>();
|
var reasons = new List<string>();
|
||||||
foreach (var requirement in requirements)
|
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;
|
continue;
|
||||||
|
|
||||||
reasons.Add(jobReason.ToMarkup());
|
reasons.Add(jobReason.ToMarkup());
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace Content.Client.UserInterface.Systems.Ghost.Controls.Roles
|
|||||||
bool hasAccess = true;
|
bool hasAccess = true;
|
||||||
FormattedMessage? reason;
|
FormattedMessage? reason;
|
||||||
|
|
||||||
if (!requirementsManager.CheckRoleTime(group.Key.Requirements, out reason))
|
if (!requirementsManager.CheckRoleRequirements(group.Key.Requirements, null, out reason))
|
||||||
{
|
{
|
||||||
hasAccess = false;
|
hasAccess = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Content.Server.Afk.Events;
|
|||||||
using Content.Server.GameTicking;
|
using Content.Server.GameTicking;
|
||||||
using Content.Server.GameTicking.Events;
|
using Content.Server.GameTicking.Events;
|
||||||
using Content.Server.Mind;
|
using Content.Server.Mind;
|
||||||
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Server.Station.Events;
|
using Content.Server.Station.Events;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
@@ -13,6 +14,7 @@ using Content.Shared.Mobs;
|
|||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
@@ -35,6 +37,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
[Dependency] private readonly MindSystem _minds = default!;
|
[Dependency] private readonly MindSystem _minds = default!;
|
||||||
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
|
[Dependency] private readonly PlayTimeTrackingManager _tracking = default!;
|
||||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||||
|
[Dependency] private readonly IServerPreferencesManager _preferencesManager = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -206,7 +209,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
playTimes = new Dictionary<string, TimeSpan>();
|
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<ProtoId<JobPrototype>> GetDisallowedJobs(ICommonSession player)
|
public HashSet<ProtoId<JobPrototype>> GetDisallowedJobs(ICommonSession player)
|
||||||
@@ -223,7 +226,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
|
|
||||||
foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
|
foreach (var job in _prototypes.EnumeratePrototypes<JobPrototype>())
|
||||||
{
|
{
|
||||||
if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes))
|
if (JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(player.UserId).SelectedCharacter))
|
||||||
roles.Add(job.ID);
|
roles.Add(job.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +249,7 @@ public sealed class PlayTimeTrackingSystem : EntitySystem
|
|||||||
for (var i = 0; i < jobs.Count; i++)
|
for (var i = 0; i < jobs.Count; i++)
|
||||||
{
|
{
|
||||||
if (_prototypes.TryIndex(jobs[i], out var job)
|
if (_prototypes.TryIndex(jobs[i], out var job)
|
||||||
&& JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes))
|
&& JobRequirements.TryRequirementsMet(job, playTimes, out _, EntityManager, _prototypes, (HumanoidCharacterProfile?) _preferencesManager.GetPreferences(userId).SelectedCharacter))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ public sealed partial class JobRequirementLoadoutEffect : LoadoutEffect
|
|||||||
|
|
||||||
var manager = collection.Resolve<ISharedPlaytimeManager>();
|
var manager = collection.Resolve<ISharedPlaytimeManager>();
|
||||||
var playtimes = manager.GetPlayTimes(session);
|
var playtimes = manager.GetPlayTimes(session);
|
||||||
return JobRequirements.TryRequirementMet(Requirement, playtimes, out reason,
|
return Requirement.Check(collection.Resolve<IEntityManager>(),
|
||||||
collection.Resolve<IEntityManager>(),
|
collection.Resolve<IPrototypeManager>(),
|
||||||
collection.Resolve<IPrototypeManager>());
|
profile,
|
||||||
|
playtimes,
|
||||||
|
out reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,16 @@ public sealed partial class DepartmentPrototype : IPrototype
|
|||||||
public string ID { get; } = string.Empty;
|
public string ID { get; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A description string to display in the character menu as an explanation of the department's function.
|
/// The name LocId of the department that will be displayed in the various menus.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField(required: true)]
|
[DataField(required: true)]
|
||||||
public string Description = string.Empty;
|
public LocId Name = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A description LocId to display in the character menu as an explanation of the department's function.
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public LocId Description = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A color representing this department to use for text.
|
/// A color representing this department to use for text.
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,228 +1,51 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
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.Prototypes;
|
||||||
using Robust.Shared.Serialization;
|
using Robust.Shared.Serialization;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Roles
|
namespace Content.Shared.Roles;
|
||||||
|
|
||||||
|
public static class JobRequirements
|
||||||
{
|
{
|
||||||
/// <summary>
|
public static bool TryRequirementsMet(
|
||||||
/// Abstract class for playtime and other requirements for role gates.
|
JobPrototype job,
|
||||||
/// </summary>
|
IReadOnlyDictionary<string, TimeSpan> playTimes,
|
||||||
[ImplicitDataDefinitionForInheritors]
|
[NotNullWhen(false)] out FormattedMessage? reason,
|
||||||
[Serializable, NetSerializable]
|
IEntityManager entManager,
|
||||||
public abstract partial class JobRequirement{}
|
IPrototypeManager protoManager,
|
||||||
|
HumanoidCharacterProfile? profile)
|
||||||
[UsedImplicitly]
|
|
||||||
[Serializable, NetSerializable]
|
|
||||||
public sealed partial class DepartmentTimeRequirement : JobRequirement
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
var sys = entManager.System<SharedRoleSystem>();
|
||||||
/// Which department needs the required amount of time.
|
var requirements = sys.GetJobRequirement(job);
|
||||||
/// </summary>
|
reason = null;
|
||||||
[DataField("department", customTypeSerializer: typeof(PrototypeIdSerializer<DepartmentPrototype>))]
|
if (requirements == null)
|
||||||
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,
|
|
||||||
IReadOnlyDictionary<string, TimeSpan> playTimes,
|
|
||||||
[NotNullWhen(false)] out FormattedMessage? reason,
|
|
||||||
IEntityManager entManager,
|
|
||||||
IPrototypeManager prototypes)
|
|
||||||
{
|
|
||||||
var sys = entManager.System<SharedRoleSystem>();
|
|
||||||
var requirements = sys.GetJobRequirement(job);
|
|
||||||
reason = null;
|
|
||||||
if (requirements == null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
foreach (var requirement in requirements)
|
|
||||||
{
|
|
||||||
if (!TryRequirementMet(requirement, playTimes, out reason, entManager, prototypes))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
foreach (var requirement in requirements)
|
||||||
/// 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)
|
|
||||||
{
|
{
|
||||||
reason = null;
|
if (!requirement.Check(entManager, protoManager, profile, playTimes, out reason))
|
||||||
|
return false;
|
||||||
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.FromMarkupPermissive(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.FromMarkupPermissive(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.FromMarkupPermissive(Loc.GetString(
|
|
||||||
"role-timer-overall-insufficient",
|
|
||||||
("time", Math.Ceiling(overallDiff))));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (overallDiff <= 0 || overallTime >= overallRequirement.Time)
|
|
||||||
{
|
|
||||||
reason = FormattedMessage.FromMarkupPermissive(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.FromMarkupPermissive(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.FromMarkupPermissive(Loc.GetString(
|
|
||||||
"role-timer-role-too-high",
|
|
||||||
("time", -roleDiff),
|
|
||||||
("job", Loc.GetString(proto)),
|
|
||||||
("departmentColor", departmentColor.ToHex())));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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-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-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-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-locked = Locked (hover for details)
|
||||||
|
|
||||||
|
role-timer-department-unknown = Unknown Department
|
||||||
|
|
||||||
role-ban = You have been banned from this role.
|
role-ban = You have been banned from this role.
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
time: 36000 #10 hours
|
time: 36000 #10 hours
|
||||||
- !type:OverallPlaytimeRequirement
|
- !type:OverallPlaytimeRequirement
|
||||||
time: 144000 #40 hrs
|
time: 144000 #40 hrs
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 10
|
weight: 10
|
||||||
startingGear: QuartermasterGear
|
startingGear: QuartermasterGear
|
||||||
icon: "JobIconQuarterMaster"
|
icon: "JobIconQuarterMaster"
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
- !type:DepartmentTimeRequirement
|
- !type:DepartmentTimeRequirement
|
||||||
department: Command
|
department: Command
|
||||||
time: 54000 # 15 hours
|
time: 54000 # 15 hours
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 20
|
weight: 20
|
||||||
startingGear: CaptainGear
|
startingGear: CaptainGear
|
||||||
icon: "JobIconCaptain"
|
icon: "JobIconCaptain"
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
- !type:DepartmentTimeRequirement
|
- !type:DepartmentTimeRequirement
|
||||||
department: Command
|
department: Command
|
||||||
time: 36000 # 10 hours
|
time: 36000 # 10 hours
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 20
|
weight: 20
|
||||||
startingGear: HoPGear
|
startingGear: HoPGear
|
||||||
icon: "JobIconHeadOfPersonnel"
|
icon: "JobIconHeadOfPersonnel"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
time: 36000 #10 hrs
|
time: 36000 #10 hrs
|
||||||
- !type:OverallPlaytimeRequirement
|
- !type:OverallPlaytimeRequirement
|
||||||
time: 144000 #40 hrs
|
time: 144000 #40 hrs
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 10
|
weight: 10
|
||||||
startingGear: ChiefEngineerGear
|
startingGear: ChiefEngineerGear
|
||||||
icon: "JobIconChiefEngineer"
|
icon: "JobIconChiefEngineer"
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
time: 36000 #10 hrs
|
time: 36000 #10 hrs
|
||||||
- !type:OverallPlaytimeRequirement
|
- !type:OverallPlaytimeRequirement
|
||||||
time: 144000 #40 hrs
|
time: 144000 #40 hrs
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 10
|
weight: 10
|
||||||
startingGear: CMOGear
|
startingGear: CMOGear
|
||||||
icon: "JobIconChiefMedicalOfficer"
|
icon: "JobIconChiefMedicalOfficer"
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
time: 36000 #10 hrs
|
time: 36000 #10 hrs
|
||||||
- !type:OverallPlaytimeRequirement
|
- !type:OverallPlaytimeRequirement
|
||||||
time: 144000 #40 hrs
|
time: 144000 #40 hrs
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 10
|
weight: 10
|
||||||
startingGear: ResearchDirectorGear
|
startingGear: ResearchDirectorGear
|
||||||
icon: "JobIconResearchDirector"
|
icon: "JobIconResearchDirector"
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
time: 108000 # 30 hrs
|
time: 108000 # 30 hrs
|
||||||
- !type:OverallPlaytimeRequirement
|
- !type:OverallPlaytimeRequirement
|
||||||
time: 144000 #40 hrs
|
time: 144000 #40 hrs
|
||||||
|
- !type:AgeRequirement
|
||||||
|
requiredAge: 20
|
||||||
weight: 10
|
weight: 10
|
||||||
startingGear: HoSGear
|
startingGear: HoSGear
|
||||||
icon: "JobIconHeadOfSecurity"
|
icon: "JobIconHeadOfSecurity"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
- type: department
|
- type: department
|
||||||
id: Cargo
|
id: Cargo
|
||||||
|
name: department-Cargo
|
||||||
description: department-Cargo-description
|
description: department-Cargo-description
|
||||||
color: "#A46106"
|
color: "#A46106"
|
||||||
roles:
|
roles:
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Civilian
|
id: Civilian
|
||||||
|
name: department-Civilian
|
||||||
description: department-Civilian-description
|
description: department-Civilian-description
|
||||||
color: "#9FED58"
|
color: "#9FED58"
|
||||||
weight: -10
|
weight: -10
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Command
|
id: Command
|
||||||
|
name: department-Command
|
||||||
description: department-Command-description
|
description: department-Command-description
|
||||||
color: "#334E6D"
|
color: "#334E6D"
|
||||||
roles:
|
roles:
|
||||||
@@ -50,6 +53,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Engineering
|
id: Engineering
|
||||||
|
name: department-Engineering
|
||||||
description: department-Engineering-description
|
description: department-Engineering-description
|
||||||
color: "#EFB341"
|
color: "#EFB341"
|
||||||
roles:
|
roles:
|
||||||
@@ -60,6 +64,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Medical
|
id: Medical
|
||||||
|
name: department-Medical
|
||||||
description: department-Medical-description
|
description: department-Medical-description
|
||||||
color: "#52B4E9"
|
color: "#52B4E9"
|
||||||
roles:
|
roles:
|
||||||
@@ -72,6 +77,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Security
|
id: Security
|
||||||
|
name: department-Security
|
||||||
description: department-Security-description
|
description: department-Security-description
|
||||||
color: "#DE3A3A"
|
color: "#DE3A3A"
|
||||||
weight: 20
|
weight: 20
|
||||||
@@ -84,6 +90,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Science
|
id: Science
|
||||||
|
name: department-Science
|
||||||
description: department-Science-description
|
description: department-Science-description
|
||||||
color: "#D381C9"
|
color: "#D381C9"
|
||||||
roles:
|
roles:
|
||||||
@@ -93,6 +100,7 @@
|
|||||||
|
|
||||||
- type: department
|
- type: department
|
||||||
id: Specific
|
id: Specific
|
||||||
|
name: department-Specific
|
||||||
description: department-Specific-description
|
description: department-Specific-description
|
||||||
color: "#9FED58"
|
color: "#9FED58"
|
||||||
weight: 10
|
weight: 10
|
||||||
|
|||||||
Reference in New Issue
Block a user