diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index 9a70d678310..38d4a411ff3 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 8b88fc1e9b0..fd84b675222 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -12,6 +12,7 @@
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 @@ public sealed partial class HumanoidProfileEditor : Control
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 @@ public sealed partial class HumanoidProfileEditor : Control
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 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_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 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
#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 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_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 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_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 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
IsDirty = false;
}
+
private void LoadoutsChanged(bool enabled)
{
_tabContainer.SetTabVisible(4, enabled);
@@ -1449,12 +1445,264 @@ private void UpdateAntagPreferences()
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 @@ private void UpdateLoadouts(bool showUnusable)
// 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 @@ out _
// 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 @@ out _
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 @@ out _
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
- Name = $"{loadout.Category}_{currentCategory}",
+ Name = $"{category.ID}_{currentCategory}",
// I hate ScrollContainers
Children =
{
@@ -1595,23 +1871,24 @@ out _
};
_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 @@ out _
}
// 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 @@ out _
}
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 @@ public bool Preference
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;
+ // Create a checkbox to get the loadout
+ _button = new Button
+ {
+ 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);
- if (trait.Description is { } desc)
+ // 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)
{
- _button.ToolTip = Loc.GetString(desc);
+ 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 @@ public bool Preference
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 @@ public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
// 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 @@ public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
// 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 3789f24cb0d..d01c7c0005c 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 fe7eb81d1e1..013ee37a416 100644
--- a/Content.Server/Flash/FlashSystem.cs
+++ b/Content.Server/Flash/FlashSystem.cs
@@ -14,13 +14,13 @@
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/Implants/ImplanterSystem.cs b/Content.Server/Implants/ImplanterSystem.cs
index 0d46241f414..3cfa3a9f5f8 100644
--- a/Content.Server/Implants/ImplanterSystem.cs
+++ b/Content.Server/Implants/ImplanterSystem.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Content.Server.Popups;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
@@ -57,6 +58,17 @@ private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent componen
return;
}
+ // Check if we are trying to implant a implant which is already implanted
+ if (implant.HasValue && !component.AllowMultipleImplants && CheckSameImplant(target, implant.Value))
+ {
+ var name = Identity.Name(target, EntityManager, args.User);
+ var msg = Loc.GetString("implanter-component-implant-already", ("implant", implant), ("target", name));
+ _popup.PopupEntity(msg, target, args.User);
+ args.Handled = true;
+ return;
+ }
+
+
//Implant self instantly, otherwise try to inject the target.
if (args.User == target)
Implant(target, target, uid, component);
@@ -67,6 +79,15 @@ private void OnImplanterAfterInteract(EntityUid uid, ImplanterComponent componen
args.Handled = true;
}
+ public bool CheckSameImplant(EntityUid target, EntityUid implant)
+ {
+ if (!TryComp(target, out var implanted))
+ return false;
+
+ var implantPrototype = Prototype(implant);
+ return implanted.ImplantContainer.ContainedEntities.Any(entity => Prototype(entity) == implantPrototype);
+ }
+
///
/// Attempt to implant someone else.
///
diff --git a/Content.Server/Speech/Components/RandomBarkComponent.cs b/Content.Server/Speech/Components/RandomBarkComponent.cs
new file mode 100644
index 00000000000..7229428f538
--- /dev/null
+++ b/Content.Server/Speech/Components/RandomBarkComponent.cs
@@ -0,0 +1,63 @@
+namespace Content.Server.Speech.Components;
+
+///
+/// Sends a random message from a list with a provided min/max time.
+///
+[RegisterComponent]
+public sealed partial class RandomBarkComponent : Component
+{
+ ///
+ /// Should the message be sent to the chat log?
+ ///
+ [DataField]
+ public bool ChatLog = false;
+
+ ///
+ /// Minimum time an animal will go without speaking
+ ///
+ [DataField]
+ public int MinTime = 45;
+
+ ///
+ /// Maximum time an animal will go without speaking
+ ///
+ [DataField]
+ public int MaxTime = 350;
+
+ ///
+ /// Accumulator for counting time since the last bark
+ ///
+ [DataField]
+ public float BarkAccumulator = 8f;
+
+ ///
+ /// Multiplier applied to the random time. Good for changing the frequency without having to specify exact values
+ ///
+ [DataField]
+ public float BarkMultiplier = 1f;
+
+ ///
+ /// List of things to be said. Filled with garbage to be modified by an accent, but can be specified in the .yml
+ ///
+ [DataField]
+ public IReadOnlyList Barks = new[]
+ {
+ "Bark",
+ "Boof",
+ "Woofums",
+ "Rawrl",
+ "Eeeeeee",
+ "Barkums",
+ "Awooooooooooooooooooo awoo awoooo",
+ "Grrrrrrrrrrrrrrrrrr",
+ "Rarrwrarrwr",
+ "Goddamn I love gold fish crackers",
+ "Bork bork boof boof bork bork boof boof boof bork",
+ "Bark",
+ "Boof",
+ "Woofums",
+ "Rawrl",
+ "Eeeeeee",
+ "Barkums",
+ };
+}
diff --git a/Content.Server/Speech/Systems/RandomBarkSystem.cs b/Content.Server/Speech/Systems/RandomBarkSystem.cs
new file mode 100644
index 00000000000..4fc9dd420d2
--- /dev/null
+++ b/Content.Server/Speech/Systems/RandomBarkSystem.cs
@@ -0,0 +1,47 @@
+using Content.Server.Chat.Systems;
+using Content.Shared.Mind.Components;
+using Robust.Shared.Random;
+using Content.Server.Speech.Components;
+
+namespace Content.Server.Speech.Systems;
+
+public sealed class RandomBarkSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly EntityManager _entity = default!;
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInit);
+ }
+
+
+ private void OnInit(EntityUid uid, RandomBarkComponent barker, ComponentInit args)
+ {
+ barker.BarkAccumulator = _random.NextFloat(barker.MinTime, barker.MaxTime) * barker.BarkMultiplier;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var barker))
+ {
+ barker.BarkAccumulator -= frameTime;
+ if (barker.BarkAccumulator > 0)
+ continue;
+
+ barker.BarkAccumulator = _random.NextFloat(barker.MinTime, barker.MaxTime) * barker.BarkMultiplier;
+ if (_entity.TryGetComponent(uid, out var actComp) &&
+ actComp.HasMind)
+ continue;
+
+ _chat.TrySendInGameICMessage(uid, _random.Pick(barker.Barks), InGameICChatType.Speak, barker.ChatLog ? ChatTransmitRange.Normal : ChatTransmitRange.HideChat);
+ }
+ }
+}
diff --git a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs
index 722a489541f..4fc158f8646 100644
--- a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs
+++ b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs
@@ -2,7 +2,7 @@
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 4b0205ff536..cf08e09e90e 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 22ee0e4861f..be2c3c05039 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 @@ private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args)
{
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 @@ private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args)
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/Weapons/Ranged/Systems/FireOnDropSystem.cs b/Content.Server/Weapons/Ranged/Systems/FireOnDropSystem.cs
new file mode 100644
index 00000000000..a6112ad49cf
--- /dev/null
+++ b/Content.Server/Weapons/Ranged/Systems/FireOnDropSystem.cs
@@ -0,0 +1,27 @@
+using Content.Shared.Throwing;
+using Content.Shared.Weapons.Ranged.Components;
+using Content.Shared.Weapons.Ranged.Systems;
+using Robust.Shared.Random;
+
+namespace Content.Server.Weapons.Ranged.Systems;
+
+public sealed class FireOnDropSystem : EntitySystem
+{
+ [Dependency] private readonly SharedGunSystem _gun = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(HandleLand);
+ }
+
+
+ private void HandleLand(EntityUid uid, GunComponent component, ref ThrowDoHitEvent args)
+ {
+ if (_random.Prob(component.FireOnDropChance))
+ _gun.AttemptShoot(uid, uid, component, Transform(uid).Coordinates.Offset(Transform(uid).LocalRotation.ToVec()));
+ }
+}
diff --git a/Content.Server/Zombies/ZombieSystem.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs
index daadd4b518b..996d7772239 100644
--- a/Content.Server/Zombies/ZombieSystem.Transform.cs
+++ b/Content.Server/Zombies/ZombieSystem.Transform.cs
@@ -35,8 +35,8 @@
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 5aaf967dd1a..ab36977efbd 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -341,14 +341,34 @@ public static readonly CVarDef
public static readonly CVarDef DebugCoordinatesAdminOnly =
CVarDef.Create("game.debug_coordinates_admin_only", true, CVar.SERVER | CVar.REPLICATED);
+
+ ///
+ /// 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 or not to allow characters to select loadout items.
+ /// 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 5dd880d3f1b..445cbc10e6d 100644
--- a/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs
+++ b/Content.Shared/Clothing/Loadouts/Prototypes/LoadoutCategoryPrototype.cs
@@ -2,6 +2,7 @@
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 bc31fc15701..9ca575fa72e 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 b76a31e422b..00000000000
--- 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 eb9a5dcbc8c..09e3db3793f 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 List ApplyCharacterLoadout(EntityUid uid, JobPrototype job, Hu
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 List ApplyCharacterLoadout(EntityUid uid, JobPrototype job, Hu
// 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 00000000000..b7200c60e85
--- /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 00000000000..e93c933a6aa
--- /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 4f9429b6a6b..161d4729ede 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/Implants/Components/ImplanterComponent.cs b/Content.Shared/Implants/Components/ImplanterComponent.cs
index 32a36361633..80330aa7e66 100644
--- a/Content.Shared/Implants/Components/ImplanterComponent.cs
+++ b/Content.Shared/Implants/Components/ImplanterComponent.cs
@@ -70,6 +70,12 @@ public sealed partial class ImplanterComponent : Component
[DataField]
public (string, string) ImplantData;
+ ///
+ /// Determines if the same type of implant can be implanted into an entity multiple times.
+ ///
+ [DataField]
+ public bool AllowMultipleImplants = false;
+
///
/// The for this implanter
///
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index ece9a82bb24..8cf504582d4 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -393,19 +393,19 @@ public HumanoidCharacterProfile WithLoadoutPreference(string loadoutId, bool pre
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 @@ public void EnsureValid(IConfigurationManager configManager, IPrototypeManager p
.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 96ebf4d83f6..084a1e1d925 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 59f9ca758bc..d0639e59339 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 5d353ac9637..62d2f5899a4 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 1db698359bd..ff62e55c2aa 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 76ff3e1005e..c1bf7e1639e 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 7c91366937c..00000000000
--- 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 2242bc6e52b..f4e077bc1af 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 override void Initialize()
{
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 00000000000..8ae0f251b86
--- /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 9fd5db84972..113939f66b7 100644
--- a/Content.Shared/Traits/Assorted/PermanentBlindnessSystem.cs
+++ b/Content.Shared/Traits/Assorted/Systems/PermanentBlindnessSystem.cs
@@ -4,7 +4,7 @@
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 @@ private void OnStartup(Entity blindness, ref Compon
_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 2bfb0da1f54..151e748445f 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 00000000000..efbac1ca7d0
--- /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 00000000000..2e6b7cc0666
--- /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 34feb8da22c..00000000000
--- 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/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs
index 2e921c4fc0a..ada99801f01 100644
--- a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs
+++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs
@@ -235,6 +235,12 @@ public sealed partial class GunComponent : Component
///
[DataField]
public bool ClumsyProof = false;
+
+ ///
+ /// The percentage chance of a given gun to accidentally discharge if violently thrown into a wall or person
+ ///
+ [DataField]
+ public float FireOnDropChance = 0.1f;
}
[Flags]
diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml
index 9cf28c91be0..3e7799a745a 100644
--- a/Resources/Changelog/Changelog.yml
+++ b/Resources/Changelog/Changelog.yml
@@ -4198,3 +4198,30 @@ Entries:
message: Peacock Tails have been added for Harpies
id: 6124
time: '2024-06-20T22:07:29.0000000+00:00'
+- author: VMSolidus
+ changes:
+ - type: Add
+ message: >-
+ NanoTrasen has disabled the unneeded safeties on your guns- Make sure
+ you're careful with them!
+ - type: Tweak
+ message: >-
+ All Firearms now have a reliability stat, some are more reliable than
+ others. The more reliable a weapon is, the less likely it is to
+ accidentally discharge when yeeted.
+ id: 6125
+ time: '2024-06-20T22:17:03.0000000+00:00'
+- author: DEATHB4DEFEAT
+ changes:
+ - type: Add
+ message: Animals will no longer be a silent, soulless shell
+ id: 6126
+ time: '2024-06-20T22:24:30.0000000+00:00'
+- author: DEATHB4DEFEAT
+ changes:
+ - type: Add
+ message: Added trait points
+ - type: Add
+ message: Added categories for traits
+ id: 6127
+ time: '2024-06-20T23:39:31.0000000+00:00'
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 00000000000..b073bdb773f
--- /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 e00cec47077..914a5c9f1bc 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/implant/implant.ftl b/Resources/Locale/en-US/implant/implant.ftl
index 22db4460aff..2f6ab9e4e2f 100644
--- a/Resources/Locale/en-US/implant/implant.ftl
+++ b/Resources/Locale/en-US/implant/implant.ftl
@@ -4,6 +4,7 @@ implanter-component-implanting-target = {$user} is trying to implant you with so
implanter-component-implant-failed = The {$implant} cannot be given to {$target}!
implanter-draw-failed-permanent = The {$implant} in {$target} is fused with them and cannot be removed!
implanter-draw-failed = You tried to remove an implant but found nothing.
+implanter-component-implant-already = {$target} already has the {$implant}!
## UI
implanter-draw-text = Draw
diff --git a/Resources/Locale/en-US/loadouts/categories.ftl b/Resources/Locale/en-US/loadouts/categories.ftl
index bdfa215e6a8..685c5cbcbd9 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 5a453ed5cbf..00000000000
--- 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 b929da96554..9b8eb74d969 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 3d8af061392..8360aaeb9df 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 00000000000..56f0adeb479
--- /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 7a3564edf66..e9163bdb548 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 c361d1b51d8..d1980bc23ad 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 79a6771a362..6168d7045a9 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/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
index 369544fdc1b..e311681ce5f 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
@@ -64,6 +64,7 @@
- type: Tag
tags:
- VimPilot
+ - type: RandomBark
- type: entity
name: bee
@@ -132,6 +133,13 @@
- ReagentId: GroundBee
Quantity: 5
- type: ZombieImmune
+ - type: RandomBark
+ barks:
+ - Bzzzzz
+ - Bzzz bzzz
+ - Bzzzzzzzzzzzz
+ - Bzz
+ barkMultiplier: 1.5
- type: entity
name: bee
@@ -242,6 +250,7 @@
- type: NpcFactionMember
factions:
- Passive
+ - type: RandomBark
- type: entity
parent: MobChicken
@@ -620,6 +629,8 @@
- type: NpcFactionMember
factions:
- Passive
+ - type: RandomBark
+ barkMultiplier: 0.7
- type: entity
name: white duck #Quack
@@ -799,6 +810,16 @@
- type: GuideHelp
guides:
- Chef
+ - type: RandomBark
+ barks:
+ - Mooooooo
+ - Moo
+ - Huff
+ - Mooooooooooo
+ - Moooooo
+ - Moooo
+ barkMultiplier: 3
+
- type: entity
name: crab
@@ -867,6 +888,12 @@
task: RuminantCompound
- type: Body
prototype: AnimalHemocyanin
+ - type: RandomBark
+ barks:
+ - click clack
+ - clack
+ - clickity clack
+ - clack clack
- type: entity
name: goat
@@ -1479,6 +1506,8 @@
makeSentient: true
name: ghost-role-information-kobold-name
description: ghost-role-information-kobold-description
+ - type: RandomBark
+ barkMultiplier: 0.65
- type: entity
name: guidebook monkey
@@ -1641,6 +1670,8 @@
- type: BadFood
- type: NonSpreaderZombie
- type: PreventSpiller
+ - type: RandomBark
+ barkMultiplier: 0.3
- type: entity
parent: MobMouse
@@ -1876,6 +1907,18 @@
- type: Tag
tags:
- VimPilot
+ - type: RandomBark
+ barkMultiplier: 1.3
+ barks:
+ - Croooaaaakkk
+ - Ribbit
+ - Ribbit
+ - Ribbit
+ - Crooaak
+ - Ribbit
+ - Ribbit
+ - Ribbit
+ - Bibbit
# Would be cool to have some functionality for the parrot to be able to sit on stuff
- type: entity
@@ -1994,6 +2037,10 @@
heatDamage:
types:
Heat : 0.2 #per second, scales with temperature & other constants
+ - type: RandomBark
+ barks:
+ - Wank
+ barkMultiplier: 0.6
- type: entity
name: grenade penguin
@@ -2109,6 +2156,14 @@
- type: Damageable
damageContainer: Biological
damageModifierSet: Scale
+ - type: RandomBark
+ barkMultiplier: 1.5
+ barks:
+ - Hsssssss
+ - Hss
+ - Hsssss
+ - Hisss
+ - Hshsss
# Code unique spider prototypes or combine them all into one spider and get a
# random sprite state when you spawn it.
@@ -2236,6 +2291,7 @@
groups:
Brute: -0.07
Burn: -0.07
+ - type: RandomBark
- type: entity
name: tarantula
@@ -2588,6 +2644,7 @@
- type: Tag
tags:
- VimPilot
+ - type: RandomBark
- type: entity
name: corrupted corgi
@@ -2747,6 +2804,7 @@
- type: Tag
tags:
- VimPilot
+ - type: RandomBark
- type: entity
name: calico cat
@@ -2941,6 +2999,10 @@
- type: Tag
tags:
- VimPilot
+ - type: RandomBark
+ barkMultiplier: 10
+ barks:
+ - Sloth
- type: entity
name: ferret
@@ -3140,6 +3202,8 @@
- type: MobPrice
price: 60
- type: NonSpreaderZombie
+ - type: RandomBark
+ barkMultiplier: 0.45
- type: entity
name: pig
@@ -3309,3 +3373,4 @@
components:
- type: ReplacementAccent
accent: nymph
+ - type: RandomBark
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
index 8ca1b2d2f0e..5141811a8ea 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
@@ -329,6 +329,8 @@
- VimPilot
- type: StealTarget
stealGroup: AnimalMcGriff
+ - type: RandomBark
+ barkMultiplier: 1.3
- type: entity
name: Paperwork
@@ -432,6 +434,8 @@
- VimPilot
- type: StealTarget
stealGroup: AnimalWalter
+ - type: RandomBark
+ barkMultiplier: 1.1
- type: entity
name: Morty
diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
index 12ea40b1e3c..8e84f46a693 100644
--- a/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
+++ b/Resources/Prototypes/Entities/Mobs/NPCs/simplemob.yml
@@ -104,3 +104,4 @@
- type: MobPrice
price: 150
- type: FloatingVisuals
+ - type: Speech
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_pka.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_pka.yml
index f85e93b893f..93621bc3a28 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_pka.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/base_pka.yml
@@ -22,6 +22,7 @@
- SemiAuto
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/kinetic_accel.ogg
+ fireOnDropChance: 1
- type: AmmoCounter
- type: Appearance
- type: GenericVisualizer
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
index 202604b8bf0..bf0c51849a3 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
@@ -18,6 +18,7 @@
selectedMode: SemiAuto
availableModes:
- SemiAuto
+ fireOnDropChance: 0.15
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/laser.ogg
- type: Battery
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml
index 16cacb79dfa..410664e46e4 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Pistols/pistols.yml
@@ -99,6 +99,15 @@
containers:
gun_magazine: !type:ContainerSlot
gun_chamber: !type:ContainerSlot
+ - type: Gun
+ fireRate: 6
+ selectedMode: SemiAuto
+ availableModes:
+ - SemiAuto
+ - FullAuto
+ soundGunshot:
+ path: /Audio/Weapons/Guns/Gunshots/pistol.ogg
+ fireOnDropChance: 0.3
- type: entity
name: cobra
@@ -125,6 +134,7 @@
volume: -14
availableModes:
- SemiAuto
+ fireOnDropChance: 0.1
- type: ItemSlots
slots:
gun_magazine:
@@ -169,6 +179,7 @@
- SemiAuto
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/mk58.ogg
+ fireOnDropChance: 0.5
- type: entity
id: WeaponPistolMk58Nonlethal
@@ -198,7 +209,7 @@
name: N1984
parent: BaseWeaponPistol
id: WeaponPistolN1984 # the spaces in description are for formatting.
- description: The sidearm of any self respecting officer. Comes in .45 magnum, the lord's caliber.
+ description: The sidearm of any self respecting officer. Comes in .45 magnum, the lord's caliber.
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Pistols/N1984.rsi
@@ -219,6 +230,7 @@
- SemiAuto
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/mk58.ogg
+ fireOnDropChance: 0.6
- type: ItemSlots
slots:
gun_magazine:
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml
index bd043c997da..c5237cdad9a 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Revolvers/revolvers.yml
@@ -29,6 +29,7 @@
- SemiAuto
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/revolver.ogg
+ fireOnDropChance: 0.5
- type: UseDelay
delay: 0.66
- type: ContainerContainer
@@ -127,6 +128,7 @@
path: /Audio/Weapons/Guns/Gunshots/revolver.ogg
params:
volume: 2.25
+ fireOnDropChance: 0.3
- type: entity
name: Python
@@ -155,6 +157,7 @@
sprite: Objects/Weapons/Guns/Revolvers/pirate_revolver.rsi
- type: Gun
fireRate: 1
+ fireOnDropChance: 1
- type: ContainerContainer
containers:
revolver-ammo: !type:Container
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml
index 5bc8125ebaa..c55b2b6b091 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Rifles/rifles.yml
@@ -67,6 +67,7 @@
fireRate: 5
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/rifle2.ogg
+ fireOnDropChance: 0.5
- type: ChamberMagazineAmmoProvider
soundRack:
path: /Audio/Weapons/Guns/Cock/ltrifle_cock.ogg
@@ -159,6 +160,7 @@
- type: Gun
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/ltrifle.ogg
+ fireOnDropChance: 0.2
- type: ItemSlots
slots:
gun_magazine:
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml
index b693bdba370..2dfc833badf 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/SMGs/smgs.yml
@@ -72,6 +72,7 @@
fireRate: 10
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/atreides.ogg
+ fireOnDropChance: 0.3
- type: MagazineVisuals
magState: mag
steps: 1
@@ -96,6 +97,7 @@
- type: Gun
soundGunshot:
path: /Audio/Weapons/Guns/Gunshots/c-20r.ogg
+ fireOnDropChance: 0.3
- type: ChamberMagazineAmmoProvider
autoEject: true
- type: MagazineVisuals
@@ -126,6 +128,7 @@
path: /Audio/Weapons/Guns/Gunshots/atreides.ogg
availableModes:
- FullAuto
+ fireOnDropChance: 0.2
- type: ItemSlots
slots:
gun_magazine:
@@ -173,6 +176,7 @@
path: /Audio/Weapons/Guns/Gunshots/atreides.ogg
availableModes:
- FullAuto
+ fireOnDropChance: 0.1
- type: ItemSlots
slots:
gun_magazine:
@@ -225,6 +229,7 @@
selectedMode: FullAuto
availableModes:
- FullAuto
+ fireOnDropChance: 0.1
- type: ItemSlots
slots:
gun_magazine:
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
index a8d9f539917..52b05b6d60b 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
@@ -28,6 +28,7 @@
path: /Audio/Weapons/Guns/Gunshots/shotgun.ogg
soundEmpty:
path: /Audio/Weapons/Guns/Empty/empty.ogg
+ fireOnDropChance: 0.2
- type: BallisticAmmoProvider
whitelist:
tags:
@@ -75,6 +76,7 @@
path: /Audio/Weapons/Guns/Gunshots/shotgun.ogg
soundEmpty:
path: /Audio/Weapons/Guns/Empty/empty.ogg
+ fireOnDropChance: 0.3
- type: ItemSlots
slots:
gun_magazine:
@@ -115,6 +117,7 @@
heldPrefix: db
- type: Gun
fireRate: 2
+ fireOnDropChance: 0.5
- type: BallisticAmmoProvider
capacity: 2
- type: Construction
@@ -190,6 +193,7 @@
heldPrefix: sawn
- type: Gun
fireRate: 4
+ fireOnDropChance: 0.5
- type: BallisticAmmoProvider
capacity: 2
- type: Construction
@@ -226,6 +230,7 @@
sprite: Objects/Weapons/Guns/Shotguns/hm_pistol.rsi
- type: Gun
fireRate: 4
+ fireOnDropChance: 1
- type: BallisticAmmoProvider
capacity: 1
- type: Construction
@@ -250,6 +255,7 @@
sprite: Objects/Weapons/Guns/Shotguns/blunderbuss.rsi
- type: Gun
fireRate: 2
+ fireOnDropChance: 1
- type: BallisticAmmoProvider
capacity: 1
- type: StaticPrice
@@ -273,6 +279,7 @@
heldPrefix: improvised
- type: Gun
fireRate: 4 #No reason to stifle the firerate since you have to manually reload every time anyways.
+ fireOnDropChance: 1
- type: BallisticAmmoProvider
capacity: 1
proto: null
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml
index c97459629cf..adb8e323f4a 100644
--- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Snipers/snipers.yml
@@ -46,6 +46,14 @@
components:
- type: Sprite
sprite: Objects/Weapons/Guns/Snipers/bolt_gun_wood.rsi
+ - type: Gun
+ fireRate: 0.75
+ selectedMode: SemiAuto
+ availableModes:
+ - SemiAuto
+ soundGunshot:
+ path: /Audio/Weapons/Guns/Gunshots/sniper.ogg
+ fireOnDropChance: 1
- type: entity
name: Hristov
@@ -82,6 +90,7 @@
selectedMode: SemiAuto
availableModes:
- SemiAuto
+ fireOnDropChance: 1
- type: UseDelayOnShoot
- type: UseDelay
delay: 8 #it's a musket
@@ -111,6 +120,7 @@
- type: Gun
minAngle: 0
maxAngle: 30 #miss him entirely because the barrel is smoothbore
+ fireOnDropChance: 1
- type: Item
size: Small
storedRotation: 90
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index 6812ca14f70..00ad5f54ea9 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -418,9 +418,9 @@
noSpawn: true
components:
- type: StationEvent
- earliestStart: 60 # DeltaV - was 45
- weight: 3 # DeltaV - was 5
- minimumPlayers: 10
+ earliestStart: 60
+ weight: 3
+ minimumPlayers: 20
reoccurrenceDelay: 30
duration: 1
- type: LoneOpsSpawnRule
diff --git a/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml b/Resources/Prototypes/Loadouts/Jobs/Heads/captain.yml
index 05f51baf1c5..d8849472ff4 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 e184e0a60a6..c4905591124 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 32e1a6e43eb..c75c871b011 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 f4a583e39a4..3d3799c0adf 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 60c6bbdb00c..4f0d785b14d 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 802bc65de59..3359d8f5d74 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 cf24ffd8525..87cb0db1790 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 fe835d6823c..87463862010 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 820825e236e..44ef2262bc5 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 37af839f3cc..5e88006fcee 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 f65b69c46a0..ad6f02e589e 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 47c4b040d3d..ecf7e4893a2 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 4372f891ad7..6ef3c3ad485 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 b231b397f71..a4381941acc 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 097577febab..5afdd303c0b 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 25aa23a5afc..b7aa00353c8 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 00000000000..ba3fd7b3456
--- /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 2f1a7f92d26..eb96d37e01a 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 657781d1b59..dcf53d9ab7f 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 9e7f7655076..3a3dc943cd7 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