Loadouts (#45)

Description copied from my Wizard's Den PR
Resolves https://github.com/Simple-Station/Einstein-Engines/issues/32

# TODO

- [x] CVars
- [x] Server side loadout point validation
- Make tabContainer less bad
- [x] Move head clothes to loadouts from lockers
- [x] Move job starting equipment to loadouts
- [x] Loadout item preview
- [x] Fix loadouts duplicating on server restart
- [x] Make sure everything is localized -- Components and Tags are an
odd thing to display to the player in the whitelist and are very
unrealistic to make localizations for, so.. not doing that, what do I
do?
- [x] Fix all items going into the bag no matter their size
- [x] Add min/cur/max points to the bar
- [x] Use buttons instead of checkboxes
- [x] "Show Unusable" button to hide things your character currently
doesn't match the whitelists for
- [x] Time whitelist option
- [x] Species whitelist option
- [x] Trait whitelist option instead of EntityWhitelist for the sake of
localization
- [ ] More loadouts (filler things while waiting for review)
- [ ] - Golden or themed items for Command roles with an undecided
amount of playtime on their respective jobs
- [x] - Goliath cloak for playing a lot of Salvage
- [x] - Senior items for playing a lot of its respective role
- [ ] - Varying materials of pocket watches for people with varying
overall playtime
- [x] Fix loadout selectors not updating to match current preferences

<details><summary><h1>Media (Click Me!)</h1></summary>
<p>

I need to rerecord these


https://github.com/space-wizards/space-station-14/assets/77995199/59713874-b043-4813-848e-56b2951b6935


https://github.com/space-wizards/space-station-14/assets/77995199/40180aee-bfe3-4f30-9df8-0f628e7e4514

</p>
</details> 

# Changelog

🆑
- add: Added a new character customization tab for selecting items to
start your shift with, loadouts!
- remove: Removed some default job equipment in favor of loadouts
- remove: Removed several clothing items from Command lockers in favor
of loadouts

---------

Co-authored-by: VMSolidus <evilexecutive@gmail.com>
This commit is contained in:
DEATHB4DEFEAT
2024-05-12 21:46:57 -07:00
committed by GitHub
parent 255e307fe9
commit 528557383a
76 changed files with 7105 additions and 150 deletions

View File

@@ -1,16 +1,14 @@
using System.Linq;
using System.Numerics;
using Content.Client.Alerts;
using Content.Client.Humanoid;
using Content.Client.Inventory;
using Content.Client.Preferences;
using Content.Client.UserInterface.Controls;
using Content.Shared.Clothing.Loadouts.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Map;
@@ -126,6 +124,7 @@ namespace Content.Client.Lobby.UI
_summaryLabel.Text = selectedCharacter.Summary;
_entityManager.System<HumanoidAppearanceSystem>().LoadProfile(_previewDummy.Value, selectedCharacter);
GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
GiveDummyLoadoutItems(_previewDummy.Value, selectedCharacter);
}
}
}
@@ -162,5 +161,13 @@ namespace Content.Client.Lobby.UI
}
}
}
public static void GiveDummyLoadoutItems(EntityUid dummy, HumanoidCharacterProfile profile)
{
var highPriorityJobId = profile.JobPriorities.FirstOrDefault(j => j.Value == JobPriority.High).Key;
var highPriorityJob = IoCManager.Resolve<IPrototypeManager>().Index<JobPrototype>(highPriorityJobId ?? SharedGameTicker.FallbackOverflowJob);
EntitySystem.Get<LoadoutSystem>().ApplyCharacterLoadout(dummy, highPriorityJob, profile);
}
}
}

View File

