diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index 9a70d67831..38d4a411ff 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
@@ -142,18 +142,29 @@
-
+
-
-
-
+
+
+
+
+
+
-
+
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index 8b88fc1e9b..fd84b67522 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -12,6 +12,7 @@ using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.Clothing.Loadouts.Systems;
+using Content.Shared.Customization.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
@@ -61,6 +62,7 @@ namespace Content.Client.Preferences.UI
private readonly IConfigurationManager _configurationManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
+ private readonly CharacterRequirementsSystem _characterRequirementsSystem;
private readonly LoadoutSystem _loadoutSystem;
private LineEdit _ageEdit => CAgeEdit;
@@ -83,10 +85,15 @@ namespace Content.Client.Preferences.UI
private TabContainer _tabContainer => CTabContainer;
private BoxContainer _jobList => CJobList;
private BoxContainer _antagList => CAntagList;
- private BoxContainer _traitsList => CTraitsList;
+ private Label _traitPointsLabel => TraitPointsLabel;
+ private int _traitCount;
+ private ProgressBar _traitPointsBar => TraitPointsBar;
+ private Button _traitsShowUnusableButton => TraitsShowUnusableButton;
+ private BoxContainer _traitsTab => CTraitsTab;
+ private TabContainer _traitsTabs => CTraitsTabs;
private Label _loadoutPointsLabel => LoadoutPointsLabel;
private ProgressBar _loadoutPointsBar => LoadoutPointsBar;
- private Button _loadoutsShowUnusableButton => CHideShowUnusableButton;
+ private Button _loadoutsShowUnusableButton => LoadoutsShowUnusableButton;
private BoxContainer _loadoutsTab => CLoadoutsTab;
private TabContainer _loadoutsTabs => CLoadoutsTabs;
private readonly List _jobPriorities;
@@ -124,6 +131,7 @@ namespace Content.Client.Preferences.UI
_preferencesManager = preferencesManager;
_configurationManager = configurationManager;
_markingManager = IoCManager.Resolve();
+ _characterRequirementsSystem = EntitySystem.Get();
_loadoutSystem = EntitySystem.Get();
SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
@@ -444,33 +452,19 @@ namespace Content.Client.Preferences.UI
#region Traits
- var traits = prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList();
- _traitPreferences = new List();
+ // Set up the traits tab
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
+ _traitPreferences = new List();
- if (traits.Count > 0)
- {
- foreach (var trait in traits)
- {
- var selector = new TraitPreferenceSelector(trait);
- _traitsList.AddChild(selector);
- _traitPreferences.Add(selector);
+ // Show/Hide the traits tab if they ever get enabled/disabled
+ var traitsEnabled = _configurationManager.GetCVar(CCVars.GameTraitsEnabled);
+ _tabContainer.SetTabVisible(3, traitsEnabled);
+ _configurationManager.OnValueChanged(CCVars.GameTraitsEnabled,
+ enabled => _tabContainer.SetTabVisible(3, enabled));
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithTraitPreference(trait.ID, preference);
- IsDirty = true;
- };
- }
- }
- else
- {
- _traitsList.AddChild(new Label
- {
- Text = "No traits available :(",
- FontColorOverride = Color.Gray,
- });
- }
+ _traitsShowUnusableButton.OnToggled += args => UpdateTraits(args.Pressed);
+
+ UpdateTraits(false);
#endregion
@@ -480,14 +474,17 @@ namespace Content.Client.Preferences.UI
_tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-loadouts-tab"));
_loadoutPreferences = new List();
- // Show/Hide loadouts tab if they ever get enabled/disabled
+ // Show/Hide the loadouts tab if they ever get enabled/disabled
var loadoutsEnabled = _configurationManager.GetCVar(CCVars.GameLoadoutsEnabled);
_tabContainer.SetTabVisible(4, loadoutsEnabled);
ShowLoadouts.Visible = loadoutsEnabled;
- _configurationManager.OnValueChanged(CCVars.GameLoadoutsEnabled, enabled => LoadoutsChanged(enabled));
+ _configurationManager.OnValueChanged(CCVars.GameLoadoutsEnabled,
+ enabled => LoadoutsChanged(enabled));
_loadoutsShowUnusableButton.OnToggled += args => UpdateLoadouts(args.Pressed);
+ UpdateLoadouts(false);
+
#endregion
#region Save
@@ -542,8 +539,6 @@ namespace Content.Client.Preferences.UI
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
_previewSpriteView.SetEntity(_previewDummy);
- UpdateLoadouts(false); // Initial UpdateLoadouts call has to have a dummy to get information from
-
#endregion Dummy
#endregion Left
@@ -562,6 +557,7 @@ namespace Content.Client.Preferences.UI
IsDirty = false;
}
+
private void LoadoutsChanged(bool enabled)
{
_tabContainer.SetTabVisible(4, enabled);
@@ -1449,12 +1445,264 @@ namespace Content.Client.Preferences.UI
private void UpdateTraitPreferences()
{
+ var points = _configurationManager.GetCVar(CCVars.GameTraitsDefaultPoints);
+ _traitCount = 0;
+
foreach (var preferenceSelector in _traitPreferences)
{
var traitId = preferenceSelector.Trait.ID;
var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
preferenceSelector.Preference = preference;
+
+ if (!preference)
+ continue;
+
+ points += preferenceSelector.Trait.Points;
+ _traitCount += 1;
+ }
+
+ _traitPointsBar.Value = points;
+ _traitPointsLabel.Text = Loc.GetString("humanoid-profile-editor-traits-header",
+ ("points", points), ("traits", _traitCount),
+ ("maxTraits", _configurationManager.GetCVar(CCVars.GameTraitsMax)));
+ }
+
+ // Yeah this is mostly just copied from UpdateLoadouts
+ // This whole file is bad though and a lot of loadout code came from traits originally
+ //TODO Make this file not hell
+ private void UpdateTraits(bool showUnusable)
+ {
+ // Reset trait points so you don't get -14 points or something for no reason
+ var points = _configurationManager.GetCVar(CCVars.GameTraitsDefaultPoints);
+ _traitPointsLabel.Text = Loc.GetString("humanoid-profile-editor-traits-header",
+ ("points", points), ("traits", 0),
+ ("maxTraits", _configurationManager.GetCVar(CCVars.GameTraitsMax)));
+ _traitPointsBar.MaxValue = points;
+ _traitPointsBar.Value = points;
+
+ // Clear current listings
+ _traitPreferences.Clear();
+ _traitsTabs.DisposeAllChildren();
+
+
+ // Get the highest priority job to use for trait filtering
+ var highJob = _jobPriorities.FirstOrDefault(j => j.Priority == JobPriority.High);
+
+ // Get all trait prototypes
+ var enumeratedTraits = _prototypeManager.EnumeratePrototypes().ToList();
+ // Get all trait categories
+ var categories = _prototypeManager.EnumeratePrototypes().ToList();
+
+ // If showUnusable is false filter out traits that are unusable based on your current character setup
+ var traits = enumeratedTraits.Where(trait =>
+ showUnusable || // Ignore everything if this is true
+ _characterRequirementsSystem.CheckRequirementsValid(
+ trait,
+ trait.Requirements,
+ highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ new Dictionary(), //TODO Make this use real playtimes
+ _entMan,
+ _prototypeManager,
+ _configurationManager,
+ out _
+ )
+ ).ToList();
+
+ // Traits to highlight red when showUnusable is true
+ var traitsUnusable = enumeratedTraits.Where(trait =>
+ _characterRequirementsSystem.CheckRequirementsValid(
+ trait,
+ trait.Requirements,
+ highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ new Dictionary(),
+ _entMan,
+ _prototypeManager,
+ _configurationManager,
+ out _
+ )
+ ).ToList();
+
+ // Every trait not in the traits list
+ var otherTraits = enumeratedTraits.Where(trait => !traits.Contains(trait)).ToList();
+
+
+ if (traits.Count == 0)
+ {
+ _traitsTab.AddChild(new Label { Text = Loc.GetString("humanoid-profile-editor-traits-no-traits") });
+ return;
+ }
+
+ // Make Uncategorized category
+ var uncategorized = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ VerticalExpand = true,
+ Name = "Uncategorized_0",
+ // I hate ScrollContainers
+ Children =
+ {
+ new ScrollContainer
+ {
+ HScrollEnabled = false,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ },
+ },
+ },
+ },
+ };
+
+ _traitsTabs.AddChild(uncategorized);
+ _traitsTabs.SetTabTitle(0, Loc.GetString("trait-category-Uncategorized"));
+
+
+ // Make categories
+ var currentCategory = 1; // 1 because we already made 0 as Uncategorized, I am not not zero-indexing :)
+ foreach (var category in categories.OrderBy(c => Loc.GetString($"trait-category-{c.ID}")))
+ {
+ // Check for existing category
+ BoxContainer? match = null;
+ foreach (var child in _traitsTabs.Children)
+ {
+ if (string.IsNullOrEmpty(child.Name))
+ continue;
+
+ if (child.Name.Split("_")[0] == category.ID)
+ match = (BoxContainer) child;
+ }
+
+ // If there is a category do nothing
+ if (match != null)
+ continue;
+
+ // If not, make it
+ var box = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ VerticalExpand = true,
+ Name = $"{category.ID}_{currentCategory}",
+ // I hate ScrollContainers
+ Children =
+ {
+ new ScrollContainer
+ {
+ HScrollEnabled = false,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ },
+ },
+ },
+ },
+ };
+
+ _traitsTabs.AddChild(box);
+ _traitsTabs.SetTabTitle(currentCategory, Loc.GetString($"trait-category-{category.ID}"));
+ currentCategory++;
+ }
+
+
+ // Fill categories
+ foreach (var trait in traits.OrderBy(t => -t.Points).ThenBy(t => Loc.GetString($"trait-name-{t.ID}")))
+ {
+ var selector = new TraitPreferenceSelector(trait, highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ traitsUnusable.Contains(trait) ? "" : "ButtonColorRed",
+ _entMan, _prototypeManager, _configurationManager, _characterRequirementsSystem);
+
+ // Look for an existing trait category
+ BoxContainer? match = null;
+ foreach (var child in _traitsTabs.Children)
+ {
+ if (string.IsNullOrEmpty(child.Name))
+ continue;
+
+ // This is fucked up
+ if (child.Name.Split("_")[0] == trait.Category
+ && child.Children.FirstOrDefault()?.Children.FirstOrDefault(g =>
+ g.GetType() == typeof(BoxContainer)) is {} g != default)
+ match = (BoxContainer) g;
+ }
+
+ // If there is no category put it in Uncategorized
+ if (string.IsNullOrEmpty(match?.Parent?.Parent?.Name)
+ || match.Parent.Parent.Name.Split("_")[0] != trait.Category)
+ uncategorized.AddChild(selector);
+ else
+ match.AddChild(selector);
+
+
+ AddSelector(selector, trait.Points, trait.ID);
+ }
+
+ // Add the selected unusable traits to the point counter
+ foreach (var trait in otherTraits.OrderBy(t => -t.Points).ThenBy(t => Loc.GetString($"trait-name-{t.ID}")))
+ {
+ var selector = new TraitPreferenceSelector(trait, highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "",
+ _entMan, _prototypeManager, _configurationManager, _characterRequirementsSystem);
+
+
+ AddSelector(selector, trait.Points, trait.ID);
+ }
+
+
+ // Hide Uncategorized tab if it's empty, other tabs already shouldn't exist if they're empty
+ _traitsTabs.SetTabVisible(0, uncategorized.Children.Any());
+
+ // Add fake tabs until tab container is happy
+ for (var i = _traitsTabs.ChildCount - 1; i < _traitsTabs.CurrentTab; i++)
+ {
+ _traitsTabs.AddChild(new BoxContainer());
+ _traitsTabs.SetTabVisible(i + 1, false);
+ }
+
+ UpdateTraitPreferences();
+ return;
+
+
+ void AddSelector(TraitPreferenceSelector selector, int points, string id)
+ {
+ if (points > 0)
+ _traitPointsBar.MaxValue += points;
+
+ _traitPreferences.Add(selector);
+ selector.PreferenceChanged += preference =>
+ {
+ // Make sure they have enough trait points
+ preference = preference ? CheckPoints(points, preference) : CheckPoints(-points, preference);
+ // Don't allow having too many traits
+ preference = preference && _traitCount + 1 <= _configurationManager.GetCVar(CCVars.GameTraitsMax);
+
+ // Update Preferences
+ Profile = Profile?.WithTraitPreference(id, preference);
+ IsDirty = true;
+ UpdateTraitPreferences();
+ UpdateTraits(_traitsShowUnusableButton.Pressed);
+ UpdateLoadouts(_loadoutsShowUnusableButton.Pressed);
+ };
+ }
+
+ bool CheckPoints(int points, bool preference)
+ {
+ var temp = _traitPointsBar.Value + points;
+ return preference ? !(temp < 0) : temp < 0;
}
}
@@ -1498,15 +1746,18 @@ namespace Content.Client.Preferences.UI
// Get all loadout prototypes
var enumeratedLoadouts = _prototypeManager.EnumeratePrototypes().ToList();
+ // Get all loadout categories
+ var categories = _prototypeManager.EnumeratePrototypes().ToList();
// If showUnusable is false filter out loadouts that are unusable based on your current character setup
var loadouts = enumeratedLoadouts.Where(loadout =>
showUnusable || // Ignore everything if this is true
- _loadoutSystem.CheckRequirementsValid(
+ _characterRequirementsSystem.CheckRequirementsValid(
+ loadout,
loadout.Requirements,
highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
- new Dictionary(),
+ new Dictionary(), //TODO Make this use real playtimes
_entMan,
_prototypeManager,
_configurationManager,
@@ -1516,7 +1767,8 @@ namespace Content.Client.Preferences.UI
// Loadouts to highlight red when showUnusable is true
var loadoutsUnusable = enumeratedLoadouts.Where(loadout =>
- _loadoutSystem.CheckRequirementsValid(
+ _characterRequirementsSystem.CheckRequirementsValid(
+ loadout,
loadout.Requirements,
highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
@@ -1544,23 +1796,47 @@ namespace Content.Client.Preferences.UI
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
Name = "Uncategorized_0",
+ // I hate ScrollContainers
+ Children =
+ {
+ new ScrollContainer
+ {
+ HScrollEnabled = false,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ },
+ },
+ },
+ },
};
_loadoutsTabs.AddChild(uncategorized);
- _loadoutsTabs.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-loadouts-uncategorized-tab"));
+ _loadoutsTabs.SetTabTitle(0, Loc.GetString("loadout-category-Uncategorized"));
+
// Make categories
var currentCategory = 1; // 1 because we already made 0 as Uncategorized, I am not not zero-indexing :)
- foreach (var loadout in loadouts.OrderBy(l => l.Category))
+ foreach (var category in categories.OrderBy(c => Loc.GetString($"loadout-category-{c.ID}")))
{
// Check for existing category
BoxContainer? match = null;
foreach (var child in _loadoutsTabs.Children)
{
- if (match != null || child.Name == null)
+ if (string.IsNullOrEmpty(child.Name))
continue;
- if (child.Name.Split("_")[0] == loadout.Category)
- match = (BoxContainer) child;
+
+ // This is fucked up
+ if (child.Name.Split("_")[0] == category.ID
+ && child.Children.FirstOrDefault()?.Children.FirstOrDefault(g =>
+ g.GetType() == typeof(BoxContainer)) is {} g != default)
+ match = (BoxContainer) g;
}
// If there is a category do nothing
@@ -1572,7 +1848,7 @@ namespace Content.Client.Preferences.UI
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
- Name = $"{loadout.Category}_{currentCategory}",
+ Name = $"{category.ID}_{currentCategory}",
// I hate ScrollContainers
Children =
{
@@ -1595,23 +1871,24 @@ namespace Content.Client.Preferences.UI
};
_loadoutsTabs.AddChild(box);
- _loadoutsTabs.SetTabTitle(currentCategory, Loc.GetString($"loadout-category-{loadout.Category}"));
+ _loadoutsTabs.SetTabTitle(currentCategory, Loc.GetString($"loadout-category-{category.ID}"));
currentCategory++;
}
+
// Fill categories
- foreach (var loadout in loadouts.OrderBy(l => l.ID))
+ foreach (var loadout in loadouts.OrderBy(l => l.Cost).ThenBy(l => Loc.GetString($"loadout-{l.ID}-name")))
{
var selector = new LoadoutPreferenceSelector(loadout, highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
- loadoutsUnusable.Contains(loadout) ? "" : "ButtonColorRed", _entMan, _prototypeManager,
- _configurationManager, _loadoutSystem);
+ loadoutsUnusable.Contains(loadout) ? "" : "ButtonColorRed",
+ _entMan, _prototypeManager, _configurationManager, _characterRequirementsSystem);
// Look for an existing loadout category
BoxContainer? match = null;
foreach (var child in _loadoutsTabs.Children)
{
- if (match != null || child.Name == null)
+ if (string.IsNullOrEmpty(child.Name))
continue;
if (child.Name.Split("_")[0] == loadout.Category)
@@ -1619,80 +1896,30 @@ namespace Content.Client.Preferences.UI
}
// If there is no category put it in Uncategorized
- if (match?.Parent?.Parent?.Name == null)
+ if (string.IsNullOrEmpty(match?.Parent?.Parent?.Name)
+ || match.Parent.Parent.Name.Split("_")[0] != loadout.Category)
uncategorized.AddChild(selector);
else
match.AddChild(selector);
- _loadoutPreferences.Add(selector);
- selector.PreferenceChanged += preference =>
- {
- // Make sure they have enough loadout points
- if (preference)
- {
- var temp = _loadoutPointsBar.Value - loadout.Cost;
- if (temp < 0)
- preference = false;
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", temp), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value = temp;
- }
- }
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", _loadoutPointsBar.Value), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value += loadout.Cost;
- }
- // Update Preference
- Profile = Profile?.WithLoadoutPreference(loadout.ID, preference);
- IsDirty = true;
- UpdateLoadoutPreferences();
- };
+
+ AddSelector(selector, loadout.Cost, loadout.ID);
}
// Add the selected unusable loadouts to the point counter
- foreach (var loadout in otherLoadouts.OrderBy(l => l.ID))
+ foreach (var loadout in otherLoadouts.OrderBy(l => l.Cost).ThenBy(l => Loc.GetString($"loadout-{l.ID}-name")))
{
var selector = new LoadoutPreferenceSelector(loadout, highJob?.Proto ?? new JobPrototype(),
- Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "", _entMan, _prototypeManager,
- _configurationManager, _loadoutSystem);
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "",
+ _entMan, _prototypeManager, _configurationManager, _characterRequirementsSystem);
- _loadoutPreferences.Add(selector);
- selector.PreferenceChanged += preference =>
- {
- // Make sure they have enough loadout points
- if (preference)
- {
- var temp = _loadoutPointsBar.Value - loadout.Cost;
- if (temp < 0)
- preference = false;
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", temp), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value = temp;
- }
- }
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", _loadoutPointsBar.Value), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value += loadout.Cost;
- }
- // Update Preference
- Profile = Profile?.WithLoadoutPreference(loadout.ID, preference);
- IsDirty = true;
- UpdateLoadoutPreferences();
- };
+
+ AddSelector(selector, loadout.Cost, loadout.ID);
}
// Hide Uncategorized tab if it's empty, other tabs already shouldn't exist if they're empty
- if (!uncategorized.Children.Any())
- _loadoutsTabs.SetTabVisible(0, false);
+ _loadoutsTabs.SetTabVisible(0, uncategorized.Children.Any());
// Add fake tabs until tab container is happy
for (var i = _loadoutsTabs.ChildCount - 1; i < _loadoutsTabs.CurrentTab; i++)
@@ -1702,6 +1929,31 @@ namespace Content.Client.Preferences.UI
}
UpdateLoadoutPreferences();
+ return;
+
+
+ void AddSelector(LoadoutPreferenceSelector selector, int points, string id)
+ {
+ _loadoutPreferences.Add(selector);
+ selector.PreferenceChanged += preference =>
+ {
+ // Make sure they have enough loadout points
+ preference = preference ? CheckPoints(points, preference) : CheckPoints(-points, preference);
+
+ // Update Preferences
+ Profile = Profile?.WithLoadoutPreference(id, preference);
+ IsDirty = true;
+ UpdateLoadoutPreferences();
+ UpdateLoadouts(_loadoutsShowUnusableButton.Pressed);
+ UpdateTraits(_traitsShowUnusableButton.Pressed);
+ };
+ }
+
+ bool CheckPoints(int points, bool preference)
+ {
+ var temp = _loadoutPointsBar.Value + points;
+ return preference ? !(temp < 0) : temp < 0;
+ }
}
@@ -1753,19 +2005,68 @@ namespace Content.Client.Preferences.UI
public event Action? PreferenceChanged;
- public TraitPreferenceSelector(TraitPrototype trait)
+ public TraitPreferenceSelector(TraitPrototype trait, JobPrototype highJob,
+ HumanoidCharacterProfile profile, string style, IEntityManager entityManager, IPrototypeManager prototypeManager,
+ IConfigurationManager configManager, CharacterRequirementsSystem characterRequirementsSystem)
{
Trait = trait;
- _button = new Button {Text = Loc.GetString(trait.Name)};
- _button.ToggleMode = true;
- _button.OnToggled += OnButtonToggled;
-
- if (trait.Description is { } desc)
+ // Create a checkbox to get the loadout
+ _button = new Button
{
- _button.ToolTip = Loc.GetString(desc);
+ VerticalAlignment = VAlignment.Center,
+ ToggleMode = true,
+ StyleClasses = { StyleBase.ButtonOpenLeft },
+ Children =
+ {
+ new BoxContainer
+ {
+ Children =
+ {
+ new Label
+ {
+ Text = trait.Points.ToString(),
+ StyleClasses = { StyleBase.StyleClassLabelHeading },
+ MinWidth = 32,
+ MaxWidth = 32,
+ ClipText = true,
+ Margin = new Thickness(0, 0, 8, 0),
+ },
+ new Label { Text = Loc.GetString($"trait-name-{trait.ID}") },
+ },
+ },
+ },
+ };
+ _button.OnToggled += OnButtonToggled;
+ _button.AddStyleClass(style);
+
+ var tooltip = new StringBuilder();
+ // Add the loadout description to the tooltip if there is one
+ var desc = Loc.GetString($"trait-description-{trait.ID}");
+ if (!string.IsNullOrEmpty(desc) && desc != $"trait-description-{trait.ID}")
+ tooltip.Append(desc);
+
+
+ // Get requirement reasons
+ characterRequirementsSystem.CheckRequirementsValid(trait, trait.Requirements, highJob, profile,
+ new Dictionary(),
+ entityManager, prototypeManager, configManager,
+ out var reasons);
+
+ // Add requirement reasons to the tooltip
+ foreach (var reason in reasons)
+ tooltip.Append($"\n{reason.ToMarkup()}");
+
+ // Combine the tooltip and format it in the checkbox supplier
+ if (tooltip.Length > 0)
+ {
+ var formattedTooltip = new Tooltip();
+ formattedTooltip.SetMessage(FormattedMessage.FromMarkupPermissive(tooltip.ToString()));
+ _button.TooltipSupplier = _ => formattedTooltip;
}
+
+ // Add the loadout preview and the checkbox to the control
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
@@ -1794,7 +2095,7 @@ namespace Content.Client.Preferences.UI
public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
HumanoidCharacterProfile profile, string style, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, LoadoutSystem loadoutSystem)
+ IConfigurationManager configManager, CharacterRequirementsSystem characterRequirementsSystem)
{
Loadout = loadout;
@@ -1816,11 +2117,32 @@ namespace Content.Client.Preferences.UI
// Create a checkbox to get the loadout
_button = new Button
{
- Text = $"[{loadout.Cost}] {(Loc.GetString($"loadout-name-{loadout.ID}") == $"loadout-name-{loadout.ID}"
- ? entityManager.GetComponent(dummyLoadoutItem).EntityName
- : Loc.GetString($"loadout-name-{loadout.ID}"))}",
- VerticalAlignment = VAlignment.Center,
ToggleMode = true,
+ StyleClasses = { StyleBase.ButtonOpenLeft },
+ Children =
+ {
+ new BoxContainer
+ {
+ Children =
+ {
+ new Label
+ {
+ Text = loadout.Cost.ToString(),
+ StyleClasses = { StyleBase.StyleClassLabelHeading },
+ MinWidth = 32,
+ MaxWidth = 32,
+ ClipText = true,
+ Margin = new Thickness(0, 0, 8, 0),
+ },
+ new Label
+ {
+ Text = Loc.GetString($"loadout-name-{loadout.ID}") == $"loadout-name-{loadout.ID}"
+ ? entityManager.GetComponent(dummyLoadoutItem).EntityName
+ : Loc.GetString($"loadout-name-{loadout.ID}"),
+ },
+ },
+ },
+ },
};
_button.OnToggled += OnButtonToggled;
_button.AddStyleClass(style);
@@ -1835,7 +2157,10 @@ namespace Content.Client.Preferences.UI
// Get requirement reasons
- loadoutSystem.CheckRequirementsValid(loadout.Requirements, highJob, profile, new Dictionary(), entityManager, prototypeManager, configManager, out var reasons);
+ characterRequirementsSystem.CheckRequirementsValid(loadout, loadout.Requirements, highJob, profile,
+ new Dictionary(),
+ entityManager, prototypeManager, configManager,
+ out var reasons);
// Add requirement reasons to the tooltip
foreach (var reason in reasons)
diff --git a/Content.Client/Traits/ParacusiaSystem.cs b/Content.Client/Traits/ParacusiaSystem.cs
index 3789f24cb0..d01c7c0005 100644
--- a/Content.Client/Traits/ParacusiaSystem.cs
+++ b/Content.Client/Traits/ParacusiaSystem.cs
@@ -1,11 +1,12 @@
using System.Numerics;
-using Content.Shared.Traits.Assorted;
+using Content.Shared.Traits.Assorted.Systems;
using Robust.Shared.Random;
using Robust.Client.Player;
using Robust.Shared.Player;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Timing;
+using Content.Shared.Traits.Assorted.Components;
namespace Content.Client.Traits;
diff --git a/Content.Server/Flash/FlashSystem.cs b/Content.Server/Flash/FlashSystem.cs
index fe7eb81d1e..013ee37a41 100644
--- a/Content.Server/Flash/FlashSystem.cs
+++ b/Content.Server/Flash/FlashSystem.cs
@@ -14,13 +14,13 @@ using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Physics;
using Content.Shared.Tag;
-using Content.Shared.Traits.Assorted;
using Content.Shared.Weapons.Melee.Events;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Timing;
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
+using Content.Shared.Traits.Assorted.Components;
namespace Content.Server.Flash
{
diff --git a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs
index 722a489541..4fc158f864 100644
--- a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs
+++ b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs
@@ -2,7 +2,7 @@ using Content.Server.GameTicking.Rules.Components;
using Content.Server.StationEvents.Components;
using Content.Server.Traits.Assorted;
using Content.Shared.Mind.Components;
-using Content.Shared.Traits.Assorted;
+using Content.Shared.Traits.Assorted.Components;
namespace Content.Server.StationEvents.Events;
diff --git a/Content.Server/Traits/Assorted/ParacusiaSystem.cs b/Content.Server/Traits/Assorted/ParacusiaSystem.cs
index 4b0205ff53..cf08e09e90 100644
--- a/Content.Server/Traits/Assorted/ParacusiaSystem.cs
+++ b/Content.Server/Traits/Assorted/ParacusiaSystem.cs
@@ -1,5 +1,7 @@
using Content.Shared.Traits.Assorted;
+using Content.Shared.Traits.Assorted.Systems;
using Robust.Shared.Audio;
+using Content.Shared.Traits.Assorted.Components;
namespace Content.Server.Traits.Assorted;
diff --git a/Content.Server/Traits/TraitSystem.cs b/Content.Server/Traits/TraitSystem.cs
index 22ee0e4861..be2c3c0503 100644
--- a/Content.Server/Traits/TraitSystem.cs
+++ b/Content.Server/Traits/TraitSystem.cs
@@ -1,7 +1,13 @@
+using System.Linq;
using Content.Server.GameTicking;
+using Content.Server.Players.PlayTimeTracking;
+using Content.Shared.Customization.Systems;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
+using Content.Shared.Roles;
using Content.Shared.Traits;
+using Pidgin.Configuration;
+using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
@@ -9,9 +15,12 @@ namespace Content.Server.Traits;
public sealed class TraitSystem : EntitySystem
{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly ISerializationManager _serializationManager = default!;
- [Dependency] private readonly SharedHandsSystem _sharedHandsSystem = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly ISerializationManager _serialization = default!;
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!;
+ [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
+ [Dependency] private readonly IConfigurationManager _configuration = default!;
public override void Initialize()
{
@@ -25,16 +34,17 @@ public sealed class TraitSystem : EntitySystem
{
foreach (var traitId in args.Profile.TraitPreferences)
{
- if (!_prototypeManager.TryIndex(traitId, out var traitPrototype))
+ if (!_prototype.TryIndex(traitId, out var traitPrototype))
{
Log.Warning($"No trait found with ID {traitId}!");
return;
}
- if (traitPrototype.Whitelist != null && !traitPrototype.Whitelist.IsValid(args.Mob))
- continue;
-
- if (traitPrototype.Blacklist != null && traitPrototype.Blacklist.IsValid(args.Mob))
+ if (!_characterRequirements.CheckRequirementsValid(traitPrototype, traitPrototype.Requirements,
+ _prototype.Index(args.JobId ?? _prototype.EnumeratePrototypes().First().ID),
+ args.Profile, _playTimeTracking.GetTrackerTimes(args.Player),
+ EntityManager, _prototype, _configuration,
+ out _))
continue;
// Add all components required by the prototype
@@ -43,22 +53,10 @@ public sealed class TraitSystem : EntitySystem
if (HasComp(args.Mob, entry.Component.GetType()))
continue;
- var comp = (Component) _serializationManager.CreateCopy(entry.Component, notNullableOverride: true);
+ var comp = (Component) _serialization.CreateCopy(entry.Component, notNullableOverride: true);
comp.Owner = args.Mob;
EntityManager.AddComponent(args.Mob, comp);
}
-
- // Add item required by the trait
- if (traitPrototype.TraitGear != null)
- {
- if (!TryComp(args.Mob, out HandsComponent? handsComponent))
- continue;
-
- var coords = Transform(args.Mob).Coordinates;
- var inhandEntity = EntityManager.SpawnEntity(traitPrototype.TraitGear, coords);
- _sharedHandsSystem.TryPickup(args.Mob, inhandEntity, checkActionBlocker: false,
- handsComp: handsComponent);
- }
}
}
}
diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs
index daadd4b518..996d777223 100644
--- a/Content.Server/Zombies/ZombieSystem.Transform.cs
+++ b/Content.Server/Zombies/ZombieSystem.Transform.cs
@@ -35,8 +35,8 @@ using Content.Shared.Pulling.Components;
using Content.Shared.Weapons.Melee;
using Content.Shared.Zombies;
using Content.Shared.Prying.Components;
-using Content.Shared.Traits.Assorted;
using Robust.Shared.Audio.Systems;
+using Content.Shared.Traits.Assorted.Components;
namespace Content.Server.Zombies
{
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index 5aaf967dd1..ab36977efb 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -341,14 +341,34 @@ namespace Content.Shared.CCVar
public static readonly CVarDef DebugCoordinatesAdminOnly =
CVarDef.Create("game.debug_coordinates_admin_only", true, CVar.SERVER | CVar.REPLICATED);
+
///
- /// Whether or not to allow characters to select loadout items.
+ /// Whether to allow characters to select traits.
+ ///
+ public static readonly CVarDef GameTraitsEnabled =
+ CVarDef.Create("game.traits_enabled", true, CVar.REPLICATED);
+
+ ///
+ /// How many traits a character can have at most.
+ ///
+ public static readonly CVarDef GameTraitsMax =
+ CVarDef.Create("game.traits_max", 5, CVar.REPLICATED);
+
+ ///
+ /// How many points a character should start with.
+ ///
+ public static readonly CVarDef GameTraitsDefaultPoints =
+ CVarDef.Create("game.traits_default_points", 5, CVar.REPLICATED);
+
+
+ ///
+ /// Whether to allow characters to select loadout items.
///
public static readonly CVarDef GameLoadoutsEnabled =
CVarDef.Create("game.loadouts_enabled", true, CVar.REPLICATED);
///
- /// How many points to give to each player for loadouts.
+ /// How many points to give to each player for loadouts.
///
public static readonly CVarDef GameLoadoutsPoints =
CVarDef.Create("game.loadouts_points", 14, CVar.REPLICATED);
diff --git a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs
index 5dd880d3f1..445cbc10e6 100644
--- a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs
+++ b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs
@@ -2,6 +2,7 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Clothing.Loadouts.Prototypes;
+
///
/// A prototype defining a valid category for s to go into.
///
diff --git a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs
index bc31fc1570..9ca575fa72 100644
--- a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs
+++ b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutPrototype.cs
@@ -1,10 +1,12 @@
using Content.Shared.Clothing.Loadouts.Systems;
+using Content.Shared.Customization.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Clothing.Loadouts.Prototypes;
+
[Prototype("loadout")]
public sealed class LoadoutPrototype : IPrototype
{
@@ -17,7 +19,7 @@ public sealed class LoadoutPrototype : IPrototype
///
/// Which tab category to put this under
///
- [DataField(customTypeSerializer:typeof(PrototypeIdSerializer))]
+ [DataField, ValidatePrototypeId]
public string Category = "Uncategorized";
///
@@ -41,5 +43,5 @@ public sealed class LoadoutPrototype : IPrototype
[DataField]
- public List Requirements = new();
+ public List Requirements = new();
}
diff --git a/Content.Shared/Clothing/Loadouts/Systems/LoadoutRequirements.cs b/Content.Shared/Clothing/Loadouts/Systems/LoadoutRequirements.cs
deleted file mode 100644
index b76a31e422..0000000000
--- a/Content.Shared/Clothing/Loadouts/Systems/LoadoutRequirements.cs
+++ /dev/null
@@ -1,375 +0,0 @@
-using System.Linq;
-using Content.Shared.CCVar;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Players.PlayTimeTracking;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Content.Shared.Roles.Jobs;
-using Content.Shared.Traits;
-using JetBrains.Annotations;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.Clothing.Loadouts.Systems;
-
-[ImplicitDataDefinitionForInheritors, MeansImplicitUse]
-[Serializable, NetSerializable]
-public abstract partial class LoadoutRequirement
-{
- ///
- /// If true valid requirements will be treated as invalid and vice versa
- ///
- [DataField]
- public bool Inverted = false;
-
- ///
- /// Checks if this loadout requirement is valid for the given parameters
- ///
- /// Description for the requirement, shown when not null
- public abstract bool IsValid(
- JobPrototype job,
- HumanoidCharacterProfile profile,
- Dictionary playTimes,
- IEntityManager entityManager,
- IPrototypeManager prototypeManager,
- IConfigurationManager configManager,
- out FormattedMessage? reason
- );
-}
-
-
-#region HumanoidCharacterProfile
-
-///
-/// Requires the profile to be within an age range
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutAgeRequirement : LoadoutRequirement
-{
- [DataField(required: true)]
- public int Min;
-
- [DataField(required: true)]
- public int Max;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- reason = FormattedMessage.FromMarkup(Loc.GetString("loadout-age-requirement",
- ("min", Min), ("max", Max)));
- return profile.Age >= Min && profile.Age <= Max;
- }
-}
-
-///
-/// Requires the profile to use either a Backpack, Satchel, or Duffelbag
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutBackpackTypeRequirement : LoadoutRequirement
-{
- [DataField(required: true)]
- public BackpackPreference Preference;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- reason = FormattedMessage.FromMarkup(Loc.GetString("loadout-backpack-type-requirement",
- ("type", Loc.GetString($"humanoid-profile-editor-preference-{Preference.ToString().ToLower()}"))));
- return profile.Backpack == Preference;
- }
-}
-
-///
-/// Requires the profile to use either Jumpsuits or Jumpskirts
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutClothingPreferenceRequirement : LoadoutRequirement
-{
- [DataField(required: true)]
- public ClothingPreference Preference;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- reason = FormattedMessage.FromMarkup(Loc.GetString("loadout-clothing-preference-requirement",
- ("preference", Loc.GetString($"humanoid-profile-editor-preference-{Preference.ToString().ToLower()}"))));
- return profile.Clothing == Preference;
- }
-}
-
-///
-/// Requires the profile to be a certain species
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutSpeciesRequirement : LoadoutRequirement
-{
- [DataField(required: true)]
- public ProtoId Species;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- reason = FormattedMessage.FromMarkup(Loc.GetString("loadout-species-requirement",
- ("species", Loc.GetString($"species-name-{Species.ToString().ToLower()}"))));
- return profile.Species == Species;
- }
-}
-
-///
-/// Requires the profile to have a certain trait
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutTraitRequirement : LoadoutRequirement
-{
- [DataField(required: true)]
- public ProtoId Trait;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- reason = FormattedMessage.FromMarkup(Loc.GetString("loadout-trait-requirement",
- ("trait", Loc.GetString($"trait-{Trait.ToString().ToLower()}-name"))));
- return profile.TraitPreferences.Contains(Trait.ToString());
- }
-}
-
-#endregion
-
-#region Jobs
-
-///
-/// Requires the selected job to be a certain one
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutJobRequirement : LoadoutRequirement
-{
- [DataField(required: true)]
- public List> Jobs;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- // Join localized job names with a comma
- var jobsString = string.Join(", ", Jobs.Select(j => Loc.GetString(prototypeManager.Index(j).Name)));
- // Form the reason message
- jobsString = Loc.GetString("loadout-job-requirement", ("job", jobsString));
-
- reason = FormattedMessage.FromMarkup(jobsString);
- return Jobs.Contains(job.ID);
- }
-}
-
-///
-/// Requires the playtime for a department to be within a certain range
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutDepartmentTimeRequirement : LoadoutRequirement
-{
- [DataField]
- public TimeSpan Min = TimeSpan.MinValue;
-
- [DataField]
- public TimeSpan Max = TimeSpan.MaxValue;
-
- [DataField(required: true)]
- public ProtoId Department;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- // Disable the requirement if the role timers are disabled
- if (!configManager.GetCVar(CCVars.GameRoleTimers))
- {
- reason = null;
- return !Inverted;
- }
-
- var department = prototypeManager.Index(Department);
-
- // Combine all of this department's job playtimes
- var playtime = TimeSpan.Zero;
- foreach (var other in department.Roles)
- {
- var proto = prototypeManager.Index(other).PlayTimeTracker;
-
- playTimes.TryGetValue(proto, out var otherTime);
- playtime += otherTime;
- }
-
- if (playtime > Max)
- {
- // Show the reason if invalid
- reason = Inverted
- ? null
- : FormattedMessage.FromMarkup(Loc.GetString("loadout-timer-department-too-high",
- ("time", playtime.Minutes - Max.Minutes),
- ("department", Loc.GetString($"department-{department.ID}")),
- ("departmentColor", department.Color)));
- return false;
- }
-
- if (playtime < Min)
- {
- // Show the reason if invalid
- reason = Inverted
- ? null
- : FormattedMessage.FromMarkup(Loc.GetString("loadout-timer-department-insufficient",
- ("time", Min.Minutes - playtime.Minutes),
- ("department", Loc.GetString($"department-{department.ID}")),
- ("departmentColor", department.Color)));
- return false;
- }
-
- reason = null;
- return true;
- }
-}
-
-///
-/// Requires the player to have a certain amount of overall job time
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutOverallTimeRequirement : LoadoutRequirement
-{
- [DataField]
- public TimeSpan Min = TimeSpan.MinValue;
-
- [DataField]
- public TimeSpan Max = TimeSpan.MaxValue;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- // Disable the requirement if the role timers are disabled
- if (!configManager.GetCVar(CCVars.GameRoleTimers))
- {
- reason = null;
- return !Inverted;
- }
-
- // Get the overall time
- var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
-
- if (overallTime > Max)
- {
- // Show the reason if invalid
- reason = Inverted
- ? null
- : FormattedMessage.FromMarkup(Loc.GetString("loadout-timer-overall-too-high",
- ("time", overallTime.Minutes - Max.Minutes)));
- return false;
- }
-
- if (overallTime < Min)
- {
- // Show the reason if invalid
- reason = Inverted
- ? null
- : FormattedMessage.FromMarkup(Loc.GetString("loadout-timer-overall-insufficient",
- ("time", Min.Minutes - overallTime.Minutes)));
- return false;
- }
-
- reason = null;
- return true;
- }
-}
-
-///
-/// Requires the playtime for a tracker to be within a certain range
-///
-[UsedImplicitly]
-[Serializable, NetSerializable]
-public sealed partial class LoadoutPlaytimeRequirement : LoadoutRequirement
-{
- [DataField]
- public TimeSpan Min = TimeSpan.MinValue;
-
- [DataField]
- public TimeSpan Max = TimeSpan.MaxValue;
-
- [DataField(required: true)]
- public ProtoId Tracker;
-
- public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
- Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, out FormattedMessage? reason)
- {
- // Disable the requirement if the role timers are disabled
- if (!configManager.GetCVar(CCVars.GameRoleTimers))
- {
- reason = null;
- return !Inverted;
- }
-
- // Get SharedJobSystem
- if (!entityManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
- {
- DebugTools.Assert("LoadoutRequirements: SharedJobSystem not found");
- reason = null;
- return false;
- }
-
- // Get the JobPrototype of the Tracker
- var trackerJob = jobSystem.GetJobPrototype(Tracker);
-
- // Get the primary department of the Tracker
- if (!jobSystem.TryGetPrimaryDepartment(trackerJob, out var department) &&
- !jobSystem.TryGetDepartment(trackerJob, out department))
- {
- DebugTools.Assert($"LoadoutRequirements: Department not found for job {trackerJob}");
- reason = null;
- return false;
- }
-
- // Get the time for the tracker
- playTimes.TryGetValue(Tracker, out var time);
- reason = null;
-
- if (time > Max)
- {
- // Show the reason if invalid
- reason = Inverted
- ? null
- : FormattedMessage.FromMarkup(Loc.GetString("loadout-timer-role-too-high",
- ("time", time.Minutes - Max.Minutes),
- ("job", trackerJob),
- ("departmentColor", department.Color)));
- return false;
- }
-
- if (time < Min)
- {
- // Show the reason if invalid
- reason = Inverted
- ? null
- : FormattedMessage.FromMarkup(Loc.GetString("loadout-timer-role-insufficient",
- ("time", Min.Minutes - time.Minutes),
- ("job", trackerJob),
- ("departmentColor", department.Color)));
- return false;
- }
-
- return true;
- }
-}
-
-#endregion
diff --git a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs
index eb9a5dcbc8..09e3db3793 100644
--- a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs
+++ b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs
@@ -1,5 +1,6 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.Loadouts.Prototypes;
+using Content.Shared.Customization.Systems;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
@@ -17,7 +18,8 @@ public sealed class LoadoutSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly IConfigurationManager _configurationManager = default!;
+ [Dependency] private readonly IConfigurationManager _configuration = default!;
+ [Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!;
public override void Initialize()
{
@@ -66,8 +68,9 @@ public sealed class LoadoutSystem : EntitySystem
continue;
- if (!CheckRequirementsValid(loadoutProto.Requirements, job, profile,
- playTimes ?? new Dictionary(), EntityManager, _prototype, _configurationManager,
+ if (!_characterRequirements.CheckRequirementsValid(loadoutProto, loadoutProto.Requirements, job, profile,
+ playTimes ?? new Dictionary(),
+ EntityManager, _prototype, _configuration,
out _))
continue;
@@ -115,35 +118,4 @@ public sealed class LoadoutSystem : EntitySystem
// The server has more information about the inventory system than the client does and the client doesn't need to put loadouts in backpacks
return failedLoadouts;
}
-
-
- public bool CheckRequirementsValid(List requirements, JobPrototype job,
- HumanoidCharacterProfile profile, Dictionary playTimes, IEntityManager entityManager,
- IPrototypeManager prototypeManager, IConfigurationManager configManager, out List reasons)
- {
- reasons = new List();
- var valid = true;
-
- foreach (var requirement in requirements)
- {
- // Set valid to false if the requirement is invalid and not inverted, if it's inverted set it to true when it's valid
- if (!requirement.IsValid(job, profile, playTimes, entityManager, prototypeManager, configManager, out var reason))
- {
- if (valid)
- valid = requirement.Inverted;
- }
- else
- {
- if (valid)
- valid = !requirement.Inverted;
- }
-
- if (reason != null)
- {
- reasons.Add(reason);
- }
- }
-
- return valid;
- }
}
diff --git a/Content.Shared/Customization/Systems/CharacterRequirements.cs b/Content.Shared/Customization/Systems/CharacterRequirements.cs
new file mode 100644
index 0000000000..b7200c60e8
--- /dev/null
+++ b/Content.Shared/Customization/Systems/CharacterRequirements.cs
@@ -0,0 +1,529 @@
+using System.Linq;
+using Content.Shared.CCVar;
+using Content.Shared.Clothing.Loadouts.Prototypes;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Content.Shared.Roles.Jobs;
+using Content.Shared.Traits;
+using JetBrains.Annotations;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Customization.Systems;
+
+
+[ImplicitDataDefinitionForInheritors, MeansImplicitUse]
+[Serializable, NetSerializable]
+public abstract partial class CharacterRequirement
+{
+ ///
+ /// If true valid requirements will be treated as invalid and vice versa
+ ///
+ [DataField]
+ public bool Inverted;
+
+ ///
+ /// Checks if this character requirement is valid for the given parameters
+ ///
+ /// Description for the requirement, shown when not null
+ public abstract bool IsValid(
+ IPrototype prototype,
+ JobPrototype job,
+ HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager,
+ IPrototypeManager prototypeManager,
+ IConfigurationManager configManager,
+ out FormattedMessage? reason
+ );
+}
+
+
+#region HumanoidCharacterProfile
+
+///
+/// Requires the profile to be within an age range
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterAgeRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public int Min;
+
+ [DataField(required: true)]
+ public int Max;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-age-requirement",
+ ("inverted", Inverted), ("min", Min), ("max", Max)));
+ return profile.Age >= Min && profile.Age <= Max;
+ }
+}
+
+///
+/// Requires the profile to use either a Backpack, Satchel, or Duffelbag
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterBackpackTypeRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public BackpackPreference Preference;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-backpack-type-requirement",
+ ("inverted", Inverted),
+ ("type", Loc.GetString($"humanoid-profile-editor-preference-{Preference.ToString().ToLower()}"))));
+ return profile.Backpack == Preference;
+ }
+}
+
+///
+/// Requires the profile to use either Jumpsuits or Jumpskirts
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterClothingPreferenceRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public ClothingPreference Preference;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-clothing-preference-requirement",
+ ("inverted", Inverted),
+ ("preference", Loc.GetString($"humanoid-profile-editor-preference-{Preference.ToString().ToLower()}"))));
+ return profile.Clothing == Preference;
+ }
+}
+
+///
+/// Requires the profile to be a certain species
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterSpeciesRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public ProtoId Species;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-species-requirement",
+ ("inverted", Inverted),
+ ("species", Loc.GetString($"species-name-{Species.ToString().ToLower()}"))));
+ return profile.Species == Species;
+ }
+}
+
+///
+/// Requires the profile to have one of the specified traits
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterTraitRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public List> Traits;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-trait-requirement", ("inverted", Inverted),
+ ("traits", string.Join(", ", Traits.Select(t => Loc.GetString($"trait-name-{t}"))))));
+
+ return Traits.Any(t => profile.TraitPreferences.Contains(t.ToString()));
+ }
+}
+
+///
+/// Requires the profile to have one of the specified loadouts
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterLoadoutRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public List> Loadouts;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes, IEntityManager entityManager, IPrototypeManager prototypeManager,
+ IConfigurationManager configManager, out FormattedMessage? reason)
+ {
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-loadout-requirement", ("inverted", Inverted),
+ ("loadouts", string.Join(", ", Loadouts.Select(l => Loc.GetString($"loadout-{l}"))))));
+
+ return Loadouts.Any(l => profile.LoadoutPreferences.Contains(l.ToString()));
+ }
+}
+
+#endregion
+
+#region Jobs
+
+///
+/// Requires the selected job to be one of the specified jobs
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterJobRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public List> Jobs;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ var jobs = new List();
+
+ // Get the job names and department colors
+ foreach (var j in Jobs)
+ {
+ var jobProto = prototypeManager.Index(j);
+ var color = Color.LightBlue;
+
+ foreach (var dept in prototypeManager.EnumeratePrototypes()
+ .OrderBy(d => Loc.GetString($"department-{d.ID}")))
+ {
+ if (!dept.Roles.Contains(j))
+ continue;
+
+ color = dept.Color;
+ break;
+ }
+
+ jobs.Add(FormattedMessage.FromMarkup($"[color={color.ToHex()}]{Loc.GetString(jobProto.Name)}[/color]"));
+ }
+
+ // Join the job names
+ var jobsList = string.Join(", ", jobs.Select(j => j.ToMarkup()));
+ var jobsString = Loc.GetString("character-job-requirement",
+ ("inverted", Inverted), ("jobs", jobsList));
+
+ reason = FormattedMessage.FromMarkup(jobsString);
+ return Jobs.Contains(job.ID);
+ }
+}
+
+///
+/// Requires the selected job to be in one of the specified departments
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterDepartmentRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public List> Departments;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ var departments = new List();
+
+ // Get the department names and colors
+ foreach (var d in Departments)
+ {
+ var deptProto = prototypeManager.Index(d);
+ var color = deptProto.Color;
+
+ departments.Add(FormattedMessage.FromMarkup($"[color={color.ToHex()}]{Loc.GetString($"department-{deptProto.ID}")}[/color]"));
+ }
+
+ // Join the department names
+ var departmentsList = string.Join(", ", departments.Select(d => d.ToMarkup()));
+ var departmentsString = Loc.GetString("character-department-requirement",
+ ("inverted", Inverted), ("departments", departmentsList));
+
+ reason = FormattedMessage.FromMarkup(departmentsString);
+ return Departments.Any(d => prototypeManager.Index(d).Roles.Contains(job.ID));
+ }
+}
+
+///
+/// Requires the playtime for a department to be within a certain range
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterDepartmentTimeRequirement : CharacterRequirement
+{
+ [DataField]
+ public TimeSpan Min = TimeSpan.MinValue;
+
+ [DataField]
+ public TimeSpan Max = TimeSpan.MaxValue;
+
+ [DataField(required: true)]
+ public ProtoId Department;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ // Disable the requirement if the role timers are disabled
+ if (!configManager.GetCVar(CCVars.GameRoleTimers))
+ {
+ reason = null;
+ return !Inverted;
+ }
+
+ var department = prototypeManager.Index(Department);
+
+ // Combine all of this department's job playtimes
+ var playtime = TimeSpan.Zero;
+ foreach (var other in department.Roles)
+ {
+ var proto = prototypeManager.Index(other).PlayTimeTracker;
+
+ playTimes.TryGetValue(proto, out var otherTime);
+ playtime += otherTime;
+ }
+
+ if (playtime > Max)
+ {
+ // Show the reason if invalid
+ reason = Inverted
+ ? null
+ : FormattedMessage.FromMarkup(Loc.GetString("character-timer-department-too-high",
+ ("time", playtime.Minutes - Max.Minutes),
+ ("department", Loc.GetString($"department-{department.ID}")),
+ ("departmentColor", department.Color)));
+ return false;
+ }
+
+ if (playtime < Min)
+ {
+ // Show the reason if invalid
+ reason = Inverted
+ ? null
+ : FormattedMessage.FromMarkup(Loc.GetString("character-timer-department-insufficient",
+ ("time", Min.Minutes - playtime.Minutes),
+ ("department", Loc.GetString($"department-{department.ID}")),
+ ("departmentColor", department.Color)));
+ return false;
+ }
+
+ reason = null;
+ return true;
+ }
+}
+
+///
+/// Requires the player to have a certain amount of overall job time
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterOverallTimeRequirement : CharacterRequirement
+{
+ [DataField]
+ public TimeSpan Min = TimeSpan.MinValue;
+
+ [DataField]
+ public TimeSpan Max = TimeSpan.MaxValue;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ // Disable the requirement if the role timers are disabled
+ if (!configManager.GetCVar(CCVars.GameRoleTimers))
+ {
+ reason = null;
+ return !Inverted;
+ }
+
+ // Get the overall time
+ var overallTime = playTimes.GetValueOrDefault(PlayTimeTrackingShared.TrackerOverall);
+
+ if (overallTime > Max)
+ {
+ // Show the reason if invalid
+ reason = Inverted
+ ? null
+ : FormattedMessage.FromMarkup(Loc.GetString("character-timer-overall-too-high",
+ ("time", overallTime.Minutes - Max.Minutes)));
+ return false;
+ }
+
+ if (overallTime < Min)
+ {
+ // Show the reason if invalid
+ reason = Inverted
+ ? null
+ : FormattedMessage.FromMarkup(Loc.GetString("character-timer-overall-insufficient",
+ ("time", Min.Minutes - overallTime.Minutes)));
+ return false;
+ }
+
+ reason = null;
+ return true;
+ }
+}
+
+///
+/// Requires the playtime for a tracker to be within a certain range
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class CharacterPlaytimeRequirement : CharacterRequirement
+{
+ [DataField]
+ public TimeSpan Min = TimeSpan.MinValue;
+
+ [DataField]
+ public TimeSpan Max = TimeSpan.MaxValue;
+
+ [DataField(required: true)]
+ public ProtoId Tracker;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ // Disable the requirement if the role timers are disabled
+ if (!configManager.GetCVar(CCVars.GameRoleTimers))
+ {
+ reason = null;
+ return !Inverted;
+ }
+
+ // Get SharedJobSystem
+ if (!entityManager.EntitySysManager.TryGetEntitySystem(out SharedJobSystem? jobSystem))
+ {
+ DebugTools.Assert("CharacterRequirements: SharedJobSystem not found");
+ reason = null;
+ return false;
+ }
+
+ // Get the JobPrototype of the Tracker
+ var trackerJob = jobSystem.GetJobPrototype(Tracker);
+
+ // Get the primary department of the Tracker
+ if (!jobSystem.TryGetPrimaryDepartment(trackerJob, out var department) &&
+ !jobSystem.TryGetDepartment(trackerJob, out department))
+ {
+ DebugTools.Assert($"CharacterRequirements: Department not found for job {trackerJob}");
+ reason = null;
+ return false;
+ }
+
+ // Get the time for the tracker
+ var time = playTimes.GetValueOrDefault(Tracker);
+ reason = null;
+
+ if (time > Max)
+ {
+ // Show the reason if invalid
+ reason = Inverted
+ ? null
+ : FormattedMessage.FromMarkup(Loc.GetString("character-timer-role-too-high",
+ ("time", time.Minutes - Max.Minutes),
+ ("job", trackerJob),
+ ("departmentColor", department.Color)));
+ return false;
+ }
+
+ if (time < Min)
+ {
+ // Show the reason if invalid
+ reason = Inverted
+ ? null
+ : FormattedMessage.FromMarkup(Loc.GetString("character-timer-role-insufficient",
+ ("time", Min.Minutes - time.Minutes),
+ ("job", trackerJob),
+ ("departmentColor", department.Color)));
+ return false;
+ }
+
+ return true;
+ }
+}
+
+#endregion
+
+#region Prototype Groups
+
+///
+/// Requires the profile to not have any of the specified traits
+///
+///
+/// Only works if you put this prototype in the denied prototypes' requirements too.
+/// Can't be inverted, use
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class TraitGroupExclusionRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public List> Prototypes;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ var invalid = profile.TraitPreferences.Any(t => Prototypes.Contains(t));
+
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-trait-group-exclusion-requirement",
+ ("traits", string.Join(", ", Prototypes.Select(t => Loc.GetString($"trait-name-{t}"))))));
+
+ return Inverted ? invalid : !invalid;
+ }
+}
+
+///
+/// Requires the profile to not have any of the specified loadouts
+///
+///
+/// Only works if you put this prototype in the denied prototypes' requirements too.
+/// Can't be inverted, use
+///
+[UsedImplicitly]
+[Serializable, NetSerializable]
+public sealed partial class LoadoutGroupExclusionRequirement : CharacterRequirement
+{
+ [DataField(required: true)]
+ public List> Prototypes;
+
+ public override bool IsValid(IPrototype prototype, JobPrototype job, HumanoidCharacterProfile profile,
+ Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out FormattedMessage? reason)
+ {
+ var invalid = profile.LoadoutPreferences.Any(l => Prototypes.Contains(l));
+
+ reason = FormattedMessage.FromMarkup(Loc.GetString("character-loadout-group-exclusion-requirement",
+ ("loadouts", string.Join(", ", Prototypes.Select(l => Loc.GetString($"loadout-{l}"))))));
+
+ return Inverted ? invalid : !invalid;
+ }
+}
+
+#endregion
diff --git a/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs b/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs
new file mode 100644
index 0000000000..e93c933a6a
--- /dev/null
+++ b/Content.Shared/Customization/Systems/CharacterRequirementsSystem.cs
@@ -0,0 +1,43 @@
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Customization.Systems;
+
+
+public sealed class CharacterRequirementsSystem : EntitySystem
+{
+ public bool CheckRequirementsValid(IPrototype prototype, List requirements, JobPrototype job,
+ HumanoidCharacterProfile profile, Dictionary playTimes,
+ IEntityManager entityManager, IPrototypeManager prototypeManager, IConfigurationManager configManager,
+ out List reasons)
+ {
+ reasons = new List();
+ var valid = true;
+
+ foreach (var requirement in requirements)
+ {
+ // Set valid to false if the requirement is invalid and not inverted
+ // If it's inverted set valid to false when it's valid
+ if (!requirement.IsValid(prototype, job, profile, playTimes,
+ entityManager, prototypeManager, configManager,
+ out var reason))
+ {
+ if (valid)
+ valid = requirement.Inverted;
+ }
+ else
+ {
+ if (valid)
+ valid = !requirement.Inverted;
+ }
+
+ if (reason != null) // To appease the compiler
+ reasons.Add(reason);
+ }
+
+ return valid;
+ }
+}
diff --git a/Content.Shared/Drunk/DrunkSystem.cs b/Content.Shared/Drunk/DrunkSystem.cs
index 4f9429b6a6..161d4729ed 100644
--- a/Content.Shared/Drunk/DrunkSystem.cs
+++ b/Content.Shared/Drunk/DrunkSystem.cs
@@ -1,6 +1,6 @@
using Content.Shared.Speech.EntitySystems;
using Content.Shared.StatusEffect;
-using Content.Shared.Traits.Assorted;
+using Content.Shared.Traits.Assorted.Components;
namespace Content.Shared.Drunk;
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index ece9a82bb2..8cf504582d 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -393,19 +393,19 @@ namespace Content.Shared.Preferences
public bool MemberwiseEquals(ICharacterProfile maybeOther)
{
- if (maybeOther is not HumanoidCharacterProfile other ||
- Name != other.Name ||
- Age != other.Age ||
- Sex != other.Sex ||
- Gender != other.Gender ||
- PreferenceUnavailable != other.PreferenceUnavailable ||
- Clothing != other.Clothing ||
- Backpack != other.Backpack ||
- SpawnPriority != other.SpawnPriority ||
- !_jobPriorities.SequenceEqual(other._jobPriorities) ||
- !_antagPreferences.SequenceEqual(other._antagPreferences) ||
- !_traitPreferences.SequenceEqual(other._traitPreferences) ||
- !_loadoutPreferences.SequenceEqual(other._loadoutPreferences))
+ if (maybeOther is not HumanoidCharacterProfile other
+ || Name != other.Name
+ || Age != other.Age
+ || Sex != other.Sex
+ || Gender != other.Gender
+ || PreferenceUnavailable != other.PreferenceUnavailable
+ || Clothing != other.Clothing
+ || Backpack != other.Backpack
+ || SpawnPriority != other.SpawnPriority
+ || !_jobPriorities.SequenceEqual(other._jobPriorities)
+ || !_antagPreferences.SequenceEqual(other._antagPreferences)
+ || !_traitPreferences.SequenceEqual(other._traitPreferences)
+ || !_loadoutPreferences.SequenceEqual(other._loadoutPreferences))
return false;
return Appearance.MemberwiseEquals(other.Appearance);
@@ -545,23 +545,42 @@ namespace Content.Shared.Preferences
.Where(prototypeManager.HasIndex)
.ToList();
+ var maxTraits = configManager.GetCVar(CCVars.GameTraitsMax);
+ var currentTraits = 0;
+ var traitPoints = configManager.GetCVar(CCVars.GameTraitsDefaultPoints);
+
+ foreach (var trait in traits.OrderBy(t => -prototypeManager.Index(t).Points).ToList())
+ {
+ var proto = prototypeManager.Index(trait);
+
+ if (traitPoints + proto.Points < 0 || currentTraits + 1 > maxTraits)
+ traits.Remove(trait);
+ else
+ {
+ traitPoints += proto.Points;
+ currentTraits++;
+ }
+ }
+
+
var loadouts = LoadoutPreferences
.Where(prototypeManager.HasIndex)
.ToList();
- var maxLoadouts = configManager.GetCVar(CCVars.GameLoadoutsPoints);
- var currentLoadouts = 0;
+ var loadoutPoints = configManager.GetCVar(CCVars.GameLoadoutsPoints);
+ var currentPoints = 0;
foreach (var loadout in loadouts.ToList())
{
var proto = prototypeManager.Index(loadout);
- if (currentLoadouts + proto.Cost > maxLoadouts)
+ if (currentPoints + proto.Cost > loadoutPoints)
loadouts.Remove(loadout);
else
- currentLoadouts += proto.Cost;
+ currentPoints += proto.Cost;
}
+
Name = name;
FlavorText = flavortext;
Age = age;
diff --git a/Content.Shared/Traits/Assorted/AccentlessComponent.cs b/Content.Shared/Traits/Assorted/Components/AccentlessComponent.cs
similarity index 89%
rename from Content.Shared/Traits/Assorted/AccentlessComponent.cs
rename to Content.Shared/Traits/Assorted/Components/AccentlessComponent.cs
index 96ebf4d83f..084a1e1d92 100644
--- a/Content.Shared/Traits/Assorted/AccentlessComponent.cs
+++ b/Content.Shared/Traits/Assorted/Components/AccentlessComponent.cs
@@ -1,7 +1,7 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Components;
///
/// This is used for the accentless trait
diff --git a/Content.Shared/Traits/Assorted/LegsParalyzedComponent.cs b/Content.Shared/Traits/Assorted/Components/LegsParalyzedComponent.cs
similarity index 65%
rename from Content.Shared/Traits/Assorted/LegsParalyzedComponent.cs
rename to Content.Shared/Traits/Assorted/Components/LegsParalyzedComponent.cs
index 59f9ca758b..d0639e5933 100644
--- a/Content.Shared/Traits/Assorted/LegsParalyzedComponent.cs
+++ b/Content.Shared/Traits/Assorted/Components/LegsParalyzedComponent.cs
@@ -1,6 +1,7 @@
-using Robust.Shared.GameStates;
+using Content.Shared.Traits.Assorted.Systems;
+using Robust.Shared.GameStates;
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Components;
///
/// Set player speed to zero and standing state to down, simulating leg paralysis.
diff --git a/Content.Shared/Traits/Assorted/LightweightDrunkComponent.cs b/Content.Shared/Traits/Assorted/Components/LightweightDrunkComponent.cs
similarity index 90%
rename from Content.Shared/Traits/Assorted/LightweightDrunkComponent.cs
rename to Content.Shared/Traits/Assorted/Components/LightweightDrunkComponent.cs
index 5d353ac963..62d2f5899a 100644
--- a/Content.Shared/Traits/Assorted/LightweightDrunkComponent.cs
+++ b/Content.Shared/Traits/Assorted/Components/LightweightDrunkComponent.cs
@@ -1,7 +1,7 @@
-using Robust.Shared.GameStates;
using Content.Shared.Drunk;
+using Robust.Shared.GameStates;
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Components;
///
/// Used for the lightweight trait. DrunkSystem will check for this component and modify the boozePower accordingly if it finds it.
diff --git a/Content.Shared/Traits/Assorted/ParacusiaComponent.cs b/Content.Shared/Traits/Assorted/Components/ParacusiaComponent.cs
similarity index 93%
rename from Content.Shared/Traits/Assorted/ParacusiaComponent.cs
rename to Content.Shared/Traits/Assorted/Components/ParacusiaComponent.cs
index 1db698359b..ff62e55c2a 100644
--- a/Content.Shared/Traits/Assorted/ParacusiaComponent.cs
+++ b/Content.Shared/Traits/Assorted/Components/ParacusiaComponent.cs
@@ -1,8 +1,9 @@
+using Content.Shared.Traits.Assorted.Systems;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Components;
///
/// This component is used for paracusia, which causes auditory hallucinations.
diff --git a/Content.Shared/Traits/Assorted/PermanentBlindnessComponent.cs b/Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs
similarity index 81%
rename from Content.Shared/Traits/Assorted/PermanentBlindnessComponent.cs
rename to Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs
index 76ff3e1005..c1bf7e1639 100644
--- a/Content.Shared/Traits/Assorted/PermanentBlindnessComponent.cs
+++ b/Content.Shared/Traits/Assorted/Components/PermanentBlindnessComponent.cs
@@ -1,6 +1,6 @@
using Robust.Shared.GameStates;
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Components;
///
/// This is used for making something blind forever.
diff --git a/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs b/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs
deleted file mode 100644
index 7c91366937..0000000000
--- a/Content.Shared/Traits/Assorted/LegsParalyzedSystem.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using Content.Shared.Body.Systems;
-using Content.Shared.Buckle.Components;
-using Content.Shared.Movement.Events;
-using Content.Shared.Movement.Systems;
-using Content.Shared.Standing;
-using Content.Shared.Throwing;
-
-namespace Content.Shared.Traits.Assorted;
-
-public sealed class LegsParalyzedSystem : EntitySystem
-{
- [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
- [Dependency] private readonly StandingStateSystem _standingSystem = default!;
- [Dependency] private readonly SharedBodySystem _bodySystem = default!;
-
- public override void Initialize()
- {
- SubscribeLocalEvent(OnStartup);
- SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnBuckleChange);
- SubscribeLocalEvent(OnThrowPushbackAttempt);
- SubscribeLocalEvent(OnUpdateCanMoveEvent);
- }
-
- private void OnStartup(EntityUid uid, LegsParalyzedComponent component, ComponentStartup args)
- {
- // TODO: In future probably must be surgery related wound
- _movementSpeedModifierSystem.ChangeBaseSpeed(uid, 0, 0, 20);
- }
-
- private void OnShutdown(EntityUid uid, LegsParalyzedComponent component, ComponentShutdown args)
- {
- _standingSystem.Stand(uid);
- _bodySystem.UpdateMovementSpeed(uid);
- }
-
- private void OnBuckleChange(EntityUid uid, LegsParalyzedComponent component, ref BuckleChangeEvent args)
- {
- if (args.Buckling)
- {
- _standingSystem.Stand(args.BuckledEntity);
- }
- else
- {
- _standingSystem.Down(args.BuckledEntity);
- }
- }
-
- private void OnUpdateCanMoveEvent(EntityUid uid, LegsParalyzedComponent component, UpdateCanMoveEvent args)
- {
- args.Cancel();
- }
-
- private void OnThrowPushbackAttempt(EntityUid uid, LegsParalyzedComponent component, ThrowPushbackAttemptEvent args)
- {
- args.Cancel();
- }
-}
diff --git a/Content.Shared/Traits/Assorted/AccentlessSystem.cs b/Content.Shared/Traits/Assorted/Systems/AccentlessSystem.cs
similarity index 62%
rename from Content.Shared/Traits/Assorted/AccentlessSystem.cs
rename to Content.Shared/Traits/Assorted/Systems/AccentlessSystem.cs
index 2242bc6e52..f4e077bc1a 100644
--- a/Content.Shared/Traits/Assorted/AccentlessSystem.cs
+++ b/Content.Shared/Traits/Assorted/Systems/AccentlessSystem.cs
@@ -1,6 +1,4 @@
-using Robust.Shared.Serialization.Manager;
-
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Systems;
///
/// This handles removing accents when using the accentless trait.
@@ -12,10 +10,10 @@ public sealed class AccentlessSystem : EntitySystem
{
base.Initialize();
- SubscribeLocalEvent(RemoveAccents);
+ SubscribeLocalEvent(RemoveAccents);
}
- private void RemoveAccents(EntityUid uid, AccentlessComponent component, ComponentStartup args)
+ private void RemoveAccents(EntityUid uid, Components.AccentlessComponent component, ComponentStartup args)
{
foreach (var accent in component.RemovedAccents.Values)
{
diff --git a/Content.Shared/Traits/Assorted/Systems/LegsParalyzedSystem.cs b/Content.Shared/Traits/Assorted/Systems/LegsParalyzedSystem.cs
new file mode 100644
index 0000000000..8ae0f251b8
--- /dev/null
+++ b/Content.Shared/Traits/Assorted/Systems/LegsParalyzedSystem.cs
@@ -0,0 +1,58 @@
+using Content.Shared.Body.Systems;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Movement.Events;
+using Content.Shared.Movement.Systems;
+using Content.Shared.Standing;
+using Content.Shared.Throwing;
+
+namespace Content.Shared.Traits.Assorted.Systems;
+
+public sealed class LegsParalyzedSystem : EntitySystem
+{
+ [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifierSystem = default!;
+ [Dependency] private readonly StandingStateSystem _standingSystem = default!;
+ [Dependency] private readonly SharedBodySystem _bodySystem = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnBuckleChange);
+ SubscribeLocalEvent(OnThrowPushbackAttempt);
+ SubscribeLocalEvent(OnUpdateCanMoveEvent);
+ }
+
+ private void OnStartup(EntityUid uid, Components.LegsParalyzedComponent component, ComponentStartup args)
+ {
+ // TODO: In future probably must be surgery related wound
+ _movementSpeedModifierSystem.ChangeBaseSpeed(uid, 0, 0, 20);
+ }
+
+ private void OnShutdown(EntityUid uid, Components.LegsParalyzedComponent component, ComponentShutdown args)
+ {
+ _standingSystem.Stand(uid);
+ _bodySystem.UpdateMovementSpeed(uid);
+ }
+
+ private void OnBuckleChange(EntityUid uid, Components.LegsParalyzedComponent component, ref BuckleChangeEvent args)
+ {
+ if (args.Buckling)
+ {
+ _standingSystem.Stand(args.BuckledEntity);
+ }
+ else
+ {
+ _standingSystem.Down(args.BuckledEntity);
+ }
+ }
+
+ private void OnUpdateCanMoveEvent(EntityUid uid, Components.LegsParalyzedComponent component, UpdateCanMoveEvent args)
+ {
+ args.Cancel();
+ }
+
+ private void OnThrowPushbackAttempt(EntityUid uid, Components.LegsParalyzedComponent component, ThrowPushbackAttemptEvent args)
+ {
+ args.Cancel();
+ }
+}
diff --git a/Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs b/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs
similarity index 59%
rename from Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs
rename to Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs
index 9fd5db8497..113939f66b 100644
--- a/Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs
+++ b/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs
@@ -4,7 +4,7 @@ using Content.Shared.Eye.Blinding.Systems;
using Content.Shared.IdentityManagement;
using Robust.Shared.Network;
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Systems;
///
/// This handles permanent blindness, both the examine and the actual effect.
@@ -18,26 +18,26 @@ public sealed class PermanentBlindnessSystem : EntitySystem
///
public override void Initialize()
{
- SubscribeLocalEvent(OnStartup);
- SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnDamageChanged);
- SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnDamageChanged);
+ SubscribeLocalEvent(OnExamined);
}
- private void OnExamined(Entity blindness, ref ExaminedEvent args)
+ private void OnExamined(Entity blindness, ref ExaminedEvent args)
{
if (args.IsInDetailsRange && !_net.IsClient)
{
- args.PushMarkup(Loc.GetString("permanent-blindness-trait-examined", ("target", Identity.Entity(blindness, EntityManager))));
+ args.PushMarkup(Loc.GetString("trait-examined-Blindness", ("target", Identity.Entity(blindness, EntityManager))));
}
}
- private void OnShutdown(Entity blindness, ref ComponentShutdown args)
+ private void OnShutdown(Entity blindness, ref ComponentShutdown args)
{
_blinding.UpdateIsBlind(blindness.Owner);
}
- private void OnStartup(Entity blindness, ref ComponentStartup args)
+ private void OnStartup(Entity blindness, ref ComponentStartup args)
{
if (!_entityManager.TryGetComponent(blindness, out var blindable))
return;
@@ -50,7 +50,7 @@ public sealed class PermanentBlindnessSystem : EntitySystem
_blinding.AdjustEyeDamage(blindness.Owner, damageToDeal);
}
- private void OnDamageChanged(Entity blindness, ref EyeDamageChangedEvent args)
+ private void OnDamageChanged(Entity blindness, ref EyeDamageChangedEvent args)
{
if (args.Damage >= BlurryVisionComponent.MaxMagnitude)
return;
diff --git a/Content.Shared/Traits/Assorted/SharedParacusiaSystem.cs b/Content.Shared/Traits/Assorted/Systems/SharedParacusiaSystem.cs
similarity index 56%
rename from Content.Shared/Traits/Assorted/SharedParacusiaSystem.cs
rename to Content.Shared/Traits/Assorted/Systems/SharedParacusiaSystem.cs
index 2bfb0da1f5..151e748445 100644
--- a/Content.Shared/Traits/Assorted/SharedParacusiaSystem.cs
+++ b/Content.Shared/Traits/Assorted/Systems/SharedParacusiaSystem.cs
@@ -1,4 +1,4 @@
-namespace Content.Shared.Traits.Assorted;
+namespace Content.Shared.Traits.Assorted.Systems;
public abstract class SharedParacusiaSystem : EntitySystem
{
diff --git a/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs b/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs
new file mode 100644
index 0000000000..efbac1ca7d
--- /dev/null
+++ b/Content.Shared/Traits/Prototypes/TraitCategoryPrototype.cs
@@ -0,0 +1,14 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Traits;
+
+
+///
+/// A prototype defining a valid category for s to go into.
+///
+[Prototype("traitCategory")]
+public sealed partial class TraitCategoryPrototype : IPrototype
+{
+ [IdDataField]
+ public string ID { get; } = default!;
+}
diff --git a/Content.Shared/Traits/Prototypes/TraitPrototype.cs b/Content.Shared/Traits/Prototypes/TraitPrototype.cs
new file mode 100644
index 0000000000..2e6b7cc066
--- /dev/null
+++ b/Content.Shared/Traits/Prototypes/TraitPrototype.cs
@@ -0,0 +1,39 @@
+using Content.Shared.Customization.Systems;
+using Content.Shared.Whitelist;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Traits;
+
+
+///
+/// Describes a trait.
+///
+[Prototype("trait")]
+public sealed partial class TraitPrototype : IPrototype
+{
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ ///
+ /// Which customization tab to place this entry in
+ ///
+ [DataField(required: true), ValidatePrototypeId]
+ public string Category = "Uncategorized";
+
+ ///
+ /// How many points this will give the character
+ ///
+ [DataField]
+ public int Points = 0;
+
+
+ [DataField]
+ public List Requirements = new();
+
+ ///
+ /// The components that get added to the player when they pick this trait.
+ ///
+ [DataField(required: true)]
+ public ComponentRegistry Components { get; private set; } = default!;
+}
diff --git a/Content.Shared/Traits/TraitPrototype.cs b/Content.Shared/Traits/TraitPrototype.cs
deleted file mode 100644
index 34feb8da22..0000000000
--- a/Content.Shared/Traits/TraitPrototype.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using Content.Shared.Whitelist;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-// don't worry about it
-
-namespace Content.Shared.Traits
-{
- ///
- /// Describes a trait.
- ///
- [Prototype("trait")]
- public sealed partial class TraitPrototype : IPrototype
- {
- [ViewVariables]
- [IdDataField]
- public string ID { get; private set; } = default!;
-
- ///
- /// The name of this trait.
- ///
- [DataField("name")]
- public string Name { get; private set; } = "";
-
- ///
- /// The description of this trait.
- ///
- [DataField("description")]
- public string? Description { get; private set; }
-
- ///
- /// Don't apply this trait to entities this whitelist IS NOT valid for.
- ///
- [DataField("whitelist")]
- public EntityWhitelist? Whitelist;
-
- ///
- /// Don't apply this trait to entities this whitelist IS valid for. (hence, a blacklist)
- ///
- [DataField("blacklist")]
- public EntityWhitelist? Blacklist;
-
- ///
- /// The components that get added to the player, when they pick this trait.
- ///
- [DataField("components")]
- public ComponentRegistry Components { get; private set; } = default!;
-
- ///
- /// Gear that is given to the player, when they pick this trait.
- ///
- [DataField("traitGear", required: false, customTypeSerializer:typeof(PrototypeIdSerializer))]
- public string? TraitGear;
- }
-}
diff --git a/Resources/Locale/en-US/customization/character-requirements.ftl b/Resources/Locale/en-US/customization/character-requirements.ftl
new file mode 100644
index 0000000000..b073bdb773
--- /dev/null
+++ b/Resources/Locale/en-US/customization/character-requirements.ftl
@@ -0,0 +1,39 @@
+character-age-requirement = You must {$inverted ->
+ [true] not be
+ *[other] be
+} be within [color=yellow]{$min}[/color] and [color=yellow]{$max}[/color] years old
+character-species-requirement = You must {$inverted ->
+ [true] not be
+ *[other] be
+} a [color=green]{$species}[/color]
+character-trait-requirement = You must {$inverted ->
+ [true] not have
+ *[other] have
+} the trait [color=lightblue]{$trait}[/color]
+character-backpack-type-requirement = You must {$inverted ->
+ [true] not use
+ *[other] use
+} a [color=lightblue]{$type}[/color] as your bag
+character-clothing-preference-requirement = You must {$inverted ->
+ [true] not wear
+ *[other] wear
+} a [color=lightblue]{$type}[/color]
+
+character-job-requirement = You must {$inverted ->
+ [true] not be
+ *[other] be
+} one of these jobs: {$jobs}
+character-department-requirement = You must {$inverted ->
+ [true] not be
+ *[other] be
+} in one of these departments: {$departments}
+
+character-timer-department-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of [color={$departmentColor}]{$department}[/color] department playtime
+character-timer-department-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes in [color={$departmentColor}]{$department}[/color] department
+character-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of playtime
+character-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime
+character-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color]
+character-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color]
+
+character-trait-group-exclusion-requirement = You cannot have one of the following traits if you select this: {$traits}
+character-loadout-group-exclusion-requirement = You cannot have one of the following loadouts if you select this: {$loadouts}
diff --git a/Resources/Locale/en-US/deltav/traits/traits.ftl b/Resources/Locale/en-US/deltav/traits/traits.ftl
index e00cec4707..914a5c9f1b 100644
--- a/Resources/Locale/en-US/deltav/traits/traits.ftl
+++ b/Resources/Locale/en-US/deltav/traits/traits.ftl
@@ -1,13 +1,13 @@
-trait-scottish-accent-name = Scottish Accent
-trait-scottish-accent-desc = Fer tha folk who come frae Hielan clan.
+trait-name-ScottishAccent = Scottish Accent
+trait-description-ScottishAccent = Fer tha folk who come frae Hielan clan.
-trait-ultravision-name = Ultraviolet Vision
-trait-ultravision-desc = Whether through custom bionic eyes, random mutation,
+trait-name-UltraVision = Ultraviolet Vision
+trait-description-UltraVision = Whether through custom bionic eyes, random mutation,
or being a Harpy, you perceive the world with ultraviolet light.
-trait-deuteranopia-name = Deuteranopia
-trait-deuteranopia-desc = Whether through custom bionic eyes, random mutation,
+trait-name-DogVision = Deuteranopia
+trait-description-DogVision = Whether through custom bionic eyes, random mutation,
or being a Vulpkanin, you have red–green colour blindness.
-trait-uncloneable-name = Uncloneable
-trait-uncloneable-desc = Cannot be cloned
+trait-name-Uncloneable = Uncloneable
+trait-description-Uncloneable = Cannot be cloned
diff --git a/Resources/Locale/en-US/loadouts/categories.ftl b/Resources/Locale/en-US/loadouts/categories.ftl
index bdfa215e6a..685c5cbcbd 100644
--- a/Resources/Locale/en-US/loadouts/categories.ftl
+++ b/Resources/Locale/en-US/loadouts/categories.ftl
@@ -1,7 +1,8 @@
-# Alphabetically ordered
+# Alphabetically ordered, except for Uncategorized since it is always first
+
+loadout-category-Uncategorized = Uncategorized
loadout-category-Accessories = Accessories
loadout-category-Items = Items
loadout-category-Jobs = Jobs
loadout-category-Outer = Outer
-loadout-category-Uncategorized = Uncategorized
loadout-category-Uniform = Uniform
diff --git a/Resources/Locale/en-US/loadouts/loadout-requirements.ftl b/Resources/Locale/en-US/loadouts/loadout-requirements.ftl
deleted file mode 100644
index 5a453ed5cb..0000000000
--- a/Resources/Locale/en-US/loadouts/loadout-requirements.ftl
+++ /dev/null
@@ -1,13 +0,0 @@
-loadout-age-requirement = You must be within {$min} and {$max} years old
-loadout-species-requirement = You must be a {$species}
-loadout-trait-requirement = You must have the trait {$trait}
-loadout-backpack-type-requirement = You must use a {$type} as your bag
-loadout-clothing-preference-requirement = You must wear a {$type}
-
-loadout-job-requirement = You must be one of these jobs: {$job}
-loadout-timer-department-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of [color={$departmentColor}]{$department}[/color] department playtime
-loadout-timer-department-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes in [color={$departmentColor}]{$department}[/color] department
-loadout-timer-overall-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes of playtime
-loadout-timer-overall-too-high = You require [color=yellow]{TOSTRING($time, "0")}[/color] fewer minutes of playtime
-loadout-timer-role-insufficient = You require [color=yellow]{TOSTRING($time, "0")}[/color] more minutes with [color={$departmentColor}]{$job}[/color]
-loadout-timer-role-too-high = You require[color=yellow] {TOSTRING($time, "0")}[/color] fewer minutes with [color={$departmentColor}]{$job}[/color]
diff --git a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
index b929da9655..9b8eb74d96 100644
--- a/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
+++ b/Resources/Locale/en-US/preferences/ui/humanoid-profile-editor.ftl
@@ -44,20 +44,40 @@ humanoid-profile-editor-department-jobs-label = {$departmentName} jobs
humanoid-profile-editor-antags-tab = Antags
humanoid-profile-editor-antag-preference-yes-button = Yes
humanoid-profile-editor-antag-preference-no-button = No
+
humanoid-profile-editor-traits-tab = Traits
+humanoid-profile-editor-traits-header = You have {$points ->
+ [1] 1 point
+ *[other] {$points} points
+} and {$maxTraits ->
+ [2147483648] {$traits ->
+ [1] {$traits} trait
+ *[other] {$traits} traits
+ }
+ *[other] {$traits}/{$maxTraits} traits
+}
+humanoid-profile-editor-traits-show-unusable-button = Show Unusable
+humanoid-profile-editor-traits-show-unusable-button-tooltip =
+ When enabled, traits that your current character setup cannot use will be shown highlighted in red.
+ You will still not be able to use the invalid traits unless your character setup changes to fit the requirements.
+ This is most likely useful only if there's a bug hiding traits you actually can use or if you want to see other species' traits or something.
+humanoid-profile-editor-traits-no-traits = No traits found
+
humanoid-profile-editor-job-priority-high-button = High
humanoid-profile-editor-job-priority-medium-button = Medium
humanoid-profile-editor-job-priority-low-button = Low
humanoid-profile-editor-job-priority-never-button = Never
+
humanoid-profile-editor-naming-rules-warning = Warning: Offensive or LRP IC names and descriptions will lead to admin intervention on this server. Read our \[Rules\] for more.
+
humanoid-profile-editor-loadouts-tab = Loadout
-humanoid-profile-editor-loadouts-uncategorized-tab = Uncategorized
-humanoid-profile-editor-loadouts-no-loadouts = No loadouts found
humanoid-profile-editor-loadouts-points-label = You have {$points}/{$max} points
humanoid-profile-editor-loadouts-show-unusable-button = Show Unusable
humanoid-profile-editor-loadouts-show-unusable-button-tooltip =
When enabled, loadouts that your current character setup cannot use will be shown highlighted in red.
You will still not be able to use the invalid loadouts unless your character setup changes to fit the requirements.
- This may be useful if you like switching between multiple jobs and don't want to have to reselect your loadout every time.
+ This may be useful if you like switching between multiple jobs and don't want to have to reselect your loadout every
+humanoid-profile-editor-loadouts-no-loadouts = No loadouts foundtime.
+
humanoid-profile-editor-markings-tab = Markings
humanoid-profile-editor-flavortext-tab = Description
diff --git a/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl b/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl
index 3d8af06139..8360aaeb9d 100644
--- a/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl
+++ b/Resources/Locale/en-US/simplestation14/Traits/disabilities.ftl
@@ -1,3 +1,2 @@
-trait-nearsighted-name = Nearsighted
-trait-nearsighted-desc = You require glasses to see properly.
-trait-nearsighted-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are pretty unfocused. It doesn't seem like {SUBJECT($target)} can see things that well.[/color]
+trait-name-Nearsighted = Nearsighted
+trait-description-Nearsighted = You require glasses to see properly.
diff --git a/Resources/Locale/en-US/traits/categories.ftl b/Resources/Locale/en-US/traits/categories.ftl
new file mode 100644
index 0000000000..56f0adeb47
--- /dev/null
+++ b/Resources/Locale/en-US/traits/categories.ftl
@@ -0,0 +1,8 @@
+# Alphabetically ordered, except for Uncategorized since it is always first
+
+trait-category-Uncategorized = Uncategorized
+trait-category-Auditory = Auditory
+trait-category-Mental = Mental
+trait-category-Physical = Physical
+trait-category-Speech = Speech
+trait-category-Visual = Visual
diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl
index 7a3564edf6..e9163bdb54 100644
--- a/Resources/Locale/en-US/traits/traits.ftl
+++ b/Resources/Locale/en-US/traits/traits.ftl
@@ -1,34 +1,33 @@
-trait-blindness-name = Blindness
-trait-blindness-desc = You are legally blind, and can't see clearly past a few meters in front of you.
+trait-name-Blindness = Blindness
+trait-description-Blindness = You are legally blind, and can't see clearly past a few meters in front of you.
+trait-examined-Blindness = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you well, if at all.[/color]
-trait-narcolepsy-name = Narcolepsy
-trait-narcolepsy-desc = You fall asleep randomly
+trait-name-Narcolepsy = Narcolepsy
+trait-description-Narcolepsy = You fall asleep randomly
-trait-pacifist-name = Pacifist
-trait-pacifist-desc = You cannot attack or hurt any living beings.
+trait-name-Pacifist = Pacifist
+trait-description-Pacifist = You cannot attack or hurt any living beings.
-permanent-blindness-trait-examined = [color=lightblue]{CAPITALIZE(POSS-ADJ($target))} eyes are glassy and unfocused. It doesn't seem like {SUBJECT($target)} can see you well, if at all.[/color]
+trait-name-LightweightDrunk = Lightweight Drunk
+trait-description-LightweightDrunk = Alcohol has a stronger effect on you
-trait-lightweight-name = Lightweight Drunk
-trait-lightweight-desc = Alcohol has a stronger effect on you
+trait-name-Muted = Muted
+trait-description-Muted = You can't speak
-trait-muted-name = Muted
-trait-muted-desc = You can't speak
+trait-name-Paracusia = Paracusia
+trait-description-Paracusia = You hear sounds that aren't really there
-trait-paracusia-name = Paracusia
-trait-paracusia-desc = You hear sounds that aren't really there
+trait-name-PirateAccent = Pirate Accent
+trait-description-PirateAccent = You can't stop speaking like a pirate!
-trait-pirate-accent-name = Pirate Accent
-trait-pirate-accent-desc = You can't stop speaking like a pirate!
+trait-name-Accentless = Accentless
+trait-description-Accentless = You don't have the accent that your species would usually have
-trait-accentless-name = Accentless
-trait-accentless-desc = You don't have the accent that your species would usually have
+trait-name-FrontalLisp = Frontal Lisp
+trait-description-FrontalLisp = You thpeak with a lithp
-trait-frontal-lisp-name = Frontal Lisp
-trait-frontal-lisp-desc = You thpeak with a lithp
+trait-name-SocialAnxiety = Social Anxiety
+trait-description-SocialAnxiety = You are anxious when you speak and stutter.
-trait-socialanxiety-name = Social Anxiety
-trait-socialanxiety-desc = You are anxious when you speak and stutter.
-
-trait-snoring-name = Snoring
-trait-snoring-desc = You will snore while sleeping.
+trait-name-Snoring = Snoring
+trait-description-Snoring = You will snore while sleeping.
diff --git a/Resources/Prototypes/DeltaV/Traits/altvision.yml b/Resources/Prototypes/DeltaV/Traits/altvision.yml
index c361d1b51d..d1980bc23a 100644
--- a/Resources/Prototypes/DeltaV/Traits/altvision.yml
+++ b/Resources/Prototypes/DeltaV/Traits/altvision.yml
@@ -1,13 +1,13 @@
- type: trait
id: UltraVision
- name: trait-ultravision-name
- description: trait-ultravision-desc
+ category: Visual
+ points: -1
components:
- type: UltraVision
- type: trait
id: DogVision
- name: trait-deuteranopia-name
- description: trait-deuteranopia-desc
+ category: Visual
+ points: -1
components:
- type: DogVision
diff --git a/Resources/Prototypes/DeltaV/Traits/neutral.yml b/Resources/Prototypes/DeltaV/Traits/neutral.yml
index 79a6771a36..6168d7045a 100644
--- a/Resources/Prototypes/DeltaV/Traits/neutral.yml
+++ b/Resources/Prototypes/DeltaV/Traits/neutral.yml
@@ -1,7 +1,6 @@
- type: trait
id: ScottishAccent
- name: trait-scottish-accent-name
- description: trait-scottish-accent-desc
- traitGear: BagpipeInstrument
+ category: Speech
+ points: 0
components:
- - type: ScottishAccent
\ No newline at end of file
+ - type: ScottishAccent
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml
index 05f51baf1c..d8849472ff 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -28,7 +28,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -40,7 +40,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -52,7 +52,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -63,7 +63,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -74,7 +74,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -85,7 +85,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -96,7 +96,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -107,7 +107,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -118,7 +118,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -130,7 +130,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
@@ -141,7 +141,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Captain
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml
index e184e0a60a..c490559112 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefEngineer.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefEngineer
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefEngineer
items:
@@ -27,7 +27,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefEngineer
items:
@@ -39,7 +39,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefEngineer
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml
index 32e1a6e43e..c75c871b01 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/chiefMedicalOfficer.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefMedicalOfficer
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefMedicalOfficer
items:
@@ -27,7 +27,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefMedicalOfficer
items:
@@ -38,7 +38,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefMedicalOfficer
items:
@@ -49,7 +49,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefMedicalOfficer
items:
@@ -61,7 +61,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ChiefMedicalOfficer
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml
index f4a583e39a..3d3799c0ad 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfPersonnel.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -28,7 +28,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -40,7 +40,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -52,7 +52,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -63,7 +63,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -74,7 +74,7 @@
category: Jobs
cost: 4
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -85,7 +85,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
@@ -97,7 +97,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfPersonnel
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml
index 60c6bbdb00..4f0d785b14 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/headOfSecurity.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -28,7 +28,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -40,7 +40,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -52,7 +52,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -64,7 +64,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -76,7 +76,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -88,7 +88,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -100,7 +100,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -112,7 +112,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -123,7 +123,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -134,7 +134,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -145,7 +145,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -156,7 +156,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
@@ -168,7 +168,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- HeadOfSecurity
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml
index 802bc65de5..3359d8f5d7 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/quarterMaster.yml
@@ -5,7 +5,7 @@
# cost: 2
# exclusive: true
# requirements:
-# - !type:LoadoutJobRequirement
+# - !type:CharacterJobRequirement
# jobs:
# - Quartermaster
# items:
@@ -17,7 +17,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Quartermaster
items:
@@ -29,7 +29,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Quartermaster
items:
@@ -41,7 +41,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Quartermaster
items:
@@ -52,7 +52,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Quartermaster
items:
@@ -64,7 +64,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Quartermaster
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml
index cf24ffd852..87cb0db179 100644
--- a/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/Heads/researchDirector.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ResearchDirector
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ResearchDirector
items:
@@ -27,7 +27,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ResearchDirector
items:
@@ -38,7 +38,7 @@
category: Jobs
cost: 2
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ResearchDirector
items:
@@ -50,7 +50,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- ResearchDirector
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/cargo.yml b/Resources/Prototypes/Loadouts/Jobs/cargo.yml
index fe835d6823..8746386201 100644
--- a/Resources/Prototypes/Loadouts/Jobs/cargo.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/cargo.yml
@@ -4,10 +4,10 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- SalvageSpecialist
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobSalvageSpecialist
min: 36000 # 10 hours
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/engineering.yml b/Resources/Prototypes/Loadouts/Jobs/engineering.yml
index 820825e236..44ef2262bc 100644
--- a/Resources/Prototypes/Loadouts/Jobs/engineering.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/engineering.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
items:
@@ -16,7 +16,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
items:
@@ -28,16 +28,16 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobAtmosphericTechnician
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobStationEngineer
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Engineering
min: 216000 # 60 hours
items:
@@ -49,16 +49,16 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobAtmosphericTechnician
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobStationEngineer
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Engineering
min: 216000 # 60 hours
items:
@@ -70,7 +70,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- AtmosphericTechnician
items:
@@ -83,7 +83,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
@@ -96,7 +96,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
@@ -110,7 +110,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
@@ -123,7 +123,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
@@ -136,7 +136,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
@@ -149,7 +149,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
@@ -162,7 +162,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
diff --git a/Resources/Prototypes/Loadouts/Jobs/medical.yml b/Resources/Prototypes/Loadouts/Jobs/medical.yml
index 37af839f3c..5e88006fce 100644
--- a/Resources/Prototypes/Loadouts/Jobs/medical.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/medical.yml
@@ -4,7 +4,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
- Paramedic
@@ -18,7 +18,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
- Chemist
@@ -31,7 +31,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
- ChiefMedicalOfficer
@@ -44,7 +44,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
items:
@@ -56,7 +56,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
items:
@@ -68,7 +68,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
items:
@@ -80,7 +80,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Chemist
items:
@@ -91,7 +91,7 @@
category: Jobs
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Chemist
items:
@@ -103,7 +103,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Paramedic
items:
@@ -115,7 +115,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Paramedic
items:
@@ -127,16 +127,16 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobChemist
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobMedicalDoctor
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Medical
min: 216000 # 60 hours
items:
@@ -148,16 +148,16 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobChemist
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobMedicalDoctor
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Medical
min: 216000 # 60 hours
items:
@@ -169,7 +169,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
items:
@@ -181,16 +181,16 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- MedicalDoctor
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobChemist
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobMedicalDoctor
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Medical
min: 216000 # 60 hours
items:
diff --git a/Resources/Prototypes/Loadouts/Jobs/science.yml b/Resources/Prototypes/Loadouts/Jobs/science.yml
index f65b69c46a..ad6f02e589 100644
--- a/Resources/Prototypes/Loadouts/Jobs/science.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/science.yml
@@ -4,10 +4,10 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Scientist
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
items:
@@ -19,10 +19,10 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Scientist
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
items:
@@ -34,7 +34,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Scientist
- ResearchAssistant
@@ -48,7 +48,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Scientist
- ResearchAssistant
@@ -62,10 +62,10 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Scientist
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
items:
@@ -77,7 +77,7 @@
cost: 1
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Scientist
- ResearchAssistant
diff --git a/Resources/Prototypes/Loadouts/Jobs/security.yml b/Resources/Prototypes/Loadouts/Jobs/security.yml
index 47c4b040d3..ecf7e4893a 100644
--- a/Resources/Prototypes/Loadouts/Jobs/security.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/security.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- SecurityOfficer
- SecurityCadet
@@ -18,19 +18,19 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- SecurityOfficer
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobWarden
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobDetective
min: 7200 # 2 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobSecurityOfficer
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Security
min: 216000 # 60 hours
items:
@@ -42,19 +42,19 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- SecurityOfficer
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobWarden
min: 21600 # 6 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobDetective
min: 7200 # 2 hours
- - !type:LoadoutPlaytimeRequirement
+ - !type:CharacterPlaytimeRequirement
tracker: JobSecurityOfficer
min: 21600 # 6 hours
- - !type:LoadoutDepartmentTimeRequirement
+ - !type:CharacterDepartmentTimeRequirement
department: Security
min: 216000 # 60 hours
items:
@@ -66,7 +66,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Warden
- HeadOfSecurity
@@ -78,7 +78,7 @@
category: Jobs
cost: 1
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Detective
- SecurityOfficer
diff --git a/Resources/Prototypes/Loadouts/Jobs/service.yml b/Resources/Prototypes/Loadouts/Jobs/service.yml
index 4372f891ad..6ef3c3ad48 100644
--- a/Resources/Prototypes/Loadouts/Jobs/service.yml
+++ b/Resources/Prototypes/Loadouts/Jobs/service.yml
@@ -4,7 +4,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Clown
items:
@@ -18,7 +18,7 @@
cost: 3
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Clown
items:
@@ -32,7 +32,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Bartender
items:
@@ -44,7 +44,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Botanist
items:
@@ -56,7 +56,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -68,7 +68,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -80,7 +80,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -92,7 +92,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -104,7 +104,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -116,7 +116,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -128,7 +128,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -140,7 +140,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Lawyer
items:
@@ -152,7 +152,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Reporter
items:
@@ -164,7 +164,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Reporter
items:
@@ -176,7 +176,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Reporter
items:
diff --git a/Resources/Prototypes/Loadouts/categories.yml b/Resources/Prototypes/Loadouts/categories.yml
index b231b397f7..a4381941ac 100644
--- a/Resources/Prototypes/Loadouts/categories.yml
+++ b/Resources/Prototypes/Loadouts/categories.yml
@@ -1,4 +1,8 @@
-# Alphabetically ordered
+# Alphabetically ordered, except for Uncategorized since it is always first
+
+- type: loadoutCategory
+ id: Uncategorized
+
- type: loadoutCategory
id: Accessories
@@ -11,8 +15,5 @@
- type: loadoutCategory
id: Outer
-- type: loadoutCategory
- id: Uncategorized
-
- type: loadoutCategory
id: Uniform
diff --git a/Resources/Prototypes/Loadouts/uniform.yml b/Resources/Prototypes/Loadouts/uniform.yml
index 097577feba..5afdd303c0 100644
--- a/Resources/Prototypes/Loadouts/uniform.yml
+++ b/Resources/Prototypes/Loadouts/uniform.yml
@@ -4,7 +4,7 @@
cost: 2
exclusive: true
requirements:
- - !type:LoadoutJobRequirement
+ - !type:CharacterJobRequirement
jobs:
- Passenger
items:
diff --git a/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml b/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml
index 25aa23a5af..b7aa00353c 100644
--- a/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml
+++ b/Resources/Prototypes/SimpleStation14/Traits/disabilities.yml
@@ -1,11 +1,12 @@
- type: trait
id: Nearsighted
- name: trait-nearsighted-name
- description: You require glasses to see properly.
- traitGear: ClothingEyesGlasses
+ category: Visual
+ points: 1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: Nearsighted
- whitelist: # Delta V fix to borgs spawning with it.
- components:
- - Blindable
-
diff --git a/Resources/Prototypes/Traits/categories.yml b/Resources/Prototypes/Traits/categories.yml
new file mode 100644
index 0000000000..ba3fd7b345
--- /dev/null
+++ b/Resources/Prototypes/Traits/categories.yml
@@ -0,0 +1,19 @@
+# Alphabetically ordered, except for Uncategorized since it is always first
+
+- type: traitCategory
+ id: Uncategorized
+
+- type: traitCategory
+ id: Auditory
+
+- type: traitCategory
+ id: Mental
+
+- type: traitCategory
+ id: Physical
+
+- type: traitCategory
+ id: Speech
+
+- type: traitCategory
+ id: Visual
diff --git a/Resources/Prototypes/Traits/disabilities.yml b/Resources/Prototypes/Traits/disabilities.yml
index 2f1a7f92d2..eb96d37e01 100644
--- a/Resources/Prototypes/Traits/disabilities.yml
+++ b/Resources/Prototypes/Traits/disabilities.yml
@@ -1,18 +1,26 @@
- type: trait
id: Blindness
- name: trait-blindness-name
- description: trait-blindness-desc
- traitGear: WhiteCane
- whitelist:
- components:
- - Blindable
+ category: Visual
+ points: 2
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: PermanentBlindness
- type: trait
id: Narcolepsy
- name: trait-narcolepsy-name
- description: trait-narcolepsy-desc
+ category: Mental
+ points: 1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: Narcolepsy
timeBetweenIncidents: 300, 600
@@ -20,15 +28,15 @@
- type: trait
id: Pacifist
- name: trait-pacifist-name
- description: trait-pacifist-desc
+ category: Mental
+ points: 3
components:
- type: Pacified
- type: trait
id: Paracusia
- name: trait-paracusia-name
- description: trait-paracusia-desc
+ category: Auditory
+ points: 1
components:
- type: Paracusia
minTimeBetweenIncidents: 0.1
@@ -39,31 +47,50 @@
- type: trait
id: Muted
- name: trait-muted-name
- description: trait-muted-desc
- blacklist:
- components:
- - BorgChassis
+ category: Speech
+ points: 1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: Muted
- type: trait
id: Uncloneable
- name: trait-uncloneable-name
- description: trait-uncloneable-desc
+ category: Physical
+ points: 2
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: Uncloneable
- type: trait
id: FrontalLisp
- name: trait-frontal-lisp-name
- description: trait-frontal-lisp-desc
+ category: Speech
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: FrontalLisp
- type: trait
id: Snoring
- name: trait-snoring-name
- description: trait-snoring-desc
+ category: Auditory
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: Snoring
diff --git a/Resources/Prototypes/Traits/inconveniences.yml b/Resources/Prototypes/Traits/inconveniences.yml
index 657781d1b5..dcf53d9ab7 100644
--- a/Resources/Prototypes/Traits/inconveniences.yml
+++ b/Resources/Prototypes/Traits/inconveniences.yml
@@ -1,15 +1,25 @@
- type: trait
id: LightweightDrunk
- name: trait-lightweight-name
- description: trait-lightweight-desc
+ category: Physical
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: LightweightDrunk
boozeStrengthMultiplier: 2
- type: trait
id: SocialAnxiety
- name: trait-socialanxiety-name
- description: trait-socialanxiety-desc
+ category: Mental
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- type: StutteringAccent
matchRandomProb: 0.1
diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml
index 9e7f765507..3a3dc943cd 100644
--- a/Resources/Prototypes/Traits/neutral.yml
+++ b/Resources/Prototypes/Traits/neutral.yml
@@ -1,18 +1,23 @@
- type: trait
id: PirateAccent
- name: trait-pirate-accent-name
- description: trait-pirate-accent-desc
+ category: Speech
components:
- type: PirateAccent
- type: trait
id: Accentless
- name: trait-accentless-name
- description: trait-accentless-desc
+ category: Speech
+ points: -1
+ requirements:
+ - !type:CharacterJobRequirement
+ inverted: true
+ jobs:
+ - Borg
+ - MedicalBorg
components:
- - type: Accentless
- removes:
- - type: LizardAccent
- - type: MothAccent
- - type: ReplacementAccent
- accent: dwarf
+ - type: Accentless
+ removes:
+ - type: LizardAccent
+ - type: MothAccent
+ - type: ReplacementAccent
+ accent: dwarf