@@ -21,7 +21,7 @@ public sealed partial class JobRequirementsManager
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
private readonly Dictionary<string, TimeSpan> _roles = new();
public readonly Dictionary<string, TimeSpan> PlayTimes = new();
private readonly List<string> _roleBans = new();
private ISawmill _sawmill = default!;
@@ -45,7 +45,7 @@ public sealed partial class JobRequirementsManager
if (e.NewLevel == ClientRunLevel.Initialize)
{
// Reset on disconnect, just in case.
_roles.Clear();
PlayTimes.Clear();
}
}
@@ -63,12 +63,12 @@ public sealed partial class JobRequirementsManager
private void RxPlayTime(MsgPlayTime message)
{
_roles.Clear();
PlayTimes.Clear();
// NOTE: do not assign _roles = message.Trackers due to implicit data sharing in integration tests.
foreach (var (tracker, time) in message.Trackers)
{
_roles[tracker] = time;
PlayTimes[tracker] = time;
}
/*var sawmill = Logger.GetSawmill("play_time");
@@ -96,7 +96,7 @@ public sealed partial class JobRequirementsManager
return CheckRoleTime(job.Requirements, out reason);
}
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason)
public bool CheckRoleTime(HashSet<JobRequirement>? requirements, [NotNullWhen(false)] out FormattedMessage? reason, string? localePrefix = null)
{
reason = null;
@@ -106,7 +106,7 @@ public sealed partial class JobRequirementsManager
var reasons = new List<string>();
foreach (var requirement in requirements)
{
if (JobRequirements.TryRequirementMet(requirement, _roles, out var jobReason, _entManager, _prototypes, _whitelisted))
if (JobRequirements.TryRequirementMet(requirement, PlayTimes, out var jobReason, _entManager, _prototypes, _whitelisted, localePrefix))
continue;
reasons.Add(jobReason.ToMarkup());
@@ -118,7 +118,7 @@ public sealed partial class JobRequirementsManager
public TimeSpan FetchOverallPlaytime()
{
return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
return PlayTimes.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
}
public IEnumerable<KeyValuePair<string, TimeSpan>> FetchPlaytimeByRoles()
@@ -127,7 +127,7 @@ public sealed partial class JobRequirementsManager
foreach (var job in jobsToMap)
{
if (_roles.TryGetValue(job.PlayTimeTracker, out var locJobName))
if (PlayTimes.TryGetValue(job.PlayTimeTracker, out var locJobName))
{
yield return new KeyValuePair<string, TimeSpan>(job.Name, locJobName);
}

View File

@@ -181,6 +181,7 @@ namespace Content.Client.Preferences.UI
if (humanoid != null)
{
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
LobbyCharacterPreviewPanel.GiveDummyLoadoutItems(_previewDummy, humanoid);
}
var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
@@ -229,10 +230,8 @@ namespace Content.Client.Preferences.UI
};
deleteButton.OnPressed += _ =>
{
deleteButton.Visible = false;
confirmDeleteButton.Visible = true;
};
var internalHBox = new BoxContainer

View File

@@ -85,6 +85,12 @@
<Control HorizontalExpand="True"/>
<Button Name="ShowClothes" Pressed="True" ToggleMode="True" Text="{Loc 'humanoid-profile-editor-clothing-show'}" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Show loadouts -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-loadouts'}" />
<Control HorizontalExpand="True"/>
<Button Name="ShowLoadouts" Pressed="True" ToggleMode="True" Text="{Loc 'Show'}" HorizontalAlignment="Right" />
</BoxContainer>
<!-- Clothing -->
<BoxContainer HorizontalExpand="True">
<Label Text="{Loc 'humanoid-profile-editor-clothing-label'}" />
@@ -142,6 +148,15 @@
<BoxContainer Name="CTraitsList" Orientation="Vertical" />
</ScrollContainer>
</BoxContainer>
<BoxContainer Name="CLoadoutsTab" Orientation="Vertical" Margin="10">
<!-- Loadouts -->
<Label Name="LoadoutPointsLabel" HorizontalAlignment="Stretch" Align="Center" />
<ProgressBar Name="LoadoutPointsBar" MaxValue="1" Value="1" MaxHeight="8" Margin="0 5" />
<Button Name="CHideShowUnusableButton" Text="{Loc 'humanoid-profile-editor-loadouts-show-unusable-button'}" ToolTip="{Loc 'humanoid-profile-editor-loadouts-show-unusable-button-tooltip'}" ToggleMode="True" Margin="0 0 0 5" />
<TabContainer Name="CLoadoutsTabs" VerticalExpand="True" />
</BoxContainer>
<BoxContainer Name="CMarkingsTab" Orientation="Vertical" Margin="10">
<!-- Markings -->
<ScrollContainer VerticalExpand="True">

View File

@@ -1,5 +1,6 @@
using System.Linq;
using System.Numerics;
using System.Text;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
using Content.Client.Lobby.UI;
@@ -9,17 +10,17 @@ using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.Clothing.Loadouts.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
using Content.Shared.Traits;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -30,7 +31,6 @@ using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -61,6 +61,7 @@ namespace Content.Client.Preferences.UI
private readonly IConfigurationManager _configurationManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
private readonly LoadoutSystem _loadoutSystem;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
@@ -83,6 +84,11 @@ namespace Content.Client.Preferences.UI
private BoxContainer _jobList => CJobList;
private BoxContainer _antagList => CAntagList;
private BoxContainer _traitsList => CTraitsList;
private Label _loadoutPointsLabel => LoadoutPointsLabel;
private ProgressBar _loadoutPointsBar => LoadoutPointsBar;
private Button _loadoutsShowUnusableButton => CHideShowUnusableButton;
private BoxContainer _loadoutsTab => CLoadoutsTab;
private TabContainer _loadoutsTabs => CLoadoutsTabs;
private readonly List<JobPrioritySelector> _jobPriorities;
private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
private readonly Dictionary<string, BoxContainer> _jobCategories;
@@ -90,6 +96,7 @@ namespace Content.Client.Preferences.UI
private readonly List<SpeciesPrototype> _speciesList;
private readonly List<AntagPreferenceSelector> _antagPreferences;
private readonly List<TraitPreferenceSelector> _traitPreferences;
private readonly List<LoadoutPreferenceSelector> _loadoutPreferences;
private SpriteView _previewSpriteView => CSpriteView;
private Button _previewRotateLeftButton => CSpriteRotateLeft;
@@ -117,6 +124,7 @@ namespace Content.Client.Preferences.UI
_preferencesManager = preferencesManager;
_configurationManager = configurationManager;
_markingManager = IoCManager.Resolve<MarkingManager>();
_loadoutSystem = EntitySystem.Get<LoadoutSystem>();
SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
@@ -140,6 +148,7 @@ namespace Content.Client.Preferences.UI
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
ShowClothes.OnPressed += ToggleClothes;
ShowLoadouts.OnPressed += ToggleClothes;
#region Sex
@@ -465,6 +474,22 @@ namespace Content.Client.Preferences.UI
#endregion
#region Loadouts
// Set up the loadouts tab
_tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-loadouts-tab"));
_loadoutPreferences = new List<LoadoutPreferenceSelector>();
// Show/Hide 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));
_loadoutsShowUnusableButton.OnToggled += args => UpdateLoadouts(args.Pressed);
#endregion
#region Save
_saveButton.OnPressed += _ => { Save(); };
@@ -472,7 +497,7 @@ namespace Content.Client.Preferences.UI
#endregion Save
#region Markings
_tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-markings-tab"));
_tabContainer.SetTabTitle(5, Loc.GetString("humanoid-profile-editor-markings-tab"));
CMarkings.OnMarkingAdded += OnMarkingChange;
CMarkings.OnMarkingRemoved += OnMarkingChange;
@@ -516,6 +541,9 @@ namespace Content.Client.Preferences.UI
_previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
_previewSpriteView.SetEntity(_previewDummy);
UpdateLoadouts(false); // Initial UpdateLoadouts call has to have a dummy to get information from
#endregion Dummy
#endregion Left
@@ -534,6 +562,12 @@ namespace Content.Client.Preferences.UI
IsDirty = false;
}
private void LoadoutsChanged(bool enabled)
{
_tabContainer.SetTabVisible(4, enabled);
ShowLoadouts.Visible = enabled;
}
private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
@@ -781,6 +815,8 @@ namespace Content.Client.Preferences.UI
_requirements.Updated -= UpdateRoleRequirements;
_preferencesManager.OnServerDataLoaded -= LoadServerData;
_configurationManager.UnsubValueChanged(CCVars.GameLoadoutsEnabled, enabled => LoadoutsChanged(enabled));
}
private void RebuildSpriteView()
@@ -1203,6 +1239,8 @@ namespace Content.Client.Preferences.UI
if (ShowClothes.Pressed)
LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
if (ShowLoadouts.Pressed)
LobbyCharacterPreviewPanel.GiveDummyLoadoutItems(_previewDummy!.Value, Profile);
_previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
}
@@ -1225,6 +1263,8 @@ namespace Content.Client.Preferences.UI
UpdateJobPriorities();
UpdateAntagPreferences();
UpdateTraitPreferences();
UpdateLoadouts(_loadoutsShowUnusableButton.Pressed);
UpdateLoadoutPreferences();
UpdateMarkings();
RebuildSpriteView();
UpdateHairPickers();
@@ -1418,6 +1458,253 @@ namespace Content.Client.Preferences.UI
}
}
private void UpdateLoadoutPreferences()
{
var points = _configurationManager.GetCVar(CCVars.GameLoadoutsPoints);
_loadoutPointsBar.Value = points;
_loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", points));
foreach (var preferenceSelector in _loadoutPreferences)
{
var loadoutId = preferenceSelector.Loadout.ID;
var preference = Profile?.LoadoutPreferences.Contains(loadoutId) ?? false;
preferenceSelector.Preference = preference;
if (preference)
{
points -= preferenceSelector.Loadout.Cost;
_loadoutPointsBar.Value = points;
_loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", _loadoutPointsBar.MaxValue));
}
}
}
private void UpdateLoadouts(bool showUnusable)
{
// Reset loadout points so you don't get -14 points or something for no reason
var points = _configurationManager.GetCVar(CCVars.GameLoadoutsPoints);
_loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", points));
_loadoutPointsBar.MaxValue = points;
_loadoutPointsBar.Value = points;
// Clear current listings
_loadoutPreferences.Clear();
_loadoutsTabs.DisposeAllChildren();
// Get the highest priority job to use for loadout filtering
var highJob = _jobPriorities.FirstOrDefault(j => j.Priority == JobPriority.High);
// Get all loadout prototypes
var enumeratedLoadouts = _prototypeManager.EnumeratePrototypes<LoadoutPrototype>().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(
loadout.Requirements,
highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
new Dictionary<string, TimeSpan>(),
_entMan,
_prototypeManager,
_configurationManager,
out _
)
).ToList();
// Loadouts to highlight red when showUnusable is true
var loadoutsUnusable = enumeratedLoadouts.Where(loadout =>
_loadoutSystem.CheckRequirementsValid(
loadout.Requirements,
highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
new Dictionary<string, TimeSpan>(),
_entMan,
_prototypeManager,
_configurationManager,
out _
)
).ToList();
// Every loadout not in the loadouts list
var otherLoadouts = enumeratedLoadouts.Where(loadout => !loadouts.Contains(loadout)).ToList();
if (loadouts.Count == 0)
{
_loadoutsTab.AddChild(new Label { Text = Loc.GetString("humanoid-profile-editor-loadouts-no-loadouts") });
return;
}
// Make Uncategorized category
var uncategorized = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
Name = "Uncategorized_0",
};
_loadoutsTabs.AddChild(uncategorized);
_loadoutsTabs.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-loadouts-uncategorized-tab"));
// 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))
{
// Check for existing category
BoxContainer? match = null;
foreach (var child in _loadoutsTabs.Children)
{
if (match != null || child.Name == null)
continue;
if (child.Name.Split("_")[0] == loadout.Category)
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 = $"{loadout.Category}_{currentCategory}",
// I hate ScrollContainers
Children =
{
new ScrollContainer
{
HScrollEnabled = false,
HorizontalExpand = true,
VerticalExpand = true,
Children =
{
new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
HorizontalExpand = true,
VerticalExpand = true,
},
},
},
},
};
_loadoutsTabs.AddChild(box);
_loadoutsTabs.SetTabTitle(currentCategory, Loc.GetString($"loadout-category-{loadout.Category}"));
currentCategory++;
}
// Fill categories
foreach (var loadout in loadouts.OrderBy(l => l.ID))
{
var selector = new LoadoutPreferenceSelector(loadout, highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
loadoutsUnusable.Contains(loadout) ? "" : "ButtonColorRed", _entMan, _prototypeManager,
_configurationManager, _loadoutSystem);
// Look for an existing loadout category
BoxContainer? match = null;
foreach (var child in _loadoutsTabs.Children)
{
if (match != null || child.Name == null)
continue;
if (child.Name.Split("_")[0] == loadout.Category)
match = (BoxContainer) child.Children.First().Children.First();
}
// If there is no category put it in Uncategorized
if (match?.Parent?.Parent?.Name == null)
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();
};
}
// Add the selected unusable loadouts to the point counter
foreach (var loadout in otherLoadouts.OrderBy(l => l.ID))
{
var selector = new LoadoutPreferenceSelector(loadout, highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "", _entMan, _prototypeManager,
_configurationManager, _loadoutSystem);
_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();
};
}
// 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);
// Add fake tabs until tab container is happy
for (var i = _loadoutsTabs.ChildCount - 1; i < _loadoutsTabs.CurrentTab; i++)
{
_loadoutsTabs.AddChild(new BoxContainer());
_loadoutsTabs.SetTabVisible(i + 1, false);
}
UpdateLoadoutPreferences();
}
private sealed class AntagPreferenceSelector : RequirementsSelector<AntagPrototype>
{
// 0 is yes and 1 is no
@@ -1456,12 +1743,12 @@ namespace Content.Client.Preferences.UI
private sealed class TraitPreferenceSelector : Control
{
public TraitPrototype Trait { get; }
private readonly CheckBox _checkBox;
private readonly Button _button;
public bool Preference
{
get => _checkBox.Pressed;
set => _checkBox.Pressed = value;
get => _button.Pressed;
set => _button.Pressed = value;
}
public event Action<bool>? PreferenceChanged;
@@ -1470,22 +1757,108 @@ namespace Content.Client.Preferences.UI
{
Trait = trait;
_checkBox = new CheckBox {Text = Loc.GetString(trait.Name)};
_checkBox.OnToggled += OnCheckBoxToggled;
_button = new Button {Text = Loc.GetString(trait.Name)};
_button.ToggleMode = true;
_button.OnToggled += OnButtonToggled;
if (trait.Description is { } desc)
{
_checkBox.ToolTip = Loc.GetString(desc);
_button.ToolTip = Loc.GetString(desc);
}
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children = { _checkBox },
Children = { _button },
});
}
private void OnCheckBoxToggled(BaseButton.ButtonToggledEventArgs args)
private void OnButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
PreferenceChanged?.Invoke(Preference);
}
}
private sealed class LoadoutPreferenceSelector : Control
{
public LoadoutPrototype Loadout { get; }
private readonly Button _button;
public bool Preference
{
get => _button.Pressed;
set => _button.Pressed = value;
}
public event Action<bool>? PreferenceChanged;
public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
HumanoidCharacterProfile profile, string style, IEntityManager entityManager, IPrototypeManager prototypeManager,
IConfigurationManager configManager, LoadoutSystem loadoutSystem)
{
Loadout = loadout;
// Display the first item in the loadout as a preview
// TODO: Maybe allow custom icons to be specified in the prototype?
var dummyLoadoutItem = entityManager.SpawnEntity(loadout.Items.First(), MapCoordinates.Nullspace);
// Create a sprite preview of the loadout item
var previewLoadout = new SpriteView
{
Scale = new Vector2(1, 1),
OverrideDirection = Direction.South,
VerticalAlignment = VAlignment.Center,
SizeFlagsStretchRatio = 1,
};
previewLoadout.SetEntity(dummyLoadoutItem);
// 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<MetaDataComponent>(dummyLoadoutItem).EntityName
: Loc.GetString($"loadout-name-{loadout.ID}"))}",
VerticalAlignment = VAlignment.Center,
ToggleMode = true,
};
_button.OnToggled += OnButtonToggled;
_button.AddStyleClass(style);
var tooltip = new StringBuilder();
// Add the loadout description to the tooltip if there is one
var desc = !Loc.TryGetString($"loadout-description-{loadout.ID}", out var description)
? entityManager.GetComponent<MetaDataComponent>(dummyLoadoutItem).EntityDescription
: description;
if (!string.IsNullOrEmpty(desc))
tooltip.Append($"{Loc.GetString(desc)}");
// Get requirement reasons
loadoutSystem.CheckRequirementsValid(loadout.Requirements, highJob, profile, new Dictionary<string, TimeSpan>(), entityManager, prototypeManager, configManager, out var reasons);
// Add requirement reasons to the tooltip
foreach (var reason in reasons)
tooltip.Append($"\n{reason.ToMarkup()}");
// Combine the tooltip and format it in the checkbox supplier
if (tooltip.Length > 0)
{
var formattedTooltip = new Tooltip();
formattedTooltip.SetMessage(FormattedMessage.FromMarkupPermissive(tooltip.ToString()));
_button.TooltipSupplier = _ => formattedTooltip;
}
// Add the loadout preview and the checkbox to the control
AddChild(new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
Children = { previewLoadout, _button },
});
}
private void OnButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
PreferenceChanged?.Invoke(Preference);
}

View File

@@ -61,8 +61,9 @@ namespace Content.IntegrationTests.Tests.Preferences
{SharedGameTicker.FallbackOverflowJob, JobPriority.High}
},
PreferenceUnavailableMode.StayInLobby,
new List<string> (),
new List<string>()
antagPreferences: new List<string>(),
traitPreferences: new List<string>(),
loadoutPreferences: new List<string>()
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
/// <inheritdoc />
public partial class Loadouts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "loadout",
columns: table => new
{
loadout_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
profile_id = table.Column<int>(type: "integer", nullable: false),
loadout_name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_loadout", x => x.loadout_id);
table.ForeignKey(
name: "FK_loadout_profile_profile_id",
column: x => x.profile_id,
principalTable: "profile",
principalColumn: "profile_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_loadout_profile_id_loadout_name",
table: "loadout",
columns: new[] { "profile_id", "loadout_name" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "loadout");
}
}
}

View File

@@ -604,6 +604,33 @@ namespace Content.Server.Database.Migrations.Postgres
b.ToTable("job", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Loadout", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("loadout_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("LoadoutName")
.IsRequired()
.HasColumnType("text")
.HasColumnName("loadout_name");
b.Property<int>("ProfileId")
.HasColumnType("integer")
.HasColumnName("profile_id");
b.HasKey("Id")
.HasName("PK_loadout");
b.HasIndex("ProfileId", "LoadoutName")
.IsUnique();
b.ToTable("loadout", (string)null);
});
modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
{
b.Property<int>("Id")
@@ -1509,6 +1536,18 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Loadout", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Loadouts")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_loadout_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.HasOne("Content.Server.Database.Preference", "Preference")
@@ -1733,6 +1772,8 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Jobs");
b.Navigation("Loadouts");
b.Navigation("Traits");
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,47 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
/// <inheritdoc />
public partial class Loadouts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "loadout",
columns: table => new
{
loadout_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
profile_id = table.Column<int>(type: "INTEGER", nullable: false),
loadout_name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_loadout", x => x.loadout_id);
table.ForeignKey(
name: "FK_loadout_profile_profile_id",
column: x => x.profile_id,
principalTable: "profile",
principalColumn: "profile_id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_loadout_profile_id_loadout_name",
table: "loadout",
columns: new[] { "profile_id", "loadout_name" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "loadout");
}
}
}

View File

@@ -568,6 +568,31 @@ namespace Content.Server.Database.Migrations.Sqlite
b.ToTable("job", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Loadout", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("loadout_id");
b.Property<string>("LoadoutName")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("loadout_name");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER")
.HasColumnName("profile_id");
b.HasKey("Id")
.HasName("PK_loadout");
b.HasIndex("ProfileId", "LoadoutName")
.IsUnique();
b.ToTable("loadout", (string)null);
});
modelBuilder.Entity("Content.Server.Database.PlayTime", b =>
{
b.Property<int>("Id")
@@ -1440,6 +1465,18 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Loadout", b =>
{
b.HasOne("Content.Server.Database.Profile", "Profile")
.WithMany("Loadouts")
.HasForeignKey("ProfileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_loadout_profile_profile_id");
b.Navigation("Profile");
});
modelBuilder.Entity("Content.Server.Database.Profile", b =>
{
b.HasOne("Content.Server.Database.Preference", "Preference")
@@ -1664,6 +1701,8 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Jobs");
b.Navigation("Loadouts");
b.Navigation("Traits");
});

View File

@@ -56,8 +56,12 @@ namespace Content.Server.Database
.IsUnique();
modelBuilder.Entity<Trait>()
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
.IsUnique();
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
.IsUnique();
modelBuilder.Entity<Loadout>()
.HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.LoadoutName})
.IsUnique();
modelBuilder.Entity<Job>()
.HasIndex(j => j.ProfileId);
@@ -347,6 +351,7 @@ namespace Content.Server.Database
public List<Job> Jobs { get; } = new();
public List<Antag> Antags { get; } = new();
public List<Trait> Traits { get; } = new();
public List<Loadout> Loadouts { get; } = new();
[Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
@@ -391,6 +396,15 @@ namespace Content.Server.Database
public string TraitName { get; set; } = null!;
}
public class Loadout
{
public int Id { get; set; }
public Profile Profile { get; set; } = null!;
public int ProfileId { get; set; }
public string LoadoutName { get; set; } = null!;
}
public enum DbPreferenceUnavailableMode
{
// These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.

View File

@@ -0,0 +1,49 @@
using Content.Server.GameTicking;
using Content.Server.Players.PlayTimeTracking;
using Content.Shared.CCVar;
using Content.Shared.Inventory;
using Content.Shared.Item;
using Content.Shared.Storage;
using Content.Shared.Storage.EntitySystems;
using Robust.Shared.Configuration;
namespace Content.Server.Clothing.Systems;
public sealed class LoadoutSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly Shared.Clothing.Loadouts.Systems.LoadoutSystem _loadout = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly SharedStorageSystem _storage = default!;
[Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
public override void Initialize()
{
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawnComplete);
}
private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent ev)
{
if (ev.JobId == null ||
!_configurationManager.GetCVar(CCVars.GameLoadoutsEnabled))
return;
// Spawn the loadout, get a list of items that failed to equip
var failedLoadouts = _loadout.ApplyCharacterLoadout(ev.Mob, ev.JobId, ev.Profile, _playTimeTracking.GetTrackerTimes(ev.Player));
// Try to find back-mounted storage apparatus
if (!_inventory.TryGetSlotEntity(ev.Mob, "back", out var item) ||
!EntityManager.TryGetComponent<StorageComponent>(item, out var inventory))
return;
// Try inserting the entity into the storage, if it can't, it leaves the loadout item on the ground
foreach (var loadout in failedLoadouts)
{
if (EntityManager.TryGetComponent<ItemComponent>(loadout, out var itemComp) &&
_storage.CanInsert(item.Value, loadout, out _, inventory, itemComp))
_storage.Insert(item.Value, loadout, out _, playSound: false);
}
}
}

View File

@@ -40,6 +40,7 @@ namespace Content.Server.Database
.Include(p => p.Profiles).ThenInclude(h => h.Jobs)
.Include(p => p.Profiles).ThenInclude(h => h.Antags)
.Include(p => p.Profiles).ThenInclude(h => h.Traits)
.Include(p => p.Profiles).ThenInclude(h => h.Loadouts)
.AsSingleQuery()
.SingleOrDefaultAsync(p => p.UserId == userId.UserId);
@@ -88,6 +89,7 @@ namespace Content.Server.Database
.Include(p => p.Jobs)
.Include(p => p.Antags)
.Include(p => p.Traits)
.Include(p => p.Loadouts)
.AsSplitQuery()
.SingleOrDefault(h => h.Slot == slot);
@@ -174,6 +176,7 @@ namespace Content.Server.Database
var jobs = profile.Jobs.ToDictionary(j => j.JobName, j => (JobPriority) j.Priority);
var antags = profile.Antags.Select(a => a.AntagName);
var traits = profile.Traits.Select(t => t.TraitName);
var loadouts = profile.Loadouts.Select(t => t.LoadoutName);
var sex = Sex.Male;
if (Enum.TryParse<Sex>(profile.Sex, true, out var sexVal))
@@ -232,7 +235,8 @@ namespace Content.Server.Database
jobs,
(PreferenceUnavailableMode) profile.PreferenceUnavailable,
antags.ToList(),
traits.ToList()
traits.ToList(),
loadouts.ToList()
);
}
@@ -285,6 +289,12 @@ namespace Content.Server.Database
.Select(t => new Trait {TraitName = t})
);
profile.Loadouts.Clear();
profile.Loadouts.AddRange(
humanoid.LoadoutPreferences
.Select(t => new Loadout {LoadoutName = t})
);
return profile;
}
#endregion

View File

@@ -341,6 +341,18 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<bool> DebugCoordinatesAdminOnly =
CVarDef.Create("game.debug_coordinates_admin_only", true, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Whether or not to allow characters to select loadout items.
/// </summary>
public static readonly CVarDef<bool> GameLoadoutsEnabled =
CVarDef.Create("game.loadouts_enabled", true, CVar.REPLICATED);
/// <summary>
/// How many points to give to each player for loadouts.
/// </summary>
public static readonly CVarDef<int> GameLoadoutsPoints =
CVarDef.Create("game.loadouts_points", 14, CVar.REPLICATED);
#if EXCEPTION_TOLERANCE
/// <summary>
/// Amount of times round start must fail before the server is shut down.

View File

@@ -1,35 +0,0 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Roles;
using Content.Shared.Station;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
namespace Content.Shared.Clothing;
/// <summary>
/// Assigns a loadout to an entity based on the startingGear prototype
/// </summary>
public sealed class LoadoutSystem : EntitySystem
{
// Shared so we can predict it for placement manager.
[Dependency] private readonly SharedStationSpawningSystem _station = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, LoadoutComponent component, MapInitEvent args)
{
if (component.Prototypes == null)
return;
var proto = _protoMan.Index<StartingGearPrototype>(_random.Pick(component.Prototypes));
_station.EquipStartingGear(uid, proto, null);
}
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Clothing.Loadouts.Prototypes;
/// <summary>
/// A prototype defining a valid category for <see cref="LoadoutPrototype"/>s to go into.
/// </summary>
[Prototype("loadoutCategory")]
public sealed class LoadoutCategoryPrototype : IPrototype
{
[IdDataField]
public string ID { get; } = default!;
}

View File

@@ -0,0 +1,45 @@
using Content.Shared.Clothing.Loadouts.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
{
/// <summary>
/// Formatted like "Loadout[Department/ShortHeadName][CommonClothingSlot][SimplifiedClothingId]", example: "LoadoutScienceOuterLabcoatSeniorResearcher"
/// </summary>
[IdDataField]
public string ID { get; } = default!;
/// <summary>
/// Which tab category to put this under
/// </summary>
[DataField(customTypeSerializer:typeof(PrototypeIdSerializer<LoadoutCategoryPrototype>))]
public string Category = "Uncategorized";
/// <summary>
/// The item to give
/// </summary>
[DataField(customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>), required: true)]
public List<string> Items = new();
/// <summary>
/// The point cost of this loadout
/// </summary>
[DataField]
public int Cost = 1;
/// <summary>
/// Should this item override other items in the same slot?
/// </summary>
[DataField]
public bool Exclusive;
[DataField]
public List<LoadoutRequirement> Requirements = new();
}

View File

@@ -0,0 +1,375 @@
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
{
/// <summary>
/// If true valid requirements will be treated as invalid and vice versa
/// </summary>
[DataField]
public bool Inverted = false;
/// <summary>
/// Checks if this loadout requirement is valid for the given parameters
/// </summary>
/// <param name="reason">Description for the requirement, shown when not null</param>
public abstract bool IsValid(
JobPrototype job,
HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> playTimes,
IEntityManager entityManager,
IPrototypeManager prototypeManager,
IConfigurationManager configManager,
out FormattedMessage? reason
);
}
#region HumanoidCharacterProfile
/// <summary>
/// Requires the profile to be within an age range
/// </summary>
[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<string, TimeSpan> 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;
}
}
/// <summary>
/// Requires the profile to use either a Backpack, Satchel, or Duffelbag
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LoadoutBackpackTypeRequirement : LoadoutRequirement
{
[DataField(required: true)]
public BackpackPreference Preference;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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;
}
}
/// <summary>
/// Requires the profile to use either Jumpsuits or Jumpskirts
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LoadoutClothingPreferenceRequirement : LoadoutRequirement
{
[DataField(required: true)]
public ClothingPreference Preference;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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;
}
}
/// <summary>
/// Requires the profile to be a certain species
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LoadoutSpeciesRequirement : LoadoutRequirement
{
[DataField(required: true)]
public ProtoId<SpeciesPrototype> Species;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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;
}
}
/// <summary>
/// Requires the profile to have a certain trait
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LoadoutTraitRequirement : LoadoutRequirement
{
[DataField(required: true)]
public ProtoId<TraitPrototype> Trait;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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
/// <summary>
/// Requires the selected job to be a certain one
/// </summary>
[UsedImplicitly]
[Serializable, NetSerializable]
public sealed partial class LoadoutJobRequirement : LoadoutRequirement
{
[DataField(required: true)]
public List<ProtoId<JobPrototype>> Jobs;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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);
}
}
/// <summary>
/// Requires the playtime for a department to be within a certain range
/// </summary>
[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<DepartmentPrototype> Department;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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<JobPrototype>(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;
}
}
/// <summary>
/// Requires the player to have a certain amount of overall job time
/// </summary>
[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<string, TimeSpan> 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;
}
}
/// <summary>
/// Requires the playtime for a tracker to be within a certain range
/// </summary>
[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<PlayTimeTrackerPrototype> Tracker;
public override bool IsValid(JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan> 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

View File

@@ -0,0 +1,149 @@
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.Station;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
namespace Content.Shared.Clothing.Loadouts.Systems;
public sealed class LoadoutSystem : EntitySystem
{
[Dependency] private readonly SharedStationSpawningSystem _station = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LoadoutComponent, MapInitEvent>(OnMapInit);
}
private void OnMapInit(EntityUid uid, LoadoutComponent component, MapInitEvent args)
{
if (component.Prototypes == null)
return;
var proto = _prototype.Index<StartingGearPrototype>(_random.Pick(component.Prototypes));
_station.EquipStartingGear(uid, proto, null);
}
/// <inheritdoc cref="ApplyCharacterLoadout(Robust.Shared.GameObjects.EntityUid,string,Content.Shared.Preferences.HumanoidCharacterProfile,System.Collections.Generic.Dictionary{string,System.TimeSpan}?)"/>
public List<EntityUid> ApplyCharacterLoadout(EntityUid uid, string job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan>? playTimes = null)
{
var jobPrototype = _prototype.Index<JobPrototype>(job);
return ApplyCharacterLoadout(uid, jobPrototype, profile, playTimes);
}
/// <summary>
/// Equips entities from a <see cref="HumanoidCharacterProfile"/>'s loadout preferences to a given entity
/// </summary>
/// <param name="uid">The entity to give the loadout items to</param>
/// <param name="job">The job to use for loadout whitelist/blacklist (should be the job of the entity)</param>
/// <param name="profile">The profile to get loadout items from (should be the entity's, or at least have the same species as the entity)</param>
/// <param name="playTimes">Playtime for the player for use with playtime requirements</param>
/// <returns>A list of loadout items that couldn't be equipped but passed checks</returns>
public List<EntityUid> ApplyCharacterLoadout(EntityUid uid, JobPrototype job, HumanoidCharacterProfile profile,
Dictionary<string, TimeSpan>? playTimes = null)
{
var failedLoadouts = new List<EntityUid>();
foreach (var loadout in profile.LoadoutPreferences)
{
var slot = "";
// Ignore loadouts that don't exist
if (!_prototype.TryIndex<LoadoutPrototype>(loadout, out var loadoutProto))
continue;
if (!CheckRequirementsValid(loadoutProto.Requirements, job, profile,
playTimes ?? new Dictionary<string, TimeSpan>(), EntityManager, _prototype, _configurationManager,
out _))
continue;
// Spawn the loadout items
var spawned = EntityManager.SpawnEntities(
EntityManager.GetComponent<TransformComponent>(uid).Coordinates.ToMap(EntityManager),
loadoutProto.Items!);
foreach (var item in spawned)
{
if (EntityManager.TryGetComponent<ClothingComponent>(item, out var clothingComp) &&
_inventory.TryGetSlots(uid, out var slotDefinitions))
{
var deleted = false;
foreach (var curSlot in slotDefinitions)
{
// If the loadout can't equip here or we've already deleted an item from this slot, skip it
if (!clothingComp.Slots.HasFlag(curSlot.SlotFlags) || deleted)
continue;
slot = curSlot.Name;
// If the loadout is exclusive delete the equipped item
if (loadoutProto.Exclusive)
{
// Get the item in the slot
if (!_inventory.TryGetSlotEntity(uid, curSlot.Name, out var slotItem))
continue;
EntityManager.DeleteEntity(slotItem.Value);
deleted = true;
}
}
}
// Equip the loadout
if (!_inventory.TryEquip(uid, item, slot, true, !string.IsNullOrEmpty(slot), true))
failedLoadouts.Add(item);
}
}
// Return a list of items that couldn't be equipped so the server can handle it if it wants
// 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<LoadoutRequirement> requirements, JobPrototype job,
HumanoidCharacterProfile profile, Dictionary<string, TimeSpan> playTimes, IEntityManager entityManager,
IPrototypeManager prototypeManager, IConfigurationManager configManager, out List<FormattedMessage> reasons)
{
reasons = new List<FormattedMessage>();
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;
}
}

View File

@@ -1,11 +1,10 @@
using System.Linq;
using System.Globalization;
using System.Text.RegularExpressions;
using Content.Shared.CCVar;
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Random.Helpers;
using Content.Shared.Roles;
using Content.Shared.Traits;
using Robust.Shared.Configuration;
@@ -30,6 +29,7 @@ namespace Content.Shared.Preferences
private readonly Dictionary<string, JobPriority> _jobPriorities;
private readonly List<string> _antagPreferences;
private readonly List<string> _traitPreferences;
private readonly List<string> _loadoutPreferences;
private HumanoidCharacterProfile(
string name,
@@ -45,7 +45,8 @@ namespace Content.Shared.Preferences
Dictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
List<string> antagPreferences,
List<string> traitPreferences)
List<string> traitPreferences,
List<string> loadoutPreferences)
{
Name = name;
FlavorText = flavortext;
@@ -61,6 +62,7 @@ namespace Content.Shared.Preferences
PreferenceUnavailable = preferenceUnavailable;
_antagPreferences = antagPreferences;
_traitPreferences = traitPreferences;
_loadoutPreferences = loadoutPreferences;
}
/// <summary>Copy constructor but with overridable references (to prevent useless copies)</summary>
@@ -68,15 +70,19 @@ namespace Content.Shared.Preferences
HumanoidCharacterProfile other,
Dictionary<string, JobPriority> jobPriorities,
List<string> antagPreferences,
List<string> traitPreferences)
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack, other.SpawnPriority,
jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences)
List<string> traitPreferences,
List<string> loadoutPreferences)
: this(other.Name, other.FlavorText, other.Species, other.Age, other.Sex, other.Gender, other.Appearance,
other.Clothing, other.Backpack, other.SpawnPriority, jobPriorities, other.PreferenceUnavailable,
antagPreferences, traitPreferences, loadoutPreferences)
{
}
/// <summary>Copy constructor</summary>
private HumanoidCharacterProfile(HumanoidCharacterProfile other)
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities), new List<string>(other.AntagPreferences), new List<string>(other.TraitPreferences))
: this(other, new Dictionary<string, JobPriority>(other.JobPriorities),
new List<string>(other.AntagPreferences), new List<string>(other.TraitPreferences),
new List<string>(other.LoadoutPreferences))
{
}
@@ -94,9 +100,12 @@ namespace Content.Shared.Preferences
IReadOnlyDictionary<string, JobPriority> jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
IReadOnlyList<string> antagPreferences,
IReadOnlyList<string> traitPreferences)
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, spawnPriority, new Dictionary<string, JobPriority>(jobPriorities),
preferenceUnavailable, new List<string>(antagPreferences), new List<string>(traitPreferences))
IReadOnlyList<string> traitPreferences,
IReadOnlyList<string> loadoutPreferences)
: this(name, flavortext, species, age, sex, gender, appearance, clothing, backpack, spawnPriority,
new Dictionary<string, JobPriority>(jobPriorities), preferenceUnavailable,
new List<string>(antagPreferences), new List<string>(traitPreferences),
new List<string>(loadoutPreferences))
{
}
@@ -122,6 +131,7 @@ namespace Content.Shared.Preferences
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
new List<string>(),
new List<string>())
{
}
@@ -150,6 +160,7 @@ namespace Content.Shared.Preferences
},
PreferenceUnavailableMode.SpawnAsOverflow,
new List<string>(),
new List<string>(),
new List<string>());
}
@@ -195,11 +206,13 @@ namespace Content.Shared.Preferences
var name = GetName(species, gender);
return new HumanoidCharacterProfile(name, "", species, age, sex, gender, HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit, BackpackPreference.Backpack, SpawnPriorityPreference.None,
return new HumanoidCharacterProfile(name, "", species, age, sex, gender,
HumanoidCharacterAppearance.Random(species, sex), ClothingPreference.Jumpsuit,
BackpackPreference.Backpack, SpawnPriorityPreference.None,
new Dictionary<string, JobPriority>
{
{SharedGameTicker.FallbackOverflowJob, JobPriority.High},
}, PreferenceUnavailableMode.StayInLobby, new List<string>(), new List<string>());
}, PreferenceUnavailableMode.StayInLobby, new List<string>(), new List<string>(), new List<string>());
}
public string Name { get; private set; }
@@ -225,6 +238,7 @@ namespace Content.Shared.Preferences
public IReadOnlyDictionary<string, JobPriority> JobPriorities => _jobPriorities;
public IReadOnlyList<string> AntagPreferences => _antagPreferences;
public IReadOnlyList<string> TraitPreferences => _traitPreferences;
public IReadOnlyList<string> LoadoutPreferences => _loadoutPreferences;
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
public HumanoidCharacterProfile WithName(string name)
@@ -277,7 +291,8 @@ namespace Content.Shared.Preferences
}
public HumanoidCharacterProfile WithJobPriorities(IEnumerable<KeyValuePair<string, JobPriority>> jobPriorities)
{
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences);
return new(this, new Dictionary<string, JobPriority>(jobPriorities), _antagPreferences, _traitPreferences,
_loadoutPreferences);
}
public HumanoidCharacterProfile WithJobPriority(string jobId, JobPriority priority)
@@ -291,7 +306,7 @@ namespace Content.Shared.Preferences
{
dictionary[jobId] = priority;
}
return new(this, dictionary, _antagPreferences, _traitPreferences);
return new(this, dictionary, _antagPreferences, _traitPreferences, _loadoutPreferences);
}
public HumanoidCharacterProfile WithPreferenceUnavailable(PreferenceUnavailableMode mode)
@@ -301,7 +316,8 @@ namespace Content.Shared.Preferences
public HumanoidCharacterProfile WithAntagPreferences(IEnumerable<string> antagPreferences)
{
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences);
return new(this, _jobPriorities, new List<string>(antagPreferences), _traitPreferences,
_loadoutPreferences);
}
public HumanoidCharacterProfile WithAntagPreference(string antagId, bool pref)
@@ -321,7 +337,7 @@ namespace Content.Shared.Preferences
list.Remove(antagId);
}
}
return new(this, _jobPriorities, list, _traitPreferences);
return new(this, _jobPriorities, list, _traitPreferences, _loadoutPreferences);
}
public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
@@ -343,7 +359,28 @@ namespace Content.Shared.Preferences
list.Remove(traitId);
}
}
return new(this, _jobPriorities, _antagPreferences, list);
return new(this, _jobPriorities, _antagPreferences, list, _loadoutPreferences);
}
public HumanoidCharacterProfile WithLoadoutPreference(string loadoutId, bool pref)
{
var list = new List<string>(_loadoutPreferences);
if(pref)
{
if(!list.Contains(loadoutId))
{
list.Add(loadoutId);
}
}
else
{
if(list.Contains(loadoutId))
{
list.Remove(loadoutId);
}
}
return new(this, _jobPriorities, _antagPreferences, _traitPreferences, list);
}
public string Summary =>
@@ -356,18 +393,21 @@ namespace Content.Shared.Preferences
public bool MemberwiseEquals(ICharacterProfile maybeOther)
{
if (maybeOther is not HumanoidCharacterProfile other) return false;
if (Name != other.Name) return false;
if (Age != other.Age) return false;
if (Sex != other.Sex) return false;
if (Gender != other.Gender) return false;
if (PreferenceUnavailable != other.PreferenceUnavailable) return false;
if (Clothing != other.Clothing) return false;
if (Backpack != other.Backpack) return false;
if (SpawnPriority != other.SpawnPriority) return false;
if (!_jobPriorities.SequenceEqual(other._jobPriorities)) return false;
if (!_antagPreferences.SequenceEqual(other._antagPreferences)) return false;
if (!_traitPreferences.SequenceEqual(other._traitPreferences)) return false;
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);
}
@@ -436,8 +476,8 @@ namespace Content.Shared.Preferences
{
// This regex replaces the first character of the first and last words of the name with their uppercase version
name = Regex.Replace(name,
@"^(?<word>\w)|\b(?<word>\w)(?=\w*$)",
m => m.Groups["word"].Value.ToUpper());
@"^(?<word>\w)|\b(?<word>\w)(?=\w*$)",
m => m.Groups["word"].Value.ToUpper());
}
if (string.IsNullOrEmpty(name))
@@ -502,8 +542,25 @@ namespace Content.Shared.Preferences
.ToList();
var traits = TraitPreferences
.Where(prototypeManager.HasIndex<TraitPrototype>)
.ToList();
.Where(prototypeManager.HasIndex<TraitPrototype>)
.ToList();
var loadouts = LoadoutPreferences
.Where(prototypeManager.HasIndex<LoadoutPrototype>)
.ToList();
var maxLoadouts = configManager.GetCVar(CCVars.GameLoadoutsPoints);
var currentLoadouts = 0;
foreach (var loadout in loadouts.ToList())
{
var proto = prototypeManager.Index<LoadoutPrototype>(loadout);
if (currentLoadouts + proto.Cost > maxLoadouts)
loadouts.Remove(loadout);
else
currentLoadouts += proto.Cost;
}
Name = name;
FlavorText = flavortext;
@@ -529,6 +586,9 @@ namespace Content.Shared.Preferences
_traitPreferences.Clear();
_traitPreferences.AddRange(traits);
_loadoutPreferences.Clear();
_loadoutPreferences.AddRange(loadouts);
}
public ICharacterProfile Validated(IConfigurationManager configManager, IPrototypeManager prototypeManager)
@@ -568,7 +628,8 @@ namespace Content.Shared.Preferences
PreferenceUnavailable,
_jobPriorities,
_antagPreferences,
_traitPreferences
_traitPreferences,
_loadoutPreferences
);
}
}

View File

@@ -101,7 +101,8 @@ namespace Content.Shared.Roles
[NotNullWhen(false)] out FormattedMessage? reason,
IEntityManager entManager,
IPrototypeManager prototypes,
bool isWhitelisted)
bool isWhitelisted,
string? localePrefix = "role-timer-")
{
reason = null;
@@ -133,7 +134,7 @@ namespace Content.Shared.Roles
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-department-insufficient",
$"{localePrefix}department-insufficient",
("time", Math.Ceiling(deptDiff)),
("department", Loc.GetString(deptRequirement.Department)),
("departmentColor", department.Color.ToHex())));
@@ -144,7 +145,7 @@ namespace Content.Shared.Roles
if (deptDiff <= 0)
{
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-department-too-high",
$"{localePrefix}department-too-high",
("time", -deptDiff),
("department", Loc.GetString(deptRequirement.Department)),
("departmentColor", department.Color.ToHex())));
@@ -164,7 +165,7 @@ namespace Content.Shared.Roles
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-overall-insufficient",
$"{localePrefix}overall-insufficient",
("time", Math.Ceiling(overallDiff))));
return false;
}
@@ -172,7 +173,7 @@ namespace Content.Shared.Roles
{
if (overallDiff <= 0 || overallTime >= overallRequirement.Time)
{
reason = FormattedMessage.FromMarkup(Loc.GetString("role-timer-overall-too-high", ("time", -overallDiff)));
reason = FormattedMessage.FromMarkup(Loc.GetString($"{localePrefix}overall-too-high", ("time", -overallDiff)));
return false;
}
@@ -200,7 +201,7 @@ namespace Content.Shared.Roles
return true;
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-role-insufficient",
$"{localePrefix}role-insufficient",
("time", Math.Ceiling(roleDiff)),
("job", Loc.GetString(proto)),
("departmentColor", departmentColor.ToHex())));
@@ -211,7 +212,7 @@ namespace Content.Shared.Roles
if (roleDiff <= 0)
{
reason = FormattedMessage.FromMarkup(Loc.GetString(
"role-timer-role-too-high",
$"{localePrefix}role-too-high",
("time", -roleDiff),
("job", Loc.GetString(proto)),
("departmentColor", departmentColor.ToHex())));

View File

@@ -0,0 +1,7 @@
# Alphabetically ordered
loadout-category-Accessories = Accessories
loadout-category-Items = Items
loadout-category-Jobs = Jobs
loadout-category-Outer = Outer
loadout-category-Uncategorized = Uncategorized
loadout-category-Uniform = Uniform

View File

@@ -0,0 +1,2 @@
loadout-description-LoadoutEyesEyepatch = Eyewear, for the fashionista without an eye.
loadout-description-LoadoutEyesBlindfold = Why would you want this?

View File

@@ -0,0 +1,2 @@
loadout-description-LoadoutHeadBeaverHat = Gentlemen.
loadout-description-LoadoutHeadTophat = A stylish black tophat.

View File

@@ -0,0 +1,13 @@
loadout-description-LoadoutItemCig = Cool guys always have one.
loadout-description-LoadoutItemCigsGreen = A pack a day keeps the doctor well-paid!
loadout-description-LoadoutItemCigsRed = A pack a day keeps the doctor well-paid!
loadout-description-LoadoutItemCigsBlue = A pack a day keeps the doctor well-paid!
loadout-description-LoadoutItemCigsBlack = A pack a day keeps the doctor well-paid!
loadout-description-LoadoutItemPAI = A little flakey on booting up, but a more loyal friend you won't find.
loadout-description-LoadoutItemLighter = A basic lighter.
loadout-description-LoadoutItemLighterCheap = A very basic lighter.
loadout-description-LoadoutItemMatches = A box of matches.
loadout-description-LoadoutItemPlushieSharkBlue = Dive into battle with your very own aquatic ally, the Blue Shark Plushie! It's more cuddly than fierce, but don't underestimate its ability to strike fear into the hearts of your enemies… or at least make them laugh as they're devoured by cuteness overload.
loadout-description-LoadoutItemPlushieSharkPink = Unleash the power of pink with the Pink Shark Plushie! This rosy-hued predator might not have real teeth, but its sheer adorableness is enough to take a bite out of anyone's resolve. Watch as foes melt away in the face of its cottony charm.
loadout-description-LoadoutItemPlushieSharkGrey = Introducing the Grey Shark Plushie, the apex predator of snuggles! With its sleek and understated design, this plushie strikes the perfect balance between cuddle companion and imaginary ocean guardian. Beware; opponents may be mesmerized by its dorsal fin's hypnotic sway!
loadout-description-LoadoutItemPlushieCarp = Brace for extraterrestrial antics with the Purple Space Carp Plushie! A fishy invader from the cosmic deep, this plushie brings a splash of humor to zero-gravity escapades. From hostile waters to interstellar giggles, it's a cuddly contradiction that's out of this world

View File

@@ -0,0 +1 @@
loadout-description-LoadoutCargoNeckGoliathCloak = For the greatest of the Salvage crew.

View File

@@ -0,0 +1,5 @@
loadout-name-LoadoutEngineeringChickenSuit = eggmospheric technician suit
loadout-description-LoadoutEngineeringChickenSuit = For the Eggmos tech who always knows where home is...
loadout-description-LoadoutEngineeringUniformJumpskirtSenior = A skirt fit for the best of the best.
loadout-description-LoadoutEngineeringUniformJumpsuitSenior = A suit fit for the best of the best.
loadout-description-LoadoutEngineeringItemInflatable = A box containing inflatable walls and doors, for quickly patching up breaches.

View File

@@ -0,0 +1,12 @@
loadout-description-LoadoutCommandCapNeckMantle = To show who has the authority around here.
loadout-description-LoadoutCommandCapNeckCloak = To really show who has the authority around here.
loadout-description-LoadoutCommandCapNeckCloakFormal = More than just to show who has the authority, it also shows who has the greatest fashion sense.
loadout-description-LoadoutCommandCapJumpsuitFormal = The outfit is quite fancy. I am curious where the wearer could be heading to justify such a stylish look.
loadout-description-LoadoutCommandCapJumpskirtFormal = The outfit is quite fancy. I am curious where the wearer could be heading to justify such a stylish look.
loadout-description-LoadoutCommandCapOuterWinter = A warm coat for the cold of space.
loadout-description-LoadoutCommandCapGloves = The gloves of the captain. They are very nice gloves.
loadout-description-LoadoutCommandCapHat = The hat of the captain. It is a very nice hat.
loadout-description-LoadoutCommandCapHatCapcap = The Captain's cap, pretty nice.
loadout-description-LoadoutCommandCapHat = The Captain's beret, very nice.
loadout-description-LoadoutCommandCapMaskGas = Why would the captain need this? I don't know, but it looks cool.
loadout-description-LoadoutCommandCapItemDrinkFlask = The finest of flasks, for the finest of drinks.

View File

@@ -0,0 +1,3 @@
loadout-description-LoadoutCommandCENeckMantle = To show who has the authority around here. It seems over-engineered.
loadout-description-LoadoutCommandCENeckCloak = To really show who has the authority around here. It seems over-engineered.
loadout-description-LoadoutCommandCEOuterWinter = A warm coat for the cold of space. It seems over-engineered.

View File

@@ -0,0 +1,5 @@
loadout-description-LoadoutCommandCMONeckMantle = To show who has the authority around here. It seems unusually clean.
loadout-description-LoadoutCommandCMONeckCloak = To really show who has the authority around here. It seems unusually clean.
loadout-description-LoadoutCommandCMOOuterWinter = A warm coat for the cold of space. It seems unusually clean.
loadout-description-LoadoutCommandCMOOuterLab = A lab coat for the CMO. It seems unusually clean.
loadout-description-LoadoutCommandCMOHatBeret = A beret for the CMO. It seems unusually clean.

View File

@@ -0,0 +1,4 @@
loadout-description-LoadoutCommandHOPNeckMantle = To show who has the authority around here.
loadout-description-LoadoutCommandHOPNeckCloak = To really show who has the authority around here.
loadout-description-LoadoutCommandHOPBackIan = A backpack that looks like Ian, how cute!
loadout-description-LoadoutCommandHOPHatCap = The HOP's cap, pretty nice.

View File

@@ -0,0 +1,10 @@
loadout-description-LoadoutCommandHOSNeckMantle = To really show who has the authority around here.
loadout-description-LoadoutCommandHOSNeckCloak = To truly show who has the authority around here.
loadout-description-LoadoutCommandHOSJumpsuitParade = A fancy uniform for a fancy officer, wonder what event they mey be attending.
loadout-description-LoadoutCommandHOSJumpsuitFormal = The outfit is quite fancy. I am curious where the wearer could be heading to justify such a stylish look.
loadout-description-LoadoutCommandHOSJumpskirtParade = A fancy uniform for a fancy officer, wonder what event they mey be attending.
loadout-description-LoadoutCommandHOSJumpskirtFormal = The outfit is quite fancy. I am curious where the wearer could be heading to justify such a stylish look.
loadout-description-LoadoutCommandHOSOuterWinter = A warm coat for the cold of space.
loadout-description-LoadoutCommandHOSOuterTrench = A trench coat for the HOS.
loadout-description-LoadoutCommandHOSHatBeret = A beret for the HOS.
loadout-description-LoadoutCommandHOSHatHoshat = The HOS's hat, pretty nice.

View File

@@ -0,0 +1,3 @@
loadout-description-LoadoutCommandQMNeckMantle = To show who has the authority around here. It's stained with beer.
loadout-description-LoadoutCommandQMNeckCloak = To really show who has the authority around here. It's stained with beer. It's stained with beer. It's stained with beer.
loadout-description-LoadoutCommandQMHeadSoft = The QM's hat. It's stained with beer.

View File

@@ -0,0 +1,3 @@
loadout-description-LoadoutCommandRDNeckMantle = To show who has the authority around here.
loadout-description-LoadoutCommandRDNeckCloak = To really show who has the authority around here.
loadout-description-LoadoutCommandRDOuterWinter = A warm coat for the cold of space.

View File

@@ -0,0 +1,3 @@
loadout-description-LoadoutMedicalUniformJumpskirtSenior = A skirt fit for the best of the best.
loadout-description-LoadoutMedicalUniformJumpsuitSenior = A suit fit for the best of the best.
loadout-description-LoadoutMedicalHeadBeretSeniorPhysician = A beret fit for the best of the best.

View File

@@ -0,0 +1,3 @@
loadout-description-LoadoutScienceUniformJumpskirtSenior = A skirt fit for the best of the best.
loadout-description-LoadoutScienceUniformJumpsuitSenior = A suit fit for the best of the best.
loadout-description-LoadoutScienceOuterLabcoatSeniorResearcher = A labcoat fit for the best of the best.

View File

@@ -0,0 +1,3 @@
loadout-description-LoadoutSecurityUniformJumpskirtSenior = A skirt fit for the best of the best.
loadout-description-LoadoutSecurityUniformJumpsuitSenior = A suit fit for the best of the best.
loadout-description-LoadoutSecurityShoesJackboots = A really nice, heavy, pair of black boots.

View File

@@ -0,0 +1,7 @@
loadout-description-LoadoutServiceClownUniformJesterAlt = For the fool who knows their place.
loadout-description-LoadoutServiceClownShoesJester = For the fool who knows their place.
loadout-description-LoadoutServiceClownHeadJesterHatAlt = For the fool who knows their place.
loadout-description-LoadoutServiceBotanistUniformOveralls = A rugged pair of overalls.
loadout-description-LoadoutServiceReporterUniformJournalist = For the reporter on the case!
loadout-description-LoadoutServiceReporterUniformDetectivesuit = Always reminds you of the one that got away...
loadout-description-LoadoutServiceReporterUniformDetectiveskirt = Always reminds you of the one that got away...

View File

@@ -0,0 +1,13 @@
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]

View File

View File

@@ -0,0 +1,5 @@
loadout-description-LoadoutOuterGhostSheet = Spooky...
loadout-description-LoadoutOuterCoatBomberjacket = A sleek bomber jacket.
loadout-description-LoadoutOuterCoatHoodieBlack = A warm hoodie.
loadout-description-LoadoutOuterCoatHoodieGrey = A warm hoodie.
loadout-description-LoadoutOuterCoatWinterCoat = For keeping nice and snug.

View File

@@ -0,0 +1,10 @@
loadout-description-LoadoutShoesBlack = Step into the shadows with these sleek and stylish black shoes, perfect for ninjas or anyone looking to leave an enigmatic impression.
loadout-description-LoadoutShoesBlue = From zero-gravity dance floors to orbital adventures, these kicks are ready to make your journey as vibrant as the cosmos.
loadout-description-LoadoutShoesBrown = Classic and reliable, these brown shoes are like your trusted sidekick on any adventure.
loadout-description-LoadoutShoesGreen = Embrace the essence of nature with these green shoes. Slip them on, and let the world become your lush and vibrant playground.
loadout-description-LoadoutShoesOrange = Gear up for high-octane thrills on the space station with these vivid orange shoes.
loadout-description-LoadoutShoesPurple = Step into the unknown with the allure of these purple shoes. Channel the mystique of space as you stroll through the station's enigmatic sectors, leaving an air of intrigue in your wake.
loadout-description-LoadoutShoesRed = Embrace the spirit of exploration with these bold red shoes that mirror the pioneering heart of the space station's inhabitants. These shoes are not just for walking; they're a declaration of your relentless determination.
loadout-description-LoadoutShoesWhite = Elevate your style with these pristine white shoes, a symbol of innovation and progress.
loadout-description-LoadoutShoesYellow = Light up the space station with these radiant yellow shoes, bringing a burst of energy to your every step.
loadout-description-LoadoutShoesSlippersDuck = Quack up your downtime with these adorable duck slippers that waddle the line between comfort and quirkiness.

View File

@@ -0,0 +1 @@
loadout-description-LoadoutUniformAncientJumpsuit = The legend of the Greytide.

View File

@@ -3,6 +3,7 @@ humanoid-profile-editor-name-label = Name:
humanoid-profile-editor-name-random-button = Randomize
humanoid-profile-editor-appearance-tab = Appearance
humanoid-profile-editor-clothing = Show clothing
humanoid-profile-editor-loadouts = Show loadout
humanoid-profile-editor-clothing-show = Show
humanoid-profile-editor-sex-label = Sex:
humanoid-profile-editor-sex-male-text = Male
@@ -49,5 +50,14 @@ 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.
humanoid-profile-editor-markings-tab = Markings
humanoid-profile-editor-flavortext-tab = Description

View File

@@ -20,7 +20,6 @@
- id: SpaceCashLuckyBill # DeltaV - LO steal objective, see Resources/Prototypes/DeltaV/Entities/Objects/Misc/first_bill.yml
- id: BoxPDACargo # Delta-V
- id: QuartermasterIDCard # Delta-V
- id: ClothingShoesBootsWinterLogisticsOfficer #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -36,7 +35,6 @@
- id: PinpointerNuclear
# - id: CaptainIDCard # DeltaV - Replaced by the spare ID system
- id: ClothingOuterHardsuitCap
- id: ClothingMaskGasCaptain
- id: WeaponDisabler
- id: CommsComputerCircuitboard
- id: ClothingHeadsetAltCommand
@@ -51,10 +49,6 @@
# - id: WeaponAntiqueLaser # DeltaV - Remove in favor of the glass box
- id: JetpackCaptainFilled
- id: MedalCase
- id: ClothingHeadHatBeretCap # Nyanotrasen - Captain's Beret
- id: ClothingShoesLeather # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesMiscWhite # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesBootsWinterCap #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -84,9 +78,6 @@
- id: JetpackCaptainFilled
- id: MedalCase
- id: ClothingHeadHatBeretCap # Nyanotrasen - Captain's Beret
- id: ClothingShoesLeather # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesMiscWhite # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesBootsWinterCap #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -115,9 +106,6 @@
- id: JetpackCaptainFilled
- id: MedalCase
- id: ClothingHeadHatBeretCap # Nyanotrasen - Captain's Beret
- id: ClothingShoesLeather # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesMiscWhite # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesBootsWinterCap #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -137,7 +125,6 @@
- id: WeaponDisabler
- id: ClothingOuterCoatHoPArmored # DeltaV
- id: ClothingOuterArmorDuraVest # DeltaV - replaced HoP's armoured coat with a standard stabproof, pending HoPcoat resprite
- id: ClothingOuterCoatHoPFormal # DeltaV - formal jacket
- id: CigarGoldCase
prob: 0.25
# Fuck the HoP they don't deserve fucking cigars.
@@ -147,17 +134,9 @@
- id: RubberStampHop
- id: BoxEncryptionKeyPassenger
- id: BoxEncryptionKeyService
- id: ClothingBackpackIan
prob: 0.5
- id: AccessConfigurator
- id: BookIanDossier # DeltaV - HoP steal objective, see Resources/Prototypes/DeltaV/Entities/Objects/Misc/ian_dossier.yml
- id: ClothingHandsGlovesInspection # DeltaV - Add inspection gloves for HoP.
- id: ClothingUniformJumpsuitHoPMess # DeltaV - Add mess kit for HoP.
- id: ClothingUniformJumpskirtHoPMess # DeltaV - Add mess kit for HoP.
- id: ClothingUniformJumpsuitBoatswain # DeltaV - Add turtleneck for HoP.
- id: ClothingShoesBootsLaceup # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesMiscWhite # DeltaV - add fancy shoes for HoP and cap
- id: ClothingShoesBootsWinterHeadOfPersonel #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -186,7 +165,6 @@
- id: RCD
- id: RCDAmmo
- id: CEIDCard # Delta-V
- id: ClothingShoesBootsWinterChiefEngineer #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -209,7 +187,6 @@
- id: AccessConfigurator
- id: BoxPDAEngineering # Delta-V
- id: CEIDCard # Delta-V
- id: ClothingShoesBootsWinterChiefEngineer #Delta V: Add departmental winter boots
- id: RCD
- id: RCDAmmo
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
@@ -226,7 +203,6 @@
- id: ClothingHandsGlovesNitrile
- id: ClothingEyesHudMedical
- id: ClothingHeadsetAltMedical
- id: ClothingCloakCmo
- id: ClothingBackpackDuffelSurgeryFilled
- id: ClothingMaskSterile
- id: ClothingHeadHatBeretCmo
@@ -241,7 +217,6 @@
- id: BoxPDAMedical # Delta-V
- id: ClothingBeltMilitaryWebbingCMO # DeltaV - add webbing for CMO. ON THIS STATION, IT'S DRIP OR [die], CAPTAIN!
- id: CMOIDCard # Delta-V
- id: ClothingShoesBootsWinterChiefMedicalOfficer #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -257,7 +232,6 @@
- id: ClothingEyesHudMedical
- id: ClothingHeadsetAltMedical
- id: ClothingBackpackDuffelSurgeryFilled
- id: ClothingMaskSterile
- id: Hypospray
- id: HandheldCrewMonitor
- id: DoorRemoteMedical
@@ -267,7 +241,6 @@
- id: BoxPDAMedical # Delta-V
- id: ClothingBeltMilitaryWebbingCMO # DeltaV - add webbing for CMO. ON THIS STATION, IT'S DRIP OR [die], CAPTAIN!
- id: CMOIDCard # Delta-V
- id: ClothingShoesBootsWinterChiefMedicalOfficer #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -289,7 +262,6 @@
- id: BoxEncryptionKeyScience
- id: BoxPDAScience # Delta-V
- id: RDIDCard # Delta-V
- id: ClothingShoesBootsWinterMystagogue #Delta V: Add departmental winter boots
- id: ClothingHeadsetAltScience
- id: EncryptionKeyBinary
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
@@ -312,7 +284,6 @@
- id: BoxEncryptionKeyScience
- id: BoxPDAScience # Delta-V
- id: RDIDCard # Delta-V
- id: ClothingShoesBootsWinterMystagogue #Delta V: Add departmental winter boots
- id: ClothingHeadsetAltScience
- id: EncryptionKeyBinary
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
@@ -330,7 +301,6 @@
- id: ClothingOuterCoatHoSTrench
- id: ClothingMaskNeckGaiter
- id: ClothingOuterHardsuitCombatHoS # DeltaV - ClothingOuterHardsuitSecurityRed replaced in favour of head of security's advanced combat hardsuit.
- id: ClothingMaskGasSwat
- id: ClothingBeltSecurityFilled
- id: ClothingHeadsetAltSecurity
- id: ClothingEyesGlassesSunglasses
@@ -347,7 +317,6 @@
- id: BoxPDASecurity # Delta-V
- id: WeaponEnergyGunMultiphase # DeltaV - HoS Energy Gun
- id: HoSIDCard # Delta-V
- id: ClothingShoesBootsWinterHeadOfSecurity #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3
@@ -377,7 +346,6 @@
- id: BoxPDASecurity # Delta-V
- id: WeaponEnergyGunMultiphase # DeltaV - HoS Energy Gun
- id: HoSIDCard # Delta-V
- id: ClothingShoesBootsWinterHeadOfSecurity #Delta V: Add departmental winter boots
- id: LunchboxCommandFilledRandom # Delta-V Lunchboxes!
prob: 0.3

View File

@@ -0,0 +1,148 @@
- type: loadout
id: LoadoutCommandCapNeckMantle
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingNeckMantleCap
- type: loadout
id: LoadoutCommandCapNeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingNeckCloakCap
- type: loadout
id: LoadoutCommandCapNeckCloakFormal
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingNeckCloakCapFormal
- type: loadout
id: LoadoutCommandCapJumpsuitFormal
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingUniformJumpsuitCapFormal
- type: loadout
id: LoadoutCommandCapJumpskirtFormal
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingUniformJumpskirtCapFormalDress
- type: loadout
id: LoadoutCommandCapOuterWinter
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingOuterWinterCap
- type: loadout
id: LoadoutCommandCapGloves
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingHandsGlovesCaptain
- type: loadout
id: LoadoutCommandCapHat
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingHeadHatCaptain
- type: loadout
id: LoadoutCommandCapHatCapcap
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingHeadHatCapcap
- type: loadout
id: LoadoutCommandCapHatBeret
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingHeadHatBeretCap
- type: loadout
id: LoadoutCommandCapMaskGas
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingMaskGasCaptain
- type: loadout
id: LoadoutCommandCapShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- ClothingShoesBootsWinterCap
- type: loadout
id: LoadoutCommandCapItemDrinkFlask
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Captain
items:
- DrinkFlask

View File

@@ -0,0 +1,46 @@
- type: loadout
id: LoadoutCommandCENeckMantle
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefEngineer
items:
- ClothingNeckMantleCE
- type: loadout
id: LoadoutCommandCENeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefEngineer
items:
- ClothingNeckCloakCe
- type: loadout
id: LoadoutCommandCEOuterWinter
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefEngineer
items:
- ClothingOuterWinterCE
- type: loadout
id: LoadoutCommandCEShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefEngineer
items:
- ClothingShoesBootsWinterChiefEngineer

View File

@@ -0,0 +1,68 @@
- type: loadout
id: LoadoutCommandCMONeckMantle
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefMedicalOfficer
items:
- ClothingNeckMantleCMO
- type: loadout
id: LoadoutCommandCMONeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefMedicalOfficer
items:
- ClothingCloakCmo
- type: loadout
id: LoadoutCommandCMOOuterWinter
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefMedicalOfficer
items:
- ClothingOuterWinterCMO
- type: loadout
id: LoadoutCommandCMOOuterLab
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefMedicalOfficer
items:
- ClothingOuterCoatLabCmo
- type: loadout
id: LoadoutCommandCMOHatBeret
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefMedicalOfficer
items:
- ClothingHeadHatBeretCmo
- type: loadout
id: LoadoutCommandCMOShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ChiefMedicalOfficer
items:
- ClothingShoesBootsWinterChiefMedicalOfficer

View File

@@ -0,0 +1,104 @@
- type: loadout
id: LoadoutCommandHOPNeckMantle
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingNeckMantleHOP
- type: loadout
id: LoadoutCommandHOPNeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingNeckCloakHop
- type: loadout
id: LoadoutCommandHOPJumpsuitTurtleneckBoatswain
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingUniformJumpsuitBoatswain
- type: loadout
id: LoadoutCommandHOPJumpsuitMess
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingUniformJumpsuitHoPMess
- type: loadout
id: LoadoutCommandHOPJumpskirtMess
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingUniformJumpskirtHoPMess
- type: loadout
id: LoadoutcommandHOPOuterCoatFormal
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingOuterCoatHoPFormal
- type: loadout
id: LoadoutCommandHOPBackIan
category: Jobs
cost: 4
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingBackpackIan
- type: loadout
id: LoadoutCommandHOPHatCap
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingHeadHatHopcap
- type: loadout
id: LoadoutCommandHOPShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfPersonnel
items:
- ClothingShoesBootsWinterHeadOfPersonel

View File

@@ -0,0 +1,175 @@
- type: loadout
id: LoadoutCommandHOSNeckMantle
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingNeckMantleHOS
- type: loadout
id: LoadoutCommandHOSNeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingNeckCloakHos
- type: loadout
id: LoadoutCommandHOSJumpsuitAlt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpsuitHoSAlt
- type: loadout
id: LoadoutCommandHOSJumpsuitBlue
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpsuitHoSBlue
- type: loadout
id: LoadoutCommandHOSJumpsuitGrey
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpsuitHoSGrey
- type: loadout
id: LoadoutCommandHOSJumpsuitParade
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpsuitHoSParadeMale
- type: loadout
id: LoadoutCommandHOSJumpsuitFormal
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpsuitHosFormal
- type: loadout
id: LoadoutCommandHOSJumpskirtAlt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpskirtHoSAlt
- type: loadout
id: LoadoutCommandHOSJumpskirtParade
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpskirtHoSParadeMale
- type: loadout
id: LoadoutCommandHOSJumpskirtFormal
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingUniformJumpskirtHosFormal
- type: loadout
id: LoadoutCommandHOSOuterWinter
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingOuterWinterHoS
- type: loadout
id: LoadoutCommandHOSOuterTrench
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingOuterCoatHoSTrench
- type: loadout
id: LoadoutCommandHOSHatBeret
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingHeadHatBeretHoS
- type: loadout
id: LoadoutCommandHOSHatHoshat
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingHeadHatHoshat
- type: loadout
id: LoadoutCommandHOSShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- HeadOfSecurity
items:
- ClothingShoesBootsWinterHeadOfSecurity

View File

@@ -0,0 +1,71 @@
# What? This isn't a thing?? :(
# - type: loadout
# id: LoadoutCommandQMNeckMantle
# category: Jobs
# cost: 2
# exclusive: true
# requirements:
# - !type:LoadoutJobRequirement
# jobs:
# - Quartermaster
# items:
# - ClothingNeckMantleQM
- type: loadout
id: LoadoutCommandQMNeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Quartermaster
items:
- ClothingNeckCloakQm
- type: loadout
id: LoadoutCommandQMUniformTurtleneck
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Quartermaster
items:
- ClothingUniformJumpsuitQMTurtleneck
- type: loadout
id: LoadoutCommandQMUniformTurtleneckSkirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Quartermaster
items:
- ClothingUniformJumpskirtQMTurtleneck
- type: loadout
id: LoadoutCommandQMHeadSoft
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Quartermaster
items:
- ClothingHeadHatQMsoft
- type: loadout
id: LoadoutCommandQMShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Quartermaster
items:
- ClothingShoesBootsWinterLogisticsOfficer

View File

@@ -0,0 +1,57 @@
- type: loadout
id: LoadoutCommandRDNeckMantle
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ResearchDirector
items:
- ClothingNeckMantleRD
- type: loadout
id: LoadoutCommandRDNeckCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ResearchDirector
items:
- ClothingNeckCloakRd
- type: loadout
id: LoadoutCommandRDOuterWinter
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- ResearchDirector
items:
- ClothingOuterWinterRD
- type: loadout
id: LoadoutCommandRDOuterMysta
category: Jobs
cost: 2
requirements:
- !type:LoadoutJobRequirement
jobs:
- ResearchDirector
items:
- ClothingOuterCoatRndMysta
- type: loadout
id: LoadoutCommandRDShoesBootsWinter
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- ResearchDirector
items:
- ClothingShoesBootsWinterMystagogue

View File

@@ -0,0 +1,14 @@
- type: loadout
id: LoadoutCargoNeckGoliathCloak
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- SalvageSpecialist
- !type:LoadoutPlaytimeRequirement
tracker: JobSalvageSpecialist
min: 36000 # 10 hours
items:
- ClothingNeckCloakGoliathCloak

View File

@@ -0,0 +1,170 @@
- type: loadout
id: LoadoutEngineeringUniformHazard
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
items:
- ClothingUniformJumpsuitEngineeringHazard
- type: loadout
id: LoadoutEngineeringOuterHazard
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
items:
- ClothingOuterVestHazard
- type: loadout
id: LoadoutEngineeringUniformJumpskirtSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- !type:LoadoutPlaytimeRequirement
tracker: JobAtmosphericTechnician
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobStationEngineer
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Engineering
min: 216000 # 60 hours
items:
- ClothingUniformJumpskirtSeniorEngineer
- type: loadout
id: LoadoutEngineeringUniformJumpsuitSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- !type:LoadoutPlaytimeRequirement
tracker: JobAtmosphericTechnician
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobStationEngineer
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Engineering
min: 216000 # 60 hours
items:
- ClothingUniformJumpsuitSeniorEngineer
- type: loadout
id: LoadoutEngineeringChickenSuit # :)
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- AtmosphericTechnician
items:
- ClothingOuterSuitChicken
- ClothingHeadHatChickenhead
- type: loadout
id: LoadoutEngineeringEyesMeson
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
items:
- ClothingEyesGlassesMeson
- type: loadout
id: LoadoutEngineeringHeadBeret
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
- ChiefEngineer
items:
- ClothingHeadHatBeretEngineering
- type: loadout
id: LoadoutEngineeringHeadHardhatBlue
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
items:
- ClothingHeadHatHardhatBlue
- type: loadout
id: LoadoutEngineeringHeadHardhatOrange
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
items:
- ClothingHeadHatHardhatOrange
- type: loadout
id: LoadoutEngineeringHeadHardhatYellow
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
items:
- ClothingHeadHatHardhatYellow
- type: loadout
id: LoadoutEngineeringHeadHardhatWhite
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
items:
- ClothingHeadHatHardhatWhite
- type: loadout
id: LoadoutEngineeringHeadHardhatRed
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- StationEngineer
- AtmosphericTechnician
items:
- ClothingHeadHatHardhatRed

View File

@@ -0,0 +1,197 @@
- type: loadout
id: LoadoutMedicalGlovesNitrile
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
- Paramedic
- ChiefMedicalOfficer
items:
- ClothingHandsGlovesNitrile
- type: loadout
id: LoadoutMedicalOuterLabcoat
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
- Chemist
items:
- ClothingOuterCoatLab
- type: loadout
id: LoadoutMedicalNeckStethoscope
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
- ChiefMedicalOfficer
items:
- ClothingNeckStethoscope
- type: loadout
id: LoadoutMedicalUniformScrubsBlue
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
items:
- UniformScrubsColorBlue
- type: loadout
id: LoadoutMedicalUniformScrubsGreen
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
items:
- UniformScrubsColorGreen
- type: loadout
id: LoadoutMedicalUniformScrubsPurple
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
items:
- UniformScrubsColorPurple
- type: loadout
id: LoadoutMedicalOuterLabcoatChem
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Chemist
items:
- ClothingOuterCoatLabChem
- type: loadout
id: LoadoutMedicalItemHandLabeler
category: Jobs
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Chemist
items:
- HandLabeler
- type: loadout
id: LoadoutMedicalUniformParamedicJumpsuit
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Paramedic
items:
- ClothingUniformJumpsuitParamedic
- type: loadout
id: LoadoutMedicalUniformParamedicJumpskirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Paramedic
items:
- ClothingUniformJumpskirtParamedic
- type: loadout
id: LoadoutMedicalUniformJumpskirtSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
- !type:LoadoutPlaytimeRequirement
tracker: JobChemist
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobMedicalDoctor
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Medical
min: 216000 # 60 hours
items:
- ClothingUniformJumpskirtSeniorPhysician
- type: loadout
id: LoadoutMedicalUniformJumpsuitSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
- !type:LoadoutPlaytimeRequirement
tracker: JobChemist
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobMedicalDoctor
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Medical
min: 216000 # 60 hours
items:
- ClothingUniformJumpsuitSeniorPhysician
- type: loadout
id: LoadoutMedicalHeadNurse
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
items:
- ClothingHeadNurseHat
- type: loadout
id: LoadoutMedicalHeadBeretSeniorPhysician
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- MedicalDoctor
- !type:LoadoutPlaytimeRequirement
tracker: JobChemist
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobMedicalDoctor
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Medical
min: 216000 # 60 hours
items:
- ClothingHeadHatBeretSeniorPhysician

View File

@@ -0,0 +1,86 @@
- type: loadout
id: LoadoutScienceUniformJumpskirtSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Scientist
- !type:LoadoutDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
items:
- ClothingUniformJumpskirtSeniorResearcher
- type: loadout
id: LoadoutScienceUniformJumpsuitSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Scientist
- !type:LoadoutDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
items:
- ClothingUniformJumpsuitSeniorResearcher
- type: loadout
id: LoadoutScienceOuterCoat
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Scientist
- ResearchAssistant
- ResearchDirector
items:
- ClothingOuterCoatRnd
- type: loadout
id: LoadoutScienceOuterLabcoat
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Scientist
- ResearchAssistant
- ResearchDirector
items:
- ClothingOuterCoatLab
- type: loadout
id: LoadoutScienceOuterLabcoatSeniorResearcher
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Scientist
- !type:LoadoutDepartmentTimeRequirement
department: Epistemics
min: 216000 # 60 hours
items:
- ClothingOuterCoatLabSeniorResearcher
- type: loadout
id: LoadoutScienceHatBeret
category: Jobs
cost: 1
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Scientist
- ResearchAssistant
- ResearchDirector
items:
- ClothingHeadHatBeretRND

View File

@@ -0,0 +1,89 @@
- type: loadout
id: LoadoutSecurityUniformGrey
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- SecurityOfficer
- SecurityCadet
- Warden
items:
- ClothingUniformJumpsuitSecGrey
- type: loadout
id: LoadoutSecurityUniformJumpskirtSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- SecurityOfficer
- !type:LoadoutPlaytimeRequirement
tracker: JobWarden
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobDetective
min: 7200 # 2 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobSecurityOfficer
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Security
min: 216000 # 60 hours
items:
- ClothingUniformJumpskirtSeniorOfficer
- type: loadout
id: LoadoutSecurityUniformJumpsuitSenior
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- SecurityOfficer
- !type:LoadoutPlaytimeRequirement
tracker: JobWarden
min: 21600 # 6 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobDetective
min: 7200 # 2 hours
- !type:LoadoutPlaytimeRequirement
tracker: JobSecurityOfficer
min: 21600 # 6 hours
- !type:LoadoutDepartmentTimeRequirement
department: Security
min: 216000 # 60 hours
items:
- ClothingUniformJumpsuitSeniorOfficer
- type: loadout
id: LoadoutSecurityMaskGasSwat
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Warden
- HeadOfSecurity
items:
- ClothingMaskGasSwat
- type: loadout
id: LoadoutSecurityShoesJackboots
category: Jobs
cost: 1
requirements:
- !type:LoadoutJobRequirement
jobs:
- Detective
- SecurityOfficer
- SecurityCadet
- Warden
- HeadOfSecurity
items:
- ClothingShoesBootsJack

View File

@@ -0,0 +1,183 @@
- type: loadout
id: LoadoutServiceClownOutfitJester
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Clown
items:
- ClothingUniformJumpsuitJester
- ClothingHeadHatJester
- ClothingShoesJester
- type: loadout
id: LoadoutServiceClownOutfitJesterAlt
category: Jobs
cost: 3
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Clown
items:
- ClothingUniformJumpsuitJesterAlt
- ClothingHeadHatJesterAlt
- ClothingShoesJester
- type: loadout
id: LoadoutServiceBartenderUniformPurple
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Bartender
items:
- ClothingUniformJumpsuitBartenderPurple
- type: loadout
id: LoadoutServiceBotanistUniformOveralls
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Botanist
items:
- ClothingUniformOveralls
- type: loadout
id: LoadoutServiceLawyerUniformBlueSuit
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpsuitLawyerBlue
- type: loadout
id: LoadoutServiceLawyerUniformBlueSkirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpskirtLawyerBlue
- type: loadout
id: LoadoutServiceLawyerUniformRedSuit
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpsuitLawyerRed
- type: loadout
id: LoadoutServiceLawyerUniformRedSkirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpskirtLawyerRed
- type: loadout
id: LoadoutServiceLawyerUniformPurpleSuit
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpsuitLawyerPurple
- type: loadout
id: LoadoutServiceLawyerUniformPurpleSkirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpskirtLawyerPurple
- type: loadout
id: LoadoutServiceLawyerUniformGoodSuit
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpsuitLawyerGood
- type: loadout
id: LoadoutServiceLawyerUniformGoodSkirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Lawyer
items:
- ClothingUniformJumpskirtLawyerGood
- type: loadout
id: LoadoutServiceReporterUniformJournalist
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Reporter
items:
- ClothingUniformJumpsuitJournalist
- type: loadout
id: LoadoutServiceReporterUniformDetectivesuit
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Reporter
items:
- ClothingUniformJumpsuitDetective
- type: loadout
id: LoadoutServiceReporterUniformDetectiveskirt
category: Jobs
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Reporter
items:
- ClothingUniformJumpskirtDetective

View File

@@ -0,0 +1,18 @@
# Alphabetically ordered
- type: loadoutCategory
id: Accessories
- type: loadoutCategory
id: Items
- type: loadoutCategory
id: Jobs
- type: loadoutCategory
id: Outer
- type: loadoutCategory
id: Uncategorized
- type: loadoutCategory
id: Uniform

View File

@@ -0,0 +1,13 @@
- type: loadout
id: LoadoutEyesEyepatch
category: Accessories
cost: 1
items:
- ClothingEyesEyepatch
- type: loadout
id: LoadoutEyesBlindfold
category: Accessories
cost: 2
items:
- ClothingEyesBlindfold

View File

@@ -0,0 +1,125 @@
- type: loadout
id: LoadoutHeadBeaverHat
category: Accessories
cost: 2
items:
- ClothingHeadHatBeaverHat
- type: loadout
id: LoadoutHeadTophat
category: Accessories
cost: 2
items:
- ClothingHeadHatTophat
- type: loadout
id: LoadoutHeadHatBluesoft
category: Accessories
cost: 1
items:
- ClothingHeadHatBluesoft
- type: loadout
id: LoadoutHeadHatBluesoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatBluesoftFlipped
- type: loadout
id: LoadoutHeadHatCorpsoft
category: Accessories
cost: 1
items:
- ClothingHeadHatCorpsoft
- type: loadout
id: LoadoutHeadHatCorpsoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatCorpsoftFlipped
- type: loadout
id: LoadoutHeadHatGreensoft
category: Accessories
cost: 1
items:
- ClothingHeadHatGreensoft
- type: loadout
id: LoadoutHeadHatGreensoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatGreensoftFlipped
- type: loadout
id: LoadoutHeadHatGreysoft
category: Accessories
cost: 1
items:
- ClothingHeadHatGreysoft
- type: loadout
id: LoadoutHeadHatGreysoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatGreysoftFlipped
- type: loadout
id: LoadoutHeadHatOrangesoft
category: Accessories
cost: 1
items:
- ClothingHeadHatOrangesoft
- type: loadout
id: LoadoutHeadHatOrangesoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatOrangesoftFlipped
- type: loadout
id: LoadoutHeadHatPurplesoft
category: Accessories
cost: 1
items:
- ClothingHeadHatPurplesoft
- type: loadout
id: LoadoutHeadHatPurplesoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatPurplesoftFlipped
- type: loadout
id: LoadoutHeadHatRedsoft
category: Accessories
cost: 1
items:
- ClothingHeadHatRedsoft
- type: loadout
id: LoadoutHeadHatRedsoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatRedsoftFlipped
- type: loadout
id: LoadoutHeadHatYellowsoft
category: Accessories
cost: 1
items:
- ClothingHeadHatYellowsoft
- type: loadout
id: LoadoutHeadHatYellowsoftFlipped
category: Accessories
cost: 1
items:
- ClothingHeadHatYellowsoftFlipped

View File

@@ -0,0 +1,90 @@
- type: loadout
id: LoadoutItemCig
category: Items
cost: 1
items:
- Cigarette
- type: loadout
id: LoadoutItemCigsGreen
category: Items
cost: 2
items:
- CigPackGreen
- type: loadout
id: LoadoutItemCigsRed
category: Items
cost: 2
items:
- CigPackRed
- type: loadout
id: LoadoutItemCigsBlue
category: Items
cost: 2
items:
- CigPackBlue
- type: loadout
id: LoadoutItemCigsBlack
category: Items
cost: 2
items:
- CigPackBlack
- type: loadout
id: LoadoutItemPAI
category: Items
cost: 3
items:
- PersonalAI
- type: loadout
id: LoadoutItemLighter
category: Items
cost: 2
items:
- Lighter
- type: loadout
id: LoadoutItemLighterCheap
category: Items
cost: 1
items:
- CheapLighter
- type: loadout
id: LoadoutItemMatches
category: Items
cost: 1
items:
- Matchbox
- type: loadout
id: LoadoutItemPlushieSharkBlue
category: Items
cost: 2
items:
- PlushieSharkBlue
- type: loadout
id: LoadoutItemPlushieSharkPink
category: Items
cost: 2
items:
- PlushieSharkPink
- type: loadout
id: LoadoutItemPlushieSharkGrey
category: Items
cost: 2
items:
- PlushieSharkGrey
- type: loadout
id: LoadoutItemPlushieCarp
category: Items
cost: 2
items:
- PlushieCarp

View File

@@ -0,0 +1,27 @@
- type: loadout
id: LoadoutNeckScarfStripedRed
category: Accessories
cost: 1
items:
- ClothingNeckScarfStripedRed
- type: loadout
id: LoadoutNeckScarfStripedBlue
category: Accessories
cost: 1
items:
- ClothingNeckScarfStripedBlue
- type: loadout
id: LoadoutNeckScarfStripedGreen
category: Accessories
cost: 1
items:
- ClothingNeckScarfStripedGreen
- type: loadout
id: LoadoutNeckScarfStripedZebra
category: Accessories
cost: 1
items:
- ClothingNeckScarfStripedZebra

View File

@@ -0,0 +1,34 @@
- type: loadout
id: LoadoutOuterGhostSheet
category: Outer
cost: 2
items:
- ClothingOuterGhostSheet
- type: loadout
id: LoadoutOuterCoatBomberjacket
category: Outer
cost: 3
items:
- ClothingOuterCoatBomber
- type: loadout
id: LoadoutOuterCoatHoodieBlack
category: Outer
cost: 2
items:
- ClothingOuterHoodieBlack
- type: loadout
id: LoadoutOuterCoatHoodieGrey
category: Outer
cost: 2
items:
- ClothingOuterHoodieGrey
- type: loadout
id: LoadoutOuterCoatWinterCoat
category: Outer
cost: 3
items:
- ClothingOuterWinterCoat

View File

@@ -0,0 +1,98 @@
# Colored
- type: loadout
id: LoadoutShoesBlack
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorBlack
- type: loadout
id: LoadoutShoesBlue
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorBlue
- type: loadout
id: LoadoutShoesBrown
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorBrown
- type: loadout
id: LoadoutShoesGreen
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorGreen
- type: loadout
id: LoadoutShoesOrange
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorOrange
- type: loadout
id: LoadoutShoesPurple
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorPurple
- type: loadout
id: LoadoutShoesRed
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorRed
- type: loadout
id: LoadoutShoesWhite
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorWhite
- type: loadout
id: LoadoutShoesYellow
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesColorYellow
# Miscellaneous
- type: loadout
id: LoadoutShoesSlippersDuck
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoeSlippersDuck
- type: loadout
id: LoadoutShoesLeather
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesLeather
- type: loadout
id: LoadoutShoesMiscWhite
category: Accessories
cost: 1
exclusive: true
items:
- ClothingShoesMiscWhite

View File

@@ -0,0 +1,261 @@
- type: loadout
id: LoadoutUniformAncientJumpsuit
category: Uniform
cost: 2
exclusive: true
requirements:
- !type:LoadoutJobRequirement
jobs:
- Passenger
items:
- ClothingUniformJumpsuitAncient
# Colored jumpsuits
- type: loadout
id: LoadoutUniformJumpsuitColorBlack
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorBlack
- type: loadout
id: LoadoutUniformJumpskirtColorBlack
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorBlack
- type: loadout
id: LoadoutUniformJumpsuitColorBlue
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorBlue
- type: loadout
id: LoadoutUniformJumpskirtColorBlue
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorBlue
- type: loadout
id: LoadoutUniformJumpsuitColorGreen
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorGreen
- type: loadout
id: LoadoutUniformJumpskirtColorGreen
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorGreen
- type: loadout
id: LoadoutUniformJumpsuitColorOrange
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorOrange
- type: loadout
id: LoadoutUniformJumpskirtColorOrange
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorOrange
- type: loadout
id: LoadoutUniformJumpsuitColorPink
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorPink
- type: loadout
id: LoadoutUniformJumpskirtColorPink
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorPink
- type: loadout
id: LoadoutUniformJumpsuitColorRed
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorRed
- type: loadout
id: LoadoutUniformJumpskirtColorRed
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorRed
- type: loadout
id: LoadoutUniformJumpsuitColorWhite
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorWhite
- type: loadout
id: LoadoutUniformJumpskirtColorWhite
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorWhite
- type: loadout
id: LoadoutUniformJumpsuitColorYellow
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorYellow
- type: loadout
id: LoadoutUniformJumpskirtColorYellow
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorYellow
- type: loadout
id: LoadoutUniformJumpsuitColorDarkBlue
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorDarkBlue
- type: loadout
id: LoadoutUniformJumpskirtColorDarkBlue
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorDarkBlue
- type: loadout
id: LoadoutUniformJumpsuitColorTeal
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorTeal
- type: loadout
id: LoadoutUniformJumpskirtColorTeal
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorTeal
- type: loadout
id: LoadoutUniformJumpsuitColorPurple
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorPurple
- type: loadout
id: LoadoutUniformJumpskirtColorPurple
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorPurple
- type: loadout
id: LoadoutUniformJumpsuitColorDarkGreen
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorDarkGreen
- type: loadout
id: LoadoutUniformJumpskirtColorDarkGreen
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorDarkGreen
- type: loadout
id: LoadoutUniformJumpsuitColorLightBrown
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorLightBrown
- type: loadout
id: LoadoutUniformJumpskirtColorLightBrown
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorLightBrown
- type: loadout
id: LoadoutUniformJumpsuitColorBrown
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorBrown
- type: loadout
id: LoadoutUniformJumpskirtColorBrown
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorBrown
- type: loadout
id: LoadoutUniformJumpsuitColorMaroon
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpsuitColorMaroon
- type: loadout
id: LoadoutUniformJumpskirtColorMaroon
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformJumpskirtColorMaroon
- type: loadout
id: LoadoutUniformJumpsuitColorRainbow
category: Uniform
cost: 2
exclusive: true
items:
- ClothingUniformColorRainbow

View File

@@ -19,7 +19,7 @@
coefficient: 0.3
- type: entity
parent: ClothingOuterStorageBase
parent: ClothingOuterStorageToggleableBase
id: ClothingOuterCoatRndMysta
name: mystagogue lab coat # Delta V - Mystagogue new coat
description: Similar to the standard model but with more purple and gold. # Delta V - Mystagogue new coat
@@ -32,3 +32,5 @@
modifiers:
coefficients:
Caustic: 0.75
- type: ToggleableClothing
clothingPrototype: ClothingHeadHoodMysta

View File

@@ -43,7 +43,6 @@
- type: startingGear
id: ChiefEngineerGear
equipment:
head: ClothingHeadHatHardhatWhite
jumpsuit: ClothingUniformJumpsuitChiefEngineer
back: ClothingBackpackChiefEngineerFilled
shoes: ClothingShoesColorBrown

View File

@@ -21,11 +21,9 @@
- type: startingGear
id: StationEngineerGear
equipment:
head: ClothingHeadHatHardhatYellow
jumpsuit: ClothingUniformJumpsuitEngineering
back: ClothingBackpackEngineeringFilled
shoes: ClothingShoesBootsWork
outerClothing: ClothingOuterVestHazard
id: EngineerPDA
eyes: ClothingEyesGlassesMeson
belt: ClothingBeltUtilityEngineering

View File

@@ -21,11 +21,9 @@
jumpsuit: ClothingUniformJumpsuitChemistry
back: ClothingBackpackChemistryFilled
shoes: ClothingShoesColorWhite
outerClothing: ClothingOuterCoatLabChem
id: ChemistryPDA
ears: ClothingHeadsetMedical
belt: ChemBag
pocket1: HandLabeler
# the purple glasses?
innerClothingSkirt: ClothingUniformJumpskirtChemistry
satchel: ClothingBackpackSatchelChemistryFilled

View File

@@ -51,7 +51,6 @@
jumpsuit: ClothingUniformJumpsuitCMO
back: ClothingBackpackCMOFilled
shoes: ClothingShoesColorBrown
outerClothing: ClothingOuterCoatLabCmo
id: CMOPDA
ears: ClothingHeadsetCMO
belt: ClothingBeltMedicalFilled

View File

@@ -23,7 +23,6 @@
jumpsuit: ClothingUniformJumpsuitMedicalDoctor
back: ClothingBackpackMedicalFilled
shoes: ClothingShoesColorWhite
outerClothing: ClothingOuterCoatLab
id: MedicalPDA
ears: ClothingHeadsetMedical
belt: ClothingBeltMedicalFilled

View File

@@ -42,8 +42,6 @@
jumpsuit: ClothingUniformJumpsuitResearchDirector
back: ClothingBackpackResearchDirectorFilled
shoes: ClothingShoesColorBrown
head: ClothingHeadHoodMysta # DeltaV - Mystagogue new hat
outerClothing: ClothingOuterCoatRndMysta # DeltaV - Mystagogue new coat
id: RnDPDA
ears: ClothingHeadsetRD
belt: BibleMystagogue # Nyanotrasen - Mystagogue book for their Ifrit

View File

@@ -51,9 +51,7 @@
jumpsuit: ClothingUniformJumpsuitHoS
back: ClothingBackpackHOSFilled
shoes: ClothingShoesBootsCombatFilled
outerClothing: ClothingOuterCoatHoSTrench
eyes: ClothingEyesGlassesSunglasses
head: ClothingHeadHatBeretHoS
id: HoSPDA
gloves: ClothingHandsGlovesCombat
ears: ClothingHeadsetAltSecurity